1 1 Introducción

Qué queremos: entender por qué la gente rota de cargo y predecir esa rotación.
Con esto, RR.HH. puede intervenir antes y no perder talento. No es solo describir: modelamos para decidir.

Marco teórico exprés (GLM binomial – enlace logit): - Respuesta binaria: \(Y\in\{0,1\}\) (1 = rotación, 0 = no rotación). - Modelo: \(\text{logit}(p)=\log\frac{p}{1-p}=\beta_0+\beta_1X_1+\cdots+\beta_kX_k\). - Interpretación: \(\exp(\beta_j)\) es el odds ratio (OR). - Estimación: máxima verosimilitud.
- Evaluación: ROC/AUC y matriz de confusión.

# 1) Carga de paquetes y datos
if (!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
  devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}

library(paqueteMODELOS)
library(dplyr); library(tidyr)
library(ggplot2); library(scales); library(forcats); library(gridExtra)
library(rsample)   # split estratificado
library(caret)     # matriz de confusión
library(pROC)      # ROC/AUC
library(summarytools) # calidad de datos incrustada
library(broom)     # tablas limpias de modelos
suppressWarnings({ if (requireNamespace("car", quietly = TRUE)) library(car) })

# Cargo la data y creo y = 1 si "Si", 0 si "No"
data("rotacion")
datos <- rotacion %>% mutate(y = if_else(Rotación == "Si", 1L, 0L))

2 3 Selección de variables e hipótesis

# 2) Selección de variables + hipótesis con referencias

#   •   Técnica: definición de variables predictoras (3 categóricas + 3 numéricas),              limpieza mínima de datos y tabla de hipótesis con referencias teóricas.
#   •   Resultado: se estableció un marco conceptual claro: horas extra, equilibrio, estado       civil, satisfacción, ingreso y antigüedad como predictores de la rotación. La tabla       1.1 mostró los signos esperados y la bibliografía.

vars_cat <- c("Horas_Extra", "Equilibrio_Trabajo_Vida", "Estado_Civil")
vars_num <- c("Satisfación_Laboral", "Ingreso_Mensual", "Antigüedad")
vars_all <- c(vars_cat, vars_num)

df <- datos %>%
  select(y, Rotación, all_of(vars_all)) %>%
  drop_na() %>%
  mutate(
    Horas_Extra = factor(Horas_Extra),
    Estado_Civil = factor(Estado_Civil),
    Equilibrio_Trabajo_Vida = ordered(Equilibrio_Trabajo_Vida, levels = sort(unique(Equilibrio_Trabajo_Vida)))
  )

hipotesis <- tibble::tibble(
  Variable = vars_all,
  Tipo = c("Categ. Binaria","Categ. Ordinal","Categ. Nominal",
           "Cuantitativa (1-4)","Cuantitativa","Cuantitativa"),
  Hipótesis = c(
    "H1: Horas extra ↑ rotación (desgaste).",
    "H2: Menor equilibrio ↑ rotación.",
    "H3: Solteros rotan más que casados.",
    "H4: ↑ Satisfacción ↓ rotación.",
    "H5: ↑ Ingreso ↓ rotación.",
    "H6: ↑ Antigüedad ↓ rotación."
  ),
  `Signo esperado` = c("+","-","Soltero>Casado","-","-","-"),
  Referencia = c(
    "Kahn (1990), burnout.",
    "Greenhaus & Beutell (1985), trabajo-familia.",
    "Becker (1993), movilidad laboral.",
    "Locke (1976), satisfacción-rotación.",
    "Herzberg (1959), motivación-higiene.",
    "Meyer & Allen (1991), compromiso."
  )
)
knitr::kable(hipotesis, caption = "Tabla 1.1 Hipótesis y referencias bibliográficas")
Tabla 1.1 Hipótesis y referencias bibliográficas
Variable Tipo Hipótesis Signo esperado Referencia
Horas_Extra Categ. Binaria H1: Horas extra ↑ rotación (desgaste). + Kahn (1990), burnout.
Equilibrio_Trabajo_Vida Categ. Ordinal H2: Menor equilibrio ↑ rotación. - Greenhaus & Beutell (1985), trabajo-familia.
Estado_Civil Categ. Nominal H3: Solteros rotan más que casados. Soltero>Casado Becker (1993), movilidad laboral.
Satisfación_Laboral Cuantitativa (1-4) H4: ↑ Satisfacción ↓ rotación. - Locke (1976), satisfacción-rotación.
Ingreso_Mensual Cuantitativa H5: ↑ Ingreso ↓ rotación. - Herzberg (1959), motivación-higiene.
Antigüedad Cuantitativa H6: ↑ Antigüedad ↓ rotación. - Meyer & Allen (1991), compromiso.

