- Catálogo de obligaciones fiscales expandido a 30 entradas con campo requierePago. - Soporte de frecuencia cuatrimestral en obligaciones y declaraciones. - Automatización de cierre de obligaciones fiscales desde Documentos › Declaraciones. - Nuevas tablas obligacion_evidencias, obligacion_periodos estados y declaracion_obligaciones. - Nuevo servicio obligacion-evidencias.service.ts y endpoints REST. - Refactor de declaraciones.service.ts para vincular obligaciones y crear evidencias. - Notificaciones por email para evidencias de obligaciones. - Adjuntar PDFs en correo de declaración subida. - Fix drill-down de CFDIs: carga completa al visualizar. - Fix sincronización SAT: tipos P/N, UUID case-insensitive, no reutilizar requestId. - Fix suscripciones pending en /configuracion/planes-despacho. - Fix sugerencias de Clave Producto SAT: importar catálogo y robustecer autocomplete. - Quitar toggle manual de completado en Configuración › Obligaciones fiscales › Tareas. - Scripts de soporte para Demo Ventas y utilerías (change-user-email, resend-welcome, import-clave-prod-serv). - Documentación de cambios en docs/CAMBIOS-2026-05-04.md.
347 lines
17 KiB
Markdown
347 lines
17 KiB
Markdown
# 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"` |
|
||
|
||
## Deploy
|
||
|
||
```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
|