# Plan — Importación Histórica Marangatu (MAR-01)

**Estado:** ✅ Implementado y refinado (2026-05-13) — pendiente deploy a producción
**Extensión MAR-01.1 (NC en VENTAS):** 🟡 Planificado (2026-05-24) — ver sección al final

---

## Resumen de implementación

| Fase | Estado | Notas |
|------|--------|-------|
| 1A — Migración SQL | ✅ | `marangatu_empresa_config` + módulo `BOT_MARANGATU` |
| 1B — Backend NestJS | ✅ | Sin `@nestjs/axios`, usa `fetch` nativo (Node 24) |
| 1C — WebSocket Gateway | ✅ | Lazy: solo conecta si hay configs en BD |
| 2A — Config frontend | ✅ | `EmpresaConfigTab.jsx` gateado por `BOT_MARANGATU` |
| 2B — Panel sync + pendientes | ✅ | Zustand store compartido + hook único en Layout |
| 2C — Flujo dos fases (importar → confirmar/rechazar) | ✅ | Estado `importado` → `pagada`/`pendiente` o `rechazado` |
| 2D — Vincular productos | ✅ | `PATCH /det/:id/vincular` + VincularDialog en panel |
| 2E — Guía contextual UI | ✅ | `MarangatuGuia.jsx` colapsible en tab |

---

## Contexto

Microservicio externo en `http://143.198.100.72:8500` realiza la captura real contra la SET. Novasis actúa como proxy: guarda credenciales cifradas, registra empresa en el microservicio, dispara sincronizaciones, persiste documentos en `compra_cab`, y muestra estado en tiempo real vía WebSocket.

---

## Flujo de importación — Dos fases

```
Cron cada 6h (o manual) → triggerSync → bot captura XMLs de la SET
      ↓
[Importar al ERP]  (manual)     → compra_cab.estado = 'importado'
      ↓                            No afecta stock ni costos
      ↓                            No aparece en tab Facturas
Panel "Pendientes de confirmación"
      ├── [Vincular productos]  → PATCH /det/:id/vincular → producto_id + afecta_stock
      ├── [Confirmar]
      │     ├── Contado  → aplica stock + costos + pago automático Efectivo → estado = 'pagada'
      │     └── Crédito  → aplica stock + costos + N cuentas_pagar         → estado = 'pendiente'
      └── [Rechazar]     → estado = 'rechazado' + motivo (auditoría, sin borrar)
```

### Estados `CompraEstado` (enum)

| Estado | Descripción |
|--------|-------------|
| `importado` | Importado desde Marangatu — en revisión. Oculto en Facturas. |
| `rechazado` | Rechazado por el usuario. Conservado para auditoría. Oculto en Facturas. |
| `pendiente` | Confirmado crédito — sin pago |
| `pagada` | Contado confirmado (pago automático) o totalmente pagada |
| `parcial` | Pago parcial |
| `anulada` | Anulada |

**Filtro en `compras.service.ts`**: el listado de Facturas siempre excluye `importado` y `rechazado` via `estado: { notIn: ['importado', 'rechazado'] }`.

---

## Fase 1A — Migraciones SQL ✅

### `prisma/migrations/20260511_marangatu_config/migration.sql`
```sql
CREATE TABLE IF NOT EXISTS marangatu_empresa_config (
  id                  UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  empresa_id          UUID NOT NULL UNIQUE REFERENCES empresas(id) ON DELETE CASCADE,
  username            VARCHAR(100) NOT NULL,
  password_encrypted  TEXT NOT NULL,          -- AES-256-CBC "iv:ciphertext"
  activo_compras      BOOLEAN NOT NULL DEFAULT false,
  activo_ventas       BOOLEAN NOT NULL DEFAULT false,
  ultima_sync_compras TIMESTAMP,
  ultima_sync_ventas  TIMESTAMP,
  created_at          TIMESTAMP DEFAULT now(),
  updated_at          TIMESTAMP DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_marangatu_config_empresa ON marangatu_empresa_config(empresa_id);
```