3 4 Calidad de datos

# 3) Calidad de datos

#   •   Técnica: uso de summarytools::dfSummary() para describir variables, distribuciones       y valores faltantes.

#   •   Resultado: la base tiene 1470 observaciones completas. Se detecta desbalance           (rotación = 16.1%), asimetría en ingreso y antigüedad, y predominio de ciertos           niveles en variables categóricas. Esto confirma que los datos son utilizables sin        imputaciones.
st_res <- summarytools::dfSummary(
  df,
  graph.magnif = 0.8,
  valid.col = TRUE
)
# NO incluir Bootstrap propio de summarytools
print(st_res, method = "render", bootstrap.css = FALSE, footnote = NA)

Data Frame Summary

df

Dimensions: 1470 x 8
Duplicates: 0
No Variable Stats / Values Freqs (% of Valid) Graph Valid Missing
1 y [integer]
Min : 0
Mean : 0.2
Max : 1
0:1233(83.9%)
1:237(16.1%)
1470 (100.0%) 0 (0.0%)
2 Rotación [character]
1. No
2. Si
1233(83.9%)
237(16.1%)
1470 (100.0%) 0 (0.0%)
3 Horas_Extra [factor]
1. No
2. Si
1054(71.7%)
416(28.3%)
1470 (100.0%) 0 (0.0%)
4 Equilibrio_Trabajo_Vida [ordered, factor]
1. 1
2. 2
3. 3
4. 4
80(5.4%)
344(23.4%)
893(60.7%)
153(10.4%)
1470 (100.0%) 0 (0.0%)
5 Estado_Civil [factor]
1. Casado
2. Divorciado
3. Soltero
673(45.8%)
327(22.2%)
470(32.0%)
1470 (100.0%) 0 (0.0%)
6 Satisfación_Laboral [numeric]
Mean (sd) : 2.7 (1.1)
min ≤ med ≤ max:
1 ≤ 3 ≤ 4
IQR (CV) : 2 (0.4)
1:289(19.7%)
2:280(19.0%)
3:442(30.1%)
4:459(31.2%)
1470 (100.0%) 0 (0.0%)
7 Ingreso_Mensual [numeric]
Mean (sd) : 6502.9 (4708)
min ≤ med ≤ max:
1009 ≤ 4919 ≤ 19999
IQR (CV) : 5468 (0.7)
1349 distinct values 1470 (100.0%) 0 (0.0%)
8 Antigüedad [numeric]
Mean (sd) : 7 (6.1)
min ≤ med ≤ max:
0 ≤ 5 ≤ 40
IQR (CV) : 6 (0.9)
37 distinct values 1470 (100.0%) 0 (0.0%)

4 5 EDA univariado (perfiles)

# 4) EDA univariado (perfiles)

#   •   Técnica: gráficos de distribución (barras, histogramas, boxplots) para respuesta,        categóricas y numéricas.
#   •   Resultado:
#   •   Rotación minoritaria (16%).
#   •   Horas extra: mayoría no hace, pero quienes sí tienen más rotación.
#   •   Equilibrio: mayoría en nivel medio.
#   •   Ingreso y antigüedad: distribuciones sesgadas a la derecha.
#   •   Satisfacción laboral: mayoría en niveles 3–4.
#     Se observa que las variables tienen sentido para el modelo.

