# Esquemas · Referencia

> Los objetos que la API acepta y devuelve, campo a campo, tal como los define el contrato OpenAPI.

Página: https://safeonuba.com/desarrolladores/referencia/esquemas · API de SafeOnuba v1 (`api_version: "2026-09-24"`), en vista previa: el contrato puede cambiar.

## Event

Tipo: `object`

- `id` (string (uuid), obligatorio): Para deduplicar: la entrega es «al menos una vez».
- `type` (EventType, obligatorio) Valores: `alert.created`, `alert.acknowledged`, `alert.resolved`, `alert.reopened`, `alert.worker_acknowledged`, `muster.started`, `muster.kind_changed`, `muster.phase_changed`, `muster.targets_changed`, `muster.updated`, `muster.worker_missing`, `muster.ended`.
- `api_version` (string, obligatorio) Valores: `2026-09-24`.
- `created_at` (string (date-time), obligatorio)
- `organization_id` (string (uuid) | null, obligatorio): La empresa del centro. `null` en los eventos de prueba.
- `site_id` (string (uuid), obligatorio)
- `data` ({ alert } | { muster }, obligatorio): El objeto completo tal como lo devolvería el `GET` en ese instante.
  - Variante 1:
    - `alert` (Alert, obligatorio)
  - Variante 2:
    - `muster` (Muster, obligatorio)

## EventType

Tipo: `string`

`muster.updated`: el recuento ha cambiado (se comprueba cada 5 s, solo se emite si cambia). `muster.targets_changed`: la sala ha descartado o rehabilitado un punto de reunión.

Valores: `alert.created`, `alert.acknowledged`, `alert.resolved`, `alert.reopened`, `alert.worker_acknowledged`, `muster.started`, `muster.kind_changed`, `muster.phase_changed`, `muster.targets_changed`, `muster.updated`, `muster.worker_missing`, `muster.ended`.

## Alert

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `type` (AlertType, obligatorio) Valores: `sos`, `fall_detected`, `restricted_zone_entry`, `forbidden_zone_entry`, `permit_expired_inside`, `permit_closed_inside`, `heat_stress`, `evacuation_help`, `worker_call_request`, `device_offline`, `low_battery`, `earthquake`, `tsunami_risk`.
- `severity` (Severity, obligatorio) Valores: `low`, `medium`, `high`, `critical`.
- `status` (AlertStatus, obligatorio) Valores: `unacknowledged`, `acknowledged`, `resolved`.
- `title` (string, obligatorio): Con `include_worker_names`, el título que lee la sala (lleva el nombre de la persona). Sin él, uno compuesto con el tipo, la zona y el `external_id`, sin nombre.
- `date_created` (string (date-time), obligatorio)
- `date_last_modified` (string (date-time), obligatorio): Para ordenar: los webhooks no garantizan el orden, y lo más viejo que lo que ya se tiene se descarta.
- `site` (SiteRef, obligatorio)
- `zone` (ZoneRef, obligatorio)
- `worker` (WorkerRef, obligatorio)
- `device` (DeviceRef, obligatorio)
- `location` (Location, obligatorio)
- `location_withheld` (boolean, obligatorio): La posición existe pero no se entrega: en «Solo en emergencia», una alerta por debajo de la gravedad que destapa no la enseña, tampoco en el panel.
- `details` (AlertDetails, obligatorio)
- `nearby` (array<Nearby>, obligatorio)
- `vehicle_warnings` (array<VehicleWarning>, obligatorio)
- `worker_acknowledged_at` (string (date-time) | null, obligatorio): El trabajador pulsó «Entendido» en su reloj (hora del reloj).
- `acknowledged` (ActionBy, obligatorio)
- `resolved` (object | null, obligatorio): Cerrada: quién, cuándo y con qué nota.
  - `at` (string (date-time), obligatorio)
  - `by` (string, obligatorio): Quién: una persona del panel, o «Cliente · Operador (vía integración)».
  - `note` (string | null, obligatorio)
- `assigned_to` (string | null, obligatorio)
- `simulated` (boolean, obligatorio): Generada en un simulacro o con relojes simulados.
- `panel_url` (string (uri), obligatorio)

