- Agrega tabla sat_proxy_errors con proxy_usado, error_code, stage, message. - sat-client.service.ts expone proxyInfo usado en cada conexión SAT. - sat.service.ts registra errores de bloqueo/devolución del SAT en sat_proxy_errors y guarda proxyUsed en sat_sync_jobs. - Nuevo job sat-proxy-report.job.ts: cron 8 AM CDMX, envía email a ADMIN_EMAIL con errores de las últimas 24h agrupados por proxy/error_code. - Template de email sat-proxy-report.ts y método sendSatProxyReport. - Registra el cron en src/index.ts. - Actualiza docs/SAT-SYNC-IMPLEMENTATION.md.
151 lines
4.9 KiB
TypeScript
151 lines
4.9 KiB
TypeScript
/**
|
|
* 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);
|