Anatomía de una Aplicación Shiny

El Esqueleto y el Cerebro: Desglosando los Componentes de la Interactividad.

El Esqueleto y el Cerebro

En la lección anterior, establecimos el ritmo de nuestro trabajo con Git. Ahora, vamos a sumergirnos en la arquitectura de lo que estamos construyendo. Una aplicación Shiny puede parecer compleja, pero en su núcleo, toda la magia se reduce a dos componentes fundamentales que colaboran entre sí.

Entender esta separación de responsabilidades no solo es clave para construir apps, sino también para depurarlas y escalarlas. Pensemos en ello como la construcción de un cuerpo: necesitamos un esqueleto que le dé forma y un cerebro que lo controle.

Los Dos Pilares: UI y Server

Toda aplicación Shiny vive en un archivo llamado app.R y se compone de dos objetos principales: ui y server.

UI (User Interface)

El Esqueleto. La UI define qué ve el usuario. Es la parte visual y estructural de la aplicación. Se encarga de dibujar los paneles, los títulos, los botones, los sliders y los espacios donde se mostrarán nuestros gráficos. Es, en esencia, una forma de escribir HTML y CSS usando la sintaxis de R.

Server (Servidor)

El Cerebro. El Server contiene cómo funciona la aplicación. Es el motor lógico que se ejecuta en segundo plano. Aquí es donde cargamos datos, hacemos cálculos, entrenamos modelos y, lo más importante, generamos los outputs (gráficos, tablas) que se mostrarán en la UI. El Server "escucha" las acciones del usuario en la UI y "reacciona" a ellas, actualizando los resultados en tiempo real.

Taller Práctico: Construyendo Nuestra Primera App

Vamos a aplicar estos conceptos para construir el esqueleto de nuestra aplicación de análisis exploratorio (EDA) de los datos del ICFES.

Paso 1: La Estructura del Archivo .gitignore

Antes de escribir el código de la app, debemos decirle a Git qué archivos ignorar. Esto es crucial para mantener nuestro repositorio limpio y seguro. En la raíz de tu proyecto, crea un archivo llamado .gitignore y añade el siguiente contenido.

# Archivos de historial y entorno de R
.Rhistory
.RData
.Rproj.user/
.Ruserdata/

# Archivos de paquetes (usar renv o similar en su lugar)
*.so
*.dll
*.o

# Datos (generalmente no se versionan si son grandes)
data/
# Descomentar la siguiente línea si los datos son pequeños y se quieren versionar
# !data/

# Modelos (a menudo grandes, considere usar Git LFS si necesita versionarlos)
models/

# Archivos de caché de Knitr y RMarkdown
*_cache/
/cache/
*.utf8.md
*.knit.md

# Archivos generados por Shiny
rsconnect/

# Archivos de sistema operativo
.DS.Store
Thumbs.db
*.sh

Este archivo le dice a Git que ignore archivos temporales de R, datos locales (que pueden ser muy grandes) y credenciales de despliegue, asegurando que solo el código fuente esencial sea versionado.

Antes de Continuar: ¿Dónde Estoy? (Para Usuarios de VS Code)

Un paso crucial antes de instalar paquetes o correr la app es asegurarse de que la terminal de VS Code está "apuntando" al directorio correcto de tu proyecto.

¡Ojo! Consola de R vs. Terminal del Sistema:
Lo que te sucedió con ls es clave: una cosa es la terminal del sistema (Bash, PowerShell, etc.) y otra la consola de R.

  • En la terminal del sistema, usas ls (o dir en Windows) para ver archivos.
  • En la consola de R, el comando para ver los archivos en tu directorio es dir().

Para verificar tu ubicación desde la consola de R, usa el siguiente comando:

# Muestra el directorio de trabajo actual en R
getwd()

# Lista los archivos en ese directorio
dir()

Si al usar dir() no ves tus archivos de proyecto, necesitas cambiar el directorio con setwd("ruta/a/tu/proyecto").

Truco útil: Para limpiar la pantalla de la consola de R en cualquier momento, usa el atajo de teclado Ctrl + L.

Paso 2: Instalar las Dependencias

Nuestra aplicación necesita ciertas "herramientas" o paquetes de R para funcionar. Para construir un dashboard interactivo y visualmente atractivo, usaremos varias librerías clave. Abre tu consola de R y ejecuta el siguiente comando para instalarlas todas de una vez.

install.packages(c("shiny", "readr", "ggplot2", "plotly", "dplyr", "bslib"))

