fix(sat,conciliacion): propagar contribuyenteId en sync SAT y campos faltantes en visor de conciliacion
- sat-sync.job.ts: cron diario e incremental ahora iteran contribuyentes por tenant y pasan contribuyenteId a startSync(). Evita que CFDIs importados del SAT queden con contribuyente_id = NULL. - sat.service.ts: retryJob() ahora reintenta con job.contribuyenteId. - conciliacion.service.ts: agrega campos faltantes al SELECT de CFDIs: status, formaPago, serie, folio, usoCfdi, subtotal, descuento, moneda, tipoCambio, ivaTraslado, ivaRetencion, isrRetencion, fechaCertSat. Antes el visor mostraba 'CANCELADO' para todos los CFDIs (status era undefined) y faltaban datos de forma de pago, impuestos, serie/folio, etc. Refs: docs/CAMBIOS-2026-05-09.md secciones 6 y 7
This commit is contained in:
@@ -142,6 +142,33 @@ WHERE contribuyente_id = '96f98a42-5f27-4f27-acf6-61822dea666c';
|
||||
|
||||
---
|
||||
|
||||
## 6. Fix: CFDIs sin `contribuyente_id` en sincronizaciones SAT
|
||||
|
||||
**Problema:** Todos los CFDIs importados por SAT sync tenían `contribuyente_id = NULL`, aunque la columna ya existía. Esto causaba que no aparecieran facturas para conciliar ni en otros módulos que filtran por contribuyente.
|
||||
|
||||
**Causa raíz:** El cron job `sat-sync.job.ts` llamaba a `startSync(tenantId, syncType)` **sin pasar `contribuyenteId`**. Los jobs se creaban con `contribuyenteId = null`, y `saveCfdis()` insertaba los CFDIs con `contribuyente_id = null`.
|
||||
|
||||
**Fix aplicado:**
|
||||
|
||||
1. **`syncTenant()` (cron diario 3 AM)** — Ahora obtiene los contribuyentes del tenant desde su BD y ejecuta `startSync()` para cada uno pasando `contribuyenteId`. Si no hay contribuyentes, sincroniza a nivel tenant (legacy).
|
||||
|
||||
2. **`incrementalSyncTenant()` (cron incremental 11h/15h/19h)** — Mismo fix.
|
||||
|
||||
3. **`retryJob()` (reintento manual)** — Ahora pasa `job.contribuyenteId` al reintentar.
|
||||
|
||||
4. **Backfill de datos** — Se actualizaron los `contribuyente_id` de los CFDIs existentes para todos los tenants:
|
||||
- Alexa Torres: 383 CFDIs
|
||||
- Horux 360: 67 CFDIs
|
||||
- Miguel Estrada: 84,429 CFDIs
|
||||
- Aarón Ahumada: 2,290 CFDIs
|
||||
- Humberto Torres: 33 CFDIs
|
||||
|
||||
**Archivos:**
|
||||
- `apps/api/src/jobs/sat-sync.job.ts`
|
||||
- `apps/api/src/services/sat/sat.service.ts`
|
||||
|
||||
---
|
||||
|
||||
## Archivos modificados
|
||||
|
||||
### Backend (`apps/api/`)
|
||||
@@ -187,8 +214,93 @@ WHERE contribuyente_id = '96f98a42-5f27-4f27-acf6-61822dea666c';
|
||||
|
||||
---
|
||||
|
||||
## 7. Fix: Visor de CFDI en conciliación mostraba todo como "Cancelado" y faltaban datos
|
||||
|
||||
**Problema:** Al abrir cualquier CFDI desde el módulo de conciliación, el visor mostraba:
|
||||
- Estatus: **CANCELADO** (aunque el CFDI estuviera vigente)
|
||||
- Forma de pago: **-** (vacío)
|
||||
- Serie/Folio: **S/N**
|
||||
- Uso CFDI: no aparecía
|
||||
- Totales desglosados (subtotal, descuento, impuestos): todos en 0
|
||||
|
||||
**Causa raíz:** El servicio `conciliacion.service.ts` solo seleccionaba un subconjunto mínimo de campos de la tabla `cfdis`. No incluía `status`, `forma_pago`, `serie`, `folio`, `uso_cfdi`, `subtotal`, `descuento`, `iva_traslado`, `iva_retencion`, `isr_retencion`, `moneda`, `tipo_cambio`, ni `fecha_cert_sat`.
|
||||
|
||||
Como `status` llegaba `undefined` al componente `CfdiInvoice`, la condición:
|
||||
```tsx
|
||||
cfdi.status === 'Vigente' || cfdi.status === '1' ? 'VIGENTE' : 'CANCELADO'
|
||||
```
|
||||
Siempre caía en el `else` mostrando CANCELADO.
|
||||
|
||||
**Fix aplicado en `apps/api/src/services/conciliacion.service.ts`:**
|
||||
- Agregados todos los campos faltantes al `SELECT` SQL
|
||||
- Agregados a la interfaz `ConciliacionCfdi`
|
||||
- Agregados al mapeo de resultados con valores por defecto seguros (`|| 0`, `|| 'MXN'`, `|| 1`)
|
||||
|
||||
**Campos agregados:**
|
||||
| Campo | Uso en visor |
|
||||
|---|---|
|
||||
| `status` | Badge VIGENTE / CANCELADO |
|
||||
| `formaPago` | Datos del comprobante |
|
||||
| `serie`, `folio` | Encabezado (serie-folio) |
|
||||
| `usoCfdi` | Panel del receptor |
|
||||
| `subtotal`, `descuento` | Totales |
|
||||
| `ivaTraslado`, `ivaRetencion`, `isrRetencion` | Desglose de impuestos |
|
||||
| `moneda`, `tipoCambio` | Moneda y tipo de cambio |
|
||||
| `fechaCertSat` | Timbre fiscal digital |
|
||||
|
||||
---
|
||||
|
||||
## Archivos modificados (actualizado)
|
||||
|
||||
### Backend (`apps/api/`)
|
||||
| Archivo | Cambio |
|
||||
|---|---|
|
||||
| `.env` | Fix typo `MP_ACCESS_TOKEN` |
|
||||
| `src/services/payment/mercadopago.service.ts` | Fix validación firma webhook |
|
||||
| `src/services/payment/invoicing.service.ts` | Notificación email + exports |
|
||||
| `src/services/email/email.service.ts` | `sendPrimerPagoFacturar()` |
|
||||
| `src/services/email/templates/primer-pago-facturar.ts` | **Nuevo** template |
|
||||
| `src/controllers/facturacion.controller.ts` | `getPagosSinFactura()` + `emitirFacturaPago()` + fix `searchRfcs()` + fix `searchConceptos()` |
|
||||
| `src/routes/facturacion.routes.ts` | Rutas `/pagos-sin-factura` + `/emitir-factura-pago/:paymentId` |
|
||||
| `src/jobs/sat-sync.job.ts` | Fix: pasa `contribuyenteId` en cron diario e incremental |
|
||||
| `src/services/sat/sat.service.ts` | Fix: `retryJob()` pasa `contribuyenteId` + `saveCfdis()` usa `contribuyente_id` |
|
||||
| `src/services/conciliacion.service.ts` | Fix: agrega campos faltantes (`status`, `formaPago`, impuestos, etc.) |
|
||||
|
||||
### Frontend (`apps/web/`)
|
||||
| Archivo | Cambio |
|
||||
|---|---|
|
||||
| `lib/api/facturacion.ts` | `getPagosSinFactura()` + `emitirFacturaPago()` |
|
||||
| `lib/hooks/use-pagos-sin-factura.ts` | **Nuevo** hooks |
|
||||
| `app/(dashboard)/admin/facturas-pendientes/page.tsx` | **Nuevo** página admin |
|
||||
| `app/(dashboard)/clientes/page.tsx` | KPI "Facturas pendientes" |
|
||||
| `components/layouts/sidebar.tsx` | Removido "Facturas Pendientes" del menú admin |
|
||||
| `components/layouts/sidebar-floating.tsx` | Removido "Facturas Pendientes" del menú admin |
|
||||
| `components/layouts/sidebar-compact.tsx` | Removido "Facturas Pendientes" del menú admin |
|
||||
| `components/layouts/topnav.tsx` | Removido "Facturas Pendientes" del menú admin |
|
||||
|
||||
---
|
||||
|
||||
## Configuración requerida en MercadoPago Dashboard
|
||||
|
||||
- **Aplicación:** Horux360 (ID: `5319386258998241`)
|
||||
- **Webhook URL:** `https://horuxfin.com/api/webhooks/mercadopago`
|
||||
- **Tópicos:** `payment`, `subscription_preapproval`
|
||||
|
||||
---
|
||||
|
||||
## Datos de organizaciones Facturapi
|
||||
|
||||
| Org | RFC | Uso |
|
||||
|---|---|---|
|
||||
| `69f23a5a242e0af47a41fa0d` | HTS240708LJA | Horux 360 (emisor principal) — ✅ Activa |
|
||||
| `69ff900f48058f06ef1234c0` | — | Org fantasma (eliminada de BD) — ❌ Obsoleta |
|
||||
| `69ff8fabc2053c5568d799c5` | XIA190128J61 | Org creada accidentalmente durante diagnóstico — ❌ Obsoleta |
|
||||
|
||||
---
|
||||
|
||||
## Notas técnicas
|
||||
|
||||
- La encriptación de API keys usa AES-256-GCM con clave derivada de `FIEL_ENCRYPTION_KEY` (SHA-256)
|
||||
- El endpoint `POST /emitir-factura-pago/:paymentId` requiere rol `platform_admin`
|
||||
- La regla "primer pago no se factura automáticamente" sigue vigente; los subsecuentes sí son automáticos
|
||||
- Los CFDIs importados por SAT sync ahora se asocian correctamente al `contribuyente_id` correspondiente
|
||||
|
||||
Reference in New Issue
Block a user