Gestión de estado y sesión

Intermedio
Streamlit
Streamlit
Actualizado: 03/09/2025

Uso de st.session_state para mantener datos

Las aplicaciones web son inherentemente sin estado, lo que significa que cada vez que un usuario interactúa con la interfaz, la aplicación se reinicia completamente. En Streamlit, esto presenta un desafío cuando necesitamos preservar información entre las diferentes ejecuciones del script.

El objeto st.session_state es la solución nativa de Streamlit para mantener datos persistentes durante toda la sesión del usuario. Funciona como un diccionario Python que conserva sus valores mientras el usuario mantenga abierta la pestaña del navegador.

Inicialización básica de variables de estado

La forma más común de trabajar con st.session_state es inicializar variables al comienzo de la aplicación para evitar errores cuando se accede a ellas por primera vez:

import streamlit as st

# Inicializar el contador si no existe
if 'contador' not in st.session_state:
    st.session_state.contador = 0

# Mostrar el valor actual
st.write(f"Valor actual del contador: {st.session_state.contador}")

Esta técnica es fundamental porque garantiza que la variable exista antes de intentar usarla, evitando errores de clave no encontrada.

Operaciones básicas con session_state

Puedes realizar todas las operaciones típicas de un diccionario Python con st.session_state:

# Asignación directa
st.session_state.mi_variable = "Hola mundo"

# Verificar existencia
if 'mi_lista' not in st.session_state:
    st.session_state.mi_lista = []

# Añadir elementos a una lista
st.session_state.mi_lista.append("nuevo elemento")

# Acceso mediante notación de diccionario
st.session_state['usuario_activo'] = True

# Eliminar una clave
if 'variable_temporal' in st.session_state:
    del st.session_state.variable_temporal

Creación de un contador interactivo

Un ejemplo práctico para entender el comportamiento de st.session_state es implementar un contador que mantenga su valor entre interacciones:

import streamlit as st

# Inicialización del contador
if 'contador' not in st.session_state:
    st.session_state.contador = 0

st.title("Contador Persistente")

# Mostrar el valor actual
st.metric("Contador", st.session_state.contador)

# Crear columnas para los botones
col1, col2, col3 = st.columns(3)

with col1:
    if st.button("➕ Incrementar"):
        st.session_state.contador += 1

with col2:
    if st.button("➖ Decrementar"):
        st.session_state.contador -= 1

with col3:
    if st.button("🔄 Reiniciar"):
        st.session_state.contador = 0

Cada vez que el usuario pulsa un botón, el valor se actualiza y se mantiene visible hasta que realice otra acción.

Almacenamiento de datos complejos

st.session_state puede almacenar cualquier tipo de dato Python, incluyendo estructuras complejas como listas, diccionarios o incluso objetos personalizados:

# Inicializar una lista de tareas
if 'tareas' not in st.session_state:
    st.session_state.tareas = []

# Inicializar configuración de usuario
if 'configuracion' not in st.session_state:
    st.session_state.configuracion = {
        'tema': 'claro',
        'idioma': 'es',
        'notificaciones': True
    }

st.title("Gestión de Tareas")

# Añadir nueva tarea
nueva_tarea = st.text_input("Nueva tarea:")
if st.button("Añadir") and nueva_tarea:
    st.session_state.tareas.append({
        'texto': nueva_tarea,
        'completada': False
    })

# Mostrar tareas existentes
if st.session_state.tareas:
    st.subheader("Lista de Tareas")
    for i, tarea in enumerate(st.session_state.tareas):
        col1, col2 = st.columns([3, 1])
        with col1:
            st.write(f"• {tarea['texto']}")
        with col2:
            if st.button("❌", key=f"eliminar_{i}"):
                st.session_state.tareas.pop(i)
                st.rerun()

Gestión de estados de usuario

Una aplicación práctica común es mantener información sobre el estado de autenticación o configuración del usuario:

# Inicializar estado de autenticación
if 'usuario_logueado' not in st.session_state:
    st.session_state.usuario_logueado = False
if 'nombre_usuario' not in st.session_state:
    st.session_state.nombre_usuario = ""

st.title("Sistema de Login Simulado")

if not st.session_state.usuario_logueado:
    # Pantalla de login
    st.subheader("Iniciar Sesión")
    nombre = st.text_input("Nombre de usuario:")
    
    if st.button("Entrar") and nombre:
        st.session_state.usuario_logueado = True
        st.session_state.nombre_usuario = nombre
        st.rerun()
