Plan de Pruebas — Módulo de Importaciones (Fase 1 + Fase 2)

---

Conceptos clave antes de empezar

┌──────────────────────┬───────────────────────────────────────────────────────────────────────────────────────┐
│ Término              │ Qué es en la práctica                                                                 │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Embarque             │ Un expediente de importación. Cabecera con proveedor, BL, fechas, modalidad.          │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Ítem                 │ Cada vehículo (perfil AUTOS) o cada SKU (perfil CONSUMO_MASIVO) dentro del embarque.  │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Componente de costo  │ Cada concepto que suma al costo total: FOB, flete, seguro, despachante, tributos…    │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Despacho aduanero    │ Liquidación impositiva del embarque (arancel, IVA importación, ISC, INC, IRE, ANA).  │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Costeo               │ Cálculo del costo unitario por ítem prorrateando los componentes según criterio.      │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Doble llave          │ Para operar Importaciones se necesita: módulo en suscripción + switch operativo ON.  │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Perfil               │ AUTOS o CONSUMO_MASIVO, configurable por empresa. Define qué campos son obligatorios. │
├──────────────────────┼───────────────────────────────────────────────────────────────────────────────────────┤
│ Bitácora             │ Feed cronológico de eventos del embarque (creado, editado, estado, ítems, costos…).   │
└──────────────────────┴───────────────────────────────────────────────────────────────────────────────────────┘

---

Estados del embarque (máquina)

BORRADOR → EN_TRANSITO → ARRIBADO → EN_DESPACHO → DESPACHADO → LIBERADO → CERRADO → SELLADO

- BORRADOR ↔ EN_TRANSITO ↔ ARRIBADO ↔ EN_DESPACHO  (reversibles)
- DESPACHADO requiere despacho confirmado (no se llega manual)
- LIBERADO requiere selección de depósito (no se llega manual)
- CERRADO y SELLADO bloquean modificaciones

---

Flujo obligatorio antes de cualquier prueba operativa

1. Activar el módulo IMPORTACIONES en la suscripción de la empresa
2. Encender el switch operativo en Configuración → Importaciones → Configuración del módulo
3. Asignar privilegios IMP_VER, IMP_CREAR_EMBARQUE, IMP_EDITAR_EMBARQUE, IMP_GESTIONAR_COSTOS,
   IMP_CERRAR_DESPACHO, IMP_CONFIG al perfil que vayas a usar
4. (Opcional pero recomendado) Cargar tasas ISC vehículos para que se sugieran al despachar
5. Asegurar que existan: depósito activo, proveedor extranjero activo, condiciones de pago,
   moneda USD habilitada con cotización en Configuración → Monedas

⚠️ Sin la doble llave activada, todos los endpoints de Importaciones devuelven 403.

---

PRUEBA 1 — Activación del módulo (doble llave)

Dónde: Configuración → Importaciones → Configuración del módulo

Pasos:

1. Iniciar sesión con un usuario que tenga IMP_CONFIG
2. Navegar a Configuración → Importaciones
3. Verificar el estado actual:
   - Si aparece warning "El módulo IMPORTACIONES no está activo en la suscripción de la empresa"
     → falta agregar IMPORTACIONES en suscripcion_modulos (tarea de admin/superAdmin)
   - Si no aparece warning → la suscripción ya tiene el módulo
4. Activar el switch "Módulo operativo activo"
5. Seleccionar perfil activo: AUTOS o CONSUMO_MASIVO
6. Guardar

Resultado esperado:

- Toast "Configuración actualizada"
- El switch queda en ON
- En el menú principal aparece el ítem Importaciones

Resultado negativo (si falla):

- Toast rojo: "El módulo IMPORTACIONES no está disponible en su plan actual"
  → falta INSERT en suscripcion_modulos
- Si el usuario no tiene IMP_CONFIG → la pestaña no es visible o muestra "Acceso restringido"

Verificar en BD:
SELECT modulo_operativo_activo, perfil_activo FROM imp_config_empresa WHERE empresa_id = 'tu-empresa';
SELECT m.codigo, sm.activo FROM suscripcion_modulos sm
JOIN modulos m ON m.id = sm.modulo_id
JOIN suscripciones s ON s.id = sm.suscripcion_id
WHERE s.empresa_id = 'tu-empresa' AND m.codigo = 'IMPORTACIONES';

---

PRUEBA 2 — Cargar Tasas ISC vehículos

Dónde: Importaciones → tab Tasas ISC vehículos

Prerrequisito: privilegio IMP_CONFIG.

Pasos:

1. Clic en "Nueva tasa"
2. Completar:

┌──────────────────┬──────────┐
│ Año desde        │ 2020     │
├──────────────────┼──────────┤
│ Año hasta        │ 2030     │
├──────────────────┼──────────┤
│ cc desde         │ 1500     │
├──────────────────┼──────────┤
│ cc hasta         │ 2000     │
├──────────────────┼──────────┤
│ Tasa ISC %       │ 5.00     │
├──────────────────┼──────────┤
│ Vigente desde    │ Hoy      │
├──────────────────┼──────────┤
│ Vigente hasta    │ (vacío)  │
└──────────────────┴──────────┘

3. Guardar

Resultado esperado:

- Toast "Tasa ISC creada"
- Aparece la fila en la tabla con chip "Empresa" en columna Origen
- Botones de editar/eliminar disponibles sobre la fila

Resultado negativo (validaciones):

- anio_hasta < anio_desde → error: "anio_hasta debe ser ≥ anio_desde"
- cc_hasta < cc_desde → error: "cc_hasta debe ser ≥ cc_desde"
- tasa_isc_pct vacío o negativo → error: "Tasa ISC inválida"