tabla_y <- table(df$y); prop_y <- prop.table(tabla_y); prop_y
## 
##         0         1 
## 0.8387755 0.1612245
ggplot(df, aes(x = factor(y, labels = c("No","Sí")))) +
  geom_bar(fill = c("steelblue","salmon"), alpha = .85) +
  geom_text(stat = "count", aes(label = paste0(after_stat(count), "\n",
       round(after_stat(count)/sum(after_stat(count))*100,1), "%")),
       vjust = -0.5) +
  labs(title = "Distribución de Rotación", x = "Rotación", y = "Frecuencia") +
  theme_minimal()

# Distribuciones categóricas
plot_cat <- function(var){
  p <- ggplot(df, aes(x = .data[[var]])) +
    geom_bar(fill = "steelblue", alpha = .8) +
    geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
    labs(title = paste("Distribución:", var), x = var, y = "Frecuencia") +
    theme_minimal()
  if (var == "Estado_Civil") p <- p + theme(axis.text.x = element_text(angle = 45, hjust = 1))
  print(p)
}
for (v in vars_cat) plot_cat(v)

# Numéricas: histograma + boxplot
plot_num <- function(var){
  p1 <- ggplot(df, aes(x = .data[[var]])) +
    geom_histogram(bins = 30, fill = "darkseagreen", color = "black", alpha = .75) +
    labs(title = paste("Histograma:", var), x = var, y = "Frecuencia") +
    theme_minimal()
  p2 <- ggplot(df, aes(y = .data[[var]])) +
    geom_boxplot(fill = "khaki", alpha = .8, width = .4) +
    coord_flip() +
    labs(title = paste("Boxplot:", var), y = var, x = "") +
    theme_minimal()
  print(gridExtra::grid.arrange(p1, p2, ncol = 2))
}
for (v in vars_num) plot_num(v)
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]

5 6 Split estratificado 60/40

# 5) Split estratificado 60/40

#   •   Técnica: partición estratificada (60% entrenamiento, 40% prueba) con rsample.
#   •   Resultado: balance conservado entre train y test, asegurando que la proporción de        rotación se mantenga. Esto permite evaluar el modelo sin sesgos.

split <- rsample::initial_split(df, prop = 0.6, strata = y)
train  <- rsample::training(split)
test   <- rsample::testing(split)

cat("Train:", nrow(train), " | Test:", nrow(test), "\n",
    "Rotación en train:", round(mean(train$y),3),
    " | en test:", round(mean(test$y),3), "\n")
## Train: 881  | Test: 589 
##  Rotación en train: 0.161  | en test: 0.161

6 7 Análisis bivariado (categóricas y numéricas)

# 6) Análisis bivariado
    
#  •    Técnica:
#  •    Para categóricas: tablas cruzadas, Chi² y gráficos de tasa.
#  •    Para numéricas: t-test, boxplots comparativos y tendencia logística.
#  •    Resultado:
#  •    Categóricas: asociaciones significativas (solteros y horas extra rotan más; bajo         equilibrio aumenta rotación).
#  •    Numéricas: diferencias significativas (menor satisfacción e ingreso → mayor              rotación).
#     El análisis bivariado muestra evidencias en la dirección esperada para H1–H5.

# Categóricas: tablas cruzadas + Chi² + tasa de rotación


