Files
HoruxDespachos/apps/api/scripts/validate-gastos.ts
2026-04-27 22:09:36 -06:00

116 lines
5.4 KiB
TypeScript

/**
* Compara Gastos del Dashboard vs Drill-down para un mes/contribuyente.
* Identifica discrepancias y rompe el detalle por lado (factura/pago/NC).
*
* Uso: tsx scripts/validate-gastos.ts <tenantRfc> <entidadId> <añoMes>
*/
import { prisma, tenantDb } from '../src/config/database.js';
import { calcularEgresosPorRegimen } from '../src/services/dashboard.service.js';
const tenantRfcArg = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG';
const contribuyenteId = process.argv[3] || 'd745a915-6a23-4818-944b-a7e1e18e536a';
const yearMonth = process.argv[4] || '2025-02';
async function main() {
const tenant = await prisma.tenant.findFirst({
where: { rfc: tenantRfcArg, active: true },
select: { id: true, rfc: true, databaseName: true },
});
if (!tenant) { console.error('Tenant not found'); process.exit(1); }
const pool = await tenantDb.getPool(tenant.id, tenant.databaseName);
const [anio, mes] = yearMonth.split('-').map(Number);
const lastDay = new Date(anio, mes, 0).getDate();
const fi = `${yearMonth}-01`;
const ff = `${yearMonth}-${String(lastDay).padStart(2, '0')}`;
console.log(`\n=== Contribuyente ${contribuyenteId}${fi} a ${ff} ===\n`);
// 1. Dashboard (calcularEgresosPorRegimen)
const dashboard = await calcularEgresosPorRegimen(
pool, tenant.id, fi, ff, undefined, undefined, false, contribuyenteId,
);
console.log('DASHBOARD calcularEgresosPorRegimen:');
console.log(` total: ${dashboard.total.toFixed(2)}`);
for (const r of dashboard.porRegimen) {
console.log(` ${r.regimenClave} (${r.regimenDescripcion}): ${r.monto.toFixed(2)}`);
}
// 2. Drill-down query (simulated — bucket=gastos uniforme)
const IMP_TRAS = `COALESCE(iva_traslado_mxn,0) + COALESCE(ieps_traslado_mxn,0) + COALESCE(impuestos_locales_trasladado_mxn,0)`;
const IMP_TRAS_PAGO = `COALESCE(iva_traslado_pago_mxn,0) + COALESCE(ieps_traslado_pago_mxn,0)`;
const EXCL_MONTO = `COALESCE((SELECT SUM(COALESCE(cc.importe_mxn,0) - COALESCE(cc.descuento_mxn,0)) FROM cfdi_conceptos cc WHERE cc.cfdi_id = cfdis.id AND cc.clave_prod_serv IN ('84121603','93161608','85101501','85121800')), 0)`;
// bucket=gastos: RECIBIDO I PUE + RECIBIDO P + RECIBIDO E PUE (excl 07)
// Sumamos tomando en cuenta el signo (E resta)
const { rows: drillRows } = await pool.query(
`SELECT
type, tipo_comprobante, metodo_pago,
COALESCE(cfdi_tipo_relacion, '') AS tipo_rel,
COUNT(*)::int AS n,
SUM(total_mxn) AS total_bruto,
SUM(COALESCE(total_mxn,0) - (${IMP_TRAS}) - (${EXCL_MONTO})) AS total_neto,
SUM(COALESCE(monto_pago_mxn,0) - (${IMP_TRAS_PAGO})) AS pago_neto
FROM cfdis
WHERE (
(type = 'RECIBIDO' AND tipo_comprobante = 'I' AND metodo_pago = 'PUE'
AND COALESCE(cfdi_tipo_relacion, '') <> '07')
OR (type = 'RECIBIDO' AND tipo_comprobante = 'P')
OR (type = 'RECIBIDO' AND tipo_comprobante = 'E' AND metodo_pago = 'PUE'
AND COALESCE(cfdi_tipo_relacion, '') <> '07')
)
AND regimen_fiscal_receptor IN ('605','606','612','621','625','626','601','603','607','608','610','611','614','615','620','622','623','624')
AND status NOT IN ('Cancelado','0')
AND ((tipo_comprobante='P' AND fecha_pago_p >= $1::date AND fecha_pago_p < ($2::date + interval '1 day'))
OR (tipo_comprobante!='P' AND fecha_emision >= $1::date AND fecha_emision < ($2::date + interval '1 day')))
AND contribuyente_id = $3
GROUP BY type, tipo_comprobante, metodo_pago, tipo_rel
ORDER BY tipo_comprobante, metodo_pago`,
[fi, ff, contribuyenteId],
);
console.log(`\nDRILL-DOWN bucket=gastos (filas del drill por bucket):`);
let drillSumaFacturas = 0, drillSumaPagos = 0, drillSumaNC = 0;
for (const r of drillRows) {
const tc = r.tipo_comprobante;
const valor = tc === 'P' ? Number(r.pago_neto) : Number(r.total_neto);
console.log(` ${r.type} ${tc} ${r.metodo_pago || '-'} rel=${r.tipo_rel || '-'} n=${r.n} total_bruto=${Number(r.total_bruto).toFixed(2)} valor_neto=${valor.toFixed(2)}`);
if (tc === 'I') drillSumaFacturas += valor;
else if (tc === 'P') drillSumaPagos += valor;
else if (tc === 'E') drillSumaNC += valor;
}
const drillTotal = drillSumaFacturas + drillSumaPagos - drillSumaNC;
console.log(` → facturas=${drillSumaFacturas.toFixed(2)} pagos=${drillSumaPagos.toFixed(2)} NC=${drillSumaNC.toFixed(2)}`);
console.log(` → drill total = ${drillTotal.toFixed(2)}`);
// 3. Comparación
const delta = dashboard.total - drillTotal;
console.log(`\n=== COMPARATIVA ===`);
console.log(` Dashboard: ${dashboard.total.toFixed(2)}`);
console.log(` Drill-down: ${drillTotal.toFixed(2)}`);
console.log(` Delta: ${delta.toFixed(2)}`);
if (Math.abs(delta) < 0.01) {
console.log(` ✓ CUADRAN`);
} else {
console.log(` ✗ NO CUADRAN — investigar`);
}
// 4. Régimenes del receptor que aparecen vs los ignorados
const { rows: regsReceptor } = await pool.query(
`SELECT DISTINCT regimen_fiscal_receptor
FROM cfdis
WHERE contribuyente_id = $1
AND type = 'RECIBIDO'
AND fecha_emision >= $2::date AND fecha_emision < ($3::date + interval '1 day')
ORDER BY regimen_fiscal_receptor`,
[contribuyenteId, fi, ff],
);
console.log(`\nRegímenes en CFDIs RECIBIDOS del periodo:`, regsReceptor.map(r => r.regimen_fiscal_receptor).join(', '));
await prisma.$disconnect();
}
main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); });