### `prisma/migrations/20260511_modulo_bot_marangatu/migration.sql`
```sql
INSERT INTO modulos (codigo, descripcion, precio, active)
VALUES ('BOT_MARANGATU', 'Bot Marangatu - Importación SET', 0, true)
ON CONFLICT DO NOTHING;
```

### `prisma/schema.prisma`
- Modelo `marangatu_empresa_config` agregado (patrón `AiEmpresaConfig`)

---

## Fase 1B — Backend NestJS ✅

### Archivos
```
src/marangatu/marangatu.module.ts
src/marangatu/marangatu-config.service.ts
src/marangatu/marangatu-sync.service.ts
src/marangatu/marangatu.gateway.ts
src/marangatu/marangatu.controller.ts
src/marangatu/dto/upsert-marangatu-config.dto.ts
src/compras/enums/compra-estado.enum.ts          ← enum CompraEstado
```

### Decisiones de implementación
- **HTTP al microservicio**: `fetch` nativo de Node 24 (sin `@nestjs/axios`)
- **WS al microservicio**: `socket.io-client` (el microservicio es Socket.IO, no WS puro)
- **Cifrado**: AES-256-CBC reutilizando `envs.aiEncryptionKey` (intencional)
- **URL microservicio**: `envs.botMarangatuBaseUrl` (default `http://143.198.100.72:8500`)
- **Formato API microservicio**: `tipoRegistro` = `"COMPRA"` o `"VENTA"` (singular)
- **Subscription WS**: emit `worker_public.subscribe { empresaRuc, tipoRegistro }`, escuchar `worker_public.event`

### Endpoints
| Método | Ruta | Descripción |
|--------|------|-------------|
| GET | `/marangatu/config` | Config empresa (sin password) |
| PUT | `/marangatu/config` | Guardar credenciales + llama `alta-integrada` |
| POST | `/marangatu/sync/compras` | Disparar sync `{ anho, mes }` al bot |
| POST | `/marangatu/sync/ventas` | Disparar sync `{ anho, mes }` al bot |
| POST | `/marangatu/importar/compras` | Consultar bot + persistir en `compra_cab` (estado `importado`) |
| POST | `/marangatu/consultar` | Solo consultar documentos del bot (sin persistir) |
| GET | `/marangatu/pendientes` | Listado de compras en estado `importado` con detalle e ítems |
| POST | `/marangatu/confirmar/:id` | Aplica stock + costos + pago → estado `pagada` o `pendiente` |
| POST | `/marangatu/rechazar/:id` | Marca como `rechazado` con motivo opcional |
| PATCH | `/marangatu/det/:detId/vincular` | Vincula ítem `compra_det` a un `producto_id` del ERP |

### Validación NC venta — emisor = empresa logueada

Para evitar que una **Nota de Crédito de COMPRA** (emitida por un proveedor) entre por error como venta cuando la SET devuelve documentos cruzados, en `importarVentas` se valida:

```
if (tipoComprobante incluye "NOTA DE CREDITO"):
    requerir xmlParsed.DE.gDatGralOpe.gEmis.dRucEm === empresas.ruc
    si no coincide → skip + contador rucEmisorNoCoincide
```

- Comparación se hace normalizando mayúsculas y removiendo tildes (`NOTA DE CRÉDITO` ≡ `NOTA DE CREDITO`).
- `empresas.ruc` se carga una sola vez al inicio de `importarVentas`.
- El log warn deja trazabilidad: `NC venta omitida — RUC emisor X ≠ empresa Y (CDC ...)`.
- El response incluye `rucEmisorNoCoincide` en el resumen.

### `rec_modo_aplicacion` al importar NC

- Si encontró factura asociada (`facturaAsociada !== null`) → `ESTRICTO`.
- Si no la encontró (NC queda libre para Recibos Multi-Facturas) → `FLEXIBLE`.

### Feed real de NC venta = `tipoRegistro=COMPRA`

Hallazgo SET: las **NC que la empresa EMITE** llegan en el feed con `tipoRegistro=COMPRA` (no en VENTAS). Por eso:

