Sistema de Gestión de Almacén
Gestión de almacén event-sourced con zonas, slots, unidades de transporte, edición de planos de planta y registro inmutable de colocaciones construido sobre Nextcloud Tables.
Última actualización: 2025-02-18
Sistema de Gestión de Almacén (WHMS)
El módulo WHMS proporciona gestión de almacén event-sourced con un registro inmutable de colocaciones. El estado actual siempre se deriva del historial de eventos — nunca se almacena directamente.
Almacén 3D Interactivo
Explore el almacén en 3D — orbite, haga zoom y haga clic en cualquier slot para inspeccionar su contenido, utilización de zona y actividad reciente.
Visualización 3D del almacén — 3 zonas, 24 slots, 13 ocupados.
Visualización de Escaneo LiDAR
Los sensores LiDAR proporcionan mapeo espacial con precisión milimétrica. Certexi procesa nubes de puntos en tiempo real para verificar la ocupación de slots.
Escaneo LiDAR simulado — 400 puntos clasificados entre paredes, estantes, pallets y superficies de piso.
Arquitectura
Principios de Diseño
Registro Inmutable de Eventos
Los eventos son solo-adición — sin actualizaciones, sin eliminaciones. Cada colocación, remoción y verificación se registra como una nueva fila. El estado actual se deriva de reproducir los eventos.
Coordenadas Locales del Almacén
El sistema usa coordenadas relativas al plano de planta (x, y) en lugar de GPS. Esto funciona en interiores, offline, y sobrevive actualizaciones del plano de planta ya que el slot_id estable es la referencia principal.
Diseño con Evidencia Primero
Cada colocación requiere evidencia:
- Prueba de escaneo — Código de barras/QR de slot + activo
- Prueba fotográfica — Foto con marca de tiempo en la colocación
- Enlace CCTV — Clip opcional de cámara cercana
- Datos de báscula — Verificación de peso opcional
Tipos de Eventos
| Tipo | Descripción |
|---|---|
PLACED | Activo colocado en un slot |
REMOVED | Activo removido de un slot |
VERIFIED | Supervisor verificó una colocación |
DISPUTED | Supervisor disputó una colocación |
Modelo de Datos
Slots (Datos Maestros)
Ubicaciones de almacenamiento físico con geocercas poligonales:
| Campo | Tipo | Descripción |
|---|---|---|
slot_id | text | Identificador único (ej. "A-01-01") |
zone | selection | Categoría de zona (A, B, C, etc.) |
zone_type | selection | storage, staging, receiving, shipping, cold, hazmat |
floor_plan_polygon | JSON | Array de puntos definiendo la geocerca |
constraints | JSON | max_weight, max_height, allowed_types |
Assets
Artículos que pueden ser colocados en slots:
| Campo | Tipo | Descripción |
|---|---|---|
asset_id | text | Identificador único (ej. "PLT-2024-00001") |
type | selection | pallet, container, box, drum, equipment |
weight_kg | number | Peso en kilogramos |
dimensions | JSON | en cm |
Eventos de Colocación (Inmutables)
Solo-Adición
La tabla de eventos de colocación es un registro inmutable. Las filas nunca deben ser actualizadas ni eliminadas. Todos los cambios de estado se registran como nuevos eventos.
Cada evento registra: event_type, slot_id, asset_barcode, operator, timestamp, photo_url, scale_weight_kg, evidence_hash (SHA-256), y opcionalmente cctv_clip_url, work_order_ref y verification_notes.
Derivación del Estado Actual
Dado que el registro es inmutable, el estado actual se computa:
async function getSlotOccupancy(slotId: number) {
const events = await queryEvents({
slot_id: slotId,
orderBy: 'timestamp',
order: 'DESC',
});
const lastPlacement = events.find(
(e) => e.event_type === 'PLACED' || e.event_type === 'REMOVED'
);
if (!lastPlacement || lastPlacement.event_type === 'REMOVED') {
return { isOccupied: false, assetBarcode: null };
}
const isVerified = events.some(
(e) => e.event_type === 'VERIFIED' && e.timestamp > lastPlacement.timestamp
);
return {
isOccupied: true,
assetBarcode: lastPlacement.asset_barcode,
isVerified,
};
}
Endpoints de API
Eventos
POST /api/whms/events— Crear un evento de colocaciónGET /api/whms/events?slot_id=X&limit=50— Consultar historial de eventos
Slots
GET /api/whms/slots— Listar todos los slotsGET /api/whms/slots/:id— Obtener slot con estado de ocupación computadoPOST /api/whms/slots— Crear un slotPUT /api/whms/slots/:id— Actualizar metadatos del slot
Assets
GET /api/whms/assets— Listar todos los activosGET /api/whms/assets/:barcode/location— Obtener ubicación actual del activo
Dashboard
GET /api/whms/dashboard/utilization— Porcentajes de utilización por zona
Editor de Planos de Planta
El editor de distribución usa Konva.js para dibujo de polígonos:
- Subir una imagen de plano de planta
- Dibujar polígonos para definir los límites de slots
- Asignar IDs de slot, zonas y restricciones
- Codificación de color por zona para claridad visual
Inventario de Componentes
El módulo WHMS está implementado a través de los siguientes componentes. Use estas rutas al importar o extender comportamiento.
Distribución y mapa
| Componente | Ruta | Descripción |
|---|---|---|
FloorPlanCanvas | @/components/whms/layout-editor/floor-plan-canvas | Plano de planta basado en Konva con slots poligonales |
GeoSlotEditor | @/components/whms/layout-editor/geo-slot-editor | Editar polígonos de slot y metadatos |
SlotSidebar | @/components/whms/layout-editor/slot-sidebar | Propiedades de slot y asignación de zona |
WhmsGeofenceMap | @/components/whms/WhmsGeofenceMap | Mapa Leaflet con capas de zona/slot |
MinecraftGrid3D | @/components/whms/MinecraftGrid3D | Vista de cuadrícula 3D de zonas y slots |
IsometricZoneViewer | @/components/whms/IsometricZoneViewer | Visualización isométrica de zonas |
BirdEyeView | @/components/whms/BirdEyeView | Vista aérea de zonas |
LidarZoneViewer | @/components/whms/LidarZoneViewer | Visualización de zonas estilo Lidar |
Escáner y colocación
| Componente | Ruta | Descripción |
|---|---|---|
PlacementFlow | @/components/whms/scanner/placement-flow | Escanear slot → escanear activo → foto → confirmar |
RemovalFlow | @/components/whms/scanner/removal-flow | Escanear slot → confirmar remoción |
ZoneScanner | @/components/whms/ZoneScanner | Escaneo de código de barras de zona/slot |
AssetDropPopup | @/components/whms/AssetDropPopup | Confirmación de colocación in-situ |
Zonas y slots
| Componente | Ruta | Descripción |
|---|---|---|
ZoneDetailModal | @/components/whms/ZoneDetailModal | Info de zona, slots, CCTV, analítica |
ZoneDrilldownPanel | @/components/whms/ZoneDrilldownPanel | Panel deslizante con detalles de zona |
ZoneCCTVTab | @/components/whms/ZoneCCTVTab | Feeds CCTV para una zona |
ZoneAnalyticsPanel | @/components/whms/ZoneAnalyticsPanel | Métricas y gráficos de zona |
SlotsTab | @/components/whms/SlotsTab | Lista/cuadrícula de slots con ocupación |
CCTVTab | @/components/whms/CCTVTab | Lista de cámaras y estado |
Dashboard y utilización
| Componente | Ruta | Descripción |
|---|---|---|
UtilizationWidget | @/components/whms/dashboard/utilization-widget | Capacidad y uso de zona |
LiveOperationsFeed | @/components/whms/LiveOperationsFeed | Feed en tiempo real de colocaciones/remociones |
RecordsTab | @/components/whms/RecordsTab | Registros de tabla vinculados al WHMS |
Gamificación (opcional)
| Componente | Ruta | Descripción |
|---|---|---|
MissionsTab | @/components/whms/MissionsTab | Misiones y unidades de transporte en mapa |
MissionCard / MissionMarker | @/components/whms/ | UI de misión y marcadores en mapa |
QuestsTab / QuestCard / QuestCreator | @/components/whms/ | Misiones y desafíos |
GamificationHeader | @/components/whms/GamificationHeader | XP, nivel, racha |
AdminGameSetup | @/components/whms/admin/AdminGameSetup | Configurar juegos y tablas de clasificación |
3D y paneles
| Componente | Ruta | Descripción |
|---|---|---|
DroppableSlot3D / DraggableAsset | @/components/whms/ | Arrastrar y soltar en vista 3D |
AssetBankDrawer / RecordPalette | @/components/whms/ | Selector de activos/registros para colocación |
FloatingPanelManager / FloatingWindow | @/components/whms/panels/ | Inventario flotante y paneles |
PanelInventory / InventoryGrid | @/components/whms/panels/ | Cuadrícula de inventario en paneles |
Patrones de UI (sandboxes)
Los siguientes sandboxes ilustran los bloques de construcción de UI usados en las pantallas del WHMS. Los componentes reales usan las mismas primitivas (Card, Badge, Button, Progress, Tabs) más mapa/3D y datos de API.
Tarjeta de estado de slot
Estado de ocupación y verificación de un solo slot en vistas de lista o cuadrícula:
<Card className="max-w-xs"> <CardHeader className="pb-2 flex flex-row items-center justify-between"> <CardTitle className="text-sm font-medium">A-01-03</CardTitle> <Badge variant="secondary">Preparación</Badge> </CardHeader> <CardContent className="space-y-2"> <p className="text-xs text-muted-foreground">Ocupado</p> <p className="text-sm font-mono">PLT-2024-00042</p> <div className="flex gap-2"> <Badge variant="outline">Verificado</Badge> <Button size="sm" variant="ghost">Ver</Button> </div> </CardContent> </Card>
Fila de utilización de zona
Una fila del widget de utilización (nombre de zona + barra de capacidad):
<div className="space-y-3 max-w-sm"> <div className="flex justify-between text-sm"> <span>Zona A — Almacenamiento</span> <span className="text-muted-foreground">72%</span> </div> <Progress value={72} className="h-2" /> <div className="flex justify-between text-sm"> <span>Zona B — Preparación</span> <span className="text-muted-foreground">24 / 32 slots</span> </div> <Progress value={75} className="h-2" /> </div>
Paso del flujo de colocación
Un paso individual en el asistente de colocación (escanear → foto → confirmar):
<Card className="max-w-sm"> <CardHeader> <CardTitle className="text-base">Paso 2: Foto</CardTitle> <CardDescription>Capturar prueba de colocación en el slot</CardDescription> </CardHeader> <CardContent className="space-y-4"> <div className="rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground"> Vista previa de cámara o placeholder </div> <div className="flex gap-2"> <Button className="flex-1">Capturar</Button> <Button variant="outline">Omitir</Button> </div> </CardContent> </Card>
Pestañas de zona (vista detallada)
Pestañas usadas en detalle de zona (Slots, CCTV, Analítica, Incidentes):
<Tabs defaultValue="slots" className="max-w-md"> <TabsList className="grid w-full grid-cols-4"> <TabsTrigger value="slots">Slots</TabsTrigger> <TabsTrigger value="cctv">CCTV</TabsTrigger> <TabsTrigger value="analytics">Analítica</TabsTrigger> <TabsTrigger value="incidents">Incidentes</TabsTrigger> </TabsList> <TabsContent value="slots" className="p-4 border rounded-b-md"> <p className="text-sm text-muted-foreground">Lista de slots con ocupación y acciones.</p> </TabsContent> <TabsContent value="cctv" className="p-4 border rounded-b-md"> <p className="text-sm text-muted-foreground">Feeds de cámaras para esta zona.</p> </TabsContent> <TabsContent value="analytics" className="p-4 border rounded-b-md"> <p className="text-sm text-muted-foreground">Gráficos y utilización.</p> </TabsContent> <TabsContent value="incidents" className="p-4 border rounded-b-md"> <p className="text-sm text-muted-foreground">Incidentes y disputas.</p> </TabsContent> </Tabs>
Elemento del feed de operaciones
Una línea en el feed de operaciones en vivo (eventos de colocación/remoción):
<div className="flex items-center gap-3 rounded-lg border p-3 max-w-md"> <Badge>PLACED</Badge> <div className="flex-1 min-w-0"> <p className="text-sm font-medium truncate">A-02-01 ← PLT-2024-00011</p> <p className="text-xs text-muted-foreground">Op. García · hace 2 min</p> </div> <Button size="sm" variant="ghost">Detalles</Button> </div>