170 lines
10 KiB
TypeScript
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); });
|