> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.crehana.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.crehana.com/_mcp/server.

## Eliminar una solicitud de tiempo libre

`DELETE /org/{slug}/timeoff/requests/{request_id}/` elimina una solicitud de
ausencia de la organización. La solicitud deja de aparecer en
`GET /org/{slug}/timeoff/requests/` y el tiempo que consumía vuelve al saldo
del colaborador.

Con una credencial de administrador de la organización —así se crean las
api-key— podés eliminar cualquier solicitud y en cualquier estado. Si la
credencial corresponde a un colaborador, sólo puede eliminar las solicitudes
propias y sólo mientras sigan pendientes; en el resto de los casos la respuesta
es `403 Forbidden`.

Una baja correcta responde:

```json
{
  "success": true,
  "message": "Time off request deleted correctly.",
  "code": "timeoff-001",
  "datetime": "2026-08-19T14:48:58.833958",
  "data": { "request_id": "531" }
}
```

La respuesta distingue el motivo del rechazo:

| Situación                                         | Status          |
| ------------------------------------------------- | --------------- |
| Solicitud eliminada                               | `200 OK`        |
| La solicitud no existe                            | `404 Not Found` |
| Sin permiso sobre esa solicitud                   | `403 Forbidden` |
| Ya estaba eliminada, o ya fue aprobada o denegada | `409 Conflict`  |

Reintentar es seguro: un segundo `DELETE` sobre la misma solicitud responde
`409` y no vuelve a tocar el saldo.

## Los reportes de asistencia se pueden consultar por API

Los cuatro seguimientos del producto **Asistencia** —los mismos que hasta ahora
solo se podían bajar en Excel desde el panel— ya están disponibles como
endpoints paginados:

| Endpoint                                               | Qué devuelve             | Una fila es                                      |
| ------------------------------------------------------ | ------------------------ | ------------------------------------------------ |
| `GET /org/{slug}/reports/attendance/shifts/`           | Creación de turnos       | un turno configurado                             |
| `GET /org/{slug}/reports/attendance/shifts-schedule/`  | Programación de turnos   | una asignación vigente de turno a un colaborador |
| `GET /org/{slug}/reports/attendance/marks/`            | Recepción de marcas      | una marca individual                             |
| `GET /org/{slug}/reports/attendance/daily-management/` | Gestión diaria de marcas | un colaborador en un día                         |

Los cuatro responden `{ "total": N, "results": [...] }` y aceptan `limit` (hasta
500\), pero paginan de dos maneras distintas según el volumen del reporte:

* **Turnos y programación** usan `offset`, como el resto de los reportes.
* **Marcas y gestión diaria** usan `cursor`. La respuesta trae `next_cursor` y
  `has_next_page`; para la página siguiente reenviás ese `next_cursor` tal cual, sin
  interpretarlo. Estos dos **no aceptan `offset`**: si lo mandás la respuesta es
  `400`, para que no recibas siempre la primera página creyendo que avanzás. La
  paginación es solo hacia adelante — no hay cursor previo. El detalle está en
  [Paginación](/pages/paginacion).

**El rango de fechas es obligatorio en marcas y en gestión diaria**, con una
ventana máxima de 31 días: son los dos reportes de mayor volumen y sin acotar
el período una sola consulta puede recorrer millones de registros. Si faltan
`from_date` o `to_date`, o el rango es más ancho que 31 días, la respuesta es
`400 Bad Request` indicando el campo. En turnos y programación el rango es
opcional.

En `shifts-schedule`, `marks` y `daily-management` podés filtrar por
colaborador con `centralized_user_id`.

> **Warning**
>
> `centralized_user_id` es el identificador **centralizado** del colaborador, que
> no es el mismo que el `user_id` de los reportes de learning: para la misma
> persona son números distintos. Si mandás el equivocado vas a recibir `200` con
> `results` vacío, no un error.

Tres diferencias a propósito respecto del archivo de Excel:

* **Los campos de tipo catálogo no vienen traducidos.** `mark_type`, `source`,
  `classification`, `day_status` y `shift_type` devuelven el valor tal como lo
  emite el motor de asistencia (`entry`, `APP`, `late`, `rotating`), no la
  etiqueta en español que ves en el Excel. Es lo que te permite mapearlos de
  forma estable: la etiqueta puede cambiar, el valor no. `days` también va en
  inglés y ordenado de lunes a domingo (`monday, friday`); en turnos rotativos
  es el ciclo `5 work / 2 rest`.
* El estado de un turno y el indicador de feriado son booleanos (`true` /
  `false`), no los textos "Activo"/"Inactivo" ni "Sí"/"No".
* Las duraciones de la jornada (`worked_minutes`, `tardiness_minutes`,
  `overtime_minutes`) vienen en minutos como número entero, listas para sumar,
  en vez del formato `hh:mm`.

> **Warning**
>
> `day_status` es el único que no es homogéneo en el origen: cuatro de sus valores
> llegan en español (`feriado`, `dia_descanso`, `feriado_trabajado`,
> `dia_descanso_trabajado`) y el resto en inglés (`ok`, `justified`,
> `unjustified_absence`, `incomplete`, `with_novelty`, `no_shift`). Se exponen tal
> como llegan, sin traducir ni retraducir: tratalos como identificadores opacos.

Los datos se consolidan con un corte periódico, así que reflejan la
información procesada hasta ese corte y no el marcaje del minuto anterior.