CODER SALE 💸

Aprovecha 50% OFF y hasta 12 cuotas sin interés en CURSOS y CARRERAS

|

Hasta el 09/09 ⏰

CODER SALE 💸

Aprovecha 50% OFF y hasta 12 cuotas sin interés en CURSOS y CARRERAS

|

Hasta el 09/09 ⏰

Hasta el 09/09 ⏰

CODER SALE 💸

Aprovecha 50% OFF y hasta 12 cuotas sin interés en CURSOS y CARRERAS

Tutorial: cómo construir tu primer dashboard interactivo con Python y Streamlit paso a paso

Tutoriales gratuitos

Descargartutoriales gratuitos

Tutoriales y videos prácticos gratuitos para incorporar nuevas herramientas de IA y empleabilidad.

Ver los tutoriales

Dan Patiño

AI Strategy & Innovation en Coderhouse

Data

Tutorial: cómo construir tu primer dashboard interactivo con Python y Streamlit paso a paso

Publicado el

Streamlit convierte un script de Python en una aplicación web con filtros, gráficos y tablas sin escribir una línea de HTML ni de JavaScript. Este tutorial va de cero a un dashboard interactivo desplegado y accesible por link, en un recorrido que podés completar en una tarde si ya manejás Python básico.

La razón para aprenderlo es concreta: un análisis en un notebook lo puede leer otro analista; un dashboard con un link lo puede usar cualquiera. Para mostrar proyectos en un portafolio, esa diferencia decide si el recruiter ve tu trabajo o cierra la pestaña.

Qué necesitás antes de empezar

  • Python 3.9 o superior instalado.

  • Nociones básicas de pandas: leer un CSV, filtrar filas, agrupar.

  • Una cuenta de GitHub si querés desplegarlo al final.

  • Un dataset propio en CSV. Si no tenés, cualquier export de ventas, gastos o métricas sirve.

No necesitás saber desarrollo web. Ese es el punto de Streamlit.

Paso 1: preparar el entorno

Creá una carpeta para el proyecto y dentro de ella un entorno virtual. Trabajar con entorno virtual evita que las dependencias de este proyecto choquen con las de otros.

mkdir mi-dashboard
cd mi-dashboard
python -m venv venv
source venv/bin/activate     # en Windows: venv\Scripts\activate
pip install streamlit pandas plotly
mkdir mi-dashboard
cd mi-dashboard
python -m venv venv
source venv/bin/activate     # en Windows: venv\Scripts\activate
pip install streamlit pandas plotly
mkdir mi-dashboard
cd mi-dashboard
python -m venv venv
source venv/bin/activate     # en Windows: venv\Scripts\activate
pip install streamlit pandas plotly
mkdir mi-dashboard
cd mi-dashboard
python -m venv venv
source venv/bin/activate     # en Windows: venv\Scripts\activate
pip install streamlit pandas plotly

Verificá que quedó bien instalado:

streamlit hello
streamlit hello
streamlit hello
streamlit hello

Se abre el navegador con la app de demostración. Cerrala con Ctrl+C en la terminal.

Paso 2: el primer script

Creá un archivo app.py con lo mínimo:

import streamlit as st
import pandas as pd

st.set_page_config(page_title="Mi dashboard", layout="wide")

st.title("Dashboard de ventas")
st.write("Primera version del tablero.")
import streamlit as st
import pandas as pd

st.set_page_config(page_title="Mi dashboard", layout="wide")

st.title("Dashboard de ventas")
st.write("Primera version del tablero.")
import streamlit as st
import pandas as pd

st.set_page_config(page_title="Mi dashboard", layout="wide")

st.title("Dashboard de ventas")
st.write("Primera version del tablero.")
import streamlit as st
import pandas as pd

st.set_page_config(page_title="Mi dashboard", layout="wide")

st.title("Dashboard de ventas")
st.write("Primera version del tablero.")

Corrélo con:

streamlit run app.py
streamlit run app.py
streamlit run app.py
streamlit run app.py

Cada vez que guardes el archivo, Streamlit detecta el cambio y ofrece recargar. Ese ciclo — editar, guardar, ver — es lo que hace que el desarrollo sea rápido.

