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,193 @@
# Refactor IVA — fórmula del owner (2026-04-26)
Cambio mayor en la fórmula del IVA causado/acreditable de `/impuestos`.
El owner pidió alinear el cálculo a un spec explícito que difiere en tres
puntos clave del código previo. Doc de las fórmulas, los cambios SQL
puntuales y la validación con Husberto.
> Relacionado: `docs/plans/2026-04-26-i07-ppd-compensacion.md` §8 (rama
> nueva I PPD/07 que se conserva en este refactor).
---
## 1. Fórmula del owner
### IVA Trasladado (lado emisor del contribuyente)
| Componente | Filtros | Campo IVA |
|---|---|---|
| (+) I PUE emisor | `tipo='I' AND metodo_pago='PUE'`, régimen emisor en lista, vigente | `iva_traslado_mxn iva_retencion_mxn` |
| (+) P emisor | `tipo='P'`, régimen emisor en lista, vigente | `iva_traslado_pago_mxn iva_retencion_pago_mxn` |
| (+) **I PPD/07 emisor — hereda** | `tipo='I' AND metodo_pago='PPD' AND tipoRel='07'`, régimen emisor en lista | suma de IVA neto de E que la referencien en mismo mes |
| () E PUE emisor | `tipo='E' AND metodo_pago='PUE'`, régimen emisor en lista | `iva_traslado_mxn iva_retencion_mxn` |
### IVA Acreditable (lado receptor)
Simétrico: cambia `rfc_emisor → rfc_receptor` y `regimen_fiscal_emisor →
regimen_fiscal_receptor`. El componente "I PPD/07 hereda" busca E del lado
**receptor** que la referencien.
### Reglas globales
- **Régimenes considerados**: `605, 606, 612, 621, 625, 626, 601, 603, 607,
608, 610, 611, 614, 615, 620, 622, 623, 624` (excluye 616 público en
general, 614 ingresos por intereses, etc. según lista del owner).
- **Filtro de régimen por lado**: el régimen del lado del contribuyente —
emisor cuando vende, receptor cuando compra.
- **Conceptos excluidos**: claves prod/serv `84121603` (seguros), `93161608`
(gobierno), `85101501` (salud), `85121800` (servicios médicos) se restan
del IVA del CFDI.
- **Tipo P**: usa `iva_traslado_pago_mxn` y `iva_retencion_pago_mxn`
directos, **sin clamp** (vs el código previo que aplicaba
`LEAST(iva, monto*0.16)` como defensa contra XMLs malformados).
- **E con tipoRel=07**: SÍ entran al NEG y restan IVA. El owner asume que
el contador emite la E/07 cuando se cancela el anticipo. Si no se
emite, el IVA del anticipo se sobrecausa (riesgo aceptado).
- **I PUE/07**: aporta IVA completo, **sin compensación** contra los
anticipos referenciados (el código previo restaba el IVA del anticipo
para evitar doble conteo cuando E/07 ausente).
---
## 2. Diferencias vs código previo
| Aspecto | Antes | Ahora |
|---|---|---|
| Clamp IVA en P | `LEAST(iva, monto×0.16)` | Campo directo |
| Compensación I PUE/07 | `GREATEST(0, IVA Σ IVA anticipos)` | Sin compensación, IVA completo |
| E con tipoRel=07 | Excluida del NEG (filtro `<> '07'`), excepto si apuntaba a I PPD/07 | Todas las E PUE entran al NEG |
| `bucketCausadoNeg`/`bucketAcreditableNeg` | Compleja con `OR E_REFERENCIA_I_PPD_07` | Simple: `E PUE del lado` |
| Predicado `E_REFERENCIA_I_PPD_07_MISMO_MES` | Existía | **Eliminado** (ya no necesario) |
| `IS_I_PUE_07`, `SUM_REL_TRAS`, `SUM_REL_RET` | Existían | **Eliminados** |
| `IS_I_PPD_07`, `SUM_E_REFERENCING_TRAS/RET`, `HAS_E_REFERENCING_MISMO_MES` | Existían | **Conservados** (rama nueva I PPD/07) |
| Presentación KPI | Trasladado / Acreditable / Retenido separados | **Igual**: separados, fórmula `T A R` |
---
## 3. Cambios concretos en `apps/api/src/services/impuestos.service.ts`
### Eliminados
- `IS_I_PUE_07`
- `SUM_REL_TRAS`, `SUM_REL_RET`
- `E_REFERENCIA_I_PPD_07_MISMO_MES`
### Modificados
- `IVA_TRAS_EXPR`, `IVA_RET_EXPR`: rama de tipo P sin `LEAST(...)`.
- `IVA_TRAS_EXPR_ALIAS`, `IVA_RET_EXPR_ALIAS`: idem para subqueries.
- `bucketCausadoNeg`, `bucketAcreditableNeg`: simplificados a `E PUE del
lado correcto` sin filtros tipoRel ni rama EXISTS.
- `signedCausadoTras/Ret`, `signedAcreditableTras/Ret`: removida la rama
`WHEN bucket POS AND IS_I_PUE_07 THEN GREATEST(0, IVA SUM_REL)`.
Quedan tres ramas: POS, I PPD/07 hereda, NEG.
### Conservados sin cambios
- `IS_I_PPD_07`
- `SUM_E_REFERENCING_TRAS`, `SUM_E_REFERENCING_RET`
- `HAS_E_REFERENCING_MISMO_MES`
- `bucketCausadoAny`, `bucketAcreditableAny` (solo usan
`HAS_E_REFERENCING_MISMO_MES`, no el predicado eliminado)
- Bloques de presentación KPI en `getResumenIva` y `getIvaMensual`
---
## 4. Validación con caso real
Husberto Ignacio Torres (RFC `TOAH680201RA2`), agosto 2025:
| KPI | Antes refactor | Después refactor |
|---|---:|---:|
| Trasladado | $119,093.08 | $111,781.45 |
| Acreditable | $147,023.59 | $182,683.84 |
| Retenido | $0.00 | $0.00 |
| Resultado IVA | $27,930.51 | **$70,902.39** |
Delta resultado: **$42,971.88** a favor del contribuyente. La diferencia
se origina en:
1. **Compensación I PUE/07 removida**: 11 I PUE/07 del mes con $48,197 IVA
bruto. Antes aportaban su remanente vs anticipos; ahora aportan completo
→ +acreditable.
2. **E/07 que cancelaba anticipos PUE ahora resta**: antes excluida del NEG;
ahora entra → más NC en el cálculo.
3. **Sin clamp P**: P recibidas con IVA reportado mayor al 16% del pago ya
no se truncan → +acreditable.
**Validación numérica** (breakdown bruto agosto 2025 lado receptor):
- I PUE recibidas: $186,714.60
- P recibidas: $43,659.91
- I PPD/07 hereda IVA de E: $21,793.10
- E PUE recibidas (resta): $69,483.77
- **Total Acreditable: $182,683.84** ✓
Lado emisor:
- I PUE emisor: $111,781.45
- P emisor: $0
- I PPD/07 emisor hereda: $0
- E PUE emisor (resta): $0
- **Total Trasladado: $111,781.45** ✓
---
## 5. Riesgos y trade-offs aceptados
### Sobrecausa cuando E/07 ausente
Sin compensación I PUE/07, el flujo `anticipo I PUE + I PUE/07 sin E/07`
sobrecausa por el monto del anticipo. En Husberto agosto 2025 hay **11 I
PUE/07 con 0 E/07 emitidas** → todo ese volumen actualmente sobrecausa.
El owner aceptó este trade-off bajo la premisa fiscal: "lo correcto es que
el contador emita la E/07 cuando aplica el anticipo". Si en producción se
detectan tenants donde sistemáticamente faltan las E/07, la decisión deberá
revisarse (revertir a compensación o introducir un toggle por tenant).
### Sin clamp en P
XMLs de proveedores que reportan el IVA de la factura completa en P
parciales causan un IVA acreditable inflado. El código previo defendía con
`LEAST(iva, monto×0.16)`. Ahora se confía en que el campo del XML sea
correcto.
### Divergencia con dashboard
`apps/api/src/services/dashboard.service.ts` mantiene su lógica de IVA
balance independiente. Después de este cambio, los KPIs del dashboard
podrían diferir de `/impuestos`. Pendiente: alinear (o documentar la
diferencia intencional).
---
## 6. Cache `metricas_mensuales`
El cambio invalida silenciosamente todas las filas pre-calculadas en
`metricas_mensuales` (cualquier periodo cerrado por contribuyente). Para
repoblar:
```sql
-- Borrar cache de un contribuyente específico:
DELETE FROM metricas_mensuales
WHERE contribuyente_id IN (SELECT entidad_id FROM contribuyentes WHERE rfc = 'XXX');
-- O global del tenant (si se rehace para todos):
DELETE FROM metricas_mensuales;
```
Después, las consultas a años cerrados caerán al path on-the-fly hasta
que el cron `computeMetricaMensual` repueble la tabla.
---
## 7. Pendientes
- **Recompute bulk** de `metricas_mensuales` para todos los tenants y años
pasados con la fórmula nueva (ahora mismo solo limpiamos la cache de
Husberto 2025).
- **Validar otros tenants**: el delta esperado depende del volumen de I
PUE/07 sin E/07 contraparte. Tenants que no usen el patrón de anticipo
no verán cambio significativo; los que sí lo usen verán acreditable
subir.
- **Alinear dashboard**: si los KPIs de `/dashboard` y `/impuestos`
divergen, decidir cuál fórmula es la canónica.
- **Documentar para usuarios finales**: el cambio en el resultado IVA es
notable (~$43K en Husberto agosto). Si se va a desplegar a producción,
preparar nota de release explicando por qué cambian los números.