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:
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:
issues es la salida de parsed.error.flatten().
3. Listados paginados por cursor
Los listados paginan contake: 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…):
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:
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.