From ea201ef5652c14564d3dea4b5ceabee51e424a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20G=C3=B3mez?= Date: Fri, 3 Jul 2026 00:43:21 +0200 Subject: [PATCH] Replace ambiguous cost-vs-revenue bar charts with clearer line charts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stacked/grouped bars made it hard to compare gasto vs ingreso when the two values are close, and margen barely visible against that scale. Extract a shared _eur_line_chart helper (single €-axis, fixed categorical colors, tooltip) and reuse it for the per-campaign, portfolio, and resumen daily views instead of duplicating Altair boilerplate per chart. --- dashboard.py | 76 +++++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/dashboard.py b/dashboard.py index 72f9675..0dfd126 100644 --- a/dashboard.py +++ b/dashboard.py @@ -197,6 +197,39 @@ def _compute_analysis(row: dict, dia_actual: int, dias_mes: int) -> dict: } +_SERIE_COLORS = { + "Inversión": "#2a78d6", + "Gasto": "#2a78d6", + "Ingreso": "#1baf7a", + "Revenue": "#1baf7a", + "Margen": "#4a3aa7", + "Margen (sumatorio)": "#4a3aa7", + "Margen (PPL)": "#eb6834", +} + + +def _eur_line_chart(df: pd.DataFrame, series: list[str], height: int = 320): + """Gráfico de líneas en € para comparar varias series por día. Un solo eje + (todas las series están en €), colores categóricos fijos y tooltip — evita + la ambigüedad de un gráfico de barras apiladas/agrupadas para comparar + magnitudes que pueden ser muy parecidas entre sí (p.ej. gasto vs ingreso).""" + series = [s for s in series if s in df.columns] + df_long = df.melt("Fecha", value_vars=series, var_name="Serie", value_name="Valor") + color_scale = alt.Scale(domain=series, range=[_SERIE_COLORS[s] for s in series]) + chart = ( + alt.Chart(df_long) + .mark_line(point=True, strokeWidth=2) + .encode( + x=alt.X("Fecha:O", title=None), + y=alt.Y("Valor:Q", title="€"), + color=alt.Color("Serie:N", scale=color_scale, sort=series, legend=alt.Legend(title=None)), + tooltip=["Fecha", "Serie", alt.Tooltip("Valor:Q", format=",.2f")], + ) + .properties(height=height) + ) + st.altair_chart(chart, width="stretch") + + def _daily_summary(rows: list[dict]) -> pd.DataFrame: """Agrega Inversión/Ingreso/Margen día a día sumando las MetricasDiarias de todas las campañas dadas, más un margen alternativo calculado con el @@ -340,24 +373,7 @@ with tab_resumen: if df_summary.empty: st.info("Sin métricas diarias disponibles para las campañas filtradas.") else: - df_long = df_summary.melt("Fecha", var_name="Serie", value_name="Valor") - serie_order = ["Inversión", "Ingreso", "Margen (sumatorio)", "Margen (PPL)"] - color_scale = alt.Scale( - domain=serie_order, - range=["#2a78d6", "#1baf7a", "#4a3aa7", "#eb6834"], - ) - chart = ( - alt.Chart(df_long) - .mark_line(point=True, strokeWidth=2) - .encode( - x=alt.X("Fecha:O", title=None), - y=alt.Y("Valor:Q", title="€"), - color=alt.Color("Serie:N", scale=color_scale, sort=serie_order, legend=alt.Legend(title=None)), - tooltip=["Fecha", "Serie", alt.Tooltip("Valor:Q", format=",.2f")], - ) - .properties(height=320) - ) - st.altair_chart(chart, width="stretch") + _eur_line_chart(df_summary, ["Inversión", "Ingreso", "Margen (sumatorio)", "Margen (PPL)"]) st.caption("Margen (sumatorio) = Ingreso reportado − Gasto · Margen (PPL) = Leads del día × PPL de campaña − Gasto") st.divider() @@ -610,14 +626,11 @@ with hist_camp_tab: 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) + # Gráfico gasto / ingreso / margen diario + numeric_cols = [c for c in ["Gasto", "Revenue", "Margen"] if c in df_daily.columns] + if numeric_cols and "Fecha" in df_daily.columns: + st.markdown("**Gasto, ingreso y margen diario**") + _eur_line_chart(df_daily.rename(columns={"Revenue": "Ingreso"}), ["Gasto", "Ingreso", "Margen"]) # Tabla detalle st.markdown("**Detalle diario**") @@ -680,13 +693,10 @@ with hist_portfolio_tab: 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) + numeric_cols = [c for c in ["Gasto", "Revenue", "Margen"] if c in gdf.columns] + if numeric_cols and "Fecha" in gdf.columns: + st.markdown("**Portfolio: gasto, ingreso y margen diario**") + _eur_line_chart(gdf.rename(columns={"Revenue": "Ingreso"}), ["Gasto", "Ingreso", "Margen"]) else: st.info("Sin datos de portfolio para el período seleccionado.")