Update: nueva version Horux Despachos
This commit is contained in:
112
apps/api/scripts/check-ieps-inflation.ts
Normal file
112
apps/api/scripts/check-ieps-inflation.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Detecta complementos P cuya ieps_traslado_pago_mxn parece inflada
|
||||
* respecto al monto pagado y respecto a la factura referenciada.
|
||||
*
|
||||
* Heurísticas:
|
||||
* 1. IEPS del P > monto_pago × 1.6 (tasa máxima teórica SAT para bebidas
|
||||
* con alto contenido alcohólico; cualquier cosa arriba es sospechoso).
|
||||
* 2. IEPS del P > IEPS de la factura original a la que se refiere
|
||||
* (imposible — un pago parcial no puede transferir más IEPS que el total).
|
||||
* 3. Ratio IEPS / monto_pago vs IEPS_original / total_original, donde la
|
||||
* proporción del P excede la del original por >5pp (señal de error
|
||||
* del proveedor).
|
||||
*/
|
||||
import { prisma, tenantDb } from '../src/config/database.js';
|
||||
|
||||
async function main() {
|
||||
const tenants = await prisma.tenant.findMany({
|
||||
select: { id: true, rfc: true, databaseName: true },
|
||||
});
|
||||
|
||||
for (const t of tenants) {
|
||||
let pool;
|
||||
try {
|
||||
pool = await tenantDb.getPool(t.id, t.databaseName);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`\n=== Tenant ${t.rfc} (${t.databaseName}) ===`);
|
||||
|
||||
// Heurística 1: IEPS > 160% del monto
|
||||
const { rows: h1 } = await pool.query(`
|
||||
SELECT uuid, rfc_emisor, rfc_receptor, monto_pago_mxn, ieps_traslado_pago_mxn,
|
||||
(ieps_traslado_pago_mxn / NULLIF(monto_pago_mxn, 0))::numeric(10,4) AS ratio
|
||||
FROM cfdis
|
||||
WHERE tipo_comprobante = 'P'
|
||||
AND status NOT IN ('Cancelado', '0')
|
||||
AND COALESCE(ieps_traslado_pago_mxn, 0) > 0
|
||||
AND COALESCE(monto_pago_mxn, 0) > 0
|
||||
AND ieps_traslado_pago_mxn > monto_pago_mxn * 1.6
|
||||
ORDER BY ieps_traslado_pago_mxn DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
console.log(`\n-- H1: IEPS > monto_pago × 1.6 (${h1.length}) --`);
|
||||
for (const r of h1) {
|
||||
console.log(` ${r.uuid.substring(0, 8)} ${r.rfc_emisor}→${r.rfc_receptor} pago=${Number(r.monto_pago_mxn).toFixed(2)} IEPS=${Number(r.ieps_traslado_pago_mxn).toFixed(2)} ratio=${r.ratio}`);
|
||||
}
|
||||
|
||||
// Heurística 2: IEPS del P > IEPS de la factura referenciada (imposible)
|
||||
// uuid_relacionado es pipe-separated; normalizar
|
||||
const { rows: h2 } = await pool.query(`
|
||||
SELECT p.uuid AS p_uuid, p.rfc_emisor, p.monto_pago_mxn, p.ieps_traslado_pago_mxn,
|
||||
i.uuid AS i_uuid, i.total_mxn AS i_total, i.ieps_traslado_mxn AS i_ieps
|
||||
FROM cfdis p
|
||||
JOIN cfdis i
|
||||
ON LOWER(i.uuid) = ANY(string_to_array(LOWER(COALESCE(p.uuid_relacionado, '')), '|'))
|
||||
AND i.status NOT IN ('Cancelado', '0')
|
||||
WHERE p.tipo_comprobante = 'P'
|
||||
AND p.status NOT IN ('Cancelado', '0')
|
||||
AND COALESCE(p.ieps_traslado_pago_mxn, 0) > 0
|
||||
AND COALESCE(p.ieps_traslado_pago_mxn, 0) > COALESCE(i.ieps_traslado_mxn, 0)
|
||||
ORDER BY p.ieps_traslado_pago_mxn DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
console.log(`\n-- H2: IEPS del P > IEPS de la factura referenciada (${h2.length}) --`);
|
||||
for (const r of h2) {
|
||||
const ratio = r.i_ieps > 0 ? Number(r.ieps_traslado_pago_mxn) / Number(r.i_ieps) : 0;
|
||||
console.log(` P=${r.p_uuid.substring(0, 8)} IEPS_P=${Number(r.ieps_traslado_pago_mxn).toFixed(2)} I=${r.i_uuid.substring(0, 8)} IEPS_I=${Number(r.i_ieps || 0).toFixed(2)} ratio=${ratio.toFixed(2)}x`);
|
||||
}
|
||||
|
||||
// Heurística 3: ratio IEPS/pago del P muy distinto del ratio IEPS/total del I
|
||||
const { rows: h3 } = await pool.query(`
|
||||
SELECT p.uuid AS p_uuid, p.monto_pago_mxn, p.ieps_traslado_pago_mxn,
|
||||
i.uuid AS i_uuid, i.total_mxn AS i_total, i.ieps_traslado_mxn AS i_ieps,
|
||||
(p.ieps_traslado_pago_mxn / NULLIF(p.monto_pago_mxn, 0))::numeric(6,4) AS ratio_p,
|
||||
(i.ieps_traslado_mxn / NULLIF(i.total_mxn, 0))::numeric(6,4) AS ratio_i
|
||||
FROM cfdis p
|
||||
JOIN cfdis i
|
||||
ON LOWER(i.uuid) = ANY(string_to_array(LOWER(COALESCE(p.uuid_relacionado, '')), '|'))
|
||||
AND i.status NOT IN ('Cancelado', '0')
|
||||
WHERE p.tipo_comprobante = 'P'
|
||||
AND p.status NOT IN ('Cancelado', '0')
|
||||
AND COALESCE(p.ieps_traslado_pago_mxn, 0) > 0
|
||||
AND COALESCE(i.ieps_traslado_mxn, 0) > 0
|
||||
AND COALESCE(p.monto_pago_mxn, 0) > 0
|
||||
AND COALESCE(i.total_mxn, 0) > 0
|
||||
AND ABS(
|
||||
(p.ieps_traslado_pago_mxn / p.monto_pago_mxn)
|
||||
- (i.ieps_traslado_mxn / i.total_mxn)
|
||||
) > 0.05
|
||||
ORDER BY p.ieps_traslado_pago_mxn DESC
|
||||
LIMIT 10
|
||||
`);
|
||||
console.log(`\n-- H3: ratio_P − ratio_I > 5pp (${h3.length}) --`);
|
||||
for (const r of h3) {
|
||||
console.log(` P=${r.p_uuid.substring(0, 8)} ratio_P=${r.ratio_p} I=${r.i_uuid.substring(0, 8)} ratio_I=${r.ratio_i} delta=${(Number(r.ratio_p) - Number(r.ratio_i)).toFixed(4)}`);
|
||||
}
|
||||
|
||||
// Resumen: total de P con IEPS > 0
|
||||
const { rows: [summary] } = await pool.query(`
|
||||
SELECT COUNT(*) FILTER (WHERE COALESCE(ieps_traslado_pago_mxn, 0) > 0)::int AS p_con_ieps,
|
||||
COUNT(*) FILTER (WHERE tipo_comprobante = 'P')::int AS p_total
|
||||
FROM cfdis
|
||||
WHERE status NOT IN ('Cancelado', '0')
|
||||
`);
|
||||
console.log(`\nResumen: ${summary.p_con_ieps} P con IEPS > 0 (de ${summary.p_total} P totales)`);
|
||||
}
|
||||
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); });
|
||||
Reference in New Issue
Block a user