Inventario Inteligente
Universidad Nacional de Colombia · 2026 - Actualmente
El Centro de Prototipado presta herramientas, componentes y materiales desde un conjunto de gabinetes, y hasta ahora nadie podía responder "¿quién tiene la estación de soldadura y desde cuándo?" sin preguntar por ahí. Esta aplicación full-stack reúne gabinetes, inventario y reservas en un solo lugar, registra cada movimiento y puede abrir un gabinete por MQTT desde la misma pantalla.
Arquitectura y Stack Tecnológico
Arquitectura Central
- Framework: Next.js 16 (App Router), server-first, con el React Compiler activado
- Lenguaje: TypeScript 5.9 en modo estricto
- Plataforma backend: Supabase — PostgreSQL, Auth y Realtime (
@supabase/ssr) - Validación: esquemas Zod como única puerta de entrada de cada server action
- Puente con hardware:
mqttsobremqtts(puerto 8883) contra un broker compatible con HiveMQ - Tablas y gráficas: TanStack Table para las grillas de administración, Recharts para el panel
- Exportación: jsPDF + jspdf-autotable para los reportes de inventario
- UI: shadcn/ui (Radix Nova) + Tailwind CSS 4,
next-themespara claro/oscuro
Arquitectura en Capas
Lecturas y escrituras están separadas por carpeta, no por convención: toda mutación vive en lib/actions/* y toda consulta en lib/data/*. Una página nunca consulta Supabase en línea y un formulario nunca lee — que es justo lo que mantiene las verificaciones de autorización en un único sitio por operación.
Flujo de una Petición
Características Principales
Características de un Vistazo
Gabinetes y apertura rápida
Cada gabinete está modelado digitalmente con su contenido y su estado. Desde el panel de administración se puede abrir directamente: la acción publica un comando al broker MQTT con su propio timeout y manejo de error, así un broker caído se ve como una acción fallida y no como un spinner que nunca termina.
Reservas
Estudiantes y personal solicitan materiales; el equipo aprueba o rechaza desde el mismo panel. La reserva tiene su propio ciclo de vida y aparece en el historial del ítem, de modo que un equipo prestado no puede quedar reservado dos veces en silencio.
Roles y acceso
Autenticación, perfiles y permisos por rol (admin / root controlan toda el área /admin). El acceso se verifica en tres capas: refresco de sesión en el proxy de petición, gate de rol en el layout de administración y assertAdmin() dentro de las propias acciones.
Historial de acceso y sesiones
Auditoría completa de aperturas de gabinete y uso de materiales, más una vista de línea de tiempo de sesiones: quién usó qué, cuándo y por cuánto tiempo.
Panel de administración
KPIs más cinco pestañas de analítica (general, inventario, mantenimiento, operaciones, reservas) construidas con Recharts, con un fallback de datos para que un RPC de analítica que falle degrade las cifras en vez de tumbar la página.
Mantenimiento
Los ítems llevan ficha e historial de mantenimiento, así una herramienta que vuelve rota una y otra vez se ve como un patrón y no como una serie de incidentes sueltos.
Destacados Técnicos
Un único resolver cacheado del usuario actual
getCurrentUser envuelve la búsqueda de usuario autenticado + perfil en el cache() de React, así un árbol de render que necesita al usuario en el layout, la página y tres componentes paga exactamente una consulta.
Las acciones devuelven estado, no lanzan
Toda acción de administración devuelve un AdminFormState serializable con error, fieldErrors y success en vez de lanzar. Los fallos de validación son datos, no excepciones — que es lo que permite que el mismo componente de formulario pinte errores por campo sin un try/catch en la UI.
Read models compuestos en TypeScript
lib/data/* no devuelve filas crudas: consulta varias tablas y compone un modelo de vista enriquecido (un Cabinet con su _count y sus item_names), así los componentes renderizan un objeto en vez de coser relaciones dentro del JSX.
Tiempo real como deltas, no como refetch
La grilla de gabinetes se suscribe con subscribeCabinetsGrid y aplica los eventos de broadcast como deltas en memoria. Dos personas trabajando el mismo gabinete ven los cambios de la otra sin que ninguna recargue.
Una tabla genérica para todos los módulos
components/tables/data-table.tsx carga el filtrado, ordenamiento y paginación una sola vez; cada módulo (gabinetes, inventario, usuarios, mantenimiento, reservas, actividad, sesiones) solo declara sus columnas tipadas.
Estructura del Proyecto
app/ ├── (home)/ # grilla de gabinetes para todo el equipo ├── (admin)/admin/ # inventario, gabinetes, categorías, mantenimiento, │ # reservas, sesiones, usuarios, actividad ├── (auth)/ # login, registro, recuperación + callback ├── history/ profile/ # vistas por usuario └── layout.tsx components/ ├── admin/ # una carpeta por módulo (tabla, formulario, acciones) ├── tables/data-table.tsx # filtrado/orden/paginación genéricos └── ui/ # primitivos de shadcn lib/ ├── actions/ # escrituras — un archivo por operación, con Zod │ └── cabinets/open-with-mqtt.ts # publica el comando de apertura ├── data/ # lecturas — un fetcher por vista ├── realtime/cabinets.ts # suscripciones de Supabase Realtime ├── schemas/ # esquemas Zod ├── types/ # modelos de dominio enriquecidos └── supabase/ # clientes browser, server, proxy + getCurrentUser proxy.ts # refresco de sesión en el borde de la petición
Impacto y Escalabilidad
- Todo movimiento de material queda trazado: quién, qué, cuándo y por cuánto tiempo.
- El puente MQTT está en código de producción, así que conectar un gabinete nuevo es una tarea de hardware, no un cambio de aplicación; las unidades ESP32 / Raspberry Pi que accionan las cerraduras se están desplegando.
- La separación lectura/escritura y la tabla genérica hacen que un módulo de administración nuevo sea, sobre todo, declarar esquema, fetcher, acción y columnas.
- Vacíos honestos: todavía no hay suite de pruebas automatizadas ni pipeline de CI en el repositorio.
Notas
Construido con Next.js 16, Supabase (PostgreSQL, Auth, Realtime), MQTT, TanStack Table, Recharts y shadcn/ui. El repositorio es privado — pertenece al Centro de Prototipado de la Universidad Nacional de Colombia.