> 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.

# Crear turnos

Si tu organización usa Asistencia en Crehana y los horarios viven en otro
sistema, podés crear los turnos por API en vez de cargarlos a mano. Un turno
creado por API es igual a uno creado desde Crehana: se puede asignar, las
marcas de la gente se comparan contra su horario y la puntualidad se clasifica
con sus tolerancias.

```
POST /api/v5/rest/org/{organization_slug}/time/attendance/shifts/
```

La referencia completa está en
[Create Shift](/api-reference/endpoints/attendance/create-shift).

## Antes de empezar

* **Credenciales de administrador.** Las credenciales de la API tienen que ser
  de una cuenta ADMIN de la organización. Con otras, la respuesta es `403`.
* **Elegí el tipo de turno.** Un turno **fijo** (`NON_ROTATING`) repite el mismo
  horario todas las semanas: lunes a viernes de 9 a 18, por ejemplo. Un turno
  **rotativo** (`ROTATING`) repite un patrón de bloques de trabajo y descanso
  sin mirar el día de la semana: 4 días de trabajo y 2 de descanso, por ejemplo.
* **Un nombre por turno.** El nombre es único entre los turnos activos de la
  organización. Si ya existe, la respuesta es `409` con el id del turno que lo
  tiene.
* **Guardá los ids de la respuesta.** El `id` del turno es el `shift_id` que
  pide [Asignar turnos](/documentacion/casos-de-uso/asignar-turnos); en un
  rotativo, el `id` de cada ciclo es el `start_cycle_id`.

## Un turno fijo de punta a punta

```bash
curl -X POST "https://www.crehana.com/api/v5/rest/org/acme/time/attendance/shifts/" \
  -H "Api-Key: $API_KEY" \
  -H "Secret-Access: $SECRET_ACCESS" \
  -H "Content-Type: application/json" \
  -d '{
    "shift_type": "NON_ROTATING",
    "name": "Mañana 09-18",
    "description": "Oficina, lunes a viernes",
    "color": "BLUE",
    "weekly_schedule": [
      { "day": "MONDAY", "entry_time": "09:00", "exit_time": "18:00" },
      { "day": "TUESDAY", "entry_time": "09:00", "exit_time": "18:00" },
      { "day": "WEDNESDAY", "entry_time": "09:00", "exit_time": "18:00" },
      { "day": "THURSDAY", "entry_time": "09:00", "exit_time": "18:00" },
      { "day": "FRIDAY", "entry_time": "09:00", "exit_time": "18:00" }
    ]
  }'
```

```json
{
  "success": true,
  "message": "Shift created.",
  "code": "attendance-005",
  "datetime": "2026-10-13T09:00:03.120394",
  "data": {
    "id": "4ed0f6c7-a2a7-4de7-90cd-ae8cf26aabb1",
    "name": "Mañana 09-18",
    "shift_type": "NON_ROTATING",
    "description": "Oficina, lunes a viernes",
    "color": "BLUE",
    "is_active": true,
    "created_at": "2026-10-13T14:00:03",
    "break_counts_as_worked": false,
    "mark_association_window_hours": 2,
    "edit_worked_minutes": false,
    "break_config": null,
    "tolerance_config": null,
    "weekly_schedule": [
      {
        "day": "MONDAY",
        "is_working_day": true,
        "entry_time": "09:00",
        "exit_time": "18:00",
        "worked_minutes": 540,
        "break_time_slots": null,
        "break_duration_minutes": null
      },
      { "day": "TUESDAY", "...": "..." },
      { "day": "SATURDAY", "is_working_day": false, "entry_time": null, "exit_time": null, "worked_minutes": null, "break_time_slots": null, "break_duration_minutes": null },
      { "day": "SUNDAY", "is_working_day": false, "...": "..." }
    ],
    "apply_same_schedule_to_all_working_days": false,
    "total_working_days": 5,
    "total_worked_hours_per_week": 45.0,
    "cycles": null
  }
}
```

El `201` confirma que el turno existe. `data.id` es el `shift_id` para
asignarlo. Los días que no mandaste (sábado y domingo) vuelven como días de
descanso, y `worked_minutes` lo calculó Crehana a partir del horario.

## Un turno rotativo

```bash
curl -X POST "https://www.crehana.com/api/v5/rest/org/acme/time/attendance/shifts/" \
  -H "Api-Key: $API_KEY" \
  -H "Secret-Access: $SECRET_ACCESS" \
  -H "Content-Type: application/json" \
  -d '{
    "shift_type": "ROTATING",
    "name": "Planta 4x2",
    "cycles": [
      { "cycle_type": "WORK", "duration_days": 4, "entry_time": "07:00", "exit_time": "19:00" },
      { "cycle_type": "REST", "duration_days": 2 }
    ]
  }'
```

