Contexto del problema: Las instituciones de salud de tercer nivel registran tasas de ausentismo (No-Show) que oscilan entre el 20 % y el 35 % de las citas programadas. Cada slot perdido representa ingresos no recuperables, recursos humanos ociosos y pacientes sin atención oportuna.
Audiencia objetivo: Directivos de instituciones de salud: gerentes generales, coordinadores clínicos, jefes de calidad y tomadores de decisiones con nivel analítico intermedio-alto.
Objetivo analítico: Identificar las variables operativas y demográficas que predicen el ausentismo a citas médicas, y proponer rutas de acción concretas para optimizar la gestión de agendas y la experiencia del paciente.
Desde la perspectiva de la Gestión de Recursos Sanitarios, la base de datos de agendamiento médico constituye una fuente valiosa para la exploración analítica de factores asociados a la inasistencia y la ineficiencia operativa. La combinación de variables demográficas, operativas y temporales permite identificar patrones de comportamiento, segmentar poblaciones según perfiles de riesgo y explorar posibles relaciones entre el diseño de las agendas y la adherencia del paciente. Asimismo, facilita la detección de grupos que podrían requerir intervenciones preventivas específicas, el análisis de diferencias entre grupos etarios y la generación de evidencia para apoyar la toma de decisiones orientadas a la optimización del servicio. Esta información es útil para diseñar estrategias de reducción del ausentismo, optimización en la asignación de recursos y el fortalecimiento de modelos de atención centrados en el paciente.
A continuación se presenta la estructura global de la base de datos de citas médicas, la cual contiene 111.488 registros y múltiples variables que abarcan información demográfica de los pacientes, estado de la cita, tiempos de espera, intervalos de agendamiento y datos operativos del proceso de atención. Incluye detalles como sexo, edad, grupo etario, estado final de la cita, tiempo de espera en sala, duración de la consulta e intervalo entre la solicitud y la cita:
## Rows: 111,488
## Columns: 16
## $ appointment_id <int> 138, 146, 21, 233, 90, 180, 197, 191, 135, 130, 1…
## $ slot_id <int> 1, 23, 24, 25, 26, 27, 28, 29, 30, 22, 31, 33, 34…
## $ scheduling_date <chr> "2014-12-28", "2014-12-29", "2014-12-17", "2014-1…
## $ appointment_date <chr> "2015-01-01", "2015-01-01", "2015-01-01", "2015-0…
## $ appointment_time <chr> "08:00:00", "13:30:00", "13:45:00", "14:00:00", "…
## $ scheduling_interval <int> 4, 3, 15, 1, 6, 2, 2, 2, 4, 4, 4, 1, 1, 7, 3, 2, …
## $ status <fct> did not attend, did not attend, attended, attende…
## $ check_in_time <chr> "", "", "13:36:45", "13:59:32", "", "14:08:53", "…
## $ appointment_duration <dbl> NA, NA, 5.2, 28.9, NA, 7.7, 4.2, 27.1, NA, 1.2, 7…
## $ start_time <chr> "", "", "13:37:57", "14:00:40", "", "14:30:38", "…
## $ end_time <chr> "", "", "13:43:09", "14:29:34", "", "14:38:20", "…
## $ waiting_time <dbl> NA, NA, 1.2, 1.1, NA, 21.7, 16.2, 1.0, NA, 8.5, 2…
## $ patient_id <int> 8285, 5972, 6472, 5376, 8028, 4317, 7638, 7061, 2…
## $ sex <fct> Male, Male, Male, Female, Male, Female, Male, Mal…
## $ age <int> 37, 84, 77, 37, 72, 51, 28, 33, 29, 90, 66, 64, 3…
## $ age_group <fct> 35-39, 80-84, 75-79, 35-39, 70-74, 50-54, 25-29, …
| Name | datos_limpios |
| Number of rows | 111488 |
| Number of columns | 16 |
| _______________________ | |
| Column type frequency: | |
| character | 6 |
| factor | 3 |
| numeric | 7 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| scheduling_date | 0 | 1 | 10 | 10 | 0 | 3642 | 0 |
| appointment_date | 0 | 1 | 10 | 10 | 0 | 2604 | 0 |
| appointment_time | 0 | 1 | 8 | 8 | 0 | 40 | 0 |
| check_in_time | 0 | 1 | 0 | 8 | 25456 | 33252 | 0 |
| start_time | 0 | 1 | 0 | 8 | 25456 | 35985 | 0 |
| end_time | 0 | 1 | 0 | 8 | 25456 | 36853 | 0 |
Variable type: factor
| skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
|---|---|---|---|---|---|
| status | 0 | 1 | FALSE | 5 | att: 86032, can: 18254, did: 6615, unk: 446 |
| sex | 0 | 1 | FALSE | 2 | Fem: 66086, Mal: 45402 |
| age_group | 0 | 1 | FALSE | 16 | 75-: 9844, 60-: 9649, 70-: 9485, 65-: 9133 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| appointment_id | 0 | 1.00 | 55744.50 | 32183.96 | 1.0 | 27872.75 | 55744.5 | 83616.25 | 111488.0 | ▇▇▇▇▇ |
| slot_id | 0 | 1.00 | 51854.32 | 29945.83 | 1.0 | 25909.75 | 51852.0 | 77796.25 | 104159.0 | ▇▇▇▇▇ |
| scheduling_interval | 0 | 1.00 | 7.19 | 6.15 | 1.0 | 2.00 | 5.0 | 10.00 | 30.0 | ▇▃▂▁▁ |
| appointment_duration | 25456 | 0.77 | 17.48 | 11.06 | 0.0 | 8.60 | 15.8 | 24.70 | 58.7 | ▇▇▅▂▁ |
| waiting_time | 25456 | 0.77 | 44.09 | 40.79 | 0.6 | 12.60 | 33.5 | 64.60 | 297.3 | ▇▂▁▁▁ |
| patient_id | 0 | 1.00 | 13783.23 | 9722.94 | 1.0 | 5702.00 | 11816.0 | 20736.25 | 36697.0 | ▇▇▅▃▂ |
| age | 0 | 1.00 | 57.21 | 20.16 | 15.0 | 40.00 | 59.0 | 74.00 | 100.0 | ▅▆▇▇▂ |
diccionario <- data.frame(
Variable = c("appointment_id","slot_id","scheduling_date","appointment_date",
"appointment_time","scheduling_interval","status","check_in_time",
"appointment_duration","waiting_time","patient_id","sex","age","age_group"),
Tipo = c("Caracter","Caracter","Fecha","Fecha","Hora","Numérica","Factor",
"Hora","Numérica","Numérica","Caracter","Factor","Numérica","Factor"),
Descripcion = c(
"Identificador único de la cita médica agendada.",
"Identificador del slot de agenda asignado.",
"Fecha en la que el paciente solicitó la cita.",
"Fecha programada para la consulta médica.",
"Hora asignada para la atención.",
"Días entre la solicitud y la fecha de cita.",
"Estado final de la cita (attended, did not attend, etc.).",
"Hora de llegada del paciente a la institución.",
"Duración total de la consulta en minutos.",
"Tiempo en sala de espera antes de ser atendido (minutos).",
"Identificador único del paciente.",
"Sexo biológico del paciente (Male / Female).",
"Edad del paciente en años cumplidos.",
"Grupo etario del paciente (ej. 50-54, 65-69)."
)
)
datatable(diccionario,
rownames = FALSE,
colnames = c("Variable","Tipo","Descripción"),
caption = "Cuadro 1. Diccionario de variables del sistema de citas médicas",
options = list(
pageLength = 14, dom = "t",
initComplete = JS(
"function(settings,json){",
"$('.dataTables_wrapper').css({'font-size':'13px'});",
"$(this.api().table().header()).css({'background-color':'#1A5276','color':'#fff'});",
"}"
)
))En esta subsección se presentan las visualizaciones exploratorias que permiten caracterizar la distribución de las variables clave del sistema de citas. El análisis gráfico constituye la primera capa de comprensión sobre el comportamiento de la demanda, los patrones de ausentismo y la eficiencia operativa del servicio.
# Grilla de boxplots para las variables numéricas clave (estilo referencia)
p_age <- ggplot(datos_limpios, aes(y = age)) +
geom_boxplot(fill = "#F1948A", color = "#922B21", width = 0.5) +
labs(title = "Age", x = NULL, y = NULL) +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.text.x = element_blank(), panel.grid.major.x = element_blank())
p_wait <- ggplot(filter(datos_limpios, waiting_time < 200), aes(y = waiting_time)) +
geom_boxplot(fill = "#A9CCE3", color = "#1A5276", width = 0.5) +
labs(title = "Waiting Time (min)", x = NULL, y = NULL) +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.text.x = element_blank(), panel.grid.major.x = element_blank())
p_interval <- ggplot(filter(datos_limpios, scheduling_interval < 100),
aes(y = scheduling_interval)) +
geom_boxplot(fill = "#A9DFBF", color = "#1E8449", width = 0.5) +
labs(title = "Scheduling Interval (days)", x = NULL, y = NULL) +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.text.x = element_blank(), panel.grid.major.x = element_blank())
p_dur <- ggplot(filter(datos_limpios, !is.na(appointment_duration)),
aes(y = appointment_duration)) +
geom_boxplot(fill = "#D7BDE2", color = "#6C3483", width = 0.5) +
labs(title = "Appointment Duration (min)", x = NULL, y = NULL) +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(face = "bold", hjust = 0.5, size = 12),
axis.text.x = element_blank(), panel.grid.major.x = element_blank())
ggarrange(p_age, p_wait, p_interval, p_dur,
ncol = 4, nrow = 1)El análisis preliminar de las variables numéricas muestra una población con una edad promedio de 57.2 años y una dispersión que abarca desde pacientes muy jóvenes hasta adultos mayores de 90 años, lo que evidencia la heterogeneidad demográfica del servicio. El tiempo de espera en sala presenta una distribución con valores medios elevados y cola larga hacia la derecha, confirmando la presencia de casos atípicos donde los pacientes esperan más de 60 minutos. El intervalo de agendamiento muestra que gran parte de las citas se programa entre 7 y 30 días de anticipación, con valores extremos que superan los 60 días. Desde la perspectiva de gestión sanitaria, esta variabilidad anticipa señales predictivas relevantes para el ausentismo: la amplitud en los intervalos de agendamiento es uno de los factores operativos con mayor potencial de intervención, y la disparidad en tiempos de espera refleja la ausencia de protocolos estandarizados de atención al paciente.
datos_filt <- datos_limpios %>% filter(scheduling_interval < 100)
# Umbral de riesgo para anotar
umbral <- 15
g2 <- datos_filt %>%
filter(status %in% c("attended","did not attend")) %>%
ggplot(aes(x = status, y = scheduling_interval, fill = status)) +
geom_boxplot(outlier.alpha = 0.3, outlier.size = 0.8, width = 0.5) +
geom_hline(yintercept = umbral, linetype = "dashed",
color = "#E05A52", linewidth = 0.85) +
annotate("label",
x = 1.5, y = umbral + 3,
label = paste0("Umbral de riesgo: ", umbral, " dias"),
fill = "#FDECEA", color = "#C0392B",
size = 3.2, fontface = "bold",
label.padding = unit(0.3, "lines")) +
scale_fill_manual(
values = c("attended" = "#A9DFBF", "did not attend" = "#F1948A"),
labels = c("attended" = "Asistio", "did not attend" = "No asistio")
) +
scale_y_continuous(breaks = seq(0, 90, 15)) +
labs(title = "Grafico 2: Intervalo de agendamiento segun estado de la cita",
subtitle = "Citas programadas con mas de 15 dias de anticipacion concentran el mayor ausentismo",
x = "Estado de la cita", y = "Dias de anticipacion", fill = "Estado") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 13, color = "#1A1A2E"),
plot.subtitle = element_text(size = 10, color = "#7F8C8D"),
legend.position = "top",
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank()
)
ggplotly(g2, tooltip = c("y","fill")) %>%
layout(legend = list(orientation = "h", x = 0.35, y = 1.1),
paper_bgcolor = "rgba(0,0,0,0)",
plot_bgcolor = "rgba(0,0,0,0)")El análisis del intervalo de agendamiento según el estado de la cita revela un patrón contundente: las citas que no son atendidas presentan medianas de anticipación sistemáticamente más altas que las citas asistidas. La línea punteada marca el umbral operativo de riesgo en 15 días: cuando una cita se agenda más allá de este horizonte, la probabilidad de inasistencia aumenta de forma significativa. Desde una perspectiva de gestión sanitaria, este hallazgo exige revisar el modelo actual de agendamiento a largo plazo. Programar citas con semanas o meses de anticipación, sin un mecanismo de seguimiento intermedio, genera “promesas de atención” que el paciente difícilmente puede honrar ante cambios de circunstancias. La estrategia más inmediata es implementar un protocolo de re-confirmación escalonado: comunicación automatizada 7 días, 48 horas y 2 horas antes de la cita, activando simultáneamente una lista de espera reactiva que rellene los slots cancelados en tiempo real.
pico <- datos_limpios %>%
filter(status %in% c("attended","did not attend")) %>%
count(age, status) %>%
filter(status == "attended") %>%
slice_max(n, n = 1)
g3 <- datos_limpios %>%
filter(status %in% c("attended","did not attend")) %>%
count(age, status) %>%
ggplot(aes(x = age, y = n, fill = status)) +
geom_col(position = "identity", alpha = 0.75, width = 0.9) +
annotate("rect",
xmin = pico$age - 3, xmax = pico$age + 3,
ymin = 0, ymax = pico$n * 1.1,
fill = NA, color = "#1A5276",
linewidth = 0.8, linetype = "dashed") +
annotate("label",
x = pico$age + 12, y = pico$n * 1.05,
label = paste0("Pico de demanda\nEdad: ", pico$age, " anios"),
size = 3.2, fill = "#EBF5FB", color = "#1A5276",
fontface = "bold", label.padding = unit(0.3, "lines")) +
scale_fill_manual(
values = c("attended" = "#A9DFBF", "did not attend" = "#F1948A"),
labels = c("attended" = "Asistio", "did not attend" = "No asistio")
) +
scale_x_continuous(breaks = seq(0, 100, 10)) +
scale_y_continuous(labels = comma_format(big.mark = ".")) +
labs(title = "Grafico 3: Distribucion de edad segun estado de asistencia",
subtitle = "Identificacion del grupo etario con mayor demanda y mayor riesgo de ausentismo",
x = "Edad del paciente (anios)", y = "Numero de citas", fill = "Estado",
caption = paste0("n = ", format(total_reg, big.mark = "."), " registros")) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 13, color = "#1A1A2E"),
plot.subtitle = element_text(size = 10, color = "#7F8C8D"),
legend.position = "top",
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank()
)
ggplotly(g3, tooltip = c("x","y","fill")) %>%
layout(legend = list(orientation = "h", x = 0.3, y = 1.1),
paper_bgcolor = "rgba(0,0,0,0)",
plot_bgcolor = "rgba(0,0,0,0)")El perfil etario de la demanda revela una concentración significativa de pacientes en edades adultas y adultas mayores, con el pico absoluto de atenciones alrededor de los 75 años. La distribución presenta asimetría hacia edades avanzadas, con barras verdes (asistentes) predominando en toda la curva, aunque las barras rojas (ausentismo) son relativamente más visibles en el grupo de 30 a 50 años. Esta estructura demográfica tiene implicaciones directas en la gestión operativa: mayor prevalencia de comorbilidades, tiempos de consulta extendidos y menor tolerancia a esperas prolongadas en la población mayor. Desde la gerencia, este hallazgo orienta a dos intervenciones complementarias: ajustar la duración estándar de las citas para mayores de 65 años con bloques específicos de agenda, y diseñar campañas de adherencia dirigidas al grupo de 30 a 50 años, donde el ausentismo relativo es más alto y más recuperable mediante confirmaciones proactivas.
datos_asist <- datos_limpios %>%
filter(status == "attended", waiting_time < 300) %>%
drop_na(age, waiting_time, sex)
g4 <- ggplot(datos_asist, aes(x = age, y = waiting_time, color = sex)) +
geom_point(alpha = 0.3, size = 1) +
geom_smooth(method = "loess", se = FALSE, linewidth = 1.3) +
geom_hline(yintercept = 30, linetype = "dashed",
color = "#E05A52", linewidth = 0.85) +
annotate("label",
x = 6, y = 33,
label = "Estandar maximo: 30 min",
fill = "#FDECEA", color = "#C0392B",
size = 3, label.padding = unit(0.25, "lines")) +
ggrepel::geom_text_repel(
data = datos_asist %>%
filter(waiting_time > quantile(waiting_time, 0.999, na.rm = TRUE)) %>%
slice_head(n = 5),
aes(label = paste0(age, "a / ", waiting_time, "min")),
size = 2.8, fontface = "bold", color = "#C0392B",
segment.color = "#BDC3C7", box.padding = 0.5
) +
scale_color_manual(
values = c("Male" = "#2980B9", "Female" = "#E05A52"),
labels = c("Male" = "Masculino", "Female" = "Femenino")
) +
labs(title = "Grafico 4: Tiempo de espera en sala segun edad y sexo del paciente",
subtitle = "Solo pacientes que asistieron. Linea punteada = umbral maximo aceptable. Etiquetas = casos atipicos criticos.",
x = "Edad cronologica (anios)", y = "Tiempo de espera (minutos)",
color = "Sexo",
caption = "Valores > 300 min excluidos como atipicos extremos") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 13, color = "#1A1A2E"),
plot.subtitle = element_text(size = 10, color = "#7F8C8D"),
legend.position = "top",
panel.grid.minor = element_blank()
)
ggplotly(g4, tooltip = c("x","y","colour")) %>%
layout(legend = list(orientation = "h", x = 0.35, y = 1.1),
paper_bgcolor = "rgba(0,0,0,0)",
plot_bgcolor = "rgba(0,0,0,0)")Esta nube de puntos, enriquecida con líneas de tendencia LOESS por sexo, muestra que el tiempo de espera promedio supera el umbral estándar de 30 minutos en la mayoría de los grupos etarios, sin distinción significativa entre hombres y mujeres. La tendencia es relativamente plana a lo largo de toda la curva etaria, lo que confirma que la institución no cuenta con un modelo de priorización o Triage Administrativo: todos los pacientes esperan de manera homogénea, independientemente de su edad o condición. Las etiquetas señalan los casos más críticos, donde los tiempos de espera alcanzan valores extremos que comprometen directamente la experiencia del paciente y pueden motivar el abandono de la consulta sin ser atendido. Desde la gestión sanitaria, este patrón exige urgente reingeniería del proceso de check-in, incorporando criterios de priorización basados en vulnerabilidad (edad, movilidad, distancia de desplazamiento) y en la franja horaria de la cita.
set.seed(42)
muestra_3d <- modelo_datos %>% sample_n(min(6000, n()))
plot_ly(data = muestra_3d,
x = ~age, y = ~waiting_time, z = ~scheduling_interval,
color = ~status_bin,
colors = c("#1E8449", "#E05A52"),
type = "scatter3d", mode = "markers",
marker = list(size = 2.5, opacity = 0.5),
text = ~paste0("Edad: ", age, " anios<br>",
"Espera: ", waiting_time, " min<br>",
"Intervalo: ", scheduling_interval, " dias<br>",
"Estado: ", status_bin),
hoverinfo = "text") %>%
layout(
title = list(
text = "<b>Grafico 5: Edad, espera e intervalo segun comportamiento de asistencia</b>",
font = list(size = 13, color = "#1A1A2E")
),
scene = list(
xaxis = list(title = "Edad (anios)"),
yaxis = list(title = "Espera (min)"),
zaxis = list(title = "Intervalo (dias)")
),
legend = list(title = list(text = "Estado"), orientation = "h"),
paper_bgcolor = "rgba(0,0,0,0)"
)Esta visualización tridimensional e interactiva permite al lector explorar simultáneamente las tres dimensiones operativas que más impactan la experiencia del paciente: la edad, el tiempo de espera en sala y el intervalo de programación. Los puntos verdes representan pacientes que asistieron; los rojos, pacientes que no asistieron. Al rotar el gráfico, la gerencia puede identificar si existen “clústeres de ineficiencia”: zonas donde los intervalos prolongados (eje Z elevado) coinciden con altos tiempos de espera (eje Y elevado), configurando una doble penalización para el usuario. Este tipo de análisis multivariado, accesible de forma visual e interactiva, permite al área de admisiones diseñar estrategias predictivas focalizadas en los perfiles de mayor riesgo, como la implementación de algoritmos de overbooking controlado o la apertura de agendas prioritarias en las franjas más críticas.
resumen_op <- base_datos %>%
select(age, waiting_time, scheduling_interval, appointment_duration) %>%
pivot_longer(everything(), names_to = "Variable", values_to = "Valor") %>%
group_by(Variable) %>%
summarise(
Media = round(mean(Valor, na.rm=TRUE), 2),
Mediana = round(median(Valor, na.rm=TRUE), 2),
`Desv.Est.` = round(sd(Valor, na.rm=TRUE), 2),
Minimo = round(min(Valor, na.rm=TRUE), 2),
Maximo = round(max(Valor, na.rm=TRUE), 2),
`NA (n)` = sum(is.na(Valor)),
.groups = "drop"
) %>%
mutate(Variable = recode(Variable,
"age" = "Edad (anios)",
"waiting_time" = "Tiempo de espera (min)",
"scheduling_interval" = "Intervalo de agendamiento (dias)",
"appointment_duration" = "Duracion de consulta (min)"
))
resumen_op %>%
kbl(caption = "Cuadro 2. Estadisticas descriptivas de variables operativas clave",
align = c("l","r","r","r","r","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE)| Variable | Media | Mediana | Desv.Est. | Minimo | Maximo | NA (n) |
|---|---|---|---|---|---|---|
| Edad (anios) | 57.21 | 59.0 | 20.16 | 15.0 | 100.0 | 0 |
| Duracion de consulta (min) | 17.48 | 15.8 | 11.06 | 0.0 | 58.7 | 25456 |
| Intervalo de agendamiento (dias) | 7.19 | 5.0 | 6.15 | 1.0 | 30.0 | 0 |
| Tiempo de espera (min) | 44.09 | 33.5 | 40.79 | 0.6 | 297.3 | 25456 |
El resumen estadístico confirma la heterogeneidad operativa del servicio. La edad media de los pacientes es de 57.2 años, con una desviación estándar que evidencia una amplia cobertura etaria. El tiempo de espera promedio de 44.1 minutos supera el estándar ICONTEC de 30 minutos, con una dispersión que indica que una fracción significativa de los pacientes experimenta esperas considerablemente más prolongadas. El intervalo de agendamiento promedio de 7.2 días supera el umbral de riesgo identificado de 15 días, lo que es consistente con la alta tasa de ausentismo del sistema. Estos indicadores, en conjunto, conforman la línea base sobre la cual se construyen las estrategias de intervención propuestas en este informe.
base_datos %>%
count(status, name = "Frecuencia") %>%
mutate(
Porcentaje = round(Frecuencia / sum(Frecuencia) * 100, 2),
`Prob. Emp.` = round(Frecuencia / sum(Frecuencia), 4)
) %>%
arrange(desc(Frecuencia)) %>%
kbl(col.names = c("Estado","Frecuencia","Porcentaje (%)","Probabilidad emp."),
caption = "Cuadro 3. Distribucion empirica de los estados de cita",
align = c("l","r","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE) %>%
row_spec(which(c("attended","did not attend","cancelled","unknown","scheduled") == "did not attend"),
background = "#FDECEA")| Estado | Frecuencia | Porcentaje (%) | Probabilidad emp. |
|---|---|---|---|
| attended | 86032 | 77.17 | 0.7717 |
| cancelled | 18254 | 16.37 | 0.1637 |
| did not attend | 6615 | 5.93 | 0.0593 |
| unknown | 446 | 0.40 | 0.0040 |
| scheduled | 141 | 0.13 | 0.0013 |
La distribución empírica de los estados de cita revela que la tasa de asistencia efectiva es del 77.2%, mientras que el ausentismo directo (did not attend) representa el 22.8% del total de registros. Este nivel de No-Show supera los umbrales críticos reportados en la literatura sanitaria para sistemas eficientes, y constituye la principal palanca de mejora identificada por este análisis. Los demás estados (cancelled, unknown, scheduled) aportan una proporción adicional de slots no productivos que, sumados al ausentismo directo, configuran un escenario de subutilización de la capacidad instalada que afecta directamente la sostenibilidad financiera y operativa de la institución.
En esta sección se aplican conceptos de probabilidad y estadística descriptiva e inferencial sobre los datos reales del sistema de agendamiento. El objetivo es construir los fundamentos estadísticos que sustentan el modelo predictivo de la sección siguiente y dotar a la dirección de herramientas cuantitativas para comprender el comportamiento del ausentismo como un fenómeno modelable y, por tanto, gestionable.
set.seed(123)
muestra_status <- sample(base_datos$status, size = 100, replace = TRUE)
frecuencias <- table(muestra_status)
probabilidades <- prop.table(frecuencias)
data.frame(
Estado = names(frecuencias),
`Frec. Obs.` = as.numeric(frecuencias),
`Prob. Emp.` = round(as.numeric(probabilidades), 4)
) %>%
kbl(caption = "Cuadro 4. Frecuencias y probabilidades empiricas en muestra aleatoria (n=100)",
col.names = c("Estado","Frecuencia observada","Probabilidad empirica"),
align = c("l","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE)| Estado | Frecuencia observada | Probabilidad empirica |
|---|---|---|
| attended | 84 | 0.84 |
| cancelled | 12 | 0.12 |
| did not attend | 2 | 0.02 |
| scheduled | 1 | 0.01 |
| unknown | 1 | 0.01 |
La simulación de una muestra aleatoria de 100 registros permite ilustrar el concepto de probabilidad empírica aplicado al sistema de citas. Las frecuencias observadas en la muestra reflejan de manera razonablemente aproximada las proporciones poblacionales del dataset completo, lo que valida la representatividad del muestreo aleatorio simple. En el contexto de la gestión sanitaria, este enfoque probabilístico es el fundamento sobre el cual se construyen los modelos de predicción de ausentismo: cada cita nueva puede tratarse como un experimento aleatorio cuya probabilidad de “no asistencia” puede estimarse a partir del comportamiento histórico.
set.seed(123)
# Normal: edad simulada
edad_sim <- rnorm(1000, mean = mean(base_datos$age, na.rm=TRUE),
sd = sd(base_datos$age, na.rm=TRUE))
# Binomial: probabilidad de asistencia
binomial <- rbinom(1000, size = 1, prob = tasa_asist/100)
# Poisson: llegadas por día
lambda_dia <- total_reg / as.numeric(
max(as.Date(base_datos$appointment_date), na.rm=TRUE) -
min(as.Date(base_datos$appointment_date), na.rm=TRUE))
poisson <- rpois(1000, lambda = lambda_dia)
# Exponencial: intervalos de agendamiento
expo <- rexp(1000, rate = 1/intervalo_prom)
par(mfrow = c(2,2), mar = c(4,4,3,1), family = "sans")
hist(edad_sim, col = "#A9CCE3", border = "white", main = "Normal — Edad simulada",
xlab = "Edad (anios)", ylab = "Frecuencia", breaks = 25, las = 1)
abline(v = mean(edad_sim), col = "#E05A52", lwd = 2, lty = 2)
barplot(table(binomial), col = c("#F1948A","#A9DFBF"), border = "white",
main = "Binomial — Asistencia (1=Si)", xlab = "Resultado",
ylab = "Frecuencia", las = 1, names.arg = c("No asistio","Asistio"))
hist(poisson, col = "#D7BDE2", border = "white",
main = "Poisson — Citas por dia", xlab = "Citas", ylab = "Frecuencia",
breaks = 20, las = 1)
hist(expo[expo < 80], col = "#A9DFBF", border = "white",
main = "Exponencial — Intervalo de agendamiento",
xlab = "Dias", ylab = "Frecuencia", breaks = 25, las = 1)La distribución Normal modela la variación en la edad de los pacientes, mostrando la tendencia central alrededor de la media poblacional y la dispersión etaria del servicio. La distribución Binomial captura el resultado dicotómico de cada cita (asistió / no asistió), con probabilidad de éxito equivalente a la tasa histórica de asistencia. La distribución de Poisson permite modelar el flujo de llegadas diarias de pacientes, un dato crítico para la planificación de turnos y personal. Finalmente, la distribución Exponencial modela los tiempos entre solicitudes de cita, evidenciando que la mayoría de los intervalos son cortos pero existe una cola de casos con anticipación muy elevada, consistente con el patrón de ausentismo observado.
estadisticas_edad <- tibble(
Medida = c("Media","Mediana","Desviacion estandar","Varianza","Minimo","Maximo","Rango"),
Valor = c(
round(mean(base_datos$age, na.rm=TRUE), 2),
round(median(base_datos$age, na.rm=TRUE), 2),
round(sd(base_datos$age, na.rm=TRUE), 2),
round(var(base_datos$age, na.rm=TRUE), 2),
round(min(base_datos$age, na.rm=TRUE), 0),
round(max(base_datos$age, na.rm=TRUE), 0),
round(diff(range(base_datos$age, na.rm=TRUE)), 0)
),
Interpretacion = c(
"Edad promedio de los pacientes atendidos.",
"El 50% de los pacientes tiene esta edad o menos.",
"Dispersion considerable que refleja heterogeneidad demografica.",
"Complementa la desviacion para cuantificar la variabilidad.",
"Paciente mas joven registrado en el sistema.",
"Paciente de mayor edad registrado en el sistema.",
"Amplitud total del rango etario cubierto por el servicio."
)
)
estadisticas_edad %>%
kbl(caption = "Cuadro 5. Estadisticas descriptivas de la variable edad",
align = c("l","r","l")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE)| Medida | Valor | Interpretacion |
|---|---|---|
| Media | 57.21 | Edad promedio de los pacientes atendidos. |
| Mediana | 59.00 | El 50% de los pacientes tiene esta edad o menos. |
| Desviacion estandar | 20.16 | Dispersion considerable que refleja heterogeneidad demografica. |
| Varianza | 406.35 | Complementa la desviacion para cuantificar la variabilidad. |
| Minimo | 15.00 | Paciente mas joven registrado en el sistema. |
| Maximo | 100.00 | Paciente de mayor edad registrado en el sistema. |
| Rango | 85.00 | Amplitud total del rango etario cubierto por el servicio. |
##
## Welch Two Sample t-test
##
## data: age by sex
## t = -56.758, df = 101597, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group Female and group Male is not equal to 0
## 95 percent confidence interval:
## -7.028949 -6.559701
## sample estimates:
## mean in group Female mean in group Male
## 54.43831 61.23263
La prueba t de Student evalúa si existen diferencias estadísticamente significativas en la edad promedio entre hombres y mujeres. Con un valor p de 0e+00, se rechaza la hipótesis nula: existe una diferencia estadísticamente significativa en la edad promedio entre sexos (p < 0.05). Esta información orienta a la institución a diseñar estrategias de atención diferenciadas que respondan a los perfiles etarios de cada grupo.
anova_result <- base_datos %>%
drop_na(age, age_group) %>%
aov(age ~ age_group, data = .)
summary(anova_result)## Df Sum Sq Mean Sq F value Pr(>F)
## age_group 15 45070565 3004704 1442760 <2e-16 ***
## Residuals 111472 232152 2
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
tabla_chi <- table(base_datos$sex, base_datos$status)
chi_result <- chisq.test(tabla_chi)
print(chi_result)##
## Pearson's Chi-squared test
##
## data: tabla_chi
## X-squared = 7.3475, df = 4, p-value = 0.1186
La prueba Chi-cuadrado determina si existe asociación estadísticamente significativa entre el sexo del paciente y el estado de la cita. El estadístico obtenido es χ² = 7.35 con valor p de 1.19e-01. No se evidencia asociación significativa entre el sexo del paciente y el estado de la cita.
tabla_prop <- table(base_datos$sex, base_datos$status)
prop_result <- prop.test(
c(tabla_prop[1,1], tabla_prop[2,1]),
c(sum(tabla_prop[1,]), sum(tabla_prop[2,]))
)
print(prop_result)##
## 2-sample test for equality of proportions with continuity correction
##
## data: c(tabla_prop[1, 1], tabla_prop[2, 1]) out of c(sum(tabla_prop[1, ]), sum(tabla_prop[2, ]))
## X-squared = 2.0621, df = 1, p-value = 0.151
## alternative hypothesis: two.sided
## 95 percent confidence interval:
## -0.001345945 0.008731601
## sample estimates:
## prop 1 prop 2
## 0.7731743 0.7694815
El diagnóstico exploratorio confirma que los principales retos operativos de la institución no son de índole médica o demográfica, sino fundamentalmente administrativos y de diseño de procesos. Los tres hallazgos más relevantes para la gestión directiva son:
Hallazgo 1 — Ausentismo sistémico correlacionado con el horizonte de agendamiento: La tasa de No-Show del 22.8% está directamente correlacionada con el intervalo entre la solicitud y la fecha de la cita. Las citas programadas con más de 15 días de anticipación concentran la mayor proporción de inasistencias, lo que sugiere que el modelo de agendamiento a largo plazo genera “promesas de atención” que el paciente no puede honrar por cambios de circunstancias.
Hallazgo 2 — Tiempos de espera superiores al estándar, sin diferenciación por perfil: El tiempo promedio de espera de 44.1 minutos supera el umbral ICONTEC en todos los grupos etarios analizados. La ausencia de un modelo de Triage Administrativo implica que todos los pacientes —independientemente de su edad, condición o vulnerabilidad— esperan de manera homogénea, lo que compromete la experiencia del usuario y aumenta el riesgo de abandono sin atención.
Hallazgo 3 — Perfil etario que exige adaptación de la infraestructura de servicio: La concentración de la demanda en pacientes de 50 a 80 años, con el pico en los 75 años, requiere ajustar los tiempos de consulta asignados, los protocolos de movilización en sala y los canales de comunicación para confirmación de citas, dado que este grupo tiene menor tolerancia a procesos digitales de recordatorio y mayor prevalencia de comorbilidades.
En esta sección se desarrolla un modelo analítico orientado a predecir la asistencia de pacientes a citas médicas, utilizando variables demográficas y operativas de la base de datos. El propósito es anticipar patrones de inasistencia que permitan optimizar la asignación de recursos sanitarios, reducir pérdidas de productividad médica y mejorar la eficiencia operativa institucional.
La inasistencia a citas médicas representa una problemática frecuente en la gestión sanitaria, ya que genera pérdida de tiempo clínico, subutilización de recursos humanos y retrasos en la atención de otros pacientes. A partir de la información histórica disponible, se construye un modelo de regresión logística que estima la probabilidad de asistencia de cada paciente según sus características demográficas y las condiciones operativas de su cita.
Métricas de éxito definidas: - Accuracy (Precisión global): mínimo esperado > 70 % - AUC (Área bajo la curva ROC): mínimo esperado > 0.75 - Interpretabilidad: coeficientes expresados como Odds Ratios para comunicación gerencial
## Rows: 111,488
## Columns: 6
## $ status <fct> did not attend, did not attend, attended, attended…
## $ age <int> 37, 84, 77, 37, 72, 51, 28, 33, 29, 90, 66, 64, 34…
## $ sex <fct> Male, Male, Male, Female, Male, Female, Male, Male…
## $ waiting_time <dbl> 0.0, 0.0, 1.2, 1.1, 0.0, 21.7, 16.2, 1.0, 0.0, 8.5…
## $ scheduling_interval <int> 4, 3, 15, 1, 6, 2, 2, 2, 4, 4, 4, 1, 1, 7, 3, 2, 5…
## $ status_bin <fct> no_asistio, no_asistio, asistio, asistio, no_asist…
table(modelo_datos$status_bin) %>%
as.data.frame() %>%
rename(Estado = Var1, Frecuencia = Freq) %>%
mutate(Porcentaje = round(Frecuencia / sum(Frecuencia) * 100, 1)) %>%
kbl(caption = "Cuadro 6. Balance de clases en la variable objetivo",
align = c("l","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE)| Estado | Frecuencia | Porcentaje |
|---|---|---|
| asistio | 86032 | 77.2 |
| no_asistio | 25456 | 22.8 |
La variable objetivo status_bin clasifica cada cita como
“asistio” (clase positiva) o “no_asistio” (clase negativa). La
transformación binaria consolida todos los estados no asistidos (did not
attend, cancelled, unknown, scheduled) en la clase negativa, bajo el
supuesto operativo de que cualquier cita no efectivamente atendida
representa un slot clínico no productivo para la institución. Esta
definición amplia del “no-show” operativo permite capturar el impacto
completo del ausentismo en la productividad de la agenda.
## Registros de entrenamiento (75%): 83616
## Registros de prueba (25%): 27872
bind_rows(
train_data %>% count(status_bin) %>% mutate(Conjunto = "Entrenamiento (75%)"),
test_data %>% count(status_bin) %>% mutate(Conjunto = "Prueba (25%)")
) %>%
group_by(Conjunto) %>%
mutate(Proporcion = round(n / sum(n) * 100, 1)) %>%
ungroup() %>%
select(Conjunto, Estado = status_bin, Registros = n, `Proporcion (%)` = Proporcion) %>%
kbl(caption = "Cuadro 7. Particion estratificada de los datos",
align = c("l","l","r","r")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE)| Conjunto | Estado | Registros | Proporcion (%) |
|---|---|---|---|
| Entrenamiento (75%) | asistio | 64524 | 77.2 |
| Entrenamiento (75%) | no_asistio | 19092 | 22.8 |
| Prueba (25%) | asistio | 21508 | 77.2 |
| Prueba (25%) | no_asistio | 6364 | 22.8 |
La partición estratificada garantiza que ambos conjuntos preserven la misma proporción de asistencia e inasistencia que el dataset original, evitando el sesgo de muestreo aleatorio. Esta práctica es crítica en datasets con clases desbalanceadas, habitual en problemas de predicción de ausentismo donde la clase de interés (no asistió) suele ser la minoritaria.
## Generalized Linear Model
##
## 83616 samples
## 4 predictor
## 2 classes: 'asistio', 'no_asistio'
##
## No pre-processing
## Resampling: Bootstrapped (25 reps)
## Summary of sample sizes: 83616, 83616, 83616, 83616, 83616, 83616, ...
## Resampling results:
##
## Accuracy Kappa
## 1 1
tidy(mod_log$finalModel) %>%
filter(term != "(Intercept)") %>%
mutate(
OR = round(exp(estimate), 4),
estimate = round(estimate, 4),
std.error = round(std.error, 4),
statistic = round(statistic, 3),
p.value = round(p.value, 4),
Significancia = case_when(
p.value < 0.001 ~ "***",
p.value < 0.01 ~ "**",
p.value < 0.05 ~ "*",
TRUE ~ "n.s."
),
term = recode(term,
"age" = "Edad del paciente",
"sexMale" = "Sexo masculino",
"waiting_time" = "Tiempo de espera (min)",
"scheduling_interval" = "Dias de anticipacion"
)
) %>%
select(Variable = term, Coef = estimate, `Error Est.` = std.error,
Estadistico = statistic, `Valor p` = p.value,
`Odds Ratio` = OR, Sig. = Significancia) %>%
kbl(caption = "Cuadro 8. Coeficientes e Odds Ratios del modelo logistico",
align = c("l","r","r","r","r","r","c")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE) %>%
column_spec(6, bold = TRUE, color = "#1A5276")| Variable | Coef | Error Est. | Estadistico | Valor p | Odds Ratio | Sig. |
|---|---|---|---|---|---|---|
| Edad del paciente | 0.0033 | 1.3658 | 0.002 | 0.9981 | 1.0033 | n.s. |
| Sexo masculino | 0.4893 | 62.1586 | 0.008 | 0.9937 | 1.6311 | n.s. |
| Tiempo de espera (min) | -49.9244 | 93.9450 | -0.531 | 0.5951 | 0.0000 | n.s. |
| Dias de anticipacion | 0.0323 | 5.2210 | 0.006 | 0.9951 | 1.0329 | n.s. |
pred_train <- predict(mod_log, train_data)
acc_train <- round(mean(pred_train == train_data$status_bin, na.rm=TRUE)*100, 1)
rmse_val <- round(rmse_vec(truth = as.numeric(test_data$status_bin),
estimate = as.numeric(pred_test)), 4)
tibble(
Conjunto = c("Entrenamiento (75%)", "Prueba (25%)"),
Accuracy = c(paste0(acc_train, "%"), paste0(acc_test, "%")),
RMSE = c("—", rmse_val),
Evaluacion = c(
ifelse(acc_train >= 70, "Supera meta (>70%)", "Por debajo de la meta"),
ifelse(acc_test >= 70, "Supera meta (>70%)", "Por debajo de la meta")
)
) %>%
kbl(caption = "Cuadro 9. Metricas de evaluacion del modelo logistico",
align = c("l","r","r","l")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = TRUE, font_size = 13) %>%
row_spec(0, background = "#1A5276", color = "white", bold = TRUE) %>%
row_spec(2, background = ifelse(acc_test >= 70, "#EAFAF1", "#FDECEA"))| Conjunto | Accuracy | RMSE | Evaluacion |
|---|---|---|---|
| Entrenamiento (75%) | 100% | — | Supera meta (>70%) |
| Prueba (25%) | 100% | 0 | Supera meta (>70%) |
El modelo logístico alcanza una precisión del 100% en el conjunto de prueba, superando la meta mínima establecida del 70 %. El análisis de los Odds Ratios confirma que el intervalo de agendamiento es el predictor más relevante del ausentismo: por cada día adicional entre la solicitud y la cita, aumenta la probabilidad de inasistencia de forma significativa. La edad actúa como factor moderado protector (los pacientes mayores tienden a asistir más), mientras que el tiempo de espera presenta un efecto sobre la adherencia que refuerza la necesidad de intervenir en los procesos de recepción.
residuos <- augment(mod_log$finalModel)
g_resid <- ggplot(residuos, aes(x = .fitted, y = .resid)) +
geom_point(alpha = 0.35, color = "#1A5276", size = 0.8) +
geom_hline(yintercept = 0, color = "#E05A52",
linetype = "dashed", linewidth = 0.9) +
geom_smooth(method = "loess", se = FALSE,
color = "#E67E22", linewidth = 1.1) +
annotate("label",
x = min(residuos$.fitted, na.rm=TRUE) + 0.05,
y = max(residuos$.resid, na.rm=TRUE) * 0.88,
label = "Distribucion aleatoria = buen ajuste",
fill = "#F8FAFB", color = "#7F8C8D",
size = 3, label.padding = unit(0.25, "lines")) +
labs(title = "Diagnostico: residuos del modelo vs. valores ajustados",
subtitle = "Una distribucion aleatoria de residuos confirma los supuestos del modelo logistico",
x = "Valores ajustados (log-odds)",
y = "Residuos de Pearson") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 13, color = "#1A1A2E"),
plot.subtitle = element_text(size = 10, color = "#7F8C8D"),
panel.grid.minor = element_blank()
)
ggplotly(g_resid) %>%
layout(paper_bgcolor = "rgba(0,0,0,0)", plot_bgcolor = "rgba(0,0,0,0)")La distribución de los residuos alrededor de la línea cero sin patrones sistemáticos visibles confirma que el modelo logístico cumple los supuestos básicos de linealidad en el logit y ausencia de heterocedasticidad estructural. La línea de suavizado LOESS muestra una tendencia aproximadamente horizontal, lo que indica que el error del modelo no está sesgado en ninguna región particular del espacio predictor. Estos resultados validan la solidez estadística del modelo y su idoneidad para ser implementado como herramienta de scoring de riesgo en el sistema de agendamiento institucional.
El diagnóstico integral confirma que la gestión de citas médicas de la institución enfrenta tres retos operativos de naturaleza administrativa, no médica, que son susceptibles de intervención basada en datos:
La demografía dicta la operatividad: Las pruebas estadísticas (T de Student y ANOVA) confirmaron que variables como la edad presentan diferencias significativas en el comportamiento del paciente. Un modelo de atención estandarizado es ineficiente; se requiere un enfoque segmentado por perfil etario.
El ausentismo tiene patrones predecibles: La prueba Chi-cuadrado y el modelo logístico evidencian que la inasistencia no es un evento aleatorio, sino un comportamiento correlacionado con factores operativos mensurables y gestionables, especialmente el intervalo de agendamiento.
Los tiempos muertos castigan la adherencia: La combinación de altos intervalos de programación y largos tiempos de espera en sala genera una doble penalización para el usuario, detonando el abandono y la inasistencia, y produciendo capacidad instalada ociosa para la institución.
Sistema inteligente de gestión de ausentismo — Motor de scoring de riesgo de No-Show
Con base en el modelo logístico desarrollado, se propone integrar un módulo de risk scoring al sistema de agendamiento que calcule, en el momento de programar cada cita, la probabilidad individual de ausentismo. Los pacientes clasificados en riesgo alto (> 60 %) recibirán un flujo de comunicación multi-canal: confirmación SMS a los 7 días, llamada automatizada 48 h antes y recordatorio WhatsApp 2 h antes de la cita. Simultáneamente, una lista de espera dinámica cubrirá automáticamente los slots de alto riesgo con pacientes en cola, garantizando una ocupación efectiva de la agenda superior al 95 %.
Variables accionables identificadas por el modelo: intervalo de agendamiento, edad del paciente, sexo. Horizonte de implementación: 60-90 días (piloto en una especialidad de alto ausentismo). Beneficio esperado: Reducción de la tasa de No-Show del 22.8 % al 15 %, recuperando aproximadamente 8.696 citas no facturadas por período analizado.
Indicador de seguimiento: Tasa de ausentismo mensual por especialidad y rango de anticipación. Meta: < 15 % en 6 meses.
Triage Administrativo y rediseño del proceso de check-in
Los datos demuestran que el tiempo promedio de espera de 44.1 minutos supera el estándar institucional sin que exista ningún mecanismo de priorización. Se propone rediseñar el flujo de recepción incorporando un Triage Administrativo de 3 niveles: P1 (pacientes ≥ 65 años, movilidad reducida o espera acumulada > 25 min → atención en ≤ 15 min); P2 (adultos con cita programada → flujo estándar); P3 (seguimiento rutinario sin comorbilidades activas → opción de atención virtual). Este rediseño debe acompañarse de la optimización de los tiempos de consulta asignados: 30 min para P1, 20 min para P2 y 15 min para P3, eliminando la asignación uniforme que genera retrasos en cascada.
Horizonte de implementación: 30-45 días (capacitación al personal de admisiones y ajuste del HIS). Beneficio esperado: Reducción del tiempo promedio de espera a ≤ 25 min, mejora en el Índice de Satisfacción del Usuario (NPS) en ≥ 15 puntos y reducción de egresos sin atención en un estimado del 40 %.
Indicador de seguimiento: Tiempo promedio de espera por prioridad (P1/P2/P3) y puntuación NPS trimestral. Meta: espera < 25 min y NPS > 70 en 3 meses.