Resumen ejecutivo

Column

Citas analizadas

111,488

Asistencia

77.2%

Inasistencia

5.9%

Column

KPI crítico: tiempo promedio de espera

Lectura ejecutiva

El indicador de espera permite valorar la oportunidad percibida por el paciente una vez llega al servicio. Un tiempo promedio bajo sugiere mejor flujo operativo; un valor elevado puede reflejar sobrecarga de agendas, retrasos acumulados o insuficiente capacidad instalada.

Ruta de acción sugerida: establecer seguimiento semanal del tiempo promedio de espera y activar alertas cuando el indicador supere el umbral definido por la institución.

Análisis visual

Column

Tendencia mensual de citas y espera

¿Qué evidencia el gráfico? Muestra la evolución mensual del tiempo promedio de espera e identifica el pico crítico del periodo analizado.

¿Por qué es relevante? Permite detectar meses con mayor presión operativa y priorizar análisis de causas: agendas saturadas, retrasos acumulados o aumento de demanda.

¿Qué ruta de acción sugiere? Comparar los meses pico con disponibilidad de talento humano, número de consultorios, duración real de citas y volumen de usuarios citados.

Column

Estado de las citas

¿Qué evidencia el gráfico? Muestra la participación porcentual de citas asistidas, canceladas y no atendidas.

¿Por qué es relevante? Las cancelaciones e inasistencias reducen productividad, dejan cupos sin usar y afectan la oportunidad de pacientes que requieren atención.

¿Qué ruta de acción sugiere? Implementar recordatorios automatizados, confirmación previa y reprogramación digital para recuperar cupos antes de que se pierdan.

Exploración interactiva

Column

Exploración interactiva de la tendencia

Justificación de la interactividad

La conversión a gráfico interactivo permite que el usuario directivo explore los meses con mayor tiempo de espera, revise valores específicos mediante tooltips y amplíe periodos críticos con zoom. Esta funcionalidad aporta valor porque transforma una visualización descriptiva en una herramienta de exploración gerencial.

Column

Citas por grupo de edad y estado

Lectura ejecutiva: la segmentación por edad permite identificar qué grupos concentran mayor carga de citas y cómo se distribuyen los estados de atención. Esta información ayuda a focalizar estrategias de comunicación y seguimiento.

Acción directiva

Column

Hallazgos críticos

Síntesis ejecutiva de hallazgos y acciones
Hallazgo Impacto_en_gestion Accion_sugerida
Se identifican diferencias en el estado final de las citas. La inasistencia y cancelación reducen productividad y disponibilidad real. Confirmación previa y recuperación temprana de cupos.
El tiempo de espera presenta variación mensual. Los picos de espera sugieren congestión operativa o desbalance de agenda. Monitoreo semanal y ajuste de agendas en meses críticos.
Existen grupos etarios con mayor concentración de citas. La demanda por edad permite focalizar acciones de comunicación y acceso. Segmentar recordatorios y rutas de atención por grupo poblacional.

Column

Recomendaciones ejecutivas

1. Implementar un tablero de seguimiento operativo semanal.
Se recomienda monitorear tiempo promedio de espera, porcentaje de asistencia, cancelación e inasistencia. El tablero debe tener semáforos de alerta para activar acciones correctivas cuando se superen los umbrales definidos.

2. Fortalecer la estrategia de confirmación y reprogramación de citas.
Se recomienda automatizar recordatorios, permitir confirmación por canales digitales y liberar cupos con anticipación cuando el paciente no pueda asistir. Esta medida puede mejorar la productividad de agenda y reducir pérdida de capacidad instalada.

Cierre estratégico

La narrativa visual permite que la información operativa deje de ser únicamente descriptiva y se convierta en un insumo para la toma de decisiones. El uso de ggplot2, plotly y flexdashboard facilita construir un informe reproducible, visualmente claro y orientado a la acción directiva.

---
title: "Narrativa visual con RStudio"
subtitle: "Dashboard ejecutivo para gestión de citas y tiempos de espera"
author: "JOP VANNDERTH MORA"
date: "`r Sys.Date()`"
output:
  flexdashboard::flex_dashboard:
    orientation: columns
    vertical_layout: fill
    source_code: embed
    theme: cosmo
