SOP — Módulo Malla de Patio (Posiciones Físicas)
Tipo: procedimiento operativo estándar. Ruta:
/malla. Estado: demo-ready. Gate D-011: datos de geometría son reales (modelo en código); contenedores mostrados en demo son ILUSTRATIVOS (datos ficticios del seed). Ver §8 para detalles.
0. Propósito y alcance
Este SOP describe cómo usar el módulo Malla de Patio Abierto para:
- Consultar el estado visual de la rejilla de posiciones físicas del patio (bloque · fila · nivel).
- Verificar ocupación, capacidad y posición de un contenedor específico.
- Asignar o reubicar un contenedor a una posición dentro de la malla.
- Identificar posiciones bloqueadas o en estado especial.
Fuera de alcance: registro de ingreso de contenedores (ver SOP de Garita), emisión de documentos fiscales/aduanales, y administración de catálogos.
1. Roles y permisos
| Rol | Permiso requerido | Capacidad en malla |
|---|---|---|
admin | patio:ver + modulacion:operar | Ver Y asignar/reubicar |
supervisor | patio:ver + modulacion:operar | Ver Y asignar/reubicar |
operador_patio | patio:ver + modulacion:operar | Ver Y asignar/reubicar |
operador_garita | patio:ver | Solo lectura |
cliente_readonly | patio:ver | Solo lectura |
Regla: todo rol con patio:ver DEBE poder acceder a /malla en modo consulta.
Solo los roles con modulacion:operar DEBEN poder colocar o mover contenedores.
Fuente:
lib/auth/roles.ts—PERMISOS_POR_ROL. ⚠️ Matriz PROPUESTA; confirmar con CEO antes de cablearse a operaciones reales.
2. Precondiciones
- El usuario DEBE tener una sesión activa y un rol asignado (ver selector de rol en demo; Supabase Auth en prod).
- El tenant DEBE estar identificado por columna
rfc(multi-tenant a nivel de app, sin RLS). - La aplicación DEBE estar corriendo y el módulo
/mallaaccesible (smoke test: ver §7). - En producción: DEBE existir una geometría de malla configurada en la base de datos
(
MALLA_DEMOo la que el operador defina). En demo: la geometría en memoria es A·B·C × filas 1-4 × niveles 1-3 = 36 slots (ILUSTRATIVO).
3. Procedimiento — Consulta de malla (lectura)
- Navegar a
/malladentro de la aplicación. - Verificar los cuatro indicadores en la parte superior:
- Ocupación % — porcentaje de posiciones usadas respecto a la capacidad total.
- Ocupadas — número de slots con contenedor asignado.
- Libres — número de slots disponibles.
- Bloques — etiquetas de bloque activos (ej. A · B · C).
- Seleccionar la vista deseada en
MallaSwitcher:- Vista 2D — rejilla canónica por bloque·fila·nivel; accesible con teclado y lectores de pantalla.
- Vista 3D — visor interactivo (Three.js/R3F); se carga de forma diferida (SSR-safe).
- Vista temporal — scrub de dwell y playback de eventos (Inc.2d, F2).
- Para localizar un contenedor específico: navegar a
/malla?ubicar=<ISO>(ej.?ubicar=CSQU3054383). El slot del contenedor DEBE quedar resaltado y con scroll automático. - Para ver el detalle de un slot ocupado: hacer clic sobre él en la vista 2D (enlace navegable) o seleccionarlo en vista 3D. Se abre el drawer del contenedor.
- Para ver el corte por bahía: hacer clic en el label de fila en la vista 2D (cuando
onFilaClickestá habilitado). ApareceBahiaCortePopupcon detalle de la fila.
4. Procedimiento — Asignación de posición (requiere modulacion:operar)
Este flujo aplica cuando un contenedor físicamente en el patio necesita registrar o cambiar su posición en la malla.
- Confirmar que el rol activo tiene
modulacion:operar(admin / supervisor / operador_patio). - Localizar el contenedor a colocar:
- Usar el buscador de inventario o navegar a
/malla?ubicar=<ISO>. - Si el contenedor ya tiene posición asignada, la malla la mostrará resaltada.
- Usar el buscador de inventario o navegar a
- Seleccionar la posición destino en la vista 2D o 3D:
- Un slot LIBRE está disponible para asignar.
- Un slot OCUPADO por otro contenedor NO DEBE seleccionarse como destino.
- Confirmar la asignación. El sistema ejecuta
Malla.asignar(iso)(primera posición libre, orden bloque→fila→nivel) oMalla.ocupar(posId, iso)si se eligió una posición específica. - Verificar que los indicadores en la cabecera se actualizan: "Ocupadas" sube en uno, "Libres" baja en uno.
- Para reubicar un contenedor ya colocado: liberar la posición actual (
Malla.liberar(iso)) y repetir los pasos 3-5.
5. Reglas e invariantes
DEBE cumplirse
- El identificador de posición DEBE seguir el formato
BLOQUE-FF-N(ej.A-01-2), dondeFFes la fila con dos dígitos yNes el nivel. - La operación
asignar(iso)DEBE ser idempotente: llamarla dos veces con el mismo contenedor DEBE devolver la misma posición sin crear un duplicado. - Cada contenedor DEBE ocupar como máximo una posición en la malla al mismo tiempo.
- Cada posición DEBE estar ocupada por como máximo un contenedor.
- Al registrar la salida de un contenedor (garita), su posición en la malla DEBE liberarse (
liberar(iso)).
NO DEBE ocurrir
- ❌ Una posición NO DEBE asignarse a dos contenedores distintos simultáneamente.
- ❌ NO DEBE colocarse un contenedor en una posición bloqueada (estado
BLOQUEADO; requierebloqueo:gestionarpara desbloquear primero). - ❌
ocupar(posId, iso)NO DEBE llamarse con unposIdque no exista en la geometría configurada — lanzaError: Posición inexistente. - ❌ NO DEBE asignarse posición cuando la malla está llena (capacidad = 0 libres) — el sistema lanza
PatioLlenoError; escalar a supervisor. - ❌ Un usuario con solo
patio:verNO DEBE tener acceso a los controles de asignación o modulación. - ❌ NO DEBE modificarse la geometría de la malla (
MALLA_DEMO/ConfigMalla) en runtime sin reiniciar el servidor — el estado in-memory se recomputa desde cero al iniciar.
6. Manejo de errores y escalamiento
| Error | Causa probable | Acción |
|---|---|---|
Pantalla en blanco / Error 500 en /malla | Fallo en getMallaSnapshot() o en la conexión a DB | Verificar logs (Runtime Logs Vercel). Si persiste, escalar al supervisor / iniciar rollback (ver rollback.sop.md). |
PatioLlenoError al intentar asignar | No hay slots libres | Notificar al supervisor para revisar posiciones y liberar contenedores salidos. |
Error: Posición inexistente | Se pasó un posId fuera de la geometría activa | Verificar que el ID sigue el formato BLOQUE-FF-N y que el bloque/fila/nivel existen en ConfigMalla. |
Error: Posición X ocupada por Y | Conflicto de doble asignación | Revisar el estado de la malla (reload), identificar cuál de los dos contenedores es el correcto. Liberar el incorrecto y reasignar. |
Contenedor buscado no resaltado en ?ubicar=ISO | ISO sin posición asignada o mal formateado | Confirmar que el ISO está en mayúsculas y registrado en inventario. |
| Vista 3D no carga (spinner indefinido) | Chunk Three.js/R3F bloqueado o red lenta | Cambiar a vista 2D. Reportar al equipo técnico si persiste. |
Escalamiento: incidentes que impidan registrar movimientos físicos en turno activo DEBEN reportarse al supervisor de inmediato. Si afectan producción, seguir rollback.sop.md.
7. Checklist de smoke test (módulo malla)
Ejecutar tras cualquier deploy que toque app/malla/, components/patio/malla-*, lib/patio/malla.ts o demo-store.ts:
-
/mallacarga sin error 500 y muestra los 4 stat cards. - El porcentaje de ocupación es coherente (ocupadas / capacidad × 100).
- La vista 2D muestra slots diferenciados por color (libre / ocupado / bloqueado).
- La vista 3D carga correctamente (puede tardar ~1-2 s).
-
?ubicar=CSQU3054383resalta el slot correcto y hace scroll. - Un usuario con rol
cliente_readonlyNO ve controles de asignación. - Un usuario con rol
operador_patioSÍ puede invocar la asignación.
8. Notas demo vs. producción
| Aspecto | Demo (estado actual) | Producción (Supabase activo) |
|---|---|---|
| Datos de contenedores | ILUSTRATIVO — seed ficticio de lib/patio/demo-dataset.ts | Datos reales del operador (Prisma + Postgres) |
| Geometría de malla | MALLA_DEMO: A·B·C × 4 filas × 3 niveles = 36 slots (fijado en código) | Configurable por tenant; persiste en DB |
| Asignación | In-memory; se pierde al reiniciar el servidor | Persistida en Postgres vía $transaction atómica |
| Auth | Selector de rol en UI (sin validación de identidad real) | Supabase IdP (D-017) — sesión con JWT real |
| Multi-tenant | Tenant único implícito | Filtrado por columna rfc; datos aislados por tenant |
getMallaSnapshot() | Lee demo-store (in-memory o Postgres si activo; force-dynamic) | Siempre Postgres; nunca snapshot horneado en build |
Los números de ocupación visibles en la demo son ILUSTRATIVOS y no representan un patio real. Los datos fiscales y aduanales (cuando existan) son SIEMPRE reales (D-011).
Referencias
lib/patio/malla.ts— modelo de dominio:Malla,Posicion,ConfigMalla,PatioLlenoErrorlib/patio/__tests__/malla.test.ts— casos de prueba de invariantesapp/malla/page.tsx— Server Component de la ruta/mallacomponents/patio/malla-switcher.tsx— orquestador de vistas (2D / 3D / temporal)components/patio/malla-2d.tsx— vista 2D accesiblelib/auth/roles.ts—PERMISOS_POR_ROL(fuente de verdad RBAC)docs/sops/deploy.sop.md— procedimiento de deploydocs/sops/rollback.sop.md— procedimiento de rollback