492 lines
19 KiB
Markdown
492 lines
19 KiB
Markdown
# Sesión 2026-04-26 — Compensación I/07 PPD + Activos Fijos
|
||
|
||
Fix focalizado: cuando una I/07 PPD aplica un anticipo y en el **mismo
|
||
mes/año** existe una E (cualquier TipoRelacion) que referencia esa
|
||
I/07 PPD, la I/07 PPD aporta al bucket = base de la E. Antes el filtro
|
||
`metodo_pago = 'PUE'` excluía la I/07 PPD del bucket de facturas pero
|
||
la E sí entraba como NC, generando **gasto/ingreso negativo** en el
|
||
periodo.
|
||
|
||
---
|
||
|
||
## 1. Caso real que motivó el fix
|
||
|
||
Husberto Ignacio Torres (TOAH680201RA2), agosto-2025, gastos:
|
||
|
||
| CFDI | Total | IVA | Base | Notas |
|
||
|---|---:|---:|---:|---|
|
||
| Anticipo `729109FC…` | ? | ? | ? | no en BD del tenant |
|
||
| **I/07 PPD `5c874749`** | $454,000 | $62,621 | $391,379 | apunta al anticipo |
|
||
| **E/07 PUE `7163da3b`** | $148,000 | $20,414 | $127,586 | apunta a `5c874749` (mismo día 2025-08-08) |
|
||
| **E/01 PUE `7aac715b`** | $10,000 | $1,379 | $8,621 | también apunta a `5c874749` (mismo día) |
|
||
|
||
Patrón observado en BD:
|
||
- La I/07 PPD apunta al **anticipo** original.
|
||
- La E (07 o 01) apunta a la **I/07 PPD** (no al anticipo).
|
||
|
||
### Comportamiento previo (Método A puro)
|
||
|
||
```
|
||
I/07 PPD → NO entra al bucket (filtro metodo_pago='PUE')
|
||
E/07 PUE → −$127,586 (NC normal en Método A)
|
||
E/01 PUE → −$8,621
|
||
Net agosto-2025 = −$136,207 ❌ (gasto negativo)
|
||
```
|
||
|
||
El anticipo aportó en su periodo (vía P/PUE original), pero al cancelar
|
||
con la E sin que la I/07 PPD haya entrado al universo del bucket, queda
|
||
una entrada negativa fantasma.
|
||
|
||
### Comportamiento nuevo (con compensación)
|
||
|
||
```
|
||
I/07 PPD compensada = +$127,586 + $8,621 = +$136,207
|
||
E/07 PUE = −$127,586
|
||
E/01 PUE = −$8,621
|
||
Net agosto-2025 = $0 ✓
|
||
```
|
||
|
||
El neto en agosto-2025 vuelve a 0 (el anticipo ya se contó antes y los
|
||
pagos P futuros materializarán el resto del servicio cuando lleguen).
|
||
|
||
---
|
||
|
||
## 2. Volumen del patrón en BD
|
||
|
||
Búsqueda con el query nuevo (`scripts/find-i07-ppd-cases.ts` filtro
|
||
RFC):
|
||
|
||
| Contribuyente | I/07 PPD ↔ E referencias directas | Mismo mes/año |
|
||
|---|---:|---:|
|
||
| Husberto (TOAH680201RA2) | 26 | **23** |
|
||
| (resto del tenant) | varios | varios |
|
||
|
||
23 casos cumplen exactamente la regla "mismo mes/año" en Husberto.
|
||
Implementarlo afecta de forma medible el dashboard.
|
||
|
||
---
|
||
|
||
## 3. Implementación
|
||
|
||
### Archivos modificados
|
||
|
||
`apps/api/src/services/dashboard.service.ts`:
|
||
|
||
#### `calcularEgresosPorRegimen` — bucket adicional `i07PpdComp`
|
||
|
||
```sql
|
||
SELECT i.regimen_fiscal_receptor AS regimen,
|
||
COALESCE(SUM((
|
||
SELECT COALESCE(SUM(
|
||
COALESCE(e.total_mxn, 0)
|
||
- COALESCE(e.iva_traslado_mxn, 0)
|
||
- COALESCE(e.ieps_traslado_mxn, 0)
|
||
- COALESCE(e.impuestos_locales_trasladado_mxn, 0)
|
||
), 0)
|
||
FROM cfdis e
|
||
WHERE e.tipo_comprobante = 'E'
|
||
AND e.status NOT IN ('Cancelado','0')
|
||
AND ${esReceptorE} -- alias 'e.'
|
||
AND LOWER(i.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||
AND date_trunc('month', e.fecha_emision) = date_trunc('month', i.fecha_emision)
|
||
)), 0) AS monto
|
||
FROM cfdis i
|
||
WHERE ${esReceptorI} -- alias 'i.'
|
||
AND i.tipo_comprobante = 'I' AND i.metodo_pago = 'PPD'
|
||
AND i.cfdi_tipo_relacion = '07'
|
||
AND i.status NOT IN ('Cancelado', '0')
|
||
AND ${FR.replace('fecha_emision', 'i.fecha_emision')}
|
||
AND i.regimen_fiscal_receptor = ANY($3)
|
||
GROUP BY i.regimen_fiscal_receptor
|
||
```
|
||
|
||
Sumado al bucket de gastos:
|
||
```ts
|
||
const monto = montoF + montoP + montoI07Comp - montoNC;
|
||
```
|
||
|
||
#### `calcularIngresosPorRegimen` Grupo 1 — bucket simétrico `g1I07PpdComp`
|
||
|
||
Misma lógica pero del lado **emisor** (`esEmisor` en lugar de
|
||
`esReceptor`, `regimen_fiscal_emisor`, filtro a `GRUPO_PF_EMPRESARIAL`).
|
||
|
||
### Helpers SQL
|
||
|
||
Para usar `esEmisor`/`esReceptor` con alias en la query, se hace
|
||
`replace` inline:
|
||
|
||
```ts
|
||
const esReceptorE = esReceptor.replace(/\brfc_receptor\b/g, 'e.rfc_receptor');
|
||
```
|
||
|
||
`esReceptor` viene de `resolveContribuyenteContext()` como fragmento
|
||
`UPPER(rfc_receptor) = 'X_RFC'`. El replace lo prepara para usar con el
|
||
alias `e.`.
|
||
|
||
### Lo que NO se tocó
|
||
|
||
- **Adquisiciones G01** (`calcularAdquisicionesMercancias`): no se
|
||
agregó la compensación todavía. Si surge un caso, replicar el patrón
|
||
con `WHERE e.uso_cfdi = 'G01'` adicional.
|
||
- **IVA causado/acreditable** (`impuestos.service.ts`): mantiene
|
||
compensación NETO_CUSTOM con E/07 (no Método A). La regla de I/07 PPD
|
||
↔ E mismo mes podría aplicar también en simetría, pero requiere
|
||
análisis por separado y está fuera de este cambio.
|
||
|
||
---
|
||
|
||
## 4. Validación
|
||
|
||
### Typecheck
|
||
✅ 0 errores en API.
|
||
|
||
### Recompute
|
||
- 212 filas en `metricas_mensuales` invalidadas con razón
|
||
`I07_PPD_COMPENSACION_E_MISMO_MES`.
|
||
- 392 filas escritas tras `processAllTenantsInvalidations()`.
|
||
- 0 errores.
|
||
|
||
### Caso de validación
|
||
Husberto agosto-2025 gastos: el balance −$136,207 generado por las E
|
||
sin compensación debe desaparecer y volver a 0 en ese periodo.
|
||
|
||
---
|
||
|
||
## 5. Trade-offs y decisiones documentadas
|
||
|
||
### Solo "mismo mes/año"
|
||
|
||
La regla del user es "máximo un periodo después". En BD real, ningún
|
||
caso de Husberto tiene E "1 mes después" — todos los 23 casos están en
|
||
el mismo mes que su I/07 PPD. La regla `date_trunc('month', e.fecha)
|
||
= date_trunc('month', i.fecha)` cubre los casos reales.
|
||
|
||
Si en el futuro aparecen E un mes después con monto significativo, se
|
||
puede ampliar a `date_trunc('month', e.fecha) BETWEEN
|
||
date_trunc('month', i.fecha) AND date_trunc('month', i.fecha + interval
|
||
'1 month')`.
|
||
|
||
### Cualquier TipoRelacion en la E
|
||
|
||
La regla original era E/07 (cancelación de anticipo). Pero los casos
|
||
reales muestran E/01 también compartiendo `cfdis_relacionados` con la
|
||
I/07 PPD (ej. `7aac715b`). Esto es congruente con el bug "TipoRelacion
|
||
sospechoso" que ya documentamos: el emisor a veces pone 01 cuando
|
||
debería ser 07. La compensación nueva captura ambos correctamente.
|
||
|
||
### Patrón de referencia: E → I/07 PPD (no E → anticipo)
|
||
|
||
El patrón observado en BD muestra que las E referencian a la I/07 PPD
|
||
directamente. Es el patrón SAT estándar (la E "ajusta" la factura, no
|
||
el anticipo). El JOIN se hace por:
|
||
|
||
```sql
|
||
LOWER(i.uuid) = ANY(string_to_array(LOWER(e.cfdis_relacionados), '|'))
|
||
```
|
||
|
||
Si en el futuro aparecieran casos con E → anticipo (otro patrón), se
|
||
puede hacer un UNION con el join alternativo.
|
||
|
||
---
|
||
|
||
## 6. Pendientes derivados
|
||
|
||
- **Validar Husberto agosto-2025** post-deploy: ya no debe mostrar
|
||
gasto negativo. Si lo hace, revisar si hay otros patrones (E que
|
||
referencia el anticipo en lugar de la I/07 PPD).
|
||
- **Decidir si aplicar a Adquisiciones G01**.
|
||
- **Decidir si aplicar a IVA causado/acreditable** simétricamente.
|
||
- **Considerar ampliar tolerancia a 1 mes después** si aparece un caso
|
||
real con monto significativo.
|
||
|
||
---
|
||
|
||
## 7. Pestaña "Activos Fijos" en /impuestos
|
||
|
||
Vista informativa nueva para llevar seguimiento de la deducción mensual
|
||
proporcional de activos fijos. **No altera dashboard ni ISR** — el SAT
|
||
trata estos CFDIs como gasto del periodo, así que el sistema los sigue
|
||
contando igual. Esta vista permite al contador planear la deducción
|
||
manual en su declaración anual.
|
||
|
||
### Decisión clave del scope (con el user)
|
||
|
||
Inicialmente se evaluó excluir activos fijos del bucket de gastos y
|
||
del cálculo de ISR. Se descartó porque eso desalineaba el sistema con
|
||
el comportamiento del SAT (que sí considera el CFDI como gasto del
|
||
periodo) y generaría confusión "el sistema no funciona". Decisión:
|
||
sistema se mantiene como está, vista nueva sirve solo para
|
||
**seguimiento informativo** del MOI.
|
||
|
||
### Modelo de cálculo
|
||
|
||
```
|
||
MOI = total_mxn − iva_traslado_mxn − ieps_traslado_mxn − impuestos_locales_trasladado_mxn
|
||
porcentajeMensual = porcentajeAnual / 12
|
||
mesesTranscurridos = (year(periodo) − year(adq)) × 12 + (month(periodo) − month(adq)) + 1
|
||
|
||
acumuladoHastaMes = MIN(MOI, MOI × pctMensual × mesesTranscurridos)
|
||
acumuladoHastaMesPrev = MIN(MOI, MOI × pctMensual × (mesesTranscurridos − 1))
|
||
acreditableEsteMes = acumHasta − acumPrev
|
||
saldoPendiente = MOI − acumHasta
|
||
```
|
||
|
||
Si el activo se da de baja: `mesesAplicables = MIN(mesesTranscurridos,
|
||
mesesEntreAdqYBaja)`. A partir del mes posterior a la baja,
|
||
`acreditableEsteMes = 0`.
|
||
|
||
Dividir `% / 12` evita el problema del primer año (mes parcial) y
|
||
permite seguimiento natural por periodo.
|
||
|
||
### Tabla de % LISR Art. 34
|
||
|
||
| Clave | Concepto | % anual |
|
||
|---|---|---:|
|
||
| I01 | Construcciones | 5% |
|
||
| I02 | Mobiliario y equipo de oficina | 10% |
|
||
| I03 | Equipo de transporte | 25% |
|
||
| I04 | Equipo de cómputo y accesorios | 30% |
|
||
| I05 | Dados, troqueles, moldes, matrices | 35% |
|
||
| I06 | Comunicaciones telefónicas | 10% |
|
||
| I07 | Comunicaciones satelitales | 8% |
|
||
| I08 | Otra maquinaria y equipo | 10% |
|
||
|
||
### Filtros (qué CFDIs entran a esta vista)
|
||
|
||
- `tipo_comprobante = 'I'` y `status NOT IN ('Cancelado','0')`
|
||
- `uso_cfdi ∈ {I01..I08}`
|
||
- Receptor = contribuyente (`esReceptor`)
|
||
- `regimen_fiscal_receptor ∈ {601, 606, 611, 612, 625, 626}`
|
||
- **Para 626**: solo si `rfcLength === 12` (PM). RESICO PF (RFC 13)
|
||
paga tasa plana sin restar deducciones.
|
||
|
||
### Estados
|
||
|
||
- `activo`: aún acreditable, no dado de baja, saldo > 0.
|
||
- `agotado`: saldo = 0 (MOI ya se dedujo completo según meses
|
||
transcurridos).
|
||
- `baja_venta` / `baja_desecho` / `baja_otro`: el contador lo dio de
|
||
baja con motivo correspondiente.
|
||
|
||
### Schema (migración 037)
|
||
|
||
```sql
|
||
CREATE TABLE activos_fijos_baja (
|
||
id serial PRIMARY KEY,
|
||
cfdi_id int NOT NULL REFERENCES cfdis(id) ON DELETE CASCADE,
|
||
fecha_baja date NOT NULL,
|
||
motivo varchar(20) NOT NULL CHECK (motivo IN ('venta','desecho','otro')),
|
||
comentario text,
|
||
dado_de_baja_por uuid NOT NULL,
|
||
created_at timestamptz DEFAULT now(),
|
||
UNIQUE (cfdi_id)
|
||
);
|
||
```
|
||
|
||
### Endpoints
|
||
|
||
```
|
||
GET /api/impuestos/activos-fijos?año=YYYY&mes=MM&contribuyenteId=...&estado=...
|
||
POST /api/impuestos/activos-fijos/:cfdiId/baja
|
||
body: { fechaBaja, motivo: 'venta'|'desecho'|'otro', comentario? }
|
||
DELETE /api/impuestos/activos-fijos/:cfdiId/baja
|
||
```
|
||
|
||
### Archivos
|
||
|
||
- **Migración 037**: `037_activos_fijos_baja.sql`.
|
||
- `apps/api/src/services/activos-fijos.service.ts`: cálculo + manejo
|
||
de baja. Usa `resolveContribuyenteContext` para obtener `rfcLength`
|
||
y filtrar 626 PM.
|
||
- `apps/api/src/controllers/activos-fijos.controller.ts`: 3 handlers
|
||
con Zod.
|
||
- `apps/api/src/routes/impuestos.routes.ts`: 3 rutas montadas en
|
||
`/api/impuestos/activos-fijos`.
|
||
- `apps/web/components/impuestos/activos-fijos-tab.tsx`: componente
|
||
con disclaimer (recordatorio de que es informativa), 4 KPIs (MOI,
|
||
acumulado previo, este mes, saldo pendiente), filtro de estado,
|
||
tabla con badge + acción de baja/reversa, modal de baja con motivo +
|
||
fecha + comentario.
|
||
- `apps/web/app/(dashboard)/impuestos/page.tsx`: botón nuevo
|
||
"Activos Fijos" en el switch de tabs + render condicional.
|
||
|
||
### UX claves
|
||
|
||
- **Disclaimer ámbar** al inicio de la pestaña recordando que el
|
||
sistema considera los CFDIs como gasto del periodo (igual que SAT)
|
||
y esta vista es solo seguimiento, no afecta cálculos automáticos.
|
||
- **Estados visuales** con badge de color (verde/gris/ámbar/rojo).
|
||
- **Filtro de estado** (todos/activos/agotados/baja).
|
||
- **Acción reversible**: dar de baja siempre se puede revertir
|
||
(DELETE en `/baja`) — la fila vuelve a calcular meses normalmente.
|
||
|
||
### NO se tocó
|
||
|
||
- `calcularEgresosPorRegimen`, `calcularAdquisicionesMercancias`,
|
||
`calcularResumenIsr`, `getIsrMensual`: intactos.
|
||
- `metricas_mensuales` cache: no requiere recompute.
|
||
- IVA causado/acreditable: sigue incluyendo estos CFDIs como antes.
|
||
|
||
### Filtro de conceptos por contribuyente (migración 038)
|
||
|
||
I06 (Comunicaciones telefónicas) y I07 (Comunicaciones satelitales)
|
||
suelen usarse para **gastos regulares** (factura de teléfono, internet
|
||
satelital) que no son adquisiciones de activos fijos. Para no ensuciar
|
||
la vista, el contador puede excluir conceptos por contribuyente.
|
||
|
||
**Migración 038**:
|
||
```sql
|
||
ALTER TABLE contribuyentes
|
||
ADD COLUMN activos_fijos_usos_excluidos jsonb DEFAULT '[]'::jsonb;
|
||
```
|
||
|
||
**Endpoints**:
|
||
```
|
||
PUT /api/impuestos/activos-fijos/usos-excluidos
|
||
body: { contribuyenteId, usos: ['I06','I07'] }
|
||
```
|
||
|
||
El response del `GET /activos-fijos` incluye `usosExcluidos` (lista
|
||
actual) para que el UI muestre badge "N excluidos".
|
||
|
||
**UI**: botón "Conceptos" en la barra de filtros abre modal con 8
|
||
checkboxes (uno por uso I01-I08). Por default todos están marcados
|
||
(considerados). Desmarcar = excluir. Persiste en BD.
|
||
|
||
### Pendientes derivados
|
||
|
||
- Auto-detectar bajas que vienen de CFDIs tipo egreso emitidos por el
|
||
contribuyente que cancelan parcialmente un activo (ej. venta de
|
||
equipo). Hoy es manual.
|
||
- Vista anual con resumen por concepto y depreciación de cierre.
|
||
- Conectar con declaraciones anuales: cuando el contador suba la
|
||
declaración anual, mostrar checkbox para "este activo lo apliqué
|
||
como deducción este ejercicio" para llevar trazabilidad.
|
||
- Considerar nuevos usos CFDI introducidos por SAT en el futuro
|
||
(mantener mapa centralizado).
|
||
- Permitir excluir CFDIs específicos (no solo conceptos completos)
|
||
para casos mixtos (ej. el cliente compra un teléfono celular
|
||
ocasional que SÍ es activo, pero la factura mensual del servicio
|
||
telefónico también es I06 y NO es activo).
|
||
|
||
---
|
||
|
||
## 8. Extensión IVA — compensación I PPD/07 ↔ E (turno 2026-04-26)
|
||
|
||
### Asimetría que motivó el cambio
|
||
|
||
El flujo del SAT con anticipo causa el IVA en tres puntos:
|
||
- **Anticipo I PUE** — IVA causado/acreditado en su mes (PUE = se causa al emitir).
|
||
- **Aplicación I/07** — la factura final que aplica el anticipo. Si es **PUE** aporta su IVA completo; si es **PPD** aporta 0 hasta que llegue el P.
|
||
- **E que cancela** — NC formal o cancelación de operación.
|
||
|
||
En el caso **PUE** (aplicación I PUE/07), la cadena cierra algebraicamente
|
||
gracias al filtro `bucketCausadoNeg/Acreditable` que excluye `tipoRelación='07'`
|
||
y al SUM_REL_TRAS que compensa la I PUE/07 contra el anticipo. Sin
|
||
necesidad de tocar nada.
|
||
|
||
En el caso **PPD**, la I PPD/07 no aporta nada en su mes (espera al P).
|
||
Si en el **mismo mes** existe una E con tipoRelación **≠ 07** que la
|
||
referencia, la E entra al `bucketAcreditableNeg` (o `bucketCausadoNeg`)
|
||
y resta IVA — pero la I PPD/07 nunca aportó nada que la E pudiera
|
||
neutralizar. Resultado: se "pierde" el IVA equivalente a la E.
|
||
|
||
### Implementación (`apps/api/src/services/impuestos.service.ts`)
|
||
|
||
Nuevos predicados/helpers:
|
||
|
||
- `IS_I_PPD_07` — gemelo de `IS_I_PUE_07` para metodo_pago='PPD'.
|
||
- `SUM_E_REFERENCING_TRAS(esLadoE)` / `SUM_E_REFERENCING_RET(esLadoE)` —
|
||
subqueries que suman el IVA de las E's que referencian la I PPD/07
|
||
actual, filtrando por **mismo lado** y **mismo mes/año**.
|
||
No filtran por `tipoRelación`: en PPD cualquier E que apunte a la
|
||
I PPD/07 cuenta (incluyendo las 07, fiscalmente correctas).
|
||
- `HAS_E_REFERENCING_MISMO_MES(esLadoE)` — EXISTS para incluir las
|
||
I PPD/07 en `bucketCausadoAny`/`bucketAcreditableAny`. Sin filtro
|
||
tipoRelación (consistente con `SUM_E_REFERENCING_*`).
|
||
- `E_REFERENCIA_I_PPD_07_MISMO_MES(esLadoIAlias)` — EXISTS desde la
|
||
fila E que verifica si esta E referencia una I PPD/07 del mismo
|
||
lado/mes. Permite distinguir dos clases de E/07:
|
||
- E/07 → anticipo I PUE puro (triángulo PUE clásico): EXISTS = false
|
||
→ la E/07 queda excluida del NEG (statu quo, la lógica
|
||
`SUM_REL_TRAS` de la I PUE/07 ya cierra el ciclo).
|
||
- E/07 → I PPD/07 (cancelación de operación PPD): EXISTS = true
|
||
→ la E/07 entra al NEG y resta IVA. La I PPD/07 hereda el mismo
|
||
IVA via `SUM_E_REFERENCING_*`, neteando dentro del mes.
|
||
|
||
`bucketCausadoNeg` y `bucketAcreditableNeg` extendidos con el
|
||
disyuntivo `OR E_REFERENCIA_I_PPD_07_MISMO_MES(...)` para que las
|
||
E/07 que apuntan a I PPD/07 no queden filtradas. Los aliases `e` y
|
||
`i` se derivan de `ctx.esEmisor`/`ctx.esReceptor` con el rewrite
|
||
`replace(/\brfc_(emisor|receptor)\b/g, 'e.rfc_$1' | 'i.rfc_$1')`.
|
||
|
||
Rama nueva en los 4 signed exprs (`signedCausadoTras/Ret`,
|
||
`signedAcreditableTras/Ret`):
|
||
|
||
```
|
||
WHEN ${esLado} AND ${IS_I_PPD_07} THEN ${SUM_E_REFERENCING_*(esLadoE)}
|
||
```
|
||
|
||
### Por qué la versión inicial filtraba `<> '07'` (descartado)
|
||
|
||
La primera implementación filtraba `tipoRelación <> '07'` en
|
||
`SUM_E_REFERENCING_*` y `HAS_E_REFERENCING_MISMO_MES`, asumiendo que
|
||
las E/07 estaban universalmente excluidas del NEG y que heredarlas
|
||
sobre-acreditaría. Eso era cierto solo para el triángulo PUE puro,
|
||
pero **ignoraba el caso fiscalmente correcto**: una E/07 que cancela
|
||
una I PPD/07 sí debe restar IVA, porque la I PPD nunca aportó nada
|
||
en su mes.
|
||
|
||
La corrección es discriminar **a qué apunta la E**, no qué tipoRelación
|
||
tiene. Si apunta a una I PPD/07 → afecta IVA simétricamente (E resta
|
||
en NEG, I PPD hereda en POS, netean a 0). Si apunta a un anticipo
|
||
I PUE puro → queda excluida (statu quo).
|
||
|
||
### Validación con caso real Husberto 2025-08
|
||
|
||
Receptor TOAH680201RA2 con 4 CFDIs en `cfdis_relacionados` enredados:
|
||
- Anticipo `729109fc` I PUE: $148K, IVA $20,413 → +$20,413 acreditable (POS, en su mes)
|
||
- Aplicación `5c874749` I PPD/07: $454K, IVA $62,621 → hereda IVA total de las E del mismo mes
|
||
- NC `7163da3b` E PUE/07: $148K, IVA $20,413 → ahora entra al NEG (apunta a I PPD/07)
|
||
- NC `7aac715b` E PUE/01: $10K, IVA $1,379 → entra al NEG (tipoRelación ≠ 07)
|
||
|
||
| Concepto | Aporte agosto 2025 |
|
||
|---|---:|
|
||
| Anticipo I PUE (POS) | + $20,413.79 |
|
||
| I PPD/07 hereda E/07 + E/01 (rama nueva) | + $21,793.10 |
|
||
| E/07 (NEG, ahora incluida porque apunta a I PPD/07) | − $20,413.79 |
|
||
| E/01 (NEG, ya estaba) | − $1,379.31 |
|
||
| **Total acreditable** | **$20,413.79** |
|
||
|
||
| Estado | Acreditable agosto 2025 |
|
||
|---|---:|
|
||
| Antes del cambio | $20,413.79 + 0 + 0 − $1,379.31 = **$19,033.69** |
|
||
| Después (versión inicial con filtro `<> '07'`) | $20,413.79 + $1,379.31 + 0 − $1,379.31 = **$20,413.79** |
|
||
| Después (versión refinada sin filtro) | Ver tabla ↑ = **$20,413.79** |
|
||
|
||
**Delta total vs antes: +$1,379.31 acreditable recuperado.** Las dos
|
||
versiones (con/sin filtro) dan el mismo resultado en el caso Husberto
|
||
porque la E/01 y la E/07 cubren montos distintos. La versión refinada
|
||
es necesaria para casos donde **solo existe la E/07** (lo correcto
|
||
fiscalmente): sin la condición nueva en `bucketAcreditableNeg`, la
|
||
E/07 quedaría excluida y la I PPD nunca sería incluida en el bucket
|
||
Any → la compensación no ocurriría.
|
||
|
||
### Cache `metricas_mensuales`
|
||
|
||
`computeMetricaMensual` en `metricas-compute.service.ts` llama a
|
||
`getResumenIva` que ya usa los signed exprs nuevos — futuros recomputes
|
||
escriben los valores correctos. Periodos cacheados con la lógica vieja
|
||
quedan stale hasta invalidarse. Pendiente: barrido de invalidación por
|
||
periodo donde existan I PPD/07 + E/(≠07) referenciándolas en mismo mes.
|
||
|
||
### Por qué no se aplicó al caso PUE
|
||
|
||
El caso anticipo I PUE + I PUE/07 + E/07 ya cierra con la lógica
|
||
existente (compensación SUM_REL_TRAS en I PUE/07, exclusión de E/07
|
||
del NEG). Algebraicamente equivalente al flujo "natural" donde la E/07
|
||
restaría — la diferencia es que el código actual es **robusto al caso
|
||
"no se emite E/07"** (común en aplicaciones íntegras), donde el flujo
|
||
natural sobrecausaría. Cambiar PUE rompería ese caso típico para
|
||
ganar nada en el atípico.
|