Verificar en BD:
SELECT anio_desde, anio_hasta, cc_desde, cc_hasta, tasa_isc_pct, vigente_desde, activo
FROM imp_isc_vehiculos WHERE empresa_id = 'tu-empresa';

---

PRUEBA 3 — Crear embarque AUTOS

Dónde: Importaciones → tab Embarques → "+ Nuevo embarque"

Prerrequisito: perfil_activo = AUTOS, privilegio IMP_CREAR_EMBARQUE.

Pasos:

1. Clic en "Nuevo embarque"
2. Completar:
   - Proveedor (extranjero): ej. MOVIMIENTO CAFE SOCIEDAD ANONIMA
   - Moneda: USD
   - País origen: KR
   - Puerto origen: Busan
   - Puerto destino: Villeta
   - Modalidad: RoRo
   - Fecha embarque, ETA: cualquier fecha futura
3. Guardar

Resultado esperado:

- Toast "Embarque guardado"
- Se asigna número correlativo automático: IMP-AAAA-NNNNNN (ej. IMP-2026-000001)
- El embarque aparece en la lista con estado BORRADOR
- Tabs de detalle Información / Ítems / Costos / Despacho / Bitácora se habilitan

Verificar en BD:
SELECT numero, perfil, estado, proveedor_id, origen_pais
FROM imp_embarques WHERE empresa_id = 'tu-empresa' ORDER BY created_at DESC LIMIT 1;

---

PRUEBA 4 — Agregar ítems perfil AUTOS

Dónde: detalle del embarque → tab Ítems → "Agregar ítem"

Pasos:

1. Clic en "Agregar ítem"
2. Completar (vehículo):
   - Chasis: KMHD841CBJU123456 (17 chars típicos VIN)
   - Marca: HYUNDAI
   - Modelo: TUCSON
   - Año: 2024
   - Cilindrada cc: 1600
   - FOB unit USD: 12000
   - Cantidad: 1
3. Guardar
4. Repetir con otro chasis distinto (ej. KMHD841CBJU654321) para tener 2 ítems

Resultado esperado:

- Cada ítem aparece en la tabla con su chasis
- FOB total del embarque actualiza al total de ítems × FOB unit
- Métricas del header se actualizan: Ítems = 2, FOB total = USD 24.000,00

Resultado negativo:

- Crear ítem con el mismo chasis dentro del mismo embarque → permitido en BD pero el constraint
  unique de chasis aplicará si está activo. Confirmar en BD.

Verificar en BD:
SELECT chasis, marca, modelo, anio, cilindrada_cc, fob_total_usd
FROM imp_embarque_items WHERE embarque_id = '<embarque-id>' AND activo = true;

---

PRUEBA 5 — Cargar componentes de costo

Dónde: detalle del embarque → tab Costos → "Agregar costo"

Escenario: cargar FOB y flete (asumimos que los ítems tienen FOB unit cargado, este componente
es el costo agregado del embarque, ej. flete marítimo).

Pasos:

1. Clic en "Agregar costo"
2. Completar para FLETE:
   - Concepto: FLETE_MARITIMO
   - Tipo: Compartido (no es directo a un ítem)
   - Criterio prorrateo: FOB
   - Importe: 2000
   - Moneda: USD
   - Cotización: 7800 (sugerida)
   - Proveedor: la naviera (opcional)
3. Guardar
4. Repetir para SEGURO_INTERNACIONAL (USD 300, criterio FOB) y DESPACHO_HONORARIOS (Gs. 1.500.000,
   criterio IGUAL, proveedor el despachante)

Resultado esperado:

- Cada componente aparece en la tabla con concepto, tipo, importe en moneda original e importe
  convertido a Gs. (importe × cotización)
- El "Costo total" del embarque (header) crece a medida que se agregan
- Los "Costos cargados" (header) cuenta las filas activas

Resultado negativo (validaciones):

- es_gasto_extra = true sin motivo → error: "Motivo obligatorio para gasto extra"
- importe = 0 → puede aceptarse, pero la fila no genera CxP
- Concepto no aplica al perfil → si imp_conceptos_costo.aplica_perfil ≠ NULL y ≠ perfil del embarque,
  el endpoint puede rechazarlo

Verificar en BD:
SELECT concepto_id, importe, cotizacion, importe_gs, es_directo, criterio_prorrateo
FROM imp_componentes_costo WHERE embarque_id = '<embarque-id>' AND activo = true;

---

PRUEBA 6 — Validación gasto extra requiere motivo

Dónde: tab Costos → "Agregar costo"

Escenario: cargar un gasto extra sin justificar.

Pasos:

1. Clic en "Agregar costo"
2. Concepto: OTROS_GASTOS, importe 500.000, marcar "es_gasto_extra"
3. Dejar "Motivo" vacío
4. Intentar guardar

Resultado esperado:

- Error: "Motivo obligatorio si es_gasto_extra=true"
- El componente NO se crea

5. Cargar motivo "Almacenamiento extendido por demora aduana"
6. Guardar

Resultado esperado:

- Toast de éxito
- En la tabla aparece el chip naranja "Gasto extra" sobre la fila

Verificar en BD:
SELECT es_gasto_extra, motivo_gasto_extra FROM imp_componentes_costo WHERE id = '<costo-id>';
-- es_gasto_extra debe ser true y motivo no nulo

---

PRUEBA 7 — Edición de cabecera registra bitácora

Dónde: detalle del embarque → botón Editar

Pasos:

1. Clic en Editar
2. Cambiar Bill of Lading: agregar "BL-CMA-CGM-9988"
3. Cambiar fecha ETA a una fecha distinta
4. Guardar
5. Ir al tab Bitácora

Resultado esperado:

- En la bitácora aparece evento EMBARQUE_EDITADO con detalle.cambios mostrando
  bill_of_lading: { anterior: null, nuevo: "BL-CMA-CGM-9988" }
  fecha_eta: { anterior: "2026-04-25", nuevo: "2026-04-30" }
- Se registra usuario_id y created_at

Verificar en BD:
SELECT evento, detalle, created_at FROM imp_embarques_bitacora
WHERE embarque_id = '<id>' ORDER BY created_at DESC LIMIT 5;

---

PRUEBA 8 — Transiciones de estado válidas

Dónde: detalle del embarque → botones "→ ESTADO" en el header del detalle

Pasos:

1. Estado actual: BORRADOR → clic "→ EN_TRANSITO"
2. Estado actual: EN_TRANSITO → clic "→ ARRIBADO"
3. Estado actual: ARRIBADO → clic "→ EN_DESPACHO"

Resultado esperado:

- Cada transición:
  - Toast "Estado actualizado: <NUEVO>"
  - El chip de estado en el header cambia de color
  - La bitácora registra ESTADO_CAMBIO con estado_anterior y estado_nuevo
- Las opciones de transición que aparecen como botones siempre son las permitidas para el estado actual

Verificar en BD:
SELECT estado, updated_at FROM imp_embarques WHERE id = '<id>';
SELECT evento, estado_anterior, estado_nuevo FROM imp_embarques_bitacora
WHERE embarque_id = '<id>' AND evento = 'ESTADO_CAMBIO' ORDER BY created_at;

---

PRUEBA 9 — Transición inválida (validación)

Pasos:

1. Embarque en estado BORRADOR
2. Intentar saltar directo a DESPACHADO vía API:
   PATCH /v1/importaciones/embarques/:id/estado { "estado": "DESPACHADO" }

Resultado esperado:

- Error 400: "Transición no permitida: BORRADOR → DESPACHADO. Próximos estados válidos: EN_TRANSITO."
- El estado del embarque NO cambia

⚠️ Nota: en el frontend los botones solo muestran transiciones válidas, así que esta prueba se hace
por API directa o cambiando manualmente el estado en BD para forzar el escenario.

---

PRUEBA 10 — Crear despacho aduanero (BORRADOR)

Dónde: tab Despacho → "Crear despacho"

Prerrequisito: embarque en EN_DESPACHO o ARRIBADO. Privilegio IMP_GESTIONAR_COSTOS.

Pasos:

1. Tab Despacho — formulario en blanco
2. Completar:
   - N° Despacho: 26-12345-IM (formato libre, hasta 100 chars)
   - Fecha de despacho: hoy
   - Despachante: seleccionar un proveedor que sea despachante (opcional)
   - Arancel: 1.500.000 Gs.
   - IVA Importación: 5.000.000 Gs.
   - ISC: 800.000 Gs. (sugerido por la tasa cargada en Prueba 2: FOB × cilindrada%)
   - INC: 0
   - Anticipo IRE: 200.000 Gs.
   - Tasa ANA: 100.000 Gs.
   - Otros: 0
3. Verificar que "Total tributos" suma 7.600.000 Gs.
4. Guardar (sin confirmar)

Resultado esperado:

- Toast "Despacho creado"
- El chip muestra estado BORRADOR
- Si el embarque estaba en ARRIBADO, automáticamente pasa a EN_DESPACHO (con bitácora ESTADO_CAMBIO)
- Bitácora registra DESPACHO_CREADO con detalle.numero_despacho y total_tributos_gs

Resultado negativo:

- Crear segundo despacho sobre el mismo embarque → error: "El embarque ya tiene un despacho cargado"
  (constraint UNIQUE imp_despachos.embarque_id)
- Estado embarque NO en (ARRIBADO, EN_DESPACHO) → error de transición

Verificar en BD:
SELECT numero_despacho, fecha_despacho, total_tributos_gs, estado, cotizacion_usada
FROM imp_despachos WHERE embarque_id = '<id>';

---

PRUEBA 11 — Editar despacho en BORRADOR

Pasos:

1. Sobre el despacho en BORRADOR, modificar el ISC a 1.000.000 Gs.
2. Guardar

Resultado esperado:

- "Total tributos" se recalcula
- Bitácora registra DESPACHO_EDITADO

Resultado negativo:

- Intentar editar un despacho CONFIRMADO → error: "Solo se puede modificar/eliminar un despacho en BORRADOR"

---

PRUEBA 12 — Confirmar despacho (operación crítica)

Dónde: tab Despacho → "Confirmar despacho"

Prerrequisito: privilegio IMP_CERRAR_DESPACHO. Despacho en BORRADOR. Embarque en EN_DESPACHO.

Pasos:

1. Clic en "Confirmar despacho"
2. Confirmar el dialog de advertencia ("Acción irreversible")

Resultado esperado:

- Toast "Despacho confirmado"
- El despacho pasa a estado CONFIRMADO (chip verde)
- El campo cotizacion_usada se popula con la tasa USD del día (si está cargada en cont_tipo_cambio)
- El embarque transiciona EN_DESPACHO → DESPACHADO automáticamente
- En tab Costos aparecen 5–7 nuevas filas (una por cada tributo > 0):
  - "Despacho 26-12345-IM" + concepto IMP_ARANCEL → 1.500.000 Gs.
  - "Despacho 26-12345-IM" + concepto IMP_IVA_IMPORTACION → 5.000.000 Gs.
  - "Despacho 26-12345-IM" + concepto IMP_ISC → 1.000.000 Gs.
  - …etc.