for (v in vars_cat){
  tab <- table(train[[v]], train$y)
  colnames(tab) <- c("No (0)", "Sí (1)")
  knitr::kable(addmargins(tab), caption = paste("Tabla cruzada:", v))

  chi <- chisq.test(tab)
  cat("Chi-cuadrado:", round(chi$statistic, 3),
      "| gl:", chi$parameter, "| p:", format(chi$p.value, scientific = TRUE), "\n",
      if (chi$p.value < 0.05) "✓ Asociación significativa (rechazo H0)." else "No significativa.", "\n")

  prop_fila <- prop.table(tab, 1)[,2]
  dfp <- tibble::tibble(cat = names(prop_fila), tasa = as.numeric(prop_fila))
  p <- ggplot(dfp, aes(x = reorder(cat, tasa), y = tasa)) +
    geom_col(fill = "coral", alpha = .85) +
    coord_flip() +
    scale_y_continuous(labels = scales::percent) +
    labs(title = paste("Tasa de rotación por", v), x = v, y = "Tasa de rotación") +
    theme_minimal()
  print(p)
}
## Chi-cuadrado: 62.818 | gl: 1 | p: 2.267422e-15 
##  ✓ Asociación significativa (rechazo H0).
## Chi-cuadrado: 17.258 | gl: 3 | p: 6.253815e-04 
##  ✓ Asociación significativa (rechazo H0).
## Chi-cuadrado: 28.603 | gl: 2 | p: 6.151536e-07 
##  ✓ Asociación significativa (rechazo H0).
# Numéricas: t-test + boxplot + tendencia logística
for (v in vars_num){
  g0 <- train %>% filter(y == 0) %>% pull(.data[[v]])
  g1 <- train %>% filter(y == 1) %>% pull(.data[[v]])
  tt <- t.test(g0, g1)
  cat("\nVariable numérica:", v, "t =", round(tt$statistic, 3), "| p =", format(tt$p.value, scientific = TRUE),
      if (tt$p.value < 0.05) "✓ Diferencia significativa." else "No significativa.", "\n")

  p1 <- ggplot(train, aes(x = factor(y, labels = c("No","Sí")), y = .data[[v]], fill = factor(y))) +
    geom_boxplot(alpha = .8) +
    scale_fill_manual(values = c("lightblue","lightcoral")) +
    labs(title = paste(v, "por Rotación"), x = "Rotación", y = v) +
    theme_minimal() + theme(legend.position = "none")

  p2 <- ggplot(train, aes(x = .data[[v]], y = y)) +
    geom_jitter(height = 0.05, alpha = .25) +
    geom_smooth(method = "glm", method.args = list(family = binomial()), se = TRUE, color = "red") +
    labs(title = "Tendencia logística", x = v, y = "Prob. rotación") +
    theme_minimal()
  print(gridExtra::grid.arrange(p1, p2, ncol = 2))
}
## 
## Variable numérica: Satisfación_Laboral t = 3.643 | p = 3.457159e-04 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## 
## Variable numérica: Ingreso_Mensual t = 5.289 | p = 2.735888e-07 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## 
## Variable numérica: Antigüedad t = 3.31 | p = 1.109546e-03 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
##   z     cells    name           grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]

7 8 Estimación del modelo logit

# 7) Estimación del modelo logit

#   •   Técnica: regresión logística binomial (glm) con las 6 variables. OR e intervalos de       confianza para interpretación.
#   •   Resultado:
#   •   Horas extra, equilibrio, estado civil (solteros), satisfacción e ingreso →               significativos.
#   •   Antigüedad no significativa.
#   •   Significancia global (LR χ²) muy alta → el modelo es mejor que el nulo.
#     El logit captura bien los predictores relevantes.


form <- y ~ Horas_Extra + Equilibrio_Trabajo_Vida + Estado_Civil +
  Satisfación_Laboral + Ingreso_Mensual + Antigüedad

