Replace ambiguous cost-vs-revenue bar charts with clearer line charts

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.
This commit is contained in:
Jose Manuel 2026-07-03 00:43:21 +02:00
parent 8cb5832f8b
commit ea201ef565

View File

@ -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 (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: def _daily_summary(rows: list[dict]) -> pd.DataFrame:
"""Agrega Inversión/Ingreso/Margen día a día sumando las MetricasDiarias """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 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: if df_summary.empty:
st.info("Sin métricas diarias disponibles para las campañas filtradas.") st.info("Sin métricas diarias disponibles para las campañas filtradas.")
else: else:
df_long = df_summary.melt("Fecha", var_name="Serie", value_name="Valor") _eur_line_chart(df_summary, ["Inversión", "Ingreso", "Margen (sumatorio)", "Margen (PPL)"])
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")
st.caption("Margen (sumatorio) = Ingreso reportado Gasto · Margen (PPL) = Leads del día × PPL de campaña Gasto") st.caption("Margen (sumatorio) = Ingreso reportado Gasto · Margen (PPL) = Leads del día × PPL de campaña Gasto")
st.divider() st.divider()
@ -610,14 +626,11 @@ with hist_camp_tab:
df_ritmo = df_ritmo.set_index("Fecha") df_ritmo = df_ritmo.set_index("Fecha")
st.line_chart(df_ritmo[["Leads acum.", "Objetivo"]]) st.line_chart(df_ritmo[["Leads acum.", "Objetivo"]])
# Gráfico gasto vs revenue # Gráfico gasto / ingreso / margen diario
numeric_cols = [c for c in ["Gasto", "Revenue"] if c in df_daily.columns] numeric_cols = [c for c in ["Gasto", "Revenue", "Margen"] if c in df_daily.columns]
if numeric_cols: if numeric_cols and "Fecha" in df_daily.columns:
st.markdown("**Gasto vs Revenue diario**") st.markdown("**Gasto, ingreso y margen diario**")
chart_data = df_daily[numeric_cols].copy() _eur_line_chart(df_daily.rename(columns={"Revenue": "Ingreso"}), ["Gasto", "Ingreso", "Margen"])
if "Fecha" in df_daily.columns:
chart_data.index = df_daily["Fecha"]
st.bar_chart(chart_data)
# Tabla detalle # Tabla detalle
st.markdown("**Detalle diario**") st.markdown("**Detalle diario**")
@ -680,13 +693,10 @@ with hist_portfolio_tab:
col_map[c] = "Fecha" col_map[c] = "Fecha"
gdf = gdf.rename(columns=col_map) gdf = gdf.rename(columns=col_map)
numeric_cols = [c for c in ["Gasto", "Revenue"] if c in gdf.columns] numeric_cols = [c for c in ["Gasto", "Revenue", "Margen"] if c in gdf.columns]
if numeric_cols: if numeric_cols and "Fecha" in gdf.columns:
st.markdown("**Portfolio: Gasto vs Revenue diario**") st.markdown("**Portfolio: gasto, ingreso y margen diario**")
chart_data = gdf[numeric_cols].copy() _eur_line_chart(gdf.rename(columns={"Revenue": "Ingreso"}), ["Gasto", "Ingreso", "Margen"])
if "Fecha" in gdf.columns:
chart_data.index = gdf["Fecha"]
st.bar_chart(chart_data)
else: else:
st.info("Sin datos de portfolio para el período seleccionado.") st.info("Sin datos de portfolio para el período seleccionado.")