UNPKG

skailan-core

Version:

Servicio de autenticación y multitenancy para Skailan.

305 lines 9.87 kB
import moment from 'moment'; import cron from 'node-cron'; export class TrialService { static instance; prisma; constructor(prisma) { this.prisma = prisma; this.initializeCronJobs(); } static getInstance(prisma) { if (!TrialService.instance) { TrialService.instance = new TrialService(prisma); } return TrialService.instance; } /** * Inicializa los trabajos cron para monitorear trials */ initializeCronJobs() { // Verificar trials expirados diariamente a las 2 AM cron.schedule('0 2 * * *', () => { this.checkExpiredTrials(); }); // Enviar advertencias 3 días antes del vencimiento cron.schedule('0 10 * * *', () => { this.sendTrialWarnings(); }); // Verificar organizaciones bloqueadas por trial expirado cron.schedule('0 */6 * * *', () => { this.blockExpiredTrials(); }); } /** * Inicia el trial para una organización */ async startTrial(organizationId, trialDays = 14) { const startDate = new Date(); const endDate = moment().add(trialDays, 'days').toDate(); await this.prisma.organization.update({ where: { id: organizationId }, data: { trialStart: startDate, trialEnd: endDate, isTrialActive: true, paymentStatus: 'TRIAL', trialDays, }, }); await this.logTrialAction(organizationId, 'STARTED', { trialDays, startDate, endDate, }); } /** * Obtiene el estado actual del trial */ async getTrialStatus(organizationId) { const organization = await this.prisma.organization.findUnique({ where: { id: organizationId }, }); if (!organization || !organization.trialEnd) { return { isActive: false, daysRemaining: 0, endDate: null, isExpired: true, isWarningSent: false, canExtend: false, }; } const now = new Date(); const endDate = organization.trialEnd; const isExpired = now > endDate; const daysRemaining = Math.max(0, moment(endDate).diff(now, 'days')); // Verificar si ya se envió una advertencia const warningLog = await this.prisma.trialLog.findFirst({ where: { organizationId, action: 'WARNING_SENT', }, orderBy: { createdAt: 'desc' }, }); const isWarningSent = !!warningLog; return { isActive: organization.isTrialActive && !isExpired, daysRemaining, endDate, isExpired, isWarningSent, canExtend: daysRemaining <= 3 && !isExpired, }; } /** * Extiende el trial de una organización */ async extendTrial(request) { const { organizationId, daysToExtend, reason, requestedBy } = request; const organization = await this.prisma.organization.findUnique({ where: { id: organizationId }, }); if (!organization) { throw new Error('Organización no encontrada'); } if (!organization.trialEnd) { throw new Error('La organización no tiene un trial activo'); } const newEndDate = moment(organization.trialEnd).add(daysToExtend, 'days').toDate(); await this.prisma.organization.update({ where: { id: organizationId }, data: { trialEnd: newEndDate, trialDays: organization.trialDays + daysToExtend, }, }); await this.logTrialAction(organizationId, 'EXTENDED', { daysExtended: daysToExtend, newEndDate, reason, requestedBy, }); } /** * Convierte un trial a suscripción pagada */ async convertTrialToPaid(organizationId, planId) { await this.prisma.organization.update({ where: { id: organizationId }, data: { isTrialActive: false, trialEnd: null, paymentStatus: 'CONFIRMED', }, }); await this.logTrialAction(organizationId, 'CONVERTED', { planId, convertedAt: new Date(), }); } /** * Verifica y bloquea organizaciones con trial expirado */ async blockExpiredTrials() { const expiredOrganizations = await this.prisma.organization.findMany({ where: { trialEnd: { lt: new Date(), }, isTrialActive: true, isBlocked: false, }, }); for (const org of expiredOrganizations) { await this.prisma.organization.update({ where: { id: org.id }, data: { isBlocked: true, isTrialActive: false, paymentStatus: 'FAILED', }, }); await this.logTrialAction(org.id, 'EXPIRED', { expiredAt: new Date(), }); } } /** * Envía advertencias a organizaciones próximas a expirar */ async sendTrialWarnings() { const threeDaysFromNow = moment().add(3, 'days').toDate(); const twoDaysFromNow = moment().add(2, 'days').toDate(); const organizationsToWarn = await this.prisma.organization.findMany({ where: { trialEnd: { gte: twoDaysFromNow, lte: threeDaysFromNow, }, isTrialActive: true, isBlocked: false, }, }); for (const org of organizationsToWarn) { // Verificar si ya se envió una advertencia const existingWarning = await this.prisma.trialLog.findFirst({ where: { organizationId: org.id, action: 'WARNING_SENT', }, }); if (!existingWarning) { await this.logTrialAction(org.id, 'WARNING_SENT', { warningSentAt: new Date(), daysRemaining: moment(org.trialEnd).diff(new Date(), 'days'), }); // Aquí se podría integrar con un servicio de notificaciones console.log(`Advertencia enviada a organización ${org.name} (${org.id})`); } } } /** * Verifica trials expirados y los marca como expirados */ async checkExpiredTrials() { const expiredOrganizations = await this.prisma.organization.findMany({ where: { trialEnd: { lt: new Date(), }, isTrialActive: true, }, }); for (const org of expiredOrganizations) { await this.prisma.organization.update({ where: { id: org.id }, data: { isTrialActive: false, }, }); await this.logTrialAction(org.id, 'EXPIRED', { expiredAt: new Date(), }); } } /** * Registra una acción del trial */ async logTrialAction(organizationId, action, details) { await this.prisma.trialLog.create({ data: { organizationId, action, details, }, }); } /** * Obtiene el historial de acciones del trial */ async getTrialHistory(organizationId) { return await this.prisma.trialLog.findMany({ where: { organizationId }, orderBy: { createdAt: 'desc' }, }); } /** * Verifica si una organización puede acceder al sistema */ async canAccess(organizationId) { const organization = await this.prisma.organization.findUnique({ where: { id: organizationId }, }); if (!organization) { return false; } // Si está bloqueada, no puede acceder if (organization.isBlocked) { return false; } // Si tiene trial activo, puede acceder if (organization.isTrialActive && organization.trialEnd && new Date() <= organization.trialEnd) { return true; } // Si tiene suscripción activa, puede acceder const activeSubscription = await this.prisma.subscription.findFirst({ where: { organizationId, status: 'ACTIVE', }, }); return !!activeSubscription; } /** * Obtiene estadísticas de trials */ async getTrialStats() { const totalOrganizations = await this.prisma.organization.count(); const activeTrials = await this.prisma.organization.count({ where: { isTrialActive: true, isBlocked: false, }, }); const expiredTrials = await this.prisma.organization.count({ where: { isTrialActive: false, trialEnd: { lt: new Date(), }, }, }); const blockedOrganizations = await this.prisma.organization.count({ where: { isBlocked: true, }, }); return { totalOrganizations, activeTrials, expiredTrials, blockedOrganizations, }; } } //# sourceMappingURL=TrialService.js.map