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.

certexi.com/app/whms/3d/editor
Loading interactive demo...

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.

certexi.com/app/whms/lidar
Loading interactive demo...

Escaneo LiDAR simulado — 400 puntos clasificados entre paredes, estantes, pallets y superficies de piso.

Arquitectura

Loading diagram…

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

TipoDescripción
PLACEDActivo colocado en un slot
REMOVEDActivo removido de un slot
VERIFIEDSupervisor verificó una colocación
DISPUTEDSupervisor disputó una colocación

Modelo de Datos

Slots (Datos Maestros)

Ubicaciones de almacenamiento físico con geocercas poligonales:

CampoTipoDescripción
slot_idtextIdentificador único (ej. "A-01-01")
zoneselectionCategoría de zona (A, B, C, etc.)
zone_typeselectionstorage, staging, receiving, shipping, cold, hazmat
floor_plan_polygonJSONArray de puntos definiendo la geocerca
constraintsJSONmax_weight, max_height, allowed_types

Assets

Artículos que pueden ser colocados en slots:

CampoTipoDescripción
asset_idtextIdentificador único (ej. "PLT-2024-00001")
typeselectionpallet, container, box, drum, equipment
weight_kgnumberPeso en kilogramos
dimensionsJSON 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ón
  • GET /api/whms/events?slot_id=X&limit=50 — Consultar historial de eventos

Slots

  • GET /api/whms/slots — Listar todos los slots
  • GET /api/whms/slots/:id — Obtener slot con estado de ocupación computado
  • POST /api/whms/slots — Crear un slot
  • PUT /api/whms/slots/:id — Actualizar metadatos del slot

Assets

  • GET /api/whms/assets — Listar todos los activos
  • GET /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:

  1. Subir una imagen de plano de planta
  2. Dibujar polígonos para definir los límites de slots
  3. Asignar IDs de slot, zonas y restricciones
  4. 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

ComponenteRutaDescripción
FloorPlanCanvas@/components/whms/layout-editor/floor-plan-canvasPlano de planta basado en Konva con slots poligonales
GeoSlotEditor@/components/whms/layout-editor/geo-slot-editorEditar polígonos de slot y metadatos
SlotSidebar@/components/whms/layout-editor/slot-sidebarPropiedades de slot y asignación de zona
WhmsGeofenceMap@/components/whms/WhmsGeofenceMapMapa Leaflet con capas de zona/slot
MinecraftGrid3D@/components/whms/MinecraftGrid3DVista de cuadrícula 3D de zonas y slots
IsometricZoneViewer@/components/whms/IsometricZoneViewerVisualización isométrica de zonas
BirdEyeView@/components/whms/BirdEyeViewVista aérea de zonas
LidarZoneViewer@/components/whms/LidarZoneViewerVisualización de zonas estilo Lidar

Escáner y colocación

ComponenteRutaDescripción
PlacementFlow@/components/whms/scanner/placement-flowEscanear slot → escanear activo → foto → confirmar
RemovalFlow@/components/whms/scanner/removal-flowEscanear slot → confirmar remoción
ZoneScanner@/components/whms/ZoneScannerEscaneo de código de barras de zona/slot
AssetDropPopup@/components/whms/AssetDropPopupConfirmación de colocación in-situ

Zonas y slots

ComponenteRutaDescripción
ZoneDetailModal@/components/whms/ZoneDetailModalInfo de zona, slots, CCTV, analítica
ZoneDrilldownPanel@/components/whms/ZoneDrilldownPanelPanel deslizante con detalles de zona
ZoneCCTVTab@/components/whms/ZoneCCTVTabFeeds CCTV para una zona
ZoneAnalyticsPanel@/components/whms/ZoneAnalyticsPanelMétricas y gráficos de zona
SlotsTab@/components/whms/SlotsTabLista/cuadrícula de slots con ocupación
CCTVTab@/components/whms/CCTVTabLista de cámaras y estado

Dashboard y utilización

ComponenteRutaDescripción
UtilizationWidget@/components/whms/dashboard/utilization-widgetCapacidad y uso de zona
LiveOperationsFeed@/components/whms/LiveOperationsFeedFeed en tiempo real de colocaciones/remociones
RecordsTab@/components/whms/RecordsTabRegistros de tabla vinculados al WHMS

Gamificación (opcional)

ComponenteRutaDescripción
MissionsTab@/components/whms/MissionsTabMisiones 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/GamificationHeaderXP, nivel, racha
AdminGameSetup@/components/whms/admin/AdminGameSetupConfigurar juegos y tablas de clasificación

3D y paneles

ComponenteRutaDescripció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:

Tarjeta de estado de slot
<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):

Fila de utilización de zona
<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):

Paso de colocación
<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):

Pestañas de detalle de zona
<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):

Elemento del feed de operaciones
<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>

Relacionado