## AlertType

Tipo: `string`

Qué ha pasado. `earthquake` y `tsunami_risk` son de la planta entera: no llevan `worker` ni `device`. Un vehículo cerca **no** es una alerta: sale dentro de la alerta de un incidente (`nearby`, `vehicle_warnings`).

Valores: `sos`, `fall_detected`, `restricted_zone_entry`, `forbidden_zone_entry`, `permit_expired_inside`, `permit_closed_inside`, `heat_stress`, `evacuation_help`, `worker_call_request`, `device_offline`, `low_battery`, `earthquake`, `tsunami_risk`.

## Severity

Tipo: `string`

Gravedad: `low` (información), `medium` (aviso), `high` (grave), `critical` (crítica: SOS, caída, «no puedo evacuar»).

Valores: `low`, `medium`, `high`, `critical`.

## AlertStatus

Tipo: `string`

`acknowledged`: un operador se ha hecho cargo. No es lo mismo que `worker_acknowledged_at`, que es que el trabajador ha visto el aviso en su reloj.

Valores: `unacknowledged`, `acknowledged`, `resolved`.

## SiteRef

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `name` (string, obligatorio)

## ZoneRef

Tipo: `object | null`

- `id` (string (uuid), obligatorio)
- `name` (string, obligatorio)

## WorkerRef

Tipo: `object | null`

- `id` (string (uuid), obligatorio)
- `external_id` (string | null, obligatorio): Código del trabajador en la empresa (el de su sistema de personal). `null` si no se ha dado.
- `name` (string): Nombre y apellidos. **Solo** si el contrato lo incluye; si no, la propiedad no aparece.
- `contractor` (string | null, obligatorio)

## DeviceRef

Tipo: `object | null`

- `id` (string (uuid), obligatorio)
- `serial` (string, obligatorio)
- `battery` (integer | null, obligatorio) (de 0 a 100)
- `date_last_seen` (string (date-time) | null, obligatorio)

## Location

Tipo: `object | null`

- `latitude` (number, obligatorio) (de -90 a 90)
- `longitude` (number, obligatorio) (de -180 a 180)
- `uncertainty_m` (number | null, obligatorio): Radio de incertidumbre en metros. `null` si no se conoce: no saberla no es tenerla perfecta. (mínimo 0)
- `source` (string, obligatorio): `zone`: la persona estaba en una zona de privacidad y el punto es el de la zona, no el suyo. Valores: `gps`, `network`, `zone`.
- `masked` (boolean, obligatorio): El punto es el representativo de una zona de privacidad.
- `position_date_utc` (string (date-time), obligatorio)
- `height` (object | null, obligatorio): Altura sobre el suelo por barómetro, cuando se puede afirmar; `null` si no.
  - `height_m` (number, obligatorio)
  - `uncertainty_m` (number, obligatorio)
  - `building` (string | null, obligatorio)
  - `floor` (string | null, obligatorio)

## AlertDetails

Tipo: `SosDetails | FallDetails | ZoneDetails | HeatStressDetails | EvacuationHelpDetails | WorkerCallDetails | DeviceOfflineDetails | LowBatteryDetails | HazardDetails`

Depende de `type`.

## SosDetails

Tipo: `object`

- `trigger` (string, obligatorio): Cómo se pidió: en la pantalla del reloj, con el botón físico, tras una caída sin respuesta o desde la sala. Valores: `watch_button`, `watch_hardware_button`, `after_fall`, `control_room`.

## FallDetails

Tipo: `object`

- `escalated_to_sos` (boolean, obligatorio): No contestó al «¿Estás bien?» del reloj y pasó a SOS.

## ZoneDetails

Tipo: `object`

- `zone_type` (string, obligatorio) Valores: `restricted`, `forbidden`.
- `permit_reference` (string | null, obligatorio): El permiso de trabajo (ATS) que regía, si lo había.

## HeatStressDetails

Tipo: `object`

Sin nada que salga del pulso: las constantes vitales no salen por la API, ni agregadas.

