# app.py — Cabanna Dashboard Unificado
# ============================================================
# MODO RAW (CSV): KPIs, comparativas, mix, tendencia, acciones sugeridas
# MODO EXPORTS: Afinidad, Clusters, Productos Ancla (desde exports/)
# Estilo: oscuro + acentos dorados
# ============================================================

import os
import json
from datetime import timedelta

import numpy as np
import pandas as pd
import streamlit as st
import plotly.express as px
import plotly.graph_objects as go

# ----------------------------- CONFIG VISUAL -----------------------------
st.set_page_config(page_title="AquaViva Grill — Dashboard", page_icon="🍤", layout="wide")
BG = "#0e0e0f"; FG = "#ffffff"; GOLD = "#d4af37"; ACCENT = "#b08d28"
TEMPLATE = "plotly_dark"
EXPORTS_DIR = "exports"

st.markdown(
    f"""
    <style>
    .stApp {{
        background-color: {BG};
        color: {FG};
    }}
    div[data-testid="stMetricValue"] {{
        color: {GOLD};
    }}
    .block-container {{
        padding-top: 1rem;
        padding-bottom: 1rem;
    }}
    </style>
    """,
    unsafe_allow_html=True
)

def style_plot(fig):
    fig.update_layout(template=TEMPLATE, font_color=FG, title_x=0.02, plot_bgcolor=BG, paper_bgcolor=BG)
    return fig

def file_exists(path: str) -> bool:
    return path and os.path.exists(path) and os.path.isfile(path)

def load_csv_safe(path: str, **kwargs) -> pd.DataFrame:
    if file_exists(path):
        try:
            return pd.read_csv(path, **kwargs)
        except Exception as e:
            st.warning(f"⚠️ No pude leer {path}: {e}")
    return pd.DataFrame()

