Files
HoruxDespachosNuevo/docs/CAMBIOS-2026-05-04.md

905 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Resumen de cambios - 4 de mayo de 2026
---
## 1. Catálogo de obligaciones fiscales: nuevas obligaciones predefinidas
**Fecha:** 2026-05-04
Se agregaron 3 obligaciones fiscales predefinidas al catálogo maestro.
### Obligaciones agregadas
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `isrtp` | Impuesto sobre remuneración al trabajo | mensual | Día 10 del mes siguiente | PM y PF | Estatal | Ninguna | No |
| `ish` | ISH - Impuesto Sobre Hospedaje | mensual | Día 15 del mes siguiente | PM y PF | Estatal | Ninguna | No |
| `sipare` | SIPARE - Cuotas obrero-patronales | mensual | Día 15 del mes siguiente | PM y PF | Seguridad social | Con empleados | No |
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Se agregaron las 3 entradas al array `OBLIGACIONES_CATALOGO` |
---
## 2. Fix: Suscripciones `pending` se mostraban como activas en /configuracion/planes-despacho
**Fecha:** 2026-06-18
### Problema
En la página **Configuración Planes**, las suscripciones con estado `pending` (primer pago aún no completado) mostraban el banner verde **"Suscripción activa"** y el badge **"Plan actual"** en verde, dando la impresión de que el plan estaba pagado y vigente.
### Causa
El frontend evaluaba `subStatus === 'authorized' || subStatus === 'pending'` para mostrar el banner de activa, y consideraba `pending` como "plan actual pagado" (`isCurrentPlanPaid`).
### Solución
- Se derivó el estado real de la suscripción con `getSubscriptionState()` de `@horux/shared`.
- El banner **"Suscripción activa"** ahora solo aparece cuando la suscripción está realmente `authorized` y dentro de su período.
- Se agregó un banner amarillo **"Suscripción pendiente de pago"** para estados `pending`.
- El badge del plan actual cambia a amarillo y muestra **"Plan actual — pendiente"** cuando la suscripción está pendiente.
- El botón **"Cancelar suscripción"** ya no se muestra para suscripciones `pending`.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx` | Lógica de estado de suscripción, banners y badges |
---
## 3. Fix: Botón "Pagar este plan" fallaba para suscripciones `pending`
**Fecha:** 2026-06-18
### Problema
Al hacer clic en **"Pagar este plan"** en una suscripción con estado `pending`, se mostraba el error:
**"No hay suscripción activa para cambiar"** en lugar de abrir MercadoPago.
### Causa
El flujo `handleContratar` intentaba crear una nueva suscripción (`subscribeMe`), pero el backend rechazaba porque ya existía una `pending`. El frontend entonces caía en `upgradeMe` y luego `changeMyPlan`, ambos validan que haya una suscripción `authorized` o `trial``pending` no califica, por eso el error.
### Solución
En `handleContratar`:
- Si el usuario selecciona el plan actual y la suscripción está `pending`, se llama directamente a `generatePaymentLink` para regenerar el link de pago de MercadoPago.
- Si el usuario intenta cambiar a otro plan estando `pending`, se muestra:
*"Completa el pago del plan actual antes de cambiar de plan."*
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx` | Lógica de `handleContratar` para estados `pending` |
---
## 4. Adjuntar PDFs en el correo de declaración subida
**Fecha:** 2026-05-04
### Cambio
Cuando se sube una declaración provisional (`POST /api/documentos/declaraciones`), el correo de notificación a owners y supervisor ahora incluye como adjuntos:
- El **acuse de declaración** (`pdf_declaracion`).
- La **liga de pago** (`pdf_liga_pago`), si se subió.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `packages/core/src/email/transport.ts` | `EmailTransport.send` acepta un arreglo opcional de `EmailAttachment` y lo pasa a `nodemailer.sendMail` |
| `apps/api/src/services/email/email.service.ts` | `sendEmail` y `sendDocumentoSubido` aceptan y reenvían `attachments` |
| `apps/api/src/services/notify-upload.service.ts` | Nueva función `buildDeclaracionAttachments` que lee los PDFs de `declaraciones_provisionales` y los pasa al correo |
| `apps/api/src/controllers/documentos.controller.ts` | Se pasa `declaracionId` a `notifyDocumentoSubido` para poder recuperar los PDFs |
### Notas
- Los documentos extra (`POST /api/documentos/extras`) **no** incluyen adjuntos; solo cambia el flujo de declaraciones.
- Si los adjuntos superan los 20 MB, se omiten y se deja un aviso en el cuerpo del correo para evitar rechazos por límite de SMTP.
## 5. Nueva obligación: FONACOT
**Fecha:** 2026-05-04
### Cambio
Se agregó la obligación `fonacot` al catálogo maestro de obligaciones fiscales.
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `fonacot` | Crédito FONACOT | Mensual | Día 5 del mes siguiente | PM/PF | Créditos de los trabajadores | Con empleados | ❌ |
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Se agregó la entrada `fonacot` en la sección **Créditos de los trabajadores** |
## 6. Nueva obligación: Aviso de actividades vulnerables
**Fecha:** 2026-05-04
### Cambio
Se agregó la obligación `actividades-vulnerables` al catálogo maestro.
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `actividades-vulnerables` | Aviso de actividades vulnerables | Mensual | Día 17 del mes siguiente | PM/PF | Federal mensual | — | ❌ |
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Se agregó la entrada `actividades-vulnerables` en la sección **Federales mensuales** |
## 7. Nueva obligación: Declaración Informativa de transparencia
**Fecha:** 2026-05-04
### Cambio
Se agregó la obligación `declaracion-transparencia` al catálogo maestro.
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `declaracion-transparencia` | Declaración Informativa de transparencia | Anual | Día 31 de mayo | PM | Federal anual | — | ❌ |
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Se agregó la entrada `declaracion-transparencia` en la sección **Anuales PM** |
## 8. Nueva obligación: Declaración Informativa Múltiple del IEPS (trimestral)
**Fecha:** 2026-05-04
### Cambio
Se agregó la obligación `ieps-trimestral` al catálogo maestro.
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `ieps-trimestral` | Declaración Informativa Múltiple del IEPS | Trimestral | Día 17 de abril, julio, octubre y enero | PM/PF | Federal trimestral | — | ❌ |
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Se agregó la entrada `ieps-trimestral` en la nueva sección **Federales trimestrales** |
## 9. Nueva obligación: SISUB y soporte de frecuencia cuatrimestral
**Fecha:** 2026-05-04
### Cambio
Se agregó la obligación `sisub` al catálogo y se extendió el sistema para soportar obligaciones con frecuencia **cuatrimestral**.
| ID | Nombre | Frecuencia | Fecha límite | Aplica a | Categoría | Condición | Recomendada por defecto |
|---|---|---|---|---|---|---|---|
| `sisub` | Sistema de Información de Subcontratación | Cuatrimestral | Día 17 de enero, mayo y septiembre | PM/PF | Seguridad social | Con empleados | ❌ |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Agregada `sisub` y `cuatrimestral` al union type de `frecuencia` |
| `apps/api/src/services/obligaciones.service.ts` | `inferirFrecuencia` y `appliesTo` soportan `cuatrimestral` |
| `apps/api/src/services/calendario-fiscal.service.ts` | Generación de eventos para meses cuatrimestrales (`1, 5, 9`) |
| `apps/api/src/services/alertas-manuales.service.ts` | `appliesToPeriod` soporta `cuatrimestral` |
| `apps/api/src/services/declaraciones.service.ts` | `Periodicidad` incluye `cuatrimestral` |
| `apps/api/src/controllers/documentos.controller.ts` | Schema de declaraciones acepta `cuatrimestral` |
| `apps/api/src/migrations/tenant/052_declaraciones_cuatrimestral.sql` | CHECK de `periodicidad` permite `cuatrimestral` |
| `apps/web/app/(dashboard)/configuracion/obligaciones/page.tsx` | Badge de frecuencia `cuatrimestral` |
| `apps/web/app/(dashboard)/pendientes/page.tsx` | Badge de frecuencia `cuatrimestral` |
## 10. Fix: sincronización SAT — tipos de CFDI, UUID case-insensitive y reutilización de requestIds
**Fecha:** 2026-05-04
### Cambios
- La verificación de CFDIs incompletos (`hasIncompleteCfdis` / `getOldestIncompleteCfdiDate`) ahora incluye los tipos de comprobante **P** (pago) y **N** (nómina), además de **I** (ingreso) y **E** (egreso).
- Al guardar/actualizar CFDIs, la comparación de `uuid` se hace con `LOWER()` para evitar duplicados por diferencias de mayúsculas/minúsculas.
- Se desactivó la reutilización de `requestId` de jobs SAT previos. Reusarlos puede agotar el límite de descargas del SAT y devolver **"Máximo de descargas permitidas"**, bloqueando el recovery.
- Se exportó `runRecoverySyncJob` para permitir su invocación manual desde scripts.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/jobs/sat-sync.job.ts` | Incluir `P` y `N` en consultas de CFDIs incompletos; exportar `runRecoverySyncJob` |
| `apps/api/src/services/sat/sat.service.ts` | Comparación `LOWER(uuid)`; comentar reutilización de `requestId` |
---
## 11. Fix: drill-down de CFDIs carga el CFDI completo al visualizar
**Fecha:** 2026-05-04
### Problema
En la vista de drill-down, al hacer clic en el ojo para ver un CFDI se usaba únicamente el objeto resumen de la lista, que no incluye conceptos ni todos los detalles.
### Solución
Ahora se llama a `getCfdiById(id)` para obtener el CFDI completo antes de abrir el visor, y se muestra un estado de carga mientras se resuelve la petición.
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/drill-down/page.tsx` | Carga completa del CFDI al hacer clic en "Ver factura" |
---
## 12. Scripts de soporte: Demo Ventas y operaciones
**Fecha:** 2026-05-04
Se crearon varios scripts de utilería bajo `apps/api/scripts/` para tareas de soporte y configuración de la cuenta Demo Ventas.
### Scripts principales
| Script | Propósito |
|---|---|
| `create-demo-ventas.ts` | Crea el tenant Demo Ventas, su BD, usuario owner y suscripción custom gratuita |
| `update-demo-ventas.ts` | Agrega usuarios supervisor/auxiliar/cliente y 5 contribuyentes adicionales a Demo Ventas |
| `seed-demo-obligaciones-tareas.ts` | Siembra obligaciones fiscales y tareas recurrentes para todos los contribuyentes de Demo Ventas |
| `fix-demo-carteras-asignaciones.ts` | Crea la subcartera del auxiliar y asigna contribuyentes, obligaciones y tareas de forma válida |
| `reset-demo-asignaciones.ts` | Deja Demo Ventas en estado "tutorial": elimina subcarteras, asignaciones y relación auxiliar-supervisor |
| `change-user-email.ts` | Cambia el correo de un usuario, genera contraseña temporal e invalida sesiones |
| `resend-welcome.ts` | Reenvía el correo de bienvenida a un usuario |
> Estos scripts no son parte del flujo productivo; se ejecutan manualmente vía `npx tsx`.
## 13. Automatización de cierre de obligaciones fiscales
**Fecha:** 2026-05-04
### Cambio
Se automatiza el cierre de **todas las obligaciones fiscales** desde la sección existente **Documentos Declaraciones**. Al subir una declaración o su comprobante de pago, el sistema crea automáticamente evidencias en `obligacion_evidencias` y actualiza el estado de cada obligación fiscal en `obligacion_periodos`.
### Reglas de cierre deterministas
- `requierePago = false` (informativas): se marcan completadas al subir la declaración (`declaracion`).
- `requierePago = true` (pago + declaración): la declaración marca `declaracion_presentada = true`; el periodo se cierra al subir el comprobante de pago (`pago`).
- Al subir una declaración con **monto $0**, se marca el pago como presentado automáticamente.
### Nuevas tablas y columnas
| Migración | Descripción |
|---|---|
| `053_obligacion_evidencias.sql` | Tabla genérica para evidencias de obligaciones (declaración, pago, acuse, complemento) |
| `054_obligacion_periodos_estados.sql` | Agrega `declaracion_presentada`, `pago_presentado` y `evidencia_id` a `obligacion_periodos` |
| `055_declaracion_obligaciones.sql` | Relaciona declaraciones provisionales con las obligaciones fiscales que cierran |
### Nuevos endpoints (uso interno / futuro)
| Método | Endpoint | Descripción |
|---|---|---|
| `GET` | `/api/documentos/obligacion-evidencias` | Listar evidencias por contribuyente/periodo/obligación |
| `POST` | `/api/documentos/obligacion-evidencias` | Subir nueva evidencia |
| `GET` | `/api/documentos/obligacion-evidencias/:id/pdf` | Descargar PDF de evidencia |
| `DELETE` | `/api/documentos/obligacion-evidencias/:id` | Eliminar evidencia y recalcular estado del periodo |
### Archivos creados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/obligacion-evidencias.service.ts` | Servicio para crear/listar/descargar/eliminar evidencias y actualizar `obligacion_periodos` |
| `apps/api/src/migrations/tenant/053_obligacion_evidencias.sql` | Tabla `obligacion_evidencias` |
| `apps/api/src/migrations/tenant/054_obligacion_periodos_estados.sql` | Columnas de estado en `obligacion_periodos` |
| `apps/api/src/migrations/tenant/055_declaracion_obligaciones.sql` | Relación declaración ↔ obligación |
| `apps/web/lib/api/obligaciones.ts` | Cliente API para obtener obligaciones por periodo |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/constants/obligaciones-fiscales.ts` | Campo `requierePago` en todas las obligaciones del catálogo |
| `apps/api/src/services/declaraciones.service.ts` | Crea evidencias en las obligaciones seleccionadas; vincula declaración con obligaciones; mantiene fallback legacy por impuestos |
| `apps/api/src/services/obligaciones.service.ts` | `getObligacionesPorPeriodo` devuelve `requierePago`, `declaracionPresentada`, `pagoPresentado` |
| `apps/api/src/services/notify-upload.service.ts` | Soporte para notificaciones de `obligacion_evidencia` |
| `apps/api/src/services/email/templates/documento-subido.ts` | Template para evidencias de obligación |
| `apps/api/src/controllers/documentos.controller.ts` | Schema de declaraciones acepta `obligacionesIds` |
| `apps/api/src/routes/documentos.routes.ts` | Rutas de evidencias |
| `apps/web/lib/api/declaraciones.ts` | `CreateDeclaracionData` acepta `obligacionesIds` |
| `apps/web/app/(dashboard)/documentos/page.tsx` | Diálogo de subida reemplaza “Impuestos cubiertos” por selector de obligaciones fiscales del periodo |
## 15. Fix: quitar toggle de completado en Configuración Obligaciones fiscales Tareas
**Fecha:** 2026-06-22
### Problema
En **Configuración Obligaciones fiscales Tareas** seguía apareciendo el botón para marcar tareas como completadas/pendientes manualmente, pero el estado de las obligaciones fiscales ahora se actualiza automáticamente desde **Documentos Declaraciones**.
### Solución
- Se convirtió el icono de check/círculo en un indicador visual de estado (completada, pendiente, atrasada) sin interacción.
- Se eliminaron las mutaciones de completar/descompletar periodo del frontend.
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/web/components/obligaciones/tareas-tab.tsx` | Icono de estado estático; eliminados `completarMutation` y `descompletarMutation` |
## 14. Fix: sugerencias de Clave Producto SAT en facturación
**Fecha:** 2026-06-22
### Problema
En **Facturación Conceptos**, el campo **Clave Producto SAT** no mostraba sugerencias al escribir.
### Causa
La tabla `cat_clave_prod_serv` de la BD central estaba vacía; el catálogo nunca se había importado.
### Solución
- Se importó el catálogo oficial CFDI 4.0 (`c_ClaveProdServ`) desde los recursos de **phpcfdi/resources-sat-catalogs** (52,513 registros).
- Se creó el script `apps/api/scripts/import-clave-prod-serv.ts` para importaciones futuras.
- Se hizo más robusto el autocomplete del campo:
- `AbortController` para cancelar búsquedas anteriores.
- Manejo de errores y `autoComplete="off"`.
- Se sanitizó el fallback regex en el backend para evitar errores con caracteres especiales.
### Archivos creados
| Archivo | Cambio |
|---|---|
| `apps/api/scripts/import-clave-prod-serv.ts` | Importa el catálogo desde CSV a PostgreSQL |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/controllers/catalogos.controller.ts` | Escapa regex en búsqueda fallback; búsqueda por clave insensible a mayúsculas |
| `apps/web/lib/api/catalogos.ts` | `searchClaveProdServ` acepta `AbortSignal` |
| `apps/web/app/(dashboard)/facturacion/page.tsx` | `handleSearchProduct` con `AbortController`, try/catch y `autoComplete="off"` |
## 16. Fix: CSF retry, backoff, delays entre tenants y timeouts aumentados
**Fecha:** 2026-06-01
### Cambios
- Se agregó **retry con backoff** en la descarga de Constancias de Situación Fiscal.
- Se agregaron **delays entre tenants** para no saturar el portal del SAT.
- Se aumentaron los timeouts del proceso de constancia.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/constancia.service.ts` | Retry con backoff y manejo de errores |
| `apps/api/src/services/sat/sat-csf-login.ts` | Timeouts y delays ajustados |
| `apps/api/src/jobs/sat-sync.job.ts` | Delay entre ejecuciones de CSF por tenant |
---
## 17. Fix: múltiples fixes de producción en SAT, pagos y admin
**Fecha:** 2026-06-10
### Cambios
- Ajustes en el pool de conexiones a tenants (`database.ts`).
- Webhooks de MercadoPago más robustos ante eventos inesperados.
- Mejoras en `admin-clientes.service.ts` e `invoicing.service.ts`.
- Sweep de jobs SAT stale ajustado.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/config/database.ts` | Configuración de pool |
| `apps/api/src/controllers/webhook.controller.ts` | Robustez en webhooks MP |
| `apps/api/src/jobs/sat-sync.job.ts` | Fixes en recovery y scheduling |
| `apps/api/src/services/admin-clientes.service.ts` | Fixes de admin |
| `apps/api/src/services/payment/invoicing.service.ts` | Fixes de facturación |
| `apps/api/src/services/sat/sat-client.service.ts` | Fixes de cliente SAT |
| `apps/api/src/services/sat/sweep-stale-jobs.service.ts` | Ajuste de stale jobs |
---
## 18. Feat: scorecards de notas de crédito en dashboard
**Fecha:** 2026-06-13
### Cambios
- Se agregaron scorecards de **notas de crédito emitidas** y **recibidas**.
- Se reordenaron las tarjetas del dashboard.
- Se ajustó el cálculo de **utilidad neta** considerando notas de crédito.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/dashboard.service.ts` | Nuevos campos de notas de crédito |
| `apps/web/app/(dashboard)/dashboard/page.tsx` | Scorecards y utilidad ajustada |
| `packages/shared/src/types/dashboard.ts` | Tipos actualizados |
---
## 19. Feat: cron de recuperación SAT diario a las 10:00 AM
**Fecha:** 2026-06-14
### Cambio
Se agregó un cron diario a las 10:00 AM (`America/Mexico_City`) que ejecuta el **recovery sync** para contribuyentes con FIEL activa y sync incompleto.
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/jobs/sat-sync.job.ts` | Nuevo `runRecoverySyncJob` y scheduling |
---
## 20. Fix: pagar plan actual en trial_expired y planes > $10k
**Fecha:** 2026-06-16
### Problema
- Los tenants con suscripción `trial_expired` no podían pagar el plan actual.
- Planes Business Control/Enterprise superiores a $10,000 no podían usar preapproval de MercadoPago.
### Solución
- Se permite pagar el plan actual incluso en estado `trial_expired`.
- Planes caros usan una **Preference one-off anual** de MercadoPago en lugar de preapproval.
- Se agregó el campo `mpPreferenceId` a `subscriptions` para trackear la preference.
- El webhook de MP maneja tanto preapproval como preference.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/controllers/despacho.controller.ts` | Lógica de pago de plan actual |
| `apps/api/src/controllers/subscription.controller.ts` | Soporte de preference |
| `apps/api/src/controllers/webhook.controller.ts` | Webhook para preference |
| `apps/api/src/services/payment/mercadopago.service.ts` | Creación de preference anual |
| `apps/api/src/services/payment/subscription.service.ts` | Pago de plan actual trial_expired |
| `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx` | UI para pagar plan actual vencido |
| `apps/api/prisma/schema.prisma` | Campo `mpPreferenceId` |
---
## 21. Feat: configuración de notificaciones por rol
**Fecha:** 2026-06-17
### Cambio
Se reemplazó el sistema de preferencias de notificación por contribuyente por uno por **rol**:
- `owner`, `supervisor`, `auxiliar` y `cliente` pueden activar/desactivar tipos de correo.
- Tipos: `documento_subido`, `weekly_update`, `subscription_expiring`, `recordatorio_fiscal`, `alertas_nuevas`, `recordatorio_proximo`.
- La tabla `notification_role_preferences` almacena la configuración por tenant.
### Archivos creados
| Archivo | Cambio |
|---|---|
| `apps/api/src/migrations/tenant/051_notification_role_preferences.sql` | Tabla de preferencias por rol |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/notification-preferences.service.ts` | Nuevo modelo por rol |
| `apps/api/src/controllers/notification-preferences.controller.ts` | CRUD por rol |
| `apps/api/src/services/notifications.service.ts` | Filtrado por rol |
| `apps/api/src/services/notify-upload.service.ts` | Usa preferencias por rol |
| `apps/api/src/services/payment/subscription.service.ts` | Usa preferencias por rol |
| `apps/api/src/jobs/weekly-update.job.ts` | Filtrado por rol |
| `apps/web/app/(dashboard)/configuracion/notificaciones/page.tsx` | UI de configuración |
---
## 22. Fix: quitar badge "Próximamente" de notificaciones ya existentes
**Fecha:** 2026-06-16
### Cambio
Se quitó el badge "Próximamente" de los tipos de notificación que ya estaban implementados.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/notification-preferences.service.ts` | Marcado de implementados |
| `apps/web/app/(dashboard)/configuracion/notificaciones/page.tsx` | Badges actualizados |
---
## 23. Feat: drill-down en pestaña nueva, rol Vendedor y scripts demo
**Fecha:** 2026-06-22
### Cambios
- El **drill-down de impuestos** ahora se abre en una pestaña nueva (`/drill-down`).
- Se agregó el rol **Vendedor** con acceso limitado a invitaciones trial y dashboard.
- Se crearon scripts de soporte para demos:
- `add-demo-cfdis.ts`
- `add-demo-notas-credito.ts`
- `create-vendedor-fernando.ts`
### Archivos creados
| Archivo | Cambio |
|---|---|
| `apps/api/scripts/add-demo-cfdis.ts` | CFDIs de demo |
| `apps/api/scripts/add-demo-notas-credito.ts` | Notas de crédito de demo |
| `apps/api/scripts/create-vendedor-fernando.ts` | Crea usuario vendedor de demo |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/drill-down/page.tsx` | Página de drill-down |
| `apps/web/app/(dashboard)/impuestos/page.tsx` | Link a drill-down en pestaña nueva |
| `apps/web/app/(dashboard)/dashboard/page.tsx` | Integración con drill-down |
| `apps/web/app/(dashboard)/admin/staff/page.tsx` | Rol Vendedor en staff |
| `apps/web/components/layouts/sidebar.tsx` | Menú para Vendedor |
| `packages/shared-ui/src/charts/kpi-card.tsx` | Soporte de link externo |
---
## 24. Fix: evita logout al cambiar de tenant
**Fecha:** 2026-06-22
### Problema
Al cambiar de empresa con el selector de tenants, múltiples requests en vuelo con el token viejo intentaban refrescar simultáneamente, causando logout o 401.
### Solución
- Se agregó un **mutex de refresh token** en el interceptor de Axios.
- Se marca el estado `isSwitching` en `membership-switcher` y `mis-empresas` para evitar clicks múltiples.
- El backend invalida el refresh token anterior al hacer switch.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/lib/api/client.ts` | Mutex de refresh |
| `apps/web/components/membership-switcher.tsx` | Flag `isSwitching` |
| `apps/web/app/(dashboard)/mis-empresas/page.tsx` | Flag `isSwitching` |
| `apps/api/src/services/auth.service.ts` | Invalidación de refresh token anterior |
---
## 25. Fix: Vendedor accede a invitaciones trial
**Fecha:** 2026-06-22
### Cambio
El rol **Vendedor** puede ver y compartir invitaciones **trial**, pero no puede invitar clientes directamente.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/controllers/client-invitations.controller.ts` | Permisos para vendedor |
| `apps/api/src/controllers/tenants.controller.ts` | Permisos para vendedor |
| `apps/web/app/(dashboard)/admin/staff/page.tsx` | UI de permisos |
| `apps/web/components/layouts/sidebar.tsx` | Menú visible para vendedor |
---
## 26. Feat: monitor de sincronización SAT
**Fecha:** 2026-06-22
### Cambio
Se agregó un cron de monitoreo de sincronizaciones SAT (cada 2 horas por defecto). Detecta:
- Jobs fallidos en las últimas N horas.
- Jobs pending sin atender.
- Jobs running atascados.
- Contribuyentes con FIEL activa pero sin sync inicial.
Envía un correo de alerta a `SAT_ALERT_EMAIL` (o `ADMIN_EMAIL` si no está configurado).
### Variables de entorno
| Variable | Default | Descripción |
|---|---|---|
| `SAT_ALERT_EMAIL` | `ADMIN_EMAIL` | Destino de alertas SAT |
| `SAT_MONITOR_SCHEDULE` | `0 */2 * * *` | Cron del monitor |
| `SAT_STUCK_RUNNING_HOURS` | `2` | Horas para considerar un job running como atorado |
| `SAT_FAILED_LOOKBACK_HOURS` | `24` | Ventana hacia atrás para reportar jobs fallidos |
### Archivos creados
| Archivo | Cambio |
|---|---|
| `apps/api/src/jobs/sat-sync-monitor.job.ts` | Lógica del monitor |
| `apps/api/src/services/email/templates/sat-sync-alert.ts` | Template de alerta |
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/index.ts` | Arranca el monitor |
| `apps/api/src/config/env.ts` | Validación de variables |
| `apps/api/.env.example` | Documentación de variables |
| `apps/api/src/services/email/email.service.ts` | `sendSatSyncAlert` |
---
## 27. Feat: reactivación de contribuyentes y limpieza al desactivar
**Fecha:** 2026-06-22
### Cambios
- Al crear un contribuyente cuyo RFC ya existía desactivado, se **reactiva** la entidad, se actualizan sus datos y se recupera su historial (CFDIs, FIEL, tareas, etc.).
- Al desactivar un contribuyente, se limpia de `cartera_entidades`, `cliente_accesos` y se desactiva su FIEL para que no siga sincronizándose.
- El timeout del proceso de constancia se aumentó a **5 minutos**.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/contribuyente.service.ts` | Reactivación y limpieza al desactivar |
| `apps/api/src/controllers/contribuyente.controller.ts` | Retorna `reactivated` y status 200/201 |
| `apps/api/src/services/constancia.service.ts` | Timeout 5 min |
| `apps/web/app/(dashboard)/contribuyentes/page.tsx` | Alerta al reactivar |
| `apps/web/lib/api/contribuyentes.ts` | Tipo `reactivated` |
---
## 28. Fix: scraper de Constancia de Situación Fiscal más robusto
**Fecha:** 2026-06-22
### Cambio
Se reescribió el scraper de CSF para soportar los múltiples flujos del portal SAT:
- Búsqueda de PDF en frames, iframes, embeds, links `blob:`/`data:` y popups.
- Validación de que el buffer descargado comienza con `%PDF-`.
- Fallback de click por `dispatchEvent` si el primer click no dispara el handler JSF.
### Archivo modificado
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/sat/sat-csf-scraper.ts` | Reescritura del scraper |
## 29. Actualización de precios: Business Control, Enterprise, RFCs adicionales y MSI
**Fecha:** 2026-06-25
### Cambio
Se actualizaron los precios de los planes anuales de despacho y la política de cobro por RFC adicional.
### Planes anuales
| Plan | Precio anterior | Nuevo precio (primer año / renovación) |
|---|---|---|
| **Business Control** (`business_control`) | $25,850 | **$30,850** |
| **Enterprise** (`business_cloud`) | $43,000 | **$68,850** |
Los cambios se aplican en la tabla `despacho_plan_prices` y afectan **nuevas suscripciones** y **renovaciones futuras** generadas por el sistema.
### RFCs adicionales (overage)
El precio mensual por cada contribuyente extra (más allá de los 100 incluidos) ahora depende del plan:
| Plan | Precio por RFC adicional |
|---|---|
| **Business Control** | $25/mes |
| **Enterprise** | $60/mes |
Se modificó `adjustDespachoOverage` para leer el precio desde el plan de la suscripción activa en lugar de usar un valor único de catálogo.
### Meses sin intereses (MSI)
Se habilitó el pago a **hasta 12 meses** en el checkout de MercadoPago para:
- Pago anual de Business Control / Enterprise (`createSubscriptionPreference`).
- Prorrateos de upgrade entre planes anuales (`createProrationPreference`).
> **Nota:** para que las cuotas aparezcan **sin intereses**, es necesario contar con promociones MSI activas en la cuenta de vendedor de MercadoPago. Sin promociones, el checkout mostrará meses con intereses.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/payment/addon.service.ts` | Precio de overage por plan ($25 / $60) |
| `apps/api/src/services/payment/mercadopago.service.ts` | `payment_methods.installments: 12` en preferences anuales y prorrateos |
| `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx` | Tarjetas de precios mostradas en UI: Business Control $30,850 y Enterprise $68,850; overage $25/$60 |
| `apps/api/prisma/seed` (implícito) | Valores actualizados en `despacho_plan_prices` vía SQL |
### Deploy
Build y reload de API para limpiar el cache de `despacho_plan_prices`:
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter api build
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## 30. Supervisor puede invitar Clientes y Auxiliares
**Fecha:** 2026-06-25
### Cambio
Antes los supervisores solo podían invitar usuarios con rol **Cliente**. Ahora también pueden invitar **Auxiliares**, siempre asignados a su propia supervisión.
### Backend
| Archivo | Cambio |
|---|---|
| `apps/api/src/controllers/usuarios.controller.ts` | Permite a `supervisor` invitar `cliente` y `auxiliar`; al invitar un auxiliar se asigna a sí mismo por defecto y no permite asignar a otro supervisor |
| `apps/api/src/routes/cartera.routes.ts` | `/carteras/supervisores` ahora acepta `owner` y `supervisor` |
| `apps/api/src/controllers/cartera.controller.ts` | `getSupervisores` filtra para que un supervisor solo se vea a sí mismo en el dropdown |
### Frontend
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/usuarios/page.tsx` | El dropdown de roles para supervisor incluye **Auxiliar**; al seleccionar auxiliar se preselecciona al supervisor logueado como responsable |
### Deploy
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter api build
pnpm --filter web build
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## 31. Owner puede editar nombre y rol de usuarios
**Fecha:** 2026-06-25
### Cambio
En la página **Configuración Usuarios**, el **owner** ahora puede editar el **nombre** y el **rol** de cualquier usuario del tenant desde un modal.
### Detalles
- Se agregó el botón **Editar** en cada fila de usuario (solo visible para `owner`).
- El modal permite cambiar:
- **Nombre**
- **Rol** (los roles disponibles dependen de si es tenant despacho o legacy)
- Un owner **no puede cambiar su propio rol** (solo su nombre).
- El backend ya tenía el endpoint `PATCH /usuarios/:id`; se ajustó la UI para exponerlo.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/usuarios/page.tsx` | Botón Editar, modal de edición, validación de rol propio |
### Deploy
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter web build
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## 32. Recordatorios periódicos en calendario
**Fecha:** 2026-06-25
### Cambio
Los recordatorios custom del calendario ahora pueden ser **periódicos**: mensual, bimestral, trimestral o anual. Se mantienen activos indefinidamente hasta que el usuario cancele la serie.
### Modelo
Se reutiliza la tabla `recordatorios` con metadatos de serie:
- **Maestro**: `serie_id IS NULL` y `recurrencia <> 'unica'`.
- **Instancias**: filas con `serie_id = maestro.id`, una por cada ocurrencia generada.
Esto permite completar una instancia sin afectar las demás, y el cron de emails de recordatorios próximos sigue funcionando sin cambios mayores.
### Funcionalidad
- Al crear un recordatorio periódico se generan instancias para un horizonte de 24 meses.
- Al **editar** una instancia se actualizan el maestro y **todas las ocurrencias futuras no completadas**.
- Al **eliminar** una instancia periódica se **cancela toda la serie**: se desactiva el maestro y se borran las instancias futuras no completadas.
- Un cron diario a las 6:00 AM extiende automáticamente las series activas para mantener el horizonte futuro.
### Frecuencias soportadas
- `mensual`
- `bimestral`
- `trimestral`
- `anual`
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/migrations/tenant/056_recordatorios_periodicos.sql` | Nuevas columnas `recurrencia`, `serie_id`, `activo`, `fecha_inicio`, `fecha_fin` e índices |
| `apps/api/src/services/recordatorios.service.ts` | Lógica de creación, edición, eliminación y regeneración de series periódicas |
| `apps/api/src/services/notifications.service.ts` | Excluir maestros del envío de recordatorios próximos |
| `apps/api/src/controllers/calendario.controller.ts` | Schemas aceptan `recurrencia` y `fechaFin` |
| `apps/api/src/jobs/recordatorios-periodicos.job.ts` | Nuevo cron de extensión de series |
| `apps/api/src/index.ts` | Registro del nuevo cron |
| `apps/web/app/(dashboard)/calendario/page.tsx` | Selector de recurrencia, fecha fin, indicador de serie y confirmación al cancelar |
### Deploy
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter api build
pnpm --filter web build
pnpm --filter @horux/api db:migrate-tenants
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## 33. Filtros de Conceptos con Aplicar/Limpiar
**Fecha:** 2026-06-30
### Cambio
En **CFDIs Conceptos**, los filtros de encabezado de tabla (UUID, Clave SAT, Descripción, No. Identificación) ahora se aplican únicamente al hacer clic en **Aplicar**, en lugar de disparar la búsqueda en cada cambio de teclado.
### Antes
Los inputs usaban `onChange` para actualizar `conceptosFilters`, lo que provocaba que `useQuery` re-lanzara la petición por cada letra escrita.
### Ahora
- Se separó el estado en dos:
- `conceptosDraftFilters`: valores que el usuario escribe en los popovers.
- `appliedConceptosFilters`: valores realmente aplicados a la query.
- **Aplicar**: copia el draft al estado aplicado, resetea paginación y cierra el popover.
- **Limpiar**: borra ese campo, aplica el cambio y resetea paginación.
- El ordenamiento por importe sigue funcionando con un clic en el encabezado.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/web/app/(dashboard)/cfdi/page.tsx` | Separación de draft/applied filters y nuevos handlers `applyConceptosFilters` / `clearConceptosFilter` |
| `apps/api/src/controllers/cfdi.controller.ts` | El endpoint `/cfdi/conceptos` ahora recibe y pasa `noIdentificacion` al servicio |
### Deploy
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter api build
pnpm --filter web build
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## 34. Filtro de fechas en CFDIs usa fecha de emisión
**Fecha:** 2026-06-30
### Cambio
El filtro de fechas en el listado y export de **CFDIs** ahora filtra por **`fecha_emision`** en lugar de `fecha_efectiva`.
### Antes
El where clause usaba:
```sql
COALESCE(fecha_efectiva, fecha_emision - interval '1 hour') BETWEEN fechaInicio AND fechaFin
```
Esto provocaba que facturas emitidas en meses anteriores pero pagadas/ejercidas en el rango seleccionado aparecieran en el resultado (por ejemplo, facturas de febrero con `fecha_efectiva` en junio).
### Ahora
El filtro usa directamente:
```sql
fecha_emision::date BETWEEN fechaInicio AND fechaFin
```
El export Excel hereda el mismo filtro, así que tabla y Excel son consistentes.
### Archivos modificados
| Archivo | Cambio |
|---|---|
| `apps/api/src/services/cfdi.service.ts` | Filtros de fecha en `getCfdis`, `getConceptosList` y `downloadXmlsZip` usan `fecha_emision::date` |
### Deploy
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter api build
pnpm --filter web build
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso
---
## Deploy histórico
### Preparación
1. Asegurarse de que `apps/api/.env` tenga las nuevas variables (tienen defaults, pero conviene declararlas):
```bash
SAT_ALERT_EMAIL=carlos@horuxfin.com
SAT_MONITOR_SCHEDULE=0 */2 * * *
SAT_STUCK_RUNNING_HOURS=2
SAT_FAILED_LOOKBACK_HOURS=24
```
2. Si hay scripts de debug antiguos en `apps/api/scripts/`, limpiar los que no sean necesarios antes de commitear.
### Comandos
```bash
cd /root/HoruxDespachosNuevo
pnpm --filter @horux/core build
pnpm --filter api build
pnpm --filter web build
npx tsx apps/api/scripts/migrate-tenants.ts
pm2 reload horux-api
pm2 reload horux-web
```
**Estado:** ✅ Exitoso