- `action` (string, obligatorio): Lo que se le ha dicho en el reloj: parar, beber y buscar sombra. No hay niveles. Valores: `stop_and_rest`.
- `heat_index_c` (number | null, obligatorio): Índice de calor de la planta (°C).
- `sun_exposure_minutes` (integer | null, obligatorio)

## EvacuationHelpDetails

Tipo: `object`

- `reason` (string | null, obligatorio): Lo que dijo en el reloj: atrapado, herido o ayudando a otra persona. Valores: `trapped`, `injured`, `helping`.
- `muster_id` (string (uuid), obligatorio)

## WorkerCallDetails

Tipo: `object`

- `voice_note` (boolean, obligatorio): Sin cobertura, el reloj grabó un aviso de voz (se escucha en el panel).

## DeviceOfflineDetails

Tipo: `object`

- `minutes_silent` (integer, obligatorio)

## LowBatteryDetails

Tipo: `object`

- `battery_pct` (integer, obligatorio) (de 0 a 100)

## HazardDetails

Tipo: `object`

- `magnitude` (number | null, obligatorio)
- `distance_km` (number | null, obligatorio)
- `official` (boolean, obligatorio): `false`: estimación de SafeOnuba sin aviso oficial. `true`: boletín oficial confirmado por una persona.
- `felt_at_site` (boolean, obligatorio)
- `eta` (string (date-time) | null, obligatorio): Llegada estimada de la ola (tsunami).

## Nearby

Tipo: `object`

Quién había a menos de 100 m cuando saltó una alerta crítica (SOS, caída). Distancia, nunca posición; quien estaba en una zona de privacidad no cuenta.

- `kind` (string, obligatorio) Valores: `person`, `vehicle`.
- `worker` (object, obligatorio)
  - `id` (string (uuid) | null, obligatorio): `null` en alertas anteriores al 24/09/2026, cuando aún no se copiaba.
  - `external_id` (string | null, obligatorio): Código del trabajador en la empresa (el de su sistema de personal). `null` si no se ha dado.
  - `name` (string): Nombre y apellidos. **Solo** si el contrato lo incluye; si no, la propiedad no aparece.
  - `contractor` (string | null, obligatorio)
- `vehicle_type` (string | null, obligatorio)
- `distance_m` (integer, obligatorio)
- `reported_at` (string (date-time), obligatorio)

## VehicleWarning

Tipo: `object`

Avisos de vehículo cerca que recibió esa persona en la ventana del incidente. Son avisos, no alertas.

- `at` (string (date-time), obligatorio)
- `vehicle_type` (string | null, obligatorio)
- `distance_m` (integer, obligatorio)
- `worker_acknowledged_at` (string (date-time) | null, obligatorio)

## ActionBy

Tipo: `object | null`

- `at` (string (date-time), obligatorio)
- `by` (string, obligatorio): Quién: una persona del panel, o «Cliente · Operador (vía integración)».

## Muster

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `site` (SiteRef, obligatorio)
- `status` (string, obligatorio) Valores: `active`, `completed`, `cancelled`.
- `phase` (string, obligatorio): `inspecting`: recuento completo y la sala revisa la planta; los relojes dicen «no vuelvas hasta nuevo aviso». Valores: `evacuating`, `inspecting`.
- `kind` (MusterKind, obligatorio) Valores: `general`, `tsunami`, `earthquake`.
- `reason` (string | null, obligatorio)
- `started_at` (string (date-time), obligatorio)
- `ended_at` (string (date-time) | null, obligatorio)
- `deadline` (object | null, obligatorio)
  - `at` (string (date-time), obligatorio)
  - `source` (string, obligatorio): Tsunami: hora de un aviso oficial, o el techo del plan de la zona. Nunca un cálculo físico. Valores: `official`, `plan_estimate`.
- `origin` (object | null, obligatorio): De dónde viene la emergencia. `null` en una evacuación sin origen (un simulacro general).
  - `latitude` (number, obligatorio)
  - `longitude` (number, obligatorio)
  - `radius_m` (integer, obligatorio)
  - `label` (string | null, obligatorio)
  - `zone` (ZoneRef, obligatorio)
