Pyworkforce
Librería Python para optimización de gestión de personal: modelo de colas Erlang-C, programación de turnos con LP y asignación multi-habilidad — reemplaza la planeación en hojas de cálculo con garantías matemáticas.

El Problema que Resuelve
Los equipos de operaciones en call centers y organizaciones de servicio toman decisiones de dotación cada semana que determinan directamente la calidad del servicio y los costos laborales. La pregunta es simple de enunciar: ¿cuántos agentes necesitas, en qué turnos y con qué habilidades?
En la práctica, la mayoría de los equipos responden esto con hojas de cálculo e intuición. La hoja les dice la dotación de la semana anterior. La intuición ajusta por estacionalidad. El resultado es un cronograma que sobredota algunas horas y subdota otras, y un proceso de planeación que no puede auditarse, repetirse ni mejorarse sistemáticamente.
Las matemáticas para hacer esto con rigor existen desde hace décadas — Erlang C para modelado de colas, programación lineal para programación de turnos, programación entera para asignación individual. Lo que faltaba era una librería Python que compusiera esto en un kit de herramientas usable y listo para producción.
Qué Construí
pyworkforce implementa el pipeline completo de gestión de personal en tres módulos componibles:
Modelado de colas responde: dado el volumen esperado de llamadas y el tiempo de atención, ¿cuántos agentes se necesitan para cumplir un objetivo de nivel de servicio? La fórmula Erlang C es el estándar de la industria; la librería la encapsula con valores predeterminados listos para producción, incluyendo shrinkage (tiempo no productivo del agente) y límites de ocupación.
Programación de turnos responde: dados los requerimientos de dotación por hora, ¿qué turnos deben funcionar y cuántos agentes en cada uno? Esto se formula como un problema de programación lineal — minimizar la brecha entre la dotación requerida y la asignada, dado un conjunto de plantillas de turno disponibles.
Asignación de turnos (rostering) responde: dadas las asignaciones de turno requeridas, ¿qué agente específico trabaja qué secuencia de turnos? Este es un problema combinatorio con restricciones duras — máximo de días consecutivos, descanso mínimo entre turnos, requisitos de habilidades, patrones de días libres.
Cada módulo puede usarse de forma independiente o encadenado en un pipeline completo.
Por Qué Importan las Garantías Matemáticas
La alternativa a la programación basada en LP son las heurísticas — patrones de turno por reglas generales, dotaciones redondeadas, copiar y pegar del histórico. Las heurísticas son rápidas de calcular y fáciles de explicar, pero producen soluciones subóptimas y, fundamentalmente, no pueden decirte cuán subóptimas son.
Un programa lineal, cuando termina con status = "Optimal", garantiza que no existe ninguna solución factible que se desempeñe mejor en el objetivo. Para un problema de dotación, eso significa: no se puede cubrir esta demanda con menos agentes cumpliendo el nivel de servicio objetivo. Ese tipo de afirmación no es posible con una hoja de cálculo.
Las restricciones de rostering funcionan de la misma manera. Si el descanso mínimo entre turnos es de 11 horas y existe un roster válido, el solver lo encuentra. Si no existe ningún roster válido bajo las restricciones dadas, devuelve "Infeasible" — lo que también es información valiosa.
El Pipeline en Práctica
Los tres módulos se componen naturalmente. Un call center que ejecute esto de extremo a extremo haría:
from pyworkforce.queuing import ErlangC
from pyworkforce.scheduling import MinAbsDifference
from pyworkforce.rostering import MinHoursRoster
# Paso 1: ¿cuántos agentes por hora para alcanzar el nivel de servicio?
erlang = ErlangC(
transactions=120, # llamadas que llegan en el intervalo
interval=60, # duración del intervalo (minutos)
aht=4, # tiempo promedio de atención (minutos)
asa=20/60, # velocidad objetivo de respuesta (minutos)
shrinkage=0.30, # tiempo no productivo del agente
)
posiciones_por_hora = erlang.required_positions(
service_level=0.80,
max_occupancy=0.85,
)["positions"]
# Paso 2: ¿qué turnos cubren esa demanda y cuántos de cada uno?
scheduler = MinAbsDifference(
num_days=7,
periods=24,
shifts_coverage={
"Mañana": [0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0],
"Tarde": [0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0],
"Noche": [1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1],
},
required_resources=[[posiciones_por_hora] * 24] * 7,
max_period_concurrency=posiciones_por_hora,
max_shift_concurrency=posiciones_por_hora,
)
plan_turnos = scheduler.solve()
# Paso 3: asignar agentes individuales a secuencias de turnos
roster = MinHoursRoster(
num_days=7,
resources=[f"agente_{i}" for i in range(25)],
shifts=["Mañana", "Tarde", "Noche"],
shifts_hours=[8, 8, 8],
min_working_hours=40,
max_resting=2,
required_resources={
"Mañana": [posiciones_por_hora] * 7,
"Tarde": [posiciones_por_hora] * 7,
"Noche": [posiciones_por_hora] * 7,
},
)
cronograma_semanal = roster.solve()
Del volumen de llamadas al cronograma semanal individual — con garantías matemáticas en cada paso.
Decisiones Clave de Diseño
El parámetro shrinkage en el módulo Erlang C merece atención. El Erlang C básico da el número de agentes necesarios asumiendo que están disponibles el 100% del tiempo. Los agentes reales toman descansos, asisten a capacitaciones, manejan tareas administrativas y se ausentan por enfermedad. Ignorar el shrinkage típicamente subestima la dotación en un 25–35%, que es exactamente por qué los planes basados en hojas de cálculo crónicamente tienen subdotación.
El objetivo de programación — MinAbsDifference — minimiza la diferencia absoluta entre la dotación requerida y la programada en todos los períodos, en lugar de solo minimizar el costo total. Esto produce una cobertura equilibrada en lugar de soluciones que sacrifican ciertas horas para alcanzar un objetivo numérico.
El módulo de rostering modela las restricciones como restricciones duras en el programa entero. No hay ponderación de penalizaciones, no hay relajación de restricciones suaves, no hay postprocesamiento. Un cronograma o respeta las restricciones o el solver devuelve infactible.
Estado Actual
pyworkforce ha sido desarrollada activamente desde 2021 y actualmente está en v0.5.4. Está publicada en PyPI, documentada en pyworkforce.rodrigo-arenas.com y construida sobre Google OR-Tools.
Fue construida a partir de trabajo directo en problemas de optimización de dotación para equipos de operaciones — el tipo de proyecto donde la respuesta de una hoja de cálculo y la respuesta matemática divergen lo suficiente como para importar.
Sobre el autor
Rodrigo Arenas es arquitecto de software e ingeniero de machine learning en Medellín, Colombia. Construye productos, plataformas y software open source para IA, Data Engineering y Machine Learning — es el creador de Ciaren, sklearn-genetic-opt y PyWorkforce.
Rodrigo Arenas — ML Engineer & Open Source Maintainer
¿Estás trabajando en algo similar? Puedo ayudarte a llevarlo a producción.