# 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 = , facturapi_org_key_iv = , facturapi_org_key_tag = WHERE rfc = 'HTS240708LJA'; ``` ### BD del tenant (`horux_hts240708lja`) ```sql UPDATE facturapi_orgs SET facturapi_org_id = '69f23a5a242e0af47a41fa0d', api_key_enc = , api_key_iv = , api_key_tag = 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