Saltar al contenido principal

Reportes

Dos rutas, el mismo motor. Nada se guarda: cada llamada calcula el rango desde las marcas, los horarios y las decisiones de ese momento, así que una marca aprobada esta mañana cambia la respuesta de esta tarde.

Las fechas y la zona horaria​

date_from y date_to son fechas civiles en la zona horaria de la empresa, las dos incluidas, con hasta 92 días entre ellas. Cada respuesta dice cuál es esa zona en company_timezone (un nombre IANA como America/Guatemala); las horas dentro del Detalle son horas de reloj en ella.

Un día de trabajo es el día en que empieza un turno: un turno nocturno de las 22:00 del 31 a las 06:00 del 1 es del 31, y su marca de salida, tomada el 1, cuenta ahí.

Filtros​

Las dos rutas reciben los mismos parámetros que la página de reportes de la app:

ParámetroQué hace
date_from, date_toObligatorios. El rango, los dos días incluidos.
locationEmpleados de estas sedes. Se repite para varias: location=<id>&location=<id>.
department, position, employeeLo mismo, por departamento, puesto o empleado.
include_inactivetrue agrega a los empleados que se fueron. Sin él solo vuelven los empleados activos hoy.

Sin filtros vuelven todos los empleados activos de todas las sedes. Los ids para filtrar vienen en las propias filas (employee, location, department, position). Una sede que no es de la empresa es un 400 location_forbidden, nunca un reporte vacío.

Para un cierre mensual, mande include_inactive=true: alguien que se fue el 20 todavía tiene la mayor parte del mes por contar. Los días fuera del empleo de una persona no se cuentan.

El Resumen​

GET /api/v1/reports/summary/ responde una fila por empleado, ordenadas por código, sin paginar. Una empresa de 2 000 personas recibe unos 1,2 MB.

Cada fila empieza por quién es la persona (employee, code, name y el id y el nombre de su location, department y position) y sigue con los totales del rango. Los días son conteos; los minutos, minutos enteros.

ColumnaQué cuenta
expected_daysDías con un turno esperado, también los abiertos y los futuros.
expected_minutesLo que pedían esos turnos, menos sus pausas descontadas.
worked_daysDías en que la persona vino: presente, incompleto o todavía en curso.
late_days, late_minutesEntradas tarde pasada la tolerancia del turno, y sus minutos.
early_leave_days, early_leave_minutesSalidas antes del fin del turno pasada su tolerancia, y sus minutos.
late_break_count, late_break_minutesPausas que duraron más que sus minutos, y los minutos de más.
absent_daysDías esperados sin ninguna marca una vez cerrada la ventana de marcaje.
incomplete_daysDías con una marca faltante una vez cerrada la ventana de marcaje.
worked_minutesTodo lo trabajado, ordinario y extra.
ordinary_minutesLo trabajado menos lo extra.
extra_minutesLo que pasa de lo que pedía el turno, más un día libre trabajado, más el tiempo dentro de un feriado que la empresa cuenta como extra.
no_schedule_daysDías sin horario: no se esperaba nada, las marcas cuentan igual.
vacation_days, sick_days, permission_days, other_leave_daysDías con turno que un período liberó, por motivo.
has_pendingUna marca del rango espera revisión: los números todavía pueden cambiar.

Una tardanza o una salida temprana que la empresa aprobó como justificada ya cuenta desde el inicio o hasta el fin del turno: no aparece en late_minutes ni en early_leave_minutes, y no baja ordinary_minutes.

Lo que debe una persona​

debe = expected_minutes - ordinary_minutes

En todo día con turno, los minutos ordinarios nunca pasan de lo que pedía el turno, así que esta resta es exactamente la suma de lo que faltó cada día: minutos de tardanza, salidas tempranas, ausencias, una pausa que se alargó. El tiempo extra de un día nunca paga una hora que faltó otro; esa es una regla que su sistema puede aplicar encima si quiere.