else:
    # Pantalla principal
    st.success(f"¡Bienvenido, {st.session_state.nombre_usuario}!")
    
    st.write("Contenido exclusivo para usuarios registrados:")
    st.info("Aquí aparecería el dashboard principal de la aplicación")
    
    if st.button("Cerrar Sesión"):
        st.session_state.usuario_logueado = False
        st.session_state.nombre_usuario = ""
        st.rerun()

Depuración del estado de sesión

Para monitorizar y depurar el contenido de st.session_state durante el desarrollo, puedes crear un panel de información que muestre todas las variables almacenadas:

# Panel de depuración (solo para desarrollo)
with st.expander("🔍 Debug: Contenido de session_state"):
    if st.session_state:
        for clave, valor in st.session_state.items():
            st.write(f"**{clave}**: {valor}")
    else:
        st.write("session_state está vacío")

Este panel te ayuda a visualizar exactamente qué datos están siendo persistidos y cómo cambian durante la interacción del usuario.

Limitaciones importantes

Es importante tener en cuenta algunas restricciones del st.session_state:

  • Límite de memoria: Los datos se almacenan en la memoria del servidor, por lo que grandes cantidades de datos pueden afectar el rendimiento
  • Duración de sesión: Los datos se pierden cuando el usuario cierra la pestaña del navegador o después de un período de inactividad
  • Concurrencia: Cada usuario tiene su propio session_state independiente

La gestión adecuada del estado es fundamental para crear aplicaciones Streamlit interactivas y con una experiencia de usuario fluida. En la siguiente sección exploraremos cómo combinar st.session_state con formularios para crear interfaces más robustas.

Formularios y validación de datos con st.form

Los formularios en Streamlit proporcionan una forma elegante de agrupar múltiples elementos de entrada y controlar cuándo se procesan los datos. A diferencia de los widgets individuales que se ejecutan inmediatamente al cambiar, los formularios esperan a que el usuario pulse un botón de envío antes de procesar toda la información.

Estructura básica de un formulario

La función st.form() crea un contenedor especial donde puedes agrupar widgets de entrada. Todo el contenido dentro del formulario se procesa simultáneamente cuando el usuario pulsa el botón de envío:

import streamlit as st

st.title("Mi Primer Formulario")

with st.form("formulario_basico"):
    nombre = st.text_input("Nombre completo:")
    edad = st.number_input("Edad:", min_value=0, max_value=120, value=25)
    
    # Botón de envío obligatorio
    submitted = st.form_submit_button("Enviar datos")
    
    if submitted:
        st.success(f"¡Datos recibidos! Hola {nombre}, tienes {edad} años.")

El botón de envío es obligatorio en cada formulario y determina cuándo se procesan los datos introducidos por el usuario.

Combinando formularios con session_state

Los formularios trabajan de manera excelente con st.session_state para mantener los datos enviados entre ejecuciones de la aplicación:

# Inicializar lista de usuarios
if 'usuarios_registrados' not in st.session_state:
    st.session_state.usuarios_registrados = []

st.title("Registro de Usuarios")

with st.form("registro_usuario"):
    st.subheader("Datos del Usuario")
    
    nombre = st.text_input("Nombre:")
    email = st.text_input("Email:")
    edad = st.number_input("Edad:", min_value=18, max_value=100, value=25)
    
    submitted = st.form_submit_button("Registrar Usuario")
    
    if submitted and nombre and email:
        # Añadir usuario a la lista persistente
        nuevo_usuario = {
            'nombre': nombre,
            'email': email,
            'edad': edad
        }
        st.session_state.usuarios_registrados.append(nuevo_usuario)
        st.success(f"Usuario {nombre} registrado correctamente!")

# Mostrar usuarios registrados
if st.session_state.usuarios_registrados:
    st.subheader("Usuarios Registrados")
    for usuario in st.session_state.usuarios_registrados:
        st.info(f"**{usuario['nombre']}** - {usuario['email']} ({usuario['edad']} años)")

Validación básica de datos

La validación es esencial para garantizar que los datos introducidos cumplan con los requisitos específicos. Puedes implementar validaciones simples directamente en el formulario:

st.title("Formulario con Validación")

