- 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.
17 KiB
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
authorizedy 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 ageneratePaymentLinkpara 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
uuidse hace conLOWER()para evitar duplicados por diferencias de mayúsculas/minúsculas. - Se desactivó la reutilización de
requestIdde 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ó
runRecoverySyncJobpara 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 marcadeclaracion_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.tspara importaciones futuras. - Se hizo más robusto el autocomplete del campo:
AbortControllerpara 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
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