HumboltTracking · ERP de Logística
Humbolt MVP
ERP de logística multi-rol: cuatro portales sobre una misma API, del despacho en oficina al escaneo del paquete en la calle.
Desarrollo frontend completo: arquitectura de portales por rol, capa de servicios generada desde OpenAPI, integración con API REST/WebSocket y portal operador móvil
100%
Trazabilidad
24/7
Monitoreo
Humbolt MVP —la marca dentro de la interfaz es “HumboltTracking · ERP de Logística”— es una aplicación web React/TypeScript que cubre el ciclo completo de un envío: alta del cliente y del destinatario, creación del envío, generación de la etiqueta con QR, armado de la ruta, escaneo del paquete en cada parada y cierre de la ruta.
Una sola build sirve a cinco roles (`super_admin`, `admin`, `operator`, `client_master`, `client_agency`). El componente raíz `src/app/App.tsx` lee el usuario autenticado, lo mapea a un rol de aplicación y monta uno de cuatro portales distintos: portal supervisor de escritorio, portal operador pensado para el móvil del chofer, portal de cliente master y portal de agencia cliente.
El proyecto arrancó como un bundle de código exportado de Figma Make (el README apunta al archivo original y `ATTRIBUTIONS.md` acredita a shadcn/ui). El trabajo posterior fue sustituir los datos simulados por la API real: un script propio, `gen_services.py`, descarga el `openapi.json` del backend y genera `src/services/types.ts` (98 interfaces y tipos) más los quince servicios que consumen 45 rutas entre REST y WebSocket.
El portal operador es el corazón operativo: obliga a un orden de trabajo —primero foto de evidencia subida al servidor, después escaneo del QR del mismo paquete— usando la cámara trasera del teléfono con `html5-qrcode` y `getUserMedia`, con registro manual de código y peso como respaldo cuando el escáner no lee.
Estado del proyecto
MVP funcional conectado a la API de producción (axiomcoretech.store); build publicado bajo la ruta base /humbolt/, con módulos de inventario, reportes e Intelligence Hub aún fuera del árbol de navegación
Cada actor ve sólo su parte del mismo dato
Una operación logística con oficina, almacén, choferes y clientes corporativos necesita que cada actor vea sólo su parte del mismo dato, y que el evento de la calle llegue al escritorio sin pasar por WhatsApp ni por papel. El supervisor necesita dar de alta clientes, destinatarios, choferes y envíos, y armar rutas con paradas; el chofer necesita, en un teléfono y con una mano, saber qué paradas tiene, dejar constancia fotográfica de la entrega y confirmar paquete por paquete; el cliente corporativo necesita ver sus envíos y sus destinatarios sin acceso al resto del sistema. Sumado a eso, el dominio venezolano impone reglas propias: documentos RIF/NIT y cédula con formato validable, y direcciones en cascada de país → estado → ciudad → municipio → parroquia → zona postal.
Oficina
Alta de clientes, destinatarios, choferes y envíos; armado de rutas.
Calle
Un teléfono, una mano: paradas, evidencia y confirmación paquete a paquete.
Cliente corporativo
Sus envíos y sus destinatarios, sin acceso al resto del sistema.
Dominio venezolano
RIF/NIT y cédula validables; dirección en cascada de país a zona postal.
módulos sin tablero común
Cuatro portales, una sola build
Las ocho losas se ensamblan en un tablero continuo. Encima se levantan las cuatro cáscaras que monta App.tsx según el rol.
Un frontend único con un shell de portal reutilizable (`PortalShell`) que recibe su propio menú por rol y renderiza la misma cáscara —barra lateral navy de 80 px en escritorio, panel deslizante en móvil— con contenidos distintos. Sobre él se montan ocho módulos de administración (Dashboard, Usuarios, Clientes, Destinatarios, Tipos de Destino, Choferes, Envíos y Rutas), cada uno con búsqueda, filtros, paginación servida por el API y tablas que colapsan a tarjetas en móvil. La comunicación pasa por una instancia de axios con interceptores que inyectan el token, traducen los tres formatos de error del backend a modal o toast, reintentan hasta tres veces con backoff y expulsan la sesión ante un 401. El dashboard escucha eventos en vivo por WebSocket con reconexión exponencial, y el portal operador cierra el circuito subiendo la evidencia por multipart y confirmando el escaneo contra la parada concreta de la ruta.
Supervisor
8 seccionessuper_admin · admin
Escritorio: alta de clientes, destinatarios, choferes y envíos, armado de rutas y asignación de paradas.
Operador
Rutas asignadasoperator
Móvil del chofer: paradas, cámara trasera, evidencia y escaneo paquete por paquete.
Cliente Master
4 seccionesclient_master
Dashboard, usuarios de su compañía, destinatarios y envíos. Nada más del sistema.
Agencia
3 seccionesclient_agency
Dashboard, destinatarios y envíos de esa agencia, con el mismo módulo reutilizado.
Portal supervisor · arrastra
M-01
Dashboard
M-02
Usuarios
M-03
Clientes
M-04
Destinatarios
M-05
Tipos de Destino
M-06
Choferes
M-07
Envíos
M-08
Rutas
Siete piezas bien apoyadas
Capa de servicios generada desde el OpenAPI del backend
`gen_services.py` descarga `https://axiomcoretech.store/openapi.json` y escribe `src/services/types.ts` con 98 interfaces y enums más los quince ficheros de servicio. El contrato del frontend deja de escribirse a mano: cuando el backend cambia, se regenera y el compilador señala lo que se rompió.
Cuatro portales, una sola build
`App.tsx` mapea el usuario de `/users/me` a uno de cinco roles y monta SupervisorPortal, OperatorApp, ClientMasterPortal o ClientAgencyPortal. Los tres portales de escritorio comparten `PortalShell`, `PageHeader` y `ResponsiveDataTable`, así que un módulo escrito para el supervisor —como Destinatarios o Envíos— se reutiliza tal cual en el portal del cliente pasando `isClientUser`.
Evidencia obligatoria antes del escaneo
En cada parada el operador debe cumplir dos pasos en orden: “1. Tomar foto de evidencia”, que sube la imagen por multipart a `/api/v1/uploads/evidence`, y “2. Escanear paquete”, que permanece deshabilitado hasta que existe una evidencia pendiente. El escaneo confirma contra `/routes/{id}/stops/{id}/scan` el mismo paquete de la foto.
Dashboard alimentado por WebSocket con reconexión progresiva
`websocket.service.ts` abre `/api/v1/ws/dashboard/live` con el token en la query, distribuye cada evento a los suscriptores y, si la conexión cae, reintenta con un retardo que crece de 3 s a un tope de 30 s multiplicando por 1,5. La UI clasifica los eventos en cuatro tipos (éxito, información, acción requerida, error) con su propio color y glow.
Interceptor que traduce tres formatos de error distintos
El interceptor de axios distingue el formato `{ status: 'VALIDATION_ERROR', message, data }`, el `detail` clásico de FastAPI —cadena o lista de `{loc, msg, type}`— y los errores de integridad de base de datos. Un 422 abre modal global; 400, 409 y 500 muestran toast; el 401 limpia la cookie y recarga; el duplicado de correo en PATCH de usuario se deja pasar para que el formulario lo pinte en su propio tooltip.
Etiquetas con QR y comprobantes generados en el cliente
`ShippingLabelGenerator` abre una ventana de impresión con una etiqueta por bulto: número de pieza `GP… 1/N`, número referencial derivado del tracking y un QR con `{tracking, reference, piece}` en JSON. El comprobante del envío se arma con jsPDF en el navegador para no depender del PDF del servidor.
Rutas con paradas y asignación envío → parada
El módulo de rutas (2.322 líneas) permite crear la ruta con sus paradas por destinatario, editarla, verla en detalle y entrar a una vista de asignación donde cada envío seleccionado elige su parada de descarga; `buildShipmentAssignmentsFromStops` resuelve el `dropoff_stop_id` real que devolvió el servidor.
Un paquete cruzando las ocho losas
Del alta del cliente al escaneo en la calle: el mismo dato cambiando de manos, y su insignia de estado cambiando de color.
Siete estados de envío, siete insignias
Del escritorio del supervisor al bolsillo del chofer
Cinco pantallas reconstruidas: la puerta de entrada, el centro de comando, la operación diaria, el trabajo de asignación y la vista de una mano.
Sistema de Gestión de Envíos
Gestión de envíos, tracking en tiempo real y control operacional multi-rol.
100%
Trazabilidad
24/7
Monitoreo
Iniciar Sesión
Ingresa tus credenciales para acceder al sistema
Correo Electrónico
Contraseña
Credenciales incorrectas o error en el servidor…
Login — HumboltTracking
Dashboard
Centro de comando operacional en tiempo real
Estado operativo
Todo al día
Sin alertas ni acciones pendientes
Resumen de eventos
47 en la sesiónSin actividad reciente en esta sesión
Dashboard del supervisor — centro de comando en vivo
Gestión de Envíos
Control centralizado de operaciones de envío
| Código de Seguimiento | Cliente | Ruta | Peso | Estado | Entrega Est. | Actualización |
|---|---|---|---|---|---|---|
| GP-4417832 | Inversiones Delta C.A. | Caracas → Valencia | 12,40 kg | Entregado | 12/03 | hace 4 min |
| GP-4417791 | Comercial Ávila | Maracay → Barquisimeto | 3,15 kg | En Tránsito | 12/03 | hace 22 min |
| GP-4417744 | Agencia Guaicaipuro | Caracas → Maracaibo | 28,00 kg | En Almacén | 13/03 | hace 1 h |
| GP-4417702 | Distribuidora Sur | Valencia → Puerto La Cruz | 7,80 kg | Retrasado | 11/03 | hace 3 h |
| GP-4417688 | Textiles del Centro | Caracas → Mérida | 1,05 kg | Pendiente | 14/03 | hace 5 h |
| GP-4417650 | Logística Andina | Mérida → Caracas | 16,60 kg | Aduana | 15/03 | ayer |
Gestión de Envíos — listado y filtros
Asignar Envíos a Ruta
Ruta Centro-Occidente — Selecciona envíos y asígnalos a paradas específicas
Paradas de la Ruta
Asignar Envíos a Ruta
← Volver a rutas
Parada 3 de 5 · Av. Bolívar, Torre Delta, Valencia
Paso 1: foto de evidencia. Paso 2: escanear o registrar el código del mismo paquete.
Escaneando · html5-qrcode
Registro manual
Portal Operador — parada de ruta (foto y escaneo)
Las maquetas reproducen la interfaz real; los datos que muestran son de ejemplo.
El evento de la calle llega al escritorio sin pasar por WhatsApp
`websocket.service.ts` abre `/api/v1/ws/dashboard/live` con el token en la query y reparte cada evento entre sus suscriptores. Cuatro tipos, cuatro colores, un solo canal.
Éxito
shipment.delivered · GP-4417832
Información
route.started · RT-0091
Acción requerida
recipient.address.pending
Error
scan.mismatch · stop 4
Reconexión progresiva
Si la conexión cae, el retardo crece multiplicando por 1,5 desde 3 s hasta un tope de 30 s. El feed vuelve solo; nadie recarga la pestaña.
25 losas de producto terminado
No es una lista de intenciones: cada una está construida, conectada al API real y en uso dentro de alguno de los cuatro portales.
Login por correo y contraseña contra `/api/v1/auth/login`, token guardado en cookie `AUTH_TOKEN` con vencimiento de un día y auto-login al recargar si la cookie sigue viva
Enrutado por rol en el cliente: super admin y admin al portal supervisor, operador al portal móvil, cliente master y agencia a sus portales acotados
Portal supervisor con ocho secciones: Dashboard, Usuarios, Clientes, Destinatarios, Tipos de Destino, Choferes, Envíos y Rutas
Gestión de usuarios con asistente de alta en varios pasos, pantalla de edición, asignación de roles, reseteo de contraseña y filtros por rol y por estado (activo, inactivo, bloqueado, invitado)
Gestión de clientes y de tipos de cliente, con cambio de estado y ficha de detalle
Módulo de destinatarios con flujo de aprobación: direcciones en estado pendiente, aprobada o rechazada, aprobables desde la vista consolidada de agencias
Alta y edición de choferes y de tipos de destino, con activación/desactivación
Creación de envíos con remitente (sucursal de origen), destinatario, tipo de servicio, cantidad de bultos, tipo y descripción de contenido, y peso y valor declarado por bulto
Listado de envíos con búsqueda por código, cliente, origen o destino, filtro de estado, ordenamiento y paginación del servidor
Siete estados de envío con su insignia de color: Pendiente, En Almacén, Aduana, En Tránsito, Entregado, Retrasado y Cancelado
Generación e impresión de etiquetas con QR y número referencial, una por bulto, y previsualización en modal
Comprobante del envío en PDF generado en el navegador con jsPDF
Carga masiva de envíos por archivo CSV/XLSX con zona de arrastre, barra de progreso y validación fila por fila
Gestión de rutas: creación con paradas por destinatario, edición, detalle, eliminación con confirmación y asignación de envíos a paradas concretas
Portal operador: lista de rutas asignadas desde `/routes/driver/me` con código, cantidad de paradas, fecha, chofer y estado (Pendiente, En tránsito, Completada, Cancelada)
Acciones de ruta del operador: “Empezar ruta” y “Completar ruta” contra los endpoints `/routes/{id}/start` y `/routes/{id}/complete`
Captura de evidencia con la cámara trasera del teléfono (`facingMode: environment`, 1280×720) y subida multipart antes de habilitar el escaneo
Escáner QR embebido con `html5-qrcode`, recuadro de escaneo calculado según el ancho de pantalla, y registro manual de código y peso como alternativa
Dashboard en vivo por WebSocket con panel “Estado operativo” (alertas y acciones pendientes) y “Resumen de eventos” de la sesión
Búsqueda global y feed de actividad en el portal supervisor
Portal de cliente master con Dashboard, Usuarios de su compañía, Destinatarios y Envíos; portal de agencia cliente con Dashboard, Destinatarios y Envíos
Validación de documentos venezolanos: RIF/NIT con prefijo J, G, V, E o P y de seis a diez dígitos, cédula con prefijo V o E, rechazo de dígitos todos iguales y limpieza de prefijos duplicados pegados al número
Direcciones en cascada contra el API de localidades: países → estados → ciudades → municipios → parroquias → zonas postales
Modal global de errores de validación alimentado por un servicio de suscripción, más notificaciones toast con Sonner
Interfaz responsive de verdad: tablas que se convierten en tarjetas en móvil, barra lateral que se transforma en panel deslizante y menú hamburguesa
Con qué está hecho
Núcleo
4 piezas
Interfaz y estilos
14 piezas
Datos y red
5 piezas
Captura y documentos
6 piezas
Formularios, tablas y gráficos
10 piezas
Pruebas y tooling
5 piezas
El tablero desmontado en capas
De la primitiva de Radix al openapi.json del backend, con el hilo esmeralda atravesándolo todo.
Primitivas de interfaz
src/app/components/uishadcn/ui · 49 componentes sobre 28 primitivas de Radix
Layout propio
src/app/components/layoutPortalShell · PageHeader · ResponsiveDataTable
Módulos de dominio
20 módulosadmin/ · shipping/ · routes/ · operator/ · client/
Servicios generados
src/services15 servicios + types.ts emitidos por gen_services.py
API REST + WebSocket
45 rutasaxiomcoretech.store · openapi.json
Arquitectura, en detalle
Aplicación de una sola página servida por Vite bajo la ruta base `/humbolt/`, sin router: la navegación es estado de React. `src/main.tsx` monta `src/app/App.tsx`, que resuelve la sesión (cookie `AUTH_TOKEN` + caché de `/users/me`) y, según el rol mapeado por `current-user.service.ts`, renderiza uno de cuatro portales. La capa de presentación se organiza en tres niveles: primitivas de shadcn/ui en `src/app/components/ui` (49 componentes), primitivas de layout propias en `src/app/components/layout` (`PortalShell` para la cáscara con menú, `PageHeader` para el encabezado con acciones, `ResponsiveDataTable` para tablas que degradan a tarjetas en móvil), y módulos de dominio agrupados por carpeta: `admin/` (20 módulos: clientes, destinatarios, sucursales, choferes, tipos de destino, usuarios de cliente, feeds del dashboard), `shipping/` (gestión, formulario, carga masiva, etiquetas), `routes/`, `operator/` (app del chofer, escáner QR, captura de foto) y `client/`. La capa de datos vive en `src/services`: `config/axios.ts` centraliza la instancia con `baseURL` tomada de `VITE_API_URL` —en desarrollo el proxy de Vite reenvía `/api` y `/api/v1/ws` a `axiomcoretech.store`—, inyecta el Bearer desde la cookie, desactiva el `Content-Type` JSON cuando el payload es `FormData` o `Blob`, y aplica la política de errores y reintentos. Los quince servicios restantes y `types.ts` son generados por `gen_services.py` a partir del OpenAPI, de modo que los nombres de método reflejan literalmente el `operationId` del backend. Fuera del eje HTTP, `websocket.service.ts` es un singleton con lista de suscriptores para el feed en vivo, y `validation-error.service.ts` es un bus mínimo de suscripción que permite al interceptor abrir el modal global de validación desde fuera del árbol de React. Las utilidades transversales (`src/utils`) aíslan la lógica pura y comprobable: normalización de paginación, fecha y hora de rutas, mapeo de paradas a asignaciones, validación de documentos, conversión de data URL a `File` y construcción del PDF de comprobante; sobre ellas corren 34 pruebas en seis ficheros con Vitest y happy-dom.
Seis cosas que se rompieron primero
Cada losa del tablero se asentó después de un fallo concreto. Aquí está el fallo y aquí está lo que lo sostiene ahora.
Se rompió · 01
El escáner QR se reiniciaba en cada pulsación de tecla. Como `OperatorApp` vuelve a renderizarse mientras el operador escribe el código o el peso manualmente, los callbacks pasados a `QRScanner` cambiaban de identidad, el efecto de arranque de la cámara se relanzaba y `html5-qrcode` lanzaba “Cannot clear while scan is ongoing” y un AbortError que tumbaba la pantalla.
Se sostiene así
Los callbacks se guardan en refs (`onScanCompleteRef`, `onCancelRef`) que se actualizan en un efecto aparte, y el manejador de éxito se estabiliza con `useCallback` sin dependencias. Así el efecto que abre la cámara sólo corre al montar; además un `finishedRef` evita procesar dos lecturas y el cierre detiene y limpia el escáner de forma ordenada.
Se rompió · 02
El backend devuelve errores en tres formas distintas —`{status:'VALIDATION_ERROR', message, data}`, el `detail` de FastAPI (cadena o lista de `{loc,msg,type}`) y errores de integridad de base de datos—, y aplicar una sola política producía o modales de más o mensajes inútiles como “Error 500 del servidor”.
Se sostiene así
Un único interceptor de respuesta clasifica: el 422 siempre abre modal; 400, 409 y 500 muestran toast; cuando `data` trae el motivo concreto y `message` es genérico, gana el motivo concreto; el prefijo técnico del 500 sólo se antepone si no hubo mensaje legible del API. Los fallos de `/auth/login` y `/auth/register` se marcan como excepción para que los resuelva el formulario, y el duplicado de correo en PATCH de usuario se deja pasar para pintarse como tooltip en el campo.
Se rompió · 03
El PDF de comprobante generado por el servidor fallaba al serializar modelos ORM anidados (ciudad, país) del destinatario.
Se sostiene así
El comprobante se construye en el navegador con jsPDF a partir de los datos ya normalizados del API: `shipment-comprobante-pdf.ts` incluye helpers que extraen el `name` de objetos de localidad, resuelven el código o nombre de la zona postal y traducen los siete estados internos a español antes de dibujar el documento.
Se rompió · 04
Al crear una ruta, las paradas del formulario no tienen todavía identificador; sólo el `POST /routes/` devuelve las paradas reales, y los envíos deben asignarse a un `dropoff_stop_id` concreto del servidor.
Se sostiene así
`buildShipmentAssignmentsFromStops` empareja cada parada de la UI con la del servidor primero por posición —validando que coincida el `recipient_id`— y, si no cuadra, buscando por la combinación de `recipient_id` y `stop_number`. Devuelve un resultado tipado `{ok:true, assignments}` o `{ok:false, error}` con un mensaje que nombra la parada problemática, y está cubierto por pruebas unitarias.
Se rompió · 05
Un login lento seguido de un segundo intento dejaba llegar la respuesta vieja y sobrescribía el estado; y los reintentos automáticos de axios convertían un DELETE con 500 en varias peticiones y varios toasts idénticos.
Se sostiene así
El formulario de login usa un `AbortController` que cancela la petición anterior más un contador de intento que descarta cualquier respuesta que no sea la del intento vigente. En el interceptor, la lógica de reintento (hasta tres, con backoff de 1 s, 2 s y 3 s) se aplica sólo a errores de red y 5xx y excluye explícitamente el método DELETE.
Se rompió · 06
El proyecto nació como un bundle exportado de Figma Make: pantallas completas pero pobladas con datos simulados y sin contrato con ningún backend.
Se sostiene así
Se escribió `gen_services.py`, que descarga el `openapi.json` del backend y emite `types.ts` (98 interfaces y enums) y los servicios; la migración se hizo módulo a módulo sustituyendo el mock por la llamada real, con `normalizePaginationMeta` para tolerar las distintas formas de metadatos de paginación. Quedan aún en el repositorio pantallas heredadas del prototipo (Intelligence Hub, Reportes, Inventario, ShippingModule) que no están enganchadas a ningún menú.
El proyecto en ocho cifras
4
portales por rol en una sola build
5
roles de usuario soportados
45
rutas de API consumidas (REST + WebSocket)
98
tipos TypeScript generados desde el OpenAPI
20
módulos de administración
49
componentes de interfaz reutilizables
34
pruebas unitarias en 6 ficheros
35.688
líneas de TypeScript/TSX en 139 ficheros
Paleta
Institucional y sobrio: azul marino profundo como color de mando —barra lateral, encabezados, títulos— y un verde esmeralda que se reserva para lo que está vivo o confirmado: el estado activo del menú, la insignia de “Entregado”, el botón de escanear, el anillo de foco. Todo respira sobre un gris casi blanco, con tipografía Inter para el texto y JetBrains Mono para códigos de seguimiento, pesos y correos. Es la estética de un panel de control serio, no de una app de consumo.
El tablero, completo
Ocho módulos, cuatro portales, una sola pieza de software
Lo que empezó como un bundle exportado de Figma Make hoy habla con el OpenAPI de producción, sube fotos desde la calle y confirma paquetes contra la parada exacta de la ruta.
Humbolt MVP
¿Quieres algo así para tu producto?
Un ERP multi-rol conectado a una API real, con la calle y la oficina en la misma build. Si tu operación necesita portales por rol, evidencia en campo y un contrato de datos que no se escriba a mano, es exactamente el terreno que conozco.