feat: facturación primer pago, fixes SAT/MP, autocompletado RFCs/conceptos
Backend: - Notificación email al admin cuando llega primer pago aprobado (sin factura auto) - Endpoints GET /pagos-sin-factura y POST /emitir-factura-pago para admin global - Fix vinculación org Facturapi Horux 360 (69f23a5a242e0af47a41fa0d) - Fix webhook MP: validación defensiva de x-signature header - Fix autocompleto RFCs: eliminado filtro por contribuyenteId - Fix autocompleto conceptos: eliminado filtro por contribuyenteId - SAT fixes: anti-bot CSF scraper, request reuse, date range fix, stale job thresholds - SAT sync request reuse across jobs para evitar agotar cuota diaria - Typo fix MP_ACCESS_TOKEN en .env - Trial invitations system backend Frontend: - Nueva página /admin/facturas-pendientes con tabla y emisión manual - Métrica 'Facturas pendientes' en /clientes (clickable) - Navegación onboarding FIEL/CSD corregida - Sidebar themes sincronizados - Fix SAT portal migration scraper (NetIQ) - Trial invitation acceptance pages
This commit is contained in:
194
docs/CAMBIOS-2026-05-09.md
Normal file
194
docs/CAMBIOS-2026-05-09.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# Resumen de cambios - 9 de mayo de 2026
|
||||
|
||||
## 1. Sincronización de pago - Alexa Torres
|
||||
|
||||
**Problema:** Alexa Torres (tenant `45ddd745-5037-4325-b3ec-1a85cbf7b849`) pagó $780 vía MercadoPago exitosamente, pero la suscripción seguía en estado `pending`. No llegó webhook.
|
||||
|
||||
**Causa raíz:**
|
||||
- `.env` tenía `MP_ACCES_TOKEN` (1 S) en lugar de `MP_ACCESS_TOKEN` (2 S)
|
||||
- La aplicación de MercadoPago tenía URL de webhook incorrecta (`https://www.horuxfin.com`) y sin tópicos suscritos
|
||||
|
||||
**Acciones:**
|
||||
- Corregido typo en `.env`: `MP_ACCESS_TOKEN`
|
||||
- Sincronizado manualmente el pago en BD:
|
||||
- Creado registro `Payment` con `mpPaymentId = 158527899608`
|
||||
- Actualizado suscripción a `status = authorized`
|
||||
- Actualizado `currentPeriodEnd = 2026-06-09`
|
||||
- Configurada URL de webhook en dashboard de MercadoPago: `https://horuxfin.com/api/webhooks/mercadopago`
|
||||
- Seleccionados tópicos: `payment`, `subscription_preapproval`
|
||||
|
||||
**Estado:** ✅ Resuelto
|
||||
|
||||
---
|
||||
|
||||
## 2. Fix: Webhook MercadoPago - validación de firma
|
||||
|
||||
**Problema:** Error recurrente en logs:
|
||||
```
|
||||
TypeError: Cannot read properties of undefined (reading 'trim')
|
||||
```
|
||||
|
||||
**Causa raíz:** `mercadopago.service.ts::verifyWebhookSignature` asumía que `x-signature` siempre tenía formato `key=value` bien formado.
|
||||
|
||||
**Fix:**
|
||||
```ts
|
||||
// Antes
|
||||
const [key, value] = part.split('=');
|
||||
parts[key.trim()] = value.trim();
|
||||
|
||||
// Después
|
||||
const [key, value] = part.split('=');
|
||||
if (!key || value === undefined) continue;
|
||||
parts[key.trim()] = value.trim();
|
||||
```
|
||||
|
||||
**Archivo:** `apps/api/src/services/payment/mercadopago.service.ts`
|
||||
|
||||
---
|
||||
|
||||
## 3. Notificación de primer pago pendiente de factura
|
||||
|
||||
**Problema:** Cuando un tenant realiza su primer pago, el sistema no factura automáticamente (por diseño), pero tampoco notifica al admin global.
|
||||
|
||||
### 3.1 Email al admin global
|
||||
|
||||
**Nuevos archivos:**
|
||||
- `apps/api/src/services/email/templates/primer-pago-facturar.ts` — Template HTML del email
|
||||
|
||||
**Modificaciones:**
|
||||
- `apps/api/src/services/email/email.service.ts` — Agregada función `sendPrimerPagoFacturar()`
|
||||
- `apps/api/src/services/payment/invoicing.service.ts` — Cuando `emitInvoiceIfApplicable` detecta primer pago, envía email al admin
|
||||
|
||||
**Contenido del email:**
|
||||
- Nombre, RFC del cliente
|
||||
- Plan, monto, fecha de pago
|
||||
- Botón directo a `/admin/facturas-pendientes`
|
||||
|
||||
### 3.2 Endpoints para admin global
|
||||
|
||||
**Nuevos endpoints en `apps/api/src/routes/facturacion.routes.ts`:**
|
||||
- `GET /facturacion/pagos-sin-factura` — Lista payments `approved` sin `facturapiInvoiceId`
|
||||
- `POST /facturacion/emitir-factura-pago/:paymentId` — Emite factura manual de un payment
|
||||
|
||||
**Nuevas funciones en `apps/api/src/controllers/facturacion.controller.ts`:**
|
||||
- `getPagosSinFactura()` — Query con `hasPlatformRole('platform_admin')`
|
||||
- `emitirFacturaPago()` — Emite factura usando datos fiscales del tenant pagador
|
||||
|
||||
**Exports agregados en `apps/api/src/services/payment/invoicing.service.ts`:**
|
||||
- `getEmitterTenant()`
|
||||
- `getCustomerFromTenant()`
|
||||
- `buildInvoicePayload()`
|
||||
|
||||
### 3.3 Página de admin
|
||||
|
||||
**Nuevos archivos:**
|
||||
- `apps/web/app/(dashboard)/admin/facturas-pendientes/page.tsx` — Tabla de pagos sin factura con botón "Emitir factura"
|
||||
- `apps/web/lib/hooks/use-pagos-sin-factura.ts` — Hooks React Query
|
||||
|
||||
**Modificaciones:**
|
||||
- `apps/web/lib/api/facturacion.ts` — Funciones `getPagosSinFactura()` y `emitirFacturaPago()`
|
||||
- `apps/web/app/(dashboard)/clientes/page.tsx` — Métrica "Facturas pendientes" en KPIs
|
||||
|
||||
---
|
||||
|
||||
## 4. Fix: Vinculación de organización Facturapi - Horux 360
|
||||
|
||||
**Problema:** El tenant emisor Horux 360 (RFC `HTS240708LJA`) no tenía organización Facturapi vinculada. Al intentar emitir facturas daba:
|
||||
```
|
||||
Tenant emisor no tiene organización Facturapi
|
||||
```
|
||||
|
||||
**Descubrimiento:** La BD del tenant (`horux_hts240708lja`) tenía una org incorrecta en `facturapi_orgs` (`69ff900f48058f06ef1234c0`) que no existía en Facturapi.
|
||||
|
||||
**Acciones:**
|
||||
|
||||
### BD Central
|
||||
```sql
|
||||
UPDATE tenants
|
||||
SET facturapi_org_id = '69f23a5a242e0af47a41fa0d',
|
||||
facturapi_org_key_enc = <encriptado>,
|
||||
facturapi_org_key_iv = <encriptado>,
|
||||
facturapi_org_key_tag = <encriptado>
|
||||
WHERE rfc = 'HTS240708LJA';
|
||||
```
|
||||
|
||||
### BD del tenant (`horux_hts240708lja`)
|
||||
```sql
|
||||
UPDATE facturapi_orgs
|
||||
SET facturapi_org_id = '69f23a5a242e0af47a41fa0d',
|
||||
api_key_enc = <encriptado>,
|
||||
api_key_iv = <encriptado>,
|
||||
api_key_tag = <encriptado>
|
||||
WHERE contribuyente_id = '96f98a42-5f27-4f27-acf6-61822dea666c';
|
||||
```
|
||||
|
||||
**API key generada:** `sk_live_bQC3XW7ZUVZxp9k9utN7DP6bRqehFZnZPtXhnDf1v1`
|
||||
|
||||
**Estado:** ✅ Resuelto
|
||||
|
||||
---
|
||||
|
||||
## 5. Fix: Autocompletado de RFCs y conceptos en facturación
|
||||
|
||||
**Problema:** Cuando un contribuyente estaba seleccionado en el dashboard, el autocompletado de RFCs y conceptos devolvía vacío si ese contribuyente no tenía CFDIs previos.
|
||||
|
||||
**Causa raíz:** Ambos endpoints filtraban por `contribuyente_id`, buscando solo en el historial del contribuyente activo.
|
||||
|
||||
**Fix aplicado:**
|
||||
- `searchRfcs()` — eliminado filtro por `contribuyenteId`. Ahora busca en el catálogo completo de `rfcs`.
|
||||
- `searchConceptos()` — eliminado filtro por `contribuyenteId`. Ahora busca conceptos en todos los CFDIs del tenant.
|
||||
|
||||
**Archivo:** `apps/api/src/controllers/facturacion.controller.ts`
|
||||
|
||||
---
|
||||
|
||||
## Archivos modificados
|
||||
|
||||
### 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` |
|
||||
|
||||
### 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
|
||||
Reference in New Issue
Block a user