Cronograma / app.py
hedtorresca's picture
Update app.py
bf5515d verified
import gradio as gr
import pandas as pd
import plotly.express as px
from datetime import datetime
import os
# --- 1. CONFIGURACIÓN DEL ARCHIVO CSV ---
CSV_FILE = "cronograma.csv"
# --- 2. LÓGICA DE CARGA Y GUARDADO DE DATOS ---
def load_data_from_csv():
"""Carga los datos desde el archivo CSV al iniciar la aplicación."""
try:
# Lee el CSV y convierte las columnas de fecha al formato correcto
df = pd.read_csv(
CSV_FILE,
parse_dates=['Fecha de Inicio', 'Fecha de Fin']
)
return df
except FileNotFoundError:
# Si el archivo no existe, crea un DataFrame vacío con las columnas correctas
return create_empty_dataframe()
except Exception as e:
print(f"Error al leer el CSV: {e}")
return create_empty_dataframe()
def save_data_to_csv(df):
"""Guarda el DataFrame completo en el archivo CSV."""
try:
df.to_csv(CSV_FILE, index=False)
print(f"Datos guardados exitosamente en {CSV_FILE}")
except Exception as e:
gr.Error(f"No se pudo guardar el archivo CSV: {e}")
def create_empty_dataframe():
"""Crea la estructura del DataFrame si el archivo no existe o está vacío."""
columns = ['ID', 'Tarea', 'Fase', 'Responsable', 'Fecha de Inicio', 'Fecha de Fin', 'Estado', 'Progreso (%)', 'Descripción']
df = pd.DataFrame(columns=columns)
# Asegurar tipos de datos correctos para un df vacío
df['ID'] = df['ID'].astype(int)
df['Progreso (%)'] = df['Progreso (%)'].astype(int)
return df
# --- Carga inicial de datos ---
initial_df = load_data_from_csv()
# --- 3. FUNCIONES DE LA APLICACIÓN (LÓGICA DE UI) ---
# (Las funciones create_gantt_chart y populate_form_on_select no cambian)
def create_gantt_chart(df):
if df.empty or df['Fecha de Inicio'].isnull().all() or df['Fecha de Fin'].isnull().all():
return px.timeline(title="Cronograma de Proyecto")
status_colors = {'No Iniciada': 'lightgrey', 'En Progreso': 'blue', 'Completada': 'green', 'Retrasada': 'orange', 'Bloqueada': 'red'}
df_filtered = df.dropna(subset=['Fecha de Inicio', 'Fecha de Fin'])
fig = px.timeline(df_filtered, x_start="Fecha de Inicio", x_end="Fecha de Fin", y="Tarea", color="Estado", color_discrete_map=status_colors, title="Diagrama de Gantt del Proyecto")
fig.update_yaxes(autorange="reversed")
return fig
def populate_form_on_select(df, evt: gr.SelectData):
if evt.index is None: return None, "", "", "", None, None, "No Iniciada", 0, ""
selected_row = df.iloc[evt.index[0]]
start_date = selected_row['Fecha de Inicio'].to_pydatetime().date() if pd.notna(selected_row['Fecha de Inicio']) else None
end_date = selected_row['Fecha de Fin'].to_pydatetime().date() if pd.notna(selected_row['Fecha de Fin']) else None
return (selected_row['ID'], selected_row['Tarea'], selected_row['Fase'], selected_row['Responsable'], start_date, end_date, selected_row['Estado'], selected_row['Progreso (%)'], selected_row['Descripción'])
def manage_tasks(df_state, *args):
"""Función central que ahora también guarda los cambios en el CSV."""
action, selected_id, task_id, name, phase, responsible, start_date, end_date, status, progress, desc = args
df = df_state.copy()
# Lógica para añadir, actualizar o eliminar
if action == "add":
if not name or not start_date or not end_date:
gr.Warning("Nombre, Fecha de Inicio y Fecha de Fin son obligatorios.")
return df, create_gantt_chart(df), gr.update(), gr.update()
new_id = int(df['ID'].max() + 1) if not df.empty else 1
new_task = pd.DataFrame([{'ID': new_id, 'Tarea': name, 'Fase': phase, 'Responsable': responsible, 'Fecha de Inicio': start_date, 'Fecha de Fin': end_date, 'Estado': status, 'Progreso (%)': progress, 'Descripción': desc}])
df = pd.concat([df, new_task], ignore_index=True)
gr.Info(f"Tarea '{name}' añadida.")
elif action == "update":
if selected_id is None:
gr.Warning("Selecciona una tarea para actualizar.")
return df, create_gantt_chart(df), gr.update(), gr.update()
df.loc[df['ID'] == selected_id, ['Tarea', 'Fase', 'Responsable', 'Fecha de Inicio', 'Fecha de Fin', 'Estado', 'Progreso (%)', 'Descripción']] = [name, phase, responsible, start_date, end_date, status, progress, desc]
gr.Info(f"Tarea ID {selected_id} actualizada.")
elif action == "delete":
if selected_id is None:
gr.Warning("Selecciona una tarea para eliminar.")
return df, create_gantt_chart(df), gr.update(), gr.update()
task_name = df.loc[df['ID'] == selected_id, 'Tarea'].iloc[0]
df = df[df['ID'] != selected_id].reset_index(drop=True)
gr.Info(f"Tarea '{task_name}' eliminada.")
# Asegurar tipos de datos correctos antes de guardar
df['Fecha de Inicio'] = pd.to_datetime(df['Fecha de Inicio'])
df['Fecha de Fin'] = pd.to_datetime(df['Fecha de Fin'])
# **Paso clave: Guardar el DataFrame modificado en el archivo CSV**
save_data_to_csv(df)
all_phases = sorted(df['Fase'].dropna().unique().tolist())
all_responsibles = sorted(df['Responsable'].dropna().unique().tolist())
return df, create_gantt_chart(df), gr.update(choices=all_phases), gr.update(choices=all_responsibles)
# --- 4. INTERFAZ DE GRADIO ---
with gr.Blocks(theme=gr.themes.Soft(), title="Gestor de Proyectos con CSV") as demo:
df_state = gr.State(initial_df)
selected_task_id = gr.State(None)
initial_phases = sorted(initial_df['Fase'].dropna().unique().tolist())
initial_responsibles = sorted(initial_df['Responsable'].dropna().unique().tolist())
status_options = ['No Iniciada', 'En Progreso', 'Completada', 'Retrasada', 'Bloqueada']
gr.Markdown("# 💾 Gestor de Proyectos (con Guardado en CSV)")
gr.Markdown("Los datos se guardan en el 'Space'. **Nota:** Los datos se reiniciarán si el Space está inactivo por mucho tiempo.")
with gr.Row():
with gr.Column(scale=1):
with gr.Accordion("📝 Panel de Gestión de Tareas", open=True):
txt_task_id = gr.Textbox(label="ID Tarea", interactive=False, visible=False)
txt_task_name = gr.Textbox(label="Nombre de la Tarea")
dd_phase = gr.Dropdown(label="Fase del Proyecto", choices=initial_phases, allow_custom_value=True)
dd_responsible = gr.Dropdown(label="Responsable", choices=initial_responsibles, allow_custom_value=True)
with gr.Row():
date_start = gr.DatePicker(label="Fecha de Inicio")
date_end = gr.DatePicker(label="Fecha de Fin")
dd_status = gr.Dropdown(label="Estado", choices=status_options, value="No Iniciada")
slider_progress = gr.Slider(label="Progreso (%)", minimum=0, maximum=100, step=1)
txt_description = gr.Textbox(label="Descripción / Notas", lines=3)
with gr.Row():
btn_add = gr.Button("✔️ Añadir Tarea", variant="primary")
btn_update = gr.Button("🔄 Actualizar Tarea", variant="secondary")
btn_delete = gr.Button("❌ Eliminar Tarea", variant="stop")
with gr.Column(scale=2):
gr.Markdown("### 🗓️ Tabla de Actividades")
df_table = gr.DataFrame(value=initial_df, headers=list(initial_df.columns), interactive=True, height=300)
gr.Markdown("### 📈 Diagrama de Gantt")
gantt_plot = gr.Plot(create_gantt_chart(initial_df))
# --- 5. LÓGICA DE EVENTOS ---
task_inputs = [txt_task_id, txt_task_name, dd_phase, dd_responsible, date_start, date_end, dd_status, slider_progress, txt_description]
btn_add.click(fn=lambda *args: manage_tasks(*args), inputs=[gr.State("add"), df_state, selected_task_id] + task_inputs, outputs=[df_state, gantt_plot, dd_phase, dd_responsible])
btn_update.click(fn=lambda *args: manage_tasks(*args), inputs=[gr.State("update"), df_state, selected_task_id] + task_inputs, outputs=[df_state, gantt_plot, dd_phase, dd_responsible])
btn_delete.click(fn=lambda *args: manage_tasks(*args), inputs=[gr.State("delete"), df_state, selected_task_id] + task_inputs, outputs=[df_state, gantt_plot, dd_phase, dd_responsible])
df_table.select(fn=populate_form_on_select, inputs=[df_state], outputs=[selected_task_id, txt_task_name, dd_phase, dd_responsible, date_start, date_end, dd_status, slider_progress, txt_description])
df_state.change(fn=lambda df: df, inputs=[df_state], outputs=[df_table])
df_state.change(fn=create_gantt_chart, inputs=[df_state], outputs=[gantt_plot])
if __name__ == "__main__":
demo.launch(debug=True)