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

76 lines
4.1 KiB
TypeScript

import { prisma, tenantDb } from '../src/config/database.js';
const yearMonth = '2025-02';
const contribuyenteId = 'd745a915-6a23-4818-944b-a7e1e18e536a';
const tenantRfc = 'DESPACHO_MO3NI6U8_B9VGG';
async function main() {
const tenant = await prisma.tenant.findFirst({ where: { rfc: tenantRfc }, select: { id: true, databaseName: true } });
if (!tenant) return;
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')}`;
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)`;
// Drill desglosado por régimen del receptor
const { rows } = await pool.query(
`SELECT
COALESCE(regimen_fiscal_receptor, 'null') AS regimen_rec,
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')
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 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 regimen_rec, type, tipo_comprobante, metodo_pago, tipo_rel
ORDER BY regimen_rec, tipo_comprobante, metodo_pago`,
[fi, ff, contribuyenteId],
);
const byReg: Record<string, { fact: number; pago: number; nc: number; detalle: any[] }> = {};
for (const r of rows) {
const reg = r.regimen_rec;
if (!byReg[reg]) byReg[reg] = { fact: 0, pago: 0, nc: 0, detalle: [] };
const v = r.tipo_comprobante === 'P' ? Number(r.pago_neto) : Number(r.total_neto);
byReg[reg].detalle.push({ tc: r.tipo_comprobante, mp: r.metodo_pago, rel: r.tipo_rel, n: r.n, valor: v, bruto: Number(r.total_bruto) });
if (r.tipo_comprobante === 'I') byReg[reg].fact += v;
else if (r.tipo_comprobante === 'P') byReg[reg].pago += v;
else if (r.tipo_comprobante === 'E') byReg[reg].nc += v;
}
console.log(`\n=== DRILL-DOWN por régimen del receptor — ${fi} a ${ff} ===\n`);
let totalAll = 0;
const TODOS_REGS = new Set(['605','606','612','621','625','626','601','603','607','608','610','611','614','615','620','622','623','624']);
for (const [reg, v] of Object.entries(byReg).sort()) {
const subtot = v.fact + v.pago - v.nc;
totalAll += subtot;
const inTodos = TODOS_REGS.has(reg) ? '✓' : '✗ (excluido de TODOS_REGIMENES)';
console.log(`Régimen ${reg} ${inTodos}`);
console.log(` fact=${v.fact.toFixed(2)} pago=${v.pago.toFixed(2)} NC=${v.nc.toFixed(2)} → subtotal=${subtot.toFixed(2)}`);
for (const d of v.detalle) {
console.log(` ${d.tc} ${d.mp || '-'} rel=${d.rel || '-'} n=${d.n} bruto=${d.bruto.toFixed(2)} neto=${d.valor.toFixed(2)}`);
}
}
console.log(`\nTotal todos regímenes: ${totalAll.toFixed(2)}`);
const inTodos = Object.entries(byReg).filter(([r]) => TODOS_REGS.has(r)).reduce((s, [, v]) => s + (v.fact + v.pago - v.nc), 0);
console.log(`Total solo en TODOS_REGIMENES: ${inTodos.toFixed(2)}`);
await prisma.$disconnect();
}
main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); });