Léala con tres cuidados:

  1. Vale cuando no_schedule_days es 0. En un día sin horario no se esperaba nada y todo lo trabajado es ordinario, así que esos minutos bajarían la deuda.
  2. Un día incompleto debe su turno entero hasta que la empresa ponga la marca que falta: Kabal no adivina a qué hora se fue alguien. Mire incomplete_days.
  3. Una fila con has_pending: true todavía puede cambiar cuando alguien revise la marca que espera.

Sobre los feriados: cuando un horario no da libre un feriado (una tienda que abre todos los días) y la empresa cuenta las horas de feriado como extra, la parte de un turno dentro del feriado no pide nada, así que no suma a expected_minutes, venga o no la persona. Si vino, esas horas son extra_minutes. Si no vino, el día sigue contando en expected_days y absent_days, y no debe nada.

El Detalle​

GET /api/v1/reports/daily/ responde cada día de cada empleado, agrupado por empleado y paginado por empleado: 100 personas por página por defecto, hasta 200 con page_size, ordenadas por código.

curl -H "Authorization: Bearer $KABAL_API_KEY" \
"https://app.kabal-attendance.com/api/v1/reports/daily/?date_from=2026-10-01&date_to=2026-10-31&page=1"
{
"date_from": "2026-10-01",
"date_to": "2026-10-31",
"company_timezone": "America/Guatemala",
"count": 230,
"page": 1,
"pages": 3,
"page_size": 100,
"results": [
{
"employee": "0193a4b2-7c1e-7d40-9a51-3f2c8b6e1d07",
"code": "E001",
"name": "Ana López",
"days": [{ "date": "2026-10-01", "state": "present", "late_minutes": 12 }]
}
]
}

Pida page=1 y siga hasta pages. Una página después de la última es un 404 not_found. El orden es estable mientras nadie se contrate a mitad de la descarga; si count cambia entre páginas, empiece de nuevo.

Cada día dice qué se esperaba (expected: el turno con sus horas, qué lo decidió en source, como el horario, un feriado, un período o un plan semanal aprobado, el nombre del feriado, y en time_off_reason por qué se liberó un día con turno), qué leyó el motor (marks, cada marca que miró, contada o no, con el papel que le dio), los números del día y un state. Un día no lleva ids internos ni las notas que la empresa escribe en una excepción o un período: lo que viaja es el motivo.

El state del día:

stateSignificado
upcomingTodavía sin marca, y la tolerancia del turno no ha pasado.
late_no_markTodavía sin marca, pasada la tolerancia, con la ventana de marcaje abierta.
in_progressFalta la entrada o la salida, con la ventana de marcaje abierta.
presentVino, con entrada y salida.
incompleteFalta la entrada o la salida y la ventana de marcaje cerró.
absentSin ninguna marca y la ventana de marcaje cerró.
restUn día libre: descanso, un feriado que el horario respeta, un período.
extra_dayTrabajó en un día libre: todo es extra.
no_scheduleLa persona no tenía horario ese día.

labels agrega lo que el motor notó sin cambiar el estado, como marcas duplicadas, una pausa que se alargó, una tardanza justificada o un feriado trabajado; la referencia las lista todas. Cada fila de un día pesa cerca de 1 KB.

Cuándo pedir un mes​

No hay una señal de "mes cerrado". Pídalo cuando RRHH de la empresa haya cerrado el mes en Kabal, y tenga en cuenta:

  • El último día de trabajo del mes cierra con su ventana de marcaje: un turno nocturno del 31 termina la mañana del 1. Pedir el 1 a las 08:00 todavía puede verlo in_progress.
  • Los días upcoming e in_progress ya suman sus expected_minutes, como suman a expected_days. Un mes pedido antes de terminar muestra una deuda que baja sola.
  • Una marca aprobada, una marca faltante agregada o una solicitud decidida después de su consulta cambian los números. Vuelva a pedir las filas con has_pending: true, o el mes entero, antes de cerrar la planilla.

Un mes entero para 2 000 personas es el Resumen más 20 páginas del Detalle, muy dentro de los límites.