El modelo mental clave

Streamlit ejecuta tu script completo, de arriba abajo, cada vez que el usuario interactúa con cualquier control. No hay callbacks ni estado implícito. Si movés un filtro, todo el script vuelve a correr con el valor nuevo. Entender esto ahorra horas de confusión más adelante.

Paso 3: cargar los datos con caché

Si el script corre entero en cada interacción, leer el CSV cada vez sería lentísimo. Para eso está el decorador de caché:

@st.cache_data
def cargar_datos(ruta):
    df = pd.read_csv(ruta, parse_dates=["fecha"])
    df["mes"] = df["fecha"].dt.to_period("M").astype(str)
    return df

df = cargar_datos("datos/ventas.csv")
@st.cache_data
def cargar_datos(ruta):
    df = pd.read_csv(ruta, parse_dates=["fecha"])
    df["mes"] = df["fecha"].dt.to_period("M").astype(str)
    return df

df = cargar_datos("datos/ventas.csv")
@st.cache_data
def cargar_datos(ruta):
    df = pd.read_csv(ruta, parse_dates=["fecha"])
    df["mes"] = df["fecha"].dt.to_period("M").astype(str)
    return df

df = cargar_datos("datos/ventas.csv")
@st.cache_data
def cargar_datos(ruta):
    df = pd.read_csv(ruta, parse_dates=["fecha"])
    df["mes"] = df["fecha"].dt.to_period("M").astype(str)
    return df

df = cargar_datos("datos/ventas.csv")

Con st.cache_data, la función se ejecuta una sola vez por combinación de argumentos y el resultado queda guardado. Es la optimización más importante de cualquier app de Streamlit y la que más se olvida.

Si tus datos vienen de una base y no de un CSV, la lógica es la misma: envolvé la consulta en una función cacheada. Para el caso de warehouses en la nube, este recorrido de BigQuery para analistas de datos explica cómo estructurar las consultas antes de traerlas a Python.

Paso 4: agregar filtros en la barra lateral

Los filtros van en la sidebar para que no compitan con las visualizaciones:

st.sidebar.header("Filtros")

categorias = st.sidebar.multiselect(
    "Categoria",
    options=sorted(df["categoria"].unique()),
    default=sorted(df["categoria"].unique())
)

rango = st.sidebar.date_input(
    "Rango de fechas",
    value=(df["fecha"].min(), df["fecha"].max())
)

df_f = df[
    (df["categoria"].isin(categorias)) &
    (df["fecha"] >= pd.Timestamp(rango[0])) &
    (df["fecha"] <= pd.Timestamp(rango[1]))
]
st.sidebar.header("Filtros")

categorias = st.sidebar.multiselect(
    "Categoria",
    options=sorted(df["categoria"].unique()),
    default=sorted(df["categoria"].unique())
)

rango = st.sidebar.date_input(
    "Rango de fechas",
    value=(df["fecha"].min(), df["fecha"].max())
)

df_f = df[
    (df["categoria"].isin(categorias)) &
    (df["fecha"] >= pd.Timestamp(rango[0])) &
    (df["fecha"] <= pd.Timestamp(rango[1]))
]
st.sidebar.header("Filtros")

categorias = st.sidebar.multiselect(
    "Categoria",
    options=sorted(df["categoria"].unique()),
    default=sorted(df["categoria"].unique())
)

rango = st.sidebar.date_input(
    "Rango de fechas",
    value=(df["fecha"].min(), df["fecha"].max())
)

df_f = df[
    (df["categoria"].isin(categorias)) &
    (df["fecha"] >= pd.Timestamp(rango[0])) &
    (df["fecha"] <= pd.Timestamp(rango[1]))
]
st.sidebar.header("Filtros")

categorias = st.sidebar.multiselect(
    "Categoria",
    options=sorted(df["categoria"].unique()),
    default=sorted(df["categoria"].unique())
)

rango = st.sidebar.date_input(
    "Rango de fechas",
    value=(df["fecha"].min(), df["fecha"].max())
)

