Problema de gestión. ¿Cómo reducir la congestión y los tiempos de espera en el servicio de urgencias de una institución prestadora de servicios de salud de alta complejidad, sin comprometer la seguridad del paciente ni la calidad de la atención? El análisis identifica cuándo se concentran las presiones asistenciales y cuáles decisiones de programación de capacidad merecen validación.
Audiencia. Gerencia general, dirección médica, subdirección de enfermería, coordinación de urgencias y equipo de planeación/calidad. Son responsables de distribuir recursos, ajustar turnos, evaluar indicadores y gestionar riesgos.
Modelo A³ (Atención → Análisis → Acción). La Atención sitúa el problema en un indicador directivo; el Análisis identifica patrones, picos y posibles causas para indagar; la Acción traduce hallazgos en decisiones verificables con responsables e indicadores.
Pregunta directiva: ¿en qué franja se concentra la mayor espera y qué cambio operativo puede probarse primero?
Alcance. Periodo sintético del 01/01/2026 al 31/03/2026, 360 observaciones de día × franja. Datos generados con semilla fija, no representan pacientes, hospitales ni resultados reales. Los umbrales de 85 % de ocupación y 60 minutos de espera son criterios de trabajo ilustrativos, sujetos a aprobación institucional.
Se simularon 12.306 llegadas. La espera promedio ponderada fue 60,5 minutos, y la ocupación promedio ponderada por demanda fue 84,9 %. Se registraron 9 días con ocupación diaria superior al umbral de alerta del 90 %. La franja con mayor espera media fue 12:00–17:59.
Decisión sugerida: validar un piloto de refuerzo operativo en esa franja, además de activar un seguimiento diario de ocupación y espera, antes de considerar expansión física. El análisis es descriptivo; no prueba causalidad ni estima beneficios clínicos.
ggplot2: ¿qué aporta a la decisión?La función central ggplot(datos, aes(...)) inicia una
representación declarativa: los datos son la fuente,
aes() asigna variables a ejes, colores o tamaños, y las
capas geom_*() escogen cómo se muestran. Con
geom_line() se estudian tendencias; geom_col()
compara categorías; annotate() destaca eventos;
ggrepel::geom_text_repel() reduce colisiones entre
etiquetas; las escalas y el tema controlan la legibilidad. No
existe una sola función que convierta datos en decisiones: la
selección de geometrías depende de la pregunta analítica. https://ggplot2.tidyverse.org/
En este dashboard se evita la decoración innecesaria
(theme_minimal()), se reserva el rojo para alertas y se usa
una paleta cualitativa de ColorBrewer (scale_fill_brewer())
para categorías. Se agrega plotly::ggplotly() a la
tendencia para inspeccionar fechas exactas, acercar periodos y explorar
puntos sin cambiar el análisis subyacente.
Criterio ético: los valores no deben utilizarse para informar sobre una institución real. Antes de aplicar el modelo se requiere anonimización, validación de datos fuente y segmentación clínica por prioridad de triaje.
Evidencia: el KPI resume una media ponderada de ocupación por demanda. Relevancia: una media elevada puede ocultar momentos críticos. Acción: revisar la serie diaria y activar alertas operativas al superar 90 % (referencia ilustrativa, no umbral regulatorio).
| Indicador | Resultado |
|---|---|
| Días sobre el umbral de alerta | 9 de 90 |
| Oportunidad estimada ≤ 60 min | 67,2 % |
| Cambio de espera: última vs. primera semana | +6,6 min |
Evidencia: el tablero vincula nivel de presión, oportunidad y variación temporal. Relevancia: una mejora aparente en el promedio puede coexistir con días de crisis. Acción: revisar cada mañana el tablero de control en el comité operativo y documentar medidas de contingencia.
Evidencia: el máximo diario de 88,9 min ocurrió el 19/02/2026; el área sombreada representa un choque de demanda incorporado deliberadamente en la simulación. Relevancia: permite reconocer ventanas para revisar dotación, clasificación y disponibilidad de camas. Acción: comparar las alertas con ausentismo, traslados y egresos, cuando existan datos observados.
Evidencia: al pasar el cursor se obtiene espera, ocupación y volumen del día; el zoom permite concentrarse en semanas. Relevancia: la dirección puede contrastar excepciones sin pedir una gráfica nueva para cada fecha. Acción: registrar tres días extremos e investigar sus causas mediante bitácoras del servicio. Este gráfico es explorable incluso sin servidor Shiny.
geom_col() + ColorBrewerEvidencia: la franja de 12:00–17:59 tiene la mayor espera simulada (71,8 min). Relevancia: una redistribución de turnos podría ser más focalizada que un aumento uniforme de plantilla. Acción: observar dotación por hora, proporción por triaje y tiempos de salida de pacientes durante dos semanas.
Evidencia: en los datos construidos se observa asociación positiva entre llegadas y ocupación. Relevancia: demanda, camas y egresos deben analizarse en conjunto. Acción: contrastar el hallazgo con datos reales y ajustar por complejidad, personal y disponibilidad de camas; la correlación mostrada no establece causalidad.
Evidencia: con la suposición de una reducción de 15 minutos exclusivamente en la franja crítica, la media global pasaría de 60,5 a 55,5 min (diferencia de 4,9 min). Relevancia: ayuda a dimensionar si un piloto localizado amerita recursos. Acción: experimentar durante cuatro semanas y comparar con franjas control o una línea base ajustada, sin asumir que el efecto hipotético se cumplirá.
1. Piloto operativo de redistribución de capacidad (4 semanas). Responsables: coordinación de urgencias, enfermería y talento humano. Acción: revisar turnos y habilitar refuerzo escalonado en la franja 12:00–17:59, sujeto a disponibilidad presupuestal y validación clínica. Seguimiento: media y percentil 90 de espera, abandono, clasificación de triaje y eventos de seguridad. Criterio de continuidad: mejora sostenida frente a línea base sin deteriorar indicadores de seguridad ni sobrecargar personal.
2. Sistema de alertas tempranas y gestión de flujo (implementación en 30 días). Responsables: dirección médica, hospitalización, calidad y analítica. Acción: reunión de capacidad dos veces al día; visualizar ocupación por áreas, tiempos de egreso, pacientes pendientes de cama y riesgo de congestión. Activar un protocolo de escalamiento cuando se cruce el umbral internamente validado. Seguimiento: días de alerta, tiempo de asignación de cama y oportunidad por triaje, con revisión semanal en comité de dirección.
Evidencia: la matriz asigna tareas, productos y responsables. Relevancia: convierte el análisis en gestión verificable, evitando recomendaciones abstractas. Acción: formalizar la decisión en comité y sostener la evaluación con datos observacionales reales.
Fuente de datos: simulación determinista en R con
set.seed(20261022) sobre 90 días × 4 franjas. Se modelan
llegadas, ocupación, tiempos de espera, abandono, ingresos y oportunidad
estimada. La serie incluye variación temporal, diferencias horarias y un
choque exógeno artificial del 15 al 21 de febrero.
Unidad de análisis: franja × día. Promedios de espera y ocupación: ponderados por llegadas; abandono: total abandonos / total llegadas × 100. Día de alerta: promedio diario de ocupación superior a 90 %. Los porcentajes de ocupación simulada pueden superar 100 % para representar presión por encima de la capacidad nominal; no son un censo validado.
Controles: semilla fija, chequeos de ausencia de datos faltantes, valores no negativos y coherencia entre recuentos. Limitaciones: datos ficticios, ausencia de severidad de pacientes, tiempos individuales y estacionalidad real; un promedio no representa mediana ni p90; escenarios hipotéticos no permiten inferencia causal. El indicador de oportunidad ≤ 60 min es modelado y no representa cumplimiento de una norma sanitaria.
Gobernanza de datos: al sustituir los datos simulados por los institucionales, utilizar identificadores anonimizados, controlar accesos, documentar reglas de limpieza y definir alertas por triaje y condiciones clínicas. Ninguna medida de eficiencia debe desplazar criterios de priorización asistencial.
Informe_analitico_urgencias.Rmd y
seleccione Knit.salidas/datos_sinteticos_urgencias.csv y
salidas/indicadores_diarios.csv.datos por un proceso documentado de lectura y depuración
que preserve las columnas analíticas.Paquetes principales: ggplot2
(gramática de gráficos), ggrepel (etiquetas legibles),
plotly (interactividad), flexdashboard
(arquitectura), dplyr y tidyr
(transformaciones), DT (tabla explorable).
Material de apoyo: RPubs: Introducción a la visualización de datos con R · ggplot2 · flexdashboard · Plotly para R.
Declaración de uso y alcance: caso académico para apoyar análisis y toma de decisiones; todas las cifras se derivan de datos creados artificialmente. Se debe evitar su difusión como indicador observado de una IPS.
Semilla: 20261022 | Registros: 360 | Días: 90
R: R version 4.6.1 (2026-06-24 ucrt)
ggplot2: 4.0.3 | flexdashboard: 0.6.3 | plotly: 4.12.1
---
title: "URG | Inteligencia para decisiones en urgencias"
subtitle: "Del dato a la decisión: congestión, oportunidad y capacidad resolutiva"
author: "Alexandra Cecilia Rivera Pardo"
date: "22 de octubre de 2026"
output:
flexdashboard::flex_dashboard:
orientation: rows
vertical_layout: scroll
source_code: embed
theme: cosmo
self_contained: true
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE,
fig.width = 10, fig.height = 5.1, dpi = 110)
paquetes <- c("flexdashboard", "ggplot2", "dplyr", "tidyr", "lubridate",
"scales", "RColorBrewer", "ggrepel", "plotly", "DT", "knitr")
faltantes <- paquetes[!vapply(paquetes, requireNamespace, logical(1), quietly = TRUE)]
if (length(faltantes)) stop(paste("Instale estos paquetes antes de tejer el archivo:",
paste(faltantes, collapse = ", ")))
for (p in paquetes) suppressPackageStartupMessages(library(p, character.only = TRUE))
options(scipen = 999)
set.seed(20261022)
color_base <- "#174A67"
color_riesgo <- "#BD4D3E"
color_seguro <- "#23816D"
formatear <- function(x, digitos=1) format(round(x, digitos), nsmall=digitos, big.mark=".", decimal.mark=",")
tema <- theme_minimal(base_size = 12) +
theme(panel.grid.minor = element_blank(),
plot.title = element_text(face="bold", color="#1D3745", size=14),
plot.subtitle = element_text(color="#526674", size=10),
legend.position = "bottom", axis.title.x = element_blank())
```
```{r datos, include=FALSE}
# DATOS 100 % SIMULADOS. Unidad analítica: día x franja horaria.
# No se modelan historias clínicas ni se atribuyen registros a ningún hospital real.
fechas <- seq(as.Date("2026-01-01"), as.Date("2026-03-31"), by = "day")
franjas <- c("00:00–05:59", "06:00–11:59", "12:00–17:59", "18:00–23:59")
base <- tidyr::expand_grid(fecha = fechas, franja = factor(franjas, levels = franjas)) %>%
mutate(mes = factor(format(fecha, "%Y-%m"), levels=c("2026-01","2026-02","2026-03")),
dia_semana = as.integer(lubridate::wday(fecha, week_start = 1)),
fin_semana = dia_semana >= 6,
n_dia = as.integer(fecha - min(fecha)) + 1,
f = as.integer(franja),
choque = fecha >= as.Date("2026-02-15") & fecha <= as.Date("2026-02-21"),
estacional = 3 * sin(n_dia / 7),
# El patrón crea un evento de congestión ilustrativo en febrero.
demanda_media = 26 + c(-12, 6, 13, 5)[f] +
5 * fin_semana + 11 * choque + estacional + .06*n_dia,
llegadas = pmax(5L, rpois(n(), pmax(2, demanda_media))),
camas_disponibles = 55L,
# Ocupación promedio por franja, no censo continuo de camas.
ocupacion = pmin(108, pmax(58, 72 + .28 * llegadas +
5*choque + 3*(f == 3) + rnorm(n(), 0, 5))),
# Espera a valoración expresada en minutos.
espera_min = pmax(12, 14 + 1.15 * llegadas +
1.5 * pmax(ocupacion - 90, 0) + rnorm(n(), 0, 9)),
ingresos_hospital = rbinom(n(), size = llegadas, prob=.22),
abandono = rbinom(n(), size = llegadas,
prob=plogis(-4.3 + .023 * espera_min)),
oportunidad_60 = pmax(0, pmin(100, 100*plogis(3.6 - .046*espera_min))),
ocupacion = round(ocupacion,1), espera_min = round(espera_min,1),
oportunidad_60 = round(oportunidad_60,1)) %>%
select(fecha, mes, franja, fin_semana, llegadas, ingresos_hospital, abandono,
camas_disponibles, ocupacion, espera_min, oportunidad_60, choque)
stopifnot(!anyNA(base), all(base$llegadas >= 0),
all(base$abandono <= base$llegadas),
all(base$ingresos_hospital <= base$llegadas),
nrow(base)==length(fechas)*length(franjas))
# Calcular promedios ponderados ANTES de resumir llegadas.
# En dplyr, summarise() puede reutilizar el nombre recién creado;
# por ello sum(llegadas) debe ir al final de cada summarise().
diario <- base %>% group_by(fecha) %>%
summarise(ocupacion = weighted.mean(ocupacion, llegadas),
espera = weighted.mean(espera_min, llegadas),
oport = weighted.mean(oportunidad_60, llegadas),
abandono = sum(abandono),
ingresos = sum(ingresos_hospital),
llegadas = sum(llegadas),
.groups="drop") %>%
mutate(semana=as.Date(lubridate::floor_date(fecha, "week", week_start=1)),
alerta = ocupacion > 90)
mensual <- base %>% group_by(mes) %>%
summarise(ocupacion=weighted.mean(ocupacion,llegadas),
espera=weighted.mean(espera_min,llegadas),
abandono=sum(abandono),
llegadas=sum(llegadas), .groups="drop")
por_franja <- base %>% group_by(franja) %>%
summarise(espera=weighted.mean(espera_min,llegadas),
ocupacion=weighted.mean(ocupacion,llegadas),
abandono=sum(abandono),
llegadas=sum(llegadas), .groups="drop") %>%
mutate(tasa_abandono=100*abandono/llegadas)
semanal <- diario %>% group_by(semana) %>%
summarise(espera=weighted.mean(espera,llegadas),
ocupacion=weighted.mean(ocupacion,llegadas), .groups="drop")
periodo_critico <- diario %>% slice_max(espera, n=1, with_ties=FALSE)
franja_critica <- por_franja %>% slice_max(espera,n=1,with_ties=FALSE)
ultima_semana <- diario %>% filter(fecha >= max(fecha)-6)
primera_semana <- diario %>% filter(fecha <= min(fecha)+6)
indicadores <- list(
total = sum(base$llegadas),
espera = weighted.mean(base$espera_min,base$llegadas),
ocupacion = weighted.mean(base$ocupacion,base$llegadas),
tasa_abandono = 100*sum(base$abandono)/sum(base$llegadas),
dias_alerta = sum(diario$alerta),
oport = weighted.mean(base$oportunidad_60,base$llegadas),
delta_espera = weighted.mean(ultima_semana$espera, ultima_semana$llegadas) -
weighted.mean(primera_semana$espera,primera_semana$llegadas)
)
# Supuestos gerenciales demostrativos; NO son estándares clínicos ni metas oficiales.
meta_ocupacion <- 85
umbral_alerta <- 90
meta_espera <- 60
# Escenario ilustrativo: 1 refuerzo de personal en la franja de mayor espera,
# que hipotéticamente reduce en 15 minutos la espera de esa franja.
min_reducidos <- 15
prop_demanda_critica <- franja_critica$llegadas / indicadores$total
espera_escenario <- indicadores$espera - prop_demanda_critica * min_reducidos
# Exportar datos generados para auditoría y reutilización.
dir.create("salidas", showWarnings = FALSE)
write.csv(base, "salidas/datos_sinteticos_urgencias.csv", row.names = FALSE,
fileEncoding="UTF-8")
write.csv(diario, "salidas/indicadores_diarios.csv", row.names=FALSE,
fileEncoding="UTF-8")
```
<style>
body { color: #263B48; }
.navbar { background-color:#123E59 !important; }
.section.level3 { border-radius: 5px; }
blockquote { border-left: 3px solid #23816D; background:#F5F9FA; padding:10px 14px; }
.small-note {font-size: 12px; color:#5a6d79;}
</style>
Contexto y decisiones {data-icon="fa-bullseye"}
=====================================
Row
-------------------------------------
### Fase de Atención | Problema estratégico y audiencia
**Problema de gestión.** ¿Cómo reducir la congestión y los tiempos de espera en el servicio de urgencias de una institución prestadora de servicios de salud de alta complejidad, **sin comprometer la seguridad del paciente ni la calidad de la atención**? El análisis identifica *cuándo* se concentran las presiones asistenciales y cuáles decisiones de programación de capacidad merecen validación.
**Audiencia.** Gerencia general, dirección médica, subdirección de enfermería, coordinación de urgencias y equipo de planeación/calidad. Son responsables de distribuir recursos, ajustar turnos, evaluar indicadores y gestionar riesgos.
**Modelo A³ (Atención → Análisis → Acción).** La **Atención** sitúa el problema en un indicador directivo; el **Análisis** identifica patrones, picos y posibles causas para indagar; la **Acción** traduce hallazgos en decisiones verificables con responsables e indicadores.
**Pregunta directiva:** ¿en qué franja se concentra la mayor espera y qué cambio operativo puede probarse primero?
**Alcance.** Periodo sintético del `r format(min(base$fecha), "%d/%m/%Y")` al `r format(max(base$fecha), "%d/%m/%Y")`, `r nrow(base)` observaciones de día × franja. **Datos generados con semilla fija, no representan pacientes, hospitales ni resultados reales.** Los umbrales de 85 % de ocupación y 60 minutos de espera son **criterios de trabajo ilustrativos**, sujetos a aprobación institucional.
### Resumen ejecutivo automático
Se simularon **`r format(indicadores$total,big.mark=".")` llegadas**. La espera promedio ponderada fue **`r formatear(indicadores$espera)` minutos**, y la ocupación promedio ponderada por demanda fue **`r formatear(indicadores$ocupacion)` %**. Se registraron **`r indicadores$dias_alerta` días** con ocupación diaria superior al umbral de alerta del 90 %. La franja con mayor espera media fue **`r as.character(franja_critica$franja)`**.
**Decisión sugerida:** validar un piloto de refuerzo operativo en esa franja, además de activar un seguimiento diario de ocupación y espera, **antes de considerar expansión física**. El análisis es descriptivo; no prueba causalidad ni estima beneficios clínicos.
Row
-------------------------------------
### Función de `ggplot2`: ¿qué aporta a la decisión?
La función central `ggplot(datos, aes(...))` inicia una representación declarativa: los **datos** son la fuente, `aes()` asigna variables a ejes, colores o tamaños, y las capas `geom_*()` escogen cómo se muestran. Con `geom_line()` se estudian tendencias; `geom_col()` compara categorías; `annotate()` destaca eventos; `ggrepel::geom_text_repel()` reduce colisiones entre etiquetas; las escalas y el tema controlan la legibilidad. **No existe una sola función que convierta datos en decisiones:** la selección de geometrías depende de la pregunta analítica. <https://ggplot2.tidyverse.org/>
En este dashboard se evita la decoración innecesaria (`theme_minimal()`), se reserva el rojo para alertas y se usa una paleta cualitativa de ColorBrewer (`scale_fill_brewer()`) para categorías. Se agrega `plotly::ggplotly()` a la tendencia para inspeccionar fechas exactas, acercar periodos y explorar puntos sin cambiar el análisis subyacente.
> **Criterio ético:** los valores no deben utilizarse para informar sobre una institución real. Antes de aplicar el modelo se requiere anonimización, validación de datos fuente y segmentación clínica por prioridad de triaje.
Monitor ejecutivo {data-icon="fa-dashboard"}
=============================================
Row {data-height=145}
-------------------------------------
### Llegadas en el periodo
```{r kpi1}
flexdashboard::valueBox(format(indicadores$total,big.mark="."), "Atenciones / llegadas simuladas", icon="fa-users", color="info")
```
### Espera promedio ponderada
```{r kpi2}
flexdashboard::valueBox(paste0(formatear(indicadores$espera)," min"),
"Criterio exploratorio: 60 min", icon="fa-clock-o",
color=if (indicadores$espera > meta_espera) "danger" else "success")
```
### Ocupación promedio
```{r kpi3}
flexdashboard::valueBox(paste0(formatear(indicadores$ocupacion)," %"),
"Referencia gerencial ilustrativa: 85 %", icon="fa-bed",
color=if (indicadores$ocupacion > meta_ocupacion) "warning" else "success")
```
### Abandono antes de atención (%)
```{r kpi4}
flexdashboard::valueBox(paste0(formatear(indicadores$tasa_abandono)," %"),
"Fracción de llegadas simuladas", icon="fa-sign-out", color="primary")
```
Row
-------------------------------------
### Ocupación: lectura en semáforo
```{r gauge, fig.height=3.6}
flexdashboard::gauge(round(indicadores$ocupacion,1), min=0, max=110,
symbol="%", label="Ocupación ponderada por llegadas",
sectors=flexdashboard::gaugeSectors(
success=c(0,85), warning=c(85,90), danger=c(90,110)))
```
**Evidencia:** el KPI resume una media ponderada de ocupación por demanda. **Relevancia:** una media elevada puede ocultar momentos críticos. **Acción:** revisar la serie diaria y activar alertas operativas al superar 90 % (referencia ilustrativa, no umbral regulatorio).
### Riesgo acumulado y vigilancia
```{r semaforo}
resumen_alertas <- data.frame(
Indicador=c("Días sobre el umbral de alerta", "Oportunidad estimada ≤ 60 min", "Cambio de espera: última vs. primera semana"),
Resultado=c(paste(indicadores$dias_alerta,"de",nrow(diario)),
paste0(formatear(indicadores$oport)," %"),
paste0(ifelse(indicadores$delta_espera > 0,"+",""),formatear(indicadores$delta_espera)," min")))
knitr::kable(resumen_alertas, format="html", align=c("l","r"))
```
**Evidencia:** el tablero vincula nivel de presión, oportunidad y variación temporal. **Relevancia:** una mejora aparente en el promedio puede coexistir con días de crisis. **Acción:** revisar cada mañana el tablero de control en el comité operativo y documentar medidas de contingencia.
Patrones y alertas {data-icon="fa-line-chart"}
==============================================
Row
-------------------------------------
### Serie temporal | Tendencia estática anotada
```{r tendencia, fig.height=5.2}
g_linea <- ggplot(diario, aes(fecha, espera)) +
geom_line(linewidth=.8, color=color_base) +
geom_hline(yintercept=meta_espera, linetype="dashed", color=color_riesgo) +
annotate("rect", xmin=as.Date("2026-02-15"), xmax=as.Date("2026-02-21"),
ymin=-Inf, ymax=Inf, fill="#EDB4A2", alpha=.16) +
geom_point(data=periodo_critico, color=color_riesgo, size=2.8) +
ggrepel::geom_text_repel(data=periodo_critico,
aes(label=paste0("Máximo: ",round(espera,1)," min")),
color=color_riesgo, size=3.6, nudge_y=14,
min.segment.length=0, max.overlaps=Inf) +
annotate("text", x=as.Date("2026-02-17"), y=min(diario$espera)+6,
label="Periodo de presión simulada", color="#8F403A", size=3.2) +
labs(title="La demanda asistencial no se distribuye homogéneamente",
subtitle="Tiempo de espera promedio diario ponderado por llegadas",
y="Minutos de espera", caption="Fuente: simulación propia, enero–marzo de 2026") +
scale_x_date(date_labels="%d %b", date_breaks="2 weeks") + tema
print(g_linea)
```
**Evidencia:** el máximo diario de **`r formatear(periodo_critico$espera)` min** ocurrió el **`r format(periodo_critico$fecha, "%d/%m/%Y")`**; el área sombreada representa un choque de demanda incorporado deliberadamente en la simulación. **Relevancia:** permite reconocer ventanas para revisar dotación, clasificación y disponibilidad de camas. **Acción:** comparar las alertas con ausentismo, traslados y egresos, cuando existan datos observados.
### Tendencia interactiva | Zoom, paneo y tooltips
```{r interactivo, fig.height=5.2}
interactivos <- diario %>%
mutate(info=paste0("Fecha: ", format(fecha,"%d/%m/%Y"),
"<br>Espera: ", round(espera,1)," min",
"<br>Ocupación: ",round(ocupacion,1)," %",
"<br>Llegadas: ",llegadas))
p_interactivo <- ggplot(interactivos,
aes(fecha, espera, text=info)) +
geom_line(color=color_base, linewidth=.7) +
geom_point(color=color_seguro, size=1.5) +
geom_hline(yintercept=meta_espera, linetype="dashed", color=color_riesgo) +
labs(x="Fecha", y="Espera (min)",
title="Explore fechas y eventos críticos") + tema
plotly::ggplotly(p_interactivo, tooltip="text") %>%
plotly::layout(dragmode="zoom") %>%
plotly::config(displaylogo=FALSE, displayModeBar=TRUE,
modeBarButtonsToAdd=c("pan2d", "resetScale2d"))
```
**Evidencia:** al pasar el cursor se obtiene espera, ocupación y volumen del día; el zoom permite concentrarse en semanas. **Relevancia:** la dirección puede contrastar excepciones sin pedir una gráfica nueva para cada fecha. **Acción:** registrar tres días extremos e investigar sus causas mediante bitácoras del servicio. Este gráfico es explorable incluso sin servidor Shiny.
Row
-------------------------------------
### Comparación de franjas | `geom_col()` + ColorBrewer
```{r franjas, fig.height=4.7}
g_franjas <- ggplot(por_franja, aes(franja, espera, fill=franja)) +
geom_col(width=.67, show.legend=FALSE) +
geom_hline(yintercept=meta_espera, linetype="dashed", color=color_riesgo) +
ggrepel::geom_text_repel(aes(label=paste0(round(espera,1), " min")),
direction="y", nudge_y=5, size=3.8, max.overlaps=Inf,
show.legend=FALSE) +
scale_fill_brewer(palette="Set2") +
scale_y_continuous(limits=c(0,max(por_franja$espera)*1.30),
expand=expansion(mult=c(0,.02))) +
labs(title="¿En qué momento del día se concentra la espera?",
subtitle="Comparación de promedios ponderados por número de llegadas",
y="Espera media (min)") + tema
print(g_franjas)
```
**Evidencia:** la franja de **`r as.character(franja_critica$franja)`** tiene la mayor espera simulada (**`r formatear(franja_critica$espera)` min**). **Relevancia:** una redistribución de turnos podría ser más focalizada que un aumento uniforme de plantilla. **Acción:** observar dotación por hora, proporción por triaje y tiempos de salida de pacientes durante dos semanas.
### Relación entre demanda y ocupación | Distribución
```{r dispersion, fig.height=4.7}
set.seed(21)
muestra <- base %>% mutate(estado=if_else(ocupacion > umbral_alerta, "Alerta >90 %", "≤90 %"))
g_scatter <- ggplot(muestra, aes(llegadas, ocupacion, color=estado)) +
geom_point(alpha=.57, size=1.8) +
geom_smooth(method="lm", se=FALSE, color=color_base, linewidth=.8) +
geom_hline(yintercept=umbral_alerta, linetype="dashed", color=color_riesgo) +
scale_color_manual(values=c("Alerta >90 %"=color_riesgo,"≤90 %"=color_seguro)) +
labs(title="Mayor demanda se asocia con presión sobre la capacidad",
subtitle="Cada punto representa un día y una franja horaria",
x="Llegadas en la franja", y="Ocupación promedio (%)", color="Condición") + tema
print(g_scatter)
```
**Evidencia:** en los datos construidos se observa asociación positiva entre llegadas y ocupación. **Relevancia:** demanda, camas y egresos deben analizarse en conjunto. **Acción:** contrastar el hallazgo con datos reales y ajustar por complejidad, personal y disponibilidad de camas; **la correlación mostrada no establece causalidad**.
Decisión y plan de mejora {data-icon="fa-check-circle"}
=====================================================
Row
-------------------------------------
### Escenario de decisión | Simulación de impacto hipotético
```{r escenario, fig.height=4.3}
escenarios <- tibble::tibble(
escenario=factor(c("Situación simulada", "Piloto de refuerzo"),
levels=c("Situación simulada", "Piloto de refuerzo")),
espera=c(indicadores$espera, espera_escenario))
g_esc <- ggplot(escenarios, aes(escenario, espera, fill=escenario)) +
geom_col(width=.6, show.legend=FALSE) +
geom_text(aes(label=paste0(round(espera,1), " min")), vjust=-.4, size=4) +
scale_fill_manual(values=c(color_base,color_seguro)) +
scale_y_continuous(limits=c(0,max(escenarios$espera)*1.20)) +
labs(title="¿Qué pasaría si se redujeran 15 minutos en la franja crítica?",
subtitle="Escenario aritmético, NO estimación causal de efectividad",
y="Espera general ponderada (min)") + tema
print(g_esc)
```
**Evidencia:** con la suposición de una reducción de 15 minutos exclusivamente en la franja crítica, la media global pasaría de **`r formatear(indicadores$espera)`** a **`r formatear(espera_escenario)` min** (diferencia de **`r formatear(indicadores$espera - espera_escenario)` min**). **Relevancia:** ayuda a dimensionar si un piloto localizado amerita recursos. **Acción:** experimentar durante cuatro semanas y comparar con franjas control o una línea base ajustada, sin asumir que el efecto hipotético se cumplirá.
### Dos recomendaciones ejecutivas viables
**1. Piloto operativo de redistribución de capacidad (4 semanas).** **Responsables:** coordinación de urgencias, enfermería y talento humano. **Acción:** revisar turnos y habilitar refuerzo escalonado en la franja **`r as.character(franja_critica$franja)`**, sujeto a disponibilidad presupuestal y validación clínica. **Seguimiento:** media y percentil 90 de espera, abandono, clasificación de triaje y eventos de seguridad. **Criterio de continuidad:** mejora sostenida frente a línea base sin deteriorar indicadores de seguridad ni sobrecargar personal.
**2. Sistema de alertas tempranas y gestión de flujo (implementación en 30 días).** **Responsables:** dirección médica, hospitalización, calidad y analítica. **Acción:** reunión de capacidad dos veces al día; visualizar ocupación por áreas, tiempos de egreso, pacientes pendientes de cama y riesgo de congestión. Activar un protocolo de escalamiento cuando se cruce el umbral *internamente validado*. **Seguimiento:** días de alerta, tiempo de asignación de cama y oportunidad por triaje, con revisión semanal en comité de dirección.
Row
-------------------------------------
### Matriz de implementación y evaluación
```{r matriz}
plan <- data.frame(
Horizonte=c("Semana 1", "Semanas 2–5", "Día 30", "Semana 6"),
Entregable=c("Línea base clínica y operacional validada",
"Piloto y medición por triaje", "Tablero de alertas en producción",
"Evaluación de piloto y decisión de escalamiento"),
Responsable=c("Calidad + analítica", "Urgencias + enfermería",
"Dirección médica + TI", "Gerencia + planeación"),
Verificación=c("Diccionario de indicadores y consistencia",
"Diferencia vs. línea base y eventos adversos",
"Cobertura y registro de activaciones",
"Acta de decisión sustentada en resultados"))
DT::datatable(plan, rownames=FALSE, options=list(pageLength=4,dom="t",scrollX=TRUE),
caption="Ruta de acción propuesta: responsables, tiempos y evidencia")
```
**Evidencia:** la matriz asigna tareas, productos y responsables. **Relevancia:** convierte el análisis en gestión verificable, evitando recomendaciones abstractas. **Acción:** formalizar la decisión en comité y sostener la evaluación con datos observacionales reales.
Metodología y reproducibilidad {data-icon="fa-code"}
===================================================
Row
-------------------------------------
### Diseño metodológico y límites de inferencia
**Fuente de datos:** simulación determinista en R con `set.seed(20261022)` sobre 90 días × 4 franjas. Se modelan llegadas, ocupación, tiempos de espera, abandono, ingresos y oportunidad estimada. La serie incluye variación temporal, diferencias horarias y un choque exógeno artificial del 15 al 21 de febrero.
**Unidad de análisis:** franja × día. **Promedios de espera y ocupación:** ponderados por llegadas; **abandono:** total abandonos / total llegadas × 100. **Día de alerta:** promedio diario de ocupación superior a 90 %. Los porcentajes de ocupación simulada pueden superar 100 % para representar presión por encima de la capacidad nominal; **no son un censo validado**.
**Controles:** semilla fija, chequeos de ausencia de datos faltantes, valores no negativos y coherencia entre recuentos. **Limitaciones:** datos ficticios, ausencia de severidad de pacientes, tiempos individuales y estacionalidad real; un promedio no representa mediana ni p90; escenarios hipotéticos no permiten inferencia causal. El indicador de oportunidad ≤ 60 min es modelado y **no representa cumplimiento de una norma sanitaria**.
**Gobernanza de datos:** al sustituir los datos simulados por los institucionales, utilizar identificadores anonimizados, controlar accesos, documentar reglas de limpieza y definir alertas por triaje y condiciones clínicas. Ninguna medida de eficiencia debe desplazar criterios de priorización asistencial.
### Diccionario de variables
```{r diccionario}
dicc <- data.frame(
Variable=c("fecha", "franja", "llegadas", "espera_min", "ocupacion", "abandono", "ingresos_hospital", "oportunidad_60"),
Definición=c("Fecha calendario", "Bloque de seis horas", "Llegadas en el bloque",
"Espera media simulada a valoración, minutos",
"Ocupación media nominal por bloque, porcentaje",
"Personas que abandonan antes de atención, número",
"Ingresos a hospitalización, número",
"Estimación sintética de atención antes de 60 min, porcentaje"))
DT::datatable(dicc, rownames=FALSE, options=list(pageLength=8, dom="t",scrollX=TRUE))
```
Row
-------------------------------------
### Transparencia del código y replicación
1. En RStudio, abra `Informe_analitico_urgencias.Rmd` y seleccione **Knit**.
2. El documento genera automáticamente `salidas/datos_sinteticos_urgencias.csv` y `salidas/indicadores_diarios.csv`.
3. Los bloques **setup** y **datos** documentan la transformación de datos; el código íntegro puede consultarse mediante **Code → Show All Code** en el archivo HTML generado (si el visor ofrece la opción).
4. Si decide usar registros reales, sustituya el bloque `datos` por un proceso documentado de lectura y depuración que preserve las columnas analíticas.
**Paquetes principales:** `ggplot2` (gramática de gráficos), `ggrepel` (etiquetas legibles), `plotly` (interactividad), `flexdashboard` (arquitectura), `dplyr` y `tidyr` (transformaciones), `DT` (tabla explorable).
**Material de apoyo:** [RPubs: Introducción a la visualización de datos con R](https://rpubs.com/Data4HR/visualizacion-humanos-reales) · [ggplot2](https://ggplot2.tidyverse.org/) · [flexdashboard](https://pkgs.rstudio.com/flexdashboard/) · [Plotly para R](https://plotly.com/ggplot2/getting-started/).
**Declaración de uso y alcance:** caso académico para apoyar análisis y toma de decisiones; todas las cifras se derivan de datos creados artificialmente. Se debe evitar su difusión como indicador observado de una IPS.
### Información de sesión (auditoría)
```{r sesion}
cat("Semilla: 20261022 | Registros:",nrow(base),"| Días:",nrow(diario),"\n")
cat("R:",R.version.string,"\n")
cat("ggplot2:",as.character(packageVersion("ggplot2")),
"| flexdashboard:",as.character(packageVersion("flexdashboard")),
"| plotly:",as.character(packageVersion("plotly")))
```