116 lines
5.4 KiB
TypeScript
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); });
|