1 Introducción y preparación de los datos

Este informe continúa el análisis exploratorio (EDA) de la semana 1 (ver informe), con la misma base Medical Appointment Scheduling System de Kaggle (appointments.csv, una fila = una cita). Ahora se pasa de describir a medir la incertidumbre: se simulan escenarios con distribuciones de probabilidad y se aplican pruebas estadísticas para saber si las diferencias observadas entre grupos son reales o pueden deberse al azar. Se tomó como referencia el análisis de Explorando medicaldata en R (RPubs).

El código de cada paso está oculto para facilitar la lectura; se despliega con el botón Code a la derecha de cada bloque. Los gráficos de simulación son interactivos (pasar el mouse, zoom, doble clic para restablecer).

# Instala ggpubr solo si no está instalado
if (!requireNamespace("ggpubr", quietly = TRUE)) install.packages("ggpubr", repos = "https://cloud.r-project.org")

library(tidyverse)   # manejo de datos y ggplot2
library(janitor)     # limpieza de nombres
library(plotly)      # gráficos interactivos
library(knitr)       # tablas
library(ggpubr)      # gráficos comparativos con pruebas estadísticas

set.seed(2026)       # semilla: hace que las simulaciones den siempre el mismo resultado
citas <- read_csv("appointments.csv") %>%
  clean_names() %>%
  mutate(
    across(where(hms::is_hms), as.character),
    status     = factor(status),
    sex        = factor(sex, labels = c("Mujer", "Hombre")),
    hora       = as.integer(substr(appointment_time, 1, 2)),          # hora programada
    jornada    = factor(ifelse(hora < 12, "Mañana", "Tarde")),
    anio       = as.integer(substr(as.character(appointment_date), 1, 4)),
    periodo    = factor(ifelse(anio <= 2019, "2015-2019", "2020-2024")),
    banda_edad = cut(age, breaks = c(0, 29, 49, 69, Inf),
                     labels = c("15-29", "30-49", "50-69", "70+"))
  )

atendidas <- citas %>% filter(status == "attended")      # citas con tiempos registrados
n_total   <- nrow(citas)

Se trabaja con 111.488 citas. Se crearon variables nuevas para comparar grupos: jornada (mañana/tarde), periodo (2015-2019 / 2020-2024) y banda de edad (15-29, 30-49, 50-69, 70+).

2 Probabilidad empírica: sample() y table()

2.1 Espacio muestral y eventos

El espacio muestral es el conjunto de resultados posibles de una cita: attended, cancelled, did not attend, scheduled, unknown. Un evento es un subconjunto de esos resultados; para la gestión interesa el evento A = “cita perdida” (cancelada o no asistida), porque es capacidad instalada que no produce atención.

# Probabilidades reales (frecuencia relativa en toda la base)
p_real <- prop.table(table(citas$status))

# 1) sample(): se eligen 500 citas al azar de la base real (muestreo aleatorio)
muestra <- citas[sample(1:n_total, size = 500), ]
p_muestra <- prop.table(table(muestra$status))

# 2) sample(): se simula una agenda de 500 citas usando las probabilidades reales
agenda_sim <- sample(names(p_real), size = 500, replace = TRUE, prob = p_real)
p_sim <- prop.table(table(factor(agenda_sim, levels = names(p_real))))

tabla_prob <- tibble(Estado = names(p_real),
                     `P. real (base completa)` = round(as.numeric(p_real), 3),
                     `P. empírica (muestra de 500)` = round(as.numeric(p_muestra[names(p_real)]), 3),
                     `P. empírica (agenda simulada)` = round(as.numeric(p_sim), 3))
kable(tabla_prob)
Estado P. real (base completa) P. empírica (muestra de 500) P. empírica (agenda simulada)
attended 0.772 0.772 0.786
cancelled 0.164 0.162 0.142
did not attend 0.059 0.062 0.070
scheduled 0.001 0.002 0.002
unknown 0.004 0.002 0.000
p_perdida <- sum(p_real[c("cancelled", "did not attend")])

