> ## Documentation Index
> Fetch the complete documentation index at: https://docs.camellapp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Route handlers

> El patrón que siguen todos los endpoints de src/app/api.

Los endpoints en `src/app/api/**/route.ts` siguen un patrón consistente, en
este orden:

## 1. `requireAdmin()` en todo `/api/admin/*`

Todo endpoint bajo `/api/admin/*` empieza llamando a `requireAdmin()` de
`@/lib/require-admin`:

```ts theme={null}
const auth = await requireAdmin();
if (auth.error) {
  return auth.error;
}
```

`requireAdmin()` devuelve `{ error, session }`. Si `error` no es `null`, el
handler lo retorna tal cual — ya es un `401` (sin sesión) o un `403` (sesión
sin rol `ADMIN`).

## 2. Validación con Zod `safeParse`

Los `searchParams` o el body se validan con `safeParse`. Si falla, la
respuesta es `400` con esta forma:

```json theme={null}
{
  "error": "Parametros invalidos",
  "issues": {}
}
```

`issues` es la salida de `parsed.error.flatten()`.

## 3. Listados paginados por cursor

Los listados paginan con `take: limit + 1` y, si hay `cursor`, con
`cursor: { id: cursor }` más `skip: 1`. La fila extra sirve para saber si hay
otra página sin hacer un segundo `count`. La respuesta tiene siempre
`nextCursor` y `hasMore`; el nombre de la clave con el array cambia según el
recurso (`users`, `camellos`…):

```json theme={null}
{
  "users": [],
  "nextCursor": "clx...",
  "hasMore": true
}
```

El cursor es el `id` de la última fila de la página anterior — también en el
listado de usuarios, donde antes era un cursor compuesto codificado en
base64url. Ese cursor especial se retiró cuando el listado de usuarios pasó
a poder ordenarse por columna: con `orderBy` variable, un `id` simple es
suficiente y funciona igual que en camellos.

## 4. `try/catch` envolviendo todo

Todo el handler queda envuelto en `try/catch`. El `catch` registra el error
con un prefijo que identifica el endpoint y responde `500`:

```ts theme={null}
} catch (error) {
  console.error("[GET /api/notifications]", error);
  return NextResponse.json(
    { error: "No fue posible listar las notificaciones" },
    { status: 500 },
  );
}
```

## Idioma de los mensajes

Los mensajes de error de la API van en español **sin tildes** —
`"Parametros invalidos"`, `"No autenticado"`, `"Sin permisos de
administrador"` — porque son valores literales que el cliente puede llegar a
comparar. La UI que los muestra sí usa tildes.
