Spaces:
Running
Running
import streamlit as st | |
import pandas as pd | |
import plotly.express as px | |
import random | |
import time | |
import joblib | |
import os | |
import statsmodels | |
from dotenv import load_dotenv | |
import os | |
from groq import Groq | |
import html | |
from pydub import AudioSegment | |
import tempfile | |
from io import BytesIO | |
import tempfile | |
#from langchain.agents.agent_toolkits import create_csv_agent | |
#from langchain_groq import ChatGroq | |
# =========================== | |
# Función para generar datos ficticios | |
# =========================== | |
def generar_datos(): | |
meses = [ | |
"Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", | |
"Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre" | |
] | |
paises = ["México", "Colombia", "Argentina", "Chile", "Perú"] | |
data = [ | |
{"mes": mes, "pais": pais, "Total": random.randint(100, 1000)} | |
for mes in meses for pais in paises | |
] | |
return pd.DataFrame(data), meses, paises | |
# =========================== | |
# Función para el dashboard principal | |
# =========================== | |
def mostrar_dashboard(): | |
# Cargar variables desde el archivo .env | |
load_dotenv() | |
# Acceder a la clave | |
groq_key = os.getenv("GROQ_API_KEY") | |
client = Groq(api_key=groq_key) | |
dfDatos, meses, paises = generar_datos() | |
# Opciones del selectbox | |
lista_opciones = ['5 años', '3 años', '1 año', '5 meses'] | |
# Mostrar barra lateral | |
mostrar_sidebar(client) | |
# Título principal | |
st.header(':bar_chart: Dashboard Sales') | |
# Mostrar métricas | |
#mostrar_metricas() | |
# Mostrar gráficos | |
mostrar_graficos(lista_opciones) | |
# =========================== | |
# Configuración inicial de la página | |
# =========================== | |
#def configurar_pagina(): | |
#st.set_page_config( | |
# page_title="Dashboard Sales", | |
# page_icon=":smile:", | |
# layout="wide", | |
# initial_sidebar_state="expanded" | |
#) | |
# =========================== | |
# Función para la barra lateral | |
# =========================== | |
def mostrar_sidebar(client): | |
sidebar_logo = r"paginas\images\Logo general.png" | |
main_body_logo = r"paginas\images\Logo.png" | |
sidebar_logo_dashboard = r"paginas\images\Logo dashboard.png" | |
st.logo(sidebar_logo, size="large", icon_image=main_body_logo) | |
st.sidebar.image(sidebar_logo_dashboard) | |
st.sidebar.title('🧠 GenAI Forecast') | |
loadCSV() | |
archivo_csv = "df_articles.csv" | |
chatBotProtech(client) | |
downloadCSV(archivo_csv) | |
# Mostrar la tabla solo si se ha subido un archivo válido | |
''' | |
if 'archivo_subido' in st.session_state and st.session_state.archivo_subido: # Verificamos si el archivo ha sido subido y es válido | |
st.sidebar.markdown("Vista previa del archivo CSV:") | |
# Usar st.dataframe() para que ocupe todo el ancho disponible | |
st.sidebar.dataframe(st.session_state.df_subido, use_container_width=True) # Mostrar la tabla con el archivo subido | |
''' | |
if st.sidebar.button("Cerrar Sesión"): | |
cerrar_sesion() | |
# =========================== | |
# Función para métricas principales | |
# =========================== | |
''' | |
def mostrar_metricas(): | |
c1, c2, c3, c4, c5 = st.columns(5) | |
valores = [89, 78, 67, 56, 45] | |
for i, col in enumerate([c1, c2, c3, c4, c5]): | |
valor1 = valores[i] | |
valor2 = valor1 - 10 # Simulación de variación | |
variacion = valor1 - valor2 | |
unidad = "unidades" if i < 4 else "%" | |
col.metric(f"Productos vendidos", f'{valor1:,.0f} {unidad}', f'{variacion:,.0f}') | |
''' | |
# Función para obtener los meses relevantes | |
def obtener_meses_relevantes(df): | |
# Extraemos los años y meses de la columna 'Date' | |
df['Year'] = pd.to_datetime(df['orddt']).dt.year | |
df['Month'] = pd.to_datetime(df['orddt']).dt.month | |
# Encontramos el primer y último año en el dataset | |
primer_ano = df['Year'].min() | |
ultimo_ano = df['Year'].max() | |
meses_relevantes = [] | |
nombres_meses_relevantes = [] | |
# Recorrer todos los años dentro del rango | |
for ano in range(primer_ano, ultimo_ano + 1): | |
for mes in [1, 4, 7, 10]: # Meses relevantes: enero (1), abril (4), julio (7), octubre (10) | |
if mes in df[df['Year'] == ano]['Month'].values: | |
# Obtener el nombre del mes | |
nombre_mes = pd.to_datetime(f"{ano}-{mes}-01").strftime('%B') # Mes en formato textual (Enero, Abril, etc.) | |
meses_relevantes.append(f"{nombre_mes}-{ano}") | |
nombres_meses_relevantes.append(f"{nombre_mes}-{ano}") | |
return meses_relevantes, nombres_meses_relevantes | |
# =========================== | |
# Función para gráficos | |
# =========================== | |
def mostrar_graficos(lista_opciones): | |
""" | |
c1, c2 = st.columns([20, 80]) | |
with c1: | |
filtroAnios = st.selectbox('Año', options=lista_opciones) | |
with c2: | |
st.markdown("### :pushpin: Ventas actuales") | |
# Si hay un archivo válido subido | |
if "archivo_subido" in st.session_state and st.session_state.archivo_subido: | |
# Cargar datos del archivo subido | |
df = st.session_state.df_subido.copy() | |
df['Date'] = pd.to_datetime(df['Date']) | |
df['Mes-Año'] = df['Date'].dt.strftime('%B-%Y') # Formato deseado | |
df = df.sort_values('Date') # Ordenar por fecha | |
# Obtener los meses relevantes del dataset | |
meses_relevantes, nombres_meses_relevantes = obtener_meses_relevantes(df) | |
# Crear la gráfica | |
fig = px.line( | |
df, | |
x='Mes-Año', | |
y='Sale', | |
title='Ventas mensuales (Archivo Subido)', | |
labels={'Mes-Año': 'Mes-Año', 'Sale': 'Ventas'}, | |
) | |
else: | |
# Datos por defecto | |
df = pd.DataFrame({ | |
"Mes-Año": ["Enero-2024", "Febrero-2024", "Marzo-2024", "Abril-2024", "Mayo-2024", "Junio-2024", "Julio-2024", "Agosto-2024", "Septiembre-2024", "Octubre-2024", "Noviembre-2024", "Diciembre-2024"], | |
"Sale": [100, 150, 120, 200, 250, 220, 280, 300, 350, 400, 450, 500], | |
}) | |
# Obtener los meses relevantes | |
meses_relevantes = ["Enero-2024", "Abril-2024", "Julio-2024", "Octubre-2024"] | |
nombres_meses_relevantes = ["Enero-2024", "Abril-2024", "Julio-2024", "Octubre-2024"] | |
# Crear la gráfica | |
fig = px.line( | |
df, | |
x='Mes-Año', | |
y='Sale', | |
title='Ventas mensuales (Datos por defecto)', | |
labels={'Mes-Año': 'Mes-Año', 'Sale': 'Ventas'}, | |
line_shape='linear' # Línea continua | |
) | |
fig.update_xaxes(tickangle=-45) # Ajustar ángulo de etiquetas en X | |
# Mejorar el diseño de la gráfica | |
fig = mejorar_diseno_grafica(fig, meses_relevantes, nombres_meses_relevantes) | |
st.plotly_chart(fig, use_container_width=True) # Evita que ocupe todo el ancho | |
# Gráfica 2: Ventas actuales y proyectadas | |
st.markdown("### :chart_with_upwards_trend: Pronóstico") | |
mostrar_ventas_proyectadas(filtroAnios) | |
""" | |
if "archivo_subido" not in st.session_state or not st.session_state.archivo_subido: | |
st.warning("Por favor, sube un archivo CSV válido para visualizar los gráficos.") | |
return | |
df = st.session_state.df_subido.copy() | |
# Fila 1: 3 gráficas | |
col1, col2, col3 = st.columns(3) | |
with col1: | |
fig1 = px.histogram(df, x='sales', title='Distribución de Ventas') | |
st.plotly_chart(fig1, use_container_width=True) | |
with col2: | |
fig2 = px.box(df, x='segmt', y='sales', title='Ventas por Segmento') | |
st.plotly_chart(fig2, use_container_width=True) | |
with col3: | |
print("") | |
# Fila 2: 2 gráficas | |
col4, col5 = st.columns(2) | |
with col4: | |
fig4 = px.pie(df, names='categ', values='sales', title='Ventas por Categoría') | |
st.plotly_chart(fig4, use_container_width=True) | |
with col5: | |
# Agrupar por nombre de producto y sumar las ventas | |
top_productos = ( | |
df.groupby('prdna')['sales'] | |
.sum() | |
.sort_values(ascending=False) | |
.head(10) | |
.reset_index() | |
) | |
# Crear gráfica de barras horizontales | |
fig5 = px.bar( | |
top_productos, | |
x='sales', | |
y='prdna', | |
orientation='h', | |
title='Top 10 productos más vendidos', | |
labels={'sales': 'Ventas', 'prdna': 'Producto'}, | |
color='sales', | |
color_continuous_scale='Blues' | |
) | |
fig5.update_layout(yaxis={'categoryorder': 'total ascending'}) | |
st.plotly_chart(fig5, use_container_width=True) | |
col6, col7 = st.columns(2) | |
with col6: | |
# Fuera del sistema de columnas | |
tabla = df.pivot_table(index='state', columns='subct', values='sales', aggfunc='sum').fillna(0) | |
if not tabla.empty: | |
tabla = tabla.astype(float) | |
fig6 = px.imshow( | |
tabla.values, | |
labels=dict(x="Categoría", y="Estado", color="Ventas"), | |
x=tabla.columns, | |
y=tabla.index, | |
text_auto=True, | |
title="Mapa de Calor: Ventas por Estado y Categoría" | |
) | |
# Ajuste del tamaño de la figura | |
# fig6.update_layout(height=600, width=1000) # Puedes ajustar según tu pantalla | |
st.plotly_chart(fig6, use_container_width=True) | |
else: | |
st.warning("No hay datos suficientes para mostrar el mapa de calor.") | |
with col7: | |
fig7 = px.bar(df.groupby('state')['sales'].sum().reset_index(), x='state', y='sales', title='Ventas por Estado') | |
st.plotly_chart(fig7, use_container_width=True) | |
# ------------------------------- | |
# CARGA DE CSV Y GUARDADO EN SESIÓN | |
# ------------------------------- | |
def loadCSV(): | |
columnas_requeridas = [ | |
'rowid','ordid','orddt', | |
'shpdt','segmt','state', | |
'cono','prodid','categ', | |
'subct','prdna','sales' | |
] | |
with st.sidebar.expander("📁 Subir archivo"): | |
uploaded_file = st.file_uploader("Sube un archivo CSV:", type=["csv"], key="upload_csv") | |
if uploaded_file is not None: | |
# Reseteamos el estado de 'descargado' cuando se sube un archivo | |
st.session_state.descargado = False | |
st.session_state.archivo_subido = False # Reinicia el estado | |
try: | |
# Leer el archivo subido | |
df = pd.read_csv(uploaded_file) | |
# Verificar que las columnas estén presentes y en el orden correcto | |
if list(df.columns) == columnas_requeridas: | |
st.session_state.df_subido = df | |
st.session_state.archivo_subido = True | |
aviso = st.sidebar.success("✅ Archivo subido correctamente.") | |
time.sleep(3) | |
aviso.empty() | |
else: | |
st.session_state.archivo_subido = False | |
aviso = st.sidebar.error(f"El archivo no tiene las columnas requeridas: {columnas_requeridas}.") | |
time.sleep(3) | |
aviso.empty() | |
except Exception as e: | |
aviso = st.sidebar.error(f"Error al procesar el archivo: {str(e)}") | |
time.sleep(3) | |
aviso.empty() | |
# =========================== | |
# Función para descargar archivo CSV | |
# =========================== | |
def downloadCSV(archivo_csv): | |
# Verificamos si el archivo ya ha sido descargado | |
if 'descargado' not in st.session_state: | |
st.session_state.descargado = False | |
if not st.session_state.descargado: | |
# Usamos st.spinner para mostrar un estado de descarga inicial | |
#with st.spinner("Preparando archivo para descarga..."): | |
# time.sleep(2) # Simulación de preparación del archivo | |
# Botón de descarga | |
descarga = st.sidebar.download_button( | |
label="Descargar archivo CSV", | |
data=open(archivo_csv, "rb"), | |
file_name="ventas.csv", | |
mime="text/csv" | |
) | |
if descarga: | |
# Marcamos el archivo como descargado | |
st.session_state.descargado = True | |
aviso = st.sidebar.success("¡Descarga completada!") | |
# Hacer que el mensaje desaparezca después de 2 segundos | |
time.sleep(3) | |
aviso.empty() | |
else: | |
aviso = st.sidebar.success("¡Ya has descargado el archivo!") | |
time.sleep(3) | |
aviso.empty() | |
# ------------------------------- | |
# CREACIÓN DE AGENTE CSV | |
# ------------------------------- | |
''' | |
def createCSVAgent(client, df): | |
temp_csv = tempfile.NamedTemporaryFile(delete=False, suffix=".csv") | |
df.to_csv(temp_csv.name, index=False) | |
agent = create_csv_agent( | |
client, | |
temp_csv.name, | |
verbose=False, | |
handle_parsing_errors=True | |
) | |
return agent | |
''' | |
''' | |
def callCSVAgent(client, prompt): | |
if "df_csv" not in st.session_state: | |
return "No hay CSV cargado aún." | |
df = st.session_state.df_csv | |
agente = createCSVAgent(client, df) | |
try: | |
respuesta = agente.run(prompt) | |
except Exception as e: | |
respuesta = f"Error al procesar la pregunta: {e}" | |
return respuesta | |
''' | |
# ------------------------------- | |
# FUNCIÓN PARA DETECTAR REFERENCIA AL CSV | |
# ------------------------------- | |
def detectedReferenceToCSV(prompt: str) -> bool: | |
palabras_clave = ["csv", "archivo", "contenido cargado", "file", "dataset"] | |
prompt_lower = prompt.lower() | |
return any(palabra in prompt_lower for palabra in palabras_clave) | |
# =========================== | |
# Función para interactuar con el bot | |
# =========================== | |
def chatBotProtech(client): | |
with st.sidebar.expander("📁 Chatbot"): | |
# Inicializar estados | |
if "chat_history" not in st.session_state: | |
st.session_state.chat_history = [] | |
if "audio_data" not in st.session_state: | |
st.session_state.audio_data = None | |
if "transcripcion" not in st.session_state: | |
st.session_state.transcripcion = "" | |
if "mostrar_grabador" not in st.session_state: | |
st.session_state.mostrar_grabador = True | |
# Contenedor para mensajes | |
messages = st.container(height=400) | |
# CSS: estilo tipo Messenger | |
st.markdown(""" | |
<style> | |
.chat-message { | |
display: flex; | |
align-items: flex-start; | |
margin: 10px 0; | |
} | |
.chat-message.user { | |
justify-content: flex-end; | |
} | |
.chat-message.assistant { | |
justify-content: flex-start; | |
} | |
.chat-icon { | |
width: 30px; | |
height: 30px; | |
border-radius: 50%; | |
background-color: #ccc; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 18px; | |
margin: 0 5px; | |
} | |
.chat-bubble { | |
max-width: 70%; | |
padding: 10px 15px; | |
border-radius: 15px; | |
font-size: 14px; | |
line-height: 1.5; | |
word-wrap: break-word; | |
} | |
.chat-bubble.user { | |
background-color: #DCF8C6; | |
color: black; | |
border-top-right-radius: 0; | |
} | |
.chat-bubble.assistant { | |
background-color: #F1F0F0; | |
color: black; | |
border-top-left-radius: 0; | |
} | |
</style> | |
""", unsafe_allow_html=True) | |
# Mostrar historial de mensajes | |
with messages: | |
st.header("🤖 ChatBot Protech") | |
for message in st.session_state.chat_history: | |
role = message["role"] | |
content = html.escape(message["content"]) # Escapar contenido HTML | |
bubble_class = "user" if role == "user" else "assistant" | |
icon = "👤" if role == "user" else "🤖" | |
# Mostrar el mensaje en una sola burbuja con ícono en el mismo bloque | |
st.markdown(f""" | |
<div class="chat-message {bubble_class}"> | |
<div class="chat-icon">{icon}</div> | |
<div class="chat-bubble {bubble_class}">{content}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# --- Manejar transcripción como mensaje automático --- | |
if st.session_state.transcripcion: | |
prompt = st.session_state.transcripcion | |
st.session_state.transcripcion = "" | |
st.session_state.chat_history.append({"role": "user", "content": prompt}) | |
with messages: | |
st.markdown(f""" | |
<div class="chat-message user"> | |
<div class="chat-bubble user">{html.escape(prompt)}</div> | |
<div class="chat-icon">👤</div> | |
</div> | |
""", unsafe_allow_html=True) | |
with messages: | |
with st.spinner("Pensando..."): | |
completion = callDeepseek(client, prompt) | |
response = "" | |
response_placeholder = st.empty() | |
for chunk in completion: | |
content = chunk.choices[0].delta.content or "" | |
response += content | |
response_placeholder.markdown(f""" | |
<div class="chat-message assistant"> | |
<div class="chat-icon">🤖</div> | |
<div class="chat-bubble assistant">{response}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
st.session_state.chat_history.append({"role": "assistant", "content": response}) | |
# Captura del input tipo chat | |
if prompt := st.chat_input("Escribe algo..."): | |
st.session_state.chat_history.append({"role": "user", "content": prompt}) | |
# Mostrar mensaje del usuario escapado | |
with messages: | |
st.markdown(f""" | |
<div class="chat-message user"> | |
<div class="chat-bubble user">{prompt}</div> | |
<div class="chat-icon">👤</div> | |
</div> | |
""", unsafe_allow_html=True) | |
# Mostrar respuesta del asistente | |
with messages: | |
with st.spinner("Pensando..."): | |
completion = callDeepseek(client, prompt) | |
response = "" | |
response_placeholder = st.empty() | |
for chunk in completion: | |
content = chunk.choices[0].delta.content or "" | |
response += content | |
response_placeholder.markdown(f""" | |
<div class="chat-message assistant"> | |
<div class="chat-icon">🤖</div> | |
<div class="chat-bubble assistant">{response}</div> | |
</div> | |
""", unsafe_allow_html=True) | |
st.session_state.chat_history.append({"role": "assistant", "content": response}) | |
# Grabación de audio (solo si está habilitada) | |
if st.session_state.mostrar_grabador and st.session_state.audio_data is None: | |
audio_data = st.audio_input("Graba tu voz aquí 🎤") | |
if audio_data: | |
st.session_state.audio_data = audio_data | |
st.session_state.mostrar_grabador = False # Ocultar input después de grabar | |
st.rerun() # Forzar recarga para ocultar input y evitar que reaparezca el audio cargado | |
# Mostrar controles solo si hay audio cargado | |
if st.session_state.audio_data: | |
st.audio(st.session_state.audio_data, format="audio/wav") | |
col1, col2 = st.columns(2) | |
with col1: | |
if st.button("✅ Aceptar grabación"): | |
with st.spinner("Convirtiendo y transcribiendo..."): | |
m4a_path = converter_bytes_m4a(st.session_state.audio_data) | |
with open(m4a_path, "rb") as f: | |
texto = callWhisper(client, m4a_path, f) | |
os.remove(m4a_path) | |
st.session_state.transcripcion = texto | |
st.session_state.audio_data = None | |
st.session_state.mostrar_grabador = True | |
st.rerun() | |
with col2: | |
if st.button("❌ Descartar grabación"): | |
st.session_state.audio_data = None | |
st.session_state.transcripcion = "" | |
st.session_state.mostrar_grabador = True | |
st.rerun() | |
# Mostrar transcripción como texto previo al input si existe | |
''' | |
if st.session_state.transcripcion: | |
st.info(f"📝 Transcripción: {st.session_state.transcripcion}") | |
# Prellenar el input simuladamente | |
prompt = st.session_state.transcripcion | |
st.session_state.transcripcion = "" # Limpiar | |
st.rerun() # Simular que se envió el mensaje | |
''' | |
#def speechRecognition(): | |
#audio_value = st.audio_input("Record a voice message") | |
def callDeepseek(client, prompt): | |
completion = client.chat.completions.create( | |
#model="meta-llama/llama-4-scout-17b-16e-instruct", | |
model = "deepseek-r1-distill-llama-70b", | |
messages=[{"role": "user", "content": prompt}], | |
temperature=0.6, | |
max_tokens=1024, | |
top_p=1, | |
stream=True, | |
) | |
return completion | |
def callWhisper(client, filename_audio,file): | |
transcription = client.audio.transcriptions.create( | |
file=(filename_audio, file.read()), | |
model="whisper-large-v3", | |
response_format="verbose_json", | |
) | |
return transcription.text | |
def converter_bytes_m4a(audio_bytes: BytesIO) -> str: | |
""" | |
Convierte un audio en bytes (WAV, etc.) a un archivo M4A temporal. | |
Retorna la ruta del archivo .m4a temporal. | |
""" | |
# Asegurarse de que el cursor del stream esté al inicio | |
audio_bytes.seek(0) | |
# Leer el audio desde BytesIO usando pydub | |
audio = AudioSegment.from_file(audio_bytes) | |
# Crear archivo temporal para guardar como .m4a | |
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".m4a") | |
m4a_path = temp_file.name | |
temp_file.close() # Cerramos para que pydub pueda escribirlo | |
# Exportar a M4A usando formato compatible con ffmpeg | |
audio.export(m4a_path, format="ipod") # 'ipod' genera .m4a | |
return m4a_path | |
# =========================== | |
# Función para cargar el modelo SARIMA | |
# =========================== | |
"""def cargar_modelo_sarima(ruta_modelo): | |
# Cargar el modelo utilizando joblib | |
modelo = joblib.load(ruta_modelo) | |
return modelo""" | |
# =========================== | |
# Función para obtener el número de periodos basado en el filtro | |
# =========================== | |
def obtener_periodos(filtro): | |
opciones_periodos = { | |
'5 años': 60, | |
'3 años': 36, | |
'1 año': 12, | |
'5 meses': 5 | |
} | |
return opciones_periodos.get(filtro, 12) | |
# =========================== | |
# Función para mostrar ventas actuales y proyectadas | |
# =========================== | |
""" | |
def mostrar_ventas_proyectadas(filtro): | |
ruta_modelo = os.path.join("arima_sales_model.pkl") | |
modelo_sarima = cargar_modelo_sarima(ruta_modelo) | |
if "archivo_subido" in st.session_state and st.session_state.archivo_subido: | |
# Cargar datos del archivo subido | |
df = st.session_state.df_subido.copy() | |
df['Date'] = pd.to_datetime(df['Date']) | |
df = df.sort_values('Date') | |
# Generar predicciones | |
periodos = obtener_periodos(filtro) | |
predicciones = generar_predicciones(modelo_sarima, df, periodos) | |
# Redondear y formatear las ventas | |
df['Sale'] = df['Sale'].round(2).apply(lambda x: f"{x:,.2f}") # Formato con 2 decimales y comas | |
predicciones = [round(val, 2) for val in predicciones] # Redondear predicciones | |
# Preparar datos para graficar | |
df['Tipo'] = 'Ventas Actuales' | |
df_pred = pd.DataFrame({ | |
'Date': pd.date_range(df['Date'].max(), periods=periodos + 1, freq='ME')[1:], | |
'Sale': predicciones, | |
'Tipo': 'Ventas Pronosticadas' | |
}) | |
df_grafico = pd.concat([df[['Date', 'Sale', 'Tipo']], df_pred]) | |
else: | |
st.warning("Por favor, sube un archivo CSV válido para generasr predicciones.") | |
return | |
# Crear gráfica | |
fig = px.line( | |
df_grafico, | |
x='Date', | |
y='Sale', | |
color='Tipo', | |
title='Ventas pronosticadas (Ventas vs Mes)', | |
labels={'Date': 'Fecha', 'Sale': 'Ventas', 'Tipo': 'Serie'} | |
) | |
# Centramos el título del gráfico | |
fig.update_layout( | |
title={ | |
'text': "Ventas Actuales y Pronosticadas", | |
'x': 0.5, # Centrado horizontal | |
'xanchor': 'center', # Asegura el anclaje central | |
'yanchor': 'top' # Anclaje superior (opcional) | |
}, | |
title_font=dict(size=18, family="Arial, sans-serif", color='black'), | |
) | |
fig.update_xaxes(tickangle=-45) | |
# Mejorar el diseño de la leyenda | |
fig.update_layout( | |
legend=dict( | |
title="Leyenda", # Título de la leyenda | |
title_font=dict(size=12, color="black"), | |
font=dict(size=10, color="black"), | |
bgcolor="rgba(240,240,240,0.8)", # Fondo semitransparente | |
bordercolor="gray", | |
borderwidth=1, | |
orientation="h", # Leyenda horizontal | |
yanchor="top", | |
y=-0.3, # Ajustar la posición vertical | |
xanchor="right", | |
x=0.5 # Centrar horizontalmente | |
) | |
) | |
st.plotly_chart(fig, use_container_width=True) | |
""" | |
# =========================== | |
# Función para generar predicciones | |
# =========================== | |
def generar_predicciones(modelo, df, periodos): | |
ventas = df['Sale'] | |
predicciones = modelo.forecast(steps=periodos) | |
return predicciones | |
# Función para mejorar el diseño de las gráficas | |
def mejorar_diseno_grafica(fig, meses_relevantes, nombres_meses_relevantes): | |
fig.update_layout( | |
title={ | |
'text': "Ventas vs Mes", | |
'x': 0.5, # Centrado horizontal | |
'xanchor': 'center', # Asegura el anclaje central | |
'yanchor': 'top' # Anclaje superior (opcional) | |
}, | |
title_font=dict(size=18, family="Arial, sans-serif", color='black'), | |
xaxis=dict( | |
title='Mes-Año', | |
title_font=dict(size=14, family="Arial, sans-serif", color='black'), | |
tickangle=-45, # Rotar las etiquetas | |
showgrid=True, | |
gridwidth=0.5, | |
gridcolor='lightgrey', | |
showline=True, | |
linecolor='black', | |
linewidth=2, | |
tickmode='array', # Controla qué etiquetas mostrar | |
tickvals=meses_relevantes, # Selecciona solo los meses relevantes | |
ticktext=nombres_meses_relevantes, # Meses seleccionados | |
tickfont=dict(size=10), # Reducir el tamaño de la fuente de las etiquetas | |
), | |
yaxis=dict( | |
title='Ventas', | |
title_font=dict(size=14, family="Arial, sans-serif", color='black'), | |
showgrid=True, | |
gridwidth=0.5, | |
gridcolor='lightgrey', | |
showline=True, | |
linecolor='black', | |
linewidth=2 | |
), | |
plot_bgcolor='white', # Fondo blanco | |
paper_bgcolor='white', # Fondo del lienzo de la gráfica | |
font=dict(family="Arial, sans-serif", size=12, color="black"), | |
showlegend=False, # Desactivar la leyenda si no es necesaria | |
margin=dict(l=50, r=50, t=50, b=50) # Márgenes ajustados | |
) | |
return fig | |
# =========================== | |
# Función para cerrar sesión | |
# =========================== | |
def cerrar_sesion(): | |
st.session_state.logged_in = False | |
st.session_state.usuario = None | |
st.session_state.pagina_actual = "login" | |
st.session_state.archivo_subido = False # Limpiar el archivo subido al cerrar sesión | |
st.session_state.df_subido = None # Limpiar datos del archivo | |
# Eliminar parámetros de la URL usando st.query_params | |
st.query_params.clear() # Método correcto para limpiar parámetros de consulta | |
# Redirigir a la página de login | |
st.rerun() | |