Files
HoruxDespachosNuevo/docs/CAMBIOS-2026-05-09.md
Horux Dev 9f11a0ba39 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
2026-05-09 21:56:42 +00:00

7.3 KiB

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:

// 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

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)

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