- `importarNotasCredito` consulta `tipoRegistro=COMPRAS` al bot y filtra solo NC (`tipoComprobante` incluye "NOTA DE CREDITO" o `iTiDE === 5`) dentro del loop.
- La validación `dRucEm === empresas.ruc` se mantiene como red de seguridad (descarta NC que se nos hayan emitido a nosotros y vengan mezcladas).
- Frontend `MarangatuSyncPanel.jsx`: cuando `tipo=VENTAS` + sub-tab=NC, el botón "Ver documentos" consulta con `tipoRegistro=COMPRAS` para mostrar el preview correcto. Al cambiar de sub-tab se limpia el preview para forzar nueva consulta.

### Filtro NC en feed de COMPRAS

El bot/SET devuelve las **NC que la empresa EMITE** bajo `tipoRegistro=COMPRA`. Esas NC pertenecen al flujo de Ventas → NC (se importan vía `importarNotasCredito` desde el feed de VENTAS), así que `importarCompras` las descarta al inicio del loop:

```
if (tipoComprobante incluye "NOTA DE CREDITO" || iTiDE === 5):
    ncFiltradas++
    continue
```

El response incluye `ncFiltradas` en el resumen para trazabilidad.

### Lógica de importación (`importarCompras`)
- Verifica duplicado con `findFirst` antes de crear
- Si ya existe en `importado`: borra ítems y reimporta (corrige datos erróneos — idempotente)
- Si ya existe en otro estado (confirmado): cuenta como `yaExistente` y salta
- NO crea productos automáticamente — `producto_id = null` si no hay coincidencia
- Retorna `{ importados, omitidos, yaExistentes, total, proveedoresCreados }`

### Lógica de `confirmarCompra`
1. Recalcula totales desde `compra_det` (no de `gGrupTot` que puede estar en 0)
2. Busca `condicion_operacion` (código 1=contado, 2=crédito), `moneda` PYG, `deposito` principal
3. Actualiza `compra_cab`: totales, estado, condición, moneda, depósito, obs limpia
4. Crea `compra_subtotales` (IVA 0/5/10)
5. Crea `cuentas_pagar`:
   - **Contado**: 1 cuenta, estado `pagada`, saldo 0
   - **Crédito con `cuotasDetalle[]`** (del XML): 1 cuenta por cuota con monto/fecha reales
   - **Crédito con `cuotas > 1`** (sin detalle): N cuentas dividiendo total + fechas equidistantes
   - **Crédito con solo plazo**: 1 cuenta con vencimiento = fecha + plazo días
6. **Contado**: crea `orden_pago_proveedor_cab` + `orden_pago_proveedor_det` + `pagos_proveedor` con medio "Efectivo", estado PAGADO
7. Aplica stock en `stock_deposito` + `movimientos_inventario` + `precio_costo` (solo ítems con `producto_id` y `afecta_stock = true`)

### Lógica de `vincularDetalle`
- Valida que el `compra_det` pertenece a una compra en estado `importado` de la empresa
- Valida que el `producto_id` existe en la empresa
- Actualiza `compra_det.producto_id` y `afecta_stock = producto.maneja_inventario`

### `getPendientes`
- `total` calculado desde `compra_det` cuando `compra_cab.total = 0` (fallback por XMLs con `gGrupTot` = 0)
- Parsea `pago_info` desde `observaciones` (separador `\n---PAGO---\n`)
- Incluye `detalle[]` con info de producto vinculado

### Formato `observaciones`
```
"Importado desde Marangatu · Contado · CDC 018005229230070010140452220260507..."
---PAGO---
{"condicion":"contado","plazo":0,"cuotas":0,"medios":[{"tipo":1,"desc":"Efectivo","monto":63000}],...}
```
Al confirmar, `observaciones` se limpia: queda solo la parte antes del separador.

