Ir al contenido principal
Knowledge Base
or-tools cp-sat algoritmos turnos optimización

Generación automática de turnos con Google OR-Tools (CP-SAT)

Cómo utilizamos Google OR-Tools con CP-SAT para generar cuadrantes completos de turnos en segundos, respetando decenas de restricciones laborales simultáneas.

HotelCode

Cuando el Excel no es suficiente para generar un cuadrante completo de turnos, utilizamos Google OR-Tools con su motor CP-SAT (Constraint Programming — Boolean Satisfiability). Es la misma familia de tecnología que usan empresas de logística para optimizar rutas de reparto o que emplean las aerolíneas para asignar tripulaciones a vuelos.

La diferencia es que aquí la aplicamos a un problema concreto de hotelería: asignar turnos (mañana, tarde, noche, libre) a cada empleado de recepción para cada día del mes, cumpliendo todas las reglas laborales, las preferencias del equipo y las necesidades de cobertura del hotel.

→ Contexto de negocio: Gestión de turnos en recepción de hotel

Qué es CP-SAT y por qué resuelve este problema

CP-SAT es un solver de programación por restricciones con variables booleanas. En términos técnicos, toma un conjunto de variables binarias, un conjunto de restricciones (constraints) que limitan los valores que esas variables pueden tomar, y una función objetivo que el solver intenta minimizar o maximizar. El motor combina técnicas de SAT solving (satisfacibilidad booleana) con programación por restricciones clásica, y utiliza un enfoque de búsqueda con propagación y aprendizaje para explorar el espacio de soluciones de forma eficiente.

La ventaja sobre un enfoque manual o heurístico es que CP-SAT garantiza que la solución encontrada cumple todas las restricciones hard (las obligatorias) y, además, es óptima o near-óptima respecto a la función objetivo (las preferencias soft). No hay que revisar el cuadrante a mano para detectar errores: el solver los elimina por construcción.

Cómo lo hemos integrado en el proyecto

La arquitectura tiene tres capas que trabajan coordinadas:

1. El backend TypeScript prepara los datos

El módulo build-solver-input.ts consulta la base de datos MySQL y ensambla un objeto JSON (SolverInput) con toda la información que el solver necesita:

  • Lista de empleados con sus reglas individuales (turno fijo, no fines de semana, preferencia de turno, días fijos)
  • Días del mes con su día de la semana y festivos
  • Configuración global (cobertura mínima por turno, horas de descanso mínimas, rango de noches consecutivas, libres mensuales mínimos y máximos)
  • Celdas bloqueadas (vacaciones aprobadas, bajas temporales, patrones fijos semanales)
  • Últimos 7 días del mes anterior para garantizar continuidad cross-month

2. El daemon Python resuelve el modelo

El solver es un proceso Python persistente (daemon.py) que el backend arranca una sola vez al iniciar el servidor. La razón de usar un daemon en vez de invocar Python por cada petición es el coste de importar OR-Tools: en frío puede tardar minutos en Windows. Con el daemon ya caliente, cada generación tarda entre 1 y 30 segundos según el tamaño del mes.

La comunicación entre Node.js y Python sigue un protocolo simple sobre stdin/stdout: una línea JSON de entrada (SolverInput), una línea JSON de salida (SolverOutput). Un semáforo garantiza que solo se procesa una petición a la vez, ya que CP-SAT no es thread-safe dentro del mismo proceso.

3. El modelo CP-SAT define las reglas

El fichero model.py construye el modelo de programación por restricciones. Las variables son booleanas:

x[empleado, día, turno] = 1 si el empleado trabaja ese turno ese día, 0 en caso contrario

Sobre estas variables se aplican 8 restricciones hard (si alguna no se puede cumplir, el solver devuelve INFEASIBLE con un diagnóstico de qué reglas conflictuaban):

  • H1 — Cobertura mínima: cada día debe haber al menos N empleados de mañana, N de tarde y N de noche, según la configuración del hotel.
  • H2 — Bloques de noche: cada empleado que hace noches debe hacer exactamente un bloque de 4 a 6 noches consecutivas por mes, con reglas de continuidad cross-month.
  • H3 — Transiciones prohibidas: no se puede pasar de noche a mañana sin libre intermedio, ni de tarde a mañana directamente.
  • H4 — Máximo días consecutivos de trabajo: ningún empleado trabaja más de 6 días seguidos sin descanso.
  • H5 — Ventana de descanso: al menos 2 libres en cualquier ventana de 7 días consecutivos.
  • H6 — Libres mensuales: cada empleado tiene entre el mínimo y máximo de días libres al mes.
  • H7 — Celdas bloqueadas: las vacaciones aprobadas, bajas temporales y patrones fijos semanales se inyectan como restricciones fijas.
  • Reglas por empleado: quien tiene noWeekends no trabaja sábados ni domingos; quien tiene fixedShift solo hace ese turno o libre.

