# Plan: Módulo Nota de Remisión — Novasis ERP

## Decisiones confirmadas

| Decisión | Valor |
|---|---|
| Módulo UI | Dentro de Facturación |
| Punto de entrada | Independiente O desde factura (ambas direcciones) |
| ABM transportista/chofer/vehículo/agente | En módulo Contactos |
| Numeración | Mismo sistema que facturas (tipo_documento = 7, creación manual) |
| Estados | Pendiente → Aprobado / Rechazado / Anulado |
| Envío SIFEN | Mismo patrón que factura y nota de crédito |
| Detalle | Productos del catálogo obligatorio, sin IVA, con precio y cantidad |
| Relación factura ↔ remisión | Many-to-many a nivel de cabecera y detalle, con cantidades aplicadas |
| Stock | Sin movimiento propio (lo gestiona la factura asociada) |

---

## Arquitectura de relaciones

### Regla de negocio clave

Una nota de remisión puede estar asociada a **cero, una o varias facturas**, y una factura puede estar asociada a **cero, una o varias notas de remisión**. La asociación se trackea a dos niveles:

1. **Cabecera**: qué facturas están vinculadas a qué remisiones
2. **Detalle**: cuántas unidades de cada ítem de remisión fueron aplicadas en cada factura (y viceversa)

```
nota_remision_cab ──────────────────────── factura_cab
       │                 (many-to-many)          │
       │          nota_remision_factura           │
       │                                         │
nota_remision_det ──────────────────────── factura_det
       │          (many-to-many con cantidad)     │
       │       nota_remision_det_aplicacion       │
       │                                         │
  cantidad_total                           cantidad_total
  cantidad_aplicada ◄──────────────────── cantidad_aplicada_remision
  cantidad_disponible                      cantidad_disponible_remision
```

### Stock e inventario (decisión revisada 2026-07-23)

**Cómo funciona hoy el resto del sistema (para consistencia):**

| Documento | Stock | Contabilidad | Reversión |
|---|---|---|---|
| Factura | Descuenta **al CREAR** (no al aprobar SIFEN; permite negativo) | `integrarFacturaVenta` **al aprobar SIFEN** | Solo al **anular**; el rechazo SIFEN no repone |
| Nota de Crédito | Repone (devolución) al crear | Al aprobar SIFEN | — |
| Remisión (actual) | **No toca stock** | Sin asiento (correcto) | Al anular revierte solo `cantidad_aplicada_remision` |

**Regla del sistema:** el **stock se mueve al CREAR** (realidad física); la **contabilidad al APROBAR** SIFEN. El rechazo no revierte; solo la anulación.

**Dos escenarios de remisión:**

- **A) Remisión CON factura asociada** (electrónica, `nota_remision_factura`): la factura **ya descontó** el stock. La remisión **NO debe tocar stock** → evita doble descuento. *(Regla crítica.)*
- **B) Remisión SIN factura** (fecha futura / consignación): la mercadería sale del depósito pero ninguna factura movió stock aún (caso "remisiono ahora, facturo a fin de mes").

**Decisión adoptada:**

1. **Remisión CON factura → no mueve stock** (lo hizo la factura).
2. **Remisión SIN factura → descuenta stock al CREAR** con tipo de movimiento propio `SALIDA_REMISION` (permite negativo, igual que factura). La mercadería físicamente salió.
3. **La factura debe detectar si sus ítems provienen de una remisión** (vía `nota_remision_factura` / `cantidad_aplicada_remision`) y **no volver a descontar** esos ítems → sin esto habría doble conteo.
4. **Timing:** stock al **crear** (no esperar aprobación SIFEN). **Rechazo → no revierte.** **Anulación → repone stock** (además del `cantidad_aplicada_remision` que ya se revierte).
5. **Vínculo con factura:** una remisión Pendiente/Rechazada/Aprobada sigue **consumiendo** la cantidad disponible de la factura; solo la anulación libera.

**Contabilidad:** la remisión **no es un hecho contable/fiscal** → no genera asiento. El ingreso y el COGS los reconoce la **factura** al aprobarse (no crear `integrarRemision`).
- Matiz enterprise (Fase 3 opcional): para el escenario B, lo ortodoxo es reclasificar activo `Inventario → Mercadería en tránsito/pendiente de facturar` en la remisión, y reconocer ingreso+COGS al facturar. Como remisión→factura ocurre dentro del mismo mes, aceptar el desfase intra-mes (descontar inventario en remisión, COGS en factura) es válido para arrancar.

Ver **Fase 7 — Integración de inventario** para el plan de implementación por fases.

---

## Modelo de datos

### Entidades base (Contactos)

#### `transportistas`
```prisma
model transportistas {
  id                    String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id            String   @db.Uuid
  persona_id            String   @unique @db.Uuid
  tipo_transportista    tipo_transportista_enum  // propio | tercero
  nro_habilitacion_mopc String?  @db.VarChar(50)
  activo                Boolean  @default(true)
  created_at            DateTime @default(now()) @db.Timestamp(6)
  updated_at            DateTime @default(now()) @db.Timestamp(6)
  empresas              empresas @relation(fields: [empresa_id], references: [id])
  personas              personas @relation(fields: [persona_id], references: [id])
  vehiculos             vehiculos[]
  nota_remision_cab     nota_remision_cab[]
}

enum tipo_transportista_enum {
  propio
  tercero
}
```

#### `choferes`
```prisma
model choferes {
  id                        String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id                String   @db.Uuid
  persona_id                String   @unique @db.Uuid
  nro_libreta_conducir      String?  @db.VarChar(30)
  categoria_licencia        categoria_licencia_enum?  // A | B | C | D | E
  fecha_vencimiento_licencia DateTime? @db.Date
  activo                    Boolean  @default(true)
  created_at                DateTime @default(now()) @db.Timestamp(6)
  updated_at                DateTime @default(now()) @db.Timestamp(6)
  empresas                  empresas @relation(fields: [empresa_id], references: [id])
  personas                  personas @relation(fields: [persona_id], references: [id])
  nota_remision_cab         nota_remision_cab[]
}

enum categoria_licencia_enum {
  A
  B
  C
  D
  E
}
```

