# Telemetría ligera del widget y del SDK

El endpoint con el que el widget y sdk.ai.reportError avisan de sus propios fallos en lotes de 10, y en qué se diferencia del registro de errores.

`POST /external/v1/tickets/client-errors` recibe, en lotes de hasta 10, los fallos que el widget de soporte y `sdk.ai.reportError` (`report_error`) detectan en el navegador: una lista de tickets que no carga, una nota de voz que no se transcribe. Así tu equipo ve en la [pestaña Errores](https://os.utilia.ai/docs/developers/api/errors-tab) que la integración falla sin esperar a que alguien lo cuente.

Este endpoint **no captura los errores de tu aplicación**, ni siquiera con el widget instalado: el widget solo avisa de los suyos. Para los de tu aplicación, en el navegador o en tu servidor, usa `install` y `capture` del SDK ([Capturar los errores de tu aplicación](https://os.utilia.ai/docs/developers/sdk/error-reporting)), que van al registro de errores completo.

**Dos formas de enviar errores**

## Telemetría ligera (POST /tickets/client-errors)

Los fallos del widget y de `ai.reportError`. Lotes de 10, sin módulo, archivo, contexto ni agrupación propia, y una respuesta con solo los recuentos. 30 peticiones por minuto.

## Registro de errores (POST /errors/report y /errors/batch · Recomendado)

Los errores de tu aplicación. Lotes de 25, todos los campos, una respuesta por error y gestión por la API. 120 peticiones por minuto. Es lo que usan `install` y `capture`.

## Plan y permisos

Hace falta el aplicativo **Integraciones** en el plan de tu organización y el permiso `analytics:errors:write` en la aplicación, el mismo que el registro de errores. El endpoint admite 30 peticiones por minuto por aplicación y no gasta el cupo por hora de la clave.

## Enviar fallos

**`POST /api/external/v1/tickets/client-errors`**

Envía un lote de hasta 10 fallos del navegador. Permiso: analytics:errors:write.

### Cabeceras

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `X-Api-Key` | `string` | Sí | Clave de la aplicación externa (utl\_...). |
| `Content-Type` | `string` | Sí | application/json |
| `x-request-id` | `string` | No | Identificador para seguir la petición. |

### Cuerpo

`{ "errors": [...] }`, con hasta 10 fallos. Una lista vacía es válida y devuelve `received: 0`. Cada fallo:

| Nombre | Tipo | Obligatorio | Descripción |
| - | - | - | - |
| `message` | `string` | Sí | Mensaje del fallo, hasta 1000 caracteres. Se registra con el prefijo \[SDK Client]. |
| `stack` | `string` | No | Traza de la pila, hasta 10.000 caracteres. |
| `code` | `string` | No | Una etiqueta propia (NETWORK_ERROR, WIDGET_LOAD) o un código HTTP, hasta 100 caracteres. Con 408, 502, 503 o 504 se registra como low; con otro 4xx no se registra. |
| `severity` | `string` | No | critical, high, medium o low. Sin indicarla, low. |
| `endpoint` | `string` | No | Ruta de la API que falló, hasta 500 caracteres. |
| `method` | `string` | No | Método HTTP de la petición que falló, hasta 10 caracteres. |
| `requestId` | `string` | No | Identificador de la petición que falló, hasta 128 caracteres. |
| `component` | `string` | No | Pieza de la interfaz donde ocurrió (TicketForm, ChatWidget), hasta 100 caracteres. |
| `url` | `string` | No | Página donde ocurrió, hasta 2000 caracteres. |
| `userAgent` | `string` | No | Navegador que lo envía, hasta 500 caracteres. |
| `release` | `string` | No | Versión de tu aplicación, hasta 100 caracteres. |
| `environment` | `string` | No | Entorno (production, staging...), hasta 40 caracteres. Se guarda en minúsculas. |

Todos los fallos de esta ruta se registran en el módulo `sdk-client`; `code`, `component`, `url` y `userAgent` se guardan en su contexto. Un campo que no está en la lista, como `context`, se descarta sin error.

### Qué pasa con cada fallo del lote

**Un fallo del lote, de la petición al grupo**

1. **Llega el lote** (Tu web): Hasta 10 fallos por petición. Si no: 400 con más de 10 fallos; 429 pasadas 30 peticiones al minuto.
2. **¿Es un fallo real?**: Se mira el code de cada fallo. Si no: un code 4xx se descarta y cuenta en discarded.
3. **Se fija la gravedad**: Sin severity, o con code 408, 502, 503 o 504, queda en low.
4. [**Se agrupa**](https://os.utilia.ai/docs/developers/api/errors-tab) (Pestaña Errores): Los repetidos suman ocurrencias en el mismo grupo.

### Respuesta

`201` cuando UTILIA OS ha procesado el lote entero, en orden:

```json
{ "received": 3, "accepted": 2, "discarded": 1 }
```

- `received`: fallos que traía la petición.
- `accepted`: los registrados.
- `discarded`: los que no se registraron, sea por un 4xx esperado, por los límites de ritmo y de cupo del registro de errores, por saturación o por un fallo al guardar. Siempre `received = accepted + discarded`.

La respuesta no dice qué fallo concreto se descartó ni por qué. Si lo necesitas, usa [el registro de errores](https://os.utilia.ai/docs/developers/api/errors), que responde por cada uno.

### Errores

| HTTP | Cuándo |
| - | - |
| `400` | El cuerpo no pasa la validación: más de 10 fallos o un campo que supera su límite. |
| `401` | Falta la clave de API o no es válida. |
| `403` | El plan no incluye Integraciones (`MODULE_NOT_ENABLED`) o falta `analytics:errors:write` (`INSUFFICIENT_SCOPE`). |
| `429` | Más de 30 peticiones por minuto. |

### Ejemplos

#### curl

```bash
curl -X POST 'https://os.utilia.ai/api/external/v1/tickets/client-errors' \
  -H 'Content-Type: application/json' \
  -H 'X-Api-Key: utl_tu_clave_de_api' \
  -d '{
    "errors": [
      {
        "message": "No se pudo cargar la lista de tickets",
        "code": "NETWORK_TIMEOUT",
        "severity": "high",
        "endpoint": "/api/external/v1/tickets",
        "method": "GET",
        "component": "TicketList",
        "url": "https://mi-app.com/soporte",
        "release": "1.1.0",
        "environment": "production"
      }
    ]
  }'
```

#### JavaScript

```typescript
// Un fallo, en un solo intento. Nunca lanza.
await sdk.ai.reportError({
  message: 'No se pudieron generar las sugerencias',
  code: 'SUGGESTIONS_FAILED',
  component: 'formulario-soporte',
  release: '2.4.1',
});
```

#### Python

```python
from utilia_sdk import ReportClientErrorParams

# Un fallo, en un solo intento. Nunca lanza.
await sdk.ai.report_error(
    ReportClientErrorParams(
        message="No se pudieron generar las sugerencias",
        code="SUGGESTIONS_FAILED",
        component="formulario-soporte",
        release="2.4.1",
    )
)
```

## Qué gravedad poner

| Gravedad | Cuándo |
| - | - |
| `critical` | La función principal está bloqueada del todo: no se pueden crear tickets. |
| `high` | Falla una función importante, pero hay alternativa: las sugerencias con IA no cargan y el formulario funciona. |
| `medium` | Algo inesperado que no bloquea a la persona y se puede reintentar. |
| `low` | Problemas menores: una vista previa que no carga, un campo que tarda. |

## Siguiente paso

- [Capturar los errores de tu aplicación](https://os.utilia.ai/docs/developers/sdk/error-reporting): install y capture en el navegador, en Node.js y en Python.
- [Widget de soporte](https://os.utilia.ai/docs/developers/widget/overview): Qué hace el widget y cómo se inserta en tu web.
