Factuarea APIDevelopers
Contrato

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ónFecha que se comprueba
POST /v1/time-entries/clock-inoccurred_at, o la hora del servidor si se omite.
POST /v1/time-entries/manualstarted_at y ended_at.
POST /v1/time-correctionsLa 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}/approveSe 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:

Campotrue cuandoEfecto en el día
is_unscheduledEl 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_employmentEl 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_date es el último día cerrado incluido, normalmente ayer. Hoy queda fuera porque su jornada sigue en curso.
  • En un mes terminado, to_date coincide con los campos total_*.
  • Cuando todavía no se ha cerrado ningún día del mes (su primer día, o un mes futuro), through_date es null y 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_correction y approve_time_correction devuelven el mismo rechazo clocking_outside_employment_period.
  • get_monthly_time_sheet y get_employee_time_balance devuelven las marcas diarias; get_monthly_time_sheet y get_team_time_balance_summary devuelven to_date.
  • get_employee_work_schedule acepta date, y update_employee acepta hire_date.
  • assign_work_schedule, unassign_work_schedule y close_monthly_time_record siguen las nuevas reglas de asignación y de cierre.

Consulta el catálogo de tools MCP.

Endpoints actualizados13

EndpointDescripción
POST/v1/time-entries/clock-inFichar la entrada de un empleado
POST/v1/time-entries/manualRegistrar una entrada manual retroactiva
POST/v1/time-correctionsSolicitar una corrección de fichaje
POST/v1/time-corrections/{time_correction}/approveAprobar una corrección de fichaje
GET/v1/time-balances/monthly-sheetObtener 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-summaryObtener 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}/deactivateDesactivar un empleado
POST/v1/work-schedules/{schedule}/assignAsignar un horario a un empleado
POST/v1/work-schedules/{schedule}/unassignDesasignar un horario de un empleado
POST/v1/monthly-time-record-closesCerrar un registro de jornada mensual

En esta página

¿Te echamos una mano?Contactar con soporte