Saltar a contenido

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í:

{ "empresa": { "id": 1 } }

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.

Pantalla Estructura, pestaña Empresas, con la fila EMPRESA-DEFAULT y el botón + Nueva 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
{
  "codigo": "AEP",
  "nombre": "Aeroparque Jorge Newbery",
  "direccion": "Av. Costanera Rafael Obligado s/n",
  "activo": true,
  "empresa": { "id": 1 }
}

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
{
  "codigo": "T-A",
  "nombre": "Terminal A",
  "activo": true,
  "sucursal": { "id": 1 }
}

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
{
  "codigo": "DAMAS",
  "nombre": "Damas",
  "activo": true
}

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.

Pantalla Sanitarios con la lista de baños, su sector/sucursal, tipo, tag asignado y estado 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
{
  "codigo": "AEP-T-B12",
  "nombre": "Baño Damas Terminal A planta 1",
  "descripcion": "Frente a puerta de embarque 12",
  "activo": true,
  "sector": { "id": 1 },
  "tipoSanitario": { "id": 1 }
}

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).

Pantalla Tags NFC con pestañas Todos/En stock/Asignados/Baja, el aviso de que el alta es por app y las acciones Desasignar/Reasignar 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.

Pantalla Operarios con pestañas Todos/Admin/Supervisor/Operador y la lista de operarios con su rol y estado 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 (userjhi_user es opcional)
Campos obligatorios usuario, nombre, apellido, activo, rol, nuevaPassword, nuevoPin
{
  "usuario": "jperez",
  "nombre": "Juan",
  "apellido": "Pérez",
  "telefono": "+54 11 5555-0000",
  "email": "jperez@corpal.com",
  "activo": true,
  "rol": "OPERADOR_LIMPIEZA",
  "nuevaPassword": "unaClaveSegura123",
  "nuevoPin": "4821"
}

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

Dónde: menú lateral Dispositivos, donde se listan y se habilitan/deshabilitan los dispositivos registrados.

Para que un teléfono pueda sincronizar, su Dispositivo tiene que estar registrado y activo. Un dispositivo no registrado (o deshabilitado) que llama a POST /api/v1/app/sync recibe 401 (cuerpo DEVICE_REVOCADO cuando estaba revocado).

Verificación incompleta en las fuentes: el alta de un dispositivo de operario

Las fuentes de verdad no documentan un endpoint de alta explícito para el celular de un operador. Lo que sí está verificado:

  • El CRUD scaffold POST /api/dispositivos existe y acepta deviceUuid, nombre, apiKeyHash, activo. Pero expone/recibe el apiKeyHash directamente, lo cual no parece el camino de provisioning pensado para un celular (habría que precalcular el hash de la api key por fuera). Tratalo con cuidado.
  • El provisioning verificado es el de la terminal Smiley: POST /api/v1/smiley/vincular {codigo} canjea un código de un solo uso y devuelve device_uuid + api_key en texto plano una sola vez. Ese flujo es para terminales, no para la app del operario.
  • Para la operación del BackOffice sobre dispositivos ya existentes está confirmado: GET /api/v1/admin/dispositivo (lista, sin api_key_hash), POST /api/v1/admin/dispositivo/{id}/deshabilitar y /habilitar — que es lo que hacen los botones de la pantalla Dispositivos.

No inventamos un endpoint de alta de dispositivo de operario que no esté en las fuentes. Si en tu deploy el celular se registra solo en el primer login (auto-provisioning por X-Device-UUID) o por otro mecanismo, confirmalo contra el código del backend antes de documentarlo como definitivo. Lo que sí podés garantizar como admin desde la pantalla Dispositivos: que el dispositivo termine activo para que el sync funcione.

Resultado verificable: en Dispositivos el equipo del operario figura como activo y, tras el primer sync, con su ultimo_sync 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.

Pantalla Rutas de limpieza con la lista de plantillas, su número de paradas y estado, y el botón + Nueva ruta 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)
{
  "nombre": "Recorrido matutino Terminal A",
  "descripcion": "Limpieza de apertura, planta 1",
  "activo": true,
  "paradas": [
    { "sanitario_id": 1, "orden": 1 },
    { "sanitario_id": 2, "orden": 2 },
    { "sanitario_id": 3, "orden": 3 }
  ]
}

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.

Pantalla Asignaciones de rutas con columnas Ruta, Operario, Días, Horario, Vigencia y Estado, y el botón + Nueva asignación 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_semana es un patrón de 7 caracteres Lun..Dom; las letras presentes marcan los días activos (ej. LMXJV-- = lunes a viernes). Patrón mal formado → 400.
  • vigencia_hasta puede ser null (vigencia indefinida, ); si viene, debe ser >= vigencia_desde.
  • El cruce de medianoche está permitido (hora_hasta antes que hora_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
{
  "ruta_id": 1,
  "operario_id": 7,
  "dias_semana": "LMXJV--",
  "hora_desde": "06:00:00",
  "hora_hasta": "14:00:00",
  "vigencia_desde": "2026-06-22",
  "vigencia_hasta": null,
  "activo": true
}

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, default 0 0 4 * * * — las 04:00) recorre las asignaciones activas cuyo día matchea dias_semana y que están en vigencia, y por cada una crea la RutaEjecucion de 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 HECHA automá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.

Dashboard con filtros de Sucursal y Sector y la grilla de sanitarios con estado, última limpieza, SLA y alertas 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.

Pantalla Rutas de hoy con filtros de fecha, sucursal y estado, y el botón Refrescar 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=&sector_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.