#### `vehiculos`
```prisma
model vehiculos {
  id                String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id        String   @db.Uuid
  transportista_id  String   @db.Uuid
  nro_matricula     String?  @db.VarChar(20)
  nro_identificacion String? @db.VarChar(50)   // chasis / VIN
  marca             String   @db.VarChar(60)
  modelo            String?  @db.VarChar(60)
  anio              Int?
  tipo_vehiculo     tipo_vehiculo_enum          // camion | camioneta | furgon | moto | cisterna | trailer | otro
  tipo_identificacion Int?                      // 1=matricula, 2=identificacion (código SIFEN)
  capacidad_carga_kg Decimal? @db.Decimal(10,2)
  informacion_adicional String? @db.VarChar(200)
  activo            Boolean  @default(true)
  created_at        DateTime @default(now()) @db.Timestamp(6)
  updated_at        DateTime @default(now()) @db.Timestamp(6)
  empresas          empresas @relation(fields: [empresa_id], references: [id])
  transportistas    transportistas @relation(fields: [transportista_id], references: [id])
  nota_remision_cab nota_remision_cab[]
}

enum tipo_vehiculo_enum {
  camion
  camioneta
  furgon
  moto
  cisterna
  trailer
  otro
}
```

#### `agentes_transporte`
```prisma
model agentes_transporte {
  id                    String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id            String   @db.Uuid
  persona_id            String   @unique @db.Uuid
  nro_habilitacion_aduana String? @db.VarChar(50)
  tipo_agente           tipo_agente_enum         // transporte | aduanas | ambos
  activo                Boolean  @default(true)
  created_at            DateTime @default(now()) @db.Timestamp(6)
  updated_at            DateTime @default(now()) @db.Timestamp(6)
  empresas              empresas @relation(fields: [empresa_id], references: [id])
  personas              personas @relation(fields: [persona_id], references: [id])
  nota_remision_cab     nota_remision_cab[]
}

enum tipo_agente_enum {
  transporte
  aduanas
  ambos
}
```

---

### Nota de Remisión

#### `nota_remision_cab`
```prisma
model nota_remision_cab {
  id                        String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  empresa_id                String   @db.Uuid
  numeracion_id             String   @db.Uuid
  cliente_id                String   @db.Uuid
  transportista_id          String   @db.Uuid
  chofer_id                 String   @db.Uuid
  vehiculo_id               String   @db.Uuid
  agente_id                 String?  @db.Uuid

  // Numeración
  establecimiento           String   @db.VarChar(3)
  punto_expedicion          String   @db.VarChar(3)
  nro_comprobante           String   @db.VarChar(7)
  serie_num                 String   @default("AA") @db.VarChar(2)

  // Fechas
  dfeemide                  DateTime @db.Timestamp(6)          // fecha emisión
  fecha_inicio_traslado     DateTime @db.Date
  fecha_fin_traslado        DateTime @db.Date
  fecha_futura_emision      DateTime? @db.Date                 // solo cuando sin doc asociado

  // Clasificaciones SIFEN
  motivo_emision            motivo_emision_remision_enum
  tipo_responsable          tipo_responsable_remision_enum
  tipo_transporte           tipo_transporte_enum
  modalidad_transporte      modalidad_transporte_enum
  condicion_negociacion     condicion_negociacion_enum?
  kilometros                Int

  // Despacho importación
  nro_despacho_importacion  String?  @db.VarChar(20)

  // Dirección salida
  departamento_salida_id    Int
  distrito_salida_id        Int
  ciudad_salida_id          Int
  direccion_salida          String   @db.VarChar(255)
  nro_casa_salida           Int      @default(0)
  complemento1_salida       String?  @db.VarChar(255)
  telefono_salida           String?  @db.VarChar(20)

  // Dirección entrega
  departamento_entrega_id   Int
  distrito_entrega_id       Int
  ciudad_entrega_id         Int
  direccion_entrega         String   @db.VarChar(255)
  nro_casa_entrega          Int      @default(0)
  complemento1_entrega      String?  @db.VarChar(255)
  telefono_entrega          String?  @db.VarChar(20)

  // Documento asociado (electronico o impreso)
  tipo_documento_asociado   String?  @db.VarChar(1)            // 1=electrónico, 2=impreso, null=sin doc
  // Electrónico
  cdc_asociado              String?  @db.VarChar(44)
  // Impreso
  timbrado_asociado         String?  @db.VarChar(8)
  establecimiento_asociado  String?  @db.VarChar(3)
  expedicion_asociado       String?  @db.VarChar(3)
  nro_doc_asociado          String?  @db.VarChar(7)
  tipo_doc_impreso_asociado String?  @db.VarChar(2)
  fecha_doc_asociado        DateTime? @db.Date

  // Info adicional
  info_adicional            String?  @db.VarChar(5000)

  // Estado
  estado                    estado_remision_enum @default(Pendiente)

  // SIFEN
  cdc                       String?  @db.VarChar(44)
  enlace_qr                 String?  @db.Text
  estado_sifen              String?  @db.VarChar(50)
  fecha_envio_sifen         DateTime? @db.Timestamp(6)
  fecha_firma_sifen         DateTime? @db.Timestamp(6)
  fecha_registro_sifen      DateTime? @db.Timestamp(6)
  mensaje_sifen             String?  @db.Text
  nro_lote                  String?  @db.VarChar(20)
  estado_evento             String?  @db.VarChar(50)
  evento_aplicado           String?  @db.VarChar(50)
  fecha_evento              DateTime? @db.Timestamp(6)
  mensaje_evento            String?  @db.Text

  created_at                DateTime @default(now()) @db.Timestamp(6)
  updated_at                DateTime @default(now()) @db.Timestamp(6)

  // Relaciones
  empresas                  empresas @relation(fields: [empresa_id], references: [id])
  numeraciones              numeraciones @relation(fields: [numeracion_id], references: [id])
  clientes                  clientes @relation(fields: [cliente_id], references: [id])
  transportistas            transportistas @relation(fields: [transportista_id], references: [id])
  choferes                  choferes @relation(fields: [chofer_id], references: [id])
  vehiculos                 vehiculos @relation(fields: [vehiculo_id], references: [id])
  agentes_transporte        agentes_transporte? @relation(fields: [agente_id], references: [id])
  items                     nota_remision_det[]
  facturas                  nota_remision_factura[]       // cabeceras de facturas vinculadas

  @@index([empresa_id, estado])
  @@index([empresa_id, dfeemide])
  @@index([cdc])
}

enum estado_remision_enum {
  Pendiente
  Aprobado
  Rechazado
  Anulado
}

enum motivo_emision_remision_enum {
  traslado_por_ventas           // 1
  traslado_por_consignacion     // 2
  traslado_por_devolucion       // 3
  traslado_por_traslado_interno // 4
  traslado_por_exhibicion       // 5
  traslado_por_importacion      // 6
  traslado_por_exportacion      // 7
  otro                          // 99
}

enum tipo_responsable_remision_enum {
  emisor_factura    // 1
  receptor_factura  // 2
}

enum tipo_transporte_enum {
  terrestre   // 1
  fluvial     // 2
  aereo       // 3
  multimodal  // 4
}

enum modalidad_transporte_enum {
  carga_general   // 1
  contenedor      // 2
  granel          // 3
  refrigerado     // 4
}

enum condicion_negociacion_enum {
  CFR  EXW  FCA  CPT  CIP  DAP  DPU  DDP
  FAS  FOB  CIF  DAF  DES  DEQ  DDU  DAT
}
```