- `targets_revision` (integer, obligatorio): Sube cada vez que cambian los destinos (descarte, rehabilitación, cambio de tipo).
- `totals` (object, obligatorio): Las mismas cifras que el banner del panel y el cierre: las cuenta el servidor. En una evacuación cerrada queda lo que se registró al cerrar —llegados y total—, y el resto sale `null`.
  - `expected` (integer, obligatorio)
  - `safe` (integer, obligatorio)
  - `help` (integer | null, obligatorio)
  - `wrong_point` (integer | null, obligatorio)
  - `pending` (integer | null, obligatorio)
  - `no_signal` (integer | null, obligatorio)
  - `not_worn` (integer | null, obligatorio)
- `assembly_points` (array<AssemblyPoint>, obligatorio): Los destinos del tipo, descartados incluidos.

## MusterKind

Tipo: `string`

Valores: `general`, `tsunami`, `earthquake`.

## AssemblyPoint

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `name` (string, obligatorio)
- `status` (string, obligatorio) Valores: `available`, `excluded`.
- `count` (integer | null, obligatorio): Personas a salvo en este punto. `null` en una evacuación cerrada: al cerrar solo se guarda el total.
- `excluded` (object | null, obligatorio)
  - `reason` (string, obligatorio)
  - `recommended` (boolean, obligatorio): Lo había recomendado el sistema (radio o viento). Quien descarta es siempre una persona.
  - `at` (string (date-time), obligatorio)
  - `by` (string, obligatorio)

## TokenResponse

Tipo: `object`

- `access_token` (string, obligatorio)
- `token_type` (string, obligatorio) Valores: `Bearer`.
- `expires_in` (number, obligatorio) Valores: `3600`.
- `scope` (string, obligatorio)

## OAuthError

Tipo: `object`

Error del token, en el formato de RFC 6749 §5.2.

- `error` (string, obligatorio) Valores: `invalid_request`, `invalid_client`, `unauthorized_client`, `unsupported_grant_type`, `invalid_scope`.
- `error_description` (string)

## TokenRequest

Tipo: `object`

- `grant_type` (string, obligatorio) Valores: `client_credentials`.
- `client_id` (string, obligatorio)
- `client_secret` (string, obligatorio)
- `scope` (string): Separados por espacios; subconjunto de los del cliente. Sin él, todos los del cliente.

## SiteList

Tipo: `object`

- `data` (array<Site>, obligatorio)

## Site

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `name` (string, obligatorio)
- `location` (string | null, obligatorio)
- `privacy_mode` (string, obligatorio): `on_demand` («Solo en emergencia»): la posición de una persona solo se ve con una alerta abierta desde `unveil_min_severity`, en evacuación o con una solicitud aprobada. La API obedece la misma regla que el panel. Valores: `continuous`, `on_demand`.
- `unveil_min_severity` (Severity, obligatorio) Valores: `low`, `medium`, `high`, `critical`.

## Problem

Tipo: `object`

Error en formato RFC 9457 (`application/problem+json`), con el código HTTP real.

- `type` (string, obligatorio): URI que identifica el tipo de problema.
- `title` (string, obligatorio)
- `status` (integer, obligatorio)
- `detail` (string)
- `instance` (string)
- `code` (string, obligatorio): Código estable del error, para programar contra él. El texto puede cambiar; esto no.

## ZoneList

Tipo: `object`

- `data` (array<Zone>, obligatorio)

## Zone

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `name` (string, obligatorio)
- `type` (string, obligatorio) Valores: `work_area`, `restricted`, `forbidden`, `assembly_point`, `privacy`, `building`.
- `geometry` (object | null, obligatorio): Polígono GeoJSON (WGS84). `null` en las zonas de privacidad: se da el nombre y el tipo, no dónde están (pendiente de validar con el comité).
  - `type` (string, obligatorio) Valores: `Polygon`.
  - `coordinates` (array<array<array>>, obligatorio)