---

```{r setup, include=FALSE}
# ============================================================
# CONFIGURACIÓN TÉCNICA
# Actividad Semana 4: Narrativa visual con RStudio.
# Recomendación docente aplicada:
# - Contenido visible redactado como informe ejecutivo.
# - Detalles técnicos conservados dentro del código con comentarios #.
# - Documento diseñado para publicarse en RPubs como HTML.
# ============================================================

options(repos = c(CRAN = "https://cloud.r-project.org"))

if (!requireNamespace("rlang", quietly = TRUE) || packageVersion("rlang") < "1.1.7") {
  install.packages("rlang", dependencies = TRUE)
}

paquetes <- c(
  "flexdashboard", "readxl", "dplyr", "ggplot2", "plotly",
  "tidyr", "ggrepel", "tidyverse", "skimr", "rsample",
  "caret", "broom", "yardstick", "htmltools", "bslib",
  "bsicons", "janitor", "lubridate", "RColorBrewer",
  "knitr", "scales", "tibble"
)

instalar <- paquetes[!(paquetes %in% installed.packages()[, "Package"])]
if (length(instalar) > 0) install.packages(instalar, dependencies = TRUE)

suppressPackageStartupMessages({
  library(flexdashboard)
  library(readxl)
  library(dplyr)
  library(ggplot2)
  library(plotly)
  library(tidyr)
  library(ggrepel)
  library(tidyverse)
  library(skimr)
  library(rsample)
  library(caret)
  library(broom)
  library(yardstick)
  library(htmltools)
  library(bslib)
  library(bsicons)
  library(janitor)
  library(lubridate)
  library(RColorBrewer)
  library(knitr)
  library(scales)
  library(tibble)
})

knitr::opts_chunk$set(warning = FALSE, message = FALSE, echo = FALSE)
```

```{r cargar-datos}
# ============================================================
# CARGA Y PREPARACIÓN DE DATOS
# Problema de gestión: seguimiento de oportunidad, ocupación y no asistencia.
# ============================================================

if (file.exists("data/appointments.xlsx")) {
  ruta_archivo <- "data/appointments.xlsx"
} else if (file.exists("appointments.xlsx")) {
  ruta_archivo <- "appointments.xlsx"
} else {
  stop("No se encontró appointments.xlsx. Debe estar en data/ o junto al archivo .Rmd.")
}

datos <- read_excel(ruta_archivo) %>%
  clean_names() %>%
  mutate(
    scheduling_date = as.Date(scheduling_date),
    appointment_date = as.Date(appointment_date),
    dias_espera = as.numeric(difftime(appointment_date, scheduling_date, units = "days")),
    age = as.numeric(age),
    waiting_time = as.numeric(waiting_time),
    appointment_duration = as.numeric(appointment_duration),
    sex = as.factor(sex),
    status = as.factor(status),
    mes = floor_date(appointment_date, "month")
  ) %>%
  filter(!is.na(appointment_date), !is.na(status))

datos_limpios <- datos %>%
  filter(!is.na(dias_espera), dias_espera >= 0, !is.na(waiting_time), waiting_time >= 0)

total_citas <- nrow(datos)
porc_asistencia <- mean(datos$status == "attended", na.rm = TRUE)
porc_no_asiste <- mean(datos$status == "did not attend", na.rm = TRUE)
espera_promedio <- mean(datos_limpios$waiting_time, na.rm = TRUE)

estado_tabla <- datos %>%
  count(status) %>%
  mutate(porcentaje = n / sum(n))

tendencia_mensual <- datos_limpios %>%
  group_by(mes) %>%
  summarise(
    citas = n(),
    espera_promedio = mean(waiting_time, na.rm = TRUE),
    dias_espera_promedio = mean(dias_espera, na.rm = TRUE),
    no_asistencia = mean(status == "did not attend", na.rm = TRUE),
    .groups = "drop"
  ) %>%
  filter(!is.na(mes))

pico_espera <- tendencia_mensual %>%
  filter(espera_promedio == max(espera_promedio, na.rm = TRUE)) %>%
  slice(1)

estado_edad <- datos_limpios %>%
  filter(!is.na(age), age >= 0) %>%
  mutate(
    grupo_edad = cut(
      age,
      breaks = c(0, 18, 30, 45, 60, 75, 120),
      labels = c("0-18", "19-30", "31-45", "46-60", "61-75", "76+"),
      include.lowest = TRUE
    )
  ) %>%
  group_by(grupo_edad, status) %>%
  summarise(citas = n(), .groups = "drop")
```

