11 KiB
Sesión 2026-04-18 / 2026-04-19 — Fixes y Features
Resumen
Sesión intensiva de correcciones del pivot Tenant→Contribuyente, mejoras de UX, nuevas funcionalidades (declaraciones, carteras, ISR mensual), y correcciones fiscales.
1. Página cancelaciones-periodo-anterior (NUEVA)
Problema: La alerta "Facturas de periodos anteriores canceladas" generaba un link a /alertas/cancelaciones-periodo-anterior que no existía (404).
Solución: Creada la página drilldown similar a /alertas/cancelaciones, mostrando CFDIs cuya fecha_cancelacion cae en el mes actual pero fecha_emision es de meses anteriores.
Archivos:
apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx(nuevo)
2. Filtro de regímenes por contribuyente
Problema: El dropdown de regímenes en Dashboard/Impuestos/Reportes mostraba regímenes de TODOS los contribuyentes del despacho, no solo del seleccionado.
Solución: Propagación de selectedContribuyenteId a través de toda la cadena: hook → API client → controller → service (getRegimenesDelPeriodo ahora acepta contribuyenteId y filtra con AND contribuyente_id = '...').
Archivos:
apps/api/src/services/dashboard.service.ts—getRegimenesDelPeriodo+contribuyenteIdapps/api/src/controllers/dashboard.controller.ts— extraecontribuyenteIdde queryapps/web/lib/api/dashboard.ts— pasacontribuyenteIdapps/web/lib/hooks/use-dashboard.ts—useRegimenesDelPeriodousaselectedContribuyenteId
3. Discrepancia de régimen — regímenes del contribuyente
Problema: La alerta y drilldown de discrepancia comparaba CFDIs contra los regímenes del tenant (central), no del contribuyente seleccionado.
Solución: Nueva función getRegimenesActivosClavesEfectivos(tenantId, pool, contribuyenteId?) que lee de contribuyentes.regimen_fiscal cuando hay contribuyenteId, o fallback a TenantRegimenActivo.
Archivos:
apps/api/src/services/regimen.service.ts— nueva funciónapps/api/src/services/alertas-auto.service.ts— usa la nueva funciónapps/api/src/controllers/alertas.controller.ts— usa la nueva función en drilldown
4. Discrepancia de régimen — filtros de fecha y régimen
Problema: No había forma de filtrar los CFDIs con discrepancia por fecha o por régimen específico.
Solución: Filtros client-side (Desde/Hasta/Régimen) en la página de discrepancia. El dropdown de régimen se construye dinámicamente de los valores únicos presentes.
Archivos:
apps/web/app/(dashboard)/alertas/discrepancia-regimen/page.tsx— filtros +contribuyenteId
5. Discrepancias — excluir cancelados
Problema: CFDIs cancelados aparecían en la alerta de discrepancia.
Solución: Doble filtro: status NOT IN ('Cancelado', '0') AND fecha_cancelacion IS NULL.
Archivos:
apps/api/src/services/alertas-auto.service.ts— alerta countapps/api/src/controllers/alertas.controller.ts— drilldown query
6. Obligaciones — demasiadas y hacia el pasado
Problema 1: CSF importaba obligaciones históricas (con fechaFin). Fix: Filtro !ob.fechaFin.
Problema 2: Obligaciones aparecían como "atrasadas" para meses anteriores a su creación. Fix: periodo >= obStartPeriodo (derivado de created_at).
Problema 3: Re-ejecutar "Generar recomendaciones" duplicaba. Fix: DELETE WHERE es_recomendada = true antes de insertar.
Archivos:
apps/api/src/services/obligaciones.service.ts
7. Matching de obligaciones CSF → catálogo (MEJORA)
Problema: Match por "primeras 3 palabras" fallaba con variantes del SAT (ej: "Pago provisional mensual" vs "Pago provisional de").
Solución: Sistema de keyword sets discriminantes. 15 reglas con múltiples variantes, normalización sin acentos, distinción PM/PF por longitud de RFC.
Análisis base: 17 CSFs reales analizadas → 22 obligaciones únicas del SAT mapeadas.
Archivos:
apps/api/src/services/obligaciones.service.ts—CATALOG_MATCH_RULES+matchCsfToCatalog()
8. Calendario — fix crash por tipo desconocido
Problema: tipoIcons[e.tipo] retornaba undefined para eventos con tipo no mapeado, crasheando React.
Solución: Fallback || Calendar en el grid del calendario (la lista lateral ya lo tenía).
Archivos:
apps/web/app/(dashboard)/calendario/page.tsx
9. Alertas del dashboard filtradas por contribuyente
Problema: Las alertas automáticas del dashboard (/dashboard/alertas) no filtraban por contribuyente, mostrando alertas de todos los RFCs. El drilldown sí filtraba, causando "alerta visible pero drilldown vacío".
Solución: Propagación de contribuyenteId al endpoint /dashboard/alertas y al hook useAlertas.
Archivos:
apps/api/src/controllers/dashboard.controller.ts—getAlertas+contribuyenteIdapps/web/lib/api/dashboard.ts—getAlertasaceptacontribuyenteIdapps/web/lib/hooks/use-dashboard.ts—useAlertasusaselectedContribuyenteId
10. Alertas page — filtro por contribuyente
Problema: La página /alertas tenía queries inline que no pasaban contribuyenteId. Al cambiar contribuyente, los datos no se actualizaban.
Solución: Ambas queries (alertas-automaticas y alertas-manuales) ahora incluyen selectedContribuyenteId en query key y lo pasan como parámetro.
Archivos:
apps/web/app/(dashboard)/alertas/page.tsx
11. CFDI — eliminar staleTime
Problema: useCfdis tenía staleTime: 30_000 que podía mostrar datos del contribuyente anterior por hasta 30 segundos.
Solución: Eliminado staleTime y gcTime.
Archivos:
apps/web/lib/hooks/use-cfdi.ts
12. Declaraciones — renombrada + periodicidad + monto
Cambios:
- Tab renombrada de "Declaraciones Provisionales" a "Declaraciones"
- Selector de periodicidad (mensual/bimestral/trimestral/semestral/anual) con opciones dinámicas de periodo
- Campo "Monto a pagar" — si $0, auto-marca como pagado y resuelve alertas de pago
- Año seleccionable dentro del formulario (para declaración anual de ejercicio anterior)
- Filtro de fecha (Desde/Hasta) basado en
created_aten lugar de filtro por año fiscal - Columna "Fecha subida" en la tabla
Migración: 021_declaraciones_periodicidad_monto.sql
Archivos:
apps/api/src/migrations/tenant/021_declaraciones_periodicidad_monto.sqlapps/api/src/services/declaraciones.service.ts— nuevos campos + auto-pago $0apps/api/src/controllers/documentos.controller.ts— Zod schema + filtro por fechaapps/web/lib/api/declaraciones.ts— tipos actualizadosapps/web/lib/hooks/use-declaraciones.ts— filtro por fechaapps/web/app/(dashboard)/documentos/page.tsx— UI completa
13. Carteras y subcarteras (NUEVO)
Modelo:
Cartera (top-level) → supervisor_user_id
└── Subcartera → auxiliar_user_id, parent_id
└── Entidades (RFCs asignados)
Flujo:
- Owner crea cartera: si hay supervisores → selector; si no → se asigna a sí mismo
- Supervisor (u Owner) crea subcarteras dentro de una cartera, asignando un auxiliar
- Cada subcartera tiene su propio subset de RFCs
- Auxiliar ve solo los RFCs de sus subcarteras (
entidades-visibles.ts)
Invite auxiliar: Requiere seleccionar supervisor. Se almacena en tabla auxiliar_supervisores.
Migración: 022_carteras_subcarteras.sql — parent_id, auxiliar_user_id en carteras + tabla auxiliar_supervisores
Archivos:
apps/api/src/migrations/tenant/022_carteras_subcarteras.sqlapps/api/src/services/cartera.service.ts— subcarteras + getSupervisoresapps/api/src/controllers/cartera.controller.ts— endpoints subcarterasapps/api/src/routes/cartera.routes.ts— rutas nuevasapps/api/src/controllers/usuarios.controller.ts— invite consupervisorUserIdapps/api/src/utils/entidades-visibles.ts— auxiliar ve subcarterasapps/web/app/(dashboard)/carteras/page.tsx— página completa reescritaapps/web/app/(dashboard)/usuarios/page.tsx— selector supervisor al invitar auxiliarapps/web/lib/api/carteras.ts— API client actualizadoapps/web/lib/hooks/use-carteras.ts— hooks actualizadospackages/shared/src/types/user.ts—UserInvite.supervisorUserId
14. ISR Mensual — tabla + Excel (NUEVO)
Backend: Nuevo endpoint GET /impuestos/isr/mensual que calcula ingresos, deducciones y base gravable por cada mes del año (excluyendo régimen 605).
Frontend: Tabla "Histórico ISR" con 12 meses + fila de totales + botón Excel. Similar a la tabla IVA existente.
Excel IVA: También agregado botón Excel a la tabla de Histórico IVA.
Archivos:
apps/api/src/services/impuestos.service.ts—getIsrMensual()apps/api/src/controllers/impuestos.controller.ts— handlerapps/api/src/routes/impuestos.routes.ts— rutaapps/web/lib/api/impuestos.ts— API clientapps/web/lib/hooks/use-impuestos.ts— hookapps/web/app/(dashboard)/impuestos/page.tsx— tabla + Excel ambas
15. ISR — exclusión régimen 605 (Sueldos)
Problema: El régimen 605 (Sueldos y Salarios) se incluía en ingresos ISR, pero el patrón ya retuvo el ISR. No debe generar ingreso/deducción para ISR del contribuyente.
Solución:
getIsrMensual: SQL filtraAND regimen_fiscal_emisor != '605'getResumenIsr: Filtra 605 deingresosPorRegimen,deduccionesPorRegimen, yregimenesConDatos
Dashboard: 605 sigue mostrándose como ingreso general (correcto).
Archivos:
apps/api/src/services/impuestos.service.ts
16. ISR — cálculo por régimen correcto
Problema: El frontend hacía baseGravable * 0.30 para todos los regímenes. Incorrecto para PF (612 usa tarifa progresiva, no 30%).
Solución: BaseGravableRegimen ahora incluye isrCausado calculado en el backend con la fórmula correcta por régimen:
- 606, 612, 621, 625 → tarifa progresiva Art. 96
- 626 PF → tasa plana RESICO por bracket
- PM (601, etc.) → base × coeficiente × 30%
KPI "ISR a Pagar": Ahora usa el isrCausado del régimen seleccionado, consistente con la tabla de abajo.
Coeficiente de Utilidad: Se oculta cuando se selecciona un régimen PF (no aplica).
Archivos:
packages/shared/src/types/impuestos.ts—BaseGravableRegimen.isrCausadoapps/api/src/services/impuestos.service.ts— calcula y devuelveisrCausadoper régimenapps/web/app/(dashboard)/impuestos/page.tsx— KPI usa per-régimen + oculta coeficiente PF
Migraciones aplicadas
| # | Archivo | Descripción |
|---|---|---|
| 021 | 021_declaraciones_periodicidad_monto.sql |
Periodicidad + monto_pago en declaraciones |
| 022 | 022_carteras_subcarteras.sql |
parent_id + auxiliar_user_id en carteras + auxiliar_supervisores |
Usuarios creados
| Rol | Despacho | |
|---|---|---|
| supervisor@patito.com | Supervisor | Patito |
| auxiliar@patito.com | Auxiliar | Patito |
| cliente@patito.com | Cliente | Patito |
Contraseña: Admin12345!