# Plan — Importación de mercadería del exterior

Estado al **2026-09-24**. Rama `feat/importacion-mercaderia-exterior` (backend y frontend).

Documento de diseño e implementación. Para el detalle de las pruebas ver `qa-importacion-mercaderia-exterior.md`; para la operación del módulo, `guias/guia-importaciones.md`.

---

## El pedido

> *«En importaciones hay que permitir asociar una factura de compra de mercadería del exterior, con lo que implica en el tema impositivo: no tiene IVA pero afecta stock, y hay que hacer la imputación contable y habilitar el mapeo de cuentas.»*

Aprobado por el cliente para implementar el 2026-09-24, sobre el documento de circuito que se le presentó (`Importar-mercaderia-del-exterior.pdf`, en la raíz del proyecto).

## Qué ya existía

Medio pedido estaba resuelto antes de escribir una línea. Verificado contra producción:

- El concepto de costo **`FOB — Precio en origen`** (DIRECTO, USD), ya en uso: DOBA tenía un componente de Gs. 376.985.027 cargado a mano.
- El **prorrateo y el costeo** de los ítems.
- El **asiento al confirmar el despacho**: Debe `INVENTARIO` + Debe `IVA_CREDITO_10`, Haber proveedor (`integracion.service.ts:516`).
- El **mapeo de cuentas**: existe el concepto `PROVEEDORES_EXTERIOR` y los proveedores se marcan con `tipo_entidad = 'PROVEEDOR_EXTERIOR'`, con override de cuenta por proveedor.
- La **generación de CxP** desde un componente de costo.

Por eso el punto «habilitar el mapeo de cuentas» del pedido no generó trabajo: ya estaba.

## Qué faltaba

1. **El stock no se movía.** La liberación creaba producto, `stock_deposito` y `movimientos_inventario` **solo para el perfil AUTOS**. Para `CONSUMO_MASIVO` apenas marcaba los ítems DISPONIBLE. En producción había un embarque liberado por Gs. 507.271.894 con **cero** movimientos de inventario.
2. **El IVA del despacho no llegaba al Libro IVA.** DOBA tenía Gs. 39.192.006 de crédito fiscal sin reflejar en ningún reporte.
3. **No había documento de compra.** `compra_cab` no conocía importaciones; la factura del exterior era texto libre en `imp_componentes_costo.numero_factura`.
4. **Sin CxP** por esa factura.

---

## Decisiones del cliente

| Pregunta | Respuesta | Cómo se implementó |
|---|---|---|
| ¿La factura reemplaza la carga manual del FOB? | **Sí** | `convertir_componente_id`: reusa el componente existente en vez de crear otro. |
| ¿Con qué cotización se valoriza? | **Configurable** | `imp_config_empresa.criterio_cotizacion` |
| ¿A qué depósito entra? | **Se elige en cada liberación** | Ya era así: `deposito_destino_id` es obligatorio en el DTO. |

**Abierto:** cuál es el criterio de cotización **por defecto** para las empresas que no lo configuren. Hoy queda en `FACTURA` —el momento en que nace la obligación con el proveedor— pero es criterio contable y lo define el cliente.

---

## Modelo de datos

Migración **`20260924_compra_importacion`** (idempotente).

```prisma
enum imp_criterio_cotizacion { FACTURA  DESPACHO  CIERRE_EMBARQUE }

imp_config_empresa.criterio_cotizacion  imp_criterio_cotizacion @default(FACTURA)

compra_cab.embarque_id         String? → imp_embarques  (onDelete: SetNull)
imp_componentes_costo.compra_id String? → compra_cab     (onDelete: SetNull)
```

`compra_cab.embarque_id` replica el patrón que ya usaba `gasto_cab.embarque_id`. `compra_id` en el componente es el espejo de `gasto_id`: dice de qué comprobante nació ese costo.

---

## El circuito

| # | Paso | Estado |
|---|---|---|
| 1 | Se abre el embarque | ya existía |
| 2 | Se carga la **factura del proveedor del exterior** desde Compras, vinculada al embarque | **nuevo** |
| 3 | Esa factura genera la **CxP** y el **componente de costo FOB** | **nuevo** |
| 4 | Se cargan los gastos de la importación (flete, seguro, despachante) | ya existía |
| 5 | Se carga el despacho aduanero | **cambia**: su IVA va al Libro IVA |
| 6 | Se confirma el despacho y se prorratea el costo | ya existía |
| 7 | Se libera: la **mercadería entra al stock** | **nuevo** para `CONSUMO_MASIVO` |
| 8 | Se genera el asiento contable | ya existía |

---

## Las tres trampas del diseño

Son la parte que hace que el circuito cierre, y las tres son de doble conteo.

