/** * Script para reparar CFDIs tipo I/E que tienen XML pero campos nulos * (subtotal, pac, tipo_cambio, impuestos, etc.) * * El problema: algunos CFDIs fueron insertados por saveMetadata y luego * saveCfdis no actualizó todos los campos correctamente. */ import { Pool } from 'pg'; import { parseXml } from '../src/services/sat/sat-parser.service'; const DB_CONFIG = { host: 'localhost', user: 'postgres', password: 'ZxHMrmnwanvLfLDdNJdRthFjWF2Lj1Rb', }; const TENANTS = [ { db: 'horux_auza640701ti9', rfc: 'AUZA640701TI9' }, { db: 'horux_hts240708lja', rfc: 'HTS240708LJA' }, { db: 'horux_momc8311199va', rfc: 'MOMC8311199VA' }, { db: 'horux_roem691011ez4', rfc: 'ROEM691011EZ4' }, { db: 'horux_toah680201ra2', rfc: 'TOAH680201RA2' }, { db: 'horux_tora0007099r6', rfc: 'TORA0007099R6' }, { db: 'horux_despacho_mpg95qp7_xzvff', rfc: 'MPG95QP7_XZVFF' }, ]; async function fixTenant(dbName: string, rfc: string): Promise<{ fixed: number; errors: number }> { const pool = new Pool({ ...DB_CONFIG, database: dbName }); let fixed = 0; let errors = 0; try { // CFDIs tipo I/E con XML pero sin subtotal (indica que saveCfdis no actualizó bien) const { rows } = await pool.query(` SELECT id, uuid, xml_original, type FROM cfdis WHERE tipo_comprobante IN ('I', 'E') AND xml_original IS NOT NULL AND xml_original <> '' AND subtotal IS NULL `); console.log(`[${rfc}] ${rows.length} CFDIs a reparar`); for (const row of rows) { try { const downloadType = row.type === 'EMITIDO' ? 'emitidos' : 'recibidos'; const parsed = parseXml(row.xml_original, downloadType); if (!parsed) { console.warn(`[${rfc}] No se pudo parsear UUID ${row.uuid}`); errors++; continue; } const tc = parsed.tipoCambio || 1; const m = (v: number) => v * tc; await pool.query(` UPDATE cfdis SET subtotal = $1, subtotal_mxn = $2, descuento = $3, descuento_mxn = $4, total = $5, total_mxn = $6, moneda = $7, tipo_cambio = $8, metodo_pago = $9, forma_pago = $10, uso_cfdi = $11, pac = $12, fecha_cert_sat = $13, uuid_relacionado = $14, isr_retencion = $15, isr_retencion_mxn = $16, iva_traslado = $17, iva_traslado_mxn = $18, iva_retencion = $19, iva_retencion_mxn = $20, ieps_traslado = $21, ieps_traslado_mxn = $22, ieps_retencion = $23, ieps_retencion_mxn = $24, impuestos_locales_trasladado = $25, impuestos_locales_trasladado_mxn = $26, impuestos_locales_retenidos = $27, impuestos_locales_retenidos_mxn = $28, regimen_fiscal_emisor = $29, regimen_fiscal_receptor = $30, codigo_postal_receptor = $31, serie = $32, folio = $33, cfdi_tipo_relacion = $34, cfdis_relacionados = $35, actualizado_en = NOW() WHERE id = $36 `, [ parsed.subtotal, m(parsed.subtotal), parsed.descuento, m(parsed.descuento), parsed.total, m(parsed.total), parsed.moneda, tc, parsed.metodoPago, parsed.formaPago, parsed.usoCfdi, parsed.pac, parsed.fechaCertSat, parsed.uuidRelacionado, parsed.isrRetencion, m(parsed.isrRetencion), parsed.ivaTraslado, m(parsed.ivaTraslado), parsed.ivaRetencion, m(parsed.ivaRetencion), parsed.iepsTraslado, m(parsed.iepsTraslado), parsed.iepsRetencion, m(parsed.iepsRetencion), parsed.impuestosLocalesTrasladado, m(parsed.impuestosLocalesTrasladado), parsed.impuestosLocalesRetenidos, m(parsed.impuestosLocalesRetenidos), parsed.regimenFiscalEmisor, parsed.regimenFiscalReceptor, parsed.codigoPostalReceptor, parsed.serie, parsed.folio, parsed.cfdiTipoRelacion, parsed.cfdisRelacionados, row.id, ]); fixed++; } catch (err: any) { console.error(`[${rfc}] Error reparando UUID ${row.uuid}:`, err.message); errors++; } } } finally { await pool.end(); } return { fixed, errors }; } async function main() { let totalFixed = 0; let totalErrors = 0; for (const tenant of TENANTS) { try { const result = await fixTenant(tenant.db, tenant.rfc); totalFixed += result.fixed; totalErrors += result.errors; console.log(`[${tenant.rfc}] Reparados: ${result.fixed}, Errores: ${result.errors}`); } catch (err: any) { console.error(`[${tenant.rfc}] Error general:`, err.message); totalErrors++; } } console.log(`\n=== TOTAL: Reparados ${totalFixed}, Errores ${totalErrors} ===`); } main().catch(console.error);