102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
/**
|
|
* Backfill de métricas mensuales pre-calculadas (Tanda A hot/cold).
|
|
*
|
|
* Itera todos los tenants activos, sus contribuyentes, y popula la tabla
|
|
* `metricas_mensuales` con los agregados de años pasados (desde el CFDI más
|
|
* antiguo hasta el año actual - 1). El año actual queda on-the-fly.
|
|
*
|
|
* Idempotente: usa upsert — re-correrlo no duplica filas, recalcula valores.
|
|
*
|
|
* Uso:
|
|
* pnpm --filter @horux/api exec tsx scripts/backfill-metricas.ts # ejecuta
|
|
* pnpm --filter @horux/api exec tsx scripts/backfill-metricas.ts --dry # dry-run
|
|
*
|
|
* Opciones via env:
|
|
* BACKFILL_DESDE_ANIO=2023 # limita el rango inferior
|
|
* BACKFILL_HASTA_ANIO=2024 # default: año actual - 1
|
|
* BACKFILL_TENANT=<uuid> # procesa solo un tenant
|
|
*/
|
|
import { prisma } from '../src/config/database.js';
|
|
import { backfillTenant } from '../src/services/metricas-compute.service.js';
|
|
|
|
const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run');
|
|
const TENANT_FILTER = process.env.BACKFILL_TENANT || null;
|
|
const DESDE_ANIO = process.env.BACKFILL_DESDE_ANIO ? parseInt(process.env.BACKFILL_DESDE_ANIO, 10) : undefined;
|
|
const HASTA_ANIO = process.env.BACKFILL_HASTA_ANIO ? parseInt(process.env.BACKFILL_HASTA_ANIO, 10) : undefined;
|
|
|
|
async function main() {
|
|
console.log(`=== Backfill metricas_mensuales ${DRY_RUN ? '(DRY RUN)' : ''} ===\n`);
|
|
if (DESDE_ANIO) console.log(`Desde año: ${DESDE_ANIO}`);
|
|
if (HASTA_ANIO) console.log(`Hasta año: ${HASTA_ANIO}`);
|
|
if (TENANT_FILTER) console.log(`Tenant filtro: ${TENANT_FILTER}`);
|
|
console.log();
|
|
|
|
const tenants = await prisma.tenant.findMany({
|
|
where: {
|
|
active: true,
|
|
...(TENANT_FILTER ? { id: TENANT_FILTER } : {}),
|
|
},
|
|
select: { id: true, rfc: true, nombre: true },
|
|
orderBy: { rfc: 'asc' },
|
|
});
|
|
|
|
console.log(`Tenants activos: ${tenants.length}\n`);
|
|
|
|
let totalContribs = 0;
|
|
let totalMeses = 0;
|
|
let totalFilas = 0;
|
|
let totalErrores = 0;
|
|
|
|
for (const t of tenants) {
|
|
process.stdout.write(`[${t.rfc}] ${t.nombre} ... `);
|
|
try {
|
|
const r = await backfillTenant(t.id, {
|
|
dryRun: DRY_RUN,
|
|
desdeAnio: DESDE_ANIO,
|
|
hastaAnio: HASTA_ANIO,
|
|
});
|
|
if (r.contribuyentesProcesados === 0) {
|
|
console.log('sin contribuyentes (skip)');
|
|
} else {
|
|
console.log(
|
|
`${r.contribuyentesProcesados} contribs, ${r.mesesProcesados} meses, ` +
|
|
`${r.filasEscritas} filas${r.errores.length > 0 ? `, ${r.errores.length} errores` : ''}`,
|
|
);
|
|
if (r.errores.length > 0 && r.errores.length <= 5) {
|
|
for (const e of r.errores) {
|
|
console.log(` ERR (${e.anio}-${String(e.mes).padStart(2, '0')}): ${e.error}`);
|
|
}
|
|
} else if (r.errores.length > 5) {
|
|
console.log(` (${r.errores.length} errores — los primeros 3):`);
|
|
for (const e of r.errores.slice(0, 3)) {
|
|
console.log(` ERR (${e.anio}-${String(e.mes).padStart(2, '0')}): ${e.error}`);
|
|
}
|
|
}
|
|
}
|
|
totalContribs += r.contribuyentesProcesados;
|
|
totalMeses += r.mesesProcesados;
|
|
totalFilas += r.filasEscritas;
|
|
totalErrores += r.errores.length;
|
|
} catch (err: any) {
|
|
console.log(`FATAL: ${err?.message || err}`);
|
|
totalErrores++;
|
|
}
|
|
}
|
|
|
|
console.log(`\n=== Resumen ===`);
|
|
console.log(` Tenants procesados: ${tenants.length}`);
|
|
console.log(` Contribuyentes: ${totalContribs}`);
|
|
console.log(` (Contribuyente, mes): ${totalMeses}`);
|
|
console.log(` Filas metricas_mensuales: ${totalFilas}${DRY_RUN ? ' (NO escritas)' : ''}`);
|
|
if (totalErrores > 0) console.log(` Errores: ${totalErrores}`);
|
|
|
|
await prisma.$disconnect();
|
|
process.exit(totalErrores > 0 ? 1 : 0);
|
|
}
|
|
|
|
main().catch(async (err) => {
|
|
console.error('Fatal:', err);
|
|
await prisma.$disconnect().catch(() => {});
|
|
process.exit(1);
|
|
});
|