**1. La factura del exterior no va al Libro IVA.** `getLibroIvaCompras` filtra `embarque_id IS NULL`. No es comprobante paraguayo: el IVA de esa importación se paga en aduana y se declara con el despacho, que sí entra al libro. Si entrara la factura también, la base gravada se contaría dos veces.

**2. La factura no mueve stock.** Una compra normal descarga stock al facturar. Acá eso duplicaría las unidades y —peor— las valorizaría **solo al FOB**, sin flete ni tributos. La mercadería entra una sola vez, al liberar, con el costo prorrateado completo. Guard: `flowResolution.afectarStockEnCompra && !cabecera.embarque_id`.

**3. El cierre del despacho excluye los componentes con `compra_id`,** igual que ya hacía con `gasto_id`. La compra genera su propio asiento por `integrarCompra`; sumarla de nuevo en el asiento de cierre contabilizaría el costo dos veces.

---

## Detalles de implementación

**Cotización — `resolverCotizacionImportacion`.** Si el criterio configurado apunta a un valor que todavía no existe (cargar la factura antes del despacho), cae a la cotización de la factura, que es la única que seguro está. Sin ese fallback se costearía a cotización 1.

**Validación antes de escribir en la liberación.** Ítems sin producto, cantidad en cero o productos inexistentes cortan **antes** de tocar inventario. Liberar es irreversible desde la pantalla: un embarque a medias dejaría stock inconsistente sin forma de rehacerlo.

**El componente FOB es compartido y se prorratea por FOB, no directo.** El costeo reparte los costos directos con `es_directo && item_id === item.id`: un componente directo **sin `item_id` no llega a ningún ítem**. Como una factura del exterior cubre varios ítems, no hay un único ítem al que atarla. La primera versión la marcaba directa y el costo se perdía en silencio — lo encontró el test E2E. Por FOB es además el reparto correcto: la factura se distribuye en proporción al valor en origen de cada ítem.

**No se crean productos en `CONSUMO_MASIVO`,** a diferencia de AUTOS. En autos cada chasis es único y crear un producto por ítem tiene sentido; en mercadería general el mismo SKU se repite entre embarques y crearlo por importación llenaría el catálogo de duplicados. El ítem tiene que venir mapeado (campo «SKU interno» del diálogo de ítems).

**Productos que no manejan inventario** quedan DISPONIBLE sin movimiento, con aviso en el log.

**`ImportacionesCosteoService` se provee directo en `ComprasModule`,** no vía `ImportacionesModule`: ese módulo importa `ComprasModule` para generar CxP, así que importarlo sería un ciclo. El servicio no tiene dependencias propias —recibe la transacción por parámetro—, así que instanciarlo dos veces no comparte estado.

---

## Commits

| Commit | Qué |
|---|---|
| `653a185` | Fase 1 — stock al liberar para `CONSUMO_MASIVO` |
| `3c5e4b5` | Fase 2 — el IVA del despacho al crédito fiscal (backend) |
| `2d4b677` | Fase 2 — despachos en el reporte (frontend) |
| `5c0a1e2` | Fases 3 y 4 — la factura del exterior como compra + CxP |
| `facc93e` | Fase 3 — selector de embarque en Compras y deep-link de regularización |
| `7ee428c` | El mapeo a producto se anuncia como obligatorio |
| `1ec1fa9` | QA de integración + documentación |
| *(pendiente)* | E2E por `ComprasService.create` + fix del componente FOB |

---

## Lo que falta para dar el plan por completo

### Bloqueante

- **La migración no está en producción.** `20260924_compra_importacion` está aplicada solo en local.
- **La rama no está mergeada** a `pos-dev` / `dev`.

### Ya no bloqueante

El circuito **sí** se recorre automáticamente de punta a punta: la suite E2E entra por `ComprasService.create` —el mismo método que llama la pantalla— y ejerce los tres guards de doble conteo que antes sólo estaban escritos. Ahí apareció el bug del componente FOB. Queda pendiente la prueba **con un usuario real en pantalla**, que es otra cosa: valida la UI, no los invariantes.

### Decisiones pendientes del cliente

- Criterio de cotización por defecto.
- **Tipo de comprobante del despacho para el export a Marangatu.** Los despachos entran al reporte en pantalla y al CSV, pero a propósito **no** se agregaron al archivo que se sube a la DNIT: poner un código equivocado ahí es peor que no tenerlo.

### Deuda conocida

- **El embarque ya liberado de DOBA no se arregla solo.** `IMP-2026-000001` está en `LIBERADO` y solo se puede liberar desde `DESPACHADO`, así que esos Gs. 507.271.894 no van a entrar al stock retroactivamente. Necesita una regularización aparte, y antes hay que mapear su ítem a un producto.
- **DOBA cargó el FOB en PYG con cotización 1** en vez de USD, así que de ese embarque no queda rastro del monto original ni del tipo de cambio.