#### `nota_remision_det`
```prisma
model nota_remision_det {
  id                        String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  nota_remision_cab_id      String   @db.Uuid
  producto_id               String   @db.Uuid
  descripcion               String   @db.VarChar(120)
  unidad_medida             String   @db.VarChar(5)     // código SIFEN: UNI, KGM, LTR...
  cantidad                  Decimal  @db.Decimal(14,4)
  precio_unitario           Decimal  @db.Decimal(19,4)
  cantidad_aplicada         Decimal  @default(0) @db.Decimal(14,4)  // suma aplicada en facturas
  item                      Int                                      // orden del ítem

  nota_remision_cab         nota_remision_cab @relation(fields: [nota_remision_cab_id], references: [id], onDelete: Cascade)
  productos                 productos @relation(fields: [producto_id], references: [id])
  aplicaciones              nota_remision_det_aplicacion[]

  // Campo virtual: cantidad_disponible = cantidad - cantidad_aplicada

  @@index([nota_remision_cab_id])
}
```

---

### Tablas de vinculación many-to-many

#### `nota_remision_factura` — vinculación a nivel de cabecera
```prisma
model nota_remision_factura {
  id                    String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  nota_remision_cab_id  String   @db.Uuid
  factura_cab_id        String   @db.Uuid
  created_at            DateTime @default(now()) @db.Timestamp(6)

  nota_remision_cab     nota_remision_cab @relation(fields: [nota_remision_cab_id], references: [id])
  factura_cab           factura_cab @relation(fields: [factura_cab_id], references: [id])

  @@unique([nota_remision_cab_id, factura_cab_id])
  @@index([factura_cab_id])
}
```

#### `nota_remision_det_aplicacion` — vinculación a nivel de detalle con cantidades
```prisma
model nota_remision_det_aplicacion {
  id                    String   @id @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  nota_remision_det_id  String   @db.Uuid        // ítem de la remisión
  factura_det_id        String   @db.Uuid        // ítem de la factura
  cantidad_aplicada     Decimal  @db.Decimal(14,4)
  created_at            DateTime @default(now()) @db.Timestamp(6)

  nota_remision_det     nota_remision_det @relation(fields: [nota_remision_det_id], references: [id])
  factura_det           factura_det @relation(fields: [factura_det_id], references: [id])

  @@index([nota_remision_det_id])
  @@index([factura_det_id])
}
```

**Campos adicionales en `factura_det`** (migración):
```sql
ALTER TABLE factura_det ADD COLUMN cantidad_aplicada_remision DECIMAL(14,4) DEFAULT 0;
-- cantidad ya facturada que proviene de una remisión
```

---

## Lógica de cantidades aplicadas

### Al crear una factura desde una remisión

```
Para cada ítem de la remisión que se incluye en la factura:

  1. Validar: cantidad_a_facturar <= nota_remision_det.cantidad_disponible
     (cantidad_disponible = cantidad - cantidad_aplicada)

  2. Crear factura_det con los datos del producto

  3. Crear nota_remision_det_aplicacion:
     { nota_remision_det_id, factura_det_id, cantidad_aplicada }

  4. Actualizar nota_remision_det.cantidad_aplicada += cantidad_a_facturar

  5. Crear nota_remision_factura si no existe el vínculo de cabecera

  6. Si todos los ítems de la remisión tienen cantidad_disponible = 0
     → la remisión queda "totalmente aplicada" (campo calculado, no de estado)
```

### Al crear una remisión desde una factura

```
Para cada ítem de la factura que se incluye en la remisión:

  1. El precio_unitario del ítem de remisión se toma de la factura

  2. Crear nota_remision_det

  3. Crear nota_remision_det_aplicacion inmediatamente
     { nota_remision_det_id, factura_det_id, cantidad_aplicada = cantidad }

  4. Actualizar factura_det.cantidad_aplicada_remision += cantidad

  5. Crear nota_remision_factura

  Nota: en este flujo la remisión nace "aplicada" sobre esa factura,
  pero si la factura tiene más ítems, pueden quedar disponibles para otras remisiones.
```

### Al anular una remisión

```
  1. Revertir todos los nota_remision_det.cantidad_aplicada
  2. Revertir todos los factura_det.cantidad_aplicada_remision
  3. Eliminar nota_remision_det_aplicacion
  4. Eliminar nota_remision_factura
  5. Estado → Anulado
```

---

## Endpoints backend

```
# Entidades Contactos
GET    /transportistas                    → listar (empresa)
POST   /transportistas                    → crear
GET    /transportistas/:id               → detalle
PATCH  /transportistas/:id               → editar
DELETE /transportistas/:id               → desactivar (activo=false)

GET    /choferes                         → listar
POST   /choferes                         → crear
GET    /choferes/:id                     → detalle
PATCH  /choferes/:id                     → editar
DELETE /choferes/:id                     → desactivar

GET    /vehiculos                        → listar (filtrable por transportista_id)
POST   /vehiculos                        → crear
GET    /vehiculos/:id                    → detalle
PATCH  /vehiculos/:id                    → editar
DELETE /vehiculos/:id                    → desactivar

GET    /agentes-transporte               → listar
POST   /agentes-transporte               → crear
GET    /agentes-transporte/:id           → detalle
PATCH  /agentes-transporte/:id           → editar
DELETE /agentes-transporte/:id           → desactivar

# Nota de Remisión
GET    /nota-remision                    → listar (filtros: estado, fecha, cliente)
POST   /nota-remision                    → crear (estado=Pendiente)
GET    /nota-remision/:id                → detalle con ítems + facturas vinculadas
PATCH  /nota-remision/:id               → editar (solo Pendiente/Rechazado)
DELETE /nota-remision/:id               → anular

POST   /nota-remision/:id/enviar-sifen   → enviar a SIFEN (mismo patrón factura)
GET    /nota-remision/:id/consultar-sifen → consultar estado en SIFEN

# Vinculación
GET    /nota-remision/:id/facturas       → facturas vinculadas + cantidades aplicadas
GET    /nota-remision/:id/disponibilidad → ítems con cantidad_disponible por ítem

POST   /nota-remision/desde-factura/:facturaId  → pre-cargar remisión desde una factura
POST   /factura/desde-remision/:remisionId      → pre-cargar factura desde una remisión
                                                   (devuelve datos, no guarda aún)
```

