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

# Filtrar por fecha

Los reportes se acotan con `from_date` y `to_date`, en formato `YYYY-MM-DD`.
Las dos son **inclusivas** y las dos son **independientes**: podés mandar una,
la otra, o las dos. De esa combinación salen los cuatro casos que necesitás.

## Las cuatro formas de pedir un rango

| Lo que querés               | Qué mandás                                |
| --------------------------- | ----------------------------------------- |
| Desde una fecha en adelante | `from_date=2026-01-01`                    |
| Hasta una fecha             | `to_date=2026-03-31`                      |
| Un rango cerrado            | `from_date=2026-01-01&to_date=2026-03-31` |
| **Un solo día**             | `from_date=2026-01-15&to_date=2026-01-15` |

> **Warning**
>
> Para un solo día tenés que mandar **las dos** con la misma fecha. Mandar solo
> `from_date` no es "ese día": es **todo desde ese día en adelante**, sin techo.
> Es la confusión más común con estos filtros.

Las inscripciones de un día puntual:

```bash
curl "https://www.crehana.com/api/v5/rest/org/acme/reports/learning/general/?from_date=2026-01-15&to_date=2026-01-15" \
  -H "Api-Key: $API_KEY" -H "Secret-Access: $SECRET_ACCESS"
```

Todas las inscripciones desde el arranque del año:

```bash
curl "https://www.crehana.com/api/v5/rest/org/acme/reports/learning/general/?from_date=2026-01-01" \
  -H "Api-Key: $API_KEY" -H "Secret-Access: $SECRET_ACCESS"
```

## `to_date` incluye el día completo

`to_date=2026-01-15` no corta en la medianoche de ese día: entra todo el 15,
hasta el último minuto. No hace falta pedir el 16 para "cerrar" el 15.

## Sobre qué fecha filtra cada reporte

El rango no siempre se aplica sobre lo mismo. Cada reporte lo aplica sobre la
fecha que ese mismo reporte devuelve, y en la zona en la que la devuelve:

| Reporte                               | Filtra sobre                               | Zona                          | Rango                         |
| ------------------------------------- | ------------------------------------------ | ----------------------------- | ----------------------------- |
| `reports/learning/quizzes`            | fecha del último intento de evaluación     | UTC                           | opcional                      |
| `reports/learning/quiz-results`       | fecha del último intento del quiz de clase | UTC                           | opcional                      |
| `reports/learning/general`            | fecha de inscripción al curso              | zona de la organización       | opcional                      |
| `reports/learning/general-v2`         | fecha de inscripción al curso              | zona de la organización       | opcional                      |
| `reports/learning/performance`        | fecha de inscripción al curso              | zona de la organización       | opcional                      |
| `reports/attendance/shifts`           | fecha de creación del turno                | —                             | opcional                      |
| `reports/attendance/shifts-schedule`  | inicio de la asignación                    | —                             | opcional                      |
| `reports/attendance/marks`            | fecha de la marca                          | zona del lugar donde se marcó | **obligatorio, máx. 31 días** |
| `reports/attendance/daily-management` | fecha de la jornada                        | zona del lugar de la marca    | **obligatorio, máx. 31 días** |

> **Note**
>
> La zona importa en los bordes del rango. Una inscripción de las 21:00 hora de
> Lima es del día siguiente en UTC: si el reporte la muestra como el día 15 y el
> filtro comparara en UTC, pedir el 15 no la traería. Por eso cada reporte filtra
> en la misma zona en la que muestra la fecha — así lo que pedís y lo que ves
> coinciden.

> **Note**
>
> El reporte general v2 aceptaba un `course_enroll_date` que ya no figura en la
> referencia. Su equivalente es **`from_date` sin `to_date`**, con una diferencia:
> `from_date` compara en la zona de la organización y el parámetro viejo comparaba
> en UTC, así que en el borde del día los resultados pueden no ser idénticos.

## Los reportes de asistencia exigen el rango

En `attendance/marks` y `attendance/daily-management` el rango es obligatorio y
no puede pasar de **31 días**. No es una restricción arbitraria: esas dos tablas
crecen con colaboradores × días, y en una organización grande una consulta sin
acotar recorre millones de filas.

La ventana es inclusiva, así que del 1 al 31 de agosto son 31 días y entra; del
1 de agosto al 1 de septiembre son 32 y no.

Si falta un extremo:

```json
{
  "errors": [
    {
      "loc": ["query", "to_date"],
      "msg": "This field is required for this report",
      "type": "value_error.missing"
    }
  ]
}
```

Si la ventana es demasiado ancha:

```json
{
  "errors": [
    {
      "loc": ["query", "to_date"],
      "msg": "The maximum date window is 31 days",
      "type": "value_error.date_range"
    }
  ]
}
```

Para bajar un histórico más largo, partilo en tramos de hasta 31 días y
concatená los resultados.

## Cuándo responde 400

Los dos casos de error se validan antes de tocar los datos, así que recibís un
`400` y no un `200` con una lista vacía que parezca "no hay nada":

| Situación                         | `msg`                                                |
| --------------------------------- | ---------------------------------------------------- |
| Formato distinto de `YYYY-MM-DD`  | `Invalid date, expected format YYYY-MM-DD`           |
| `from_date` posterior a `to_date` | `from_date must be earlier than or equal to to_date` |

La forma completa de las respuestas de error está en
[Errores](/documentacion/conceptos/errores).

## Recomendaciones

* **Mandá el rango siempre que puedas.** Un reporte sin acotar devuelve el
  histórico completo de la organización y se pagina mucho peor — ver
  [Paginación](/documentacion/conceptos/paginacion).
* **Para sincronizar a diario**, pedí el día anterior con las dos fechas
  iguales. Es el caso de "los movimientos de ayer" y es una sola llamada.
* **Si comparás contra el panel**, tené en cuenta la zona: los reportes de
  learning se filtran en la zona de la organización, y los de evaluaciones en
  UTC.