```json
{
  "success": true,
  "message": "Shift created.",
  "code": "attendance-005",
  "datetime": "2026-10-13T09:01:10.004210",
  "data": {
    "id": "78e62b28-6a19-4d8c-b7f4-0c2e9a5d1f33",
    "name": "Planta 4x2",
    "shift_type": "ROTATING",
    "is_active": true,
    "cycles": [
      {
        "id": "1d8f297b-f186-46bb-9952-ac25304806ac",
        "cycle_order": 1,
        "cycle_type": "WORK",
        "duration_days": 4,
        "entry_time": "07:00",
        "exit_time": "19:00",
        "worked_minutes_per_day": 720,
        "break_time_slots": null,
        "break_duration_minutes": null
      },
      {
        "id": "9c0a5e2d-1b7e-4a3f-8d2c-5e6f7a8b9c0d",
        "cycle_order": 2,
        "cycle_type": "REST",
        "duration_days": 2,
        "entry_time": null,
        "exit_time": null,
        "worked_minutes_per_day": null,
        "break_time_slots": null,
        "break_duration_minutes": null
      }
    ],
    "total_pattern_days": 6,
    "total_work_days": 4,
    "total_rest_days": 2,
    "number_of_cycles": 2,
    "weekly_schedule": null,
    "...": "..."
  }
}
```

El patrón se repite en ese orden: 4 días de trabajo, 2 de descanso, 4 de
trabajo… Al asignar el turno, `start_cycle_id` dice con qué ciclo arranca la
persona el día `start_date`. Guardá el `id` de cada ciclo.

## Los campos

### `shift_type`

`NON_ROTATING` o `ROTATING`. Decide qué más va en el cuerpo: un turno fijo
lleva `weekly_schedule`; uno rotativo lleva `cycles`. Mandar el otro campo es
`422`.

### `name` y `description`

`name` hasta 150 caracteres, único entre los turnos activos de la organización.
`description` es opcional, hasta 250. Los dos se guardan sin espacios al
principio ni al final.

### `weekly_schedule` (turno fijo)

Una entrada por día de la semana, con `day` (`MONDAY` a `SUNDAY`), `entry_time`
y `exit_time`. Mandá solo los días laborables: los que faltan quedan como días
de descanso. Un día repetido es `422`; ningún día laborable es `400`.

Si preferís listar los 7, un día de descanso se manda con
`"is_working_day": false` y sin horario.

Con `apply_same_schedule_to_all_working_days: true` todos los días laborables
tienen que llevar el mismo horario y el mismo descanso; después, Crehana los
mantiene iguales cuando se edita uno.

### `cycles` (turno rotativo)

Los bloques del patrón, en orden. Cada uno lleva `cycle_type` (`WORK` o `REST`)
y `duration_days` (al menos 1). Un bloque `WORK` lleva `entry_time` y
`exit_time`; un bloque `REST` no lleva horario. Hacen falta al menos un `WORK`
y un `REST`.

### Las horas

Siempre en `HH:MM` de 24 horas: `09:00`, `18:30`, `23:00`. Una salida menor que
la entrada es un **turno nocturno**: `22:00` a `06:00` termina al día
siguiente. Si un turno nocturno pisa la entrada del día siguiente (sale a las
`06:00` del martes y el martes entra a las `05:00`), la respuesta es `400`.

### Descansos: `break_config`

Sin `break_config` el turno no tiene descanso y ningún día ni ciclo puede
llevar datos de descanso. Con `break_config` se define cómo es el descanso a
nivel del turno, y en cada día o ciclo laboral va el descanso en sí:

| `break_type`     | Qué va en cada día o ciclo laboral                                                                                                                      | Ejemplo                      |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| `FIXED_SCHEDULE` | `break_time_slots`: uno o más tramos con `break_label` (`LUNCH`, `COFFEE_BREAK`, `REST`), `start_time` y `end_time`, dentro del horario y sin solaparse | Almuerzo 13:00-14:00         |
| `FIXED_DURATION` | `break_duration_minutes`: cuánto dura el descanso                                                                                                       | 60 minutos, a cualquier hora |

`deduction_method` dice cómo se descuenta del tiempo trabajado: `REAL`
descuenta el descanso que la persona marcó; `FIXED` descuenta siempre el
configurado. Con `break_counts_as_worked: true` el descanso cuenta como
trabajado y no se descuenta.

```json
{
  "shift_type": "NON_ROTATING",
  "name": "Mañana con almuerzo",
  "break_config": { "break_type": "FIXED_SCHEDULE", "deduction_method": "REAL" },
  "weekly_schedule": [
    {
      "day": "MONDAY",
      "entry_time": "09:00",
      "exit_time": "18:00",
      "break_time_slots": [
        { "break_label": "LUNCH", "start_time": "13:00", "end_time": "14:00" }
      ]
    }
  ]
}
```

### Tolerancias: `tolerance_config`

Minutos de gracia antes y después del horario, cada uno con los eventos a los
que aplica (`ENTRY`, `EXIT`, `BREAK`). Una entrada dentro de la tolerancia
posterior no cuenta como tardanza.

```json
{
  "tolerance_config": {
    "before_minutes": 10,
    "before_applicable_events": ["ENTRY"],
    "after_minutes": 15,
    "after_applicable_events": ["ENTRY", "EXIT"]
  }
}
```