La probabilidad real del evento A es P(A) = 0.223: aproximadamente 22 de cada 100 citas se pierden. Las dos muestras de 500 dan probabilidades empíricas cercanas a las reales, pero no idénticas: esa diferencia es la variabilidad muestral.

# ¿Cuántas citas hacen falta para que la probabilidad empírica se estabilice?
sim_larga <- sample(c("Perdida", "Cumplida"), size = 5000, replace = TRUE,
                    prob = c(p_perdida, 1 - p_perdida))
lgn <- tibble(n = 1:5000, prop = cumsum(sim_larga == "Perdida") / (1:5000))

g_lgn <- ggplot(lgn, aes(n, prop)) +
  geom_line(color = "#2C7FB8") +
  geom_hline(yintercept = p_perdida, linetype = "dashed", color = "red") +
  labs(title = "Ley de los grandes números: proporción acumulada de citas perdidas",
       x = "Número de citas simuladas", y = "Proporción de citas perdidas") +
  theme_minimal()
ggplotly(g_lgn)

Interpretación: con pocas citas la proporción salta mucho; a partir de unas mil se estabiliza alrededor de la línea roja (valor real). Para la gestión, un indicador calculado sobre pocos días o pocos pacientes (por ejemplo, el % de inasistencia de una semana) puede ser engañoso; conviene medirlo con periodos suficientemente largos.

3 Distribuciones discretas: runif(), rbinom() y dpois()

# Citas por día y número de inasistencias por día (datos reales)
por_dia <- citas %>%
  group_by(appointment_date) %>%
  summarise(citas = n(), no_asiste = sum(status == "did not attend"))

n_dia    <- round(mean(por_dia$citas))                 # citas promedio por día
p_dna    <- mean(citas$status == "did not attend")      # probabilidad de no asistir
lambda   <- mean(por_dia$no_asiste)                     # inasistencias promedio por día

Cada día se programan en promedio 43 citas, la probabilidad de que un paciente no asista es p = 0.059 y en promedio hay λ = 2.54 inasistencias por día.

3.1 runif() y rbinom(): inasistencias diarias en tres escenarios

Se simulan 1.000 días de agenda. Con runif() cada paciente recibe un número aleatorio entre 0 y 1; si es menor que p, se cuenta como inasistencia (ensayo de Bernoulli). Con rbinom() se obtiene directamente el número de inasistencias de un día de 43 citas. Se comparan tres escenarios de gestión.

escenarios <- c("Con recordatorios (p = 3%)" = 0.03,
                "Situación actual"           = p_dna,
                "Sin control (p = 12%)"      = 0.12)

# runif(): un día simulado paciente por paciente en la situación actual
dia_runif <- runif(n_dia) < p_dna
inasist_runif <- sum(dia_runif)

# rbinom(): 1.000 días por escenario
sim_binom <- map_dfr(names(escenarios), function(e)
  tibble(escenario = e, inasistencias = rbinom(1000, size = n_dia, prob = escenarios[e])))
sim_binom$escenario <- factor(sim_binom$escenario, levels = names(escenarios))

sim_binom %>% group_by(escenario) %>%
  summarise(Promedio = round(mean(inasistencias), 2),
            `P(5 o más por día)` = round(mean(inasistencias >= 5), 3),
            Máximo = max(inasistencias)) %>% kable()
escenario Promedio P(5 o más por día) Máximo
Con recordatorios (p = 3%) 1.37 0.011 6
Situación actual 2.63 0.119 8
Sin control (p = 12%) 5.15 0.590 13
g_bin <- ggplot(sim_binom, aes(inasistencias, fill = escenario)) +
  geom_bar(position = "dodge") +
  labs(title = "Inasistencias por día en 1.000 días simulados (rbinom)",
       x = "Inasistencias en el día", y = "Número de días", fill = "Escenario") +
  scale_fill_brewer(palette = "Set2") + theme_minimal()
