# WMS — Próximos pasos (2026-05-16)

> Plan de la sesión post-rescate. Cada item es una sub-rama `feature/wms-{nombre}` mergeada `--no-ff` a `feature/wms-refactor`. La validación en navegador queda al final como S-7 (responsabilidad del usuario).

## Estado (cerrado 2026-05-16)

Las sesiones S-1 a S-6 se cerraron en una sola pasada. Solo queda **S-7 validación en navegador** (responsabilidad del usuario).

| S | Tema | Estado |
|---|---|---|
| S-1 | Limpieza docs obsoletos + plan PROXIMOS_PASOS | ✅ |
| S-2 | Seeder ampliado WITH_BODEGA + WITH_EMPAQUES | ✅ |
| S-3 | Validación cruzada lote (ID específica) | ✅ |
| S-4 | Subdivisiones nivel desde UI | ✅ |
| S-5 | Drag-drop artículos al editor 2D | ✅ |
| S-6 | Reporte costo contable vs físico | ✅ |
| S-7 | Validación en navegador | 🔄 pendiente (user) |

## Sesiones

### S-1 ✅ Limpieza de documentación obsoleta (esta sesión)

- Eliminar `docs/wms/PLAN_REFACTOR.md` (todo su contenido está cerrado y reflejado en `PLAN_EJECUCION.md`).
- Eliminar `docs/wms/GUIA_PRUEBAS.md` (referencia las fases 1-10 originales borradas en el reset; no se actualizó al refactor ni al rescate).
- Reescribir `docs/wms/README.md` para que apunte a este doc como punto de entrada.
- Compactar `PLAN_EJECUCION.md`:
  - Quitar duplicado "Refactor — Bloque C" en tabla "Estado actual".
  - Comprimir sección "Iteración 2 — Modelo espacial (pausada)" a un párrafo (todo cancelado).
  - Apéndice A (estructura): regenerar a partir del estado actual del repo; sacar `LayoutTopDown`, `ZonaPolygon`, `RackShape`, `RackForm`, `ToolbarLayout`, `GridBackground`, `useWmsCanvas`, etc. (borrados en bloque B-2).
  - Apéndice C (comandos): sacar `wms:migrate-ubicaciones` (borrado en bloque A); agregar `wms:verificar-integracion` (R-3) y `db:seed --class=PosArticulosWmsDemoSeeder`.

### S-2 Seeder ampliado con flags

Extender `PosArticulosWmsDemoSeeder` con dos flags (env vars o comando wrapper):
- `WITH_BODEGA=1`: además crea `pos_bodega_articulos` (pivote) para todas las bodegas activas.
- `WITH_EMPAQUES=1`: además crea `wms_articulo_empaques` tipo `primario` con dimensiones razonables por SKU (los SKUs perecibles WMS-A001/A002 reciben mayor `peso_bruto_kg`, los ferretería WMS-F00x mayores dimensiones, etc.).

Salida del seeder reporta qué se creó por etapa. Idempotente en las 3 capas.

### S-3 Validación cruzada de lote para método contable "ID específica" (PENDIENTES 1.2)

En `WmsTareaController::completarLinea`:
- Si la empresa tiene `par_empresas.metodo_inventario_id = 4` (ID específica) y la tarea es `pick_list`:
  - Validar que el `lote` recibido en el payload (o el `lote` de la línea) coincida con el `wms_ubicacion_stocks.lote` de la ubicación destino antes de descontar.
  - Si no coincide, retornar HTTP 422 con mensaje "Lote físico no coincide con lote contable. Esperado: X, recibido: Y."
- Tests vía tinker: caso OK + caso fallo.

### S-4 Subdivisiones de nivel desde la UI (PENDIENTES 1.5)

Backend:
- Endpoint `POST /wmsRackNiveles/{id}/subdividir` que recibe `divisiones` y `subdivisiones_por_division` (array). Regenera `wms_ubicaciones` y `wms_rack_nivel_subdivisiones` del nivel.
- Validar que no haya stock activo en las ubicaciones del nivel (igual que `WmsRackController::update`).