---

## Estructura backend (NestJS)

```
src/
├── transportistas/
│   ├── transportistas.module.ts
│   ├── transportistas.controller.ts
│   ├── transportistas.service.ts
│   └── dto/
│       ├── create-transportista.dto.ts
│       └── update-transportista.dto.ts
│
├── choferes/
│   ├── choferes.module.ts
│   ├── choferes.controller.ts
│   ├── choferes.service.ts
│   └── dto/
│
├── vehiculos/
│   ├── vehiculos.module.ts
│   ├── vehiculos.controller.ts
│   ├── vehiculos.service.ts
│   └── dto/
│
├── agentes-transporte/
│   ├── agentes-transporte.module.ts
│   ├── agentes-transporte.controller.ts
│   ├── agentes-transporte.service.ts
│   └── dto/
│
└── nota-remision/
    ├── nota-remision.module.ts
    ├── nota-remision.controller.ts
    ├── nota-remision.service.ts
    └── dto/
        ├── create-nota-remision.dto.ts    ← cabecera + items[]
        └── update-nota-remision.dto.ts
```

---

## Estructura frontend (React)

Estado real al 2026-07-15. Los archivos marcados con ⚠ existen pero tienen deuda técnica de estándar.

```
src/
├── components/ventas/
│   ├── RemisionesTab.jsx           ⚠ listado — sin TablePaginationBar / StandardTable / ScreenGuia / EmptyState
│   └── NuevaRemisionTemplate.jsx   ⚠ formulario — styled-components custom en lugar de MUI Paper sections / MUI Grid v7
│
├── api/
│   ├── nota-remision.service.js      ✅  (getNotasRemision, createNotaRemision, anularNotaRemision)
│   ├── transportistas.service.js     ✅
│   ├── choferes.service.js           ✅
│   ├── vehiculos.service.js          ✅
│   └── agentes-transporte.service.js ✅
│
└── tanstack/
    └── RemisionesStack.jsx           (pendiente — hoy las queries viven en el componente)
```

Archivos que **faltan crear** (Fase 4 pendiente):

```
src/
├── components/ventas/
│   └── RemisionDetalleDialog.jsx   ← vista detalle + facturas vinculadas + disponibilidad
│
└── tanstack/
    └── RemisionesStack.jsx         ← extraer queries/mutations
```

### Estándar de UI obligatorio para esta pantalla

Seguir `docs/ui-standards.md`. Decisiones ya tomadas:

**Listado `RemisionesTab`:**
- `ScreenGuia` colapsable arriba (pasos: crear, enviar SIFEN, vincular factura)
- `EmptyState` cuando no hay remisiones (CTA "Nueva Remisión")
- `TablePaginationBar` de `_standards/` (server-side, no client-side con limit fijo)
- Paginación server-side → `getNotasRemision({ page, limit, ...filtros })` con `lastPage` del backend
- Estado chip usando colores semánticos: Pendiente `#f59e0b`, Aprobado `#22c55e`, Rechazado `#ef4444`, Anulado `#94a3b8`
- Iconografía exclusivamente `@iconify/react` paquete `lucide:` (actualmente mezcla `@mui/icons-material`)
- Filtros: search por texto + filtro estado + rango fechas en `<Stack direction="row">`
- Acción "Generar remisión desde factura" como botón outline en el header

**Formulario `NuevaRemisionTemplate`:**
- Reemplazar styled-components custom por **secciones `<Paper variant="outlined">`** con header `Typography variant="caption" fontWeight={700}` + ícono `mdi:` (ver patrón en `GastosTemplate.jsx` y `ProveedorFormDialog`)
- `Grid` con **`size={{ xs, md }}`** (MUI v7) — actualmente usa flexbox custom
- `ScreenGuia` colapsable al inicio del formulario
- `InlineValidationBanner` para errores de submit multi-campo (actualmente usa `toast`)
- Selectores de transportista/chofer/vehículo/agente: `<Autocomplete>` con backend-search y debounce 300 ms (actualmente mezcla SearchDropdown custom con Select)
- Cascada depto → distrito → ciudad: mismo helper que el resto de formularios con ubicación
- Fechas con `toFechaCalendarioISO()` al enviar y `fmtFechaCalendario()` al mostrar (del `utils/fecha.js`)
- Permisos gateados con `usePermission("VENTAS")` → permiso `VEN_REM_*`

### Wireframe de secciones del formulario

```
┌─────────────────────────────────────────────────────────┐
│  ScreenGuia (colapsable)                                │
├─────────────────────────────────────────────────────────┤
│  Paper ① DATOS DEL DOCUMENTO                           │
│  Cliente* [Autocomplete server-search]  Fecha* [date]  │
│  Sucursal* [sel]  Punto Exp.* [sel]  Nro [auto]        │
│  Motivo* [Autocomplete/Select]  Responsable*  Km* [n]  │
│  Doc. Asociado: ○ Ninguno  ○ Electrónico  ○ Impreso    │
│  [CDC input | timbrado+nro+fecha según selección]      │
├─────────────────────────────────────────────────────────┤
│  Paper ② TRANSPORTE                                    │
│  Transportista* [Autocomplete]  Chofer* [Autocomplete] │
│  Vehículo* [Autocomplete filtrado por transportista]   │
│  Agente [Autocomplete opcional]                        │
│  Tipo* [Select]  Modalidad* [Select]  Cond. neg. [Sel] │
│  Nro. despacho imp. [text]                             │
│  Fecha inicio traslado*  [date]  Fecha fin* [date]     │
├─────────────────────────────────────────────────────────┤
│  Paper ③ DIRECCIONES  (Grid 2 columnas: Salida|Entrega)│
│  Depto* [Autocomplete]  Distrito* [cascada]  Ciudad*   │
│  Dirección* [text]  Nro Casa [n]  Complemento [text]   │
│  Teléfono [text]                                       │
├─────────────────────────────────────────────────────────┤
│  Paper ④ MERCADERÍAS                                   │
│  [Autocomplete producto + cant + precio → Agregar]     │
│  Tabla: Cód | Descripción | U.M. | Cant | Precio | ✕  │
│  Info adicional [textarea]                             │
├─────────────────────────────────────────────────────────┤
│  InlineValidationBanner (si hay errores)               │
│                 [Cancelar]  [Guardar]  [Procesar]      │
└─────────────────────────────────────────────────────────┘
```

---

## Fases de implementación

### Fase 1 — Migraciones y entidades base ✅ COMPLETA

