194 lines
7.9 KiB
Markdown
194 lines
7.9 KiB
Markdown
# 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.
|