### Campos xmlParsed — Paths correctos (Manual SET 150)
```
gDtipDE.gCamItem[]                              ← ítems (NO dentro de gCamFE)
gDtipDE.gCamCond                                ← condición de pago (NO dentro de gCamFE)
gCamItem[n].dCodInt                             ← código interno (para buscar producto ERP)
gCamItem[n].dDesProSer                          ← descripción
gCamItem[n].dCantProSer                         ← cantidad
gCamItem[n].gValorItem.dPUniProSer              ← precio unitario
gCamItem[n].gValorItem.dTotBruOpeItem           ← subtotal bruto
gCamItem[n].gValorItem.gValorRestaItem.dTotOpeItem  ← subtotal neto
gCamItem[n].gCamIVA.dTasaIVA                    ← tasa IVA (0, 5, 10)
gCamItem[n].gCamIVA.dLiqIVAItem                 ← monto IVA liquidado
gCamCond.iCondOpe                               ← "1"=contado "2"=crédito
gCamCond.gPagCond[]                             ← medios de pago contado (iTiPago, dMonTiPag)
gCamCond.gPagCred.dPlazoCre                     ← plazo crédito en días
gCamCond.gPagCred.dCuotas                       ← cantidad cuotas
gCamCond.gPagCred.gPagCredCuo[]                 ← detalle cuotas (dNroCuo, dMontCuota, dVencCuo)
```

### Cron
`@Cron('0 */6 * * *')` — llama `triggerSync` para COMPRAS cuando `activo_compras = true`.
**Solo dispara la captura en el bot** — NO auto-importa al ERP (el import sigue siendo manual para que el usuario revise).

---

## Fase 1C — WebSocket Gateway ✅

**Archivo:** `src/marangatu/marangatu.gateway.ts`

- Usa `socket.io-client` (no `ws` puro) para conectar al microservicio
- `onModuleInit()`: conecta solo si hay filas en `marangatu_empresa_config`
- On connect: emite `worker_public.subscribe` por cada empresa × tipo (COMPRA/VENTA)
- Reemite como `marangatu_event` al namespace `/marangatu` del frontend

---

## Fase 2A — Frontend: Config ✅

```
src/api/marangatu.service.js
src/tanstack/MarangatuStack.jsx
src/store/MarangatuStore.jsx
src/components/marangatu/MarangatuConfigSection.jsx
src/components/organismos/EmpresaConfigDesign/EmpresaConfigTab.jsx
```

---

## Fase 2B — Frontend: Panel sync + pendientes ✅

```
src/hooks/useMarangatuSocket.jsx                       ← hook único en Layout
src/components/marangatu/MarangatuSyncPanel.jsx        ← captura manual + log WS
src/components/marangatu/MarangatuPendientesPanel.jsx  ← tabla pendientes + vincular + confirmar/rechazar
src/components/marangatu/MarangatuGuia.jsx             ← guía colapsible (flujo + consideraciones)
src/components/templates/ComprasTemplate.jsx           ← tab "Marangatu" con badge count
```

### `MarangatuPendientesPanel`
- Filas expandibles: ítems con precio, IVA, producto ERP vinculado, badge stock
- Chip **"Vincular"** en ítems sin producto → `VincularDialog` con buscador de productos del ERP
- Sección condición de pago: contado (medios + montos) / crédito (plazo, cuotas, tabla de cuotas)
- Botón **Confirmar**: aplica stock + costos + pago → desaparece del panel
- Botón **Rechazar**: diálogo de confirmación → estado `rechazado` → desaparece del panel

### `MarangatuGuia`
- Panel colapsible al tope de la pestaña, cerrado por defecto
- 4 tarjetas con el flujo paso a paso (Capturar → Importar → Vincular → Confirmar/Rechazar)
- 5 notas de consideraciones (stock, reimportación, rechazo, pago automático contado)

---

## A futuro — Staging table (MAR-02)

Separar completamente el flujo Marangatu de `compra_cab` mediante una tabla `marangatu_staging`:
- Los datos del bot van a staging sin tocar tablas principales
- El usuario revisa y confirma → en ese momento se crea la `compra_cab`
- Ventaja: cero contaminación del flujo normal de compras
- Requiere migración + refactor completo del flujo

---

## MAR-01.1 — Extensión: Importación de Notas de Crédito en VENTAS 🟡