ggplotly(g_bin)

Interpretación: el día simulado con runif() tuvo 3 inasistencias. Con rbinom() se ve todo el rango posible: en la situación actual lo usual son 2 a 4 inasistencias diarias, pero hay días con bastantes más. Bajar la probabilidad al 3 % con recordatorios desplaza la distribución hacia 0-2, mientras que un escenario sin control (12 %) duplica las pérdidas. La simulación permite estimar el beneficio de una intervención antes de implementarla.

3.2 dpois(): probabilidad exacta del número de inasistencias

k <- 0:10
pois <- tibble(k = k,
               teorica = dpois(k, lambda),                                      # modelo de Poisson
               real    = as.numeric(table(factor(pmin(por_dia$no_asiste, 10), levels = k))) /
                         nrow(por_dia))                                         # frecuencia real con table()

g_pois <- pois %>% pivot_longer(-k, names_to = "fuente", values_to = "prob") %>%
  mutate(fuente = recode(fuente, teorica = "Poisson (dpois)", real = "Datos reales")) %>%
  ggplot(aes(k, prob, fill = fuente)) +
  geom_col(position = "dodge") +
  labs(title = "Inasistencias por día: modelo de Poisson vs. datos reales",
       x = "Inasistencias en el día", y = "Probabilidad", fill = "") +
  scale_x_continuous(breaks = k) + theme_minimal()
ggplotly(g_pois)
p_cero  <- dpois(0, lambda)
p_5omas <- 1 - sum(dpois(0:4, lambda))

Interpretación: el modelo de Poisson con λ = 2.54 se ajusta muy bien a los datos reales. Según el modelo, la probabilidad de un día sin ninguna inasistencia es apenas 7.9 %, y la de tener 5 o más es 11.4 %. Con esto se puede planear un sobreagendamiento controlado: agendar 2 cupos extra por día cubriría la pérdida típica sin generar congestión casi nunca.

4 Distribuciones continuas: rnorm(), rexp() y runif()

Se compara cada variable real con un modelo teórico y se usan las funciones de densidad (d...) y de distribución acumulada (p...) para calcular probabilidades útiles.

mu_dur <- mean(atendidas$appointment_duration); sd_dur <- sd(atendidas$appointment_duration)
mu_esp <- mean(atendidas$waiting_time)

n_sim <- 5000
sim_cont <- tibble(
  `Duración - normal (rnorm)`        = rnorm(n_sim, mean = mu_dur, sd = sd_dur),
  `Espera - exponencial (rexp)`      = rexp(n_sim, rate = 1 / mu_esp),
  `Anticipación - uniforme (runif)`  = runif(n_sim, min = 1, max = 30)
) %>% pivot_longer(everything(), names_to = "modelo", values_to = "valor")

g_cont <- ggplot(sim_cont, aes(valor)) +
  geom_histogram(aes(y = after_stat(density)), bins = 40, fill = "#41AB5D", color = "white") +
  facet_wrap(~ modelo, scales = "free") +
  labs(title = "Datos simulados con distribución normal, exponencial y uniforme",
       x = "Minutos / días", y = "Densidad") + theme_minimal()
ggplotly(g_cont)
# Datos reales vs. curva teórica (dnorm y dexp)
g_dnorm <- ggplot(atendidas, aes(appointment_duration)) +
  geom_histogram(aes(y = after_stat(density)), binwidth = 2, fill = "grey80", color = "white") +
  stat_function(fun = dnorm, args = list(mean = mu_dur, sd = sd_dur), color = "#2C7FB8", linewidth = 1) +
  labs(title = "Duración real vs. curva normal (dnorm)", x = "Minutos", y = "Densidad") + theme_minimal()

