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

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

Cubre el circuito aprobado en `Importar-mercaderia-del-exterior.pdf`: la factura del proveedor del exterior se carga como compra vinculada a un embarque, alimenta el costo del embarque, la mercadería entra al stock al liberar y el IVA del despacho llega al Libro IVA.

---

## Cómo correr las pruebas

```bash
cd backend
npx jest src/importaciones/__tests__/
```

Son **dos suites, 15 casos**:

- `importacion-mercaderia-exterior.integration.spec.ts` — 11 casos sobre las piezas por separado.
- `importacion-mercaderia-exterior.e2e.spec.ts` — 4 casos que recorren el circuito **por la puerta de entrada real**, `ComprasService.create`, el mismo método que llama el controller cuando el usuario guarda la factura.

**Corre contra la base local de desarrollo, no contra mocks.** Lo que se quiere verificar son invariantes que dependen de consultas reales —que el stock se mueva una sola vez, que la factura del exterior no entre al Libro IVA, que el costo no se duplique—. Con Prisma mockeado ninguna de esas afirmaciones probaría nada: se estaría verificando el mock.

La suite es **hermética y repetible**:

- Elige la empresa anfitriona por consulta (la que más productos activos tenga, con depósito y proveedor), no por UUID fijo, así sobrevive a un reseed.
- Crea sus propios embarques, ítems, despachos y compras, marcados con un prefijo `QA<base36>`.
- Los borra en el `afterAll`, en orden inverso a las FK, incluso si un test falló antes.
- Antes de empezar **purga restos de corridas anteriores** que hayan muerto antes de limpiar.
- Si la base local no tiene los prerrequisitos, se saltea con un aviso en vez de fallar: un entorno sin datos no es un defecto del código.

Verificado: dos corridas seguidas pasan, y al terminar quedan cero embarques de prueba, cero compras de prueba y la configuración de la empresa anfitriona restaurada a su valor original.

---

## Qué cubre — 15 casos, todos en verde

### Fase 1 · La mercadería entra al stock al liberar

| # | Caso | Qué prueba |
|---|---|---|
| 1 | Liberar crea stock y movimiento por ítem | Un movimiento por ítem, **ni más ni menos** — el riesgo es duplicar. Valida cantidad, depósito destino, que el costo del kardex sea el **prorrateado** y no el FOB pelado, que el embarque quede `LIBERADO` y los ítems `DISPONIBLE` con su `producto_id` resuelto. |
| 2 | Rechaza ítems sin producto del catálogo | Lo importante no es el mensaje de error: es que **no escriba nada**. El segundo ítem del embarque es válido y no debe entrar solo. Se verifica que queden 0 movimientos y el embarque siga en `DESPACHADO`. |
| 3 | Rechaza cantidad en cero | Mismo criterio: valida antes de escribir. |

Los casos 2 y 3 existen porque **liberar es irreversible desde la pantalla**: un embarque a medias dejaría stock inconsistente sin forma de rehacerlo.

### Fase 3 · Cotización configurable

| # | Caso | Qué prueba |
|---|---|---|
| 4 | Criterio `FACTURA` | Usa la cotización de la factura. |
| 5 | Criterio `CIERRE_EMBARQUE` | Usa la del embarque **y cae a la de la factura si todavía no está cargada**. El fallback importa: sin él, cargar la factura antes del despacho costearía a cotización 1. |

### Fase 2 y 3 · Qué entra y qué no entra al Libro IVA

| # | Caso | Qué prueba |
|---|---|---|
| 6 | El despacho aporta su IVA importación | Aparece en el resumen y en el detalle, la base se deriva del IVA al 10%, y **suma al crédito fiscal del período**, que es el objetivo de la fase. Además: en el listado de «solo electrónicos» no aparece, porque no es un documento que la DNIT ya tenga por SIFEN. |
| 7 | La compra vinculada a un embarque queda afuera | Con **control negativo**: se le quita el `embarque_id` a la misma compra y se verifica que entonces sí aparece. Sin ese control, el test pasaría igual si el filtro estuviera roto y el libro no trajera nada. |

### Fase 3 · El componente de costo que nace de la factura

| # | Caso | Qué prueba |
|---|---|---|
| 8 | Crea el FOB y lo ata a la compra | Concepto FOB, importe, cotización, `importe_gs` calculado, y `es_directo = true` (la mercadería es costo directo del ítem, no se prorratea como el flete). |
| 9 | Convertir un costo cargado a mano lo **reusa** | Invariante: después de convertir sigue habiendo **un solo** componente, con el importe de la factura. Duplicarlo inflaría el costeo del embarque. |
| 10 | No deja pisar un costo que ya tiene comprobante | |
| 11 | No deja imputar a un embarque `CERRADO` | |

---

### E2E · El circuito por la puerta de entrada real

Esta suite existe para cerrar los tres huecos que la anterior dejaba declarados.