Solo necesitas hacer esto una vez. Estas librerías nos darán el poder de crear la app, leer datos, manipularlos, crear gráficos y darles un estilo profesional.

Paso 3: Escribir el Código Base de la App

Primero, creemos la estructura inicial. En la raíz de tu proyecto, crea (o reemplaza) el archivo app.R con este código. Por ahora, solo cargará los datos y mostrará una tabla, como antes.

# Cargar la librería Shiny
library(shiny)
library(readr) # Para leer archivos de texto

#  ---  UI (User Interface)  --- 
# Define la apariencia de la aplicación
ui <- fluidPage(
  titlePanel("Visor de Datos Saber 11"),
  sidebarLayout(
    sidebarPanel(
      h3("Controles"),
      p("Aquí irán nuestros filtros e inputs en el futuro.")
    ),
    mainPanel(
      h2("Datos Cargados"),
      dataTableOutput("tabla_datos")
    )
  )
)

#  ---  SERVER (Lógica del Servidor)  --- 
# Define cómo funciona la aplicación
server <- function(input, output) {
  datos <- read_delim("data/raw/Examen_Saber_11_20242.txt", delim = ";")
  output$tabla_datos <- renderDataTable({
    datos
  })
}

#  ---  Ejecutar la Aplicación  --- 
shinyApp(ui = ui, server = server)

Paso 4: Ejecutar la Aplicación Localmente

Con el código listo, es hora de ver nuestra creación en acción. Hay dos maneras sencillas de ejecutar la aplicación desde tu editor:

1. Usando el Botón "Run App" (RStudio): Si tienes el archivo app.R abierto en RStudio, verás un botón "Run App" en la parte superior del panel del editor. Al hacer clic, RStudio lanzará la aplicación en una nueva ventana.

2. Usando la Consola (RStudio y VS Code): Este método funciona en cualquier entorno. Asegúrate de que tu terminal o consola de R esté en el directorio raíz de tu proyecto (la carpeta que contiene app.R) y ejecuta el siguiente comando:

shiny::runApp()

Solucionando un Error Común

Si al ejecutar shiny::runApp() ves un error que dice Error in shinyAppDir(): App dir must contain either app.R or server.R, ¡no te preocupes! Es el error más común al empezar.

Simplemente significa que tu consola de R no está "viendo" el archivo app.R. La solución es volver al paso "Antes de Continuar: ¿Dónde Estoy?" y usar getwd() y setwd() para navegar a la carpeta correcta de tu proyecto.

¿Cómo detener la aplicación?
Cuando una app está corriendo, tu consola de R estará ocupada. Para detener la aplicación y poder volver a usar la consola, simplemente presiona la tecla Esc o busca y haz clic en el ícono de "Stop" (una señal de pare roja) que aparece en la consola de R.

Paso 5: Construyendo un Dashboard Completo con Tarjetas y Pestañas

Ahora, transformemos nuestra app de un simple visor a un dashboard más completo. Reemplaza todo el contenido de tu archivo app.R con el siguiente código. Este código introduce tarjetas de KPIs (indicadores clave), pestañas para organizar el contenido y texto dinámico.

# Cargar todas las librerías necesarias
library(shiny)
library(readr)
library(dplyr)
library(ggplot2)
library(plotly)
library(bslib)

#  ---  Lógica de Datos (fuera del Server)  --- 
# Leemos y preparamos los datos una sola vez al iniciar la app
datos_saber <- read_delim("data/raw/Examen_Saber_11_20242.txt", delim = ";", show_col_types = FALSE) %>%
  na.omit() # Asegurarnos de quitar NAs para los cálculos

puntajes_choices <- datos_saber %>%
  select(starts_with("punt_")) %>%
  names()

# Cálculos para las tarjetas de KPI
total_estudiantes <- format(nrow(datos_saber), big.mark = ",")
puntaje_global_promedio <- round(mean(datos_saber$punt_global, na.rm = TRUE), 1)
colegios_oficiales <- format(sum(datos_saber$cole_naturaleza == "OFICIAL"), big.mark = ",")
colegios_no_oficiales <- format(sum(datos_saber$cole_naturaleza == "NO OFFICIAL"), big.mark = ",")