g_dexp <- ggplot(atendidas, aes(waiting_time)) +
  geom_histogram(aes(y = after_stat(density)), binwidth = 5, fill = "grey80", color = "white") +
  stat_function(fun = dexp, args = list(rate = 1 / mu_esp), color = "red", linewidth = 1) +
  labs(title = "Espera real vs. curva exponencial (dexp)", x = "Minutos", y = "Densidad") + theme_minimal()

subplot(ggplotly(g_dnorm), ggplotly(g_dexp), nrows = 1, titleX = TRUE, titleY = TRUE)
probs <- tibble(
  Pregunta = c("P(consulta dure más de 30 min)", "P(espera más de 30 min)", "P(espera más de 60 min)"),
  `Modelo teórico` = c(1 - pnorm(30, mu_dur, sd_dur), 1 - pexp(30, 1/mu_esp), 1 - pexp(60, 1/mu_esp)),
  `Datos reales`   = c(mean(atendidas$appointment_duration > 30),
                       mean(atendidas$waiting_time > 30), mean(atendidas$waiting_time > 60))
) %>% mutate(across(where(is.numeric), ~ round(.x, 3)))
kable(probs)
Pregunta Modelo teórico Datos reales
P(consulta dure más de 30 min) 0.129 0.148
P(espera más de 30 min) 0.506 0.538
P(espera más de 60 min) 0.256 0.278

Interpretación:

  • Duración (normal): media de 17.5 min y desviación de 11.1 min. La curva normal describe razonablemente el centro, aunque los datos reales tienen una cola hacia la derecha (algunas consultas largas). Con pnorm() se estima qué fracción de consultas excede una franja de 30 minutos.
  • Espera (exponencial): la forma real (muchas esperas cortas y pocas muy largas) se parece a la exponencial con media 44.1 min. Con pexp() el modelo estima que alrededor de la mitad de los pacientes espera más de 30 minutos, igual que los datos reales: la espera es el principal problema de calidad.
  • Anticipación (uniforme): runif() simula un escenario donde la cita se pide con igual probabilidad entre 1 y 30 días. En la realidad la anticipación se concentra en pocos días (mediana 5 días), así que la demanda no es uniforme: la mayoría pide cita para la semana siguiente.

5 Indicadores clave: estadística descriptiva

resumen_ind <- function(x) c(Media = mean(x), Mediana = median(x), `Desv. estándar` = sd(x),
                             Varianza = var(x), Mínimo = range(x)[1], Máximo = range(x)[2])

desc <- rbind(`Tiempo de espera (min)`     = resumen_ind(atendidas$waiting_time),
              `Duración consulta (min)`    = resumen_ind(atendidas$appointment_duration),
              `Anticipación (días)`        = resumen_ind(citas$scheduling_interval),
              `Citas por día`              = resumen_ind(por_dia$citas))
kable(round(desc, 2))
Media Mediana Desv. estándar Varianza Mínimo Máximo
Tiempo de espera (min) 44.09 33.5 40.79 1663.73 0.6 297.3
Duración consulta (min) 17.48 15.8 11.06 122.42 0.0 58.7
Anticipación (días) 7.19 5.0 6.15 37.78 1.0 30.0
Citas por día 42.81 43.0 3.28 10.78 1.0 52.0
summary(atendidas %>% select(waiting_time, appointment_duration, scheduling_interval, age))
##   waiting_time    appointment_duration scheduling_interval      age        
##  Min.   :  0.60   Min.   : 0.00        Min.   : 1.000      Min.   : 15.00  
##  1st Qu.: 12.60   1st Qu.: 8.60        1st Qu.: 2.000      1st Qu.: 40.00  
##  Median : 33.50   Median :15.80        Median : 5.000      Median : 59.00  
##  Mean   : 44.09   Mean   :17.48        Mean   : 7.198      Mean   : 57.23  
##  3rd Qu.: 64.60   3rd Qu.:24.70        3rd Qu.:10.000      3rd Qu.: 74.00  
##  Max.   :297.30   Max.   :58.70        Max.   :30.000      Max.   :100.00

