- 1
- Carga de paquetes (sección 1.1)
- 2
- Interfaz del usuario (sección 1.2)
- 3
- Servidor (sección 1.3)
- 4
- Llamado de la app (sección 1.4)
Lúzcase con Shiny
1 Introducción

Las Shiny apps son elementos web interactivos basados en R que se construyen mediante el paquete shiny.
Si esta descripción no le basta y prefiere ver una de estas apps en acción, ejecute el siguiente código
library(shiny)
runExample("01_hello")Si tras ejecutar este código, le gusta lo que ha visto y desea aprender a construir sus propias apps, usted está en el sitio adecuado. Lo invitamos a seguir leyendo.
En este capítulo se describe la anatomía general de una aplicación Shiny: sus componentes, su comunicación interna y la forma en la que el usuario interactúa con ella. Estos son los elementos arquitectónicos fundamentales de cualquier aplicación Shiny, y comprenderlos permitirá avanzar con mayor fluidez en capítulos siguientes.
Toda aplicación Shiny se estructura alrededor de dos componentes:
Interfaz de usuario (
ui)Servidor (
server)
La comunicación entre estos componentes es la que genera el elemento web con el cual interactúa el usuario:
Al comienzo de los tiempos…
El esquema clásico para desarrollar aplicaciones Shiny se organizaba en dos scripts independientes: uno para la interfaz del usuario —denominadoui.R— y otro para la lógica del servidor —llamadoserver.R.Cuando Shiny maduró…
Los manuales recientes, así como la documentación oficial, recomiendan organizar la aplicación en un único script —llamadoapp.R— que integra ambos componentes.
Aunque la modalidad basada en dos scripts sigue estando soportada por Shiny, este manual desarrolla únicamente la modalidad basada en un script.
Además de los dos componentes centrales, el script de una aplicación Shiny incluye una sección para la carga de paquetes y una llamada final que ejecuta la app.
Una aplicación típica de Shiny tendrá la siguiente estructura:
1.1 Carga de paquetes
En esta primera sección se carga el paquete shiny, que incluye las principales funciones1 empleadas en la aplicación. Cuando la app requiere funciones de otros paquetes, estos suelen cargarse también en este mismo bloque.
bslib! 🚀
En las aplicaciones modernas se destaca el papel de bslib, un paquete que amplía las herramientas de construcción provistas originalmente por shiny y que introduce una forma más flexible y actualizada de gestionar temas, estilos y diseños, apoyándose en versiones recientes de Bootstrap.
Por ello, en aplicaciones contemporáneas es habitual cargar también bslib desde el inicio.
1.2 Interfaz del usuario (ui)
El componente ui define la interfaz del usuario, es decir, todos los elementos con los que este interactúa, tanto para ingresar información (controles de entrada) como para recibir resultados (salidas).
Los controles de entrada permiten que el usuario suministre valores a la app, a partir de interfaces interactivas muy diversas. Entre los más comunes se encuentran:
- Botones (
actionButton) - Cajas de texto (
textInput,numericInput) - Selectores y menús desplegables (
selectInput) - Botones de opción (
radioButtons) - Casillas de verificación (
checkboxInput,checkboxGroupInput) - Barras deslizantes (
sliderInput) - Selectores de fechas (
dateInput,dateRangeInput)
Nótese que muchas de las funciones que se usan para definir los controles de entrada incluyen Input en su nombre.
Mediante funciones específicas, la app puede generar múltiples salidas. Las más habituales incluyen:
- Gráficos (
plotOutput) - Tablas (
tableOutput,dataTableOutput) - Texto (
textOutput,verbatimTextOutput) - Imágenes (
imageOutput) - Elementos html enriquecidos (tablas dinámicas, informes, widgets, etc.)
De forma análoga, todas las funciones que definen salidas incluyen Output en su nombre.
La presentación de los controles de entrada y de las salidas en la interfaz se controla mediante funciones que definen la estructura, la disposición y, en algunos casos, el aspecto visual de la interfaz.
ui o UI? 🖥️
En este libro se usa ui para referirse al bloque de código que define las características de la interfaz del usuario. Por su parte, UI (User interface) se refiere a la interfaz visual con la cual el usuario interactúa.
La estructura se refiere al esqueleto general de la interfaz: las secciones que la componen y su organización jerárquica.
La disposición (layout) define cómo se distribuyen espacialmente los elementos dentro de esa estructura, ya sea en filas, columnas o áreas específicas.
El aspecto visual abarca elementos estéticos como colores, estilos tipográficos y resaltados, y puede controlarse desde funciones especializadas o mediante opciones de temas.
A continuación se listan las principales funciones para controlar la estructura de la UI. Estas funciones definen el esqueleto general de la interfaz, dividiéndola en secciones grandes como barra lateral, contenido principal, pestañas o navegación.
- Estructura general de la página
fluidPage. Estructura base clásica en Shiny. Permite una página fluida con secciones generales.page_fluid. Alternativa moderna equivalente, integrada en el paquetebslib.
- Páginas con barra lateral
page_sidebar. Crea una página con barra lateral y un área principal bien diferenciada.layout_sidebar. Variante más flexible para incrustar una barra lateral dentro de otra estructura.
- Navegación por pestañas o páginas
tabsetPanel. Conjunto de pestañas tradicionales dentro de la página.tabPanel. Contenido asociado a cada pestaña.navbarPage. Barra de navegación superior con múltiples páginas o secciones.nav_panel/nav_pages(en {bslib}). Alternativas modernas y más estilizables.
- Secciones temáticas o tarjetas
card. Crea contenedores temáticos (estilo “tarjetas”) que agrupan contenido relacionado.card_body. Sección interna de la tarjeta.
A continuación se listan las principales funciones para controlar la disposición (layout) de elementos. Estas funciones determinan cómo se distribuyen las entradas, las salidas y el texto dentro de cada sección estructural.
- Distribuciones en filas y columnas
fluidRow. Crea una fila fluida donde se pueden ubicar columnas.column. Ubica contenido en columnas con anchos específicos.
- Distribuciones modernas (paquete
bslib)layout_columns. Distribuye elementos de manera fluida en múltiples columnas, adaptándose al ancho disponible.layout_ncol. Fuerza una disposición en \(n\) columnas uniformes.layout_fill. Hace que un elemento ocupe todo el espacio disponible (útil para tablas o gráficos grandes).
- Distribuciones tipo rejilla
layout_grid. Permite construir rejillas personalizadas con varias filas y columnas, similar a CSS Grid.
- Alineación y agrupación fina
div. Agrupa elementos sin imponer estructura rígida.span. Agrupa elementos en línea.tagList. Permite concatenar varios elementos como una lista de componentes.
1.3 Servidor (server)
Este componente contiene la lógica y los cálculos que se ejecutan detrás de escena para generar las salidas que se muestran en la interfaz.
El server es una función tradicional de R creada con function, que recibe tres argumentos:
input: Lista de valores provenientes del componenteui.output: Lista donde el servidor escribe las salidas.session: Objeto opcional que permite comunicación avanzada entre los componentesserveryui.
server <- function(input, output, session) {
...
}A continuación se presenta un listado por grupos con las funciones más comunes dentro del componente server.
- Funciones de renderizado (
render*). Generan las salidas que serán mostradas en la interfaz.
renderPlot: Genera gráficos.renderTable: Tablas estáticas.renderDataTable. Tablas interactivas (DT).renderText: Texto plano.renderPrint: Salidas de impresión (p. ej.,summary).renderUI: Elementos de UI construidos dinámicamente.
reactive: Construye un objeto reactivo que almacena valores dependientes de otros inputs.observe: Ejecuta un bloque de código cada vez que cambien sus dependencias, pero no devuelve un valor.observeEvent: Variante deobserveque se activa solo cuando ocurre un evento específico, como presionar un botón.eventReactive: Construye un objeto reactivo que solo se actualiza únicamente cuando ocurre un evento; a diferencia deobserveEvent, sí produce un valor utilizable en otras expresiones reactivas.isolate: Permite leer un input sin crear una dependencia reactiva.req: Suspende la ejecución de una expresión reactiva hasta que se cumpla una condición.Funciones de actualización (
update*). Se usan para modificar inputs desde el servidor.
updateTextInputupdateSliderInputupdateSelectInputupdateNumericInput
1.4 Llamado de la app
La última instrucción de toda aplicación Shiny consiste en invocarla mediante la función shinyApp, con argumentos ui y server. Esta instrucción crea una app que vincula sus dos componentes centrales y la ejecuta.
shinyApp(ui, server)Desde una perspectiva global, la ui actúa como un intermediario entre el usuario y el server: construye una interfaz limpia y amigable a través de la cual el usuario ingresa datos y recibe resultados, sin tener que preocuparse por paquetes, funciones, argumentos, tipos o clases de objetos.
La ui crea controles de entrada en la interfaz (como cajas de texto, casillas de verificación o selectores), a través de los cuales el usuario introduce datos brutos. Asimismo, la ui se encarga de tramitar con el server todo lo necesario para que el usuario reciba los correspondientes resultados.
Toda aplicación Shiny incluye dos objetos contenedores de información:
input. Contiene los insumos utilizados por los procesos que se realizan en elserver.output. Contiene las salidas generadas en elservery mostradas en la interfaz con mediación de laui.
La figura 1.1 ilustra el rol de los diferentes componentes y contenedores.
ui, el server, el input y el output en una aplicación Shiny.
Los nombres de los objetos que relacionan los dos componentes de una aplicación Shiny se refieren a sus roles desde el punto de vista del componente server:
Los
inputson las entradas delserver.Los
outputson las salidas delserver.
Los objetos input y output no son de la clase list: el objeto input pertenece a la clase reactivevalues, mientras que output pertenece a la clase shinyoutput. Estas clases especiales determinan su papel dentro del sistema reactivo de Shiny.
Sin embargo, ambos son contenedores tipo lista (typeof devuelve "list"). Esto significa que su estructura interna es la misma que la de una lista ordinaria, lo que permite consultar sus elementos mediante el descriptor $.
En Shiny, cada vez que un usuario abre la aplicación, se inicia una sesión, que permanece activa mientras la interfaz esté abierta en el navegador.
El comportamiento de la app dentro de una sesión se rige por un motor reactivo, encargado de que ciertos valores se actualicen automáticamente cuando sus entradas asociadas se modifican.
Cuando el usuario interactúa con un control de entrada (barra deslizante, botón, casilla, etc.), el objeto input reacciona y actualiza sus valores de manera automática.
Las funciones de renderizado del componente server (renderPlot, renderText, etc.) también son reactivas: cuando cambian sus dependencias en el input se reejecutan y escriben un nuevo valor en el output.
Las funciones de salida de la ui (plotOutput, textOutput, etc.) son igualmente reactivas: cuando el output cambia, actualizan las correspondientes salidas en la UI.
La figura 1.2 ilustra esta lógica. Las cajas iluminadas representan los elementos reactivos del sistema.
Cuando el usuario cambia un valor de entrada, Shiny detecta la modificación y, de inmediato vuelve a calcular todo lo que depende de ese valor y actualiza las salidas correspondientes, sin que sea necesario abrir nuevamente la app ni realizar ninguna reejecución manual.
El objeto reactivo input hace parte del sistema Shiny, no siendo necesario —ni posible— definirlo explícitamente dentro de los componentes de la aplicación. Cada función de entrada del componente ui crea internamente un slot dentro del input. Lo único que hace el desarrollador es asignar el nombre de dicho slot. Por ejemplo, la instrucción sliderInput(inputId = "edad", ...) genera el slot input$edad, mediante el cual es posible acceder al valor ingresado por el usuario a través de la barra deslizante.
El hecho de que ni el usuario ni el desarrollador escriban directamente ni el input ni el output conduce inevitablemente a la pregunta sobre la forma en la que estos objetos se actualizan.
El input se actualiza reactivamente a partir de la interacción del usuario con los controles de entrada, sin intervención de las funciones usadas para crear tales controles (numericInput, dateInput o sliderInput, etc.). Aunque las funciones de entrada definen slots en el input, no intervienen en su actualización.
El output se actualiza desde el componente server, mediante funciones como renderPlot, renderTable o renderPrint.
La comunicación entre la ui y el server, es decir, el proceso reactivo interno que se desencadena una vez el usuario ingresa información, se establece a través del input y el output.
El usuario —el piloto de la app— interactúa con los controles de entrada de la interfaz, que se crean en la ui mediante funciones como actionButton (botones), sliderInput (barras deslizantes), dateInput (selectores de fecha), etc.
Cada vez que el usuario modifica uno de estos controles, se actualiza automáticamente el input. Seguidamente, alguna función de renderizado en el server procesa esta información y genera un nuevo output, el cual es utilizado por las funciones de salida de la ui para actualizar las salidas visibles en la interfaz (cf. figura 1.1).
Finalmente vale la pena resaltar la naturaleza de los diferentes elementos que intervienen en el proceso:
Controles de entrada. Capturan información bruta: texto, valores numéricos, archivos, clics, etc.
input. Es un contendedor reactivo de información organizada, que puede ser procesada directamente por R (vectores, data frames, etc.). Esta es la información de entrada de las funciones de renderizado delserver.output. Es un contendedor (no reactivo) de objetos que representan resultados ya procesados: la estructura de un gráfico, una tabla, un fragmento de texto, etc. Allí se almacena la información necesaria para que alguna función de lauipueda mostrar la correspondiente salida.Salidas. Son las representaciones visibles en la interfaz: gráficos, tablas, texto, etc. Se construyen en la
uia partir de la información almacenada en eloutput.
Todo el procesamiento (cálculos, generación de gráficos, tablas, resúmenes, etc.) ocurre en el server.
Cuando se genera un gráfico con renderPlot, la expresión asociada se ejecuta en R —mediante plot, ggplot2 u otras funciones de graficación—, se abre un dispositivo gráfico y se produce la imagen final.
Esa imagen es escrita por renderPlot en el output, lo que desencadena el proceso reactivo de una función complementaria como plotOutput, que —sin recalcular la imagen ni reprocesar los datos— se encarga de mostrarla en la interfaz.
1.5 Shiny en acción
Ejemplo 1.1 Un saludo minimalista
Esta app invita al usuario a ingresar su nombre y genera un saludo personalizado.
1library(shiny)
2# ui (User Interface) -------
ui <- fluidPage(
textInput(inputId = "nombre", label = "Ingresa tu nombre"),
textOutput("saludo")
)
3# server -------
server <- function(input, output) {
output$saludo <- renderText({
paste("Hola,", input$nombre)
})
}
4shinyApp(ui, server)- 1
- Carga de paquetes
- 2
-
ui - 3
-
server - 4
- Llamado de la app
Por la sencillez de esta app, basta con cargar el paquete shiny. Las funciones fluidPage, textInput, textOutput, renderText y shinyApp están incluidas en este paquete. Por su parte, las funciones function y paste forman parte del paquete base.
Todo componente ui se construye mediante una función que define la estructura base o esqueleto general de la interfaz (en este caso, fluidPage). Esta función actúa como contenedor de las demás funciones en la ui y crea la página de la interfaz en la que se muestran los controles de entrada y las salidas. En el capítulo 2 se detalla lo concerniente a esta y otras funciones usadas para este fin.
Asimismo, la ui de toda app funcional diseñada para reaccionar a las entradas del usuario debe incluir al menos una función asociada con las entradas y otra con las salidas, siendo admisibles múltiples entradas y salidas. Estas funciones son los argumentos principales —y, en este caso, los únicos— de la función que define la estructura base.
De manera simplificada, la ui del presente ejemplo está organizada así:
ui <- fluidPage(textInput(...), textOutput(...))La función textInput genera una caja para captura de texto. La función textOutput muestra un resultado en formato texto. Todo esto aparece en la interfaz del usuario. Esta es la razón por la que a este componente se le denomina ui.
El valor del argumento inputId de la función textInput es el que se utiliza como slot del input: en este ejemplo, "nombre". Cuando el usuario escribe texto —su nombre— en la caja de entrada, el valor de input$nombre se actualiza automáticamente, dando lugar a una reacción en cadena en la que el siguiente paso es la respuesta reactiva de renderText en el componente server.
La función renderText controla el mecanismo reactivo. La función que le sirve como argumento —paste en este ejemplo— es la que controla el procesamiento del texto. Mediante la instrucción paste("Hola,", input$nombre), se une la cadena de texto "Hola, " al valor de input$nombre, para generar un saludo personalizado.
La función renderText se encarga de copiar ese saludo personalizado en el slot saludo de output (output$saludo).
saludo!? 🤝
El slot saludo de output se define mediante la asignación output$saludo <- renderText(...).
En contraste, los slots en el componente ui se definen a partir del argumento inputId de la función asociada con los controles de entrada.
Una vez el output actualiza su contenido, la función textOutput se ejecuta reactivamente y despliega el contenido del output$saludo en la interfaz: el saludo personalizado.
A continuación se resume el flujo de información:
- El usuario escribe “Luzma” en la caja de captura de texto.
- El control creado por
textInputactualiza el valor"Luzma"eninput$nombre. - La función
renderTextreacciona al cambio eninput$nombre, realiza un llamado a la funciónpastey guarda el resultado —“Hola, Luzma”— enoutput$saludo. - La función
textOutputactúa reactivamente a la actualización en el valor deoutput$saludoy muestra su contenido en la interfaz: Hola, Luzma.
Siendo este un manual técnico, no vamos a discutir a fondo el valor de un saludo personalizado. Nos limitaremos a decir que puede ser un bálsamo reconfortante en un momento de necesidad. Desde luego, sería preferible un abrazo, pero a la fecha no tenemos conocimiento de la implementación del paquete hugs.
Lo que sí vamos a hacer es contrastar la aplicación Shiny usada para generar el saludo personalizado con un script mucho más sencillo como el del código 1.1, que en esencia haría lo mismo:
saludo <- function(nombre) {
cat("Hola,", nombre)
}
saludo("Luzma")En términos de sencillez, el código 1.1 es definitivamente preferible a la aplicación Shiny. Y aunque produce exactamente el mismo resultado, exigiría que alguien necesitado de un saludo tuviera R instalado2, que entendiera la lógica de las funciones, de manera que pudiera cargar la función saludo en memoria y que supiera exactamente cómo invocarla e ingresar sus argumentos (sin olvidar las comillas al ingresar cadenas de caracteres).
En contraste, un usuario que desee recibir un saludo a través de la aplicación Shiny no necesita saber nada de R ni de la lógica de sus funciones y sus argumentos. ¡Ni siquiera necesita tener instalado R! Todo lo que necesita hacer es ingresar su nombre en la casilla de captura de texto.
Las aplicaciones Shiny están diseñadas para realizar una tarea a partir de los insumos en bruto proporcionados por el usuario, encargándose internamente de formatearlos adecuadamente y de entregar el resultado.
En desarrollo…
SOBRE EL AUTOR
Guillermo Correa-Londoño es un Ingeniero Forestal que obtuvo su título de Maestría en Estadística en el Colegio de Postgraduados (Texcoco, México) y de doctorado en Estadística Multivariante Aplicada en la Universidad de Salamanca (Salamanca, España). Actualmente es Profesor Titular, adscrito al Departamento de Ciencias Agronómicas de la Facultad de Ciencias Agrarias de la Universidad Nacional de Colombia. Su ámbito de desempeño está relacionado principalmente con investigación aplicada en ciencias agrarias y biotecnología.
email: gcorrea@unal.edu.co
CvLAC
ORC
Otros textos del mismo autor
Índice de Condición Integral. Se desarrolla una metodología que desglosa los diferentes procesos involucrados en la construcción de un Índice de Condición Integral, que permite realizar valoraciones a partir de variables complejas en diversos ámbitos. La propuesta está acompañada de un paquete para R.
R paso a paso. Este manual introduce al neófito en el fascinante mundo de R y sus posibilidades, iniciando desde cero, hasta llevarlo a un punto en el que programe sus propias funciones personalizadas.
Quarto. Se presenta este entorno de trabajo que facilita estructurar de manera elegante un documento que reúne las salidas de R u otros lenguajes, con el código que las genera y los comentarios e interpretaciones redactados por el usuario.
A veces las únicas.↩︎
Si no fuera así, podría encontrar las instrucciones en R paso a paso↩︎