Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,101 @@
/**
* 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);
});