- El "Costo total" del embarque crece: ahora incluye FOB+flete+seguro+honorarios+tributos
- Costo unitario por ítem se recalcula con el prorrateo
- Bitácora registra DESPACHO_CONFIRMADO + ESTADO_CAMBIO (EN_DESPACHO → DESPACHADO)
- En cont_asientos aparece un asiento NUEVO en estado CONFIRMADO con:
  - DEBE: Inventario (total - IVA importación) + IVA Crédito (IVA importación)
  - HABER: Proveedores (total)
  - Glosa: "Cierre despacho importación 26-12345-IM (embarque IMP-2026-000001)"

Resultado negativo:

- Embarque NO está en EN_DESPACHO → error: "Para confirmar el despacho el embarque debe estar en
  EN_DESPACHO"
- Confirmar dos veces → segunda vez error: "Solo se puede confirmar un despacho en estado BORRADOR"
- Sin permiso IMP_CERRAR_DESPACHO → 403

Verificar en BD:

-- 1) Despacho confirmado
SELECT estado, confirmado_at, cotizacion_usada FROM imp_despachos WHERE id = '<despacho-id>';

-- 2) Componentes nuevos por tributos
SELECT cc.descripcion, cc.importe_gs, c.codigo
FROM imp_componentes_costo cc
JOIN imp_conceptos_costo c ON c.id = cc.concepto_id
WHERE cc.embarque_id = '<id>' AND c.codigo LIKE 'IMP_%';

-- 3) Asiento contable
SELECT a.estado, a.glosa, a.total_debe_pyg, a.total_haber_pyg
FROM cont_asientos a
JOIN cont_documentos d ON d.id = a.documento_id
WHERE d.origen_tipo = 'imp_despacho' AND d.origen_id = '<despacho-id>';
-- total_debe_pyg debe ser igual a total_haber_pyg

-- 4) Embarque en DESPACHADO
SELECT estado FROM imp_embarques WHERE id = '<id>';

---

PRUEBA 13 — Liberar embarque AUTOS (crear stock e inventario)

Dónde: detalle del embarque → botón "→ LIBERADO"

Prerrequisito: embarque en DESPACHADO. Privilegio IMP_CERRAR_DESPACHO. Al menos un depósito activo.

Pasos:

1. Clic en "→ LIBERADO" → se abre modal "Liberar embarque"
2. Verificar el alert informativo: "Se crea 1 producto por chasis (N en este embarque) e ingresa
   al depósito seleccionado"
3. Seleccionar Depósito destino del select
4. Clic en "Liberar"

Resultado esperado:

- Toast con resumen: "Embarque liberado: 2 producto(s) creado(s), 2 ítem(s) actualizado(s)"
- El embarque pasa a LIBERADO (chip verde)
- En la base de datos:
  - Por cada ítem con chasis: 1 fila nueva en productos con cod_producto = chasis,
    descripcion = "MARCA MODELO AÑO CHASIS", precio_costo = costo_unitario_gs del ítem
  - 1 fila por (deposito, producto) en stock_deposito con cantidad_disponible = 1
  - 1 fila por producto en movimientos_inventario con tipo_movimiento ENTRADA,
    documento_origen = 'IMP', documento_id = embarque_id
  - imp_embarque_items.producto_id se popula y estado pasa a DISPONIBLE
- Bitácora registra EMBARQUE_LIBERADO + ESTADO_CAMBIO (DESPACHADO → LIBERADO)

Resultado negativo:

- Embarque no en DESPACHADO → error: "Solo se puede liberar un embarque en DESPACHADO"
- Depósito de otra empresa → error: "Depósito destino inválido para la empresa"
- Sin permiso IMP_CERRAR_DESPACHO → 403
- Si un chasis ya existe como cod_producto en productos para esa empresa, se reusa esa fila
  en lugar de crear duplicado

Verificar en BD:

-- 1) Productos creados
SELECT cod_producto, descripcion, precio_costo, maneja_inventario
FROM productos WHERE empresa_id = 'tu-empresa' AND cod_producto IN ('CHASIS1','CHASIS2');

-- 2) Stock con cantidad 1
SELECT p.cod_producto, sd.cantidad_disponible, d.descripcion AS deposito
FROM stock_deposito sd
JOIN productos p ON p.id = sd.producto_id
JOIN depositos d ON d.id = sd.deposito_id
WHERE p.empresa_id = 'tu-empresa' AND p.cod_producto IN ('CHASIS1','CHASIS2');

-- 3) Movimientos de inventario
SELECT mi.cantidad, mi.documento_origen, mi.documento_id, p.cod_producto
FROM movimientos_inventario mi
JOIN productos p ON p.id = mi.producto_id
WHERE mi.documento_id = '<embarque-id>' AND mi.documento_origen = 'IMP';

-- 4) Ítems vinculados
SELECT chasis, producto_id, estado FROM imp_embarque_items
WHERE embarque_id = '<id>' AND activo = true;

---

PRUEBA 14 — Liberar embarque CONSUMO_MASIVO (sin crear productos)

Pasos:

1. Repetir flujo desde Prueba 3, pero con perfil_activo = CONSUMO_MASIVO y cargar ítems con
   sku_proveedor en lugar de chasis. Avanzar hasta DESPACHADO.
2. Liberar al depósito X

Resultado esperado:

- Los ítems pasan a estado DISPONIBLE
- NO se crean productos (perfil CONSUMO_MASIVO en v1 no auto-crea)
- NO se crean movimientos de inventario
- Bitácora EMBARQUE_LIBERADO con detalle.productos_creados = 0

Verificar en BD:
SELECT estado FROM imp_embarque_items WHERE embarque_id = '<id>' AND activo = true;
-- Todos en DISPONIBLE

---

PRUEBA 15 — Generar Cuenta por Pagar desde componente de costo

Dónde: tab Costos → en cualquier fila con proveedor cargado, ícono 💳 "Generar CxP"