- `assembly_point` (object | null, obligatorio): Solo en los puntos de reunión.
  - `serves` (array<string>, obligatorio) Valores: `general`, `tsunami`, `earthquake`.
  - `elevation_m` (number | null, obligatorio)
  - `elevation_source` (string | null, obligatorio) Valores: `dem`, `verified`.
  - `vertical` (boolean, obligatorio): Refugio en altura (un edificio).
  - `capacity` (integer | null, obligatorio)

## AlertPage

Tipo: `object`

- `data` (array<Alert>, obligatorio)
- `next_cursor` (string | null, obligatorio): Cursor opaco para la página siguiente; `null` si no hay más.

## AlertUpdate

Tipo: `object`

Dos estados, `acknowledged` y `resolved`, más el operador. Reconocer autoasigna la alerta si nadie la llevaba, como en el panel.

- `status` (string, obligatorio) Valores: `acknowledged`, `resolved`.
- `resolution_reason` (string): Al cerrar: por qué. Texto libre; va a la cronología de la alerta junto a la nota. (hasta 200 caracteres)
- `note` (string) (hasta 500 caracteres)
- `actor` (Actor, obligatorio)

## Actor

Tipo: `object`

El operador de la sala del cliente que hace la acción. Obligatorio: «reconocida» mide la respuesta de una persona, y sin nombre la cronología diría solo el del cliente.

- `name` (string, obligatorio) (hasta 120 caracteres)
- `external_id` (string) (hasta 64 caracteres)

## MusterPage

Tipo: `object`

- `data` (array<Muster>, obligatorio)
- `next_cursor` (string | null, obligatorio): Cursor opaco para la página siguiente; `null` si no hay más.

## MissingWorkerList

Tipo: `object`

- `data` (array<MissingWorker>, obligatorio)

## MissingWorker

Tipo: `object`

- `worker` (WorkerRef, obligatorio)
- `device` (DeviceRef, obligatorio)
- `status` (string, obligatorio) Valores: `help`, `wrong_point`, `pending`, `no_signal`, `not_worn`.
- `needs_rescue` (boolean, obligatorio): Tiene un SOS o una caída abierta y no ha llegado: no puede salir por su pie.
- `help_reason` (string | null, obligatorio) Valores: `trapped`, `injured`, `helping`.
- `assembly_point` (object | null, obligatorio): El punto donde está o dijo estar (con `wrong_point`, uno que no sirve).
  - `id` (string (uuid), obligatorio)
  - `name` (string, obligatorio)
  - `valid` (boolean, obligatorio)
- `declared_at` (string (date-time) | null, obligatorio)
- `last_seen_at` (string (date-time) | null, obligatorio)
- `location` (Location, obligatorio)
- `location_withheld` (boolean, obligatorio)

## WorkerPage

Tipo: `object`

- `data` (array<Worker>, obligatorio)
- `next_cursor` (string | null, obligatorio): Cursor opaco para la página siguiente; `null` si no hay más.

## Worker

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `external_id` (string | null, obligatorio): Código del trabajador en la empresa (el de su sistema de personal). `null` si no se ha dado.
- `name` (string): Nombre y apellidos. **Solo** si el contrato lo incluye; si no, la propiedad no aparece.
- `contractor` (string | null, obligatorio)
- `role` (string | null, obligatorio)
- `is_active` (boolean, obligatorio)
- `device` (DeviceRef, obligatorio)
- `date_last_seen` (string (date-time) | null, obligatorio): La más reciente entre la última posición y el último contacto del reloj.

## DevicePage

Tipo: `object`

- `data` (array<Device>, obligatorio)
- `next_cursor` (string | null, obligatorio): Cursor opaco para la página siguiente; `null` si no hay más.

## Device

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `serial` (string, obligatorio)
- `battery` (integer | null, obligatorio) (de 0 a 100)
- `date_last_seen` (string (date-time) | null, obligatorio)
- `model` (string | null, obligatorio)
- `status` (string, obligatorio) Valores: `active`, `inactive`, `maintenance`, `lost`.
- `worn` (boolean | null, obligatorio): `null`: no se sabe (sin sensor de muñeca).
- `worker` (WorkerRef, obligatorio)

## PositionList

Tipo: `object`

- `data` (array<Position>, obligatorio)

## Position