Interpretación: en el tiempo de espera la media (44.1 min) es mayor que la mediana (33.5 min) y la desviación estándar es casi igual a la media: la variable es muy asimétrica y dispersa (hay pacientes que esperan horas). La duración de la consulta es mucho más estable. Las citas por día tienen poca varianza, lo que confirma una demanda estable y predecible (hallazgo de la semana 1).

6 Pruebas de hipótesis

En todas las pruebas se usa un nivel de significancia de α = 0,05: si el valor p es menor que 0,05, la diferencia se considera estadísticamente significativa (es poco probable que se deba al azar).

6.1 t.test(): comparación de medias e intervalos de confianza

# a) Intervalo de confianza y comparación con una meta de 30 minutos
t_meta <- t.test(atendidas$waiting_time, mu = 30)
ic_esp <- t.test(atendidas$waiting_time)$conf.int

# b) Espera en la mañana vs. en la tarde
t_jornada <- t.test(waiting_time ~ jornada, data = atendidas)

# c) Espera por periodo: 2015-2019 vs. 2020-2024
t_periodo <- t.test(waiting_time ~ periodo, data = atendidas)

# d) Espera de mujeres vs. hombres
t_sexo <- t.test(waiting_time ~ sex, data = atendidas)

res_t <- function(t, nombre) tibble(Comparación = nombre,
  `Media grupo 1` = as.character(round(t$estimate[1], 1)),
  `Media grupo 2` = ifelse(length(t$estimate) > 1, as.character(round(t$estimate[2], 1)), "—"),
  `IC 95% (dif. o media)` = paste0("[", round(t$conf.int[1], 1), " ; ", round(t$conf.int[2], 1), "]"),
  `valor p` = fp(t$p.value))

bind_rows(res_t(t_meta, "Espera media vs. meta de 30 min"),
          res_t(t_jornada, "Mañana vs. Tarde"),
          res_t(t_periodo, "2015-2019 vs. 2020-2024"),
          res_t(t_sexo, "Mujer vs. Hombre")) %>% kable()
Comparación Media grupo 1 Media grupo 2 IC 95% (dif. o media) valor p
Espera media vs. meta de 30 min 44.1 — [43.8 ; 44.4] <0.001
Mañana vs. Tarde 27.2 55.3 [-28.6 ; -27.7] <0.001
2015-2019 vs. 2020-2024 43.2 45 [-2.3 ; -1.2] <0.001
Mujer vs. Hombre 44.1 44.1 [-0.6 ; 0.6] 0.992

Interpretación:

  • Con 95 % de confianza, la espera media real está entre 43.8 y 44.4 minutos, claramente por encima de la meta de 30 minutos (p <0.001).
  • Mañana vs. tarde: en la tarde se espera en promedio 55.3 min, frente a 27.2 en la mañana (p <0.001). Es la diferencia más importante del informe: los retrasos se acumulan a lo largo del día.
  • Periodo: la diferencia es significativa (p <0.001), pero de solo 1.8 minutos. Con muestras tan grandes, diferencias pequeñas salen significativas; hay que distinguir significancia estadística de relevancia práctica. La espera empeoró levemente en el tiempo.
  • Sexo: no hay diferencia (p = 0.992): la atención es equitativa entre mujeres y hombres.

6.2 aov(): análisis de varianza (ANOVA) entre varios grupos

anova_edad <- aov(waiting_time ~ banda_edad, data = atendidas)
anova_dur  <- aov(appointment_duration ~ banda_edad, data = atendidas)
anova_hora <- aov(waiting_time ~ factor(hora), data = atendidas)