def load_json_safe(path: str) -> dict:
    if file_exists(path):
        try:
            with open(path, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception as e:
            st.warning(f"⚠️ No pude leer {path}: {e}")
    return {}

@st.cache_data(show_spinner=False)
def load_exports(exports_dir: str):
    paths = {
        "afinidad_pairs": os.path.join(exports_dir, "afinidad_pairs.csv"),
        "afinidad_graph": os.path.join(exports_dir, "afinidad_graph.json"),
        "productos_feats": os.path.join(exports_dir, "productos_feats.csv"),
        "cluster_summary": os.path.join(exports_dir, "cluster_summary.csv"),
        "productos_ancla": os.path.join(exports_dir, "productos_ancla.csv"),
    }
    data = {
        "afinidad_pairs": load_csv_safe(paths["afinidad_pairs"]),
        "afinidad_graph": load_json_safe(paths["afinidad_graph"]),
        "productos_feats": load_csv_safe(paths["productos_feats"]),
        "cluster_summary": load_csv_safe(paths["cluster_summary"]),
        "productos_ancla": load_csv_safe(paths["productos_ancla"]),
        "paths": paths,
    }
    return data

def es_bebida(clasif: str) -> bool:
    return isinstance(clasif, str) and (("bebida" in clasif.lower()) or ("cerveza" in clasif.lower()))

# =================================================================
#  NUEVAS FUNCIONES PARA GENERAR DATOS FICTICIOS
# =================================================================
@st.cache_data(show_spinner="Generando datos de ejemplo...")
def generar_datos_ficticios(num_dias=90, num_transacciones_dia=200):
    """Genera un DataFrame de ventas de restaurante con datos aleatorios."""
    np.random.seed(42)
    
    # --- Catálogos Ficticios ---
    SUCURSALES = ["AquaViva Mazatlán", "AquaViva Vallarta", "AquaViva Cancún"]
    
    PLATILLOS = {
        "Entradas Frías": [("Ceviche de la Casa", 180), ("Aguachile Verde", 210), ("Tostadas de Atún Sellado", 230)],
        "Entradas Calientes": [("Queso Fundido con Camarón", 190), ("Tacos Gobernador", 170)],
        "Platos Fuertes": [("Pescado Zarandeado", 350), ("Filete de Res Mar y Tierra", 450), ("Pulpo a las Brasas", 380)],
        "Tacos y Especiales": [("Tacos de Pescado Baja", 150), ("Tacos de Pulpo Enchilado", 180)],
        "Bebidas (sin alcohol)": [("Limonada Mineral", 60), ("Naranjada Fresca", 60), ("Agua de Jamaica con Menta", 55)],
        "Cervezas y Coctelería": [("Margarita Clásica", 140), ("Mojito Cubano", 130), ("Cerveza Pacífico", 70), ("Cerveza Artesanal Local", 95)]
    }

    # --- Lógica de Generación ---
    rng_fechas = pd.date_range(end=pd.Timestamp.now(), periods=num_dias, freq='D')
    rows = []
    
    for fecha in rng_fechas:
        for sucursal in SUCURSALES:
            # Simular un número variable de transacciones por día y sucursal
            num_transacciones_actual = np.random.randint(num_transacciones_dia - 50, num_transacciones_dia + 50)
            for _ in range(num_transacciones_actual):
                clasificacion = np.random.choice(list(PLATILLOS.keys()), p=[0.15, 0.1, 0.25, 0.15, 0.15, 0.2])
                nombre_platillo, precio_base = PLATILLOS[clasificacion][np.random.randint(0, len(PLATILLOS[clasificacion]))]
                
                # Simular pequeñas variaciones en precio y cantidad
                precio_final = precio_base * np.random.uniform(0.98, 1.03)
                platillos_vendidos = np.random.randint(1, 4)
                
                rows.append({
                    "FECHA": fecha,
                    "SUCURSAL": sucursal,
                    "NOMBRE_PLATILLO": nombre_platillo,
                    "PRECIO_PLATILLO": round(precio_final, 2),
                    "CLASIFICACION": clasificacion,
                    "PLATILLOS_VENDIDOS": platillos_vendidos,
                    "INGRESO_ESTIMADO": round(precio_final * platillos_vendidos, 2)
                })
    
    return pd.DataFrame(rows)

def generar_metas_ficticias(sucursales):
    """Genera un DataFrame de metas diarias para cada sucursal."""
    metas_data = {
        "SUCURSAL": sucursales,
        "META_DIARIA": [np.random.randint(25000, 45000) for _ in sucursales]
    }
    return pd.DataFrame(metas_data)


# ----------------------------- SIDEBAR -----------------------------
st.sidebar.title("Modo de uso")
modo = st.sidebar.radio("Selecciona el modo", ["RAW (CSV)", "EXPORTS (desde notebook)"])

# ----------------------------- HEADER -----------------------------
st.title("Dashboard Ejecutivo")
st.caption("KPIs y operación día a día (RAW) + Afinidad/Clusters/Anclas (EXPORTS).")





# =================================================================
#                  MODO RAW (CON DATOS FICTICIOS)
# =================================================================
if modo == "RAW (CSV)":
    st.sidebar.markdown("---")
    st.sidebar.header("Datos de Muestra")
    st.sidebar.info("Este modo ahora usa datos ficticios generados automáticamente para demostración. No es necesario subir archivos.")

    # ---------- GENERACIÓN Y CARGA DE DATOS FICTICIOS ----------
    df = generar_datos_ficticios()
    metas = generar_metas_ficticias(df['SUCURSAL'].unique().tolist())
    
    # ---------- FILTROS ----------
    min_date, max_date = df["FECHA"].min(), df["FECHA"].max()
    c1, c2, c3 = st.columns([1,1,2])
    with c1:
        sucursales = ["Todas"] + sorted(df["SUCURSAL"].dropna().unique().tolist())
        suc_sel = st.selectbox("Sucursal", sucursales, index=0)
    with c2:
        default_start = max(min_date, max_date - timedelta(days=30))
        date_range = st.date_input("Rango de fechas",
                                   value=(default_start.date(), max_date.date()),
                                   min_value=min_date.date(), max_value=max_date.date())
    with c3:
        freq = st.select_slider("Frecuencia para tendencia", options=["Día","Semana","Mes"], value="Día")

    start_date, end_date = pd.to_datetime(date_range[0]), pd.to_datetime(date_range[1])
    mask = df["FECHA"].between(start_date, end_date)
    if suc_sel != "Todas":
        mask &= (df["SUCURSAL"] == suc_sel)
    dff = df.loc[mask].copy()
    if dff.empty:
        st.warning("No hay datos para ese filtro.")
        st.stop()

    # ---------- KPIs ----------
    ingreso_total = dff["INGRESO_ESTIMADO"].sum()
    platillos_total = dff["PLATILLOS_VENDIDOS"].sum()
    ticket_prom = (ingreso_total / platillos_total) if platillos_total > 0 else np.nan

    dff["TIPO"] = np.where(dff["CLASIFICACION"].apply(es_bebida), "Bebidas", "Alimentos")
    ingreso_por_tipo = dff.groupby("TIPO", as_index=False)["INGRESO_ESTIMADO"].sum()
    ing_beb = ingreso_por_tipo.query("TIPO == 'Bebidas'")["INGRESO_ESTIMADO"].sum()
    ing_alim = ingreso_por_tipo.query("TIPO == 'Alimentos'")["INGRESO_ESTIMADO"].sum()
    pct_beb = (ing_beb/ingreso_total*100) if ingreso_total>0 else 0
    pct_ali = (ing_alim/ingreso_total*100) if ingreso_total>0 else 0

    top_prod = (dff.groupby("NOMBRE_PLATILLO", as_index=False)
                   .agg(INGRESO=("INGRESO_ESTIMADO","sum"), CANT=("PLATILLOS_VENDIDOS","sum"))
                   .sort_values("INGRESO", ascending=False).head(1))
    prod_top_name = top_prod["NOMBRE_PLATILLO"].iloc[0] if not top_prod.empty else "-"
    prod_top_ing  = top_prod["INGRESO"].iloc[0] if not top_prod.empty else 0.0
    prod_top_qty  = top_prod["CANT"].iloc[0] if not top_prod.empty else 0

    # Meta / Cumplimiento
    meta_total = cumpl_pct = gap_abs = None
    if metas is not None:
        dias = (end_date - start_date).days + 1
        if suc_sel == "Todas":
            meta_dia_all = metas["META_DIARIA"].sum()
            meta_total = meta_dia_all * dias
        else:
            m = metas.loc[metas["SUCURSAL"] == suc_sel, "META_DIARIA"]
            if not m.empty:
                meta_total = float(m.iloc[0]) * dias
        if meta_total and meta_total > 0:
            cumpl_pct = ingreso_total / meta_total * 100.0
            gap_abs = ingreso_total - meta_total

    # ---------- CARDS ----------
    k1, k2, k3, k4 = st.columns(4)
    k1.metric("Ingreso total", f"${ingreso_total:,.0f}")
    k2.metric("Ticket (aprox)", f"${ticket_prom:,.2f}")
    k3.metric("% Bebidas vs Alimentos", f"{pct_beb:.1f}% / {pct_ali:.1f}%")
    k4.metric("Producto TOP (ingreso)", f"{prod_top_name}", help=f"Ingreso: ${prod_top_ing:,.0f} • Uds: {int(prod_top_qty)}")

    if meta_total is not None:
        k5, k6 = st.columns(2)
        k5.metric("Cumplimiento vs meta", f"{cumpl_pct:.1f}%" if cumpl_pct is not None else "—")
        k6.metric("GAP (Ingreso - Meta)", f"${gap_abs:,.0f}" if gap_abs is not None else "—")

    st.markdown("---")
    
    # ---------- COMPARATIVA ENTRE SUCURSALES ----------
    st.subheader("Comparativa por sucursal")
    comp = (df[df["FECHA"].between(start_date, end_date)]
            .groupby("SUCURSAL", as_index=False)
            .agg(INGRESO=("INGRESO_ESTIMADO","sum"),
                 PLATILLOS=("PLATILLOS_VENDIDOS","sum")))
    comp["TICKET_APROX"] = comp.apply(lambda r: r["INGRESO"]/r["PLATILLOS"] if r["PLATILLOS"]>0 else np.nan, axis=1)

    fig_comp = px.bar(comp.sort_values("INGRESO", ascending=False), x="SUCURSAL", y="INGRESO",
                      text="INGRESO", title="Ingreso total por sucursal")
    fig_comp.update_traces(texttemplate='%{text:,.0f}', textposition='outside')
    st.plotly_chart(style_plot(fig_comp), use_container_width=True)

    with st.expander("Ver Top 3 productos por sucursal (ingreso y volumen)"):
        for s in comp["SUCURSAL"]:
            dfs = df[(df["SUCURSAL"] == s) & (df["FECHA"].between(start_date, end_date))]
            tops_ing = (dfs.groupby("NOMBRE_PLATILLO", as_index=False)
                        .agg(INGRESO=("INGRESO_ESTIMADO","sum"))
                        .sort_values("INGRESO", ascending=False).head(3))
            tops_vol = (dfs.groupby("NOMBRE_PLATILLO", as_index=False)
                        .agg(VOLUMEN=("PLATILLOS_VENDIDOS","sum"))
                        .sort_values("VOLUMEN", ascending=False).head(3))
            st.markdown(f"**Sucursal: {s}**")
            cA, cB = st.columns(2)
            with cA:
                st.write("Top 3 por **ingreso**"); st.dataframe(tops_ing)
            with cB:
                st.write("Top 3 por **volumen**"); st.dataframe(tops_vol)

    st.markdown("---")

    # ---------- MIX POR CLASIFICACIÓN ----------
    st.subheader("Mix por clasificación")
    mix = (dff.groupby("CLASIFICACION", as_index=False)
           .agg(INGRESO=("INGRESO_ESTIMADO","sum"), PLATILLOS=("PLATILLOS_VENDIDOS","sum")))
    mix["%_INGRESO"] = (mix["INGRESO"]/mix["INGRESO"].sum()*100).round(1)

    cA, cB = st.columns(2)
    with cA:
        fig_mix = px.pie(mix, names="CLASIFICACION", values="INGRESO", title="% ingreso por clasificación", hole=0.45)
        st.plotly_chart(style_plot(fig_mix), use_container_width=True)
    with cB:
        fig_mix_bar = px.bar(mix.sort_values("INGRESO", ascending=False), x="CLASIFICACION", y="INGRESO",
                             text="INGRESO", title="Ingreso por clasificación")
        fig_mix_bar.update_traces(texttemplate='%{text:,.0f}', textposition='outside')
        st.plotly_chart(style_plot(fig_mix_bar), use_container_width=True)

    st.markdown("---")

    # ---------- TENDENCIA ----------
    st.subheader("Tendencia temporal")
    dft = df[df["FECHA"].between(start_date, end_date)].copy()
    if suc_sel != "Todas":
        dft = dft[dft["SUCURSAL"] == suc_sel]

    if freq == "Día":
        dft2 = dft.groupby("FECHA", as_index=False)["INGRESO_ESTIMADO"].sum()
    elif freq == "Semana":
        dft["WEEK"] = dft["FECHA"].dt.to_period("W").apply(lambda r: r.start_time)
        dft2 = dft.groupby("WEEK", as_index=False)["INGRESO_ESTIMADO"].sum().rename(columns={"WEEK":"FECHA"})
    else:
        dft["MES"] = dft["FECHA"].dt.to_period("M").astype("datetime64[ns]")
        dft2 = dft.groupby("MES", as_index=False)["INGRESO_ESTIMADO"].sum().rename(columns={"MES":"FECHA"})

    fig_line = px.line(dft2, x="FECHA", y="INGRESO_ESTIMADO", markers=True, title=f"Ingreso por {freq.lower()}")
    st.plotly_chart(style_plot(fig_line), use_container_width=True)

    st.markdown("---")

    # ---------- ACCIONES SUGERIDAS ----------
    st.subheader("Acciones sugeridas (reglas simples)")
    hist = df.copy()
    hist["TIPO"] = np.where(hist["CLASIFICACION"].apply(es_bebida), "Bebidas", "Alimentos")

    base_ref = hist.groupby(["SUCURSAL","TIPO"], as_index=False)["INGRESO_ESTIMADO"].sum()
    base_ref_total = base_ref.groupby("SUCURSAL", as_index=False)["INGRESO_ESTIMADO"].sum().rename(columns={"INGRESO_ESTIMADO":"TOTAL"})
    base_ref = base_ref.merge(base_ref_total, on="SUCURSAL", how="left")
    base_ref["PCT_HIST"] = np.where(base_ref["TOTAL"]>0, base_ref["INGRESO_ESTIMADO"]/base_ref["TOTAL"]*100, np.nan)

    act = dff.groupby(["SUCURSAL","TIPO"], as_index=False)["INGRESO_ESTIMADO"].sum()
    act_total = act.groupby("SUCURSAL", as_index=False)["INGRESO_ESTIMADO"].sum().rename(columns={"INGRESO_ESTIMADO":"TOTAL"})
    act = act.merge(act_total, on="SUCURSAL", how="left")
    act["PCT_ACT"] = np.where(act["TOTAL"]>0, act["INGRESO_ESTIMADO"]/act["TOTAL"]*100, np.nan)

    cmp = act.merge(base_ref[["SUCURSAL","TIPO","PCT_HIST"]], on=["SUCURSAL","TIPO"], how="left")
    cmp["DELTA_PCT"] = cmp["PCT_ACT"] - cmp["PCT_HIST"]

    def recomendar(row):
        tip, dpp = row["TIPO"], row["DELTA_PCT"]
        if pd.isna(dpp): return "Sin referencia histórica"
        if dpp <= -8:
            return "Lanzar promo de bebidas y upselling." if tip == "Bebidas" else "Impulsar platos estrella y combos con bebida."
        if dpp >= 10:
            return "Mantener estrategia y asegurar inventario."
        return "Monitorear y ajustar fino."

    cmp["ACCION"] = cmp.apply(recomendar, axis=1)
    cmp_fmt = (cmp[["SUCURSAL","TIPO","PCT_HIST","PCT_ACT","DELTA_PCT","ACCION"]]
               .sort_values(["SUCURSAL","TIPO"]))
    cmp_fmt["PCT_HIST"] = cmp_fmt["PCT_HIST"].map(lambda x: f"{x:.1f}%" if pd.notna(x) else "—")
    cmp_fmt["PCT_ACT"]  = cmp_fmt["PCT_ACT"].map(lambda x: f"{x:.1f}%" if pd.notna(x) else "—")
    cmp_fmt["DELTA_PCT"]= cmp["DELTA_PCT"].map(lambda x: f"{x:+.1f} pp" if pd.notna(x) else "—")
    st.dataframe(cmp_fmt, use_container_width=True)

    with st.expander("Notas y supuestos"):
        st.write("""
        - Ticket ≈ Ingreso / Platillos vendidos (no contamos tickets reales).
        - Producto más rentable = mayor ingreso (no hay costos).
        - Bebidas si `CLASIFICACION` contiene 'bebida' o 'cerveza'.
        - Metas: `SUCURSAL, META_DIARIA`.
        - Reglas y umbrales personalizables.
        """)
    # ============================================================
    # NUEVA SECCIÓN: Tendencias y estacionalidad (DENTRO de RAW)
    # ============================================================
    st.markdown("---")
    st.header("Tendencias y estacionalidad")

    # --- Helpers locales (mismos que en el notebook) ---
    from datetime import date
    def easter_sunday(year:int) -> date:
        a = year % 19; b = year // 100; c = year % 100
        d = b // 4; e = b % 4; f = (b + 8) // 25; g = (b - f + 1) // 3
        h = (19*a + b - d - g + 15) % 30; i = c // 4; k = c % 4
        l = (32 + 2*e + 2*i - h - k) % 7; m = (a + 11*h + 22*l) // 451
        month = (h + l - 7*m + 114) // 31; day = ((h + l - 7*m + 114) % 31) + 1
        return date(year, month, day)

    def holiday_windows(year:int):
        es = easter_sunday(year)
        semana_santa = (es - timedelta(days=3), es)            # Jue–Dom
        verano = (date(year,7,1), date(year,8,31))             # Jul–Ago
        invierno = (date(year,12,15), date(year+1,1,6))        # 15 Dic–6 Ene
        independencia = (date(year,9,13), date(year,9,16))     # 13–16 Sep
        return {"Semana Santa": semana_santa, "Verano": verano, "Invierno": invierno, "Independencia": independencia}

    SEASON_MAP = {12:"Invierno",1:"Invierno",2:"Invierno",3:"Primavera",4:"Primavera",5:"Primavera",
                  6:"Verano",7:"Verano",8:"Verano",9:"Otoño",10:"Otoño",11:"Otoño"}

    # --- Serie diaria/semanal/mensual dentro del rango y sucursal seleccionados ---
    daily = (dff.groupby(["SUCURSAL","FECHA"], as_index=False)["INGRESO_ESTIMADO"].sum()
               .rename(columns={"INGRESO_ESTIMADO":"ingreso"}))

    # Completa días faltantes (por sucursal)
    all_rows = []
    for s, dsub in daily.groupby("SUCURSAL"):
        dr = pd.date_range(dsub["FECHA"].min(), dsub["FECHA"].max(), freq="D")
        dd = dsub.set_index("FECHA").reindex(dr).fillna(0.0).rename_axis("FECHA").reset_index()
        dd["SUCURSAL"] = s; all_rows.append(dd)
    daily_full = pd.concat(all_rows, ignore_index=True)

    # Select sucursal a mostrar (sub-filtro visual)
    suc_visual = st.selectbox("Sucursal para gráficos de tendencia", sorted(comp["SUCURSAL"].unique().tolist()))
    dsub = daily_full[daily_full["SUCURSAL"]==suc_visual].copy()

    cA, cB = st.columns(2)
    with cA:
        fig = px.line(dsub, x="FECHA", y="ingreso", markers=True, title=f"{suc_visual} — Ingreso diario")
        st.plotly_chart(style_plot(fig), use_container_width=True)
    with cB:
        ws = (dsub.assign(SEMANA=dsub["FECHA"].dt.to_period("W").apply(lambda r: r.start_time))
                   .groupby("SEMANA", as_index=False)["ingreso"].sum())
        figw = px.line(ws, x="SEMANA", y="ingreso", markers=True, title=f"{suc_visual} — Ingreso semanal")
        st.plotly_chart(style_plot(figw), use_container_width=True)

    ms = (dsub.assign(MES=dsub["FECHA"].dt.to_period("M").astype("datetime64[ns]"))
               .groupby("MES", as_index=False)["ingreso"].sum())
    figm = px.line(ms, x="MES", y="ingreso", markers=True, title=f"{suc_visual} — Ingreso mensual")
    st.plotly_chart(style_plot(figm), use_container_width=True)

    # --- Día de la semana (ordenado en español) ---
    DOW_ORDER = ["Lunes","Martes","Miércoles","Jueves","Viernes","Sábado","Domingo"]
    tmp = dff.copy()
    tmp["DOW"] = tmp["FECHA"].dt.dayofweek.map({0:"Lunes",1:"Martes",2:"Miércoles",3:"Jueves",4:"Viernes",5:"Sábado",6:"Domingo"})
    dow = (tmp.groupby(["SUCURSAL","DOW"], as_index=False)["INGRESO_ESTIMADO"].sum()
              .rename(columns={"INGRESO_ESTIMADO":"ingreso"}))
    dow_s = dow[dow["SUCURSAL"]==suc_visual].copy()
    dow_s["DOW"] = pd.Categorical(dow_s["DOW"], categories=DOW_ORDER, ordered=True)
    fig_dow = px.bar(dow_s.sort_values("DOW"), x="DOW", y="ingreso", title=f"{suc_visual} — Ingreso por día de la semana")
    st.plotly_chart(style_plot(fig_dow), use_container_width=True)

    # --- Ingreso por HORA (si existe DATETIME/HORA) ---
    has_dt = "DATETIME" in df.columns and pd.api.types.is_datetime64_any_dtype(df["DATETIME"])
    if has_dt:
        sub_h = df[(df["SUCURSAL"]==suc_visual) & (df["FECHA"].between(start_date, end_date))].copy()
        sub_h["HORA_INT"] = sub_h["DATETIME"].dt.hour
        hh = sub_h.groupby("HORA_INT", as_index=False)["INGRESO_ESTIMADO"].sum().rename(columns={"INGRESO_ESTIMADO":"ingreso"})
        fig_h = px.line(hh, x="HORA_INT", y="ingreso", markers=True, title=f"{suc_visual} — Ingreso por hora")
        st.plotly_chart(style_plot(fig_h), use_container_width=True)
    else:
        st.caption("ℹ️ No hay columna de tiempo (DATETIME/HORA). Se omite el gráfico por hora.")

    # --- Overlays de eventos estacionales (ventanas) ---
    years = sorted(dsub["FECHA"].dt.year.unique().tolist())
    shapes = []
    for y in years:
        wins = holiday_windows(int(y))
        for ev, (ini, fin) in wins.items():
            # Maneja rangos que cruzan año
            if ini <= fin:
                rng = [pd.Timestamp(ini), pd.Timestamp(fin)]
            else:  # Invierno
                rng = [pd.Timestamp(ini), pd.Timestamp(date(y,12,31))]
                shapes.append(dict(type="rect", xref="x", yref="paper",
                                   x0=rng[0], x1=rng[1], y0=0, y1=1, fillcolor=ACCENT, opacity=0.07, line_width=0))
                rng = [pd.Timestamp(date(y+1,1,1)), pd.Timestamp(fin)]
            shapes.append(dict(type="rect", xref="x", yref="paper",
                               x0=rng[0], x1=rng[1], y0=0, y1=1, fillcolor=ACCENT, opacity=0.07, line_width=0))

    fig_overlay = px.line(dsub, x="FECHA", y="ingreso", title=f"{suc_visual} — Ingreso diario (con eventos estacionales)")
    fig_overlay.update_layout(shapes=shapes)
    st.plotly_chart(style_plot(fig_overlay), use_container_width=True)

    # --- Ranking por temporada (estación) y por evento ---
    df_tmp = df[df["FECHA"].between(start_date, end_date)].copy()
    df_tmp["SEASON"] = df_tmp["FECHA"].dt.month.map(SEASON_MAP)

    st.subheader("Ranking de productos por estación")
    season_sel = st.selectbox("Elige estación", ["Primavera","Verano","Otoño","Invierno"])
    rank_season = (df_tmp[df_tmp["SEASON"]==season_sel]
                   .groupby(["SUCURSAL","NOMBRE_PLATILLO","CLASIFICACION"], as_index=False)
                   .agg(INGRESO=("INGRESO_ESTIMADO","sum"),
                        CANT=("PLATILLOS_VENDIDOS","sum")))
    rank_season = rank_season[rank_season["SUCURSAL"]==suc_visual].sort_values("INGRESO", ascending=False).head(15)
    st.dataframe(rank_season, use_container_width=True)

    st.subheader("Ranking de productos por evento (estacional)")
    evento_sel = st.selectbox("Elige evento", ["Semana Santa","Verano","Invierno","Independencia"])
    def in_event(ts, ev_name):
        y = ts.year; wins = holiday_windows(y)
        ini, fin = wins[ev_name]; d = ts.date()
        if ini <= fin: return (d >= ini) and (d <= fin)
        return (d >= ini) or (d <= fin)
    df_tmp["EN_EVENTO"] = df_tmp["FECHA"].apply(lambda x: in_event(x, evento_sel))
    rank_event = (df_tmp[(df_tmp["EN_EVENTO"]) & (df_tmp["SUCURSAL"]==suc_visual)]
                  .groupby(["NOMBRE_PLATILLO","CLASIFICACION"], as_index=False)
                  .agg(INGRESO=("INGRESO_ESTIMADO","sum"),
                       CANT=("PLATILLOS_VENDIDOS","sum"))
                  .sort_values("INGRESO", ascending=False).head(15))
    st.dataframe(rank_event, use_container_width=True)



# =================================================================
#                         MODO EXPORTS (NOTEBOOK)
# =================================================================
else:
    st.sidebar.markdown("---")
    st.sidebar.header("EXPORTS")
    exports_dir = st.sidebar.text_input("Carpeta de exports", value=EXPORTS_DIR)
    data = load_exports(exports_dir)

    st.sidebar.markdown("### Archivos detectados")
    for name, path in data["paths"].items():
        ok = "✅" if file_exists(path) else "—"
        st.sidebar.write(f"{ok} {os.path.basename(path)}")

    # ---------------- KPIs ejecutivos desde productos_feats ----------------
    st.header("1) KPIs ejecutivos (desde productos_feats)")
    feats = data["productos_feats"]; afinidad_pairs = data["afinidad_pairs"]

    if feats.empty:
        st.info("Sube/crea primero `productos_feats.csv` desde tu notebook.")
    else:
        ingreso_total = feats["total_rev"].sum() if "total_rev" in feats.columns else np.nan
        total_qty_global = feats["total_qty"].sum() if "total_qty" in feats.columns else np.nan
        ticket_aprox = ingreso_total / total_qty_global if pd.notna(ingreso_total) and total_qty_global and total_qty_global>0 else np.nan

        if "total_rev" in feats.columns:
            top_row = feats.sort_values("total_rev", ascending=False).head(1)
        else:
            top_row = feats.sort_values("rev_per_day", ascending=False).head(1)
        top_name = top_row["NOMBRE_PLATILLO"].iloc[0] if not top_row.empty else "-"
        top_ing  = float(top_row["total_rev"].iloc[0]) if not top_row.empty and "total_rev" in top_row else np.nan

        k1, k2, k3, k4 = st.columns(4)
        k1.metric("Ingreso total", f"${ingreso_total:,.0f}" if pd.notna(ingreso_total) else "—")
        k2.metric("Ticket (aprox)", f"${ticket_aprox:,.2f}" if pd.notna(ticket_aprox) else "—")
        k3.metric("Item TOP (ingreso)", top_name)
        k4.metric("Ingreso TOP", f"${top_ing:,.0f}" if pd.notna(top_ing) else "—")
        st.markdown("---")

    # ---------------- Afinidad ----------------
    st.header("2) Afinidad de productos")
    if afinidad_pairs.empty:
        st.info("Falta `afinidad_pairs.csv`.")
    else:
        st.subheader("Tabla — Top pares por producto")
        st.dataframe(afinidad_pairs.head(200), use_container_width=True)

        st.subheader("Mapa de afinidad (heatmap de lift)")
        N_HEAT = st.slider("Productos a mostrar (heatmap)", 10, 50, 20, step=5)
        deg = (afinidad_pairs
               .melt(id_vars=["cooc_days","lift","item_a","item_b"], value_vars=["item_a","item_b"], value_name="item")
               .groupby("item", as_index=False)["cooc_days"].sum()
               .sort_values("cooc_days", ascending=False))
        top_items = deg["item"].head(N_HEAT).tolist()
        aff_top = afinidad_pairs[(afinidad_pairs["item_a"].isin(top_items)) & (afinidad_pairs["item_b"].isin(top_items))].copy()

        if aff_top.empty:
            st.info("No hay suficientes pares para el heatmap con el umbral actual.")
        else:
            M = aff_top.pivot(index="item_a", columns="item_b", values="lift").fillna(0)
            M2 = M.combine_first(M.T)
            fig_heat = px.imshow(M2, color_continuous_scale="Viridis", title="Lift entre productos (Top N)")
            st.plotly_chart(style_plot(fig_heat), use_container_width=True)

        st.subheader("Red de afinidad (grafo)")
        net = data["afinidad_graph"]
        if not net:
            st.info("Falta `afinidad_graph.json` o está vacío (expórtalo desde el notebook).")
        else:
            nodes_df = pd.DataFrame(net.get("nodes", []))
            links_df = pd.DataFrame(net.get("links", []))
            if nodes_df.empty or links_df.empty:
                st.info("El grafo no tiene nodos/enlaces después de filtrar.")
            else:
                # posiciones pseudo-aleatorias reproducibles
                rng = np.random.default_rng(7)
                nodes_df["x"] = rng.uniform(-1, 1, size=len(nodes_df))
                nodes_df["y"] = rng.uniform(-1, 1, size=len(nodes_df))

                edge_x, edge_y = [], []
                for r in links_df.itertuples():
                    a = nodes_df.loc[nodes_df["id"]==r.source]
                    b = nodes_df.loc[nodes_df["id"]==r.target]
                    if a.empty or b.empty: 
                        continue
                    x0,y0 = float(a["x"]), float(a["y"])
                    x1,y1 = float(b["x"]), float(b["y"])
                    edge_x += [x0, x1, None]; edge_y += [y0, y1, None]

                edge_trace = go.Scatter(x=edge_x, y=edge_y, mode='lines', line=dict(width=1), hoverinfo='none', showlegend=False)

                node_sizes = []
                for r in nodes_df.itertuples():
                    deg_i = ((links_df["source"]==r.id) | (links_df["target"]==r.id)).sum()
                    node_sizes.append(6 + 2*int(deg_i))

                node_trace = go.Scatter(
                    x=nodes_df["x"], y=nodes_df["y"], mode="markers+text",
                    marker=dict(size=node_sizes, color=GOLD),
                    text=nodes_df["name"], textposition="top center",
                    hoverinfo="text", showlegend=False
                )
                fig_graph = go.Figure(data=[edge_trace, node_trace],
                                      layout=go.Layout(title="Red de afinidad (filtrada)", margin=dict(l=10,r=10,t=60,b=10)))
                st.plotly_chart(style_plot(fig_graph), use_container_width=True)

    st.markdown("---")

    # ---------------- Clusters ----------------
    st.header("3) Clusters de producto")
    cluster_summary = data["cluster_summary"]
    feats = data["productos_feats"] if "productos_feats" in data else pd.DataFrame()

    if cluster_summary.empty or feats.empty:
        st.info("Faltan `cluster_summary.csv` o `productos_feats.csv`.")
    else:
        st.subheader("Resumen por cluster")
        st.dataframe(cluster_summary.sort_values(["rol_cluster","rev_per_day"], ascending=[True, False]), use_container_width=True)

        st.subheader("Mapa support vs ingreso/día")
        fig_sc = px.scatter(
            feats, x="support", y="rev_per_day", color="rol_cluster",
            hover_data=["NOMBRE_PLATILLO","total_rev","total_qty","price_median"],
            title="Productos: Support vs Ingreso por día"
        )
        fig_sc.update_traces(marker=dict(size=10, line=dict(width=0)))
        fig_sc.update_layout(xaxis_title="Support (días con venta / total)", yaxis_title="Ingreso por día")
        st.plotly_chart(style_plot(fig_sc), use_container_width=True)

        st.subheader("Top productos por ingreso/día")
        N_TOP = st.slider("Top N", 5, 30, 12)
        feats_top = feats.sort_values("rev_per_day", ascending=False).head(N_TOP)
        fig_bar_top = px.bar(feats_top, x="NOMBRE_PLATILLO", y="rev_per_day", color="rol_cluster",
                             title=f"Top {N_TOP} por ingreso/día")
        fig_bar_top.update_layout(xaxis_tickangle=-35, yaxis_title="Ingreso por día")
        st.plotly_chart(style_plot(fig_bar_top), use_container_width=True)

    st.markdown("---")

    # ---------------- Productos Ancla ----------------
    st.header("4) Productos ancla (arrastre complementario)")
    anchors = data["productos_ancla"]
    if anchors.empty:
        st.info("Falta `productos_ancla.csv`.")
    else:
        st.subheader("Tabla — Productos ancla")
        st.dataframe(anchors.head(50), use_container_width=True)

        st.subheader("Ranking por anchor score")
        N_ANCLA = st.slider("Top N anclas", 5, 30, 20)
        fig_anchor = px.bar(
            anchors.head(N_ANCLA), x="NOMBRE_PLATILLO", y="anchor_score", color="rol_cluster",
            hover_data=["rev_per_day","support","top_complements"],
            title="Top productos ancla (anchor score)"
        )
        fig_anchor.update_layout(xaxis_tickangle=-35, yaxis_title="Anchor score (lift * support_ab acumulado)")
        st.plotly_chart(style_plot(fig_anchor), use_container_width=True)

    st.markdown("---")

    # ---------------- Data Explorer ----------------
    st.header("5) Data Explorer")
    tab1, tab2, tab3 = st.tabs(["Afinidad (pares)", "Productos (feats)", "Clusters & Anclas"])

    with tab1:
        if afinidad_pairs.empty: st.info("No hay `afinidad_pairs.csv`.")
        else: st.dataframe(afinidad_pairs, use_container_width=True)

    with tab2:
        if feats.empty: st.info("No hay `productos_feats.csv`.")
        else: st.dataframe(feats, use_container_width=True)

    with tab3:
        if cluster_summary.empty and anchors.empty:
            st.info("No hay `cluster_summary.csv` ni `productos_ancla.csv`.")
        else:
            if not cluster_summary.empty:
                st.write("Cluster Summary"); st.dataframe(cluster_summary, use_container_width=True)
            if not anchors.empty:
                st.write("Productos Ancla"); st.dataframe(anchors, use_container_width=True)

# ----------------------------- FOOTER -----------------------------
st.caption("Tablero Ejecutivo. Hecho con Streamlit + Plotly.")