Prerrequisito: privilegio IMP_GESTIONAR_COSTOS. Componente con importe_gs > 0 y proveedor.

Pasos:

1. En la fila del componente FLETE_MARITIMO, clic en 💳
2. Se abre dialog "Generar Cuenta por Pagar"
3. Verificar que muestra "Componente: Flete marítimo · Gs. X.XXX.XXX"
4. Completar:
   - Proveedor: viene precargado del componente, o seleccionar uno
   - Fecha emisión: hoy
   - N° factura: ej. "001-001-0000123"
   - Plazo días: 30
   - Verificar que muestra "Vence: <hoy + 30 días>"
5. Clic en "Generar CxP"

Resultado esperado:

- Toast "CxP generada (1 cuota/s)"
- En la fila del componente, el ícono 💳 desaparece y aparece chip verde "CxP"
- En cuentas_pagar nueva fila con embarque_id, proveedor_id, monto = importe_gs del componente,
  origen_tipo = 'importacion', origen_id = id del componente
- imp_componentes_costo.cuenta_pagar_id apunta a la fila de cuentas_pagar creada
- Bitácora registra CXP_GENERADA con detalle.componente_id, proveedor_id, total_gs, cuotas

Resultado negativo:

- Sin importe_gs (componente con monto 0) → error: "El componente no tiene importe en Gs."
- Componente sin proveedor (en componente y dto) → error: "Debe especificar proveedor"
- Componente ya con cuenta_pagar_id → error: "El componente ya tiene una CxP asociada"
- Proveedor no pertenece a la empresa → error 400

Verificar en BD:
SELECT cp.numero_cuota, cp.total_cuotas, cp.monto_original, cp.fecha_vencimiento,
       cp.embarque_id, cp.origen_tipo, cp.origen_id, cp.estado
FROM cuentas_pagar cp WHERE embarque_id = '<embarque-id>' AND origen_tipo = 'importacion';

SELECT cuenta_pagar_id FROM imp_componentes_costo WHERE id = '<costo-id>';
-- Debe estar populado

---

PRUEBA 16 — Bitácora completa del embarque

Dónde: tab Bitácora

Pasos:

1. Después de haber recorrido las pruebas 3 a 15 sobre un mismo embarque
2. Abrir tab Bitácora
3. Refrescar con el botón

Resultado esperado:

- Listado cronológico (desc) con eventos:
  - EMBARQUE_CREADO
  - ITEM_AGREGADO (×2)
  - COSTO_AGREGADO (×3)
  - EMBARQUE_EDITADO (con cambios JSON)
  - ESTADO_CAMBIO (BORRADOR → EN_TRANSITO)
  - ESTADO_CAMBIO (EN_TRANSITO → ARRIBADO)
  - ESTADO_CAMBIO (ARRIBADO → EN_DESPACHO)
  - DESPACHO_CREADO
  - DESPACHO_EDITADO
  - DESPACHO_CONFIRMADO
  - ESTADO_CAMBIO (EN_DESPACHO → DESPACHADO)
  - EMBARQUE_LIBERADO
  - ESTADO_CAMBIO (DESPACHADO → LIBERADO)
  - CXP_GENERADA
- Cada evento muestra chip de color por tipo, fecha-hora, y JSON expandido con detalle

---

PRUEBA 17 — Cierre y sellado del expediente

Dónde: detalle del embarque

Pasos:

1. Embarque en LIBERADO → clic "→ CERRADO"
2. Verificar bitácora: ESTADO_CAMBIO (LIBERADO → CERRADO)
3. Embarque en CERRADO → clic "→ SELLADO"
4. Verificar bitácora: ESTADO_CAMBIO (CERRADO → SELLADO)

Resultado esperado:

- Las transiciones se ejecutan
- En estado SELLADO el botón "Eliminar" desaparece
- Botón "Editar" sigue visible pero el backend rechaza modificaciones (ESTADOS_EDITABLES no incluye SELLADO)

Resultado negativo:

- Intentar eliminar embarque SELLADO → error 400: "No se puede eliminar un embarque sellado"
- Intentar agregar/editar ítem o costo en CERRADO/SELLADO → error: "El embarque en estado X no admite cambios"

---

PRUEBA 18 — Aislamiento multi-tenant

Escenario: Verificar que un embarque de la Empresa A NO es visible/editable desde la Empresa B.

Pasos:

1. Iniciar sesión como usuario de Empresa A → crear embarque, anotar el id
2. Cerrar sesión, iniciar como usuario de Empresa B (otra empresa)
3. Intentar:
   GET /v1/importaciones/embarques/<id-de-A>
   PATCH /v1/importaciones/embarques/<id-de-A> { … }
   POST /v1/importaciones/embarques/<id-de-A>/items { … }

Resultado esperado:

- Todas las llamadas devuelven 404 "Embarque no encontrado"
  (todos los queries del service filtran por empresa_id)
- El listado GET /v1/importaciones/embarques NO incluye embarques de A
- En la UI, el embarque de A no aparece en el listado de B

---

PRUEBA 19 — Permisos (control de acceso)

Escenario: Validar que cada privilegio bloquea/habilita correctamente.

Setup: crear 4 perfiles con privilegios distintos en IMPORTACIONES.

19A — Solo IMP_VER

- Listado y detalle visibles
- NO puede crear embarques (botón "Nuevo embarque" oculto, POST → 403)
- NO puede crear despacho (botón oculto, POST → 403)
- NO puede confirmar despacho (botón oculto, POST → 403)
- NO puede liberar (botón "→ LIBERADO" no aparece, POST → 403)
- NO puede generar CxP (ícono 💳 oculto, POST → 403)
- NO puede ver/editar tasas ISC (tab oculto si solo tiene IMP_VER y no IMP_CONFIG)

