Update: nueva version Horux Despachos
This commit is contained in:
193
docs/plans/2026-04-26-iva-refactor.md
Normal file
193
docs/plans/2026-04-26-iva-refactor.md
Normal 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.
|
||||
Reference in New Issue
Block a user