- [x] Migración: `transportistas`, `choferes`, `vehiculos`, `agentes_transporte`
- [x] Migración: `nota_remision_cab`, `nota_remision_det` (tablas legacy existentes — se añadieron columnas v2 con ALTER TABLE)
- [x] Migración: `nota_remision_factura`, `nota_remision_det_aplicacion`
- [x] Migración: columna `cantidad_aplicada_remision` en `factura_det`
- [x] Módulo `transportistas` — CRUD completo + DTO + search
- [x] Módulo `choferes` — CRUD completo + DTO + search
- [x] Módulo `vehiculos` — CRUD completo + DTO + `findByTransportista()`
- [x] Módulo `agentes-transporte` — CRUD completo + DTO + search
- [x] Agregar módulo `REMISION` en seed (tier Profesional+)
- [x] Bug fix: include de Prisma usa `tipo_documento_identidad` y `naturaleza_receptor` (no `tipo_documento`/`naturaleza`)

**Valor:** entidades disponibles para usar desde el formulario ✓

---

### Fase 2 — Backend nota de remisión ✅ COMPLETA (núcleo + SIFEN + KUDE)

- [x] `NotaRemisionService.create()` — SELECT FOR UPDATE en numeración, validaciones, guardado, estado Pendiente
- [x] `NotaRemisionService.findAll()` — con filtros (estado, fecha, cliente, numero) y paginación
- [x] `NotaRemisionService.findOne()` — detalle completo con ítems
- [x] `NotaRemisionService.anular()` — revierte `cantidad_aplicada_remision` en `factura_det` + estado Anulado
- [x] `NotaRemisionService.updateSifen()` — raw SQL update de campos SIFEN
- [x] `NotaRemisionService.enviarSifen()` — payload iTiDE=7 y envío al middleware SIFEN
- [x] `NotaRemisionSifenPayloadService.buildPayload()` — construye JSON con todos los campos SIFEN + Detalles + DocumentosAsociados
- [x] `NotaRemisionPayloadModule` — módulo compartido para exportar `NotaRemisionSifenPayloadService` a otros módulos (KUDE)
- [x] `KudeService.generateKudeNotaRemision()` — invoca msv-kude con `tipo_documento: 7`
- [x] `KudeController` — case `7` en `switch (dto.tipo_documento)`
- [x] Bug fix: `tx.empresas_puntos_expedicion` (no `tx.punto_expediciones`)
- [x] Bug fix: comparaciones `itipdocaso === '1'` (string, no number)
- [ ] `NotaRemisionService.update()` — edición solo en estado Pendiente/Rechazado
- [ ] `NotaRemisionService.getDisponibilidad()` — cantidad disponible por ítem
- [ ] `NotaRemisionService.desdeFactura()` — pre-carga datos desde factura existente
- [ ] `NotaRemisionService.consultarSifen()` — mismo patrón polling que factura
- [ ] Lógica de vinculación al crear: `nota_remision_factura` + `nota_remision_det_aplicacion`
- [ ] Al crear factura desde remisión: validar cantidades disponibles + crear aplicaciones

**Valor:** API de creación/listado/anulación/envío SIFEN/KUDE operativa. Falta vinculación bidireccional con facturas y update.

---

### Fase 3 — ABM Contactos (frontend) ✅ COMPLETA

- [x] `PersonaFormFields.jsx` — componente compartido (naturaleza, tipo doc, ruc/doc, datos de contacto)
- [x] `TransportistaFormDialog.jsx` + `TransportistasTab.jsx` — CRUD completo con tabla
- [x] `ChoferFormDialog.jsx` + `ChofereTab.jsx` — ídem con licencia y vencimiento (chip rojo si expirada)
- [x] `VehiculoFormDialog.jsx` + `VehiculosTab.jsx` — sin persona, FK transportista
- [x] `AgenteTransporteFormDialog.jsx` + `AgentesTransporteTab.jsx` — ídem transportista
- [x] Integrado en `Contactos.jsx` — tabs 2 (Transportistas), 3 (Choferes), 4 (Vehículos), 5 (Agentes)
- [x] Bug fix: edit mode usa `p?.naturaleza_receptor?.id` y `p?.tipo_documento_identidad?.id`
- [x] Bug fix: `ClienteFormDialog` — infinite loop por arrays `|| []` en deps de useEffect

**Valor:** usuario puede gestionar transportistas, choferes, vehículos y agentes ✓

---

### Fase 4 — Formulario nota de remisión (frontend) 🔄 EN PROGRESO

#### Funcionalidad implementada (con deuda técnica de estándar)

- [x] `RemisionesTab.jsx` — listado con búsqueda, estados chips ⚠ (ver deuda abajo)
  - [x] Columna Acciones alineada al patrón de facturas: **Ver detalle (eye) + Ver KUDE (PDF) + Eventos SIFEN (warning, condicional)** — **sin** botón de anular en la fila (se anula desde la vista detalle)
  - [x] Menú Eventos SIFEN con gating por permisos (`VEN_REMISION_SIFEN_CANCELAR`, `VEN_REMISION_SIFEN_INUTILIZAR`, `VEN_REMISION_SIFEN_ENVIAR`) y por `sifenActivo`
  - [x] Validación ECAN: helper `esCancelableECAN` de **168h/7 días** (excepción SIFEN para remisión vs 48h para factura/NC) — tooltip "Han pasado más de 7 días desde el envío" cuando aplica
  - [x] `KudePreviewModal` integrado con `tipoDocumento={7}` para vista previa del PDF desde la fila
- [x] `NuevaRemisionTemplate.jsx` — formulario funcional con styled-components custom ⚠ (ver deuda abajo)
  - [x] Sección Datos del Documento — sucursal, numeración (tipo 7), fecha/hora, cliente search
  - [x] Sección Datos de Transporte — transportista search, chofer, vehículo (filtrado por transportista), agente search, motivo, tipo, modalidad, km
  - [x] Sección Período de Traslado — fechas inicio y fin
  - [x] Sección Direcciones — salida y entrega (texto libre por ahora)
  - [x] Sección Mercaderías — búsqueda de producto, tabla editable con descripción/unidad/cantidad/precio
  - [x] Sección Información Adicional — textarea
  - [x] Ruta `/ventas/remisiones/nueva` con guard `REMISION`
  - [x] Tab Remisiones en `Ventas.jsx`
  - [x] **Auto-abrir `KudePreviewModal` tras guardar** (mismo UX que facturas): al recibir `id` del backend abre el preview con `tipoDocumento={7}`; navega a `/ventas` al cerrar