19B — IMP_CREAR_EMBARQUE + IMP_VER

- Puede crear embarques nuevos
- NO puede editar cabecera de embarques existentes (necesita IMP_EDITAR_EMBARQUE)

19C — IMP_GESTIONAR_COSTOS

- Puede CRUD componentes de costo
- Puede crear/editar/eliminar despacho
- NO puede CONFIRMAR despacho (necesita IMP_CERRAR_DESPACHO)
- NO puede LIBERAR (necesita IMP_CERRAR_DESPACHO)

19D — IMP_CERRAR_DESPACHO

- Puede confirmar despacho
- Puede liberar embarque
- Si no tiene IMP_GESTIONAR_COSTOS, NO puede crear/editar el despacho previo

19E — Llamadas API directas

Sin el token correcto o sin el privilegio:
- POST /v1/importaciones/embarques/.../despachos/.../confirmar → 403 sin IMP_CERRAR_DESPACHO
- POST /v1/importaciones/isc-vehiculos → 403 sin IMP_CONFIG

---

PRUEBA 20 — Eliminación de embarque

Pasos:

1. Sobre un embarque en BORRADOR (sin costos confirmados ni despacho), clic "Eliminar"
2. Confirmar dialog

Resultado esperado:

- Toast "Embarque eliminado"
- El embarque ya no aparece en el listado (soft delete: activo = false)
- Bitácora registra EMBARQUE_ELIMINADO con estado_anterior

Resultado negativo:

- Embarque SELLADO → error: "No se puede eliminar un embarque sellado"

Verificar en BD:
SELECT activo FROM imp_embarques WHERE id = '<id>';
-- activo = false (soft delete)

---

PRUEBA 21 — Bloqueo cambio de perfil con embarques activos

Dónde: Configuración → Importaciones → Configuración del módulo

Escenario: Empresa con perfil_activo = AUTOS y embarques activos en perfil AUTOS no en estado terminal.

Pasos:

1. Tener al menos 1 embarque en estado distinto de CERRADO/SELLADO
2. En la configuración, intentar cambiar perfil_activo a CONSUMO_MASIVO
3. Guardar

Resultado esperado:

- Error 400: "No se puede cambiar de perfil mientras existan embarques activos en perfil AUTOS"
- El perfil queda en AUTOS

4. Cerrar/sellar todos los embarques AUTOS activos → reintentar el cambio
5. Guardar

Resultado esperado:

- Toast de éxito
- Los nuevos embarques exigen perfil CONSUMO_MASIVO

---

PRUEBA 22 — Liquidación de costos: cuadre numérico

Escenario de auditoría: validar que el costo total del embarque cuadra con la suma de componentes
y que el costo unitario por ítem se calcula correctamente.

Setup: embarque AUTOS con 2 ítems iguales (mismo FOB unit USD 10.000), componentes:
- FOB: USD 20.000 (directo, criterio FOB)  → 20.000 × 7800 = 156.000.000 Gs.
- FLETE: USD 2.000 (compartido, criterio FOB) → 2.000 × 7800 = 15.600.000 Gs.
- DESPACHO HONORARIOS: 1.500.000 Gs. (compartido, criterio IGUAL)
- Tributos del despacho: 7.600.000 Gs. (compartido, criterio FOB) — los inserta confirmarDespacho

Pasos:

1. Después de confirmar el despacho, abrir tab Ítems
2. Verificar costo_unitario_gs por ítem

Resultado esperado:

- Costo total del embarque = 156.000.000 + 15.600.000 + 1.500.000 + 7.600.000 = 180.700.000 Gs.
- Cada ítem (mismo FOB, criterio FOB para componentes compartidos):
  - FOB directo: 78.000.000 Gs. (10.000 × 7800)
  - Flete prorrateado por FOB: 7.800.000 Gs. (50% del flete)
  - Honorarios prorrateados IGUAL: 750.000 Gs. (1.500.000 / 2)
  - Tributos prorrateados por FOB: 3.800.000 Gs.
  - Costo unitario total: ~90.350.000 Gs.

Verificar en BD:
SELECT
  (SELECT SUM(importe_gs) FROM imp_componentes_costo WHERE embarque_id='<id>' AND activo=true) AS total_componentes,
  costo_total_gs FROM imp_embarques WHERE id='<id>';
-- Los dos valores deben coincidir (con tolerancia de redondeo)

SELECT chasis, fob_total_usd, costo_unitario_gs, costo_total_gs
FROM imp_embarque_items WHERE embarque_id='<id>' AND activo=true;
-- Cada costo_unitario_gs debería estar cerca de 90.350.000

---

PRUEBA 23 — Aislamiento de tasas ISC entre empresas

Pasos:

1. Cargar una tasa ISC en Empresa A
2. Cambiar de empresa a B (con sesión separada)
3. Ir a Importaciones → Tasas ISC vehículos

Resultado esperado:

- La tasa de A NO aparece en B
- (Si en el futuro se siembran tasas sistema con empresa_id NULL, A y B verán esas además de las propias)

---

PRUEBA 24 — Cotización del despacho

Escenario: confirmar despacho cuando NO hay tasa USD en cont_tipo_cambio.

Pasos:

1. Borrar/no cargar la tasa USD del día en Contabilidad → Tipo de Cambio
2. Confirmar un despacho

Resultado esperado:

- El despacho se confirma exitosamente
- imp_despachos.cotizacion_usada queda en NULL
- El asiento contable se genera igual usando importe_gs ya convertido en cada componente
- En la UI del despacho confirmado, no se muestra "Cotización congelada"

Cargar la tasa y confirmar otro despacho:

- cotizacion_usada se popula con el valor vigente para esa fecha

