Files
TI-FCYT-URBAN/bot-wsp/src/scheduling/scheduling.service.ts
T
Valentin Romero 25d4ba6cc0 add bot wsp
2026-06-30 22:04:56 -03:00

156 lines
5.7 KiB
TypeScript

import { Injectable, Logger } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { InvoicesService } from '~/invoices/invoices.service';
import { PrismaService } from '~/prisma/prisma.service';
import { SessionsService } from '~/sessions/sessions.service';
import { SystemConfigService } from '~/common/system-config.service';
@Injectable()
export class SchedulingService {
private readonly logger = new Logger(SchedulingService.name);
private lastSessionCleanup: number = 0;
private lastTokenCleanup: number = 0;
constructor(
private readonly sessions: SessionsService,
private readonly invoices: InvoicesService,
private readonly prisma: PrismaService,
private readonly systemConfig: SystemConfigService,
) { }
// Desactiva sesiones vencidas cada 30 minutos (configurable)
@Cron(CronExpression.EVERY_MINUTE, { name: 'deactivateExpiredSessions' })
async deactivateExpiredSessionsJob() {
// Verificar si debe ejecutarse según la configuración
const intervalMinutes = this.systemConfig.getSessionCleanupInterval();
const now = Date.now();
if (now - this.lastSessionCleanup < intervalMinutes * 60 * 1000) {
return; // Aún no es tiempo de ejecutar
}
this.lastSessionCleanup = now;
try {
const count = await this.sessions.deactivateExpiredActives();
if (count > 0) {
this.logger.log(`Desactivadas ${count} sesiones vencidas.`);
} else {
this.logger.verbose('No había sesiones vencidas para desactivar.');
}
} catch (err: any) {
this.logger.error('Error desactivando sesiones vencidas', err?.stack || err);
}
}
// Todos los meses se deben de generar las facturas y enlaces de pagos
// para que el usuario pueda pagar la clase que este inscripto y activa.
// Se ejecuta diariamente y verifica si es el día configurado del mes
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT, { name: 'generateMonthlyInvoices' })
async generateMonthlyInvoicesAndPaymentLinksJob() {
const today = new Date().getDate();
const configuredDay = this.systemConfig.getInvoiceGenerationDay();
// Solo ejecutar si es el día configurado del mes
if (today !== configuredDay) {
return;
}
let totalProcessed = 0;
let totalSuccess = 0;
let totalFailed = 0;
const errors: Array<{ sessionId: string; assistantId: string; error: string }> = [];
try {
const sessions = await this.sessions.findAllSessionInThisMonth();
this.logger.log(`Iniciando generación de facturas para ${sessions.length} sesiones`);
for (const session of sessions) {
const { id: sessionId, assistants } = session;
for (const assistant of assistants) {
totalProcessed++;
try {
await this.invoices.newInvoiceForSession(
assistant.id,
sessionId,
new Date()
);
totalSuccess++;
this.logger.verbose(
`✓ Factura creada: session=${sessionId}, assistant=${assistant.id}`
);
} catch (invoiceErr: any) {
totalFailed++;
const errorMsg = invoiceErr?.message || String(invoiceErr);
errors.push({
sessionId,
assistantId: assistant.id,
error: errorMsg
});
this.logger.warn(
`✗ Error creando factura: session=${sessionId}, assistant=${assistant.id}, error=${errorMsg}`
);
}
}
}
// Resumen final
this.logger.log(
`Generación de facturas completada: ` +
`Total=${totalProcessed}, Éxito=${totalSuccess}, Fallos=${totalFailed}`
);
if (errors.length > 0) {
this.logger.error(
`Se encontraron ${errors.length} errores durante la generación:`,
JSON.stringify(errors, null, 2)
);
}
} catch (err: any) {
this.logger.error(
'Error crítico generando facturas mensuales y enlaces de pago',
err?.stack || err
);
}
}
// Limpia tokens expirados cada hora (configurable)
@Cron(CronExpression.EVERY_MINUTE, { name: 'cleanupExpiredTokens' })
async cleanupExpiredTokensJob() {
// Verificar si debe ejecutarse según la configuración
const intervalMinutes = this.systemConfig.getTokenCleanupInterval();
const now = Date.now();
if (now - this.lastTokenCleanup < intervalMinutes * 60 * 1000) {
return; // Aún no es tiempo de ejecutar
}
this.lastTokenCleanup = now;
try {
const result = await this.prisma.token.deleteMany({
where: {
expiresAt: {
lt: new Date(), // Eliminar los expirados
},
},
});
if (result.count > 0) {
this.logger.log(`Eliminados ${result.count} tokens expirados`);
} else {
this.logger.verbose('No había tokens expirados para eliminar.');
}
} catch (err: any) {
this.logger.error('Error limpiando tokens expirados', err?.stack || err);
}
}
}