# Sesión 2026-04-27 — Compensación I PPD/07 solo con E PUE + filtro CFDI por tipo

Dos cambios independientes pedidos en la sesión:

1. La compensación I PPD/07 ↔ E (introducida 2026-04-26) ahora solo aplica
   cuando la **E referenciante es PUE**. Si la E es PPD, no se compensa.
2. Bug: el filtro de tipo de comprobante (I/E/P/T/N) en `/cfdi` no estaba
   funcionando — el dropdown actualizaba el state pero la query SQL nunca
   recibía el parámetro.

---

## 1. Compensación I PPD/07 — restricción a E PUE

### Motivación

La asimetría detectada: el bucket NEG (`bucketCausadoNeg`/`bucketAcreditableNeg`
en `impuestos.service.ts`, y `nc` en `dashboard.service.ts`) **solo cuenta E
PUE** — las E PPD no restan en su mes (esperan al P). Pero la compensación
I PPD/07 ↔ E disparaba con cualquier E (PUE o PPD) referenciante en mismo
mes, generando aporte sin contraparte cuando la E era PPD.

Caso concreto: si la I PPD/07 era cancelada por una **E PPD** en mismo mes,
el periodo recibía `+IVA/base` de la I PPD/07 sin que la E PPD restara nada
(porque las E PPD están fuera del NEG). Resultado: ingreso/gasto inflado en
el mes.

Regla nueva: solo E PUE dispara compensación, simétrico al NEG.

### Cambios — 7 puntos

Filtro `AND e.metodo_pago = 'PUE'` agregado en cada subquery que detecta
E referenciantes:

**`apps/api/src/services/impuestos.service.ts`** (3 puntos)
- L99 — `SUM_E_REFERENCING_TRAS`
- L110 — `SUM_E_REFERENCING_RET`
- L136 — `HAS_E_REFERENCING_MISMO_MES` (predicado EXISTS para el WHERE)

**`apps/api/src/services/dashboard.service.ts`** (4 puntos)
- L377 — `g1I07PpdComp` (ingresos PF Empresarial G1, base)
- L543 — `i07PpdComp` (egresos, base)
- L752 — `s4` (balance IVA por régimen, lado emisor)
- L775 — `r4` (balance IVA por régimen, lado receptor)

### Comportamiento esperado por escenario

| Escenario | Antes | Ahora |
|---|---|---|
| I PPD/07 + E PUE mismo mes | Compensación (suma a 0) | Compensación (suma a 0) — sin cambio |
| I PPD/07 + E PPD mismo mes | I PPD/07 hereda IVA/base (E PPD no resta → ingreso/gasto inflado) | I PPD/07 no compensa (E PPD se cobra vía P más tarde, ahí causa) |
| I PPD/07 + sin E mismo mes | I PPD/07 espera al P | Sin cambio |

### Cache `metricas_mensuales`

`metricas-compute.service.ts` invoca `calcular*PorRegimen` y `getResumenIva`,
así que la lógica nueva fluye automáticamente en el próximo recompute. Filas
ya cacheadas con la lógica vieja siguen sirviéndose hasta su recompute manual
(o vía `METRICAS_BYPASS_CACHE=1`).

---

## 2. Filtro de tipo de comprobante en `/cfdi` — fix

### Síntoma

Dropdown "Tipo de comprobante" en `/cfdi` (Select con I/E/P/T/N) actualizaba
`filters.tipoComprobante` correctamente, pero la lista no filtraba — todos
los tipos seguían apareciendo.

### Causa raíz — 2 puntos rotos

El service (`apps/api/src/services/cfdi.service.ts:93-95`) ya tenía la lógica:

```ts
if (filters.tipoComprobante) {
  whereClause += ` AND tipo_comprobante = $${paramIndex++}`;
  params.push(filters.tipoComprobante);
}
```

Pero **dos eslabones intermedios** no propagaban el filtro:

1. **Cliente HTTP** (`apps/web/lib/api/cfdi.ts`, función `getCfdis`):
   serializaba `tipo`, `estado`, `fechaInicio`, etc. — pero **no**
   `tipoComprobante`. El query string llegaba al backend sin el parámetro.

2. **Controller** (`apps/api/src/controllers/cfdi.controller.ts`,
   `getCfdis`): construía `filters` desde `req.query` sin leer
   `tipoComprobante`. Aunque el cliente lo hubiera enviado, el controller
   lo descartaba.

### Fix

`apps/web/lib/api/cfdi.ts` — agregada serialización:

```ts
if (filters.tipoComprobante) params.set('tipoComprobante', filters.tipoComprobante);
```

`apps/api/src/controllers/cfdi.controller.ts` — agregado al spread de filters:

```ts
tipoComprobante: req.query.tipoComprobante as any,
```

### Lección

Patrón frecuente en este repo: agregar un filtro nuevo requiere **3 puntos
sincronizados** — tipo en `@horux/shared`, serialización en
`apps/web/lib/api/*.ts`, parsing en `apps/api/src/controllers/*.ts`. El tipo
existía (`CfdiFilters.tipoComprobante` en `packages/shared/src/types/cfdi.ts:115`),
y el service consumía. Lo intermedio quedó desconectado y falló silencioso.

---

## 3. Pendientes derivados

- **Recompute de `metricas_mensuales`** para meses con I PPD/07 + E PPD que
  hayan cacheado valores inflados. Disparar via `metricas-compute.service.ts`
  o setear `METRICAS_BYPASS_CACHE=1` temporalmente para auditar diferencias.
- **Smoke test** en `/cfdi` con cada tipo (I/E/P/T/N) tras refrescar.
- **Reverter aún pendiente** (de sesión 2026-04-26): owner pidió evaluar si
  toda la rama I PPD/07 ↔ E debería desaparecer. Fix de hoy es paliativo —
  cierra la asimetría más visible pero la regla de "I PPD hereda E" sigue
  siendo opinión, no SAT canónico.
