import pandas as pd import plotly.express as px import streamlit as st from config import DATA_SOURCE, data_source_label, validate_config from export.excel_exporter import export_to_excel from services.data_factory import create_data_service st.set_page_config( page_title="Monitoreo Planner", page_icon="📊", layout="wide", ) st.title("📊 Monitoreo de Proyectos — Microsoft Planner") st.caption(f"Fuente de datos: **{data_source_label()}**") missing = validate_config() if missing: st.error(f"Faltan variables en `.env`: **{', '.join(missing)}**") if DATA_SOURCE == "powerautomate": st.info("Configura `POWER_AUTOMATE_URL` con la URL del trigger HTTP de tu flujo. " "Guia: `docs/POWER_AUTOMATE_FLUJO.md`") else: st.info("Copia `.env.example` a `.env` y completa las credenciales de Azure.") st.stop() @st.cache_data(ttl=300, show_spinner="Obteniendo datos...") def load_projects(): service = create_data_service() return service.get_all_projects() if st.button("🔄 Actualizar datos", type="primary"): load_projects.clear() with st.spinner(f"Conectando via {data_source_label()}..."): try: projects = load_projects() except Exception as e: st.error(f"Error al obtener datos: {e}") if DATA_SOURCE == "powerautomate": st.info("Verifica que el flujo de Power Automate este **activado** y que la URL del trigger sea correcta.") else: st.info("Verifica permisos de la app en Azure (`Tasks.Read`, `User.Read`).") st.stop() if not projects: st.warning("No se encontraron proyectos.") st.stop() all_tasks = [] for p in projects: all_tasks.extend(p["tareas"]) total_tasks = len(all_tasks) completed = sum(1 for t in all_tasks if t["porcentaje"] == 100) overdue = sum(1 for t in all_tasks if t["vencida"]) progress_pct = round((completed / total_tasks) * 100, 1) if total_tasks else 0 col1, col2, col3, col4 = st.columns(4) col1.metric("Proyectos", len(projects)) col2.metric("Tareas totales", total_tasks) col3.metric("Avance global", f"{progress_pct}%") col4.metric("Tareas vencidas", overdue) st.divider() col_filter1, col_filter2 = st.columns(2) project_names = sorted({p["proyecto"] for p in projects}) selected_projects = col_filter1.multiselect("Filtrar por proyecto", project_names, default=project_names) statuses = ["Por hacer", "En progreso", "Completada"] selected_statuses = col_filter2.multiselect("Filtrar por estado", statuses, default=statuses) filtered_projects = [p for p in projects if p["proyecto"] in selected_projects] filtered_tasks = [ t for p in filtered_projects for t in p["tareas"] if t["estado"] in selected_statuses ] chart_col1, chart_col2 = st.columns(2) summary_df = pd.DataFrame([{ "proyecto": p["proyecto"], "completadas": p["completadas"], "en_progreso": p["en_progreso"], "por_hacer": p["por_hacer"], } for p in filtered_projects]) if not summary_df.empty: melted = summary_df.melt(id_vars="proyecto", var_name="estado", value_name="cantidad") estado_labels = { "completadas": "Completadas", "en_progreso": "En progreso", "por_hacer": "Por hacer", } melted["estado"] = melted["estado"].map(estado_labels) fig_bar = px.bar( melted, x="proyecto", y="cantidad", color="estado", title="Tareas por proyecto y estado", barmode="stack", ) fig_bar.update_layout(xaxis_tickangle=-45) chart_col1.plotly_chart(fig_bar, use_container_width=True) fig_pie = px.pie( summary_df, values="completadas", names="proyecto", title="Tareas completadas por proyecto", ) chart_col2.plotly_chart(fig_pie, use_container_width=True) st.subheader("Detalle de tareas") tasks_df = pd.DataFrame(filtered_tasks) if not tasks_df.empty: display_cols = ["proyecto", "tarea", "estado", "porcentaje", "prioridad", "fecha_vencimiento", "asignados", "vencida"] st.dataframe(tasks_df[display_cols], use_container_width=True, hide_index=True) else: st.info("No hay tareas con los filtros seleccionados.") st.divider() excel_bytes = export_to_excel(filtered_projects if filtered_projects else projects) st.download_button( label="📥 Descargar Excel", data=excel_bytes, file_name="reporte_planner.xlsx", mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type="primary", )