- [x] **msv-kude — template de remisión rediseñado** (`kude_remision.js`)
  - [x] Nueva signatura `createPDF(documento, empresa)` (paridad con `kude_nota_credito.js`)
  - [x] `parseDocumento(documento, empresa)` — mapea el payload JSON de `NotaRemisionSifenPayloadService.buildPayload()` a la estructura interna del template
  - [x] Timbrado + inicio de vigencia desde `empresa.num_timbrado` / `empresa.fecha_inicio_timbrado` (inyectados en `routes/kude/index.js`)
  - [x] Logo vía `utils.getLogoFromUrl(empresa.logo)` con fallback a `getLogoData`
  - [x] Override `empresa.actividad_economica_kude` para display del PDF
  - [x] Mapeos internos para códigos SIFEN (`iMotEmiNR`, `iRespEmiNR`, `iTipTrans`, `iModTrans`, `cCondNeg`) → descripciones legibles
  - [x] Fallback URL SET si `link_documento` es null (evita crash del QR generator)
  - [x] Route `msv-kude/src/routes/kude/index.js` case 7 pasa `empresa` como segundo argumento

#### Funcionalidad pendiente

- [ ] `SeccionDocAsociado` — radio ○ Sin documento / ○ Electrónico (CDC) / ○ Impreso (timbrado+nro+fecha). Ya existe lógica parcial en el form, falta completar el flujo impreso
- [ ] Cascada Departamento → Distrito → Ciudad en Direcciones (actualmente texto libre sin validación SIFEN)
- [ ] `RemisionDetalleDialog.jsx` — vista detalle con ítems, estado SIFEN, facturas vinculadas, chips de disponibilidad (`X disponible de Y`) y **botón anular** (ya no está en la fila)
- [ ] Botón "Generar remisión" desde el listado/detalle de facturas (`RemisionesTab` como origen)
- [ ] Queries en `RemisionesStack.jsx` (extraer de componente)
- [ ] Backend: `nota_remision_cab.enlace_qr` no se está persistiendo tras aprobación SIFEN — hoy el KUDE muestra fallback SET. Verificar y persistir el link real que devuelve SIFEN.

#### Deuda técnica de estándar UI (bloquea PR final)

- [x] ~~**`RemisionesTab`**: `TablePaginationBar` + paginación server-side~~ ✅ (ya usa page/limit/lastPage)
- [x] ~~**`RemisionesTab`**: `ScreenGuia` colapsable con pasos del flujo~~ ✅ (con ref + scroll desde EmptyState)
- [x] ~~**`RemisionesTab`**: `EmptyState` con CTA "Nueva Remisión"~~ ✅ (dos variantes: sin datos / sin resultados de filtro)
- [x] ~~**`RemisionesTab`**: íconos `@iconify/react`~~ ✅ (nunca usó `@mui/icons-material`)
- [~] **`RemisionesTab`**: tabla MUI manual → `StandardTable` — se dejó la tabla propia por tener columnas configurables (selector de columnas visibles) que StandardTable no cubre; no aporta valor migrar.
- [ ] **`NuevaRemisionTemplate`**: reemplazar styled-components de secciones → `<Paper variant="outlined">` con header `Typography caption fontWeight={700}` (patrón `GastosTemplate.jsx`)
- [ ] **`NuevaRemisionTemplate`**: reemplazar flexbox custom de campos → `<Grid size={{ xs, md }}>` (MUI v7)
- [ ] **`NuevaRemisionTemplate`**: agregar `ScreenGuia` colapsable al inicio
- [ ] **`NuevaRemisionTemplate`**: reemplazar validación por `toast` → `InlineValidationBanner`
- [ ] **`NuevaRemisionTemplate`**: fechas al enviar → `toFechaCalendarioISO()`; al mostrar → `fmtFechaCalendario()`
- [x] ~~**`NuevaRemisionTemplate`**: integrar `KudePreviewModal` para preview del PDF al procesar~~ ✅ hecho
- [ ] **`NuevaRemisionTemplate`**: permisos con `usePermission("VENTAS")` → `VEN_REM_*`

**Valor:** flujo de creación funcional + KUDE end-to-end + preview automático. Faltan doc asociado completo, cascada de direcciones, vista detalle y alineación al estándar UI.

---

### Fase 5 — Integración bidireccional factura ↔ remisión 🔄 EN PROGRESO

- [x] **Facturar desde N remisiones (Remisión → Factura)** — desde el POS-Admin, diálogo "Facturar
      Remisiones" precarga el carrito consolidando ítems de remisiones sin documento asociado del cliente
      (agrupa por `producto_id + descripción`, permite editar antes). Al facturar: **no descuenta stock**
      (Fase 7.1), crea `nota_remision_det_aplicacion` + `nota_remision_factura`, incrementa
      `nota_remision_det.cantidad_facturada`. **Facturación parcial** soportada (redistribución FIFO). Anular
      la factura revierte todo (vínculos + cantidad_facturada) sin tocar stock.
  - Backend: migración `cantidad_facturada`; `GET /nota-remision/facturables/:clienteId`;
    `DetalleFacturaDto.aplicaciones_remision`; `facturas.service.ts` (skip-stock + vínculos en `create`,
    reversión en `anularFactura`).
  - Frontend: `getRemisionesFacturables` + hook; `RemisionesPendientesDialog.jsx`; `handleSelectRemisiones`
    en `POSAdminTemplate` + botón en `InvoiceHeader`; helper `utils/remisionFacturacion.js` (consolidar + FIFO).
- [x] En remisión: ver facturas vinculadas (`RemisionDetalleDialog`) — hecho en fase previa.
- [x] En factura: ver remisiones vinculadas + "remisionado X/Y" por ítem (`FacturaDetalleDialog`) — hecho.
- [ ] Chips `X disponible de Y` por ítem en la vista detalle de remisión (mostrar `cantidad_facturada`).
- [ ] (Opcional) Precarga análoga en `POSRetailTemplate` (hoy solo POS-Admin; retail es contado sin
      documentos-origen).

**Valor:** trazabilidad completa remisión ↔ factura con cantidades, en ambos sentidos.

---

### Fase 6 — Envío SIFEN 🔄 EN PROGRESO

- [x] Botón "Enviar a SIFEN" en `RemisionesTab` (menú de eventos SIFEN con permission gating)
- [x] `NotaRemisionService.enviarSifen()` — payload iTiDE=7 y envío al middleware
- [x] `NotaRemisionSifenPayloadService.buildPayload()` — construye el JSON completo
- [x] Evento ECAN (cancelación) — con ventana de **168h/7 días** (excepción para remisión)
- [x] Evento INUT (inutilización) — con permission `VEN_REMISION_SIFEN_INUTILIZAR`
- [x] KUDE del documento aprobado — `KudeService.generateKudeNotaRemision` + template `msv-kude/kude_remision.js`
- [ ] `NotaRemisionService.consultarSifen()` — polling estado igual que factura
- [ ] Verificar que `enlace_qr` se persiste desde la respuesta SIFEN (hoy null en algunos casos)
- [ ] Botón "Consultar SIFEN" para reintentos manuales de sincronización

