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
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:
.envteníaMP_ACCES_TOKEN(1 S) en lugar deMP_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
PaymentconmpPaymentId = 158527899608 - Actualizado suscripción a
status = authorized - Actualizado
currentPeriodEnd = 2026-06-09
- Creado registro
- 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ónsendPrimerPagoFacturar()apps/api/src/services/payment/invoicing.service.ts— CuandoemitInvoiceIfApplicabledetecta 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 paymentsapprovedsinfacturapiInvoiceIdPOST /facturacion/emitir-factura-pago/:paymentId— Emite factura manual de un payment
Nuevas funciones en apps/api/src/controllers/facturacion.controller.ts:
getPagosSinFactura()— Query conhasPlatformRole('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— FuncionesgetPagosSinFactura()yemitirFacturaPago()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 porcontribuyenteId. Ahora busca en el catálogo completo derfcs.searchConceptos()— eliminado filtro porcontribuyenteId. 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/:paymentIdrequiere rolplatform_admin - La regla "primer pago no se factura automáticamente" sigue vigente; los subsecuentes sí son automáticos