SOP — Módulo Garita (Patio Abierto)
Tipo: procedimiento operativo estándar. Estado: demo-ready / DB-swappable. Ruta:
/garita. Fuente:app/garita/page.tsx,components/patio/actions.ts,components/patio/garita-resolver.ts.
0. Propósito y alcance
Este SOP cubre la operación completa del módulo de garita:
- C1 — Ingreso: registro manual de entrada de un contenedor al patio
(evento
INGRESO_PATIO, estadoEN_PATIO), capturando datos del EIR. - C2 — Cola de salida: operación del cruce de garita (
MODULADO → LIBERADO, eventoGARITA_SALIDA) y salida definitiva (LIBERADO → SALIDO, eventoSALIDA). - EIR: generación y descarga del recibo de intercambio de equipo (Equipment Interchange Receipt) como PDF imprimible.
- Bloqueos: activar o retirar el gate de cumplimiento que impide la salida.
Fuera de alcance: modulación (Kanban), citas de arribo, administración de usuarios.
1. Roles y permisos
| Acción | Permiso requerido | Roles autorizados |
|---|---|---|
| Registrar ingreso (C1) | ingreso:registrar | admin, supervisor, operador_patio |
| Agendar cita de arribo | ingreso:registrar | admin, supervisor, operador_patio |
| Pasar garita (MODULADO → LIBERADO) | garita:operar | admin, supervisor, operador_garita |
| Registrar salida (LIBERADO → SALIDO) | salida:registrar | admin, supervisor, operador_garita |
| Bloquear / desbloquear salida | bloqueo:gestionar | admin, supervisor |
| Ver la cola / inventario | patio:ver | todos los roles |
Regla invariante: la UI (<Protect>, botón con candado) es defensa cosmética.
El permiso DEBE validarse en el Server Action (exigirPermiso en lib/auth/server).
Un operador sin permiso que llame la action directamente recibe ActionResult.ok = false.
2. Precondiciones
- El usuario DEBE tener sesión activa y un rol asignado (Supabase Auth; en demo, selector de rol).
- Para C1: DEBE haber posiciones libres en la malla (si el patio está lleno, la acción rechaza con
PatioLlenoError). - Para C2 (pasar garita / registrar salida): el contenedor DEBE estar en el estado correcto —
MODULADOpara pasar garita,LIBERADOpara registrar salida. Cualquier otra transición es rechazada conTransicionInvalidaError. - Para C2 (salida): la salida del contenedor NO DEBE estar bloqueada. Si lo está, la acción rechaza con
SalidaBloqueadaError. - Para bloqueos: el usuario DEBE tener
bloqueo:gestionar.
3. Procedimiento — C1: Registrar ingreso
- Navegar a
/garita. El formulario de ingreso aparece en la parte superior (sección C1). - Capturar el código ISO 6346 del contenedor (campo
iso). Formato obligatorio: 4 letras + 7 dígitos, dígito verificador válido (p. ej.MSCU1234567). - Completar los datos del EIR (todos opcionales, pero DEBEN capturarse si el operador los conoce):
- Transportista, placa, chofer.
- Peso bruto (kg) y tara (kg).
- Sello / número de candado.
- Condición al ingreso:
OKoDañado.
- Si aplica Carta Porte (figura de transporte SICT), capturar:
- RFC transportista — DEBE ser RFC válido si se captura (el sistema lo valida).
- Tipo de permiso SICT y número de permiso (máx. 50 caracteres).
- Pulsar "Registrar ingreso". El botón muestra "Registrando…" mientras la acción está pendiente.
- Verificar el toast de resultado:
- Éxito:
"Ingreso registrado · {ISO} → {posición}". El formulario se limpia automáticamente. - Error: ver §6 (Manejo de errores).
- Éxito:
- El contenedor queda en estado
EN_PATIOcon posición asignada en la malla. Las rutas/,/mallay/garitase revalidan automáticamente.
4. Procedimiento — C2: Cola de salida (pasar garita / registrar salida)
- La cola de salida se muestra debajo del formulario de ingreso.
- Móvil: una tarjeta por contenedor con el botón de acción prominente.
- Desktop: tabla con columnas: Contenedor, Estado, Posición, Salida, Último movimiento, Acción.
- Para cada fila, revisar la columna Salida:
- Badge verde / sin badge → salida permitida.
- Badge "Bloqueada" + motivo → salida impedida; el botón de acción no aparece.
- Según el estado del contenedor:
- Estado
MODULADO→ botón "Pasar garita" (requieregarita:operar). Al confirmar: eventoGARITA_SALIDA, estado pasa aLIBERADO. - Estado
LIBERADO→ botón "Registrar salida" (requieresalida:registrar). Al confirmar: eventoSALIDA, estado pasa aSALIDOy la posición queda libre.
- Estado
- Si el rol del usuario no tiene el permiso correspondiente, el botón aparece deshabilitado con icono de candado. El operador NO DEBE intentar forzar la acción por otros medios.
- Verificar el toast de resultado:
- Éxito:
"Garita cruzada · {ISO}"o"Salida registrada · {ISO}". - Error: ver §6.
- Éxito:
- Las rutas
/garita,/,/mallase revalidan al completar la acción.
5. Procedimiento — EIR (descargar recibo)
- En la cola de salida, localizar el contenedor y pulsar "Descargar EIR".
- Se abre una nueva ventana con el documento HTML standalone y se dispara automáticamente el diálogo de impresión del navegador.
- El operador PUEDE guardar como PDF o imprimir en papel. El documento incluye:
- Código ISO, estado, recinto, fecha de ingreso.
- Datos de entrada capturados en C1 (transportista, placa, chofer, sello, condición, peso bruto, tara, peso neto calculado).
- Líneas de firma: Operador de garita / Transportista-chofer.
- Marca de agua:
"Datos de entrada capturados manualmente por el operador".
- Si el navegador bloquea la ventana emergente: aparece un toast de error.
El operador DEBE permitir ventanas emergentes para
patio-abierto.vercel.appy reintentar. - El timestamp del documento se sella en el momento del click (no se predetermina ni inventa).
6. Procedimiento — Bloquear / desbloquear salida
- Localizar el contenedor (en
/garitao en/contenedor/{ISO}). - Con
bloqueo:gestionar, activar "Bloquear salida":- Campos: código ISO (validado ISO 6346 estricto) y motivo opcional.
- El contenedor queda marcado con
salida_bloqueada = truey el motivo registrado.
- Para retirar el bloqueo, usar "Desbloquear": mismos campos, sin motivo obligatorio.
- Toast de confirmación:
"Salida bloqueada · {ISO}"o"Bloqueo retirado · {ISO}". - La cola de garita en
/garitay la malla en/mallareflejan el cambio de inmediato.
7. Reglas e invariantes
DEBE:
- El ISO 6346 DEBE validarse con dígito verificador en todo flujo de escritura (ingreso, pasar garita, registrar salida, bloqueo). Un código malformado rechaza la acción.
- La defensa RBAC DEBE vivir en el Server Action; la validación de la UI es solo cosmética.
- El bloqueo físico (
salida_bloqueada) DEBE tener prioridad sobre el gate de cumplimiento (API aduanal). Si ambos conflictan, el físico manda. - En modo overlay, el gate-check DEBE consultarse a la API aduanal para cada salida.
Si la API no responde o está degradada, el sistema DEBE caer al resultado físico
(fuente
"degradado") sin lanzar error al operador. - El EIR DEBE incluir únicamente datos capturados por el operador; NUNCA datos inventados.
NO DEBE:
- Un operador SIN
garita:operarNO DEBE poder ejecutarpasarGarita(ni por UI ni por API). - Un contenedor con salida bloqueada NO DEBE avanzar a
LIBERADOniSALIDO. - Un contenedor ya en
SALIDONO DEBE reingresar con el mismo registro (se rechaza con error). - El
event_iddel evento de salida NO DEBE reutilizarse entre clicks (cada acción generacrypto.randomUUID(); no se garantiza idempotencia por diseño actual). - El
event_idNO DEBE ser predecible o sequential en producción.
DEBERÍA:
- El operador DEBERÍA verificar la condición física del contenedor antes de registrar el ingreso.
- Al bloquear, DEBERÍA capturarse el motivo del bloqueo para trazabilidad.
8. Manejo de errores y escalamiento
| Error | Causa | Acción del operador |
|---|---|---|
"Código ISO 6346 inválido (esperado ABCD1234567)" | ISO malformado o dígito verificador incorrecto | Verificar el código físicamente en la placa del contenedor y reingresar |
"RFC del transportista inválido" | RFC no válido en Carta Porte | Corregir el RFC o dejar el campo vacío si no aplica |
"Salida bloqueada: {motivo}" | SalidaBloqueadaError — gate de cumplimiento o bloqueo manual activo | Escalar a supervisor o admin para resolver el bloqueo antes de continuar |
"Movimiento no permitido en el estado actual" | TransicionInvalidaError — el contenedor no está en el estado esperado | Verificar el estado en el inventario; no forzar la transición |
"Patio lleno: no hay posiciones libres" | PatioLlenoError — malla sin celdas disponibles | Escalar a supervisor; no registrar el ingreso hasta liberar posición |
"No tienes permiso para esta acción." | Rol insuficiente | No forzar; escalar al supervisor o admin para que ejecute la acción |
| Toast de ventana bloqueada (EIR) | Bloqueador de pop-ups activo | Permitir ventanas emergentes en el navegador y reintentar |
| Error genérico (sin toast específico) | Error inesperado en Server Action | Capturar pantalla, anotar el ISO y hora, escalar a admin/G |
Escalamiento: un bloqueo de salida no resuelto DEBE escalar al supervisor (BLOQUEO_GESTIONAR).
Un error sin toast claro DEBE reportarse al administrador de la plataforma (G / CEO).
9. Notas — Demo vs Producción
⚠️ ILUSTRATIVO — Las notas de esta sección describen diferencias entre el estado actual (demo) y el estado objetivo (producción). No representan comportamiento en producción real.
| Aspecto | Demo (hoy) | Producción (objetivo) |
|---|---|---|
| Persistencia de datos | In-memory (demo-store); se pierde entre reinicios serverless | Postgres vía Prisma (activar con DATABASE_URL) |
| Gate aduanal | getModoOperacion() = "physical_only" (sin CUSTOMS_API_BASE_URL) | Overlay activo; gate-check consulta API fachada aduanal |
| Sesión / rol activo | Selector de rol en demo (sin Supabase Auth real) | Supabase Auth con JWT; rol asignado por tenant (rfc) |
| EIR — recinto | null en demo (campo vacío en el documento) | Identificador real del recinto aduanal o fiscal |
| Idempotencia de salida | event_id = crypto.randomUUID() por click (no idempotente) | En producción DEBE implementarse idempotencia real por clave de negocio |
10. Checklist de operación (por turno)
- Iniciar sesión con el rol correcto (
operador_garitaooperador_patiosegún turno) - Verificar que la cola de garita carga en
/garitasin errores - Por cada camión entrante: capturar ISO + datos EIR y registrar ingreso (C1)
- Por cada unidad en cola de salida: verificar estado (
MODULADO/LIBERADO) y bloqueos - Pasar garita / registrar salida según estado; descargar EIR y obtener firmas físicas
- Reportar cualquier bloqueo de salida no resuelto al supervisor antes de finalizar turno