**Valor:** documento electrónico válido ante la SET, con KUDE y eventos SIFEN operativos.

---

### Fase 7 — Integración de inventario 🔄 EN PROGRESO

Basado en la decisión revisada de **Stock e inventario** (ver arriba). Objetivo: que la
remisión mueva stock de forma consistente con factura, sin doble descuento.

**Fase 7.2 — Stock en remisión sin factura ✅ COMPLETA**
- [x] Tipo de movimiento `SALIDA_REMISION` en `tipo_movimiento_inventario` (find-or-create
      en `NotaRemisionService.tipoMovRemision`, no requiere seed).
- [x] Remisión **sin** factura asociada (`validFacturasIds.length === 0`) → descuenta stock
      **al crear** (permite negativo, `documento_origen: 'nota_remision'`), solo productos
      con `maneja_inventario` (`NotaRemisionService.descontarStockRemision`).
- [x] **Selección de depósito** (`cabecera.deposito_id`): el frontend muestra un selector
      "Depósito (descuenta stock)" cuando la remisión moverá stock (sin factura electrónica),
      con default al **principal ★** de la sucursal. El backend valida el depósito
      (empresa+activo) y cae al principal si no vino o es inválido — igual que factura.
- [x] Respeta la **política de stock** de la sucursal (`empresas_sucursales.config.politica_stock`):
      `estricto` bloquea la remisión si `disponible < cantidad` (throw → rollback); `advertencia`/`libre`
      permiten negativo. Mismo criterio que factura.
- [x] Solo afecta productos con `maneja_inventario = true` (los demás se saltan).
- [x] Remisión **con** factura asociada → **no toca stock** (la factura ya descontó).
- [x] Anulación de remisión → **repone** stock (movimiento inverso `ENTRADA_REMISION_ANULADA`,
      `NotaRemisionService.reponerStockRemision`) además de revertir `cantidad_aplicada_remision`.
- [x] Rechazo SIFEN → **no** revierte stock (solo la anulación lo hace).

**Auditoría (`AuditService`) — detallada en todas las operaciones:** ✅
- `CREATE nota_remision`: número, cliente/transportista/chofer/vehículo/agente, motivo, transporte,
  km, direcciones, ítems, documento asociado, si afecta stock y el depósito usado, usuario.
- `ANULAR nota_remision`: estado anterior + cantidad de ítems con stock repuesto.
- `nota_remision_sifen_envio`: resultado [OK]/[ERROR] del envío al middleware (lote / mensaje).
- `nota_remision_sifen_sync`: transición de estado SIFEN (Aprobado/Rechazado) al sincronizar.
- Todas registran `user_id` (el controller pasa `user.id`); el `AuditService` nunca corta el flujo
  (falla en silencio).

**Fase 7.1 — Blindaje anti-doble-descuento en factura desde remisión ✅ COMPLETA**
- [x] Al facturar desde remisiones (ítems con `aplicaciones_remision`), `facturas.service.ts` `create`
      detecta `desdeRemisiones` y **salta el descuento de stock** (tanto la deducción FIFO/lotes como el
      bloque `stock_deposito`/`movimientos_inventario`), conservando la creación de `factura_det`. La
      mercadería ya salió por la remisión → sin doble descuento.
- [x] `anularFactura` no repone stock para facturas creadas desde remisión (nunca lo descontaron).

**Fase 7.3 — (Opcional/enterprise) Mercadería en tránsito** ⏳
- [ ] Depósito/cuenta virtual "Mercadería en tránsito / pendiente de facturar".
- [ ] Asiento de reclasificación `Inventario → En tránsito` en la remisión sin factura;
      reconocimiento de ingreso + COGS al facturar, limpiando el tránsito.

**Valor:** inventario refleja la realidad física del traslado, sin duplicar egresos ni
descuadrar cuando remisión y factura conviven. Contabilidad de venta permanece en la factura.

---

## Bugs corregidos (historial)

| Área | Bug | Fix |
|---|---|---|
| Backend - todos los servicios de transporte | `Unknown field tipo_documento` en Prisma include | Renombrar a `tipo_documento_identidad` y `naturaleza_receptor` |
| Backend - nota-remision service | `tx.punto_expediciones` no existe | Usar `tx.empresas_puntos_expedicion` |
| Backend - nota-remision service | `itipdocaso === 1` type mismatch | Cambiar a `=== '1'` (string desde DTO) |
| Frontend - ClienteFormDialog | Maximum update depth exceeded (infinite loop) | Reemplazar arrays `|| []` en deps de useEffect con `referenciales` (ref estable) |
| Frontend - TransportistaFormDialog, ChoferFormDialog, AgenteTransporteFormDialog | Edit mode no cargaba naturaleza/tipo_doc | Usar `p?.naturaleza_receptor?.id` y `p?.tipo_documento_identidad?.id` |
| Frontend - NuevaRemisionTemplate | Inputs con altura inconsistente | CSS variable `CTRL_H = 38px` aplicada a `input`, `select`, `SearchInputWrap`, `SelectedValue` |
| Frontend - RemisionesTab | `window.confirm()` nativo para anular | Migrar a MUI Dialog — luego eliminado por completo: el botón de anular ya no está en la fila, se hace desde la vista detalle (patrón facturas) |
| Frontend - Fac/NC/Rem Tabs | ECAN aplicando 168h a todos los tipos | Fix: solo remisión = 168h/7 días; factura y NC = 48h. Helper `esCancelableECAN` con umbral por tipo + texto del tooltip |
| msv-kude - kude_remision.js | Sólo aceptaba XML (`createPDF(xmlString)`) | Reescrito para `(documento, empresa)` + `parseDocumento()` que consume el JSON de `NotaRemisionSifenPayloadService.buildPayload()` (paridad con `kude_nota_credito.js`) |
| msv-kude - kude_remision.js | Crash `qrcode: No input text` cuando `link_documento` es null | Fallback a URL SET (`https://ekuatia.set.gov.py/consultas/`) |
| msv-kude - routes/kude/index.js | Case 7 llamaba `kude.remision.createPDF(documento)` sin empresa | Ahora pasa `(documento, empresa)` para acceso a timbrado + logo + actividad económica |
| Backend - kude module | KudeService no podía construir payload de remisión | Nuevo `NotaRemisionPayloadModule` que exporta `NotaRemisionSifenPayloadService`; importado en `KudeModule` |

