Initial commit - Horux Despachos NL

This commit is contained in:
2026-05-03 16:47:53 -06:00
commit b00b677c54
647 changed files with 133843 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
# 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!`