summary(anova_edad)
##                Df    Sum Sq Mean Sq F value Pr(>F)
## banda_edad      3      3124    1041   0.626  0.598
## Residuals   86028 143128841    1664
summary(anova_dur)
##                Df   Sum Sq Mean Sq F value Pr(>F)
## banda_edad      3      407   135.6   1.107  0.345
## Residuals   86028 10531256   122.4
summary(anova_hora)
##                 Df    Sum Sq Mean Sq F value Pr(>F)    
## factor(hora)     9  21722581 2413620    1710 <2e-16 ***
## Residuals    86022 121409384    1411                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
p_aov <- function(m) summary(m)[[1]][["Pr(>F)"]][1]

Interpretación:

  • Espera según banda de edad: p = 0.598; no hay diferencias, todos los grupos esperan lo mismo.
  • Duración de la consulta según banda de edad: p = 0.345; confirma lo visto en el boxplot de la semana 1: la edad no cambia el consumo de tiempo médico, por lo que una franja de igual duración para todos es adecuada.
  • Espera según la hora de la cita: p <0.001; hay diferencias muy significativas entre horas (ver gráfico de líneas en la sección 7). El problema de espera no depende del paciente, sino de la organización de la agenda.

6.3 chisq.test(): asociación entre variables categóricas

perdidas <- citas %>% filter(status %in% c("attended", "cancelled", "did not attend")) %>%
  mutate(status = droplevels(status))

tab_edad    <- table(perdidas$banda_edad, perdidas$status)
tab_jornada <- table(perdidas$jornada, perdidas$status)
chi_edad    <- chisq.test(tab_edad)
chi_jornada <- chisq.test(tab_jornada)

kable(round(prop.table(tab_edad, 1) * 100, 1), caption = "% de cada estado por banda de edad")
% de cada estado por banda de edad
attended cancelled did not attend
15-29 77.3 16.7 5.9
30-49 77.4 16.6 5.9
50-69 77.7 16.3 6.0
70+ 77.7 16.4 5.9
kable(round(prop.table(tab_jornada, 1) * 100, 1), caption = "% de cada estado por jornada")
% de cada estado por jornada
attended cancelled did not attend
Mañana 77.4 16.6 6
Tarde 77.7 16.4 6

Interpretación: ni la banda de edad (χ² = 2.22, p = 0.898) ni la jornada (χ² = 1.23, p = 0.54) están asociadas con que la cita se cumpla, se cancele o se pierda: los porcentajes son casi idénticos en todas las filas. Para la gestión, las campañas de recordatorio deben dirigirse a todos los pacientes, no a un grupo de edad u horario específico.

6.4 prop.test(): comparación de proporciones

# a) Proporción de inasistencia: mujeres vs. hombres
inas <- citas %>% filter(status %in% c("attended", "did not attend")) %>%
  group_by(sex) %>% summarise(x = sum(status == "did not attend"), n = n())
pt_sexo <- prop.test(inas$x, inas$n)

# b) Proporción de cancelación: 2015-2019 vs. 2020-2024
canc <- citas %>% group_by(periodo) %>% summarise(x = sum(status == "cancelled"), n = n())
pt_periodo <- prop.test(canc$x, canc$n)

tibble(Comparación = c("Inasistencia: Mujer vs. Hombre", "Cancelación: 2015-2019 vs. 2020-2024"),
       `Prop. grupo 1` = round(c(pt_sexo$estimate[1], pt_periodo$estimate[1]), 4),
       `Prop. grupo 2` = round(c(pt_sexo$estimate[2], pt_periodo$estimate[2]), 4),
       `valor p` = c(fp(pt_sexo$p.value), fp(pt_periodo$p.value))) %>% kable()
Comparación Prop. grupo 1 Prop. grupo 2 valor p
Inasistencia: Mujer vs. Hombre 0.0709 0.0721 0.496
Cancelación: 2015-2019 vs. 2020-2024 0.1649 0.1626 0.313