df_f = df[
    (df["categoria"].isin(categorias)) &
    (df["fecha"] >= pd.Timestamp(rango[0])) &
    (df["fecha"] <= pd.Timestamp(rango[1]))
]

El patrón es siempre el mismo: cada control devuelve un valor, y con esos valores filtrás el DataFrame. Todo lo que venga después trabaja sobre df_f, no sobre df.

Controles disponibles

Control

Cuándo usarlo

st.selectbox

Una sola opción entre varias

st.multiselect

Varias opciones simultáneas

st.slider

Rangos numéricos continuos

st.date_input

Fechas o rangos de fechas

st.radio

Pocas opciones excluyentes y visibles

st.text_input

Búsqueda libre por texto

Paso 5: métricas principales

Antes de los gráficos, los números que resumen todo. Se muestran en columnas:

col1, col2, col3 = st.columns(3)

col1.metric("Ventas totales", f"${df_f['monto'].sum():,.0f}")
col2.metric("Transacciones", f"{len(df_f):,}")
col3.metric("Ticket promedio", f"${df_f['monto'].mean():,.0f}")
col1, col2, col3 = st.columns(3)

col1.metric("Ventas totales", f"${df_f['monto'].sum():,.0f}")
col2.metric("Transacciones", f"{len(df_f):,}")
col3.metric("Ticket promedio", f"${df_f['monto'].mean():,.0f}")
col1, col2, col3 = st.columns(3)

col1.metric("Ventas totales", f"${df_f['monto'].sum():,.0f}")
col2.metric("Transacciones", f"{len(df_f):,}")
col3.metric("Ticket promedio", f"${df_f['monto'].mean():,.0f}")
col1, col2, col3 = st.columns(3)

col1.metric("Ventas totales", f"${df_f['monto'].sum():,.0f}")
col2.metric("Transacciones", f"{len(df_f):,}")
col3.metric("Ticket promedio", f"${df_f['monto'].mean():,.0f}")

st.metric acepta un tercer argumento, delta, para mostrar la variación respecto de un período anterior. Es el detalle que convierte un tablero descriptivo en uno útil para decidir.

Paso 6: los gráficos

Plotly da interactividad — zoom, hover, ocultar series — con muy poco código. La API de alto nivel que vamos a usar está documentada en la referencia oficial de Plotly Express, que conviene tener abierta al lado mientras armás los gráficos:

import plotly.express as px

serie = df_f.groupby("mes", as_index=False)["monto"].sum()

fig = px.line(serie, x="mes", y="monto",
              title="Evolucion mensual de ventas",
              markers=True)
st.plotly_chart(fig, use_container_width=True)

por_cat = df_f.groupby("categoria", as_index=False)["monto"].sum()
fig2 = px.bar(por_cat.sort_values("monto"), x="monto", y="categoria",
              orientation="h", title="Ventas por categoria")
st.plotly_chart(fig2, use_container_width=True)
import plotly.express as px

serie = df_f.groupby("mes", as_index=False)["monto"].sum()

fig = px.line(serie, x="mes", y="monto",
              title="Evolucion mensual de ventas",
              markers=True)
st.plotly_chart(fig, use_container_width=True)

por_cat = df_f.groupby("categoria", as_index=False)["monto"].sum()
fig2 = px.bar(por_cat.sort_values("monto"), x="monto", y="categoria",
              orientation="h", title="Ventas por categoria")
st.plotly_chart(fig2, use_container_width=True)
import plotly.express as px

serie = df_f.groupby("mes", as_index=False)["monto"].sum()

fig = px.line(serie, x="mes", y="monto",
              title="Evolucion mensual de ventas",
              markers=True)
st.plotly_chart(fig, use_container_width=True)

por_cat = df_f.groupby("categoria", as_index=False)["monto"].sum()
fig2 = px.bar(por_cat.sort_values("monto"), x="monto", y="categoria",
              orientation="h", title="Ventas por categoria")
st.plotly_chart(fig2, use_container_width=True)
import plotly.express as px

serie = df_f.groupby("mes", as_index=False)["monto"].sum()

fig = px.line(serie, x="mes", y="monto",
              title="Evolucion mensual de ventas",
              markers=True)
st.plotly_chart(fig, use_container_width=True)

