API HTTP¶
El backend de WorkDone Sanitarios expone una API REST versionada. Esta página cubre las convenciones generales, los endpoints principales agrupados por consumidor, y en profundidad el endpoint /sync —el transaccional que mueve toda la operación offline-first.
Fuente de verdad
Los contratos de /api/v1/app/sync y /api/v1/smiley/sync son contratos compartidos: la fuente de verdad vive en docs/SYNC_PROTOCOL.md y docs/SYNC_PROTOCOL_SMILEY.md del backend, y las copias en los repos consumidores se generan por script. Para el detalle conceptual del flujo, ver Sincronización. Para las entidades, ver el modelo de datos.
Convenciones generales¶
- Versión 2 del contrato. Los endpoints custom van bajo
/api/v1/app/*,/api/v1/admin/*,/api/v1/cliente/*,/api/v1/publico/*y/api/v1/smiley/*. El CRUD scaffold de JHipster queda en/api/*(sin/v1) por compatibilidad. - Errores RFC 7807 (problem+json).
- Timestamps ISO 8601 con offset (ej.
2026-06-10T10:15:32.123-03:00). - Autenticación según consumidor (ver Autenticación y autorización):
- Operario móvil: JWT (
Authorization: Bearer …), headerX-Device-UUIDobligatorio en login/refresh. - Terminal Smiley: headers
X-Device-Uuid+X-Api-Key. - BackOffice / portal cliente: JWT.
- QR público: sin auth, rate-limited por IP.
- Operario móvil: JWT (
Capitalización del header de device (validado contra código)
El operario móvil y su filtro usan X-Device-UUID; la terminal Smiley usa X-Device-Uuid. Es una inconsistencia real en el código (AppAuthController.java / AuthInterceptor.kt vs SmileyApiKeyAuthFilter.java / SmileyAuthInterceptor.kt), pero inocua: los headers HTTP son case-insensitive por RFC 7230 y cada subsistema es internamente consistente.
Endpoints principales¶
La tabla resume los endpoints documentados, agrupados por consumidor. No es exhaustiva del CRUD scaffold.
Autenticación¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| POST | /api/authenticate |
Login BackOffice web (JHipster) | — |
| POST | /api/v1/app/auth/login-password |
Login operario por password | — (header X-Device-UUID) |
| POST | /api/v1/app/auth/login-pin |
Login operario por PIN | — (header X-Device-UUID) |
| POST | /api/v1/app/auth/refresh |
Rotación de refresh token | — (header X-Device-UUID) |
| POST | /api/v1/app/auth/logout |
Logout (valida por hash del refresh) | público |
App móvil — operación¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| POST | /api/v1/app/sync ★ |
Sync transaccional (pull deltas + push trabajos/NFC) | ROLE_OPERARIO |
| GET | /api/v1/app/me |
Perfil del operario (incluye rol) |
ROLE_OPERARIO |
| GET | /api/v1/app/trabajos/actual |
Trabajo abierto actual (expone tipo) |
ROLE_OPERARIO |
| GET | /api/v1/app/trabajos/historial?limit=50 |
Historial filtrado por operario + rol (incluye eventos[]) |
ROLE_OPERARIO |
| GET | /api/v1/app/eventos-limpieza |
Catálogo de eventos activo, filtrado por rol | ROLE_OPERARIO |
App móvil — gestión NFC¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| GET | /api/v1/app/nfc/resolver/{uuid} |
Resuelve estado del tag (NO_REGISTRADO\|EN_STOCK\|ASIGNADO\|BAJA) |
cualquier operario |
| POST | /api/v1/app/nfc/alta-stock |
Alta de tag en stock ({uuid_tag, alias}), idempotente |
solo ADMIN |
| POST | /api/v1/app/nfc/asignar |
Asigna tag a sanitario ({uuid_tag, sanitario_id}) |
solo ADMIN |
| POST | /api/v1/app/nfc/desasignar |
Desasigna tag ({uuid_tag}) |
solo ADMIN |
| POST | /api/v1/app/nfc/reasignar |
Reasigna tag, transaccional ({uuid_tag, sanitario_nuevo_id, notas?}) |
solo ADMIN |
| GET | /api/v1/app/sanitarios/disponibles |
Árbol Sucursal›Sector›Sanitario con tiene_tag |
solo ADMIN |
Toda mutación NFC pasa por TagNfcLifecycleService
El servicio audita cada cambio (TagNfcEvento) y normaliza el UUID. Rol insuficiente → 403.
Admin (BackOffice) — selección¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| GET | /api/v1/admin/trabajos?desde&hasta&tipo&rol&operario_id&… |
Consulta de trabajos con filtros asociativos (AND) | ROLE_ADMIN |
| GET | /api/v1/admin/dashboard/estado?sucursal_id§or_id |
Grilla de estado en tiempo real + alertas activas | ROLE_ADMIN |
| GET | /api/v1/admin/alertas/activas |
Alertas activas | ROLE_ADMIN |
| POST | /api/v1/admin/alertas/{id}/atender |
Atender una alerta | ROLE_ADMIN |
| GET | /api/v1/admin/reportes/eventos?desde&hasta&codigo&… |
Ranking evento × sanitario/sector (con export) | ROLE_ADMIN |
| POST | /api/v1/admin/dispositivo/{id}/deshabilitar · /habilitar |
Revocación / rehabilitación de dispositivos | ROLE_ADMIN |
Terminal Smiley (kiosk)¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| GET | /api/v1/smiley/ping |
"Probar conexión" del setup ({version, server_time}) |
público |
| POST | /api/v1/smiley/vincular |
Canjea código de un solo uso → credenciales del device | público (rate-limited) |
| POST | /api/v1/smiley/sync |
Sync de opiniones (pull motivos + push opiniones) | ROLE_TERMINAL |
| GET | /api/v1/smiley/estado |
Refresca el idle ({ultima_limpieza_hace_min}) |
ROLE_TERMINAL |
Público (QR)¶
| Método | Ruta | Para qué | Auth |
|---|---|---|---|
| GET | /api/v1/publico/sanitario/{slug} |
Estado público del sanitario | sin auth (rate-limited por IP) |
| POST | /api/v1/publico/opinion |
Opinión anónima ({slug, valor, motivo_codigo?, client_uuid}) |
sin auth (rate-limited por IP) |
| GET | /api/v1/publico/motivos |
Catálogo de motivos activos | sin auth (rate-limited por IP) |
CRUD scaffold (JHipster, sin /v1)¶
Todo el CRUD scaffold /api/* exige ROLE_ADMIN. Endpoints verificados: /api/sanitarios, /api/tag-nfcs, /api/operarios, /api/sla-limpiezas, /api/dispositivos, /api/trabajos, /api/alertas, /api/empresas, /api/sucursals, /api/sectors, /api/tipo-sanitarios, /api/evento-limpiezas.
Pluralización a la inglesa
Se acepta la pluralización que genera JHipster (/api/sucursals, /api/sectors). El dominio en español vive en las entidades, no en los paths. /api/ubicacions dejó de existir en v2.
El endpoint /sync en profundidad¶
POST /api/v1/app/sync es el endpoint único transaccional del sync de operarios: en una sola llamada el cliente baja deltas de catálogos (pull) y sube su cola de trabajos y eventos NFC (push). Es retrocompatible con clientes v1.
Request¶
{
"device_uuid": "...",
"operario_id": 5,
"last_sync": { // null por tabla = pull completo de esa tabla
"sanitario": "...", "tag_nfc": "...", "operario": "...",
"sucursal": "...", "sector": "...", "tipo_sanitario": "...", "evento_limpieza": "..."
},
"trabajos_pendientes": [{
"client_uuid": "...", "uuid_tag_leido": "...", "operario_id": 5,
"ts_local_device": "2026-06-10T10:42:18.456-03:00",
"tipo_tentativo": "EGRESO",
"tipo": "LIMPIEZA", // v2 · LIMPIEZA | SUPERVISION · default LIMPIEZA
"eventos": [{ "evento_id": 3, "codigo": "REPONER_INSUMOS", "observacion": "..." }] // v2 · solo EGRESO
}],
"nfc_eventos_pendientes": [{ // v2 · cola de gestión NFC (rol ADMIN)
"client_uuid": "...", "accion": "ALTA_STOCK|ASIGNACION|DESASIGNACION|REASIGNACION",
"uuid_tag": "...", "alias": "...", "sanitario_id": 12, "sanitario_nuevo_id": null,
"ts_local_device": "..."
}],
"device_now": "2026-06-10T10:42:20.000-03:00", // ANEXO DT/DT-B4 · hora del device al enviar · OPCIONAL
"app_version": "0.1.0", // G1 · telemetría · OPCIONAL
"os_version": "Android 15",
"bateria_pct": 87,
"red_tipo": "WIFI" // WIFI | CELULAR | ETHERNET | OTRA | SIN_RED
}
Compatibilidad v1
tipo, eventos, nfc_eventos_pendientes, device_now y los 4 campos de telemetría son opcionales con defaults seguros (LIMPIEZA, [], [], sin medición de drift). Un cliente viejo que no los manda no rompe nada.
Response¶
{
"server_ts": "...", // nuevo watermark
"deltas": { /* updated[] + deleted_ids[] por cada uno de los 7 catálogos */ },
"trabajos_procesados": [{
"client_uuid": "...", "estado": "OK", "accion_real": "EGRESO",
"trabajo_id": 1235, "sanitario_id": 5,
"inicio_ts": "2026-06-10T10:15:32.000-03:00", // verdad del server (clave en cierre automático)
"fin_ts": "2026-06-10T10:42:18.000-03:00", // null si quedó abierto (INGRESO) o no se persistió trabajo
"duracion_seg": 1606,
"tipo": "LIMPIEZA", "eventos": [{ "codigo": "...", "nombre": "...", "observacion": "..." }],
"warnings": []
}],
"nfc_eventos_procesados": [{ "client_uuid": "...", "estado": "OK" }],
"mi_ruta_hoy": { /* ... */ }, // V21_RUTAS · ruta asignada al operario hoy · OPCIONAL (NON_NULL)
"config": { "silencio_cliente_seg": 8, "confirmar_egreso_min": 2 } // ANEXO DT/DT-B4 · config remota anti doble-tap
}
Notas de los deltas (7 catálogos): tag_nfc expone estado + alias (la app resuelve taps solo contra estado='ASIGNADO'); operario expone rol; sanitario expone sector_id + tipo_sanitario_id; evento_limpieza.roles_permitidos viaja como array JSON (["OPERADOR_LIMPIEZA","SUPERVISOR"]), no CSV.
Estados por trabajo¶
El campo estado de cada ítem de trabajos_procesados puede ser:
| Estado | ¿Persiste trabajo? | Significado |
|---|---|---|
OK |
sí | Procesado correctamente |
TAG_DESCONOCIDO |
sí (sanitario nullable) | Tag no resuelto offline; trabajo con modo=OFFLINE_PENDIENTE_RESOLUCION + alerta |
OPERARIO_INACTIVO |
sí | Se acepta con warning + alerta |
SANITARIO_INACTIVO |
no | El cliente purga sin reintentar |
DUPLICADO_IGNORADO |
sí (devuelve el existente) | client_uuid ya procesado |
ROL_NO_PERMITIDO |
no | Tap de un operario rol ADMIN; el cliente purga |
RECHAZADO_REBOTE |
no (queda en tap_descartado) |
Doble-tap accidental al salir; idempotente |
MANUAL_INVALIDO |
no | Registro manual sin sanitario/motivo válido |
Drift de reloj¶
Si el request trae device_now, el server estampa dispositivo.drift_seg = |device_now − server_now|. Si excede app.tap.drift-max-seg (default 300 s), genera una alerta RELOJ_DESVIADO por device (dedup por dispositivo_id), auto-atendida cuando un sync posterior reporta drift normal.
El drift nunca se mide con los ts_local_device de los ítems
La cola offline trae timestamps viejos legítimos. El drift solo se calcula con device_now (la hora del device al momento de enviar).
Códigos de error y conflictos¶
Errores RFC 7807. Códigos custom v2:
| Code | HTTP | Cuándo |
|---|---|---|
CONFLICTO_ASIGNACION |
409 | El sanitario ya tiene tag activo, el tag no está EN_STOCK, o conflicto concurrente |
TRANSICION_INVALIDA |
409 | Transición inválida en la máquina de estados del tag (ej. BAJA sobre ASIGNADO) |
ROL_INSUFICIENTE |
403 | Mutación NFC móvil con operario no-ADMIN |
EVENTO_NO_PERMITIDO |
403 | Evento fuera de los roles_permitidos del rol del operario |
EVENTO_EXCLUSIVO_COMBINADO |
400 | Evento exclusivo combinado con otros en el mismo cierre |
EVENTOS_REQUERIDOS |
400 | EGRESO normal sin eventos |
DEVICE_REVOCADO |
401 | Dispositivo con activo=false llamando a /sync |
| (sin code) | 400 | Header X-Device-UUID ausente/vacío en login/refresh |
La resolución de conflictos en el push (asignación NFC, máquina de estados del tag) se explica en detalle en Sincronización.