Además, el modelo incluye una función objetivo soft con 4 componentes ponderados que el solver minimiza:

  • S1 (peso 10) — Balanceo de noches: minimiza la diferencia entre el empleado con más noches acumuladas y el que tiene menos.
  • S2 (peso 2) — Libres sueltos: penaliza los días libres que no tienen otro libre adyacente.
  • S3 (peso 1) — Preferencia de turno: penaliza asignar un turno distinto al preferido por el empleado.
  • S4 (peso 3) — Bloques de trabajo cortos: penaliza los bloques de 1 o 2 días de trabajo aislados.

Continuidad entre meses: el problema de los días virtuales

Uno de los aspectos más interesantes del diseño es la continuidad cross-month. Las reglas de descanso, los bloques de noche y los días consecutivos de trabajo no respetan los límites del calendario: si un empleado terminó el mes anterior con 5 días seguidos de trabajo, el mes nuevo no puede empezar con otro día de trabajo sin violar el máximo de 6 consecutivos.

Para resolverlo, el solver recibe los últimos 7 días del mes anterior como días virtuales (índices -7 a -1). Estos días se modelan como variables bloqueadas al turno real que tenía el empleado, y todas las restricciones iteran sobre días_virtuales + días_reales de forma uniforme. El solver no sabe que los días negativos son “del mes anterior”: simplemente ve una secuencia de 38 días donde los primeros 7 están fijados. Al devolver la solución, los días virtuales se descartan y solo se aplican los días reales (1 al 31) en la base de datos.

Diagnóstico cuando no hay solución

Si las restricciones son contradictorias —por ejemplo, la cobertura mínima requiere más turnos de los que el equipo puede dar con los libres mínimos configurados—, CP-SAT devuelve INFEASIBLE. En lugar de mostrar un error genérico, el sistema incluye un analizador de infeasibilidad heurístico que identifica la causa más probable: exceso de cobertura demandada vs. capacidad del equipo, empleado con demasiados turnos bloqueados, bloque de noches demasiado largo para el tamaño del mes, o horas de descanso irrazonables.

Además, sugiere relajaciones concretas (reducir libres mínimos en 1, bajar cobertura mínima en 1) para que el jefe de recepción pueda ajustar la configuración y reintentar.

Validación dual: el solver y el validador TS

Un detalle arquitectónico importante: el solver genera el cuadrante, pero hay un validador independiente en TypeScript (schedule-validator.ts) que ejecuta las mismas reglas sobre el resultado. Este validador corre en el frontend en tiempo real cuando el jefe de recepción edita celdas manualmente, y también se ejecuta como prueba de paridad tras cada generación del solver. Si el solver y el validador discrepan, la prueba de paridad lo detecta.

Es una capa de seguridad que garantiza que la generación automática y la edición manual comparten la misma lógica de negocio, aunque estén implementadas en lenguajes distintos.

En resumen

Para el jefe de recepción o el director de hotel, lo importante es esto:

Antes: confeccionar el cuadrante mensual llevaba entre 3 y 6 horas. Se hacía en Excel, revisando a mano que se cumplieran los descansos, las coberturas mínimas, las vacaciones aprobadas, los turnos fijos de cada empleado y las preferencias.

Ahora: el sistema genera un cuadrante completo para todo el equipo en cuestión de segundos. Respeta automáticamente todas las reglas laborales, las vacaciones y bajas ya aprobadas, los turnos fijos y las preferencias de cada empleado. Además, equilibra el reparto de noches entre el equipo y agrupa los libres para evitar desplazamientos innecesarios.

Si la configuración hace imposible encontrar un cuadrante válido, el sistema explica qué regla está causando el problema y sugiere ajustes concretos.

Y cuando el jefe de recepción quiere ajustar manualmente alguna celda después de la generación automática, el sistema valida cada cambio en tiempo real y avisa si esa edición rompe alguna regla.

¿Listo para digitalizar la operativa de tu hotel?

Prueba la demo sin registro o contáctanos para una presentación personalizada.