Files
HoruxDespachosNuevo/docs/plans/2026-04-18-session-fixes-and-features.md

11 KiB
Raw Permalink Blame History

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.tsgetRegimenesDelPeriodo + contribuyenteId
  • apps/api/src/controllers/dashboard.controller.ts — extrae contribuyenteId de query
  • apps/web/lib/api/dashboard.ts — pasa contribuyenteId
  • apps/web/lib/hooks/use-dashboard.tsuseRegimenesDelPeriodo usa selectedContribuyenteId

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ón
  • apps/api/src/services/alertas-auto.service.ts — usa la nueva función
  • apps/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 count
  • apps/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.tsCATALOG_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.tsgetAlertas + contribuyenteId
  • apps/web/lib/api/dashboard.tsgetAlertas acepta contribuyenteId
  • apps/web/lib/hooks/use-dashboard.tsuseAlertas usa selectedContribuyenteId

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_at en 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.sql
  • apps/api/src/services/declaraciones.service.ts — nuevos campos + auto-pago $0
  • apps/api/src/controllers/documentos.controller.ts — Zod schema + filtro por fecha
  • apps/web/lib/api/declaraciones.ts — tipos actualizados
  • apps/web/lib/hooks/use-declaraciones.ts — filtro por fecha
  • apps/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.sqlparent_id, auxiliar_user_id en carteras + tabla auxiliar_supervisores

Archivos:

  • apps/api/src/migrations/tenant/022_carteras_subcarteras.sql
  • apps/api/src/services/cartera.service.ts — subcarteras + getSupervisores
  • apps/api/src/controllers/cartera.controller.ts — endpoints subcarteras
  • apps/api/src/routes/cartera.routes.ts — rutas nuevas
  • apps/api/src/controllers/usuarios.controller.ts — invite con supervisorUserId
  • apps/api/src/utils/entidades-visibles.ts — auxiliar ve subcarteras
  • apps/web/app/(dashboard)/carteras/page.tsx — página completa reescrita
  • apps/web/app/(dashboard)/usuarios/page.tsx — selector supervisor al invitar auxiliar
  • apps/web/lib/api/carteras.ts — API client actualizado
  • apps/web/lib/hooks/use-carteras.ts — hooks actualizados
  • packages/shared/src/types/user.tsUserInvite.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.tsgetIsrMensual()
  • apps/api/src/controllers/impuestos.controller.ts — handler
  • apps/api/src/routes/impuestos.routes.ts — ruta
  • apps/web/lib/api/impuestos.ts — API client
  • apps/web/lib/hooks/use-impuestos.ts — hook
  • apps/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 filtra AND regimen_fiscal_emisor != '605'
  • getResumenIsr: Filtra 605 de ingresosPorRegimen, deduccionesPorRegimen, y regimenesConDatos

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.tsBaseGravableRegimen.isrCausado
  • apps/api/src/services/impuestos.service.ts — calcula y devuelve isrCausado per régimen
  • apps/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

Email Rol Despacho
supervisor@patito.com Supervisor Patito
auxiliar@patito.com Auxiliar Patito
cliente@patito.com Cliente Patito

Contraseña: Admin12345!