---

## Payload SIFEN (iTiDE = 7)

Campos clave del JSON que se envía al middleware:

```typescript
{
  iTiDE: 7,
  dInfoFisc: "según el Art. 3 Inc. 7 de la Resolución general Nro. 41/2014",
  dEst: establecimiento,
  dPunExp: punto_expedicion,
  dNumDoc: nro_comprobante,
  dSerieNum: "AA",
  dFeEmiDE: fecha_emision,                    // ISO 8601

  // Receptor
  iNatRec, iTiOpe, cPaisRec, iTiContRec,
  dRucRec, dDVRec, dNomRec, dDirRec,
  cDepRec, cDisRec, cCiuRec, dTelRec, dEmailRec,

  // Remisión
  iMotEmiNR: motivo_emision,                  // 1-7 o 99
  iRespEmiNR: tipo_responsable,               // 1 o 2
  dKmR: kilometros,
  iTipTrans: tipo_transporte,                 // 1-4
  iModTrans: modalidad_transporte,            // 1-4
  cCondNeg: condicion_negociacion,            // Incoterm o null
  dNuDespImp: nro_despacho_importacion,
  iRespFlete: tipo_responsable,
  dIniTras: fecha_inicio_traslado,            // YYYY-MM-DD
  dFinTras: fecha_fin_traslado,

  // Dirección salida
  dDirLocSal, dComp1Sal, dNumCasSal, cDepSal, cDisSal, cCiuSal, dTelSal,

  // Dirección entrega
  dDirLocEnt, dComp1Ent, dNumCasEnt, cDepEnt, cDisEnt, cCiuEnt, dTelEnt,

  // Vehículo
  dMarVeh, dTipIdenVeh, dNroIDVeh, dNroMatVeh, dAdicVeh,

  // Transportista
  iNatTrans, dNomTrans, dRucTrans, dDVTrans, iTipIDTrans, dNumIDTrans, dDomFisc,

  // Chofer
  dNomChof, dNumIDChof, dDirChof,

  // Agente (opcional)
  dNombAg, dRucAg, dDVAg, dDirAge,

  // Fecha futura (cuando sin doc asociado)
  dFecEm: fecha_futura_emision,

  // Info adicional
  dInfAdic: info_adicional,

  // Detalle
  Detalles: items.map(item => ({
    dCodInt: producto.codigo_referencial,
    dDesProSer: item.descripcion,
    cUniMed: item.unidad_medida,
    dCantProSer: item.cantidad,
  })),

  // Documentos asociados (0 o más)
  DocumentosAsociados: [{
    iTipDocAso,    // 1=electrónico, 2=impreso
    dCdCDERef,     // CDC si electrónico
    dNTimDI, dEstDocAso, dPExpDocAso, dNumDocAso, dFecEmiDI  // si impreso
  }]
}
```

---

## Consideraciones de seguridad y validación

1. **Cantidades**: siempre validar `cantidad_a_aplicar <= cantidad_disponible` antes de guardar
2. **Anulación en cascada**: al anular remisión, revertir todas las aplicaciones antes de cambiar estado
3. **Multi-tenant**: todos los endpoints filtran por `empresa_id` del usuario autenticado
4. **Estado**: solo se puede editar en estado `Pendiente` o `Rechazado`
5. **SIFEN aprobado**: no se puede editar ni anular directamente — requiere evento de anulación SIFEN
6. **Módulo guard**: `@RequireModule('FACTURACION')` en todos los endpoints de nota-remision

---

## Próximos pasos recomendados (orden sugerido)

El trabajo pendiente se divide en dos tracks paralelos. Track A (funcionalidad) desbloquea Fase 5 (vinculación factura↔remisión); Track B (estándar UI) debe completarse antes del PR final de la Fase 4.

**Estado actual (2026-07-16)**: núcleo backend + KUDE + SIFEN + preview automático operativos. El bloqueador de Track A ahora es la vista detalle y la vinculación con facturas; el resto es refinamiento de UI y datos.

### Track A — Funcionalidad pendiente (Fase 4 + 5)

| Prioridad | Ítem | Referencia |
|-----------|------|------------|
| 1 | `RemisionDetalleDialog` — vista detalle + estado SIFEN + chips disponibilidad + **botón anular** (ya no en la fila) | Similar a `NotaCreditoDetalleDialog` |
| 2 | Cascada Departamento → Distrito → Ciudad en NuevaRemisionTemplate | Mismo helper que `ClienteDireccionesModal` |
| 3 | SeccionDocAsociado completa (radio ninguno/electrónico/impreso + campos condicionales) | Ya hay lógica parcial en el form |
| 4 | Fase 5 — vinculación bidireccional factura ↔ remisión (aplicaciones + disponibilidad + "crear desde") | Sección Fase 5 |
| 5 | `NotaRemisionService.update()` — edición solo en Pendiente/Rechazado | Backend Fase 2 pendiente |
| 6 | Backend — persistir `enlace_qr` desde la respuesta SIFEN | Actualmente KUDE usa fallback SET |
| 7 | `NotaRemisionService.consultarSifen()` — polling estado | Mismo patrón factura |

### Track B — Deuda técnica de estándar UI (Fase 4 bloqueo PR)

Orden recomendado para minimizar re-trabajo:

1. **`NuevaRemisionTemplate` → refactor estructura** (Paper sections + Grid MUI v7) — hacerlo primero porque los ítems de funcionalidad de Track A se implementan ya con el estándar correcto
2. **`NuevaRemisionTemplate` → ScreenGuia + InlineValidationBanner** — agregar después del refactor estructural
3. **`NuevaRemisionTemplate` → fechas con utils/fecha.js** — revisar todos los `dfeemide`, `fecha_inicio_traslado`, `fecha_fin_traslado`
4. **`RemisionesTab` → paginación server-side + TablePaginationBar** — reemplazar limit fijo
5. **`RemisionesTab` → StandardTable + EmptyState + ScreenGuia** — completar listado

### Referencia de permisos

Definir en seed/módulo `REMISION` (ya existe) los permisos:

```
VEN_REM_VER        — ver listado y detalle
VEN_REM_CREAR      — crear nueva remisión
VEN_REM_EDITAR     — editar en estado Pendiente/Rechazado
VEN_REM_ANULAR     — anular
VEN_REM_SIFEN      — enviar/consultar SIFEN
```

Usar `usePermission("VENTAS").can("VEN_REM_*")` en el frontend para gatear CTAs.
