267 lines
11 KiB
Markdown
267 lines
11 KiB
Markdown
# 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!`
|