Interpretación: la inasistencia de mujeres y hombres (p = 0.496) y la tasa de cancelación entre los dos periodos (p = 0.313) no difieren significativamente. Las pérdidas de capacidad son un problema estructural y constante en el tiempo: no han mejorado solas en diez años, lo que justifica una intervención activa.

7 Gráficos comparativos con ggpubr

g_a <- ggboxplot(atendidas, x = "jornada", y = "waiting_time", fill = "jornada",
                 palette = c("#66C2A5", "#FC8D62"), outlier.shape = NA,
                 xlab = "Jornada", ylab = "Espera (min)", title = "Espera: mañana vs. tarde") +
  coord_cartesian(ylim = c(0, 200)) +
  stat_compare_means(method = "t.test", label.y = 190) + theme(legend.position = "none")

g_b <- ggline(atendidas %>% mutate(hora = factor(hora)), x = "hora", y = "waiting_time", add = "mean_ci",
              color = "#2C7FB8", xlab = "Hora programada", ylab = "Espera media (min, IC 95%)",
              title = "La espera crece con cada hora del día")

ggarrange(g_a, g_b, ncol = 2, labels = c("A", "B"))

g_c <- ggviolin(atendidas, x = "banda_edad", y = "appointment_duration", fill = "banda_edad",
                palette = "Set2", add = "boxplot", add.params = list(fill = "white"),
                xlab = "Banda de edad", ylab = "Duración (min)", title = "Duración de consulta por edad") +
  stat_compare_means(method = "anova", label.y = 65) + theme(legend.position = "none")

prop_banda <- perdidas %>% group_by(banda_edad) %>%
  summarise(pct = round(100 * mean(status != "attended"), 1))

g_d <- ggbarplot(prop_banda, x = "banda_edad", y = "pct", fill = "#8DA0CB",
                 label = TRUE, xlab = "Banda de edad", ylab = "% de citas perdidas", title = "% de citas perdidas por edad") +
  ylim(0, 30)

ggarrange(g_c, g_d, ncol = 2, labels = c("C", "D"))

Interpretación: el panel A muestra que toda la caja de la tarde está por encima de la de la mañana, y la prueba t lo confirma. El panel B es el hallazgo clave: la espera media sube de forma casi lineal desde la primera cita (8 a. m.) hasta la última (5 p. m.), es decir, cada retraso se arrastra a las citas siguientes (efecto “bola de nieve”). Los paneles C y D muestran lo contrario para la edad: las formas de los violines y las barras son prácticamente iguales, sin diferencias significativas.

8 Conclusiones y decisiones de gestión

  1. La pérdida de capacidad es constante y predecible: cerca del 22.3 % de las citas se pierde, con una distribución de inasistencias diarias que sigue un modelo de Poisson (λ ≈ 2.5). No depende de la edad, el sexo, la jornada ni el periodo. Decisión: recordatorios a todos los pacientes y sobreagendamiento controlado de 2 cupos diarios; la simulación binomial muestra que bajar la inasistencia al 3 % reduciría las pérdidas a menos de la mitad.
  2. La espera es el principal problema de calidad: la media está entre 43.8 y 44.4 min (IC 95 %), por encima de la meta de 30 minutos, y empeoró levemente en 2020-2024.
  3. La causa de la espera es la organización de la agenda, no el paciente: la espera se duplica en la tarde y crece hora a hora (t-test y ANOVA significativos), mientras que la duración de la consulta es igual en todas las edades. Decisión: introducir “franjas de amortiguación” (cupos libres a media mañana y media tarde) para absorber retrasos, controlar las consultas que exceden la franja y monitorear la puntualidad de inicio.
  4. Atención equitativa: no hay diferencias por sexo ni por edad en espera, duración o inasistencia.
  5. Lección metodológica: con bases grandes, una diferencia pequeña puede ser estadísticamente significativa; las decisiones deben considerar también el tamaño del efecto (minutos o puntos porcentuales) y su costo para la institución.