105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
/**
|
|
* Cron diario 8:30 AM (America/Mexico_City) que envía emails de:
|
|
* - Alertas fiscales nuevas (Option B — una sola vez por alerta).
|
|
* - Recordatorios próximos a vencer en ventanas 3d / 1d / 0d.
|
|
*
|
|
* Por-tenant try/catch: un fallo en un tenant no bloquea al resto.
|
|
*/
|
|
import cron from 'node-cron';
|
|
import { prisma, tenantDb } from '../config/database.js';
|
|
import { processNewAlertas, processProximosRecordatorios } from '../services/notifications.service.js';
|
|
|
|
const SCHEDULE = '30 8 * * *'; // 08:30 AM diario
|
|
|
|
let task: ReturnType<typeof cron.schedule> | null = null;
|
|
|
|
/** Ejecuta ambos procesos para UN tenant. Exportado para disparo manual. */
|
|
export async function runNotificationsForTenant(tenantId: string): Promise<{
|
|
alertasNuevas: number;
|
|
recordatoriosEnviados: number;
|
|
}> {
|
|
const tenant = await prisma.tenant.findUnique({
|
|
where: { id: tenantId },
|
|
select: { id: true, nombre: true, rfc: true, active: true, databaseName: true },
|
|
});
|
|
if (!tenant || !tenant.active) {
|
|
return { alertasNuevas: 0, recordatoriosEnviados: 0 };
|
|
}
|
|
|
|
const pool = await tenantDb.getPool(tenantId, tenant.databaseName);
|
|
const ctx = { rfc: tenant.rfc, nombre: tenant.nombre };
|
|
|
|
const [alertasResult, recordResult] = await Promise.all([
|
|
processNewAlertas(pool, tenantId, ctx).catch(err => {
|
|
console.error(`[Notifications] Alertas (${tenant.rfc}) fallo:`, err.message || err);
|
|
return { contribuyentes: 0, nuevasTotal: 0 };
|
|
}),
|
|
processProximosRecordatorios(pool, tenantId, ctx).catch(err => {
|
|
console.error(`[Notifications] Recordatorios (${tenant.rfc}) fallo:`, err.message || err);
|
|
return { enviados: 0 };
|
|
}),
|
|
]);
|
|
|
|
if (alertasResult.nuevasTotal > 0 || recordResult.enviados > 0) {
|
|
console.log(`[Notifications] ${tenant.rfc}: ${alertasResult.nuevasTotal} alertas nuevas, ${recordResult.enviados} recordatorios`);
|
|
}
|
|
|
|
return {
|
|
alertasNuevas: alertasResult.nuevasTotal,
|
|
recordatoriosEnviados: recordResult.enviados,
|
|
};
|
|
}
|
|
|
|
/** Itera todos los tenants activos. */
|
|
export async function runNotifications(): Promise<{
|
|
tenants: number;
|
|
alertasNuevas: number;
|
|
recordatoriosEnviados: number;
|
|
}> {
|
|
const tenants = await prisma.tenant.findMany({
|
|
where: { active: true },
|
|
select: { id: true, rfc: true },
|
|
});
|
|
|
|
let alertasNuevas = 0;
|
|
let recordatoriosEnviados = 0;
|
|
for (const t of tenants) {
|
|
try {
|
|
const r = await runNotificationsForTenant(t.id);
|
|
alertasNuevas += r.alertasNuevas;
|
|
recordatoriosEnviados += r.recordatoriosEnviados;
|
|
} catch (err: any) {
|
|
console.error(`[Notifications] Tenant ${t.rfc} fallo completo:`, err.message || err);
|
|
}
|
|
}
|
|
return { tenants: tenants.length, alertasNuevas, recordatoriosEnviados };
|
|
}
|
|
|
|
export function startNotificationsJob(): void {
|
|
if (task) {
|
|
console.warn('[Notifications Cron] Ya iniciado');
|
|
return;
|
|
}
|
|
task = cron.schedule(SCHEDULE, async () => {
|
|
try {
|
|
const result = await runNotifications();
|
|
console.log(
|
|
`[Notifications Cron] ${result.tenants} tenants — ` +
|
|
`${result.alertasNuevas} alertas nuevas, ${result.recordatoriosEnviados} recordatorios`,
|
|
);
|
|
} catch (err: any) {
|
|
console.error('[Notifications Cron] Error general:', err.message || err);
|
|
}
|
|
}, {
|
|
timezone: 'America/Mexico_City',
|
|
});
|
|
console.log(`[Notifications Cron] Programado: ${SCHEDULE} (08:30 AM diario America/Mexico_City)`);
|
|
}
|
|
|
|
export function stopNotificationsJob(): void {
|
|
if (task) {
|
|
task.stop();
|
|
task = null;
|
|
}
|
|
}
|