Frontend:
- En la modal "Niveles" de `/pos/wms/racks/index.vue` agregar columna "Subdividir" con botón que abre sub-modal:
  - Selector cantidad de divisiones (1-10).
  - Por cada división, selector cantidad de subdivisiones (1-5).
  - Confirmar → llama el endpoint y recarga niveles.

### S-5 Drag-drop de artículos al editor 2D (operacional)

- Montar `PanelArticulosDisponibles` en `/pos/wms/layout/{bodegaId}` como panel colapsable (toggle desde toolbar) en columna izquierda.
- Capa "drop overlay" sobre el canvas Konva que detecta drop sobre racks.
- Al dropear sobre un rack, reutilizar el modal de asignación de la vista lateral (refactorizable a composable `useAsignarStockModal`).
- Para no duplicar lógica: extraer modal + sugerencia + POST a composable `useAsignarStock`.

### S-6 Reporte costo contable vs físico (PENDIENTES 1.4) — **Pendiente OK del approach**

Ver sección "Approach propuesto" abajo.

### S-7 Validación en navegador (responsabilidad del user)

Checklist mínima para cerrar el ciclo:
- [ ] Crear empresa + bodega + layout activo si no existen.
- [ ] Correr seeder con `WITH_BODEGA=1 WITH_EMPAQUES=1` (S-2).
- [ ] Editor 2D: dibujar 1 habitación + 2 racks + 1 zona almacenamiento.
- [ ] Verificar generación auto de niveles + ubicaciones en racks.
- [ ] Vista lateral del rack: drag-drop un artículo, ver sugerencia, asignar, verificar stock.
- [ ] Eliminar stock desde la tabla.
- [ ] Tareas: crear venta de un artículo `WMS-*` → ver tarea pendiente generada por el observer → completar con escáner QR.
- [ ] Conteo cíclico: crear conteo, contar con delta != 0, verificar movimiento ajuste creado.
- [ ] Reportes: ocupación/rotación/vencimientos cargan datos.
- [ ] PUT snapshot (R-9): si el editor lo expone, probar guardado masivo.
- [ ] Corre `wms:verificar-integracion` con `--simular=venta` y ver tarea creada.

---

## Approach — S-6 Reporte costo contable vs físico

### En palabras simples

Imaginate que tenés un artículo, digamos una caja de leche, y dos personas distintas anotan cuánto cuesta:

- El **contador** (POS) lleva un libro donde va calculando el "costo promedio" de toda la leche que entró y salió. Para él, esa caja de leche **vale $1.000**.
- El **bodeguero** (WMS) tiene en sus estantes 3 cajas de leche: una que compró a $900, otra a $1.000 y otra a $1.100. Si las promedia, cada caja **vale $1.000 también**. Todo bien, coinciden.

Pero ahora pasa lo siguiente: el bodeguero descubre que en realidad había una caja extra olvidada que costó $500, y la agrega al sistema con un ajuste manual. Ahora él tiene 4 cajas y el promedio le da $875 por caja. **El contador no se enteró**, sigue diciendo $1.000.

**Ese es el problema que detecta este reporte**: cuando lo que dice el balance ($1.000) y lo que dice la realidad física ($875) se alejan, las cuentas mienten. El reporte:

1. Por cada artículo y cada bodega, mira los dos números.
2. Calcula cuánto se alejan (en %, y en plata total).
3. Lista lo que se aleja más de cierto umbral (por defecto 1%) para que vos investigues por qué.

No "arregla" nada por sí solo — solo te dice **"acá hay algo raro, fijate"**. Si querés corregir, lo hacés con ajustes manuales o tareas, después de entender la causa.

### Cuándo se va a desviar (causas típicas)

- Ajustes manuales del bodeguero sin tocar el costo del POS.
- Compras a precios distintos del PMP esperado (un pedido urgente caro, una promo barata).
- Conteos cíclicos que aparecen unidades de más o de menos.
- Errores de carga (alguien tipeó mal un costo).

### Qué te muestra la pantalla

| Columna | Significa |
|---|---|
| Costo POS | Lo que dice el contador (`pos_bodega_articulos.c_unitario`). |
| Costo WMS pond. | Promedio ponderado de lo que hay realmente en las ubicaciones físicas. |
| Δ costo abs | Resta directa entre los dos. Positivo = el balance está más caro que la realidad. |
| Δ costo % | Mismo, pero como porcentaje del costo POS. |
| Δ valor | Es la plata total que sobra o falta en el balance contable: `(stock_pos × costo_pos) − (stock_wms × costo_wms)`. **Este es el número que más importa**. |

