Montar un entorno desde cero (alta y asociación de entidades)¶
Esta guía te lleva, paso a paso y en el orden correcto, a dar de alta un entorno operativo completo de WorkDone Sanitarios desde una base vacía. Al terminar tenés un entorno operativo: una empresa con sucursales, sectores, sanitarios con sus tags, operarios, y rutas asignadas — listo para que el personal de campo empiece a tapear.
Todo lo que hacés acá se hace en el BackOffice web: la aplicación React que el propio backend sirve en http://localhost:8080 (en desarrollo entrás con admin / admin). Cada paso de esta guía es una pantalla real del menú lateral. Para los integradores que prefieren el API, debajo de cada paso dejamos el endpoint y el JSON exactos en un bloque "Bajo el capó".
El orden importa: cada entidad depende de la anterior por una FK (foreign key). No podés crear un Sector sin su Sucursal, ni un Sanitario sin su Sector. Seguí la secuencia y no vas a chocar contra un 404 de "padre inexistente".
A quién apunta esta guía
A un administrador (rol ADMIN / ROLE_ADMIN) que opera el BackOffice. La sesión web de admin y todos los endpoints /api/* del CRUD scaffold y los /api/v1/admin/* exigen ROLE_ADMIN; un operario de campo recibe 403. Si todavía no sabés cómo se autentica el BackOffice, mirá Sincronización y autenticación.
El orden de dependencias¶
Este diagrama es el mapa de toda la guía. Cada flecha es una dependencia: la entidad de destino necesita que la de origen exista primero.
flowchart TD
E[1 Empresa] --> S[2 Sucursal]
S --> SE[3 Sector]
TS[4 TipoSanitario] --> SAN[5 Sanitario]
SE --> SAN
SAN --> SLA[6 SlaLimpieza 1 a 1]
SAN --> TAG[7 TagNfc alta y asignacion]
OP[8 Operario] --> DEV[9 Dispositivo]
SAN --> RUTA[10 Ruta y paradas]
RUTA --> ASIG[11 Asignacion operario a ruta]
OP --> ASIG
ASIG --> EJEC[12 Ejecucion del dia generada por scheduler]
Cómo se expresan las FKs bajo el capó (CRUD scaffold JHipster)
Cuando creás una entidad desde la pantalla, el BackOffice arma el body por vos. Si vas por API directo, recordá: en el CRUD scaffold (/api/*) una FK no se manda como empresa_id, se manda como un objeto anidado con el id del padre. Por ejemplo, una Sucursal referencia a su empresa así:
En cambio, los endpoints de rutas (/api/v1/admin/ruta*) usan un contrato propio con FKs planas en snake_case (ruta_id, operario_id). Cada paso de abajo muestra el formato exacto que corresponde — no los mezcles.
Paso 1 — Empresa (la raíz)¶
Dónde: menú lateral Estructura → pestaña Empresas → botón + Nueva.
La Empresa es el cliente del servicio (ej. AA2000). No tiene dependencias: es la raíz de toda la jerarquía. No es multi-tenant; es solo el primer nivel de agrupación. En la práctica suele haber una sola (EMPRESA-DEFAULT); el nivel existe para futuras expansiones.
La pantalla Estructura concentra los cuatro primeros niveles de la jerarquía en pestañas: Empresas, Sucursales, Sectores y Tipos de sanitario. Cada pestaña tiene su propio botón + Nueva y su lista.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/empresas |
| FK que necesita | Ninguna |
| Campos obligatorios | codigo, nombre, activo |
{
"codigo": "AA2000",
"nombre": "Aeropuertos Argentina 2000",
"activo": true,
"visibleOpiniones": false
}
El 201 Created devuelve el EmpresaDTO con su id. Lo necesitás en el paso 2.
Resultado verificable: la nueva empresa aparece en la lista de la pestaña Empresas con estado Active.
Paso 2 — Sucursal (FK → Empresa)¶
Dónde: Estructura → pestaña Sucursales → + Nueva. Al crearla, elegís la empresa del paso 1 como padre.
Una Sucursal es una sede del cliente (ej. AEP, EZE). Pertenece a una Empresa.
Plural a la inglesa (solo en el API)
El path es /api/sucursals (así pluralizó JHipster), no /api/sucursales. Es una decisión deliberada del proyecto: el dominio en español vive en las entidades, no en las rutas. La UI siempre dice "Sucursales".
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/sucursals |
| FK que necesita | empresa → del paso 1 |
| Campos obligatorios | codigo, nombre, activo, empresa |
Resultado verificable: la sucursal aparece en la pestaña Sucursales. Su id lo usás en el paso 3.
Paso 3 — Sector (FK → Sucursal)¶
Dónde: Estructura → pestaña Sectores → + Nueva, eligiendo la sucursal del paso 2.
Un Sector es una zona dentro de la sucursal (ej. Terminal A, Patio de comidas).
El código de Sector es único DENTRO de la sucursal
Existe una UK compuesta (sucursal_id, codigo). Podés tener un sector "T-A" en AEP y otro "T-A" en EZE sin conflicto, pero no dos "T-A" en la misma sucursal.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/sectors |
| FK que necesita | sucursal → del paso 2 |
| Campos obligatorios | codigo, nombre, activo, sucursal |
Resultado verificable: el sector aparece en la pestaña Sectores. Su id lo usás en el paso 5.
Paso 4 — TipoSanitario (catálogo, sin FK)¶
Dónde: Estructura → pestaña Tipos de sanitario. Suele venir ya sembrado (seed: DAMAS, CABALLEROS, DISCAPACITADOS, MIXTO); en ese caso solo revisás la lista y saltás al paso 5. Si falta alguno, + Nueva.
TipoSanitario es un catálogo chico que clasifica los sanitarios.
Chequeá el seed antes de crear
Mirá primero qué hay en la pestaña. Si los tipos ya están sembrados, no los dupliques: los reusás al crear el sanitario en el paso 5.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/tipo-sanitarios (alta) · GET /api/tipo-sanitarios (chequear seed) |
| FK que necesita | Ninguna |
| Campos obligatorios | codigo, nombre, activo |
Resultado verificable: la pestaña Tipos de sanitario lista los tipos disponibles, listos para asignar.
Paso 5 — Sanitario (FK → Sector + TipoSanitario)¶
Dónde: menú lateral Sanitarios → botón + Nuevo. Elegís el sector (paso 3) y, opcionalmente, el tipo de sanitario (paso 4).
El Sanitario es el baño concreto donde va pegada la placa NFC. Es el centro de gravedad del sistema: la historia de limpiezas, las opiniones y el SLA viven en él.
La pantalla Sanitarios lista cada baño con su código, sector y sucursal, tipo, el tag NFC asignado (paso 7) y su estado. El botón + Nuevo abre el alta.
El código de Sanitario es único GLOBAL
A diferencia del Sector, el codigo del Sanitario (ej. "AEP-T-B12") es único en todo el sistema, no por sucursal.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/sanitarios |
| FK que necesita | sector (obligatoria, paso 3) + tipoSanitario (opcional, paso 4) |
| Campos obligatorios | codigo, nombre, activo, sector |
Resultado verificable: el sanitario aparece en la lista de Sanitarios (todavía sin tag). Su id lo usás en los pasos 6, 7 y 10.
Paso 6 — SlaLimpieza (1:1 con el Sanitario)¶
Dónde: se configura desde la ficha del sanitario (en el detalle/edición de la pantalla Sanitarios). Es 1:1: como máximo un SLA por sanitario (sanitario es UK).
La SlaLimpieza define la exigencia de limpieza de un sanitario: cada cuánto, en qué ventana horaria y qué días.
diasSemana y los días sin exigencia
diasSemana es un patrón de 7 caracteres Lun..Dom (LMXJVSD). Una letra presente = se exige limpieza ese día; ausente = el sanitario queda en estado SIN_EXIGENCIA (gris, sin alertas SLA). El default del schema es 'LMXJVSD' (todos los días). Más detalle en Reglas operativas.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/sla-limpiezas |
| FK que necesita | sanitario → del paso 5 |
| Campos obligatorios | frecuenciaMin, ventanaDesde, ventanaHasta, activo, sanitario |
{
"frecuenciaMin": 120,
"ventanaDesde": "06:00:00",
"ventanaHasta": "23:00:00",
"duracionMinimaSeg": 30,
"diasSemana": "LMXJVSD",
"activo": true,
"sanitario": { "id": 1 }
}
Validaciones: frecuenciaMin está acotado (mín. 5, máx. 1440 min). ventanaDesde/ventanaHasta son horas (LocalTime, formato HH:mm:ss), no strings libres.
Resultado verificable: el sanitario ya tiene su reloj de SLA; en el Dashboard (paso 12) aparece su columna SLA con la frecuencia (ej. c/60m). Recordá que solo una limpieza válida lo resetea.
Paso 7 — TagNfc: alta de stock + asignación al sanitario¶
Dónde: menú lateral Tags NFC. La pantalla lista todos los tags con sus pestañas (Todos / En stock / Asignados / Baja) y permite Desasignar / Reasignar desde el BackOffice.
Ponerle una placa física a un sanitario son dos transiciones de la máquina de estados del tag: primero alta de stock (EN_STOCK), después asignación al sanitario (ASIGNADO).
La pantalla Tags NFC muestra el ciclo de vida de cada tag (alias, UUID, estado, sanitario, fecha de asignación). El propio BackOffice avisa: "El alta de tags se hace desde la app móvil (requiere leer el chip NFC). Desde acá solo podés gestionar los tags ya registrados."
El alta de stock es SOLO desde la app móvil (rol ADMIN)
Dar de alta un tag requiere leer el chip onsite, así que el alta de stock (POST /api/v1/app/nfc/alta-stock) se hace exclusivamente desde la app móvil con un operario ADMIN. No existe alta de stock por web — la pantalla Tags NFC lo deja claro. La asignación, en cambio, se puede hacer desde la app o desde acá.
Como es un flujo con sus propias reglas (whitelist estricta, normalización de UUID, máximo 1 tag asignado por sanitario, auditoría en TagNfcEvento), está documentado aparte:
➡️ Seguí la guía dedicada: Registrar y asignar un tag NFC.
Resultado verificable: en la pestaña Asignados de Tags NFC, tu tag figura ligado al sanitario del paso 5. Bajo el capó, GET /api/v1/app/nfc/resolver/{uuid} devuelve { "estado": "ASIGNADO", "sanitario": { ... } }.
Paso 8 — Operario (con su rol)¶
Dónde: menú lateral Operarios → botón + Nuevo.
El Operario es la identidad del personal en la app móvil. Su rol decide qué hace en el sistema.
La pantalla Operarios lista al personal con su usuario, nombre, rol (chip de color), teléfono, email y último login. Las pestañas superiores filtran por rol; las acciones por fila incluyen editar, resetear password (ícono de llave) y resetear PIN.
Valores de rol (enum RolOperario):
| Rol | Qué hace |
|---|---|
OPERADOR_LIMPIEZA |
Operario de campo; sus taps crean trabajos LIMPIEZA. |
SUPERVISOR |
Supervisa; sus taps crean trabajos SUPERVISION. |
ADMIN |
No crea trabajos por tap — su app es la de Gestión NFC (alta/asignación de tags). |
La password y el PIN son write-only y obligatorios en el alta
nuevaPassword (mín. 8, máx. 128 chars) y nuevoPin (4 a 8 dígitos) se cargan en el alta y el server los hashea con BCrypt. Los hashes (pinHash, passwordHash) tienen @JsonIgnore: nunca vuelven en la respuesta ni viajan al móvil. Para cambiarlos después, usá las acciones de la fila (resetear password / resetear PIN), que bajo el capó son POST /api/admin/operarios/{id}/reset-password y /reset-pin.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/operarios (exige ROLE_ADMIN) |
| FK que necesita | Ninguna obligatoria (user → jhi_user es opcional) |
| Campos obligatorios | usuario, nombre, apellido, activo, rol, nuevaPassword, nuevoPin |
Resultado verificable: el operario aparece en la lista de Operarios con estado Activo. Ya puede loguearse en la app (POST /api/v1/app/auth/login-password o login-pin). Su id lo usás en el paso 11.
Paso 9 — Dispositivo del operario (alta + vinculación por código, flujo B)¶
Dónde: menú lateral Dispositivos, donde se listan los dispositivos, se dan de alta, se generan/regeneran códigos de vinculación y se habilitan/deshabilitan/desvinculan.
La pantalla Dispositivos: "Registrá uno y entregá el código al operario para vincular su teléfono". Cada fila muestra estado (Activo), vinculación (Vinculado / Sin vincular) y acciones (Desvincular).
Para que un teléfono pueda loguear y sincronizar, su Dispositivo tiene que estar registrado y activo. El login valida el device por el header X-Device-UUID antes de emitir tokens (ADR-044): un device no registrado o deshabilitado no obtiene tokens ni aunque las credenciales sean correctas, y un POST /api/v1/app/sync de un device no registrado o revocado recibe 401 (DEVICE_NO_REGISTRADO o DEVICE_REVOCADO).
Esto genera un huevo-y-gallina: el teléfono necesita estar registrado para loguear, pero su device_uuid real solo se conoce desde el propio teléfono. El flujo B (vinculación por código) lo resuelve, con el mismo patrón que la terminal Smiley: el admin da de alta el dispositivo y genera un código; el operario ingresa ese código en la app y el teléfono aporta su device_uuid real al canjearlo.
flowchart TD
A[Admin en BackOffice, pantalla Dispositivos] --> B[POST alta con solo el nombre]
B --> C[Backend crea APP_OPERARIO con device_uuid placeholder PENDIENTE y devuelve un codigo de 8 chars valido 2 horas]
C --> D[Admin le pasa el codigo al operario]
D --> E[Operario en la app, link Vincular con codigo, ingresa el codigo]
E --> F[App llama POST api v1 app vincular con codigo mas su device_uuid real]
F --> G[Backend fija el device_uuid real y marca el codigo como usado]
G --> H[El operario ya puede loguear normal, el device quedo registrado y activo]
En el BackOffice (pantalla Dispositivos):
- Alta: creás el dispositivo poniendo solo el nombre (ej. "Celular Juan Pérez"). El backend crea el registro
APP_OPERARIOcon undevice_uuidplaceholder (PENDIENTE-*, por eso figura como no vinculado) y te devuelve un código de vinculación de 8 caracteres, válido 2 horas. Anotalo: se muestra para dárselo al operario. - Regenerar código: si el código venció o lo perdiste, lo regenerás con un click. Es idempotente: si el device pendiente ya tiene un código vigente, devuelve el mismo; solo genera uno nuevo si no hay o está vencido.
- Habilitar / Deshabilitar: togglean
activo. Deshabilitar es la revocación (escenario de robo): en el próximo sync el teléfono recibe401 DEVICE_REVOCADOy se bloquea solo. - Desvincular: rompe el binding — resetea el
device_uuida un placeholder, limpia el código y deja el deviceactivo=false. El teléfono que tenía ese uuid deja de estar registrado (su próximo sync/login daDEVICE_NO_REGISTRADO) y la app reabre el enrolamiento (vuelve a mostrar el link "Vincular con código"). Para reusar el registro: generás un código nuevo y el operario vuelve a vincular.
En la app móvil (lo hace el operario): en el login aparece el link "Vincular con código" (solo mientras el device no esté vinculado). Lo toca, ingresa el código que le dio el admin y listo: el teléfono queda registrado y ya puede loguear con usuario + PIN/password. El detalle del flujo del lado app está en App móvil.
El paralelo con la terminal Smiley (y en qué se diferencian)
Los dos son "vinculación por código de un solo uso": la app del operario usa POST /api/v1/app/vincular {codigo, device_uuid} y la terminal Smiley usa POST /api/v1/smiley/vincular {codigo}. La diferencia clave: la terminal recibe una api_key en texto plano (una sola vez) porque se autentica con headers X-Device-Uuid + X-Api-Key; la app del operario NO recibe api_key — autentica por JWT tras loguear. Por eso el api_key_hash del dispositivo del operario es solo un placeholder hasheado y nunca se expone.
No existe alta de dispositivo por CRUD scaffold pensada para esto
El alta correcta del celular de un operario es POST /api/v1/admin/dispositivo (flujo B), no el CRUD scaffold /api/dispositivos. El flujo B nunca pide ni devuelve el apiKeyHash: el admin solo pone el nombre y el backend se encarga del resto.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Alta | POST /api/v1/admin/dispositivo con body { "nombre": "..." } → 201 {dispositivo, codigo, codigo_expira_en}. nombre vacío → 400. |
| Regenerar código | POST /api/v1/admin/dispositivo/{id}/codigo-vinculacion → 200 {codigo, codigo_expira_en} (idempotente). 404 si no existe · 400 si no es APP_OPERARIO · 409 si ya está vinculado. |
| Canje (lo hace la app, público) | POST /api/v1/app/vincular con {codigo, device_uuid} → {device_uuid, nombre}. Sin auth, rate-limited por IP. 400 código inválido · 410 usado/expirado · 409 device_uuid ya registrado en otro device · 429 rate limit. |
| Listar | GET /api/v1/admin/dispositivo → [{id, device_uuid, nombre, activo, vinculado, ultimo_sync, drift_seg}] (sin api_key_hash; vinculado=false mientras el uuid sea el placeholder PENDIENTE-*). |
| Habilitar / Deshabilitar | POST /api/v1/admin/dispositivo/{id}/habilitar · /{id}/deshabilitar (togglean activo; 404 si no existe). |
| Desvincular | POST /api/v1/admin/dispositivo/{id}/desvincular → 200 rompe el binding (vinculado=false, código limpio, activo=false; 404 si no existe). |
El codigo_vinculacion es único en DB (índice filtrado, migration 037). El device_uuid real lo aporta el teléfono al canjear el código, nunca el admin.
Resultado verificable: tras el alta, el dispositivo aparece en Dispositivos como no vinculado y con su código a la vista. Cuando el operario canjea el código desde la app, pasa a vinculado y activo; tras el primer sync, su ultimo_sync queda poblado.
Paso 10 — Ruta y sus paradas (FK → Sanitario)¶
Dónde: menú lateral Rutas → botón + Nueva ruta.
Una Ruta es una plantilla reusable: una lista ordenada de sanitarios (las paradas) que define el recorrido. La ruta es una capa de planificación arriba del tap, nunca un bloqueo: un tap fuera de ruta no es un error.
La pantalla Rutas de limpieza lista cada plantilla con su nombre, descripción, #Paradas y estado. El botón + Nueva ruta abre el alta donde agregás las paradas (sanitarios) en orden.
Contrato snake_case + paradas anidadas (al ir por API)
Este endpoint no es CRUD scaffold: usa un contrato propio con campos en snake_case y las paradas embebidas en el body. El orden de cada parada es >= 1 y la combinación (ruta, sanitario) es única (no repitas el mismo sanitario dos veces en una ruta).
El orden es sugerido, no obligatorio (R1)
El orden marca el "siguiente sugerido" en la app, pero cualquier parada puede hacerse en cualquier orden. La ruta nunca impide un tap.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/v1/admin/ruta (exige ROLE_ADMIN) |
| FK que necesita | sanitario_id de cada parada → del paso 5 |
| Campos obligatorios | nombre, activo, paradas (array, puede ir vacío) |
Resultado verificable: la ruta aparece en la lista de Rutas con su contador de #Paradas y estado Activa. Su id lo usás en el paso 11.
Paso 11 — Asignar el operario a la ruta (FK → Ruta + Operario)¶
Dónde: menú lateral Asignaciones → botón + Nueva asignación. Elegís la ruta (paso 10) y el operario (paso 8).
La RutaAsignacion conecta una ruta con un operario de forma recurrente (qué días, en qué franja horaria, durante qué vigencia). De acá salen, automáticamente, las ejecuciones de cada día.
La pantalla Asignaciones de rutas muestra cada asignación recurrente: la ruta, el operario, los días activos, el horario (ej. 08:00–16:00), la vigencia y el estado. El subtítulo lo resume: "El scheduler genera ejecuciones automáticamente."
Reglas de la asignación
dias_semanaes un patrón de 7 caracteresLun..Dom; las letras presentes marcan los días activos (ej.LMXJV--= lunes a viernes). Patrón mal formado →400.vigencia_hastapuede sernull(vigencia indefinida,∞); si viene, debe ser>= vigencia_desde.- El cruce de medianoche está permitido (
hora_hastaantes quehora_desde). - Se permiten N asignaciones por operario (sin solape exigido). El caso típico es 1.
Bajo el capó (API)
| Qué | Detalle |
|---|---|
| Endpoint | POST /api/v1/admin/ruta-asignacion (exige ROLE_ADMIN) |
| FK que necesita | ruta_id (paso 10) + operario_id (paso 8) |
| Campos obligatorios | ruta_id, operario_id, dias_semana, hora_desde, hora_hasta, vigencia_desde, activo |
Cómo se generan las ejecuciones del día¶
La asignación es la plantilla recurrente; lo que el operario ve cada día es una RutaEjecucion concreta:
- Un scheduler nocturno (
@Scheduled, default0 0 4 * * *— las 04:00) recorre las asignaciones activas cuyo día matcheadias_semanay que están en vigencia, y por cada una crea laRutaEjecucionde hoy (PENDIENTE) con sus paradas como snapshot de la plantilla. Es idempotente. (Detalle de schedulers en Jobs y alertas.) - Una limpieza válida (operador) o una supervisión cerrada (supervisor) sobre un sanitario de la ruta marca esa parada
HECHAautomáticamente — vía evento, el tap no sabe de rutas. - Reasignación del día: si el operario titular falta, movés la ejecución de hoy a otro operario desde la pantalla Rutas de hoy, sin tocar la asignación recurrente. Bajo el capó:
PATCH /api/v1/admin/ruta-ejecucion/{id}/reasignar
Content-Type: application/json
{ "operario_id": 9 }
La reasignación es solo del día y con estados acotados
Solo aplica a ejecuciones de hoy en estado PENDIENTE/EN_CURSO (400 si no es de hoy, 409 si está en otro estado, 404 si el operario o la ejecución no existen). Las paradas ya HECHA conservan su trabajo. La gestión de licencias/francos está fuera de alcance del módulo.
Resultado verificable: la asignación aparece en la lista de Asignaciones con sus días, horario y vigencia. Tras correr el scheduler (o el día siguiente), la ejecución del día se ve en Rutas de hoy (ver paso 12).
Paso 12 — Verificación final del entorno¶
Cerrás el círculo confirmando que las dos puntas — operario en campo y administrador en las pantallas — ven lo que tienen que ver.
El Dashboard pinta tus sanitarios¶
Dónde: menú lateral Dashboard. Filtrás por sucursal y sector y ves la grilla en tiempo real.
El Dashboard muestra el estado actual de cada sanitario: estado (Libre/Limpiando/SLA vencido/Sin datos/Sin exigencia), última limpieza, hace cuánto, el SLA configurado (paso 6) y alertas. Es la vista de "todo en orden".
El tablero de Rutas de hoy¶
Dónde: menú lateral Rutas de hoy. Filtrás por fecha, sucursal y estado, y desde acá reasignás las ejecuciones PENDIENTE/EN_CURSO.
La pantalla Rutas de hoy lista las ejecuciones generadas por el scheduler para la fecha elegida, con su operario efectivo, estado y progreso (paradas_hechas/total). Si todavía no corrió el scheduler para hoy, muestra "Sin ejecuciones para los filtros seleccionados".
Resumen de las verificaciones¶
| Verificación | Cómo |
|---|---|
| El operario ve "mi ruta de hoy" | En el response de POST /api/v1/app/sync aparece el bloque mi_ruta_hoy con las ejecuciones de hoy del operario y sus paradas. (Ausente si no tiene ruta hoy.) |
| El dashboard muestra los sanitarios | Pantalla Dashboard. Bajo el capó, GET /api/v1/admin/dashboard/estado?sucursal_id=§or_id=. |
| El tablero de rutas del día | Pantalla Rutas de hoy. Bajo el capó, GET /api/v1/admin/ruta-ejecucion?fecha=&sucursalId= lista las ejecuciones con progreso y permite reasignar. |
Entorno operativo
Si el sync del operario trae mi_ruta_hoy y el Dashboard pinta tus sanitarios, el entorno está montado: jerarquía física, tags, operarios, dispositivo activo y rutas asignadas, todo encadenado por sus FKs.
Ver también¶
- Registrar y asignar un tag NFC — el detalle del paso 7 (alta de stock + asignación), que se hace por la app móvil.
- Modelo de datos — todas las entidades, columnas y FKs de las que habla esta guía.
- Jobs y alertas — el scheduler que genera las ejecuciones del día y los demás jobs.
- Portal del cliente — cómo el cliente final ve el estado de sus sanitarios una vez que el entorno está operativo.