**Contexto:** la respuesta de `/api/consulta-documentos` con `tipoRegistro: "VENTA"` incluye también NC emitidas por la propia empresa (`tipoComprobante: "NOTA DE CREDITO"`, `iTiDE = "5"`). Hoy `importarVentas` solo crea facturas; las NC se ignoran. Esta extensión las soporta siguiendo el mismo patrón "consultar → importar manual" (sin stock ni contable), aplicando únicamente el descuento de saldo en cabecera de la factura asociada vía `dCdCDERef`.

### Decisiones de diseño (consensuadas 2026-05-24)

| # | Tema | Decisión |
|---|------|----------|
| 1 | Alcance | Solo NC propias en flujo VENTAS (no COMPRAS) |
| 2 | Flujo | Mismo que facturas: consultar bot → importar manual → crea registros directos. **NO** toca stock ni contable |
| 3 | Factura asociada faltante | **Importar igualmente** la NC con `factura_cab_id = null`. Caso de uso: se importan NCs anteriores (p.ej. 08-2025) cuyas facturas no se cargan al ERP (que arranca con facturas 01-2026). Estas NC quedan disponibles para aplicarse vía **Recibos Multi-Facturas**. Loguear evento `"NC sin factura asociada — disponible para aplicación posterior"` (informativo, no omite). |
| 4 | Detalle vs cabecera | Solo afectar `factura_cab.saldo_disponible` **cuando exista la factura asociada**. Sin factura → no se toca ninguna factura, la NC queda con saldo libre a aplicar. **No** tocar `factura_det.cantidad_nc_aplicada`. `nota_credito_det` se guarda como referencia |
| 5 | Timbrado/numeración | Respetar el del XML tal cual (`dNumTim`, `dEst`, `dPunExp`, `dNumDoc`). Marcar `origen = 'MARANGATU'` |
| 6 | Estado SIFEN | Copiar del XML: `cdc`, `estadoSifen`, `protocolo`, `codRespuesta`, `fechaProceso`. **Nunca** reenviar al middleware |
| 7 | UI | Dos sub-tabs dentro de la pestaña VENTAS: "Facturas" y "Notas de Crédito", con botones de **Importar** separados |
| 8 | Idempotencia | NC ya existente por CDC → cuenta como `yaExistente`, se salta. No revierte ni reaplica |
| 9 | Cliente | Si existe factura asociada → heredar `cliente_id` de la factura. Si **no** existe factura → resolver por `dRucRec` del XML (`gDatGralOpe.gDatRec.dRucRec`) buscando en `personas.ruc` **o** `personas.nro_documento` (matcheando tanto el valor con DV como sin DV: `12345678-1` y `12345678`). Si tampoco hay match → `cliente_id = null` (la NC queda importada igual). |
| 10 | Moneda y montos | Confiar en XML: moneda distinta a la factura → importar igual. Monto > saldo → importar igual (saldo puede quedar negativo) |
| 11 | Permisos | Reutilizar permisos existentes del bot Marangatu — sin permisos adicionales |

### Paths xmlParsed relevantes (Manual SET 150 — NC, `iTiDE=5`)

```
gDtipDE.gCamItem[]                              ← ítems de la NC
gDtipDE.gCamItem[n].dCodInt                     ← código interno
gDtipDE.gCamItem[n].dDesProSer                  ← descripción
gDtipDE.gCamItem[n].dCantProSer                 ← cantidad
gDtipDE.gCamItem[n].gValorItem.dPUniProSer      ← precio unitario
gDtipDE.gCamItem[n].gCamIVA.dTasaIVA            ← tasa IVA
gDtipDE.gCamItem[n].gCamIVA.dLiqIVAItem         ← IVA liquidado
gTotSub.dTotGralOpe                             ← total NC
gTotSub.dTotIVA                                 ← IVA total
gCamDEAsoc[0].dCdCDERef                         ← CDC de la factura asociada (44 dígitos)
gCamDEAsoc[0].iTipDocAso                        ← "1" = Electrónico
gDatGralOpe.gOpeCom.cMoneOpe                    ← moneda
gDatGralOpe.dFeEmiDE                            ← fecha emisión NC
gTimb.dNumTim / dEst / dPunExp / dNumDoc        ← timbrado y numeración del XML
```