mod <- glm(form, data = train, family = binomial())
summary(mod)
## 
## Call:
## glm(formula = form, family = binomial(), data = train)
## 
## Coefficients:
##                             Estimate Std. Error z value Pr(>|z|)    
## (Intercept)               -0.4720962  0.3501646  -1.348  0.17759    
## Horas_ExtraSi              1.7005677  0.2106426   8.073 6.85e-16 ***
## Equilibrio_Trabajo_Vida.L -0.9706096  0.3349008  -2.898  0.00375 ** 
## Equilibrio_Trabajo_Vida.Q  0.5929861  0.2746021   2.159  0.03082 *  
## Equilibrio_Trabajo_Vida.C -0.0805798  0.1954857  -0.412  0.68019    
## Estado_CivilDivorciado    -0.5068281  0.3174625  -1.596  0.11038    
## Estado_CivilSoltero        0.9716104  0.2225038   4.367 1.26e-05 ***
## Satisfación_Laboral       -0.4135609  0.0917855  -4.506 6.61e-06 ***
## Ingreso_Mensual           -0.0001021  0.0000339  -3.011  0.00260 ** 
## Antigüedad                -0.0326126  0.0236644  -1.378  0.16816    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 778.14  on 880  degrees of freedom
## Residual deviance: 630.52  on 871  degrees of freedom
## AIC: 650.52
## 
## Number of Fisher Scoring iterations: 5
# Chequeo rápido de multicolinealidad (si 'car' está disponible)
if (requireNamespace("car", quietly = TRUE)) {
  vif_vals <- car::vif(mod)
  print(vif_vals)
  cat("VIF máximo:", round(max(vif_vals), 2), "\n")
}
##                             GVIF Df GVIF^(1/(2*Df))
## Horas_Extra             1.072495  1        1.035613
## Equilibrio_Trabajo_Vida 1.030890  3        1.005083
## Estado_Civil            1.062364  2        1.015239
## Satisfación_Laboral     1.049526  1        1.024464
## Ingreso_Mensual         1.327795  1        1.152300
## Antigüedad              1.312828  1        1.145787
## VIF máximo: 3
coefs <- broom::tidy(mod) %>%
  mutate(OR = exp(estimate),
         IC_inf = exp(estimate - 1.96 * std.error),
         IC_sup = exp(estimate + 1.96 * std.error),
         Sig = ifelse(p.value < 0.05, "✓", ""))

# Nota de interpretación para el lector:
# Equilibrio_Trabajo_Vida es ordinal y, por defecto, usa contrastes polinomiales.
# En los nombres de términos: .L = tendencia lineal y .Q = tendencia cuadrática.

knitr::kable(coefs %>% select(term, estimate, OR, IC_inf, IC_sup, p.value, Sig),
             digits = 3, caption = "Coeficientes del logit, OR e IC 95%")
Coeficientes del logit, OR e IC 95%
term estimate OR IC_inf IC_sup p.value Sig
(Intercept) -0.472 0.624 0.314 1.239 0.178
Horas_ExtraSi 1.701 5.477 3.624 8.277 0.000
Equilibrio_Trabajo_Vida.L -0.971 0.379 0.197 0.730 0.004
Equilibrio_Trabajo_Vida.Q 0.593 1.809 1.056 3.099 0.031
Equilibrio_Trabajo_Vida.C -0.081 0.923 0.629 1.353 0.680
Estado_CivilDivorciado -0.507 0.602 0.323 1.122 0.110
Estado_CivilSoltero 0.972 2.642 1.708 4.087 0.000
Satisfación_Laboral -0.414 0.661 0.552 0.792 0.000
Ingreso_Mensual 0.000 1.000 1.000 1.000 0.003
Antigüedad -0.033 0.968 0.924 1.014 0.168
# Significancia global
lr_chi <- mod$null.deviance - mod$deviance
lr_p <- pchisq(lr_chi, mod$df.null - mod$df.residual, lower.tail = FALSE)
cat("LR χ² =", round(lr_chi, 3), "| p =", format(lr_p, scientific = TRUE), "\n")
## LR χ² = 147.622 | p = 2.740858e-27

8 9 Evaluación (ROC/AUC y matrices)

# 8) Evaluación (ROC/AUC y matrices de confusión)

#   •   Técnica:
#   •   Curva ROC y AUC para evaluar poder discriminante.
#   •   Matrices de confusión en corte 0.5 y en corte óptimo (Youden).
#   •   Resultado:
#   •   AUC = 0.747 (modelo aceptable).
#   •   Corte óptimo mejora sensibilidad/especificidad frente al umbral fijo 0.5.
#     El modelo predice razonablemente la rotación y permite ajustar decisiones según          tolerancia a falsos positivos/negativos.


