238 lines
12 KiB
Markdown
238 lines
12 KiB
Markdown
# Features Pendientes — Horux Despachos
|
|
|
|
> Documentado 2026-04-19. Actualizado 2026-04-22 con estado real.
|
|
|
|
## Índice de estado al 2026-04-22
|
|
|
|
| # | Feature | Estado |
|
|
|---|---|---|
|
|
| 1 | Editar contribuyentes asignados a Cliente | ✅ **Completado** |
|
|
| 2 | Pendientes → Despacho + métricas de seguimiento | ⏸ Abierto |
|
|
| 3 | Cobro del plan desde Planes (MP) | ✅ **Completado** (Tanda 1 MP sesión 2026-04-21) |
|
|
| 4 | Timbres asignados al despacho | ✅ **Verificado** (ya usa `consumeTimbre(tenantId)`) |
|
|
| 5 | Add-ons por contribuyente | ✅ **Completado** (sesión 2026-04-22) |
|
|
| 6 | Enlazar obligaciones ↔ declaraciones | ✅ **Completado 2026-04-23** (backend + UI trazabilidad) |
|
|
| 7 | Calendario — obligaciones con colores | ✅ **Completado 2026-04-23** (backend + colores + íconos + leyenda) |
|
|
| 8 | Sección "Extras" en Documentos | ✅ **Completado** (sesión 2026-04-21) |
|
|
| 9 | Avisos por correo al subir declaración / doc extra | ✅ **Completado 2026-04-23** |
|
|
| 10 | Alertas obligaciones — filtros per-contribuyente | ✅ **Investigado 2026-04-23** — bug no reproducible; protecciones verificadas |
|
|
|
|
---
|
|
|
|
## 1. Editar contribuyentes asignados a usuario tipo Cliente — ✅ COMPLETADO
|
|
|
|
**Implementación verificada al 2026-04-22:**
|
|
- Backend: `GET /usuarios/:id/accesos` (`getClienteAccesos`) y `POST /usuarios/:id/accesos` (`setClienteAccesos`, reemplaza todos los accesos) en `usuarios.controller.ts:162-194`.
|
|
- Frontend: `apps/web/app/(dashboard)/usuarios/page.tsx` tiene botón "Editar RFCs con acceso" por cada usuario tipo `cliente` (línea 366), abre modal con checkboxes por contribuyente. Solo visible para owner en despacho.
|
|
- Guardrails: endpoint gateado por `req.user.role === 'owner'`.
|
|
|
|
---
|
|
|
|
## 2. Convertir "Pendientes" a "Despacho" + métricas de seguimiento
|
|
|
|
**Estado:** La página `/pendientes` muestra obligaciones por periodo con barras de progreso por contribuyente.
|
|
|
|
**Cambios necesarios:**
|
|
- Renombrar a "Despacho" en sidebar
|
|
- Agregar métricas de seguimiento del despacho:
|
|
- Total de contribuyentes activos
|
|
- Contribuyentes con FIEL vencida o sin FIEL
|
|
- Contribuyentes con opinión de cumplimiento negativa
|
|
- Declaraciones pendientes del mes
|
|
- Progreso de obligaciones del mes (% completado global)
|
|
- CFDIs sincronizados vs pendientes
|
|
- Resumen de alertas activas por prioridad
|
|
- Mantener la vista de pendientes/obligaciones actual como sección inferior
|
|
|
|
---
|
|
|
|
## 3. Cobro del plan desde Planes — ✅ COMPLETADO (Tanda 1 MP, sesión 2026-04-21)
|
|
|
|
Integración MP completa para planes despacho (`business_control` y
|
|
`business_cloud`), con dualidad (año 1 $21K / renovación $15K) via Opción B
|
|
(updatePreapprovalAmount tras primer pago). Ver
|
|
`docs/plans/2026-04-21-session-2-mp-setup-and-bugfixes.md` §5 y §7.
|
|
|
|
---
|
|
|
|
## 4. Timbres asignados al despacho — ✅ VERIFICADO
|
|
|
|
Confirmado al 2026-04-22: `facturacion.controller.ts:60` llama
|
|
`consumeTimbre(tenantId)` pasando el tenantId del despacho (no contribuyenteId).
|
|
El pool de timbres (`timbre_suscripciones` + `timbre_paquetes`) es compartido
|
|
entre todos los contribuyentes del despacho. La UI de timbres ya lo refleja
|
|
así. No se necesitan cambios.
|
|
|
|
---
|
|
|
|
## 5. Add-ons por contribuyente — ✅ COMPLETADO (sesión 2026-04-22)
|
|
|
|
**Implementado:**
|
|
- Schema: `SubscriptionAddon.contribuyenteId String?` (opcional; NULL = tenant-level)
|
|
- Migration `20260422172323_subscription_addons_contribuyente_id`
|
|
- Service `addon.service.ts`: `subscribeAddon(contribuyenteId)`, `listActiveAddons(tenantId, contribuyenteId?)` con preapproval MP propio por add-on
|
|
- Controller `subscription.controller.ts`: `GET /me/addons?contribuyenteId=...`, `POST /me/addons { addonCodename, contribuyenteId }`
|
|
- UI: botón ✨ Sparkles en `/contribuyentes` por cada RFC → dialog con catálogo `ADDONS_POR_CONTRIBUYENTE` (hoy solo Lolita IA $250/mes)
|
|
- Cableado automático del overage Business Cloud: `adjustBusinessCloudOverage` en `addon.service.ts`, llamado desde `contribuyente.controller.ts:create` y `:deactivate`
|
|
|
|
**Modelo descartado:** primer intento fue tabla tenant `contribuyente_addons`
|
|
con feature-toggles (facturación/conciliación/documentos/calendario/reportes).
|
|
Revertido — los add-ons reales son servicios de cobro recurrente, no
|
|
switches de features. Los gates por módulo quedan como feature futura
|
|
(requerirían middleware `requireAddon(key)` en rutas existentes).
|
|
|
|
Ver `docs/plans/2026-04-22-pendientes-y-addons.md` § "Feature: Add-ons por
|
|
contribuyente" para detalle completo.
|
|
|
|
---
|
|
|
|
## 6. Enlazar obligaciones con Declaraciones — ✅ COMPLETADO 2026-04-23
|
|
|
|
**Backend (ya existía parcialmente, se completó la trazabilidad):**
|
|
- `completarObligacionesPorDeclaracion` en `declaraciones.service.ts` hace
|
|
matching por keyword (`IVA → 'iva'`, `ISR → 'isr'`, `SUELDOS → 'sueldos'|'salarios'|'nómina'`, etc.)
|
|
contra `obligaciones_contribuyente.nombre` y hace `INSERT ... ON CONFLICT
|
|
DO UPDATE` en `obligacion_periodos` marcando `completada=true`.
|
|
- `createDeclaracion` llama esta función tras crear la declaración; recibe
|
|
el `id` de la declaración y lo propaga.
|
|
|
|
**Nuevo en esta sesión (trazabilidad):**
|
|
- **Migration 030** `obligacion_periodos.declaracion_id INT REFERENCES
|
|
declaraciones_provisionales(id) ON DELETE SET NULL` + índice parcial.
|
|
Aplicada a Zorro + Patito vía `pnpm db:migrate-tenants`.
|
|
- `completarObligacionesPorDeclaracion(..., declaracionId)` guarda el FK.
|
|
- `getObligacionesPorPeriodo` hace LEFT JOIN a `declaraciones_provisionales`
|
|
y devuelve el objeto `declaracion: { id, año, mes, tipo, pdfFilename } | null`
|
|
por periodo completado. Nuevo tipo exportado `DeclaracionLink`.
|
|
- UI `/pendientes` (vista single-contribuyente) muestra link
|
|
`↗ Declaración MM/YYYY [Compl.]` junto a cada obligación completada
|
|
que tenga FK. Click abre el PDF en nueva pestaña via
|
|
`/documentos/declaraciones/:id/pdf/declaracion`.
|
|
|
|
**Qué pasa al borrar la declaración:** `ON DELETE SET NULL` — el periodo
|
|
sigue marcado `completada=true` pero pierde la referencia. Decisión
|
|
intencional: el usuario puede volver a abrir manualmente si corresponde,
|
|
pero el estado se preserva.
|
|
|
|
**Obligaciones marcadas manualmente** (sin declaración asociada): ya
|
|
funcionaban antes, siguen funcionando. El campo `declaracion_id` queda
|
|
NULL y la UI no muestra el link.
|
|
|
|
---
|
|
|
|
## 7. Calendario — obligaciones con colores — ✅ COMPLETADO 2026-04-23
|
|
|
|
**Backend** (`calendario-fiscal.service.ts:generarEventosDesdeObligaciones`):
|
|
- Lee `obligacion_periodos` para determinar completitud por (obligación, periodo).
|
|
- Emite eventos con uno de 3 tipos:
|
|
- `obligacion-completada` — si `obligacion_periodos.completada = true` para el periodo.
|
|
- `obligacion-atrasada` — si no completada y `fechaLimite < now()`.
|
|
- `obligacion-pendiente` — si no completada y aún en ventana.
|
|
- `fechaLimite` ajustada a día hábil más próximo (considera inhábiles del año).
|
|
|
|
**Frontend** (`apps/web/app/(dashboard)/calendario/page.tsx`):
|
|
- `tipoColors`: amber / green / red para los 3 estados.
|
|
- `tipoIcons`: `Clock` (pendiente), `Check` (completada), `AlertTriangle` (atrasada).
|
|
- Leyenda visible en el CardContent del calendario que explica los colores + `custom` (violet).
|
|
- El fetch `useEventos(año)` pasa el `selectedContribuyenteId` del store; el controller
|
|
detecta despacho y usa `generarEventosDesdeObligaciones` en vez del catálogo estático
|
|
(`generarEventosFiscales`) de Horux 360.
|
|
|
|
---
|
|
|
|
## 8. Sección "Extras" en Documentos — ✅ COMPLETADO (sesión 2026-04-21)
|
|
|
|
Implementado: tabla `documentos_extras`, endpoints CRUD, pestaña en UI.
|
|
Ver `docs/plans/2026-04-21-session-2-mp-setup-and-bugfixes.md` §12.
|
|
|
|
---
|
|
|
|
## 9. Avisos por correo electrónico — ✅ COMPLETADO 2026-04-23
|
|
|
|
**Implementación:**
|
|
- **Template** `apps/api/src/services/email/templates/documento-subido.ts` — usa
|
|
`baseTemplate` de la marca. Parametrizado por `kind: 'declaracion' | 'extra'`
|
|
y bloques condicionales para periodo/tipo/impuestos/monto (declaración) o
|
|
nombre/categoría/descripción (extra). HTML escapado para evitar XSS.
|
|
- **`emailService.sendDocumentoSubido(recipients, data)`** —
|
|
`apps/api/src/services/email/email.service.ts`. Loop por recipient con
|
|
try/catch individual para que un fallo en un destinatario no bloquee los
|
|
demás. Subject incluye RFC + periodo/nombre.
|
|
- **Helpers de resolución en `utils/memberships.ts`:**
|
|
- `getTenantOwnerEmails(tenantId)` — lista todos los owners activos.
|
|
- `getUserEmailById(userId)` — resolver supervisor por UUID.
|
|
- **Orquestador** `apps/api/src/services/notify-upload.service.ts:notifyDocumentoSubido`
|
|
— lee `entidades_gestionadas.supervisor_user_id` desde BD tenant, resuelve
|
|
email, dedupea con owners, EXCLUYE al uploader (no notifica su propia
|
|
acción). Usa `FRONTEND_URL/documentos` como link al sistema.
|
|
- **Callsites** en `controllers/documentos.controller.ts`:
|
|
- `crearDeclaracion` dispara notify tras el INSERT con periodo "Abril 2026",
|
|
tipo, impuestos, montoPago.
|
|
- `crearExtra` dispara notify con nombre + categoría + descripción.
|
|
- **Fire-and-forget**: `.catch(err => console.error(...))` en ambos
|
|
call-sites — el response HTTP ya retornó cuando el email viaja.
|
|
Si SMTP no está configurado, el transport de `@horux/core` loguea a
|
|
consola en vez de fallar (dev mode).
|
|
|
|
**Fuera de alcance:** flag por despacho para activar/desactivar
|
|
notificaciones (feature futura cuando haya preferencias de notificación
|
|
a nivel tenant/user).
|
|
|
|
---
|
|
|
|
## 10. Revisar alertas de obligaciones (posible bug) — ✅ INVESTIGADO 2026-04-23
|
|
|
|
**Reporte original:** "las alertas manuales de obligaciones muestran más
|
|
obligaciones de las que tiene el contribuyente".
|
|
|
|
**Investigación:** auditoría SQL sobre los despachos activos (Patito, Zorro):
|
|
|
|
- 0 alertas `ob-*` con `obligacion_id` inexistente (huérfanas)
|
|
- 0 alertas para obligaciones con `activa=false`
|
|
- 0 alertas para periodos ya completados
|
|
- Count per-contribuyente: alertas ≤ obligaciones activas en todos los casos
|
|
- Todas las alertas actuales son del periodo actual (`2026-04`), 1 por obligación
|
|
|
|
**Protecciones verificadas en el código:**
|
|
|
|
1. **`removeObligacion` (`obligaciones.service.ts:296-311`)** — al
|
|
desactivar una obligación hace soft-delete (`activa=false`) +
|
|
`DELETE alertas WHERE tipo LIKE 'ob-{id}-%'` + `DELETE obligacion_periodos
|
|
WHERE obligacion_id=$1`. Evita alertas huérfanas incluso si queda
|
|
residuo por pools/caches.
|
|
2. **`inactiveFilter` en `getAlertasManualesPendientes`
|
|
(`alertas-manuales.service.ts:269-273`)** — defense-in-depth: excluye
|
|
en query alertas cuyo obligacion_id esté `activa=false`.
|
|
3. **`contribuyenteFilter` strict
|
|
(`alertas-manuales.service.ts:223-227`)** — cuando se pasa
|
|
`contribuyenteId`, el WHERE solo incluye alertas cuyo SUBSTRING del
|
|
`tipo` coincida con un `id` de `obligaciones_contribuyente` del RFC.
|
|
Cross-contribuyente leak imposible.
|
|
4. **`sincronizarDesdeObligacionesContribuyente` genera solo current +
|
|
previous month** — sin acumulación histórica espontánea.
|
|
|
|
**Conclusión:** el bug reportado fue probablemente corregido
|
|
implícitamente en la sesión 2026-04-18/19 cuando se agregó el cleanup
|
|
en `removeObligacion` y los filtros en `getAlertasManualesPendientes`.
|
|
El escenario único donde persistiría acumulación es un usuario que
|
|
deje periodos sin completar durante meses — pero eso refleja
|
|
correctamente la realidad fiscal (cada periodo incumplido es una
|
|
obligación pendiente propia).
|
|
|
|
**Acción:** cerrar #10. Si reaparece el síntoma, correr auditoría
|
|
SQL previa al reporte para identificar el drift específico.
|
|
|
|
---
|
|
|
|
## Cambios completados en esta sesión (2026-04-18 / 2026-04-19)
|
|
|
|
Ver `docs/plans/2026-04-18-session-fixes-and-features.md` para el detalle completo de los 16 cambios implementados, incluyendo:
|
|
- Filtro contribuyente en regímenes, alertas, CFDIs, CSD, bancos
|
|
- Declaraciones con periodicidad + monto + filtro por fecha
|
|
- Carteras y subcarteras
|
|
- ISR mensual + exclusión 605 + cálculo correcto por régimen
|
|
- Matching de obligaciones CSF mejorado
|
|
- Descarte persistente de discrepancias
|
|
- 4 migraciones (021-024)
|
|
- 3 usuarios creados (supervisor, auxiliar, cliente)
|