### Backend — Cambios

#### `marangatu-sync.service.ts`

1. **`importarVentas` (refactor):** filtrar solo `tipoComprobante === "FACTURA"`. NC se ignoran en este flujo.
2. **Nuevo método `importarNotasCredito(empresaId, dto)`:**
   - Llama `consultarDocumentos(empresaId, 'VENTAS', dto)` y filtra `tipoComprobante === "NOTA DE CREDITO"`.
   - Para cada NC (en orden):
     - Verificar duplicado por `cdc` en `nota_credito_cab` → si existe, `yaExistente++` y skip.
     - Buscar factura por `cdc = xmlParsed.DE.gCamDEAsoc[0].dCdCDERef` en `factura_cab` (misma empresa). **No bloquea**: si no se encuentra, la NC se importa igualmente con `factura_cab_id = null` y se loguea en `omitidasDetalle` como informativo con motivo `"NC importada sin factura asociada (disponible para Recibos Multi-Facturas)"` — esto cuenta en `importadas`, no en `omitidas`.
     - Resolver `cliente_id`:
       1. Si hay factura asociada → `factura.cliente_id`.
       2. Si no → lookup por `dRucRec` contra `personas.ruc` **o** `personas.nro_documento` (probar con DV y sin DV), filtrando por `empresa_id`.
       3. Si no hay match → `null`.
     - Crear `nota_credito_cab` con:
       - `factura_cab_id` = factura encontrada o `null`
       - `cliente_id` = resuelto según prioridad anterior
       - Timbrado/numeración del XML (`gTimb.*`)
       - Totales del XML (`gTotSub.dTotGralOpe`, etc.)
       - Moneda del XML
       - Estado SIFEN: `cdc`, `estadoSifen='Aprobado'`, `protocolo`, `codRespuesta`, `fechaProceso`
       - `origen = 'MARANGATU'` (campo nuevo o usar `observaciones`/flag existente)
       - `dinfadic`: `"MARANGATU_NC | CDC:<cdc> | <numero>"` (+ sufijo `" | sin factura asociada"` si aplica)
     - Crear `nota_credito_det` por cada `gCamItem` (referencia informativa, sin `producto_id`).
     - **Solo si hay factura asociada**: actualizar `factura_cab.saldo_disponible = saldo_disponible - totalNC` (puede quedar negativo). Si no hay factura, **omitir** este paso — la NC queda con saldo libre para aplicarse después desde Recibos Multi-Facturas.
     - **NO** tocar `factura_det.cantidad_nc_aplicada`.
     - **NO** mover stock.
     - **NO** generar asientos contables.
   - Retornar `{ importadas, omitidas, yaExistentes, total, omitidasDetalle, sinFacturaAsociada }` (este último es contador informativo del subconjunto importado sin factura).

#### `marangatu.controller.ts`

- Nuevo endpoint: `POST /marangatu/importar/notas-credito` → `syncService.importarNotasCredito(user.empresa_id, dto)`.

#### Migración (si se agrega flag `origen`)

```sql
ALTER TABLE nota_credito_cab
  ADD COLUMN IF NOT EXISTS origen VARCHAR(20) DEFAULT 'MANUAL';
-- valores: 'MANUAL' | 'MARANGATU'
```

Alternativa: reutilizar `observaciones` con prefijo `"MARANGATU_NC"` (sin migración).

### Frontend — Cambios

#### `marangatu.service.js`
- Agregar `importarNotasCredito({ anho, mes })` → `POST /marangatu/importar/notas-credito`.

#### `MarangatuSyncPanel.jsx` (pestaña VENTAS)
- Refactor a dos sub-tabs internos: **"Facturas"** y **"Notas de Crédito"**.
- Cada sub-tab tiene su propio botón **Importar al ERP** y su propia tabla filtrada por `tipoComprobante`.
- Conteos en los chips de cada sub-tab (cantidad de facturas / cantidad de NC en la respuesta del bot).