Contexto {.sidebar}
=======================================================================

### Definición estratégica y audiencia

**Problema de gestión real:** disminuir la congestión operativa asociada a tiempos de espera, cancelaciones e inasistencias en la programación de citas ambulatorias.

**Audiencia directiva:** gerencia general, coordinación médica, dirección de calidad, líderes de experiencia del usuario y responsables de agenda asistencial.

**Enfoque A³ aplicado:**

- **Atención:** identificar el problema crítico de oportunidad y uso de agenda.
- **Análisis:** visualizar tendencias, estados de cita y grupos con mayor carga operativa.
- **Acción:** proponer decisiones ejecutivas para optimizar capacidad, confirmar citas y reducir tiempos improductivos.

Resumen ejecutivo
=======================================================================

Column {data-width=280}
-----------------------------------------------------------------------

### Citas analizadas

```{r value-total}
valueBox(comma(total_citas), "Total de citas registradas", icon = "fa-calendar-check", color = "primary")
```

### Asistencia

```{r value-asistencia}
valueBox(percent(porc_asistencia, accuracy = 0.1), "Citas asistidas", icon = "fa-user-check", color = "success")
```

### Inasistencia

```{r value-noasiste}
valueBox(percent(porc_no_asiste, accuracy = 0.1), "Citas no atendidas", icon = "fa-user-times", color = ifelse(porc_no_asiste > 0.10, "danger", "warning"))
```

Column {data-width=420}
-----------------------------------------------------------------------

### KPI crítico: tiempo promedio de espera

```{r gauge-espera}
gauge(
  round(espera_promedio, 1),
  min = 0,
  max = 60,
  symbol = " min",
  gaugeSectors(success = c(0, 15), warning = c(15, 30), danger = c(30, 60))
)
```

### Lectura ejecutiva

El indicador de espera permite valorar la oportunidad percibida por el paciente una vez llega al servicio. Un tiempo promedio bajo sugiere mejor flujo operativo; un valor elevado puede reflejar sobrecarga de agendas, retrasos acumulados o insuficiente capacidad instalada.

**Ruta de acción sugerida:** establecer seguimiento semanal del tiempo promedio de espera y activar alertas cuando el indicador supere el umbral definido por la institución.

Análisis visual
=======================================================================

Column {data-width=500}
-----------------------------------------------------------------------

### Tendencia mensual de citas y espera

```{r tendencia-estatica, fig.height=5}
grafico_tendencia <- ggplot(tendencia_mensual, aes(x = mes, y = espera_promedio)) +
  geom_line(linewidth = 1, color = "#2C3E50") +
  geom_point(size = 2, color = "#2C3E50") +
  geom_point(data = pico_espera, aes(x = mes, y = espera_promedio), color = "#C0392B", size = 4) +
  annotate("rect",
           xmin = pico_espera$mes - 15, xmax = pico_espera$mes + 15,
           ymin = pico_espera$espera_promedio - 2, ymax = pico_espera$espera_promedio + 2,
           alpha = 0.12, fill = "#C0392B") +
  ggrepel::geom_text_repel(
    data = pico_espera,
    aes(label = paste0("Pico: ", round(espera_promedio, 1), " min")),
    color = "#C0392B", size = 3.5
  ) +
  labs(title = "Evolución mensual del tiempo promedio de espera",
       x = "Mes", y = "Tiempo promedio de espera (minutos)") +
  theme_minimal()

grafico_tendencia
```

**¿Qué evidencia el gráfico?** Muestra la evolución mensual del tiempo promedio de espera e identifica el pico crítico del periodo analizado.

**¿Por qué es relevante?** Permite detectar meses con mayor presión operativa y priorizar análisis de causas: agendas saturadas, retrasos acumulados o aumento de demanda.

**¿Qué ruta de acción sugiere?** Comparar los meses pico con disponibilidad de talento humano, número de consultorios, duración real de citas y volumen de usuarios citados.

