El control horario respeta el periodo de alta
Cambio que rompe: fichar antes de la fecha de alta de un empleado o después de su fecha de baja responde ahora 422 con el subcode clocking_outside_employment_period. Los saldos diarios añaden is_unscheduled e is_outside_employment, la hoja mensual y el resumen de equipo añaden un bloque to_date con los días cerrados, el horario del empleado acepta una fecha, hire_date pasa a ser editable, y reasignar o desasignar un horario que aún no ha empezado lo anula.
El registro de jornada respeta ahora el periodo de alta de cada empleado:
desde hire_date hasta termination_date, ambos días incluidos. Hasta ahora la
API aceptaba fichajes en cualquier fecha, la hoja mensual ocultaba los minutos
fichados en días sin horario, una hire_date errónea no se podía corregir y el
saldo del mes en curso mezclaba los días cerrados con los que aún faltan. Esta
publicación cierra esos huecos.
Cambio que rompe: fichar fuera del periodo de alta responde 422
Toda escritura que crea o mueve un fichaje comprueba ahora su fecha frente al
periodo de alta del empleado. Si la fecha cae antes de hire_date o después de
termination_date, la petición se rechaza y no se escribe nada.
| Operación | Fecha que se comprueba |
|---|---|
POST /v1/time-entries/clock-in | occurred_at, o la hora del servidor si se omite. |
POST /v1/time-entries/manual | started_at y ended_at. |
POST /v1/time-corrections | La hora propuesta de una corrección add_missing_entry (inicio y fin) o adjust_time. No se crea ninguna solicitud. |
POST /v1/time-corrections/{time_correction}/approve | Se vuelve a comprobar al aprobar, frente al periodo de alta vigente en ese momento. La solicitud sigue pending. |
{
"error": {
"type": "invalid_request_error",
"code": "business_rule_violation",
"subcode": "clocking_outside_employment_period",
"message": "No se puede fichar el 04/05/2026: el empleado está de alta desde el 11/05/2026.",
"param": "occurred_at",
"doc_url": "https://docs.factuarea.com/guides/errors#business_rule_violation",
"request_id": "req_01HKQS5NTIMEOUTSIDEMPLOY01"
}
}La regla compara la fecha de calendario del instante en su propio desfase UTC,
la misma fecha a la que la hoja horaria imputa el fichaje. Pausar, reanudar y
fichar la salida de un tramo ya abierto nunca se bloquean, y tampoco las
correcciones remove_entry ni los rechazos.
Qué hacer. Si tu integración ficha a empleados antes de su fecha de alta, por
ejemplo porque los crea con una fecha provisional, corrige antes hire_date con
PUT /v1/employees/{employee} (consulta
hire_date editable) y después envía los fichajes. Ramifica según
error.subcode; el message es texto en español orientado a personas que
indica la fecha de alta o de baja. Consulta
clocking_outside_employment_period.
Marcas diarias: is_unscheduled e is_outside_employment
Cada elemento de days[] en GET /v1/time-balances/monthly-sheet y
GET /v1/time-balances/employee/{employee} gana dos booleanos:
| Campo | true cuando | Efecto en el día |
|---|---|---|
is_unscheduled | El día está dentro del periodo de alta pero no tiene ningún horario de trabajo vigente. | worked_minutes son los minutos realmente fichados; expected_minutes, balance_minutes y overtime_minutes valen 0. |
is_outside_employment | El día es anterior a hire_date o posterior a termination_date. | expected_minutes vale 0 aunque una asignación de horario cubra el día; el saldo y las horas extra valen 0. Los fichajes registrados antes de esta publicación siguen contando como minutos trabajados. |
Las dos marcas nunca son true a la vez. Antes de esta publicación, un periodo
sin ningún horario mostraba 0 minutos trabajados, y los minutos fichados en un
día sin horario podían contar como horas extra. Ahora esos minutos siempre
aparecen como trabajados y nunca cuentan como horas extra. Por eso
total_balance_minutes es la suma de los saldos diarios y puede diferir de
total_worked_minutes − total_expected_minutes cuando hay minutos fichados en
esos días.
El detalle diario congelado del informe de cierre mensual no incluye estas marcas, y los cierres ya congelados conservan sus cifras.
to_date frente a los campos total_*
Los campos total_* de GET /v1/time-balances/monthly-sheet y de cada fila de
GET /v1/time-balances/team-summary conservan su significado: cubren el mes
completo. Por eso, en el mes en curso son una proyección que ya resta los
minutos esperados de hoy y de los días que faltan, y total_balance_minutes
muestra un déficit hasta que termina el mes.
Las dos respuestas añaden ahora un bloque to_date con el acumulado de los
días cerrados, es decir, todos los anteriores a hoy:
"to_date": {
"through_date": "2026-05-24",
"expected_minutes": 7200,
"worked_minutes": 7290,
"balance_minutes": 90,
"overtime_minutes": 60
}through_datees el último día cerrado incluido, normalmente ayer. Hoy queda fuera porque su jornada sigue en curso.- En un mes terminado,
to_datecoincide con los campostotal_*. - Cuando todavía no se ha cerrado ningún día del mes (su primer día, o un mes
futuro),
through_dateesnully las cuatro cifras valen 0. - El bloque está siempre presente en las dos respuestas.
GET /v1/time-balances/employee/{employee}no lo incluye.
Muestra to_date.balance_minutes como el saldo hasta la fecha y reserva los
campos total_* para la proyección del mes.
Horario del empleado en una fecha
GET /v1/work-schedules/employee/{employee} acepta un query param opcional
date (Y-m-d, hoy por defecto) y devuelve el horario vigente ese día. Úsalo
para leer un horario que empieza en el futuro, como el asignado desde una fecha
de alta pendiente:
curl "https://api.factuarea.com/v1/work-schedules/employee/01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c?date=2026-10-12" \
-H "Authorization: Bearer $FACTUAREA_API_KEY"Si no hay horario vigente ese día sigue respondiendo
404 schedule_assignment_not_found. Un date que no es una fecha Y-m-d
válida responde 422.
hire_date editable
PUT /v1/employees/{employee} acepta hire_date (Y-m-d). Si se omite, no
cambia. No puede ser posterior a la termination_date del empleado: en ese caso
la respuesta es 422 parameter_invalid_value con subcode invalid_hire_date y
param termination_date. Una fecha mal formada también responde 422.
curl -X PUT https://api.factuarea.com/v1/employees/01931b3e-7c4a-7f2e-9a8b-4d6e7f8a9b0c \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 01928f10-7c0e-7c4a-9b7d-2f8a6e3c1d4c" \
-d '{ "hire_date": "2026-09-01" }'Cambiarla nunca borra nada. Los fichajes y las asignaciones de horario
existentes se conservan, y los fichajes que quedan fuera del nuevo periodo de
alta siguen en el registro, marcados con is_outside_employment.
El catálogo de errores incluye ahora invalid_hire_date. Tanto
PUT /v1/employees/{employee} como POST /v1/employees/{employee}/deactivate
lo documentan con un ejemplo 422; la baja ya lo devolvía cuando la
termination_date era anterior a la hire_date. Consulta
invalid_hire_date.
Anular una asignación que aún no ha empezado
POST /v1/work-schedules/{schedule}/assign y
POST /v1/work-schedules/{schedule}/unassign respondían 422 cuando la
asignación abierta del empleado empezaba en el futuro y la nueva fecha era
anterior o igual a su inicio, por ejemplo al adelantar un horario asignado desde
una fecha de alta errónea. Ahora esa asignación se anula: se cierra como
tramo vacío (effective_to igual a su effective_from), se conserva en el
histórico y nunca está vigente. Una desasignación sin effective_to, que toma
por defecto la fecha de hoy, también la anula.
Retrotraer el inicio de una asignación que ya está vigente, es decir, que empezó
hoy o antes, sigue respondiendo 422.
Cierre mensual
POST /v1/monthly-time-record-closes incluye ahora a todo empleado cuyo periodo
de alta se solapa con el mes (dado de alta hasta su último día y sin baja
anterior a su primer día), sea cual sea su estado actual. Un empleado que causa
baja durante el mes aparece en el informe y en la exportación de nómina de ese
mes; uno dado de alta después del mes queda fuera. La respuesta y el formato del
sello no cambian.
MCP
Las tools comparten las mismas reglas:
clock_in,record_manual_time_entry,request_time_correctionyapprove_time_correctiondevuelven el mismo rechazoclocking_outside_employment_period.get_monthly_time_sheetyget_employee_time_balancedevuelven las marcas diarias;get_monthly_time_sheetyget_team_time_balance_summarydevuelvento_date.get_employee_work_scheduleaceptadate, yupdate_employeeaceptahire_date.assign_work_schedule,unassign_work_scheduleyclose_monthly_time_recordsiguen las nuevas reglas de asignación y de cierre.
Consulta el catálogo de tools MCP.
Endpoints actualizados13
| Endpoint | Descripción |
|---|---|
POST/v1/time-entries/clock-in | Fichar la entrada de un empleado |
POST/v1/time-entries/manual | Registrar una entrada manual retroactiva |
POST/v1/time-corrections | Solicitar una corrección de fichaje |
POST/v1/time-corrections/{time_correction}/approve | Aprobar una corrección de fichaje |
GET/v1/time-balances/monthly-sheet | Obtener la hoja horaria mensual de un empleado |
GET/v1/time-balances/employee/{employee} | Obtener el saldo horario de un empleado para un periodo |
GET/v1/time-balances/team-summary | Obtener el resumen de saldo horario del equipo |
GET/v1/work-schedules/employee/{employee} | Obtener el horario de un empleado en una fecha |
PUT/v1/employees/{employee} | Actualizar un empleado |
POST/v1/employees/{employee}/deactivate | Desactivar un empleado |
POST/v1/work-schedules/{schedule}/assign | Asignar un horario a un empleado |
POST/v1/work-schedules/{schedule}/unassign | Desasignar un horario de un empleado |
POST/v1/monthly-time-record-closes | Cerrar un registro de jornada mensual |