`BREAK` solo se puede usar con un `break_config`. Los minutos no pueden ser
negativos y cada lado necesita al menos un evento.

### `worked_minutes` y `edit_worked_minutes`

Por defecto Crehana calcula los minutos trabajados de cada día o ciclo: la
franja entre entrada y salida menos el descanso configurado (o la franja
completa con `break_counts_as_worked`). Si mandás `worked_minutes` (o
`worked_minutes_per_day` en un ciclo) sin `edit_worked_minutes: true`, se
ignora.

Con `edit_worked_minutes: true` vos fijás el valor y pasa a ser obligatorio en
cada día o ciclo laboral.

### `mark_association_window_hours`

Cuántas horas antes de la entrada y después de la salida una marca todavía
pertenece a este turno. Entre 1 y 12; por defecto 2. Subilo si tu gente suele
marcar con mucha anticipación.

### `color`

El color del turno en el calendario de Crehana: `GRAY` (por defecto),
`PURPLE`, `BLUE`, `GREEN`, `BEIGE`, `ORANGE`, `PINK` o `YELLOW_GREEN`.

## Reintentos: el nombre protege contra duplicados

Si se corta la conexión y no sabés si el turno se creó, reenviá la misma
creación. Si ya existe un turno activo con ese nombre, la respuesta es `409`
con `detail.code: SHIFT_NAME_ALREADY_EXISTS` y `detail.shift_id` con el id del
turno existente: guardalo y seguí con la asignación. No se crea un duplicado.

```json
{
  "detail": {
    "errors": ["An active shift with this name already exists in the organization."],
    "code": "SHIFT_NAME_ALREADY_EXISTS",
    "shift_id": "4ed0f6c7-a2a7-4de7-90cd-ae8cf26aabb1"
  }
}
```

Ojo: el `409` dice que el nombre está tomado, no que el turno existente tenga
el mismo horario. Si cambiaste el horario, editalo desde Crehana o creá el
turno con otro nombre.

## Respuestas

| Status | Cuándo                                                                                                                                             | Qué hacer                                                     |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
| `201`  | Turno creado                                                                                                                                       | Guardar `data.id` y, en un rotativo, el `id` de cada ciclo    |
| `400`  | El horario rompe una regla de Asistencia, o el `organization_slug` no existe                                                                       | Ver `detail.errors`: dice la regla. Reintentar igual no sirve |
| `401`  | Faltan las credenciales o no son válidas                                                                                                           | Revisar los dos headers                                       |
| `403`  | Las credenciales no administran esa organización                                                                                                   | Usar credenciales de un admin de la organización              |
| `409`  | Ya hay un turno activo con ese nombre                                                                                                              | Usar `detail.shift_id`, o cambiar el nombre                   |
| `422`  | El cuerpo no cumple el esquema (falta un campo, una hora que no es `HH:MM`, un día repetido, un valor fuera del vocabulario, un campo desconocido) | Corregir el campo que indica `detail[].loc`                   |
| `500`  | Error nuestro                                                                                                                                      | Reintentar con backoff                                        |

| `detail.code`               | Status | Significa                                                                                                                                                                                                                                                                   |
| --------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `VALIDATION_ERROR`          | `400`  | Regla del turno: ningún día laborable, entrada igual a salida, un nocturno que pisa el día siguiente, descanso fuera del horario o solapado, `BREAK` en la tolerancia sin descanso, rotativo sin `WORK` o sin `REST`, `REST` con horario, `edit_worked_minutes` sin minutos |
| `SHIFT_NAME_ALREADY_EXISTS` | `409`  | Ya hay un turno activo con ese nombre; `detail.shift_id` trae su id                                                                                                                                                                                                         |
| `FORBIDDEN`                 | `403`  | Las credenciales no administran la organización                                                                                                                                                                                                                             |

Los textos de `errors` son para un log o para una persona: programá contra el
status y, si hace falta, contra `detail.code`. Más en
[Errores](/documentacion/conceptos/errores).

## Lo que la API no hace (todavía)

* No lista los turnos ni sus ciclos. Guardá los ids de la respuesta.
* No edita, desactiva ni elimina turnos: eso se hace desde Crehana.

## Lista de verificación

* [ ] Las credenciales son de un ADMIN de la organización.
* [ ] `shift_type` es `NON_ROTATING` con `weekly_schedule`, o `ROTATING` con `cycles`.
* [ ] Las horas van en `HH:MM` y sabés que una salida menor que la entrada es un turno nocturno.
* [ ] Si hay descanso, está `break_config` y cada día o ciclo laboral trae su tramo o su duración.
* [ ] Guardás `data.id` y, en un rotativo, el `id` de cada ciclo para asignar el turno.
* [ ] Tu integración trata el `409` como "ya existe" y usa `detail.shift_id`.
* [ ] Los `500` y los cortes de red se reintentan. Los `4xx` se registran y no se reintentan.