with st.form("formulario_validado"):
    # Campos del formulario
    nombre = st.text_input("Nombre completo:")
    telefono = st.text_input("Teléfono (formato: 123456789):")
    email = st.text_input("Email:")
    password = st.text_input("Contraseña:", type="password")
    
    submitted = st.form_submit_button("Validar y Enviar")
    
    if submitted:
        # Lista para almacenar errores
        errores = []
        
        # Validar nombre
        if not nombre or len(nombre) < 2:
            errores.append("El nombre debe tener al menos 2 caracteres")
        
        # Validar teléfono
        if not telefono.isdigit() or len(telefono) != 9:
            errores.append("El teléfono debe tener exactamente 9 dígitos")
        
        # Validar email
        if "@" not in email or "." not in email.split("@")[-1]:
            errores.append("El email no tiene un formato válido")
        
        # Validar contraseña
        if len(password) < 6:
            errores.append("La contraseña debe tener al menos 6 caracteres")
        
        # Mostrar resultados
        if errores:
            for error in errores:
                st.error(f"❌ {error}")
        else:
            st.success("✅ Todos los datos son válidos!")
            st.balloons()

Formulario de configuración avanzado

Los formularios son ideales para páginas de configuración donde el usuario necesita ajustar múltiples parámetros a la vez:

# Inicializar configuración por defecto
if 'config_app' not in st.session_state:
    st.session_state.config_app = {
        'tema': 'Claro',
        'idioma': 'Español',
        'notificaciones': True,
        'max_elementos': 10
    }

st.title("Configuración de la Aplicación")

with st.form("configuracion"):
    st.subheader("Preferencias Generales")
    
    # Opciones de tema
    tema = st.selectbox(
        "Tema visual:",
        options=['Claro', 'Oscuro', 'Automático'],
        index=['Claro', 'Oscuro', 'Automático'].index(st.session_state.config_app['tema'])
    )
    
    # Idioma
    idioma = st.radio(
        "Idioma:",
        options=['Español', 'Inglés', 'Francés'],
        index=['Español', 'Inglés', 'Francés'].index(st.session_state.config_app['idioma'])
    )
    
    # Notificaciones
    notificaciones = st.checkbox(
        "Activar notificaciones",
        value=st.session_state.config_app['notificaciones']
    )
    
    # Número máximo de elementos
    max_elementos = st.slider(
        "Máximo de elementos por página:",
        min_value=5, max_value=50, 
        value=st.session_state.config_app['max_elementos']
    )
    
    # Botones del formulario
    col1, col2 = st.columns(2)
    
    with col1:
        guardar = st.form_submit_button("💾 Guardar Configuración")
    
    with col2:
        restablecer = st.form_submit_button("🔄 Restablecer")
    
    if guardar:
        # Actualizar configuración
        st.session_state.config_app = {
            'tema': tema,
            'idioma': idioma,
            'notificaciones': notificaciones,
            'max_elementos': max_elementos
        }
        st.success("Configuración guardada correctamente!")
    
    if restablecer:
        # Volver a valores por defecto
        st.session_state.config_app = {
            'tema': 'Claro',
            'idioma': 'Español',
            'notificaciones': True,
            'max_elementos': 10
        }
        st.info("Configuración restablecida a valores por defecto")
        st.rerun()

# Mostrar configuración actual
st.subheader("Configuración Actual")
config_actual = st.session_state.config_app
st.json(config_actual)

Formularios con validación condicional

Para casos más complejos, puedes implementar validaciones que dependen de los valores de otros campos:

st.title("Solicitud de Cuenta Bancaria")