por_cat = df_f.groupby("categoria", as_index=False)["monto"].sum()
fig2 = px.bar(por_cat.sort_values("monto"), x="monto", y="categoria",
              orientation="h", title="Ventas por categoria")
st.plotly_chart(fig2, use_container_width=True)

Siempre use_container_width=True: hace que el gráfico se adapte al ancho disponible y evita que se corte en pantallas chicas.

Qué gráfico para qué pregunta

  • Líneas para evolución en el tiempo.

  • Barras horizontales para comparar categorías, especialmente si los nombres son largos.

  • Dispersión para relación entre dos variables numéricas.

  • Mapa de calor para cruces de dos dimensiones categóricas.

Evitá los gráficos de torta con más de tres categorías: son difíciles de comparar visualmente. Si querés profundizar en criterios de diseño de tableros, el enfoque de Looker Studio desde cero aplica igual acá, aunque cambie la herramienta.

Paso 7: tabla y descarga

Cerrá el tablero con el detalle y la posibilidad de exportarlo:

st.subheader("Detalle")
st.dataframe(df_f, use_container_width=True, hide_index=True)

st.download_button(
    "Descargar CSV filtrado",
    data=df_f.to_csv(index=False).encode("utf-8"),
    file_name="datos_filtrados.csv",
    mime="text/csv"
)
st.subheader("Detalle")
st.dataframe(df_f, use_container_width=True, hide_index=True)

st.download_button(
    "Descargar CSV filtrado",
    data=df_f.to_csv(index=False).encode("utf-8"),
    file_name="datos_filtrados.csv",
    mime="text/csv"
)
st.subheader("Detalle")
st.dataframe(df_f, use_container_width=True, hide_index=True)

st.download_button(
    "Descargar CSV filtrado",
    data=df_f.to_csv(index=False).encode("utf-8"),
    file_name="datos_filtrados.csv",
    mime="text/csv"
)
st.subheader("Detalle")
st.dataframe(df_f, use_container_width=True, hide_index=True)

st.download_button(
    "Descargar CSV filtrado",
    data=df_f.to_csv(index=False).encode("utf-8"),
    file_name="datos_filtrados.csv",
    mime="text/csv"
)

El botón de descarga es el detalle que más agradecen los usuarios de negocio: les permite seguir trabajando en su planilla con los datos ya filtrados.

Paso 8: organizar con pestañas

Cuando el tablero crece, las pestañas evitan el scroll infinito:

tab1, tab2, tab3 = st.tabs(["Resumen", "Tendencias", "Detalle"])

with tab1:
    st.plotly_chart(fig2, use_container_width=True)

with tab2:
    st.plotly_chart(fig, use_container_width=True)

with tab3:
    st.dataframe(df_f, use_container_width=True)
tab1, tab2, tab3 = st.tabs(["Resumen", "Tendencias", "Detalle"])

with tab1:
    st.plotly_chart(fig2, use_container_width=True)

with tab2:
    st.plotly_chart(fig, use_container_width=True)

with tab3:
    st.dataframe(df_f, use_container_width=True)
tab1, tab2, tab3 = st.tabs(["Resumen", "Tendencias", "Detalle"])

with tab1:
    st.plotly_chart(fig2, use_container_width=True)

with tab2:
    st.plotly_chart(fig, use_container_width=True)

with tab3:
    st.dataframe(df_f, use_container_width=True)
tab1, tab2, tab3 = st.tabs(["Resumen", "Tendencias", "Detalle"])

with tab1:
    st.plotly_chart(fig2, use_container_width=True)

with tab2:
    st.plotly_chart(fig, use_container_width=True)

with tab3:
    st.dataframe(df_f, use_container_width=True)

Paso 9: desplegarlo gratis

Streamlit Community Cloud permite publicar la app sin costo desde un repositorio de GitHub. Los pasos:

  1. Congelá las dependencias. Creá un archivo requirements.txt con las librerías y sus versiones: pip freeze > requirements.txt. Revisalo y dejá solo lo que la app usa.

  2. Subí el repo a GitHub con app.py, requirements.txt y los datos (si son livianos y no son sensibles).

  3. Conectá tu cuenta en Streamlit Community Cloud y autorizá el acceso al repositorio.

  4. Indicá repo, rama y ruta del archivo y confirmá el despliegue. La documentación oficial del proceso está en la guía de deploy de Streamlit.

