Files
HoruxDespachosNuevo/apps/api/scripts/debug-i07-ppd.ts

170 lines
10 KiB
TypeScript

/**
* Diseca cómo se compensa la I PPD con cfdi_tipo_relacion='07' (aplicación
* de anticipo) en el cálculo de deducciones, evaluando:
* - Si NO entra al sumatorio normal (I PUE / P) por ser PPD
* - Si entra a la compensación I/07 PPD ↔ E del mismo mes
* - Si tiene cfdis_relacionados (qué referencia hacia atrás)
* - Si es referenciada por algún CFDI hacia adelante (P, E, otra I)
* - Cómo afecta con considerarActivos ON vs OFF
*/
import { prisma, tenantDb } from '../src/config/database.js';
async function main() {
const t = await prisma.tenant.findFirst({ where: { rfc: 'DESPACHO_MO3NI6U8_B9VGG' } });
if (!t) { console.log('Patito tenant not found'); return; }
const pool = await tenantDb.getPool(t.id, t.databaseName);
const TARGET = '5c874749-748f-11f0-96b1-2b9310891836';
const RFC = 'TOAH680201RA2';
// ───────────────────────────────────────────────────────────────────────────
// 0) Datos del CFDI
// ───────────────────────────────────────────────────────────────────────────
const { rows: [c] } = await pool.query(`
SELECT uuid, type, tipo_comprobante, metodo_pago, forma_pago, uso_cfdi,
cfdi_tipo_relacion, cfdis_relacionados,
total_mxn, iva_traslado_mxn, ieps_traslado_mxn,
rfc_emisor, nombre_emisor, regimen_fiscal_emisor,
rfc_receptor, nombre_receptor, regimen_fiscal_receptor,
fecha_emision, fecha_pago_p, status,
saldo_pendiente_mxn
FROM cfdis WHERE LOWER(uuid) = LOWER($1)
`, [TARGET]);
console.log('═══ CFDI ═══');
for (const [k, v] of Object.entries(c)) {
console.log(` ${k.padEnd(28)} ${v}`);
}
// ───────────────────────────────────────────────────────────────────────────
// 1) ¿Hace referencia hacia atrás (vía cfdis_relacionados)?
// ───────────────────────────────────────────────────────────────────────────
console.log('\n═══ Referencias hacia atrás (cfdis_relacionados) ═══');
if (!c.cfdis_relacionados) {
console.log(' (ninguna — cfdis_relacionados es NULL)');
} else {
const uuids = String(c.cfdis_relacionados).split('|').map((s: string) => s.trim()).filter(Boolean);
for (const u of uuids) {
const { rows: [rel] } = await pool.query(`
SELECT uuid, tipo_comprobante, metodo_pago, total_mxn, fecha_emision, cfdi_tipo_relacion
FROM cfdis WHERE LOWER(uuid) = LOWER($1)
`, [u]);
console.log(` ${u}`, rel ?? '(no encontrado)');
}
}
// ───────────────────────────────────────────────────────────────────────────
// 2) ¿Es referenciada hacia adelante? (P que la pague, E que la cancele, otra I tipo_relacion=07 que sustituya)
// ───────────────────────────────────────────────────────────────────────────
console.log('\n═══ CFDIs que referencian a este (hacia adelante) ═══');
// P que la pagan vía uuid_relacionado
const { rows: pagos } = await pool.query(`
SELECT uuid, monto_pago_mxn, fecha_pago_p
FROM cfdis
WHERE tipo_comprobante = 'P'
AND LOWER(uuid_relacionado) = LOWER($1)
AND status NOT IN ('Cancelado','0')
ORDER BY fecha_pago_p
`, [TARGET]);
console.log(` P que la pagan (${pagos.length}):`);
let totalPagado = 0;
for (const p of pagos) {
totalPagado += Number(p.monto_pago_mxn || 0);
console.log(` ${p.uuid} | $${p.monto_pago_mxn} | ${p.fecha_pago_p}`);
}
console.log(` → Total pagado vía P: $${totalPagado.toLocaleString('es-MX')}`);
console.log(` Total CFDI original: $${c.total_mxn}`);
console.log(` Saldo pendiente: $${c.saldo_pendiente_mxn ?? '?'}`);
// E que la cancelan vía cfdis_relacionados
const { rows: ecanc } = await pool.query(`
SELECT uuid, tipo_comprobante, metodo_pago, total_mxn, cfdi_tipo_relacion, fecha_emision
FROM cfdis
WHERE tipo_comprobante = 'E'
AND cfdis_relacionados IS NOT NULL
AND LOWER($1) = ANY(string_to_array(LOWER(cfdis_relacionados), '|'))
AND status NOT IN ('Cancelado','0')
`, [TARGET]);
console.log(`\n E que la referencian (${ecanc.length}):`);
for (const e of ecanc) {
console.log(` ${e.uuid} | total=$${e.total_mxn} | tipo_rel=${e.cfdi_tipo_relacion} | ${e.fecha_emision}`);
}
// ───────────────────────────────────────────────────────────────────────────
// 3) Compensación I/07 PPD ↔ E lado RECEPTOR (mismo mes)
// ───────────────────────────────────────────────────────────────────────────
console.log('\n═══ ¿Entra en compensación I/07 PPD ↔ E (mes/año del CFDI)? ═══');
const fecha = new Date(c.fecha_emision);
const mesAnio = `${fecha.getFullYear()}-${String(fecha.getMonth() + 1).padStart(2, '0')}`;
console.log(` CFDI mes/año: ${mesAnio}`);
console.log(` cfdi_tipo_relacion='07': ${c.cfdi_tipo_relacion === '07' ? '✓ SÍ' : '✗ NO'}`);
console.log(` metodo_pago='PPD': ${c.metodo_pago === 'PPD' ? '✓ SÍ' : '✗ NO'}`);
if (c.cfdi_tipo_relacion === '07' && c.metodo_pago === 'PPD') {
// Calcular el aporte que tendría a la compensación (suma de E del mismo mes)
const { rows: comp } = await pool.query(`
SELECT
COALESCE(SUM(
COALESCE(e.total_mxn, 0)
- COALESCE(e.iva_traslado_mxn, 0)
- COALESCE(e.ieps_traslado_mxn, 0)
- COALESCE(e.impuestos_locales_trasladado_mxn, 0)
), 0)::numeric(14,2) AS aporte
FROM cfdis e
WHERE e.tipo_comprobante = 'E'
AND e.metodo_pago = 'PUE'
AND e.status NOT IN ('Cancelado','0')
AND UPPER(e.rfc_receptor) = $1
AND LOWER($2) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
AND date_trunc('month', e.fecha_emision) = date_trunc('month', $3::timestamp)
`, [RFC, TARGET, c.fecha_emision]);
console.log(`\n Aporte a la compensación (suma E mismo mes): $${comp[0].aporte}`);
if (Number(comp[0].aporte) > 0) {
console.log(` → SÍ entra en compensación`);
} else {
console.log(` → NO entra (no hay E en mismo mes que la referencien)`);
}
}
// ───────────────────────────────────────────────────────────────────────────
// 4) ¿Aparece en el cálculo "I PUE recibidas" o "P recibidos"?
// ───────────────────────────────────────────────────────────────────────────
console.log('\n═══ ¿Aparece en el cálculo directo? ═══');
console.log(` I PUE recibidas requiere: tipo_comprobante='I' AND metodo_pago='PUE'`);
console.log(` Este CFDI: tipo_comprobante='${c.tipo_comprobante}', metodo_pago='${c.metodo_pago}'`);
console.log(`${c.tipo_comprobante === 'I' && c.metodo_pago === 'PUE' ? '✓ SÍ entra' : '✗ NO entra'} (es ${c.tipo_comprobante} ${c.metodo_pago})`);
// ───────────────────────────────────────────────────────────────────────────
// 5) Predicado de filtro de activos
// ───────────────────────────────────────────────────────────────────────────
console.log('\n═══ Predicado de filtro de activos sobre este CFDI ═══');
const ACTIVOS = "('I01','I02','I03','I04','I05','I06','I07','I08')";
const t1 = await pool.query(`
SELECT
(tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS}) AS regla1_directo,
(tipo_comprobante = 'P' AND EXISTS (
SELECT 1 FROM cfdis i_act
WHERE LOWER(i_act.uuid) = LOWER(cfdis.uuid_relacionado)
AND i_act.tipo_comprobante = 'I'
AND i_act.uso_cfdi IN ${ACTIVOS}
)) AS regla2,
(tipo_comprobante = 'E' AND cfdis.cfdis_relacionados IS NOT NULL AND EXISTS (
SELECT 1 FROM cfdis r_act
WHERE LOWER(r_act.uuid) = ANY(string_to_array(LOWER(cfdis.cfdis_relacionados), '|'))
AND (r_act.tipo_comprobante = 'I' AND r_act.uso_cfdi IN ${ACTIVOS})
)) AS regla3
FROM cfdis WHERE LOWER(uuid) = LOWER($1)
`, [TARGET]);
console.log(` regla1 (I directo activo): ${t1.rows[0].regla1_directo ? '🔴 TRUE' : '🟢 FALSE'}`);
console.log(` regla2 (P paga I activo): ${t1.rows[0].regla2 ? '🔴 TRUE' : '🟢 FALSE'}`);
console.log(` regla3 (E ref. I/P activo): ${t1.rows[0].regla3 ? '🔴 TRUE' : '🟢 FALSE'}`);
const filtrado = t1.rows[0].regla1_directo || t1.rows[0].regla2 || t1.rows[0].regla3;
console.log(` → Si "Considerar activos" OFF → ${filtrado ? '🔴 EXCLUIDO' : '🟢 PASA'}`);
await prisma.$disconnect();
}
main().catch(e => { console.error(e); process.exit(1); });