#### `MarangatuGuia.jsx`
- Agregar tarjeta nueva en el flujo: **"Importar NC después de Facturas"** explicando el orden recomendado.
- Nota: las NC cuya factura no esté en ERP se importan igualmente y quedan disponibles para aplicar vía **Recibos Multi-Facturas**. Útil cuando el corte de migración incluye NC anteriores a la fecha de corte de facturas (ej. facturas desde 01-2026 + NC desde 08-2025).

### Casos de uso — Verificación

1. Lote con FACTURA + NC del mismo período → importar facturas primero, luego NC; ambas crean registros, factura asociada queda con saldo descontado.
2. NC cuyo `dCdCDERef` apunta a factura nunca importada (caso migración con NC históricas) → **importa igual** con `factura_cab_id=null`, `cliente_id` resuelto por RUC del receptor. Queda disponible para Recibos Multi-Facturas. Aparece informativamente en `omitidasDetalle` con motivo `"NC importada sin factura asociada"`.
3. Reimportar mismo período de NC → todas como `yaExistente`, sin cambios en saldos.
4. NC con monto > saldo factura → importa, `saldo_disponible` queda negativo (flag de revisión opcional en futuro).
5. NC en moneda distinta a la factura → importa igual, no se hace conversión.
6. Validar que el módulo de NC manual del ERP NO ve las NC marcadas con `origen='MARANGATU'` en su listado de "borradores a enviar a SIFEN" (ya están aprobadas).
7. **Recibo Multi-Facturas usando NC importada sin factura**: cliente con factura nueva del ERP + NC histórica importada de Marangatu (sin `factura_cab_id`) → la NC debe poder seleccionarse en el flujo de cobros multi-factura y aplicarse al saldo de la factura nueva del mismo cliente.

### Fuera de alcance MAR-01.1

- NC en COMPRAS (NC recibidas de proveedores) — quedaría para MAR-01.2 si se requiere.
- Aplicación de NC a múltiples facturas (hoy `nota_credito_cab.factura_cab_id` es 1:1).
- Stock / contabilidad / reversión de aplicación (consistente con cómo se importan las facturas históricas).
- Match de items de NC con líneas específicas de `factura_det`.

---

## Variables de entorno

```env
BOT_MARANGATU_BASE_URL=http://143.198.100.72:8500
# MARANGATU_ENCRYPTION_KEY=    # Opcional; usa AI_ENCRYPTION_KEY si no está
```

---

## Pendientes para producción

- [ ] `npx prisma migrate deploy` en servidor (2 migraciones nuevas)
- [ ] `npx prisma generate` en servidor
- [ ] Agregar `BOT_MARANGATU_BASE_URL` al `.env` del servidor
- [ ] Asignar módulo `BOT_MARANGATU` a empresas vía `suscripcion_modulos`
- [ ] Verificar formato de eventos WS del microservicio (`tipo`: `COMPRA`/`VENTA`)
- [ ] Fix import `@nestjs-modules/mailer`: cambiar `dist/adapters/handlebars.adapter` → `adapters/handlebars.adapter`

---

## Verificación

1. Guardar credenciales → fila en `marangatu_empresa_config` con `password_encrypted`
2. Log backend: `"Empresa RUC registrada en microservicio Marangatu"`
3. Captura Manual → eventos en tiempo real en el log del panel WS
4. Importar al ERP → filas en `compra_cab` con `estado='importado'`, NO visibles en Facturas
5. Expandir fila → ver ítems → chip "Vincular" en ítems sin producto → buscar y vincular
6. Confirmar compra **contado** → estado `pagada`, `cuentas_pagar` pagada, orden de pago con Efectivo, aparece en Facturas
7. Confirmar compra **crédito con cuotas** → estado `pendiente`, N registros en `cuentas_pagar` (uno por cuota con fecha/monto correcto)
8. Rechazar → estado `rechazado`, desaparece del panel, no afecta stock
9. Reimportar mismo período → registros en `importado` se actualizan (idempotente)
10. Verificar que Facturas excluye `importado` y `rechazado` siempre