prob_test <- predict(mod, newdata = test, type = "response")
roc_obj <- pROC::roc(test$y, prob_test, quiet = TRUE)
auc_val <- pROC::auc(roc_obj)
old_par <- par(no.readonly = TRUE)
par(mar = c(5, 5, 4.5, 2))  # márgenes: abajo, izq, arriba, der
plot(roc_obj, main = paste("Curva ROC | AUC =", round(as.numeric(auc_val), 3)),
     col = "blue", lwd = 2); abline(a = 0, b = 1, lty = 2, col = "red")
par(old_par)

metricas_desde_cm <- function(obs, pred_bin){
  cm <- table(Real = factor(obs, levels = c(0,1), labels = c("No","Sí")),
              Pred = factor(pred_bin, levels = c(0,1), labels = c("No","Sí")))
  TN <- cm[1,1]; FP <- cm[1,2]; FN <- cm[2,1]; TP <- cm[2,2]
  exact <- (TP + TN)/sum(cm)
  sens  <- TP/(TP+FN); espec <- TN/(TN+FP)
  prec  <- TP/(TP+FP); npv <- TN/(TN+FN)
  list(cm=cm, exact=exact, sens=sens, espec=espec, prec=prec, npv=npv)
}

pred05 <- ifelse(prob_test >= 0.5, 1, 0); m05 <- metricas_desde_cm(test$y, pred05)
knitr::kable(m05$cm, caption = "Matriz de confusión (corte 0.5)")
Matriz de confusión (corte 0.5)
No
No 479 15
70 25
umbral <- pROC::coords(roc_obj, "best", best.method = "youden")$threshold
cat("Umbral óptimo (Youden):", round(umbral, 3), "\n")
## Umbral óptimo (Youden): 0.184
pred_opt <- ifelse(prob_test >= umbral, 1, 0); mopt <- metricas_desde_cm(test$y, pred_opt)
knitr::kable(mopt$cm, caption = paste0("Matriz de confusión (umbral óptimo=", round(umbral,3), ")"))
Matriz de confusión (umbral óptimo=0.184)
No
No 384 110
33 62

9 10 Predicción individual

# 9) Predicción individual

#   •   Técnica: simulación de un caso hipotético (empleado con horas extra, bajo                equilibrio, soltero, baja satisfacción). Se calcula la probabilidad de rotación y        se toma una decisión con base en el umbral óptimo.
#   •   Resultado: el modelo devuelve una probabilidad alta y la decisión “INTERVENIR”.          Esto ejemplifica cómo usar el modelo en la práctica de RR.HH.


empleado <- data.frame(
  Horas_Extra = factor("Si", levels = levels(train$Horas_Extra)),
  Equilibrio_Trabajo_Vida = ordered("1", levels = levels(train$Equilibrio_Trabajo_Vida)),
  Estado_Civil = factor("Soltero", levels = levels(train$Estado_Civil)),
  Satisfación_Laboral = 2,
  Ingreso_Mensual = median(train$Ingreso_Mensual),
  Antigüedad = 1
)
prob_emp <- predict(mod, newdata = empleado, type = "response")
decision <- ifelse(prob_emp >= umbral, "INTERVENIR", "Monitoreo")
cat("Probabilidad de rotación:", round(prob_emp,3), " (", round(prob_emp*100,1), "%)\n",
    "Umbral óptimo:", round(umbral,3), "-> Decisión:", decision, "\n")
## Probabilidad de rotación: 0.858  ( 85.8 %)
##  Umbral óptimo: 0.184 -> Decisión: INTERVENIR

10 11 Conclusiones

# 10 Conclusiones