Las filas se pintan **amarillo** si la diferencia supera $1.000 CLP y **rojo** si supera $10.000 CLP. El reporte arranca filtrando todo lo que tenga menos de 1% de diferencia para que no haga ruido — si querés ver hasta el último centavo, bajás el umbral a 0.

### Cómo está implementado (técnico)

**Endpoint**: `GET /wmsReportes/costoContableVsFisico`

Parámetros:
- `pos_bodega_id` — opcional, filtra a una bodega.
- `umbral_pct` — opcional, default `1.0`. Solo lista artículos con `|delta_pct| >= umbral`. Si pasás `0` lista todo.

**Query**: subquery sobre `wms_ubicacion_stocks` agrupada por `(pos_articulo_id, pos_bodega_id)` con:
- `SUM(stock_unidades)` → `stock_wms`
- `SUM(stock × costo) / SUM(stock)` → `costo_fisico_ponderado`

Después `JOIN pos_bodega_articulos` para traer `stock_pos` y `c_unitario` (= `costo_pos`), más `pos_articulos` y `pos_bodegas` para nombres legibles. Filtro multi-tenant por `par_empresa_id` del usuario.

**Cálculos en PHP** (no en SQL para no inflar la query):
```
delta_abs   = costo_pos - costo_fisico_ponderado
delta_pct   = (delta_abs / costo_pos) * 100   // null si costo_pos = 0
delta_valor = (stock_pos × costo_pos) - (stock_wms × costo_fisico_ponderado)
```

Después se filtra por `umbral_pct` y se ordena por `|delta_valor|` descendente — los problemas más caros aparecen primero.

**Totales agregados** en la respuesta: cantidad de artículos divergentes, cuántos son "sobrante contable" (POS dice más), cuántos "faltante contable" (POS dice menos), y la suma absoluta de `|delta_valor|` (cuánta plata total está en juego).

**Frontend**: `pages/pos/wms/reportes/costo-contable-vs-fisico.vue` con 3 cards de stats arriba + tabla con filas coloreadas por `|delta_valor|` + export Excel via `exportCostoContableVsFisico` que reutiliza el patrón de los otros 3 reportes.

### Lo que NO hace este reporte (decisiones)

- **No corrige nada automáticamente.** Si quisiéramos un botón "ajustar costo POS al valor WMS", sería destructivo (afecta el balance contable). Decisión: que el ajuste sea siempre manual y consciente.
- **No detalle por lote.** Si querés ver qué `wms_ubicacion_stocks.costo_unitario` específicos contribuyen al promedio ponderado, hay que ir a la vista lateral del rack. Una v2 podría agregar drill-down al click en una fila.
- **No histórico.** Es un reporte "vivo" del estado actual; no guarda snapshots diarios. Para tendencia temporal habría que agregar tabla `wms_costo_snapshot_diario` + job cron — sale fácil pero excede el alcance.

### Por qué se eligió este approach

- **Reutiliza datos existentes**: `pos_bodega_articulos.c_unitario` ya lo mantiene `InventoryCostService` según el método contable de la empresa (FIFO/LIFO/PMP/ID específica). No hay que recalcular nada.
- **Cero migraciones nuevas**: una sola query con un subselect y join — performance OK para catálogos de hasta 10k artículos por bodega.
- **UI consistente**: misma estructura que los otros 3 reportes (ocupación/rotación/vencimientos) → la persona que ya conoce esos 3 entiende este al instante.
- **Coexiste con futuro snapshot**: si más tarde se agrega histórico diario, este reporte queda como el "vivo" y el otro como el "tendencia". No se pisan.

---

## Orden de ejecución

```
S-1 (limpieza docs)  →  hecho en esta misma rama
S-2 (seeder ampliado)
S-3 (validación lote ID específica)
S-4 (subdivisiones UI)
S-5 (drag-drop editor 2D)
S-6 (reporte costo, tras OK del approach)
S-7 (validación en navegador — user)
```

Sin push, sin merge a `feature/wms` ni a develop/main hasta cerrar S-7.
