# Alertas

> Tipos, gravedad y estados de una alerta; la diferencia entre que la sala se haga cargo y que el trabajador vea el aviso; y cómo reconocerla o cerrarla.

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

Una **alerta** es algo que ha pasado en la planta y que alguien tiene que atender: un SOS, una caída, una entrada donde no se debe, un reloj sin batería o un aviso de terremoto. Es el objeto central de la API.

## El objeto

La alerta llega completa en cada webhook (`data.alert`) y es lo que devuelve `GET /v1/alerts/{id}`. Los campos que más se usan:

| Campo | Qué es |
| --- | --- |
| `type`, `severity`, `status` | Qué ha pasado, cómo de grave es y en qué punto de la respuesta está. |
| `title` | El título que lee la sala. |
| `site`, `zone` | Dónde. `zone` puede ser `null`. |
| `worker`, `device` | A quién le ha pasado y con qué reloj. `null` en las alertas de toda la planta. |
| `location`, `location_withheld` | Dónde está la persona, si la privacidad de la planta deja verlo. |
| `details` | Datos propios de cada tipo. |
| `acknowledged`, `resolved`, `assigned_to` | Quién se ha hecho cargo, quién la ha cerrado y a quién está asignada. |
| `date_created`, `date_last_modified` | Cuándo se creó y cuándo cambió por última vez. |
| `simulated` | `true` si viene de un simulacro o de relojes simulados. |
| `panel_url` | Enlace a su ficha en el panel de SafeOnuba. |

La lista entera, campo a campo, está en el esquema [`Alert`](https://safeonuba.com/desarrolladores/referencia/esquemas#Alert).

## Tipos

| type | Qué significa |
| --- | --- |
| `sos` | La persona ha pedido ayuda desde el reloj, o la sala lo ha lanzado por ella. |
| `fall_detected` | El reloj ha detectado una caída. |
| `restricted_zone_entry` | Entrada en una zona restringida. |
| `forbidden_zone_entry` | Entrada en una zona prohibida. |
| `permit_expired_inside` | El permiso de trabajo ha caducado con la persona todavía dentro de la zona. |
| `permit_closed_inside` | El permiso de trabajo se ha cerrado con la persona todavía dentro de la zona. |
| `heat_stress` | Estrés térmico: el reloj le ha indicado que pare y descanse. |
| `evacuation_help` | Durante una evacuación, la persona dice desde el reloj que no puede salir por su pie o que está ayudando a otra. |
| `worker_call_request` | La persona avisa a la sala desde el reloj. Sin cobertura, el reloj graba un aviso de voz que se escucha en el panel. |
| `device_offline` | El reloj lleva un tiempo sin comunicar. |
| `low_battery` | Al reloj le queda poca batería. |
| `earthquake` | Terremoto. Es de toda la planta: no lleva `worker` ni `device`. |
| `tsunami_risk` | Riesgo de tsunami. Es de toda la planta: no lleva `worker` ni `device`. |

### Un vehículo cerca no es una alerta

Los avisos de vehículo cerca no generan alertas propias. Van dentro de la alerta de un incidente, en dos listas:

- `nearby`: quién había a menos de 100 metros cuando saltó una alerta crítica (SOS o caída), personas o vehículos. Da la distancia, nunca la posición, y no cuenta a quien estaba en una zona de privacidad.
- `vehicle_warnings`: los avisos de vehículo cerca que recibió esa persona en la ventana del incidente, con la distancia y si los vio en el reloj.

### Detalles de cada tipo

`details` cambia según `type`. Por ejemplo, [`SosDetails`](https://safeonuba.com/desarrolladores/referencia/esquemas#SosDetails) dice cómo se pidió el SOS (`trigger`), [`ZoneDetails`](https://safeonuba.com/desarrolladores/referencia/esquemas#ZoneDetails) trae el permiso de trabajo que regía (`permit_reference`) y [`HazardDetails`](https://safeonuba.com/desarrolladores/referencia/esquemas#HazardDetails) la magnitud, la distancia y si el aviso es oficial. Las variantes están en [`AlertDetails`](https://safeonuba.com/desarrolladores/referencia/esquemas#AlertDetails).

> **Nunca hay constantes vitales**
>
> Ni en `details` ni en ningún otro sitio. [`HeatStressDetails`](https://safeonuba.com/desarrolladores/referencia/esquemas#HeatStressDetails) dice lo que se le ha indicado al trabajador (`stop_and_rest`), el índice de calor de la planta y los minutos al sol, nada que salga de su pulso.

## Gravedad

| severity | Qué significa |
| --- | --- |
| `low` | Información. |
| `medium` | Aviso. |
| `high` | Grave. |
| `critical` | Crítica: SOS, caída, «no puedo evacuar». |

El filtro `severity` de `GET /v1/alerts` y el `min_severity` de los webhooks son **mínimos**: `high` devuelve `high` y `critical`.

## Estados

| status | Qué significa |
| --- | --- |
| `unacknowledged` | Nadie se ha hecho cargo todavía. |
| `acknowledged` | Un operador se ha hecho cargo. |
| `resolved` | Cerrada. |

- **Reconocer** asigna la alerta a quien la reconoce si nadie la llevaba. Reconocer no es cerrar: una alerta reconocida sigue marcando a la persona en el mapa hasta que se cierra.
- **Cerrar** deja constancia de quién, cuándo y con qué nota, en `resolved`.
- Una alerta cerrada puede **reabrirse**. Se avisa con el evento `alert.reopened`.

> **Dos «visto» distintos**
>
> `status: "acknowledged"` quiere decir que un operador de la sala se ha hecho cargo. `worker_acknowledged_at` es otra cosa: la hora (del reloj) a la que el trabajador pulsó «Entendido» en su muñeca. Cada uno tiene su evento: `alert.acknowledged` y `alert.worker_acknowledged`.

## Reconocer y cerrar desde tu sala

`PUT /v1/alerts/{id}` con el scope `alerts:write`. El cuerpo:

| Campo | Obligatorio | Qué es |
| --- | --- | --- |
| `status` | Sí | `acknowledged` o `resolved`. |
| `actor` | Sí | El operador de tu sala: `name` y, si quieres, `external_id`. |
| `resolution_reason` | No | Al cerrar, por qué. Hasta 200 caracteres. |
| `note` | No | Una nota. Hasta 500 caracteres. |

El operador es obligatorio porque «reconocida» mide la respuesta de una persona. En la cronología de la alerta queda «Cliente · Operador (vía integración)».

```bash
curl -X PUT https://sandbox.api.safeonuba.com/v1/alerts/0f6c2b7e-3a1d-4c55-9b0e-7d2f4a8c1e90 \
  -H "Authorization: Bearer $SAFEONUBA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "resolved",
    "resolution_reason": "Falsa alarma",
    "note": "Confirmado por radio con el encargado.",
    "actor": { "name": "E. Ejemplo", "external_id": "OP-117" }
  }'
```

```javascript
const res = await fetch(`https://sandbox.api.safeonuba.com/v1/alerts/${alertaId}`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${await tokenSafeOnuba()}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'resolved',
    resolution_reason: 'Falsa alarma',
    note: 'Confirmado por radio con el encargado.',
    actor: { name: operador.nombre, external_id: operador.id },
  }),
});
if (res.status === 409) {
  // Ya estaba cerrada, o la transición no es posible: nada que hacer.
} else if (!res.ok) {
  throw new Error(`${res.status}: ${await res.text()}`);
}
```

```python
res = requests.put(
    f"https://sandbox.api.safeonuba.com/v1/alerts/{alerta_id}",
    headers={"Authorization": f"Bearer {token_safeonuba()}"},
    json={
        "status": "resolved",
        "resolution_reason": "Falsa alarma",
        "note": "Confirmado por radio con el encargado.",
        "actor": {"name": operador.nombre, "external_id": operador.id},
    },
    timeout=10,
)
if res.status_code != 409:  # 409: ya estaba cerrada
    res.raise_for_status()
