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)