#  ---  UI (User Interface)  --- 
ui <- fluidPage(
  theme = bs_theme(bootswatch = "darkly"),
  titlePanel("Dashboard Exploratorio Pruebas Saber 11"),

  # Fila para las tarjetas de KPIs
  fluidRow(
    # Tarjeta 1: Total Estudiantes
    column(3,
           div(class = "kpi-card",
               h3(textOutput("total_estudiantes_kpi")),
               p("Total Estudiantes en la Muestra")
           )
    ),
    # Tarjeta 2: Puntaje Global Promedio
    column(3,
           div(class = "kpi-card",
               h3(textOutput("puntaje_global_kpi")),
               p("Puntaje Global Promedio")
           )
    ),
    # Tarjeta 3: Colegios Oficiales
    column(3,
           div(class = "kpi-card",
               h3(textOutput("colegios_oficiales_kpi")),
               p("Colegios Oficiales")
           )
    ),
    # Tarjeta 4: Colegios No Oficiales
    column(3,
           div(class = "kpi-card",
               h3(textOutput("colegios_no_oficiales_kpi")),
               p("Colegios No Oficiales")
           )
    )
  ),

  hr(), # Una línea horizontal para separar

  sidebarLayout(
    sidebarPanel(
      h3("Filtros del Gráfico"),
      selectInput(
        inputId = "var_x",
        label = "Seleccione Puntaje Eje X:",
        choices = puntajes_choices,
        selected = "punt_matematicas"
      ),
      selectInput(
        inputId = "var_y",
        label = "Seleccione Puntaje Eje Y:",
        choices = puntajes_choices,
        selected = "punt_lectura_critica"
      )
    ),
    mainPanel(
      # Usaremos pestañas para organizar mejor el contenido
      tabsetPanel(
        type = "tabs",
        tabPanel("Gráfico Interactivo",
                 h3(textOutput("titulo_grafico")),
                 plotlyOutput("scatter_plot")
        ),
        tabPanel("Tabla de Datos",
                 h3("Datos Completos"),
                 p("Explora los datos crudos en la siguiente tabla interactiva."),
                 dataTableOutput("tabla_completa")
        )
      )
    )
  ),

  # Estilos CSS para las tarjetas de KPI
  tags$style(HTML("
    .kpi-card {
      background-color: #1e1e1e;
      padding: 20px;
      border-radius: 10px;
      text-align: center;
      border-top: 4px solid #ff9900;
      margin-bottom: 20px;
    }
    .kpi-card h3 {
      font-family: 'Teko', sans-serif;
      font-size: 3rem;
      margin-top: 0;
      color: #ff9900;
    }
    .kpi-card p {
      text-align: center;
      margin-bottom: 0;
      color: #a0a0a0;
      font-size: 1rem;
    }
  "))
)

#  ---  SERVER (Lógica del Servidor)  --- 
server <- function(input, output) {

  # KPIs para las tarjetas
  output$total_estudiantes_kpi <- renderText({ total_estudiantes })
  output$puntaje_global_kpi <- renderText({ puntaje_global_promedio })
  output$colegios_oficiales_kpi <- renderText({ colegios_oficiales })
  output$colegios_no_oficiales_kpi <- renderText({ colegios_no_oficiales })

  # Título dinámico para el gráfico
  output$titulo_grafico <- renderText({
    paste("Relación entre", toupper(gsub("_", " ", input$var_x)), "y", toupper(gsub("_", " ", input$var_y)))
  })

  # Renderizar el gráfico de dispersión interactivo
  output$scatter_plot <- renderPlotly({
    p <- ggplot(datos_saber, aes_string(x = input$var_x, y = input$var_y)) +
      geom_point(aes(color = cole_naturaleza), alpha = 0.6) +
      labs(
        x = "", # Los quitamos porque el título dinámico ya es suficiente
        y = "",
        color = "Naturaleza Colegio"
      ) +
      theme_minimal() +
      theme(
        legend.position = "bottom" # Mover leyenda abajo
      )
    ggplotly(p)
  })

  # Renderizar la tabla de datos completa
  output$tabla_completa <- renderDataTable({
    datos_saber
  })
}

#  ---  Ejecutar la Aplicación  --- 
shinyApp(ui = ui, server = server)

Vuelve a ejecutar la aplicación. ¡Ahora verás un dashboard mucho más informativo y organizado!

Paso 6: Versionar el Resultado

¡Felicidades! Has creado un dashboard interactivo y funcional. Este es un hito importante. Ahora, guarda tu progreso usando el flujo de trabajo de Git.

# Añade los archivos nuevos y modificados
git add .gitignore app.R

# Crea un "punto de guardado" con un mensaje claro y actualizado
git commit -m "feat: Transforma la app en un dashboard con KPIs y pestañas"

# Sube tus cambios al repositorio remoto en GitHub
git push