/** * Backfill: Extrae conceptos de XMLs existentes en cfdis y los inserta en cfdi_conceptos. * Uso: npx tsx scripts/backfill-conceptos.ts [batchSize] */ import { Pool } from 'pg'; import { parseXml } from '../src/services/sat/sat-parser.service.js'; async function main() { const databaseName = process.argv[2]; const batchSize = parseInt(process.argv[3] || '100', 10); if (!databaseName) { console.error('Uso: npx tsx scripts/backfill-conceptos.ts [batchSize]'); process.exit(1); } const pool = new Pool({ host: 'localhost', port: 5432, user: 'postgres', password: 'ZxHMrmnwanvLfLDdNJdRthFjWF2Lj1Rb', database: databaseName, }); let totalProcessed = 0; let totalConceptos = 0; let errors = 0; while (true) { const { rows: cfdis } = await pool.query(` SELECT c.id, c.uuid, c.xml_original FROM cfdis c LEFT JOIN cfdi_conceptos cc ON cc.cfdi_id = c.id WHERE c.xml_original IS NOT NULL AND cc.id IS NULL LIMIT $1 `, [batchSize]); if (cfdis.length === 0) break; for (const row of cfdis) { try { const cfdi = parseXml(row.xml_original, 'emitidos'); if (!cfdi || !cfdi.conceptos || cfdi.conceptos.length === 0) { // Algunos XMLs pueden no tener conceptos (ej. tipo P, N, T) // Pero aún así los marcamos como "procesados" insertando un concepto dummy? // No, mejor solo saltamos. totalProcessed++; continue; } const tc = cfdi.tipoCambio || 1; const m = (v: number) => v * tc; for (const c of cfdi.conceptos) { await pool.query(` INSERT INTO cfdi_conceptos ( cfdi_id, clave_prod_serv, no_identificacion, descripcion, cantidad, clave_unidad, unidad, valor_unitario, valor_unitario_mxn, importe, importe_mxn, descuento, descuento_mxn, isr_retencion, isr_retencion_mxn, iva_traslado, iva_traslado_mxn, iva_retencion, iva_retencion_mxn, ieps_traslado, ieps_traslado_mxn, ieps_retencion, ieps_retencion_mxn ) VALUES ( $1,$2,$3,$4,$5,$6,$7,$8,$9,$10, $11,$12,$13,$14,$15,$16,$17,$18,$19,$20, $21,$22,$23 ) `, [ row.id, c.claveProdServ, c.noIdentificacion, c.descripcion, c.cantidad, c.claveUnidad, c.unidad, c.valorUnitario, m(c.valorUnitario), c.importe, m(c.importe), c.descuento, m(c.descuento), c.isrRetencion, m(c.isrRetencion), c.ivaTraslado, m(c.ivaTraslado), c.ivaRetencion, m(c.ivaRetencion), c.iepsTraslado, m(c.iepsTraslado), c.iepsRetencion, m(c.iepsRetencion), ]); } totalConceptos += cfdi.conceptos.length; totalProcessed++; } catch (err: any) { errors++; console.error(`[ERROR] UUID ${row.uuid}:`, err.message); } } console.log(`[PROGRESS] Procesados: ${totalProcessed}, Conceptos: ${totalConceptos}, Errores: ${errors}`); } console.log(`\n[COMPLETE] Total CFDIs procesados: ${totalProcessed}`); console.log(`[COMPLETE] Total conceptos insertados: ${totalConceptos}`); console.log(`[COMPLETE] Errores: ${errors}`); await pool.end(); } main().catch(err => { console.error(err); process.exit(1); });