Update: nueva version Horux Despachos
This commit is contained in:
126
apps/api/scripts/backfill-facturapi-cfdis.ts
Normal file
126
apps/api/scripts/backfill-facturapi-cfdis.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Backfill one-shot: completa los campos de emisor/subtotal/IVA/XML en las
|
||||
* filas de `cfdis` con `source='facturapi'` que fueron insertadas por la
|
||||
* versión buggy del controller (previo al fix 2026-04-24).
|
||||
*
|
||||
* Descarga el XML real de Facturapi, lo parsea con el mismo parser SAT,
|
||||
* upsertea la fila de `rfcs` del emisor, y actualiza la fila de `cfdis`.
|
||||
*/
|
||||
import { prisma, tenantDb } from '../src/config/database.js';
|
||||
import { downloadXmlContribuyente } from '../src/services/contribuyente-facturapi.service.js';
|
||||
import * as facturapiService from '../src/services/facturapi.service.js';
|
||||
import { parseXml } from '../src/services/sat/sat-parser.service.js';
|
||||
|
||||
const TENANT_RFC = process.argv[2] || 'DESPACHO_MO3NI6U8_B9VGG';
|
||||
|
||||
async function main() {
|
||||
const tenant = await prisma.tenant.findFirst({
|
||||
where: { rfc: TENANT_RFC },
|
||||
select: { id: true, databaseName: true },
|
||||
});
|
||||
if (!tenant) {
|
||||
console.log(`Tenant ${TENANT_RFC} no encontrado`);
|
||||
return;
|
||||
}
|
||||
const pool = await tenantDb.getPool(tenant.id, tenant.databaseName);
|
||||
|
||||
const { rows: pendientes } = await pool.query<{
|
||||
id: number;
|
||||
uuid: string;
|
||||
facturapi_id: string;
|
||||
contribuyente_id: string | null;
|
||||
rfc_emisor: string | null;
|
||||
}>(
|
||||
`SELECT id, uuid, facturapi_id, contribuyente_id, rfc_emisor
|
||||
FROM cfdis
|
||||
WHERE source = 'facturapi'
|
||||
AND (COALESCE(rfc_emisor, '') = '' OR xml_original IS NULL OR subtotal = 0)
|
||||
ORDER BY fecha_emision ASC`,
|
||||
);
|
||||
|
||||
console.log(`Encontradas ${pendientes.length} CFDIs Facturapi a backfillear en ${TENANT_RFC}\n`);
|
||||
|
||||
let ok = 0;
|
||||
let fail = 0;
|
||||
|
||||
for (const row of pendientes) {
|
||||
try {
|
||||
console.log(`\n[${row.uuid}] facturapi_id=${row.facturapi_id} contrib=${row.contribuyente_id}`);
|
||||
|
||||
const xmlBuffer = row.contribuyente_id
|
||||
? await downloadXmlContribuyente(pool, row.contribuyente_id, row.facturapi_id)
|
||||
: await facturapiService.downloadXml(tenant.id, row.facturapi_id);
|
||||
const xmlString = xmlBuffer.toString('utf-8');
|
||||
const parsed = parseXml(xmlString, 'emitidos');
|
||||
if (!parsed) {
|
||||
console.log(` ⚠️ Parser retornó null — skip`);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(` emisor=${parsed.rfcEmisor} (${parsed.nombreEmisor}, régimen ${parsed.regimenFiscalEmisor})`);
|
||||
console.log(` receptor=${parsed.rfcReceptor} (${parsed.nombreReceptor}, régimen ${parsed.regimenFiscalReceptor})`);
|
||||
console.log(` subtotal=${parsed.subtotal} total=${parsed.total} iva_traslado=${parsed.ivaTraslado}`);
|
||||
|
||||
// Upsert rfcs emisor
|
||||
const { rows: [emisorRow] } = await pool.query(
|
||||
`INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (rfc) DO UPDATE SET
|
||||
razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social),
|
||||
regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END
|
||||
RETURNING id`,
|
||||
[parsed.rfcEmisor, parsed.nombreEmisor || null, parsed.regimenFiscalEmisor || null],
|
||||
);
|
||||
// Upsert rfcs receptor
|
||||
const { rows: [receptorRow] } = await pool.query(
|
||||
`INSERT INTO rfcs (rfc, razon_social, regimen_fiscal) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (rfc) DO UPDATE SET
|
||||
razon_social = COALESCE(NULLIF($2, ''), rfcs.razon_social),
|
||||
regimen_fiscal = CASE WHEN $3 IS NOT NULL AND $3 != '' THEN $3 ELSE rfcs.regimen_fiscal END
|
||||
RETURNING id`,
|
||||
[parsed.rfcReceptor, parsed.nombreReceptor || null, parsed.regimenFiscalReceptor || null],
|
||||
);
|
||||
|
||||
await pool.query(
|
||||
`UPDATE cfdis SET
|
||||
fecha_cert_sat = $2,
|
||||
rfc_emisor_id = $3, rfc_emisor = $4, nombre_emisor = $5,
|
||||
regimen_fiscal_emisor = $6,
|
||||
rfc_receptor_id = $7, rfc_receptor = $8, nombre_receptor = $9,
|
||||
regimen_fiscal_receptor = $10,
|
||||
subtotal = $11, subtotal_mxn = $11,
|
||||
total = $12, total_mxn = $12,
|
||||
iva_traslado = $13, iva_traslado_mxn = $13,
|
||||
iva_retencion = $14, iva_retencion_mxn = $14,
|
||||
xml_original = $15,
|
||||
serie = COALESCE($16, serie), folio = COALESCE($17, folio)
|
||||
WHERE id = $1`,
|
||||
[
|
||||
row.id,
|
||||
parsed.fechaCertSat,
|
||||
emisorRow.id, parsed.rfcEmisor, parsed.nombreEmisor,
|
||||
parsed.regimenFiscalEmisor,
|
||||
receptorRow.id, parsed.rfcReceptor, parsed.nombreReceptor,
|
||||
parsed.regimenFiscalReceptor,
|
||||
parsed.subtotal,
|
||||
parsed.total,
|
||||
parsed.ivaTraslado,
|
||||
parsed.ivaRetencion,
|
||||
xmlString,
|
||||
parsed.serie, parsed.folio,
|
||||
],
|
||||
);
|
||||
|
||||
console.log(` ✅ actualizada fila id=${row.id}`);
|
||||
ok++;
|
||||
} catch (e: any) {
|
||||
console.log(` ❌ error: ${e?.message || String(e)}`);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n=== Resumen: ${ok} actualizadas, ${fail} fallidas ===`);
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
|
||||
main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); });
|
||||
Reference in New Issue
Block a user