---

Checklist de "todo funciona"

ACTIVACIÓN Y CONFIGURACIÓN

[ ] Doble llave: módulo en suscripción + switch operativo, ambos activos
[ ] Perfil activo seleccionable AUTOS / CONSUMO_MASIVO
[ ] Tasas ISC: CRUD completo, validaciones de rangos
[ ] Tasas ISC sistema (si se siembran) son sólo lectura

EMBARQUES — CRUD

[ ] Crear embarque genera número correlativo IMP-AAAA-NNNNNN
[ ] Listado paginado con filtros estado y búsqueda
[ ] Detalle muestra cabecera + tabs Información/Ítems/Costos/Despacho/Bitácora
[ ] Edición de cabecera registra evento EMBARQUE_EDITADO con diff
[ ] Eliminación es soft delete (activo = false)
[ ] No se puede eliminar embarque SELLADO

ÍTEMS

[ ] Agregar ítem AUTOS con chasis, marca, modelo, año, cilindrada
[ ] Agregar ítem CONSUMO_MASIVO con sku_proveedor, descripcion_origen
[ ] Editar ítem registra evento ITEM_EDITADO con cambios
[ ] Eliminar ítem (soft delete) marca componentes asociados también como inactivos
[ ] FOB total del embarque se actualiza en vivo

COSTOS

[ ] Componentes USD se guardan con cotización propia (importe_gs = importe × cotizacion)
[ ] Gasto extra requiere motivo (validación bloquea si falta)
[ ] Componente directo a un ítem afecta solo al costo de ese ítem
[ ] Componente compartido se prorratea según criterio (FOB / PESO / CBM / CANTIDAD / IGUAL)
[ ] Costo total y costo unitario se recalculan automáticamente

ESTADOS

[ ] Transiciones válidas según máquina de estados
[ ] Transiciones inválidas devuelven 400 con próximos estados disponibles
[ ] Cada cambio de estado se registra en la bitácora

DESPACHO

[ ] Crear despacho con estado embarque ARRIBADO o EN_DESPACHO
[ ] Si embarque estaba en ARRIBADO, pasa a EN_DESPACHO automáticamente al crear despacho
[ ] Editar despacho solo en BORRADOR
[ ] Constraint UNIQUE: un solo despacho por embarque
[ ] Total tributos = arancel + IVA imp + ISC + INC + IRE + ANA + otros (todos en Gs.)
[ ] Confirmar despacho:
  [ ] Pasa el embarque a DESPACHADO
  [ ] Inserta 1 fila en imp_componentes_costo por cada tributo > 0 (concepto IMP_*)
  [ ] Recalcula costo del embarque y costo_unitario_gs por ítem
  [ ] Congela cotización USD del día (si está disponible)
  [ ] Genera 1 asiento en cont_asientos: DEBE Inventario+IVA Crédito / HABER Proveedores
  [ ] Asiento total_debe_pyg = total_haber_pyg
  [ ] Bitácora registra DESPACHO_CONFIRMADO + ESTADO_CAMBIO

LIBERACIÓN (perfil AUTOS)

[ ] Modal de liberación obliga a elegir depósito activo de la empresa
[ ] Por cada ítem con chasis: crea 1 producto, 1 stock_deposito, 1 movimiento_inventario
[ ] Si el chasis ya existe como cod_producto, reusa el producto existente (no duplica)
[ ] productos.precio_costo = costo_unitario_gs del ítem
[ ] productos.cod_producto = chasis
[ ] stock_deposito.cantidad_disponible = cantidad del ítem (típicamente 1 para AUTOS)
[ ] movimientos_inventario.documento_origen = 'IMP', documento_id = embarque_id
[ ] imp_embarque_items.producto_id se popula y estado pasa a DISPONIBLE
[ ] Bitácora registra EMBARQUE_LIBERADO con resumen + ESTADO_CAMBIO

LIBERACIÓN (perfil CONSUMO_MASIVO)

[ ] Items pasan a estado DISPONIBLE
[ ] NO se crean productos ni stock ni movimientos (en v1)

CUENTAS POR PAGAR

[ ] Botón 💳 visible solo en componentes con proveedor y sin cuenta_pagar_id
[ ] Componente ya con CxP muestra chip verde "CxP"
[ ] Generar CxP crea fila en cuentas_pagar con embarque_id y origen_tipo = 'importacion'
[ ] imp_componentes_costo.cuenta_pagar_id apunta a la primera cuota
[ ] No se puede regenerar CxP sobre componente que ya tiene cuenta_pagar_id
[ ] Componente sin proveedor (en componente ni en dto) bloquea generación

BITÁCORA

[ ] Listado cronológico (más recientes primero) con tope 200
[ ] Eventos cubiertos: EMBARQUE_CREADO/EDITADO/ELIMINADO, ESTADO_CAMBIO,
    ITEM_AGREGADO/EDITADO/ELIMINADO, COSTO_AGREGADO/EDITADO/ELIMINADO,
    DESPACHO_CREADO/EDITADO/CONFIRMADO/ELIMINADO, EMBARQUE_LIBERADO, CXP_GENERADA
[ ] Cada evento incluye usuario_id, created_at, detalle JSON
[ ] Tab refresca automáticamente tras mutaciones (al cambiar selected.updated_at)
[ ] Botón "Refrescar" funciona

PERMISOS

[ ] IMP_VER suficiente para listar/leer
[ ] IMP_CREAR_EMBARQUE habilita POST embarques
[ ] IMP_EDITAR_EMBARQUE habilita PATCH/DELETE embarques, ítems y transiciones reversibles
[ ] IMP_GESTIONAR_COSTOS habilita CRUD componentes, despacho y generar CxP
[ ] IMP_CERRAR_DESPACHO habilita confirmar despacho y liberar
[ ] IMP_CONFIG habilita CRUD tasas ISC y configuración del módulo
[ ] superAdmin bypassa todos los privilegios (no la doble llave: igual necesita módulo activo)