Column {data-width=500}
-----------------------------------------------------------------------

### Estado de las citas

```{r estado-citas, fig.height=5}
grafico_estado <- ggplot(estado_tabla, aes(x = status, y = porcentaje, fill = status)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = percent(porcentaje, accuracy = 0.1)), vjust = -0.3, size = 3.5) +
  scale_y_continuous(labels = percent_format()) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Distribución de citas según estado", x = "Estado de la cita", y = "Porcentaje") +
  theme_minimal()

grafico_estado
```

**¿Qué evidencia el gráfico?** Muestra la participación porcentual de citas asistidas, canceladas y no atendidas.

**¿Por qué es relevante?** Las cancelaciones e inasistencias reducen productividad, dejan cupos sin usar y afectan la oportunidad de pacientes que requieren atención.

**¿Qué ruta de acción sugiere?** Implementar recordatorios automatizados, confirmación previa y reprogramación digital para recuperar cupos antes de que se pierdan.

Exploración interactiva
=======================================================================

Column {data-width=650}
-----------------------------------------------------------------------

### Exploración interactiva de la tendencia

```{r tendencia-interactiva, fig.height=5}
plotly::ggplotly(grafico_tendencia)
```

### Justificación de la interactividad

La conversión a gráfico interactivo permite que el usuario directivo explore los meses con mayor tiempo de espera, revise valores específicos mediante tooltips y amplíe periodos críticos con zoom. Esta funcionalidad aporta valor porque transforma una visualización descriptiva en una herramienta de exploración gerencial.

Column {data-width=350}
-----------------------------------------------------------------------

### Citas por grupo de edad y estado

```{r edad-estado, fig.height=5}
grafico_edad <- ggplot(estado_edad, aes(x = grupo_edad, y = citas, fill = status)) +
  geom_col(position = "dodge") +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Citas por grupo de edad y estado",
       x = "Grupo de edad", y = "Número de citas", fill = "Estado") +
  theme_minimal()

grafico_edad
```

**Lectura ejecutiva:** la segmentación por edad permite identificar qué grupos concentran mayor carga de citas y cómo se distribuyen los estados de atención. Esta información ayuda a focalizar estrategias de comunicación y seguimiento.

Acción directiva
=======================================================================

Column {data-width=500}
-----------------------------------------------------------------------

### Hallazgos críticos

```{r tabla-hallazgos}
hallazgos <- tibble(
  Hallazgo = c(
    "Se identifican diferencias en el estado final de las citas.",
    "El tiempo de espera presenta variación mensual.",
    "Existen grupos etarios con mayor concentración de citas."
  ),
  Impacto_en_gestion = c(
    "La inasistencia y cancelación reducen productividad y disponibilidad real.",
    "Los picos de espera sugieren congestión operativa o desbalance de agenda.",
    "La demanda por edad permite focalizar acciones de comunicación y acceso."
  ),
  Accion_sugerida = c(
    "Confirmación previa y recuperación temprana de cupos.",
    "Monitoreo semanal y ajuste de agendas en meses críticos.",
    "Segmentar recordatorios y rutas de atención por grupo poblacional."
  )
)

knitr::kable(hallazgos, caption = "Síntesis ejecutiva de hallazgos y acciones")
```

Column {data-width=500}
-----------------------------------------------------------------------

### Recomendaciones ejecutivas

**1. Implementar un tablero de seguimiento operativo semanal.**  
Se recomienda monitorear tiempo promedio de espera, porcentaje de asistencia, cancelación e inasistencia. El tablero debe tener semáforos de alerta para activar acciones correctivas cuando se superen los umbrales definidos.

**2. Fortalecer la estrategia de confirmación y reprogramación de citas.**  
Se recomienda automatizar recordatorios, permitir confirmación por canales digitales y liberar cupos con anticipación cuando el paciente no pueda asistir. Esta medida puede mejorar la productividad de agenda y reducir pérdida de capacidad instalada.

### Cierre estratégico

La narrativa visual permite que la información operativa deje de ser únicamente descriptiva y se convierta en un insumo para la toma de decisiones. El uso de `ggplot2`, `plotly` y `flexdashboard` facilita construir un informe reproducible, visualmente claro y orientado a la acción directiva.