| # | Caso | Qué prueba |
|---|---|---|
| 12 | La factura entra por `create`, genera el FOB y **no descarga stock** | El **guard de doble stock**, que es el invariante más caro de equivocar. Valida además que la compra quede vinculada al embarque y que el componente nazca con el importe y el criterio correctos. |
| 13 | **Control negativo**: la misma compra sin embarque **sí** descarga stock | Sin esto el caso 12 no prueba nada: el stock podría no moverse por config, por flujo o porque el ítem no afecta stock. |
| 14 | Factura → despacho → liberación | El stock entra **una sola vez** por las 20 unidades y valorizado al costo **completo** (120.000, no los 100.000 del FOB pelado). |
| 15 | El cierre del despacho deja afuera lo que ya facturó la compra | El **guard de doble contabilización**: entra el arancel, no entran los 2.000.000 de la factura. Sin el filtro por `compra_id` el asiento sería de 2.500.000. |

El caso 13 obligó a un ajuste que vale anotar: la empresa anfitriona ingresa stock por recepción contra OC, así que con su configuración **ni la compra con embarque ni la compra sin embarque movían stock** — el control negativo daba igual que el positivo y el test no probaba nada. La suite ahora fuerza `afectar_stock_en_compra_directa` y la restaura al terminar.

---

## Lo que las pruebas NO cubren

### 1. ~~El asiento contable del cierre~~ — cubierto

Lo cubre `importacion-contable-fase2.e2e.spec.ts`, que instancia los servicios reales de contabilidad (mapeo, períodos, asientos) y verifica a qué cuenta va cada línea: los dos tipos de anticipo, el asiento del despacho con la cuenta puente, el control negativo sin puente, el cierre de la importación y su idempotencia. Ahí apareció el CHECK de `cont_documentos.tipo`, que no admitía el tipo nuevo.

### 2. La interfaz

Nadie apretó un botón. Las suites entran por los servicios; que la pantalla de Compras mande el `embarque_id` correcto, que el selector liste los embarques imputables y que el deep-link de regularización precargue bien está verificado por lectura de código y lint, no por uso.

### 3. Nada de esto se probó contra producción

Solo base local. La migración `20260924_compra_importacion` está aplicada **únicamente en local**.

---

## Verificación manual pendiente

Los invariantes ya están cubiertos; lo que falta validar es la **experiencia en pantalla**. Este recorrido conviene hacerlo con un usuario antes de acercar el circuito a producción:

1. Crear un embarque `CONSUMO_MASIVO` y cargarle ítems **mapeados a productos del catálogo** (campo «SKU interno» del diálogo de ítems).
2. En **Compras**, cargar la factura del proveedor del exterior eligiendo el embarque en «Factura de importación».
   - ✅ Verificar que aparezca el componente FOB en el embarque, con el importe de la factura.
   - ✅ Verificar que **no** se haya generado movimiento de inventario por esa compra.
   - ✅ Verificar que la factura **no** aparezca en el Libro IVA de compras del período.
   - ✅ Si la compra es a crédito, verificar que se haya generado la cuenta por pagar.
3. Cargar el despacho con su IVA importación y confirmarlo.
   - ✅ Verificar que el IVA aparezca en el Libro IVA del mes del **despacho** (no del de la factura).
   - ✅ Verificar que el asiento de cierre **no** incluya el importe del FOB (ya lo contabilizó la compra).
4. Liberar el embarque eligiendo depósito.
   - ✅ Verificar que el stock suba **una sola vez** y con el costo prorrateado completo.
5. Regularización: en un embarque con FOB cargado a mano, usar el botón de convertir del detalle.
   - ✅ Verificar que lleve a **Compras** (no a Gastos) y que al guardar reuse el componente en vez de crear otro.

---

## Defectos encontrados

### 🔴 El costo de la mercadería no llegaba al costeo — encontrado por el E2E

El componente FOB se creaba con `es_directo: true` y **sin `item_id`**. El costeo reparte los directos con `es_directo && item_id === item.id`, así que ese componente **no coincidía con ningún ítem y su costo se perdía en silencio**: el embarque quedaba valorizado sin la mercadería, que es justamente el costo más grande de una importación.

No daba error ni advertencia. Sólo se ve comparando el total esperado contra el costeado, que es lo que hace el caso 14.

**Corregido:** el componente pasa a ser compartido con criterio `FOB`, así la factura se reparte entre los ítems en proporción a su valor en origen. Si los ítems no tienen FOB cargado, el costeo corta con un mensaje explícito — preferible a repartir con un criterio inventado.

Es exactamente el tipo de defecto que el QA por piezas **no podía encontrar**: cada pieza hacía lo suyo bien, y el costo se perdía en la junta.

### Fallas de la propia suite

Las primeras corridas fallaron por errores de los tests, no del producto:

- `imp_embarques.numero` es `VarChar(20)` y la etiqueta de los datos de prueba no entraba. Se acortó.
- La limpieza referenciaba `factura_compra_subtotales`, que no existe: el modelo correcto es `compra_subtotales`. El `.catch()` que tenía alrededor no servía de nada, porque acceder a un modelo inexistente del cliente Prisma revienta antes de devolver la promesa.

La segunda falla dejó datos sin borrar, y eso hizo caer los dos casos del Libro IVA en la corrida siguiente — no por el código, sino por basura acumulada. De ahí salió la purga defensiva del `beforeAll`: **una suite que cuenta filas de un período compartido tiene que barrer sus propios restos, o falla de forma engañosa.**

---

## Decisiones del cliente que siguen abiertas

- **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.
- **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.