MULTI-TENANT

[ ] Embarques aislados por empresa_id en queries
[ ] Tasas ISC aisladas por empresa_id
[ ] CxP generadas vinculadas con embarque_id correcto
[ ] Productos creados por liberación quedan en la empresa correcta

INTEGRACIÓN CONTABLE

[ ] Asiento del despacho aparece en Contabilidad → tab Asientos en estado CONFIRMADO
[ ] cont_documentos tiene fila con origen_tipo = 'imp_despacho' y origen_id = id del despacho
[ ] Si el módulo CONTABILIDAD no está en suscripción, el despacho se confirma igual y NO se genera asiento
   (la integración es fire-and-forget y no bloquea el flujo de negocio)

---

ESTADO DE IMPLEMENTACIÓN CONSOLIDADO

┌──────────────────────────────────────────────────┬──────────────────┬──────────────────────────────────────────┐
│ Funcionalidad                                    │ Estado           │ Detalle                                  │
├──────────────────────────────────────────────────┼──────────────────┼──────────────────────────────────────────┤
│ Activación doble llave (suscripción + switch)    │ ✅ Implementado  │ Fase 1                                    │
│ CRUD embarques (cabecera + estado + bitácora)    │ ✅ Implementado  │ Fase 1                                    │
│ Ítems perfil AUTOS                                │ ✅ Implementado  │ Fase 1                                    │
│ Ítems perfil CONSUMO_MASIVO                       │ ⚠️ Parcial       │ CRUD funciona, sin auto-creación stock   │
│ Componentes de costo + costeo en tiempo real      │ ✅ Implementado  │ Fase 1                                    │
│ Bitácora del expediente                           │ ✅ Implementado  │ Fase 1 (cierre)                           │
│ Catálogo de conceptos sistema (FOB, flete, etc.) │ ✅ Implementado  │ Fase 1                                    │
│ Despacho aduanero CRUD                            │ ✅ Implementado  │ Fase 2                                    │
│ Confirmación de despacho con tributos             │ ✅ Implementado  │ Fase 2                                    │
│ Tasas ISC vehículos administrables                │ ✅ Implementado  │ Fase 2 (lookup pendiente de UI sugerida) │
│ Liberación AUTOS (productos + stock + movimiento) │ ✅ Implementado  │ Fase 2                                    │
│ Liberación CONSUMO_MASIVO                         │ ✅ Implementado  │ Solo cambio estado ítem (v1)             │
│ Generación de CxP por componente                  │ ✅ Implementado  │ Fase 2                                    │
│ Integración contable cierre despacho              │ ✅ Implementado  │ Fase 2 (fire-and-forget)                  │
│ Permisos por privilegio                           │ ✅ Implementado  │ Fase 1 + Fase 2                           │
│ Aislamiento multi-tenant                          │ ✅ Implementado  │ Fase 1                                    │
│ Recostificación posterior al cierre               │ ❌ Pendiente     │ Fase 4                                    │
│ Sellado con permiso IMP_SELLAR_COSTO              │ ❌ Pendiente     │ Fase 4 (privilegio aún no creado)        │
│ Reportes (liquidación PDF/XLSX)                   │ ❌ Pendiente     │ Fase 4                                    │
│ Dashboard (embarques por estado, costos)          │ ❌ Pendiente     │ Fase 4                                    │
│ Escribanías y trámites de transferencia           │ ❌ Pendiente     │ Fase 3                                    │
│ Sync factura emitida → ítem VENDIDO               │ ❌ Pendiente     │ Fase 3                                    │
└──────────────────────────────────────────────────┴──────────────────┴──────────────────────────────────────────┘

---

⚠️ Nota módulo desactivado: si al entrar a Importaciones aparece "El módulo IMPORTACIONES está
desactivado para esta empresa", verificar que imp_config_empresa.modulo_operativo_activo = true
para esa empresa. Si la suscripción no incluye IMPORTACIONES, primero hay que sumarlo a
suscripcion_modulos (tarea de superadmin/holding).

⚠️ Nota cambio de perfil: una vez creado un embarque, su perfil queda fijo (definido al crearlo
según perfil_activo). Cambiar el perfil de la empresa solo afecta a embarques nuevos. Y el cambio
de perfil_activo se bloquea si hay embarques activos del perfil saliente que no estén en CERRADO/SELLADO.

⚠️ Nota cotización por componente: cada componente USD permite definir su propia cotización al
cargarse. La cotización del despacho (cotizacion_usada) es una referencia adicional para el día de
confirmación, NO se aplica retroactivamente a los componentes ya cargados con su cotización propia.

⚠️ Nota CxP en Gs.: la CxP generada usa importe_gs (ya convertido) y se etiqueta con la moneda Gs.
Las cotizaciones por componente se preservan. Si querés rastrear el monto en USD, mirá el
componente original (imp_componentes_costo.importe + imp_componentes_costo.moneda_id).

⚠️ Nota integración contable: si el módulo CONTABILIDAD no está activo en la suscripción, el cierre
de despacho NO genera asiento (la llamada está protegida con tieneModuloContabilidad). El despacho
se confirma normalmente y el costeo se actualiza igual.

⚠️ Nota máquina de estados: las transiciones a DESPACHADO y LIBERADO NO se hacen manualmente desde
los botones del header. DESPACHADO se llega al confirmar despacho; LIBERADO se llega al ejecutar
la liberación con depósito. Por eso esos botones abren modales o requieren prerrequisitos.