with st.form("solicitud_cuenta"):
    # Datos personales
    st.subheader("Información Personal")
    nombre = st.text_input("Nombre completo:")
    edad = st.number_input("Edad:", min_value=16, max_value=100, value=25)
    
    # Información financiera
    st.subheader("Información Financiera")
    ingresos = st.number_input("Ingresos mensuales (€):", min_value=0.0, value=1000.0)
    tipo_cuenta = st.selectbox("Tipo de cuenta:", ["Básica", "Premium", "VIP"])
    
    # Documentación
    st.subheader("Documentación")
    tiene_dni = st.checkbox("Tengo DNI válido")
    tiene_nomina = st.checkbox("Tengo nómina")
    
    submitted = st.form_submit_button("Solicitar Cuenta")
    
    if submitted:
        errores = []
        advertencias = []
        
        # Validaciones básicas
        if not nombre:
            errores.append("El nombre es obligatorio")
        
        # Validaciones condicionales según edad
        if edad < 18:
            errores.append("Debes ser mayor de edad para abrir una cuenta")
        elif edad < 25:
            advertencias.append("Los menores de 25 años requieren avalista")
        
        # Validaciones según tipo de cuenta
        if tipo_cuenta == "Premium" and ingresos < 2000:
            errores.append("La cuenta Premium requiere ingresos mínimos de 2000€")
        elif tipo_cuenta == "VIP" and ingresos < 5000:
            errores.append("La cuenta VIP requiere ingresos mínimos de 5000€")
        
        # Validar documentación
        if not tiene_dni:
            errores.append("El DNI es obligatorio")
        if tipo_cuenta != "Básica" and not tiene_nomina:
            errores.append("Las cuentas Premium y VIP requieren nómina")
        
        # Mostrar resultados
        if errores:
            st.subheader("❌ Errores encontrados:")
            for error in errores:
                st.error(error)
        else:
            if advertencias:
                st.subheader("⚠️ Advertencias:")
                for advertencia in advertencias:
                    st.warning(advertencia)
            
            st.success(f"✅ Solicitud de cuenta {tipo_cuenta} aprobada para {nombre}")
            st.info(f"Procesaremos tu solicitud en 2-3 días laborables")

Formularios anidados y organizados

Para interfaces complejas, puedes organizar formularios usando columnas y expandir secciones para mejorar la experiencia del usuario:

st.title("Formulario de Contacto Empresarial")

with st.form("contacto_empresarial"):
    # Dividir en columnas para mejor organización
    col1, col2 = st.columns(2)
    
    with col1:
        st.subheader("Datos de Contacto")
        nombre = st.text_input("Nombre:")
        empresa = st.text_input("Empresa:")
        email = st.text_input("Email corporativo:")
    
    with col2:
        st.subheader("Información Adicional")
        telefono = st.text_input("Teléfono:")
        cargo = st.text_input("Cargo:")
        sector = st.selectbox("Sector:", [
            "Tecnología", "Finanzas", "Salud", 
            "Educación", "Retail", "Otro"
        ])
    
    # Sección expandible para más detalles
    with st.expander("Detalles de la Consulta (Opcional)"):
        tipo_consulta = st.multiselect(
            "Tipo de consulta:",
            ["Soporte técnico", "Ventas", "Facturación", "Partnerships"]
        )
        mensaje = st.text_area("Mensaje detallado:", height=100)
        urgencia = st.select_slider(
            "Nivel de urgencia:",
            options=["Baja", "Media", "Alta", "Crítica"]
        )
    
    # Términos y condiciones
    acepta_terminos = st.checkbox("Acepto los términos y condiciones")
    acepta_marketing = st.checkbox("Acepto recibir comunicaciones comerciales")
    
    enviado = st.form_submit_button("📨 Enviar Consulta")
    
    if enviado:
        if not acepta_terminos:
            st.error("Debes aceptar los términos y condiciones")
        elif not all([nombre, empresa, email]):
            st.error("Los campos básicos son obligatorios")
        else:
            # Simular envío exitoso
            st.success("¡Consulta enviada correctamente!")
            
            # Mostrar resumen
            st.subheader("Resumen de tu consulta:")
            st.write(f"**Contacto**: {nombre} ({cargo}) - {empresa}")
            st.write(f"**Email**: {email}")
            if tipo_consulta:
                st.write(f"**Tipo**: {', '.join(tipo_consulta)}")
            if mensaje:
                st.write(f"**Mensaje**: {mensaje}")

Los formularios con st.form() ofrecen un control preciso sobre cuándo y cómo se procesan los datos del usuario. La combinación de formularios, validación y st.session_state permite crear interfaces robustas que proporcionan feedback inmediato y mantienen el estado de la aplicación de manera coherente.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Streamlit

Documentación oficial de Streamlit
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Streamlit es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Streamlit

Explora más contenido relacionado con Streamlit y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

  • Comprender el uso de st.session_state para mantener datos persistentes durante la sesión del usuario.
  • Aprender a inicializar y manipular variables dentro de st.session_state.
  • Implementar formularios con st.form para agrupar entradas y controlar el envío de datos.
  • Aplicar validaciones básicas y condicionales en formularios para asegurar la integridad de los datos.
  • Combinar la gestión de estado con formularios para crear aplicaciones interactivas y robustas.