Tipo: `object`

Solo las posiciones que el velo deja ver en ese instante: en «Solo en emergencia», quien no tiene nada abierto no aparece.

- `device_id` (string (uuid), obligatorio)
- `worker` (WorkerRef, obligatorio)
- `location` (Location, obligatorio)
- `in_vehicle` (boolean, obligatorio)

## EventPage

Tipo: `object`

- `data` (array<Event>, obligatorio)
- `next_cursor` (string | null, obligatorio): Cursor opaco para la página siguiente; `null` si no hay más.

## WebhookEndpointList

Tipo: `object`

- `data` (array<WebhookEndpoint>, obligatorio)

## WebhookEndpoint

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `url` (string (uri), obligatorio)
- `event_types` (array<EventType | "alert.*" | "muster.*">, obligatorio)
- `filters` (object, obligatorio)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Valores: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)
- `is_active` (boolean, obligatorio)
- `failing_since` (string (date-time) | null, obligatorio): Lleva fallando desde entonces. El destino **no** se desactiva solo: dejaría de llegar el siguiente SOS sin que nadie lo decida.
- `created_at` (string (date-time), obligatorio)

## WebhookEndpointCreated

Tipo: `object`

- `id` (string (uuid), obligatorio)
- `url` (string (uri), obligatorio)
- `event_types` (array<EventType | "alert.*" | "muster.*">, obligatorio)
- `filters` (object, obligatorio)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Valores: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)
- `is_active` (boolean, obligatorio)
- `failing_since` (string (date-time) | null, obligatorio): Lleva fallando desde entonces. El destino **no** se desactiva solo: dejaría de llegar el siguiente SOS sin que nadie lo decida.
- `created_at` (string (date-time), obligatorio)
- `secret` (string, obligatorio): El secreto de firma. **Solo se enseña esta vez.**

## WebhookEndpointCreate

Tipo: `object`

- `url` (string, obligatorio)
- `event_types` (array<EventType | "alert.*" | "muster.*">, obligatorio)
- `filters` (object)
  - `site_ids` (array<string (uuid)>)
  - `min_severity` (Severity) Valores: `low`, `medium`, `high`, `critical`.
  - `contractors` (array<string>)

## WebhookSecretRotated

Tipo: `object`

- `secret` (string, obligatorio)
- `previous_valid_until` (string (date-time), obligatorio)

## SandboxScenarioRun

Tipo: `object`

- `run_id` (string (uuid), obligatorio)
- `scenario` (SandboxScenario, obligatorio) Valores: `sos`, `fall`, `heat_stress`, `evacuation`.
- `site_id` (string (uuid), obligatorio)
- `resource` (object, obligatorio): Lo que ha abierto: consultadlo con su `GET`, o esperad el webhook.
  - `type` (string, obligatorio) Valores: `alert`, `muster`.
  - `id` (string (uuid), obligatorio)
- `ends_at` (string (date-time), obligatorio): Cuándo se cierra solo.

## SandboxScenario

Tipo: `string`

- `sos`, `fall`, `heat_stress`: una persona de prueba pulsa el SOS, se cae o sufre estrés térmico. Sale la alerta (`alert.created`) y, si nadie la cierra antes con `PUT /v1/alerts/{id}`, se cierra sola a los 10 min (`alert.resolved`).
- `evacuation`: una evacuación general de 3 min. A los 15 s llegan cuatro personas al punto A; a los 35 s alguien llega al B y la sala lo descarta (`muster.targets_changed`); a los 55 s una persona pide ayuda (`alert.created`, `evacuation_help`); a los 95 s quien estaba en B se va al C; a los 3 min, todo despejado (`muster.ended`). Entre medias, `muster.updated` cada vez que cambia el recuento.

Valores: `sos`, `fall`, `heat_stress`, `evacuation`.

## SandboxScenarioRequest

Tipo: `object`

- `scenario` (SandboxScenario, obligatorio) Valores: `sos`, `fall`, `heat_stress`, `evacuation`.
- `site_id` (string (uuid)): El centro de sandbox. Sin él, el primero de los vuestros que sea de sandbox.
