Update: nueva version Horux Despachos

This commit is contained in:
consultoria-as
2026-04-27 22:09:36 -06:00
commit 6b36db1403
614 changed files with 125926 additions and 0 deletions

View File

@@ -0,0 +1,374 @@
# Sesión 2026-04-26 — Resumen del día
Sesión consolidada con cuatro frentes: (1) limpieza de la columna "Tipo"
en CFDI y drill-downs, (2) rebrand de planes despacho con nuevos precios
y dos planes nuevos para empresas, (3) overage despacho a 100 RFCs y
generalización a Business Control + Enterprise, (4) compensación IVA
para el patrón I/07 PPD ↔ E mismo mes.
Los frentes fiscales y de planes tienen documentos dedicados; este doc
agrega una guía y captura lo que no entró ahí.
---
## Índice
1. [Limpieza columna "Tipo" en CFDI y drill-downs](#1-limpieza-columna-tipo-en-cfdi-y-drill-downs)
2. [Rebrand de planes despacho](#2-rebrand-de-planes-despacho)
3. [Overage despacho generalizado](#3-overage-despacho-generalizado)
4. [Compensación IVA I/07 PPD ↔ E mismo mes](#4-compensación-iva-i07-ppd--e-mismo-mes)
5. [Refactor IVA — fórmula del owner](#5-refactor-iva--fórmula-del-owner)
6. [Notificaciones email automáticas (alertas + recordatorios)](#6-notificaciones-email-automáticas-alertas--recordatorios)
7. [Pendientes](#7-pendientes)
Documentos relacionados creados/actualizados hoy:
- `docs/plans/2026-04-26-i07-ppd-compensacion.md` (creado en otro turno; §8
agregada hoy con la extensión IVA)
- `docs/plans/2026-04-26-rebrand-planes-despacho.md` (creado hoy)
- `docs/plans/2026-04-26-iva-refactor.md` (creado hoy — refactor que
reemplaza la compensación I PUE/07 y el clamp en P)
- `docs/plans/2026-04-26-notifications-email.md` (creado hoy — cron 8:30 AM
con emails por alerta nueva y recordatorio próximo a vencer)
- `docs/plans/2026-04-26-sprints-1-2-3.md` (creado hoy — pre-deploy IVA,
bugs latentes, decisiones del owner D1-D7, sprint 6 SAT)
- `docs/plans/2026-04-26-admin-global-setup.md` (creado hoy — bootstrap
admin global, gestión clientes, add-ons UI, auto-facturación, redirect
login → /clientes)
---
## 1. Limpieza columna "Tipo" en CFDI y drill-downs
### Problema
La columna "Tipo" (EMITIDO/RECIBIDO) era ruido: la información ya está
implícita en la posición del RFC emisor/receptor relativa al
contribuyente activo. Aparecía en `/cfdi`, en los drill-downs de
métricas del dashboard y en los drill-downs de alertas, además de
duplicarse en cada export Excel.
### Cambios
**Frontend `/cfdi`** (`apps/web/app/(dashboard)/cfdi/page.tsx`):
- Removida `<th>Tipo</th>` y la celda `<td>` con el badge.
- Removida `'Tipo': cfdi.type === 'EMITIDO' ? 'Emitido' : 'Recibido'`
de las dos funciones de export.
- Filtros "Todos / Emitidos / Recibidos" cambiaron de filtrar por la
columna `type` a filtrar por RFC del contribuyente. La razón: con
multi-contribuyente por tenant el `type` puede ser inconsistente
cuando dos contribuyentes del mismo tenant se facturan entre sí.
RFC en posición emisor/receptor es fuente de verdad.
**Backend** (`apps/api/src/services/cfdi.service.ts`):
```ts
if (filters.tipo === 'EMITIDO') {
whereClause += ` AND rfc_emisor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`;
params.push(filters.contribuyenteId);
} else if (filters.tipo === 'RECIBIDO') {
whereClause += ` AND rfc_receptor = (SELECT rfc FROM contribuyentes WHERE entidad_id = $${paramIndex++})`;
params.push(filters.contribuyenteId);
}
```
**Drill-downs actualizados** (mismo patrón en cada uno: removido
`{ header: 'Tipo', key: 'type', width: 10 }` de `EXCEL_COLUMNS`,
`<th>Tipo</th>` de thead, `<td>{cfdi.type}</td>` de tbody):
- `apps/web/app/(dashboard)/drill-down/page.tsx` — drill-down genérico
de métricas del dashboard.
- `apps/web/app/(dashboard)/alertas/cancelaciones/page.tsx`
- `apps/web/app/(dashboard)/alertas/cancelaciones-periodo-anterior/page.tsx`
- `apps/web/app/(dashboard)/alertas/efectivo/page.tsx`
- `apps/web/app/(dashboard)/alertas/tipo-relacion-sospechosa/page.tsx`
(también removida la columna "Dirección" redundante).
---
## 2. Rebrand de planes despacho
Detalle completo: `docs/plans/2026-04-26-rebrand-planes-despacho.md`.
### Resumen de cambios
| Plan (codename) | Display | Anual MXN | RFCs | CFDIs/contrib. | Timbres/mes | Backup | Features extra |
|---|---|---:|---:|---:|---:|---|---|
| `mi_empresa` | Mi Empresa | $6,960 | 1 | 1M | 50 | No | — |
| `mi_empresa_plus` | Mi Empresa + | $10,800 | 1 | 1M | 50 | No | API + Lolita IA |
| `business_control` | Business Control ★ | $25,850 | 100 | 1M | 0 | Sí | API |
| `business_cloud` | Enterprise (display) | $43,000 | 100 | 3M | 0 | Sí | API |
★ = "Más popular".
`business_cloud` mantiene su codename interno por compat con
suscripciones vigentes; solo cambia el `name` display.
### Archivos tocados hoy
- `apps/api/src/controllers/subscription.controller.ts``VALID_PLANS`
y `DESPACHO_ONLY_ANNUAL` extendidos con `mi_empresa` y
`mi_empresa_plus`.
- `apps/api/src/services/payment/subscription.service.ts` — type alias
`Plan` extendido con los dos literales nuevos para que `subscribe`,
`scheduleChange`, `initiateUpgrade` y `applyPendingChanges` los
acepten.
### Trabajo de fondo previo (no en esta sesión pero relacionado)
- `packages/shared/src/constants/despacho-plans.ts` — catálogo y
helpers (`isDespachoPaidPlan`, `permiteOverage`,
`despachoPlanTieneDualidad`).
- `apps/api/prisma/schema.prisma` — enum `Plan` con
`mi_empresa` y `mi_empresa_plus` (migración
`20260426073942_add_mi_empresa_plan`).
- `apps/web/app/(dashboard)/configuracion/planes-despacho/page.tsx`
UI con grid de 4 cards alineadas verticalmente
(`flex flex-col` + `mt-auto`), botón "Contratar".
---
## 3. Overage despacho generalizado
### Antes
`addon.service.ts` tenía `BUSINESS_CLOUD_INCLUDED_RFCS = 3` y la
función `adjustBusinessCloudOverage` filtraba con
`sub.plan !== 'business_cloud'`. Solo Enterprise generaba overage.
### Después
- Constante renombrada `BUSINESS_CLOUD_INCLUDED_RFCS = 3`
`DESPACHO_INCLUDED_RFCS = 100`.
- Función renombrada `adjustBusinessCloudOverage`
`adjustDespachoOverage`.
- Filtro de plan ahora usa `permiteOverage(sub.plan)` (helper en
`@horux/shared`) que retorna `true` para `business_control` y
`business_cloud`. Mi Empresa / Mi Empresa+ tienen límite duro
de 1 RFC y `permiteOverage` retorna false — no entran a overage.
- Codename del catálogo `contribuyente_extra_business_cloud` se
preserva por compat con `subscription_addons` existentes; solo
cambia el `nombre` display a "Contribuyente adicional (RFC extra)".
### Archivos tocados
- `apps/api/src/services/payment/addon.service.ts` — constante,
función, plan check, comentarios.
- `apps/api/src/controllers/contribuyente.controller.ts` — import +
dos callsites (`create` y `deactivate`) + comentarios actualizados.
- `apps/api/prisma/seed.ts` — nombre del addon catalogo a genérico.
---
## 4. Compensación IVA I/07 PPD ↔ E mismo mes
Detalle completo: `docs/plans/2026-04-26-i07-ppd-compensacion.md` §8.
### Asimetría que motivó el fix
Para `I PUE/07` la cadena anticipo + aplicación + E/07 cierra
algebraicamente con la lógica existente
(`SUM_REL_TRAS` + filtro `<> '07'` en NEG). Para `I PPD/07` la
aplicación no aporta IVA en su mes (espera al P), pero si en el
**mismo mes** existe una E con `tipoRelación ≠ 07` que la referencia,
la E sí resta IVA en NEG y la I PPD nunca aportó nada que la
neutralice. Resultado previo: IVA acreditable / causado de la E
"perdido".
### Solución
Mirror del `i07PpdComp` que ya aplicamos en gastos/ingresos G1: la
I PPD/07 hereda como aporte el IVA de la E que la cancela (mismo
lado, mismo mes/año, `tipoRelación ≠ 07`). Net I PPD + E = 0
dentro del mes.
### Archivos tocados
- `apps/api/src/services/impuestos.service.ts`:
- **Predicado nuevo** `IS_I_PPD_07`.
- **Helpers nuevos** `SUM_E_REFERENCING_TRAS(esLadoE)` /
`SUM_E_REFERENCING_RET(esLadoE)`. La I PPD/07 hereda IVA de
TODAS las E que la referencien (mismo lado/mes), sin filtrar
tipoRelación.
- **Helper EXISTS** `HAS_E_REFERENCING_MISMO_MES(esLadoE)` agregado
a `bucketCausadoAny` y `bucketAcreditableAny` para que las
I PPD/07 relevantes entren al `WHERE` de los queries que usan
estos buckets.
- **Predicado EXISTS** `E_REFERENCIA_I_PPD_07_MISMO_MES(esLadoIAlias)`
que se evalúa **desde la fila E**: detecta si una E referencia
una I PPD/07 del mismo lado/mes. Permite distinguir E/07 que
apuntan a anticipo I PUE puro (siguen excluidas del NEG, statu
quo) de E/07 que apuntan a I PPD/07 (entran al NEG en el caso
PPD).
- **`bucketCausadoNeg` y `bucketAcreditableNeg` extendidos** con
disyuntivo `OR E_REFERENCIA_I_PPD_07_MISMO_MES(...)` — sin esto,
la compensación no ocurriría cuando la operación tiene solo una
E/07 (lo fiscalmente correcto pero raro en práctica).
- **Rama nueva** en los 4 signed exprs (`signedCausadoTras`,
`signedCausadoRet`, `signedAcreditableTras`,
`signedAcreditableRet`):
```sql
WHEN ${esLado} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_*(esLadoE)}
```
`esLadoE` = `ctx.esEmisor`/`ctx.esReceptor` con rewrite
`replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1')`. Análogamente
`esLadoIAlias` para alias `i` en `E_REFERENCIA_I_PPD_07_MISMO_MES`.
### Validación con caso real
Husberto Ignacio Torres (RFC `TOAH680201RA2`), agosto 2025:
- Anticipo `729109fc` I PUE: $148K, IVA $20,413.79.
- Aplicación `5c874749` I PPD/07: $454K, IVA $62,620.69.
- NC `7163da3b` E PUE/07: $148K, IVA $20,413.79 (cancela anticipo).
- NC `7aac715b` E PUE/01: $10K, IVA $1,379.31 (sustitución).
| Estado | Acreditable agosto 2025 |
|---|---:|
| Antes | $19,033.69 |
| Después | $20,413.00 |
**Delta: +$1,379.31** acreditable recuperado, exactamente la E/01 que
restaba sin contraparte. La E/07 sigue sin afectar IVA (correcto).
### Cache
`computeMetricaMensual` llama a `getResumenIva` que ya usa los signed
exprs nuevos. Periodos cacheados con la lógica vieja quedan stale
hasta que se recompute.
---
## 5. Refactor IVA — fórmula del owner
Detalle completo: `docs/plans/2026-04-26-iva-refactor.md`.
Cambio mayor en `/impuestos`. El owner pidió alinear el cálculo a un spec
explícito que difiere del código previo en tres puntos:
1. **Sin clamp del IVA en P**: campos `iva_traslado_pago_mxn` /
`iva_retencion_pago_mxn` se usan directos. Antes:
`LEAST(iva, monto × 0.16)`.
2. **Sin compensación I PUE/07**: las I PUE/07 aportan IVA completo. La
E/07 (si se emite) resta normalmente vía bucket NEG. Antes había
`GREATEST(0, IVA Σ IVA anticipos referenciados)`.
3. **E con tipoRel=07 entra al NEG**: ya no se filtran las E/07 del
bucket NEG. Antes el filtro `<> '07'` las excluía (excepto las que
apuntaban a I PPD/07 vía un disyuntivo EXISTS).
### Conservados
- Rama I PPD/07 con `SUM_E_REFERENCING_TRAS/RET` (hereda IVA de E del
mismo mes que la cancelan).
- Estructura de tres KPIs separados: Trasladado / Acreditable / Retenido
con fórmula `Resultado = T A R`.
- Exclusiones por clave_prod_serv (`84121603`, `93161608`, `85101501`,
`85121800`).
- Filtro de régimen por lado del contribuyente (emisor cuando vende,
receptor cuando compra).
### Eliminados del código
- `IS_I_PUE_07`
- `SUM_REL_TRAS`, `SUM_REL_RET`
- `E_REFERENCIA_I_PPD_07_MISMO_MES`
### Validación con Husberto agosto 2025
| KPI | Antes | Después | Delta |
|---|---:|---:|---:|
| Trasladado | $119,093.08 | $111,781.45 | $7,311.63 |
| Acreditable | $147,023.59 | $182,683.84 | +$35,660.25 |
| Resultado IVA | $27,930.51 | $70,902.39 | **$42,971.88** |
Delta favorable al contribuyente (más acreditable, menos a pagar).
### Riesgos aceptados
- Sin compensación I PUE/07 + E/07 ausente → sobrecausa el IVA del anticipo.
En Husberto agosto: 11 I PUE/07 con 0 E/07 emitidas. El owner aceptó
bajo la premisa "fiscalmente el contador debe emitir la E/07".
- Sin clamp P → vulnerables a XMLs de proveedores que reportan IVA total
en pagos parciales.
---
## 6. Notificaciones email automáticas (alertas + recordatorios)
Detalle completo: `docs/plans/2026-04-26-notifications-email.md`.
Cron diario **8:30 AM America/Mexico_City** que cierra el pendiente
histórico de "emails automáticos para alertas/recordatorios" (estaba en
CLAUDE.md "Problemas conocidos"). Modelo elegido por el owner: **Option B
— por evento** (una notificación cuando algo se activa, no digest diario).
### Comportamiento
- **Alertas**: por cada contribuyente activo, llama
`generarAlertasAutomaticas`. Las que aparecen por primera vez se
insertan en `alertas_notificadas` (BD tenant) y disparan email batched
al supervisor + auxiliares + clientes del contribuyente. Una alerta
solo se notifica **una vez en la vida** (MVP). Las que dejan de
aparecer se marcan `resuelta_at` (informativo, no email).
- **Recordatorios**: 3 ventanas (3 días antes, 1 día antes, mismo día).
Cada ventana se envía a lo más una vez vía columnas `email_3d_at /
email_1d_at / email_0d_at` en `recordatorios`. Recipientes: clientes +
auxiliares; si no hay auxiliares, también supervisores; si owner es
supervisor sin auxiliares, también owner.
### Archivos
- Migraciones tenant 039 (alertas_notificadas) y 040 (columnas en recordatorios)
- 2 templates email: `alertas-nuevas.ts`, `recordatorio-proximo.ts`
- `services/notifications.service.ts`: resolución destinatarios + procesamiento
- `jobs/notifications.job.ts`: cron `30 8 * * *` con disparo manual exportado
- `email.service.ts`: helpers `sendAlertasNuevas` + `sendRecordatorioProximo`
- `index.ts`: wire del cron solo si `NODE_ENV === 'production'`
### Operación
- En **dev** el cron NO arranca automáticamente (evita spam con datos de
prueba). Disparo manual: `runNotificationsForTenant(tenantId)`.
- Migraciones aplicadas a los 3 tenants existentes (Patito, Zorro,
mo3nhzvl).
- Sin SMTP configurado los emails se loguean a consola (transport detecta
`SMTP_USER` vacío). **Pendiente real**: configurar SMTP en `.env` para
prod.
---
## 7. Pendientes
**Resoluciones de hoy** (cerradas):
- ✓ Drill-down dashboard también limpiado de columna "Tipo".
- ✓ Compensación IVA aplicada solo al caso PPD (PUE no necesita —
evaluado y descartado, ver §8 del doc i07-ppd).
- ✓ Versión inicial filtraba `<> '07'`; refinada para distinguir por
destino de la E (apunta a I PPD/07 vs apunta a anticipo I PUE puro).
Ahora cubre el caso fiscalmente correcto donde solo existe E/07.
- ✓ Refactor IVA al spec explícito del owner: removida compensación I
PUE/07, removido clamp en P, todas las E PUE entran al NEG. Caso
Husberto agosto 2025 valida $111K trasladado / $182K acreditable.
- ✓ **Notificaciones email automáticas de alertas/recordatorios**
(CLAUDE.md "Problemas conocidos"): cron diario 8:30 AM con detección
de alertas nuevas + 3 ventanas de recordatorio (3d/1d/0d). Modelo
Option B (por evento). Detalle en `2026-04-26-notifications-email.md`.
- ✓ **#2 Convertir `/pendientes` → "Despacho"** (verificado, ya estaba
hecho de sesión previa: módulo `/despachos` con sub-nav).
- ✓ **Recrear org Facturapi de Carlos** (verificado, ya estaba hecho:
TORC9611214CA tiene `facturapi_org_id` y `csd_uploaded=true`).
- ✓ **Sprint 1 — pre-deploy IVA**: validados otros tenants (72-100% de
I PUE/07 con E contraparte), borrado bulk de `metricas_mensuales`
(353 filas en años < 2026), `dashboard.service.ts` alineado con la
fórmula nueva (s4/r4 nuevos, sin clamp P, sin compensación I PUE/07,
sin filtro `<> '07'`). Detalle en `2026-04-26-sprints-1-2-3.md`.
- ✓ **Sprint 2 — bugs latentes**: overage al cambiar de plan
(`reconcileOverageAfterPlanChange` en upgrade/scheduled/cancel),
`getMyPlan` lee `tenant.plan` directamente. "Completadas > Pendientes"
no es bug. Visibilidad auxiliares: investigado, sin reproducción.
- ✓ **Sprint 3 — decisiones owner D1-D7**: Mi Empresa(+) con billing
dual (mensual default + anual con 17% / 10 meses); re-notificación
alertas tras 30 días de resuelta. Resto confirmado o sin cambio.
- ✓ **Sprint 6 — investigación SAT**: logging `codeRequest` verificado
activo (`sat-client.service.ts:116-184`), listo para diagnosticar
rejections futuras. Manuel NO necesitaba re-sync (242 CFDIs
completos); el bug real era de Alexa con record stale del 2026-04-21
— reconciliado a `completed`. Detalle en `2026-04-26-sprints-1-2-3.md`.
- ✓ **Bootstrap admin global del fork**: ejecutado
`pnpm bootstrap:admin-global` con `HORUX_ADMIN_EMAIL=carlos@horuxfin.com`
+ `HORUX_TI_EMAIL=ivan@horuxfin.com`. Crea tenant `Horux 360`
(`HTS240708LJA`) y asigna `platform_admin`/`platform_ti`.
Contraseña fijada manualmente a `Admin12345!` (bcrypt cost 12).
**Pendientes derivados de hoy:**
- Configurar **SMTP en `.env`** de producción para que el cron de
notificaciones envíe correos reales (sin esto se loguean a consola).
- **Alerta automática "P con IVA > 16% del pago"** (follow-up D5) —
detectar XMLs malformados sin reintroducir clamp global.
- **Reportar bug 2.4 con detalle** si reaparece visibilidad de
auxiliares en carteras: capturar pantalla + rol + URL exacta.