```

```csharp
var cuerpo = JsonContent.Create(new
{
    status = "resolved",
    resolution_reason = "Falsa alarma",
    note = "Confirmado por radio con el encargado.",
    actor = new { name = operador.Nombre, external_id = operador.Id },
});
var res = await http.PutAsync($"https://sandbox.api.safeonuba.com/v1/alerts/{alertaId}", cuerpo);
if (res.StatusCode != System.Net.HttpStatusCode.Conflict) res.EnsureSuccessStatusCode();
```

La respuesta es la alerta ya actualizada. Si la transición no es posible —por ejemplo, cerrar o reconocer una alerta ya cerrada— la API responde `409`.

## Consultar alertas

`GET /v1/alerts` devuelve las alertas de la más reciente a la más antigua, paginadas. Filtros:

| Parámetro | Qué filtra |
| --- | --- |
| `site_id` | Un centro. |
| `type` | Un tipo. |
| `status` | Un estado. |
| `severity` | Gravedad mínima. |
| `since`, `until` | Intervalo de fechas, en ISO 8601. |
| `limit`, `cursor` | Paginación (ver [Paginación](https://safeonuba.com/desarrolladores/paginacion)). |

Para seguir las alertas en tiempo real no consultes en bucle: usa [webhooks](https://safeonuba.com/desarrolladores/webhooks). La lista sirve para cargar el estado al arrancar y para buscar.

## Nombres de los trabajadores

Los nombres son opcionales por contrato. Si tu contrato no los incluye, `worker.name` no aparece y `title` se compone con el tipo, la zona y el identificador de empresa (`external_id`), sin nombre. Ver [Trabajadores y relojes](https://safeonuba.com/desarrolladores/conceptos/trabajadores-y-relojes).
