Saltar a contenido

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 …), header X-Device-UUID obligatorio 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.

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&sector_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 Procesado correctamente
TAG_DESCONOCIDO sí (sanitario nullable) Tag no resuelto offline; trabajo con modo=OFFLINE_PENDIENTE_RESOLUCION + alerta
OPERARIO_INACTIVO 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.