Nunca subas credenciales al repositorio. Si la app se conecta a una base de datos, usá el sistema de secrets de la plataforma y leelos con st.secrets.

Errores frecuentes y cómo resolverlos

  • La app se recarga muy lento. Falta caché en la carga de datos o en las transformaciones pesadas. Agregá st.cache_data.

  • Los filtros no afectan los gráficos. Estás graficando sobre el DataFrame original en lugar del filtrado.

  • Un valor se pierde al interactuar. Necesitás st.session_state para persistir estado entre ejecuciones.

  • Funciona local pero falla desplegado. Casi siempre es una dependencia faltante en requirements.txt o una ruta de archivo absoluta. Usá rutas relativas.

  • El CSV es demasiado grande. Convertilo a Parquet o filtrá los datos antes de traerlos. Vale revisar la comparativa entre pandas, Polars y DuckDB si el volumen es el cuello de botella.

Qué agregar después

  1. Comparación de períodos con deltas en las métricas.

  2. Un formulario con st.form para agrupar filtros y evitar recargas en cada cambio.

  3. Conexión a base de datos en lugar de CSV estático.

  4. Un resumen automático generado por un modelo de lenguaje sobre los datos filtrados.

Si todavía estás afianzando la base de manipulación de datos antes de sumar capas, esta guía paso a paso de Python para análisis de datos cubre bien pandas desde cero.

Cursos recomendados de Coderhouse

Preguntas frecuentes

¿Streamlit sirve para dashboards de producción o solo para prototipos?

Sirve para producción en escala chica y mediana: tableros internos, herramientas para un equipo, demos de modelos. Para aplicaciones con miles de usuarios simultáneos o control de permisos granular conviene un framework web tradicional. Para el 90% de los casos de un equipo de datos, Streamlit alcanza.

¿Necesito saber HTML o JavaScript?

No. Streamlit genera la interfaz a partir del script de Python. Podés inyectar HTML o CSS con st.markdown si querés personalizar el estilo, pero es completamente opcional y no hace falta para un tablero funcional.

¿Cuánto cuesta desplegar una app de Streamlit?

Streamlit Community Cloud tiene un plan gratuito que alcanza para proyectos personales y de portafolio, con límites de recursos y de cantidad de apps privadas. Si necesitás más capacidad o control, se puede desplegar en cualquier servicio que corra contenedores.

¿Streamlit o Power BI?

Depende de quién mantiene el tablero. Power BI es mejor para usuarios de negocio que necesitan armar reportes sin programar y para gobierno de datos en empresas grandes. Streamlit gana cuando el análisis requiere lógica en Python — un modelo, un cálculo custom, una simulación — que una herramienta de BI no puede expresar.

¿Cómo hago que la app cargue más rápido?

Tres medidas, en orden de impacto: cachear la carga de datos con st.cache_data, filtrar en la fuente en lugar de traer todo a memoria, y usar formatos columnares como Parquet en lugar de CSV. Con esas tres, la mayoría de los tableros pasa de segundos a milisegundos por interacción.

Sobre el autor

Dan Patiño

Soy Dan Patiño, responsable de AI Strategy & Innovation en Coderhouse. Mi día a día consiste en fusionar la gestión táctica del e-commerce (CRO, Email Marketing y SEO) con el desarrollo de soluciones disruptivas. Me especializo en crear apps internas con IA para automatizar tareas y potenciar la innovación dentro del equipo. Creo fielmente que la tecnología es el mejor aliado de la estrategia. Para profundizar en mi recorrido profesional, te espero en mi perfil de LinkedIn.

Imagen promocionando quiz gratis de Coderhouse para encontrar tu formación.
Imagen promocionando quiz gratis de Coderhouse para encontrar tu formación.
Imagen promocionando quiz gratis de Coderhouse para encontrar tu formación.
Imagen promocionando quiz gratis de Coderhouse para encontrar tu formación.