From 94c2d3b7300f763937a3727a4aa669583a3a33d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Thu, 2 Jul 2026 13:08:42 +0200 Subject: [PATCH] Add Streamlit dashboard for leads-optimizer metrics Adds dashboard.py with streamlit/pandas deps, plus internal docs. Co-Authored-By: Claude Sonnet 5 --- .claude/settings.local.json | 9 +- dashboard.py | 678 ++++++++++++++++++++++++++++++++++++ docs/Documentación.pdf | Bin 0 -> 72839 bytes requirements.txt | 2 + 4 files changed, 688 insertions(+), 1 deletion(-) create mode 100644 dashboard.py create mode 100644 docs/Documentación.pdf diff --git a/.claude/settings.local.json b/.claude/settings.local.json index aa182c9..525b0be 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,14 @@ { "permissions": { "allow": [ - "Bash(pip install *)" + "Bash(pip install *)", + "Bash(python -m py_compile run.py airtable_client.py google_ads_client.py)", + "Bash(python -m py_compile run.py airtable_client.py slack_reporter.py)", + "Bash(python -m py_compile run.py)", + "Bash(python run.py)", + "Bash(git add *)", + "Bash(python migrate_leads_field.py)", + "Bash(python backfill_leads_google_mayo.py)" ] } } diff --git a/dashboard.py b/dashboard.py new file mode 100644 index 0000000..835b665 --- /dev/null +++ b/dashboard.py @@ -0,0 +1,678 @@ +"""Dashboard interactivo para leads-optimizer — Streamlit.""" +import streamlit as st +import pandas as pd +import json +import subprocess +import sys +import os +import glob +from datetime import datetime, date +import calendar + +sys.path.insert(0, os.path.dirname(__file__)) + +from airtable_client import AirtableClient +from analyzer import analyze + +st.set_page_config( + page_title="Leads Optimizer", + layout="wide", + initial_sidebar_state="expanded", +) + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +URGENCIA_ICON = { + "PAUSAR": "⛔", + "SPRINT": "🚀", + "ACELERAR": "📈", + "FRENAR": "📉", + "EN_RITMO": "✅", +} + +URGENCIA_ORDER = {"PAUSAR": 0, "SPRINT": 1, "ACELERAR": 2, "FRENAR": 3, "EN_RITMO": 4} + +CRITICIDAD_ORDER = {"Crítico": 0, "Peligro": 1, "Mantener": 2} + + +def _eur(v: float) -> str: + return f"{v:.2f}€" + + +def _pct(v: float) -> str: + sign = "+" if v >= 0 else "" + return f"{sign}{v * 100:.1f}%" + + +def _ritmo_bar(ritmo: float) -> str: + filled = int(abs(ritmo) * 10) + bar = "█" * min(filled, 10) + return ("🟢 +" if ritmo > 0 else "🔴 ") + bar if ritmo != 0 else "⚪ —" + + +# ── Data loading ────────────────────────────────────────────────────────────── + +@st.cache_data(ttl=300, show_spinner="Cargando datos de Airtable...") +def _load_gcm(mes: int, anio: int) -> list[dict]: + """Lee GACampaignMes para el mes/año dado.""" + at = AirtableClient() + mes_num = str(mes) + anio_str = str(anio) + formula = f"AND({{Mes}}='{mes_num}',{{Año}}='{anio_str}')" + records = at.gacampaignmes.all( + formula=formula, + fields=[ + "CampaignID", "Campaign Name (from CampaignID)", + "Mes", "Año", "Status", "PPL", "CPAMax", "CapTotalMes", + "CosteMes", "ConvMes", "ConvLeadsLakeMesFinal", + "MetricasDiarias", "Consejo", "Criticidad", "Log", + ], + ) + campaigns_records = at.campaigns.all(fields=["CampaignID"]) + at_id_to_gid = {r["id"]: str(r["fields"].get("CampaignID", "")).strip() for r in campaigns_records} + + result = [] + for r in records: + f = r["fields"] + at_cids = f.get("CampaignID", []) + gid = at_id_to_gid.get(at_cids[0], "") if at_cids else "" + name_list = f.get("Campaign Name (from CampaignID)", []) + name = name_list[0] if name_list else "Sin nombre" + + metricas_raw = f.get("MetricasDiarias") or "{}" + try: + metricas = json.loads(metricas_raw) + except (json.JSONDecodeError, TypeError): + metricas = {} + + result.append({ + "airtable_id": r["id"], + "google_campaign_id": gid, + "nombre": name, + "status": f.get("Status", "Pausada"), + "ppl": float(f.get("PPL") or 0), + "cpa_max": float(f.get("CPAMax") or 0), + "cap": int(f.get("CapTotalMes") or 0), + "coste_mes": float(f.get("CosteMes") or 0), + "conv_mes": float(f.get("ConvMes") or 0), + "leads_lake": int(f.get("ConvLeadsLakeMesFinal") or 0), + "metricas": metricas, + "consejo": f.get("Consejo", ""), + "criticidad": f.get("Criticidad", "Mantener"), + "log": f.get("Log", ""), + }) + return result + + +@st.cache_data(ttl=300, show_spinner="Cargando cappings de cursos...") +def _load_cursomes(mes: int, anio: int) -> list[dict]: + at = AirtableClient() + meses_es = { + 1: "Enero", 2: "Febrero", 3: "Marzo", 4: "Abril", + 5: "Mayo", 6: "Junio", 7: "Julio", 8: "Agosto", + 9: "Septiembre", 10: "Octubre", 11: "Noviembre", 12: "Diciembre", + } + mes_nombre = meses_es[mes] + formula = f"AND({{Mes}}='{mes_nombre}',{{Año}}='{anio}')" + records = at.cursomes.all( + formula=formula, + fields=["CursoID", "Mes", "Año", "Caping Admitido"], + ) + cursos_records = at.cursos.all(fields=["CursoID", "Nombre"]) + rec_to_cursoid = {r["id"]: str(int(r["fields"].get("CursoID", 0))) for r in cursos_records if r["fields"].get("CursoID")} + rec_to_nombre = {r["id"]: r["fields"].get("Nombre", "") for r in cursos_records} + + result = [] + for r in records: + f = r["fields"] + curso_rec_ids = f.get("CursoID", []) + cursoid = rec_to_cursoid.get(curso_rec_ids[0], "") if curso_rec_ids else "" + nombre = rec_to_nombre.get(curso_rec_ids[0], cursoid) if curso_rec_ids else cursoid + result.append({ + "airtable_id": r["id"], + "cursoid": cursoid, + "nombre": nombre, + "mes": f.get("Mes", ""), + "anio": f.get("Año", ""), + "cap": int(f.get("Caping Admitido") or 0), + }) + return sorted(result, key=lambda x: x["nombre"]) + + +def _compute_analysis(row: dict, dia_actual: int, dias_mes: int) -> dict: + """Calcula urgencia/ritmo con los datos de Airtable (sin Google Ads API).""" + leads = row["leads_lake"] or int(row["conv_mes"]) + cap = row["cap"] + ppl = row["ppl"] + cpa_max = row["cpa_max"] + gasto = row["coste_mes"] + + ratio_leads = leads / cap if cap > 0 else 0 + ratio_mes = dia_actual / dias_mes + ritmo = ratio_leads - ratio_mes + cpa_actual = gasto / leads if leads > 0 else 0 + revenue = leads * ppl + margen = (revenue - gasto) / revenue if revenue > 0 else 0 + leads_restantes = cap - leads + dias_restantes = dias_mes - dia_actual + + if ratio_leads >= 1.0: + urgencia = "PAUSAR" + elif cap > 0 and ratio_leads < ratio_mes - 0.15 and dias_restantes <= 5: + urgencia = "SPRINT" + elif ritmo < -0.15: + urgencia = "ACELERAR" + elif ritmo > 0.15: + urgencia = "FRENAR" + else: + urgencia = "EN_RITMO" + + return { + "leads": leads, + "cap": cap, + "ratio_leads": ratio_leads, + "ratio_mes": ratio_mes, + "ritmo": ritmo, + "urgencia": urgencia, + "cpa_actual": cpa_actual, + "rentable": cpa_actual <= cpa_max if cpa_actual > 0 else True, + "margen": margen, + "revenue": revenue, + "leads_restantes": leads_restantes, + "dias_restantes": dias_restantes, + } + + +# ── Sidebar ─────────────────────────────────────────────────────────────────── + +st.sidebar.title("Leads Optimizer") +today = date.today() + +col_m, col_a = st.sidebar.columns(2) +mes_sel = col_m.number_input("Mes", min_value=1, max_value=12, value=today.month) +anio_sel = col_a.number_input("Año", min_value=2024, max_value=2030, value=today.year, step=1) + +if st.sidebar.button("🔄 Actualizar datos", use_container_width=True): + st.cache_data.clear() + +st.sidebar.divider() + +# Filtros adicionales (se rellenan tras cargar datos) +urgencia_filter = st.sidebar.multiselect( + "Urgencia", + ["PAUSAR", "SPRINT", "ACELERAR", "FRENAR", "EN_RITMO"], + default=[], + placeholder="Todas", +) +solo_activas = st.sidebar.checkbox("Solo campañas activas", value=True) + +# ── Carga de datos ──────────────────────────────────────────────────────────── + +gcm_rows = _load_gcm(int(mes_sel), int(anio_sel)) + +dia_actual = today.day if (today.month == int(mes_sel) and today.year == int(anio_sel)) else calendar.monthrange(int(anio_sel), int(mes_sel))[1] +dias_mes = calendar.monthrange(int(anio_sel), int(mes_sel))[1] + +# Enriquecer con análisis calculado +for row in gcm_rows: + row["analysis"] = _compute_analysis(row, dia_actual, dias_mes) + +# Aplicar filtros +filtered = gcm_rows +if solo_activas: + filtered = [r for r in filtered if r["status"] == "Activa"] +if urgencia_filter: + filtered = [r for r in filtered if r["analysis"]["urgencia"] in urgencia_filter] + +# Ordenar por urgencia +filtered.sort(key=lambda r: URGENCIA_ORDER.get(r["analysis"]["urgencia"], 9)) + +# ── Título ──────────────────────────────────────────────────────────────────── + +st.title(f"Leads Optimizer · {calendar.month_name[int(mes_sel)]} {int(anio_sel)}") +st.caption(f"Día {dia_actual} de {dias_mes} · {len(filtered)} campañas") + +# ── KPIs globales ───────────────────────────────────────────────────────────── + +total_gasto = sum(r["coste_mes"] for r in filtered) +total_leads = sum(r["analysis"]["leads"] for r in filtered) +total_revenue = sum(r["analysis"]["revenue"] for r in filtered) +total_margen = total_revenue - total_gasto +margen_pct = total_margen / total_revenue if total_revenue > 0 else 0 + +k1, k2, k3, k4, k5 = st.columns(5) +k1.metric("Gasto total", _eur(total_gasto)) +k2.metric("Leads entregados", f"{total_leads:,}") +k3.metric("Revenue estimado", _eur(total_revenue)) +k4.metric("Margen total", _eur(total_margen)) +k5.metric("Margen %", _pct(margen_pct)) + +st.divider() + +# ── Pestañas ────────────────────────────────────────────────────────────────── + +tab_resumen, tab_campanas, tab_historico, tab_ejecucion = st.tabs([ + "📊 Resumen", + "📋 Campañas", + "📈 Histórico", + "⚡ Ejecución", +]) + + +# ════════════════════════════════════════════════════════════════════════════ # +# Tab 1 — RESUMEN # +# ════════════════════════════════════════════════════════════════════════════ # + +with tab_resumen: + # ── Conteo por urgencia ─────────────────────────────────────────────────── + urgencia_counts = {} + for r in filtered: + u = r["analysis"]["urgencia"] + urgencia_counts[u] = urgencia_counts.get(u, 0) + 1 + + u_cols = st.columns(5) + for i, (urg, icon) in enumerate(URGENCIA_ICON.items()): + cnt = urgencia_counts.get(urg, 0) + u_cols[i].metric(f"{icon} {urg}", cnt) + + st.divider() + + # ── Alertas activas ─────────────────────────────────────────────────────── + alerts = [] + for r in filtered: + if r["log"]: + alerts.append((r["nombre"], r["log"])) + a = r["analysis"] + if a["margen"] < -0.5 and a["revenue"] > 0: + alerts.append((r["nombre"], f"Margen muy negativo: {_pct(a['margen'])}")) + + if alerts: + with st.expander(f"⚠️ {len(alerts)} alertas activas", expanded=True): + for nombre, msg in alerts: + st.warning(f"**{nombre}** — {msg}") + + # ── Tabla de campañas ───────────────────────────────────────────────────── + st.subheader("Estado de campañas") + + if not filtered: + st.info("Sin campañas con los filtros actuales.") + else: + table_rows = [] + for r in filtered: + a = r["analysis"] + icon = URGENCIA_ICON.get(a["urgencia"], "") + table_rows.append({ + "Urgencia": f"{icon} {a['urgencia']}", + "Campaña": r["nombre"], + "Leads": a["leads"], + "Cap": a["cap"], + "Ritmo": _pct(a["ritmo"]), + "Gasto": _eur(r["coste_mes"]), + "CPA act.": _eur(a["cpa_actual"]) if a["cpa_actual"] > 0 else "—", + "CPA máx.": _eur(r["cpa_max"]), + "Margen": _pct(a["margen"]) if a["revenue"] > 0 else "—", + "Criticidad": r["criticidad"] or "—", + "Consejo": (r["consejo"] or "")[:80] + ("…" if len(r["consejo"] or "") > 80 else ""), + }) + + df = pd.DataFrame(table_rows) + event = st.dataframe( + df, + use_container_width=True, + hide_index=True, + on_select="rerun", + selection_mode="single-row", + ) + + sel = event.selection.rows + if sel: + row = filtered[sel[0]] + a = row["analysis"] + st.divider() + st.subheader(row["nombre"]) + c1, c2, c3, c4 = st.columns(4) + c1.metric("Leads / Cap", f"{a['leads']} / {a['cap']}") + c2.metric("Ritmo", _pct(a["ritmo"])) + c3.metric("CPA actual", _eur(a["cpa_actual"]) if a["cpa_actual"] > 0 else "—") + c4.metric("Margen", _pct(a["margen"]) if a["revenue"] > 0 else "—") + if row["consejo"]: + st.info(f"💡 {row['consejo']}") + if row["log"]: + st.warning(f"⚠️ {row['log']}") + link = f"https://ads.google.com/aw/campaigns?campaignId={row['google_campaign_id']}" + st.markdown(f"[Abrir en Google Ads ↗]({link})") + + +# ════════════════════════════════════════════════════════════════════════════ # +# Tab 2 — CAMPAÑAS (editor de parámetros) # +# ════════════════════════════════════════════════════════════════════════════ # + +with tab_campanas: + st.subheader("Parámetros de campañas — GACampaignMes") + st.caption("Edita PPL, CPA Máx y Cap directamente. Pulsa Guardar para escribir en Airtable.") + + if not gcm_rows: + st.info("Sin datos para el mes seleccionado.") + else: + editor_data = pd.DataFrame([ + { + "Campaña": r["nombre"], + "Status": r["status"], + "PPL (€)": r["ppl"], + "CPA Máx (€)": r["cpa_max"], + "Cap mes": r["cap"], + "Gasto": _eur(r["coste_mes"]), + "Leads lake": r["leads_lake"], + "_id": r["airtable_id"], + } + for r in gcm_rows + ]) + + edited = st.data_editor( + editor_data.drop(columns=["_id"]), + use_container_width=True, + hide_index=True, + disabled=["Campaña", "Status", "Gasto", "Leads lake"], + column_config={ + "PPL (€)": st.column_config.NumberColumn(min_value=0.0, format="%.2f"), + "CPA Máx (€)": st.column_config.NumberColumn(min_value=0.0, format="%.2f"), + "Cap mes": st.column_config.NumberColumn(min_value=0, step=1), + }, + ) + + if st.button("💾 Guardar cambios en Airtable", type="primary"): + at = AirtableClient() + batch = [] + for i, orig in enumerate(gcm_rows): + new_ppl = float(edited.at[i, "PPL (€)"]) + new_cpa = float(edited.at[i, "CPA Máx (€)"]) + new_cap = int(edited.at[i, "Cap mes"]) + changes = {} + if new_ppl != orig["ppl"]: + changes["PPL"] = new_ppl + if new_cpa != orig["cpa_max"]: + changes["CPAMax"] = new_cpa + if new_cap != orig["cap"]: + changes["CapTotalMes"] = new_cap + if changes: + batch.append({"id": orig["airtable_id"], "fields": changes}) + + if batch: + for i in range(0, len(batch), 10): + at.gacampaignmes.batch_update(batch[i:i+10]) + st.success(f"✅ {len(batch)} registros actualizados.") + st.cache_data.clear() + else: + st.info("Sin cambios detectados.") + + st.divider() + st.subheader("Cappings por curso — CursoMes") + st.caption("Ajusta el capping mensual admitido por curso.") + + cursomes_rows = _load_cursomes(int(mes_sel), int(anio_sel)) + if not cursomes_rows: + st.info("Sin registros de CursoMes para el período seleccionado.") + else: + cm_data = pd.DataFrame([ + { + "Curso ID": r["cursoid"], + "Nombre": r["nombre"], + "Cap admitido": r["cap"], + "_id": r["airtable_id"], + } + for r in cursomes_rows + ]) + + cm_edited = st.data_editor( + cm_data.drop(columns=["_id"]), + use_container_width=True, + hide_index=True, + disabled=["Curso ID", "Nombre"], + column_config={ + "Cap admitido": st.column_config.NumberColumn(min_value=0, step=1), + }, + ) + + if st.button("💾 Guardar cappings en Airtable", type="primary", key="save_cursomes"): + at = AirtableClient() + batch = [] + for i, orig in enumerate(cursomes_rows): + new_cap = int(cm_edited.at[i, "Cap admitido"]) + if new_cap != orig["cap"]: + batch.append({"id": orig["airtable_id"], "fields": {"Caping Admitido": new_cap}}) + if batch: + for j in range(0, len(batch), 10): + at.cursomes.batch_update(batch[j:j+10]) + st.success(f"✅ {len(batch)} cappings actualizados.") + st.cache_data.clear() + else: + st.info("Sin cambios detectados.") + + +# ════════════════════════════════════════════════════════════════════════════ # +# Tab 3 — HISTÓRICO # +# ════════════════════════════════════════════════════════════════════════════ # + +with tab_historico: + st.subheader("Métricas diarias por campaña") + + camp_names = [r["nombre"] for r in filtered if r["metricas"]] + if not camp_names: + st.info("Sin métricas diarias disponibles para las campañas filtradas.") + else: + sel_camp = st.selectbox("Campaña", camp_names, key="hist_camp") + camp_row = next((r for r in filtered if r["nombre"] == sel_camp), None) + + if camp_row and camp_row["metricas"]: + metricas = camp_row["metricas"] + # MetricasDiarias puede ser dict {fecha: {coste, ingresos, margen, leads, ...}} + # o list [{fecha, coste, ...}] + if isinstance(metricas, dict): + daily = [{"fecha": k, **v} for k, v in sorted(metricas.items())] + else: + daily = sorted(metricas, key=lambda x: x.get("fecha", "")) + + if daily: + df_daily = pd.DataFrame(daily) + st.caption(f"Columnas raw: {list(df_daily.columns)}") + + # Normalizar nombres de columnas (pueden variar) + col_map = {} + for c in df_daily.columns: + cl = c.lower() + if c in col_map.values(): + continue + if ("fecha" in cl or "date" in cl) and "Fecha" not in col_map.values(): + col_map[c] = "Fecha" + elif ("coste" in cl or "cost" in cl) and "Gasto" not in col_map.values(): + col_map[c] = "Gasto" + elif ("ingres" in cl or "revenue" in cl) and "Revenue" not in col_map.values(): + col_map[c] = "Revenue" + elif ("margen" in cl or "margin" in cl) and "Margen" not in col_map.values(): + col_map[c] = "Margen" + elif "lead" in cl and "Leads" not in col_map.values(): + col_map[c] = "Leads" + df_daily = df_daily.rename(columns=col_map) + + # KPIs del período + d1, d2, d3, d4 = st.columns(4) + if "Gasto" in df_daily: + d1.metric("Gasto total", _eur(df_daily["Gasto"].sum())) + if "Revenue" in df_daily: + d2.metric("Revenue total", _eur(df_daily["Revenue"].sum())) + if "Margen" in df_daily: + gasto_sum = df_daily["Gasto"].sum() if "Gasto" in df_daily else 1 + rev_sum = df_daily["Revenue"].sum() if "Revenue" in df_daily else 0 + margen_total = rev_sum - gasto_sum + d3.metric("Margen €", _eur(margen_total)) + lead_col = next((c for c in df_daily.columns if "lead" in c.lower()), None) + if lead_col: + d4.metric("Leads totales", int(pd.to_numeric(df_daily[lead_col], errors="coerce").fillna(0).sum())) + + # Gráfico de ritmo: ratio_leads vs ratio_mes día a día + if "Leads" in df_daily and camp_row["cap"] > 0: + st.markdown("**Ritmo: leads acumulados vs objetivo esperado**") + df_ritmo = df_daily[["Fecha", "Leads"]].copy() if "Fecha" in df_daily else df_daily[["Leads"]].copy() + df_ritmo["Leads acum."] = df_daily["Leads"].cumsum() + df_ritmo["Objetivo"] = [ + round((i + 1) / dias_mes * camp_row["cap"], 1) + for i in range(len(df_ritmo)) + ] + if "Fecha" in df_ritmo.columns: + df_ritmo = df_ritmo.set_index("Fecha") + st.line_chart(df_ritmo[["Leads acum.", "Objetivo"]]) + + # Gráfico gasto vs revenue + numeric_cols = [c for c in ["Gasto", "Revenue"] if c in df_daily.columns] + if numeric_cols: + st.markdown("**Gasto vs Revenue diario**") + chart_data = df_daily[numeric_cols].copy() + if "Fecha" in df_daily.columns: + chart_data.index = df_daily["Fecha"] + st.bar_chart(chart_data) + + # Tabla detalle + st.markdown("**Detalle diario**") + display_cols = [c for c in ["Fecha", "Leads", "Gasto", "Revenue", "Margen"] if c in df_daily.columns] + st.dataframe(df_daily[display_cols], use_container_width=True, hide_index=True) + else: + st.info("Sin datos diarios para esta campaña.") + else: + st.info("Sin métricas diarias para esta campaña.") + + st.divider() + st.subheader("Portfolio agregado (GAMes)") + + @st.cache_data(ttl=300, show_spinner="Cargando portfolio...") + def _load_games(mes: int, anio: int) -> dict: + at = AirtableClient() + formula = f"AND({{Mes}}='{mes}',{{Año}}='{anio}')" + records = at.games.all(formula=formula) + if not records: + return {} + f = records[0]["fields"] + try: + metricas = json.loads(f.get("MetricasDiarias") or "{}") + except Exception: + metricas = {} + return { + "metricas": metricas, + "coste_mes": float(f.get("CosteMes") or 0), + "conv_mes": float(f.get("ConvMes") or 0), + "ppl_medio": float(f.get("PPLMedio") or 0), + "cpa_medio": float(f.get("CPAMedio") or 0), + } + + games = _load_games(int(mes_sel), int(anio_sel)) + if games: + g1, g2, g3, g4 = st.columns(4) + g1.metric("Gasto mes (portfolio)", _eur(games["coste_mes"])) + g2.metric("Leads mes (portfolio)", f"{games['conv_mes']:.0f}") + g3.metric("PPL medio", _eur(games["ppl_medio"])) + g4.metric("CPA medio", _eur(games["cpa_medio"])) + + if games["metricas"]: + if isinstance(games["metricas"], dict): + gdf = pd.DataFrame([{"Fecha": k, **v} for k, v in sorted(games["metricas"].items())]) + else: + gdf = pd.DataFrame(sorted(games["metricas"], key=lambda x: x.get("fecha", ""))) + + col_map = {} + for c in gdf.columns: + cl = c.lower() + if "coste" in cl or "cost" in cl: + col_map[c] = "Gasto" + elif "ingres" in cl or "revenue" in cl: + col_map[c] = "Revenue" + elif "margen" in cl: + col_map[c] = "Margen" + elif "lead" in cl or "conv" in cl: + col_map[c] = "Leads" + elif "fecha" in cl or "date" in cl: + col_map[c] = "Fecha" + gdf = gdf.rename(columns=col_map) + + numeric_cols = [c for c in ["Gasto", "Revenue"] if c in gdf.columns] + if numeric_cols: + st.markdown("**Portfolio: Gasto vs Revenue diario**") + chart_data = gdf[numeric_cols].copy() + if "Fecha" in gdf.columns: + chart_data.index = gdf["Fecha"] + st.bar_chart(chart_data) + else: + st.info("Sin datos de portfolio para el período seleccionado.") + + +# ════════════════════════════════════════════════════════════════════════════ # +# Tab 4 — EJECUCIÓN # +# ════════════════════════════════════════════════════════════════════════════ # + +with tab_ejecucion: + st.subheader("Lanzar run.py") + + col_dry, col_prod = st.columns(2) + + with col_dry: + st.markdown("**Dry Run** — Solo análisis, sin cambios en Google Ads") + if st.button("▶ Ejecutar Dry Run", use_container_width=True): + st.session_state["run_mode"] = "dry" + st.session_state["run_started"] = True + + with col_prod: + st.markdown("**Producción** — Aplica cambios reales en Google Ads") + confirm = st.checkbox("Confirmo que quiero ejecutar en producción") + prod_disabled = not confirm + if st.button("🚀 Ejecutar en Producción", use_container_width=True, disabled=prod_disabled): + st.session_state["run_mode"] = "prod" + st.session_state["run_started"] = True + + if st.session_state.get("run_started"): + st.session_state["run_started"] = False + mode = st.session_state.get("run_mode", "dry") + env = os.environ.copy() + env["DRY_RUN"] = "true" if mode == "dry" else "false" + + run_py = os.path.join(os.path.dirname(__file__), "run.py") + st.info(f"Ejecutando run.py en modo {'Dry Run' if mode == 'dry' else 'PRODUCCIÓN'}…") + log_box = st.empty() + lines = [] + + with st.spinner("En ejecución…"): + proc = subprocess.Popen( + [sys.executable, run_py], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + encoding="utf-8", + errors="replace", + env=env, + cwd=os.path.dirname(__file__), + ) + for line in proc.stdout: + lines.append(line.rstrip()) + log_box.code("\n".join(lines[-60:]), language="") + proc.wait() + + if proc.returncode == 0: + st.success("✅ Ejecución completada correctamente.") + else: + st.error(f"❌ Terminó con código {proc.returncode}.") + + st.cache_data.clear() + + st.divider() + st.subheader("Historial de ejecuciones") + + logs_dir = os.path.join(os.path.dirname(__file__), "logs") + log_files = sorted(glob.glob(os.path.join(logs_dir, "*.log")), reverse=True) + + if not log_files: + st.info("Sin logs guardados. Los logs se generan al ejecutar run.py.") + else: + log_names = [os.path.basename(f) for f in log_files] + sel_log = st.selectbox("Fichero de log", log_names) + if sel_log: + log_path = os.path.join(logs_dir, sel_log) + with open(log_path, encoding="utf-8", errors="replace") as fh: + content = fh.read() + st.code(content, language="") diff --git a/docs/Documentación.pdf b/docs/Documentación.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c5e26ef2bc8f4276a22426abb9d4584cd88236c9 GIT binary patch literal 72839 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.32.0 +streamlit>=1.35.0 +pandas>=2.0.0