Analítica de Personas · Semestre otoño 2026 · Semana 15 · Prof. René Gempp
El Apunte 33 construyó un modelo que proyecta la dotación año a año. Ese modelo produce números con decimales: 867,3 activos al cabo de dos años; 813,1 contrataciones necesarias. La precisión numérica es una trampa. El modelo es tan bueno como sus supuestos, y los supuestos no se validan contra el futuro porque el futuro no existe todavía.
La respuesta correcta a la incertidumbre no es buscar el pronóstico más preciso, sino mapear el rango de futuros plausibles (Schoemaker, 1995). Lo que nos interesa de un plan de dotación no es que acierte el número exacto — ninguno lo hace — sino que ayude a Carolina a tomar una decisión robusta: una que sea buena a través de los distintos escenarios posibles, no solo óptima para uno.
purrrSimular múltiples escenarios implica correr la misma proyección muchas veces con supuestos distintos. La herramienta del tidyverse para esto es purrr: un paquete de programación funcional que reemplaza los bucles for con funciones limpias y pipelines legibles.
map()map(lista, función) aplica función a cada elemento de lista y devuelve una list con los resultados. El flujo de trabajo típico:
# 1. Definir la grilla de supuestos (un tibble, una fila por escenario)
escenarios <- tibble(
escenario = c("Optimista", "Base", "Pesimista"),
factor_salida = c(0.75, 1.00, 1.25)
)
# 2. map() sobre la columna de supuestos
# \(f) es la sintaxis moderna de función anónima (R 4.1+)
resultado <- escenarios |>
mutate(
P_esc = map(factor_salida, \(f) ajustar_rotacion(P5, f)),
proy = map(P_esc, \(Pe) proyectar_dotacion(Pe, n0, horizonte = 2))
)
# 3. unnest() despliega la list-column a formato largo
simulacion <- resultado |>
select(escenario, proy) |>
unnest(proy)
map() crea list-columns; unnest() las despliega a formato largo. Este patrón — definir grilla → map() → unnest() — es la forma idiomática de simulación en el tidyverse.
map()| Función | Devuelve | Cuándo usarla |
|---|---|---|
map() | list | Resultado es un objeto complejo (tibble, modelo, gráfico) |
map_dbl() | vector double | Resultado es un número (el total de activos, la brecha) |
map_chr() | vector character | Resultado es un string |
map_dfr() | data frame apilado | Resultado es un tibble; map_dfr los apila por filas |
pmap() | list | Varios argumentos a la vez (un escenario tiene varias dimensiones) |
ajustar_rotacion()Esta función toma la matriz P y un factor, escala la probabilidad de salida de cada nivel y reajusta las demás para que la fila siga sumando 1:
ajustar_rotacion <- function(P5, factor_salida) {
P2 <- P5
for (i in 1:4) {
sal <- P2[i, "Salida"] * factor_salida
otras <- P2[i, 1:4]
if (sum(otras) > 0) otras <- otras / sum(otras) * (1 - sal)
P2[i, 1:4] <- otras
P2[i, "Salida"] <- sal
}
P2
}
# factor = 0.75: rotación baja un 25% (programa de retención exitoso)
# factor = 1.00: base (sin cambios respecto al histórico)
# factor = 1.25: rotación sube un 25% (condiciones adversas)
Una vez que unnest() despliega la simulación, agrupamos por escenario y año para obtener el total de activos proyectados:
totales <- simulacion |>
group_by(escenario, anio) |>
summarise(activos = sum(headcount), .groups = "drop")
# Brecha total por escenario a 2 años (demanda = 1.680)
totales |>
filter(anio == 2) |>
mutate(brecha = round(1680 - activos))
#> escenario activos brecha
#> Optimista 946 734 ← si el programa de retención funciona
#> Base 868 812 ← manteniendo tasas históricas
#> Pesimista 794 886 ← si la rotación sube
El rango es amplio: entre 734 y 886 contrataciones según cómo evolucione la rotación. Una diferencia de 152 personas — no es un detalle.
geom_ribbon() + geom_line()La forma honesta de comunicar la proyección es mostrar la banda de incertidumbre (optimista–pesimista) junto a la trayectoria base. geom_ribbon() dibuja el área entre dos curvas; geom_line() superpone la línea central.
# 1. Calcular el piso (Pesimista) y techo (Optimista) por año
banda <- totales |>
group_by(anio) |>
summarise(ymin = min(activos), ymax = max(activos), .groups = "drop")
linea_base <- totales |> filter(escenario == "Base")
# 2. Graficar
ggplot() +
geom_ribbon(data = banda, aes(x = anio, ymin = ymin, ymax = ymax),
fill = "#B85042", alpha = 0.18) +
geom_line(data = linea_base, aes(x = anio, y = activos),
color = "#1B2A4A", linewidth = 1.3) +
geom_point(data = linea_base, aes(x = anio, y = activos),
color = "#1B2A4A", size = 2.5) +
geom_hline(yintercept = 1680, linetype = "dashed", color = "#E8B96E") +
annotate("text", x = 0.1, y = 1705, hjust = 0,
label = "Demanda objetivo (+40%) = 1.680", color = "#E8B96E") +
scale_x_continuous(breaks = 0:2) +
scale_y_continuous(labels = scales::comma) +
labs(title = "Plan de dotación: rango de escenarios",
subtitle = "Banda = optimista–pesimista (rotación ±25%) · línea = base",
x = "Año", y = "Dotación activa proyectada (sin contratar)") +
theme_minimal()
geom_ribbon(). Sus argumentos esenciales son ymin y ymax: los límites inferior y superior de la banda. El argumento alpha controla la transparencia (0 = invisible, 1 = opaco). Se combina con geom_line() para superponer la trayectoria central: primero el ribbon (fondo), luego la línea (frente).
El resultado muestra que la brecha es la distancia vertical entre la línea punteada (demanda) y la banda sombreada. El mensaje visual es inmediato: la organización necesita contratar masivamente para llegar a la meta, y el rango de incertidumbre es considerable.
gtPara la presentación al directorio, la visualización se complementa con una tabla que muestra las cifras por escenario:
# Construir la tabla de brechas por escenario
plan_esc <- totales |>
filter(anio == 2) |>
transmute(escenario,
oferta_interna = round(activos),
demanda = 1680,
contratar = round(1680 - activos))
# Formatear con gt
plan_esc |>
gt() |>
tab_header(title = "Plan de contratación a 2 años — InnovaCo SpA",
subtitle = "Objetivo +40% · tres escenarios de rotación") |>
fmt_number(columns = c(oferta_interna, demanda, contratar), decimals = 0) |>
cols_label(escenario = "Escenario", oferta_interna = "Oferta interna",
demanda = "Demanda", contratar = "A contratar (neto)")
El resultado es una tabla limpia con los tres escenarios: Optimista (~734), Base (~812), Pesimista (~886).
Una decisión robusta es buena a través del rango de futuros plausibles, no solo óptima para el escenario más probable. Para el plan de dotación de InnovaCo, la recomendación operativa emerge directamente de los tres escenarios:
Declarar los supuestos no es una debilidad; es la marca de un análista competente. El directorio de InnovaCo necesita saber de qué depende el plan para poder cuestionar los supuestos con los que está en desacuerdo.
| # | Supuesto | Si es incorrecto… |
|---|---|---|
| 1 | Ausencia de memoria: la probabilidad de salir solo depende del nivel actual, no de la antigüedad. | El modelo subestima la rotación de nuevos ingresos (mayor hazard en los primeros años). |
| 2 | Estacionariedad: las tasas de 2021–2025 seguirán vigentes en 2026–2027. | Si hay un ciclo económico adverso o el programa de retención funciona, las tasas cambian. |
| 3 | Homogeneidad: todos los de un nivel tienen la misma probabilidad de salir o promover. | El modelo no distingue alto desempeño de bajo desempeño. |
| 4 | Gerencia corregida: usamos 92%/8% como supuesto experto, no la estimación empírica de 22 casos. | Si la tasa real de salida de gerencia es mayor, la proyección sobreestima la estabilidad en el nivel alto. |
| 5 | Demanda uniforme: +40% proporcional en todos los niveles. | Si las jefaturas no necesitan crecer tanto, la brecha real es menor y el plan de desarrollo interno más urgente. |
| 6 | Mercado externo: asumimos que los candidatos están disponibles al precio histórico. | Un mercado laboral apretado para ingenieros (el perfil de Profesional en InnovaCo) puede hacer la contratación más cara o imposible. |
El modelo de Markov que construimos está orientado al crecimiento: proyectar cuántos contratar. Pero la misma herramienta puede usarse para proyectar reducciones. Si la demanda cayera un 20% en vez de crecer un 40%, el análisis diría que sobran ~400 personas. El modelo no distingue entre crecimiento y ajuste — produce números en ambos casos con la misma indiferencia matemática.
Esto plantea preguntas que el analista de personas necesita poder sostener:
| Término | Significado |
|---|---|
purrr::map() | Aplica una función a cada elemento de una lista; devuelve una lista. |
| List-column | Columna de un tibble que contiene listas o tibbles anidados. |
unnest() | Despliega una list-column a formato largo (filas individuales). |
geom_ribbon() | Dibuja un área sombreada entre dos curvas (ymin y ymax). |
| Escenario | Una combinación de supuestos sobre el futuro (rotación, crecimiento). |
| Decisión robusta | Decisión que es buena a través del rango de escenarios, no solo óptima para uno. |
| Planificación por escenarios | Mapear futuros plausibles en lugar de buscar el pronóstico "correcto". |
Cappelli, P. (2008). Talent management for the twenty-first century. Harvard Business Review, 86(3), 74–81.
Schoemaker, P. J. H. (1995). Scenario planning: A tool for strategic thinking. Sloan Management Review, 36(2), 25–40.
Sterman, J. D. (2000). Business dynamics: Systems thinking and modeling for a complex world. McGraw-Hill.
Wickham, H., & Grolemund, G. (2017). R for data science. O'Reilly. Disponible en r4ds.had.co.nz.