Files
HoruxDespachos/docs/plans/2026-04-18-session-fixes-and-features.md
2026-04-27 22:09:36 -06:00

267 lines
11 KiB
Markdown
Raw Permalink 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.
# 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` + `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.ts``useRegimenesDelPeriodo` 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.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` + `contribuyenteId`
- `apps/web/lib/api/dashboard.ts``getAlertas` acepta `contribuyenteId`
- `apps/web/lib/hooks/use-dashboard.ts``useAlertas` 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.sql``parent_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.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` — 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.ts``BaseGravableRegimen.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!`