#   1.  Comparación de signos esperados vs. estimados
# Cuando miramos los coeficientes del modelo, la mayoría se comportaron como lo
# habíamos anticipado en la Tabla 1.1. Por ejemplo:
#   •   Horas extra (H1): el coeficiente salió positivo (OR > 1), lo que confirma 
# que trabajar más horas está ligado a un mayor riesgo de rotación.
#   •   Equilibrio trabajo–vida (H2): menor equilibrio también mostró una relación
# positiva con rotación, en línea con la hipótesis.
#   •   Estado civil (H3): ser soltero se asoció con más rotación comparado con 
# casados, lo que también coincide con lo esperado.
#   •   Satisfacción laboral (H4): coeficiente negativo (OR < 1), lo cual confirma 
# que a mayor satisfacción, menor rotación.
#   •   Ingreso mensual (H5): mayor ingreso reduce la probabilidad de rotación.
#   •   Antigüedad (H6): la señal fue negativa, pero sin significancia estadística,
# es decir, no se puede concluir que la antigüedad sea un factor determinante.
# En resumen, se confirma la lógica de las hipótesis H1–H5. H6 queda débil.
#   2.  Variables significativas (p < 0.05)
# Los resultados muestran que las variables Horas extra, Equilibrio 
# trabajo–vida, Estado civil, Satisfacción laboral e Ingreso mensual sí influyen
# de manera estadísticamente significativa. Esto significa que no es casualidad: 
# realmente aportan en explicar por qué la gente rota.
#   3.  Interpretación con OR (Odds Ratio)
# El OR nos dice cuánto cambia el riesgo de rotación con cada variable. 
# Por ejemplo:
#   •   Horas extra: OR ≈ 5.5 → quienes hacen horas extra tienen 5 veces más 
# probabilidad de rotar.
#   •   Satisfacción laboral: OR < 1 → cada punto extra en satisfacción baja 
# claramente el riesgo.
#   •   Ingreso mensual: aunque cercano a 1, su efecto es protector, porque 
# ingresos más altos hacen que la gente se quede.
#   4.  AUC como medida de discriminación
# El modelo logró un AUC ≈ 0.75, lo que se considera un poder predictivo 
# aceptable (ni pobre ni excelente, está en un nivel útil). Esto significa que
# el modelo puede distinguir en un 75% de los casos si un empleado rotará o no.
#   5.  Matrices de confusión y trade-off
# Con el corte estándar (0.5) se gana en especificidad (detecta bien a los
# que se quedan), pero se pierde sensibilidad (no detecta todos los que rotan).
# Ajustando el umbral con el criterio de Youden, se logra un mejor balance, 
# sacrificando un poco de un lado para ganar del otro.


# Estrategias prácticas (basadas en los hallazgos)
#   1.  Limitar las horas extra (H1)
# El desgaste físico y emocional por trabajar demasiado es uno de los factores
# más claros de rotación. Una estrategia es poner topes a las horas extras, 
# rotar tareas críticas o compensar con descansos efectivos.
#   2.  Reforzar la satisfacción laboral (H4)
# La satisfacción salió como variable clave y protectora. Aquí Recursos Humanos
# puede trabajar en:
#   •   Programas de feedback continuo.
#   •   Reconocimiento y recompensas.
#   •   Planes de carrera claros para que los empleados vean futuro en la empresa.
#   3.  Cuidar el onboarding y la antigüedad (H6, aunque no significativa)
# Aunque la antigüedad no fue concluyente estadísticamente, sí es lógico que 
# empleados nuevos sean más vulnerables a irse. Por eso, reforzar los primeros
# meses con programas de mentoría, acompañamiento cercano y capacitación puede 
# ayudar a retenerlos.


# En resumen:
# El modelo confirma que la rotación no es al azar. Hay patrones claros: horas
# extra, satisfacción laboral, equilibrio vida-trabajo, estado civil e ingreso 
# mensual explican buena parte del fenómeno. Con estas palancas se pueden 
# diseñar políticas de retención más efectivas y reducir la rotación de forma
# medible.

# Limitaciones y siguientes pasos 
# - Clase minoritaria (16%): considerar weights en glm o técnicas como SMOTE para robustez.
# - Validación cruzada para estabilidad del AUC y selección de variables.
# - Calibración de probabilidades (Platt/Isotónica) si el score se usará para umbrales operativos.

sessionInfo()