/* ============================================================
   BWAY PROD — Auth + Dashboards
   Hash-routed SPA layer that mounts on top of the landing.
   Loaded after app.jsx so it can use the global React + Supabase.
   ============================================================ */

(function () {
  const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;
  const e = React.createElement;

  // -----------------------------------------------------------
  // 1. Supabase client (singleton)
  // -----------------------------------------------------------
  const cfg = window.__BWAY_CONFIG__ || {};
  const SUPABASE_READY =
    !!window.supabase &&
    !!cfg.SUPABASE_URL  && !cfg.SUPABASE_URL.startsWith("PASTE_") &&
    !!cfg.SUPABASE_ANON && !cfg.SUPABASE_ANON.startsWith("PASTE_");
  let sb = null;
  if (SUPABASE_READY) {
    sb = window.supabase.createClient(cfg.SUPABASE_URL, cfg.SUPABASE_ANON, {
      auth: { persistSession: true, autoRefreshToken: true, detectSessionInUrl: true, flowType: "pkce" },
    });
    window.bwaySupabase = sb;
  }

  // -----------------------------------------------------------
  // 2. Hash router
  // -----------------------------------------------------------
  function parseHash() {
    let raw = (window.location.hash || "").replace(/^#/, "");
    // Supabase implicit-flow appends auth params after a SECOND '#'
    // (e.g. `/activate?token=ABC#access_token=...`) which would corrupt
    // our query parsing. Strip everything from the second '#' onward.
    const secondHashIdx = raw.indexOf("#");
    if (secondHashIdx > -1) raw = raw.slice(0, secondHashIdx);
    const [path, query] = raw.split("?");
    const params = {};
    if (query) {
      query.split("&").forEach((kv) => {
        const [k, v] = kv.split("=");
        if (k) params[decodeURIComponent(k)] = v ? decodeURIComponent(v) : "";
      });
    }
    return { path: path || "/", params };
  }
  function navigate(to) {
    if (typeof to === "string") {
      const target = to.startsWith("#") ? to : ("#" + to);
      if (window.location.hash === target) return;
      window.location.hash = target;
    }
  }
  window.bwayNavigate = navigate;

  function useRoute() {
    const [route, setRoute] = useState(parseHash());
    useEffect(() => {
      const on = () => setRoute(parseHash());
      window.addEventListener("hashchange", on);
      return () => window.removeEventListener("hashchange", on);
    }, []);
    return route;
  }

  // -----------------------------------------------------------
  // 3. AuthContext
  // -----------------------------------------------------------
  const AuthContext = createContext({ status: "loading", session: null, profile: null });
  const useAuth = () => useContext(AuthContext);

  function AuthProvider({ children }) {
    const [status, setStatus]   = useState(SUPABASE_READY ? "loading" : "no_config");
    const [session, setSession] = useState(null);
    const [profile, setProfile] = useState(null);

    const loadProfile = useCallback(async (s) => {
      if (!s?.user) { setProfile(null); return; }
      const { data, error } = await sb
        .from("profiles")
        .select("id, role, full_name, company, is_active, activated_at, avatar_url")
        .eq("id", s.user.id)
        .single();
      if (error) {
        // If the JWT references a user that no longer exists in auth.users
        // (e.g. session cached after the user was deleted in Supabase), the
        // session is permanently broken. Sign out + reload so the next render
        // hits the login screen cleanly instead of looping on errors.
        const msg = (error.message || "").toLowerCase();
        if (msg.includes("user from sub claim") || msg.includes("user not found") || msg.includes("jwt") && msg.includes("does not exist")) {
          try { await sb.auth.signOut(); } catch {}
          window.location.href = window.location.pathname;
          return;
        }
        setProfile(null);
        return;
      }
      setProfile(data);
    }, []);

    useEffect(() => {
      if (!SUPABASE_READY) return;
      let resolved = false;
      const resolve = (s) => {
        if (resolved) return;
        resolved = true;
        setSession(s ?? null);
        setStatus(s ? "authed" : "anon");
        // Defer profile load — don't block the spinner exit on it.
        loadProfile(s ?? null).catch(() => {});
      };
      // Primary: getSession() resolves quickly even when there's no session.
      sb.auth.getSession()
        .then(({ data }) => resolve(data?.session ?? null))
        .catch(() => resolve(null));
      // Listener for future auth state changes
      const { data: sub } = sb.auth.onAuthStateChange((_event, s) => {
        setSession(s);
        loadProfile(s).catch(() => {});
        setStatus(s ? "authed" : "anon");
      });
      // Fallback: never stay on "loading" longer than 3s, even if Supabase
      // hangs (network glitch, slow JSX transpile on first load, etc).
      const fallback = setTimeout(() => resolve(null), 3000);
      return () => {
        clearTimeout(fallback);
        sub?.subscription?.unsubscribe();
      };
    }, [loadProfile]);

    const signOut = useCallback(async () => {
      try { await sb.auth.signOut(); } catch {}
      // Force navigation to the landing page (NOT /login) with a hard reload
      // so AuthRouter unmounts cleanly, Lenis resumes, and videos restart.
      window.location.href = window.location.pathname + window.location.search;
    }, []);

    const value = useMemo(() => ({ status, session, profile, supabase: sb, signOut, refresh: () => loadProfile(session) }),
                          [status, session, profile, signOut, loadProfile]);
    return e(AuthContext.Provider, { value }, children);
  }

  // -----------------------------------------------------------
  // 4. Shared UI atoms
  // -----------------------------------------------------------
  function Field({ label, hint, children, colSpan }) {
    return e("label", {
      className: "dash-field" + (colSpan === 2 ? " dash-field-wide" : "")
    },
      e("span", { className: "dash-field-label" }, label),
      children,
      hint ? e("span", { className: "dash-field-hint" }, hint) : null
    );
  }
  function Btn({ kind = "gold", children, loading, ...rest }) {
    return e("button", {
      ...rest,
      className: "btn btn-" + kind + " dash-btn" + (loading ? " is-loading" : "") + (rest.className ? " " + rest.className : ""),
      disabled: loading || rest.disabled
    },
      loading ? e("span", { className: "dash-btn-spinner" }) : null,
      children,
      e("span", { className: "arrow" })
    );
  }
  function Banner({ kind = "info", children }) {
    return e("div", { className: "dash-banner is-" + kind }, children);
  }
  function Spinner({ size = 22, label = "Cargando" }) {
    return e("div", { className: "dash-spinner-wrap" },
      e("span", { className: "dash-spinner", style: { width: size, height: size } }),
      label ? e("span", { className: "dash-spinner-label" }, label) : null
    );
  }
  // 7-segment compact stage track. Pass either an array of stages OR
  // a numeric `currentStage`. Highlights done/in_progress/pending visually.
  function StageTrack({ stages, currentStage, size = "sm" }) {
    let segs;
    if (Array.isArray(stages)) {
      segs = stages.slice().sort((a, b) => (a.stage_id || 0) - (b.stage_id || 0))
        .map(s => s.status || "pending");
    } else {
      const c = Math.max(1, Math.min(7, currentStage || 1));
      segs = Array.from({ length: 7 }, (_, i) => {
        if (i + 1 < c) return "done";
        if (i + 1 === c) return "in_progress";
        return "pending";
      });
    }
    const cur = segs.findIndex((s) => s === "in_progress");
    return e("div", { className: "stage-track stage-track-" + size, "aria-label": "Progreso por etapas", style: { "--cur": cur } },
      // Light wave that dollies left → current segment
      cur >= 0 && size !== "sm" ? e("span", {
        className: "stage-track-dolly",
        style: { "--cur-pct": ((cur + 0.5) / 7 * 100) + "%" }
      }) : null,
      segs.map((status, i) =>
        e("span", {
          key: i,
          className: "stage-track-seg is-" + status,
          title: "Etapa " + (i + 1) + ": " + (status === "in_progress" ? "en progreso" : status)
        })
      )
    );
  }

  // Radial progress ring rendered with SVG. Pure visual, no deps.
  // Hook: animated number count-up with easeOutCubic
  function useCountUp(target, duration = 1200) {
    const [n, setN] = useState(0);
    useEffect(() => {
      const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      if (reduced) { setN(target); return; }
      let raf;
      const start = performance.now();
      const tick = (now) => {
        const t = Math.min(1, (now - start) / duration);
        const eased = 1 - Math.pow(1 - t, 3);
        setN(target * eased);
        if (t < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, [target, duration]);
    return n;
  }

  function ProgressRing({ pct, size = 92, label }) {
    const target = Math.max(0, Math.min(100, pct || 0));
    const animPct = useCountUp(target, 1400);
    const r = (size - 14) / 2;
    const c = 2 * Math.PI * r;
    const off = c - (animPct / 100) * c;
    const gid = "ringGrad-" + size;
    const fid = "ringGlow-" + size;
    return e("div", { className: "progress-ring is-3d", style: { width: size, height: size } },
      e("div", { className: "progress-ring-aura" }),
      e("svg", { viewBox: `0 0 ${size} ${size}`, width: size, height: size, className: "progress-ring-svg" },
        e("defs", null,
          e("linearGradient", { id: gid, x1: "0%", y1: "0%", x2: "100%", y2: "100%" },
            e("stop", { offset: "0%",  stopColor: "#f4d68a" }),
            e("stop", { offset: "55%", stopColor: "#d6b25d" }),
            e("stop", { offset: "100%", stopColor: "#a07d3b" })
          ),
          e("filter", { id: fid, x: "-30%", y: "-30%", width: "160%", height: "160%" },
            e("feGaussianBlur", { stdDeviation: "2.5", result: "blur" }),
            e("feMerge", null,
              e("feMergeNode", { in: "blur" }),
              e("feMergeNode", { in: "SourceGraphic" })
            )
          )
        ),
        e("circle", {
          cx: size / 2, cy: size / 2, r,
          className: "progress-ring-track"
        }),
        e("circle", {
          cx: size / 2, cy: size / 2, r,
          className: "progress-ring-fill",
          stroke: `url(#${gid})`,
          filter: `url(#${fid})`,
          style: { strokeDasharray: c, strokeDashoffset: off, transform: `rotate(-90deg)`, transformOrigin: "50% 50%" }
        })
      ),
      e("div", { className: "progress-ring-text" },
        e("b", null, Math.round(animPct)),
        e("span", null, "%"),
        label ? e("em", null, label) : null
      )
    );
  }

  // BWAY logo used on auth screens and as fallback monogram on sidebar.
  function BwayLogo({ size = 64, withName = true }) {
    return e("div", { className: "bway-logo", style: { width: size } },
      e("img", {
        src: "assets/bway-logo.png",
        alt: "BWAY PROD",
        className: "bway-logo-img",
        onError: (ev) => { ev.currentTarget.style.display = "none"; }
      }),
      withName ? e("span", { className: "bway-logo-name" },
        "BWAY", e("em", null, " Prod."),
        e("span", { className: "bway-logo-tag" }, "Cinematografía que posiciona marca")
      ) : null
    );
  }

  function StatusPill({ status }) {
    const map = {
      pending:     { label: "Pendiente",   cls: "is-pending" },
      in_progress: { label: "En progreso", cls: "is-progress" },
      done:        { label: "Completado",  cls: "is-done" },
      blocked:     { label: "Bloqueado",   cls: "is-blocked" }
    };
    const m = map[status] || { label: status || "—", cls: "" };
    return e("span", { className: "phase-pill " + m.cls }, m.label);
  }

  // -----------------------------------------------------------
  // 5. Login + activation screens
  // -----------------------------------------------------------
  // Translates Supabase auth errors into friendly Spanish messages.
  function translateAuthError(msg) {
    if (!msg) return "No se pudo procesar la solicitud. Intenta de nuevo.";
    const m = msg.toLowerCase();
    if (m.includes("invalid login credentials") || m.includes("invalid email or password"))
      return "Correo o contraseña incorrectos. Verifica tus datos e intenta de nuevo.";
    if (m.includes("email not confirmed"))
      return "Tu correo aún no está confirmado. Revisa tu bandeja de entrada.";
    if (m.includes("user not found"))
      return "No existe una cuenta con ese correo. Pide a tu admin que te invite.";
    if (m.includes("too many requests") || m.includes("rate limit"))
      return "Demasiados intentos. Espera unos minutos antes de volver a intentar.";
    if (m.includes("password should be") || m.includes("password must"))
      return "La contraseña debe tener al menos 8 caracteres.";
    if (m.includes("network") || m.includes("failed to fetch"))
      return "Sin conexión con el servidor. Revisa tu internet.";
    if (m.includes("email address") && m.includes("invalid"))
      return "El correo no tiene un formato válido.";
    if (m.includes("user already registered") || m.includes("already exists"))
      return "Ese correo ya está registrado. Intenta iniciar sesión.";
    return msg; // Fall back to original if unknown
  }

  function LoginScreen() {
    const { profile } = useAuth();
    const [email, setEmail]       = useState("");
    const [password, setPassword] = useState("");
    const [busy, setBusy]         = useState(false);
    const [err,  setErr]          = useState(null);
    const [msg,  setMsg]          = useState(null);
    const [resetMode, setReset]   = useState(false);

    // If already authed and active, bounce to the right dashboard
    useEffect(() => {
      if (profile?.role && profile.is_active) {
        navigate("/dashboard/" + profile.role);
      }
    }, [profile]);

    const onSubmit = async (ev) => {
      ev.preventDefault();
      setErr(null); setMsg(null); setBusy(true);
      try {
        if (resetMode) {
          const { error } = await sb.auth.resetPasswordForEmail(email, {
            redirectTo: (cfg.SITE_URL || window.location.origin) + "/#/activate"
          });
          if (error) throw error;
          setMsg("Si la cuenta existe, revisa tu correo para reestablecer la contraseña.");
        } else {
          const { error } = await sb.auth.signInWithPassword({ email, password });
          if (error) throw error;
        }
      } catch (x) {
        setErr(translateAuthError(x.message) || "No se pudo iniciar sesión");
      } finally {
        setBusy(false);
      }
    };

    return e("div", { className: "auth-screen" },
      e("div", { className: "auth-bg", "aria-hidden": "true" },
        e("div", { className: "auth-bg-grain" }),
        e("div", { className: "auth-bg-flare" })
      ),
      e("a", { href: "#/", className: "auth-back", onClick: (ev) => { ev.preventDefault(); navigate("/"); } },
        "← Volver al estudio"),
      e("form", { className: "auth-card", onSubmit },
        e(BwayLogo, { size: 72 }),
        e("div", { className: "auth-kicker" }, "BWAY · ACCESO"),
        e("h1", { className: "auth-h" },
          "Bienvenido al ",
          e("span", { className: "em" }, "estudio."),
        ),
        e("p", { className: "auth-sub" },
          resetMode
            ? "Introduce tu correo y te enviamos un link para reestablecer tu contraseña."
            : "Entra a tu panel de producción."
        ),
        err ? e(Banner, { kind: "error" }, err) : null,
        msg ? e(Banner, { kind: "info" },  msg) : null,
        e(Field, { label: "Correo" },
          e("input", { type: "email", required: true, autoComplete: "email",
            className: "dash-input", value: email, onChange: (ev) => setEmail(ev.target.value) })),
        !resetMode ? e(Field, { label: "Contraseña" },
          e("input", { type: "password", required: true, autoComplete: "current-password",
            className: "dash-input", value: password, onChange: (ev) => setPassword(ev.target.value) })) : null,
        e(Btn, { type: "submit", loading: busy }, resetMode ? "Enviar link" : "Iniciar sesión"),
        e("button", {
          type: "button",
          className: "auth-link",
          onClick: () => { setReset((v) => !v); setErr(null); setMsg(null); }
        }, resetMode ? "Volver al login" : "¿Olvidaste tu contraseña?"),
        e("p", { className: "auth-foot" },
          "¿Eres cliente nuevo? Espera la invitación de tu director de proyecto.")
      )
    );
  }

  function ActivateScreen() {
    const route = useRoute();
    const token = route.params.token || "";
    const { session, refresh } = useAuth();
    const [password,  setPassword]  = useState("");
    const [password2, setPassword2] = useState("");
    const [busy,  setBusy]  = useState(false);
    const [err,   setErr]   = useState(null);
    const [stage, setStage] = useState(session ? "set_password" : "waiting");

    // Activation arrives one of two ways:
    //   PKCE     →  ?code=...    in the query string
    //   Implicit →  #access_token=...&refresh_token=...  in the hash
    // Detect either and create the session manually so the rest of the
    // screen has a valid auth state.
    useEffect(() => {
      if (session) return;
      const search = window.location.search || "";
      const hash   = window.location.hash || "";

      const codeMatch = search.match(/[?&]code=([^&]+)/);
      if (codeMatch) {
        (async () => {
          try {
            const { error } = await sb.auth.exchangeCodeForSession(window.location.href);
            if (error) { setErr(translateAuthError(error.message) || "No se pudo confirmar el enlace."); return; }
            // Clean URL: drop ?code & ?type params, preserve hash routing
            window.history.replaceState({}, "", window.location.pathname + window.location.hash);
            await refresh();
          } catch (x) { setErr(translateAuthError(x.message) || "No se pudo confirmar el enlace."); }
        })();
        return;
      }

      // Implicit fallback — hash may look like: #/activate?token=<ours>#access_token=...&refresh_token=...
      const accessMatch  = hash.match(/access_token=([^&#]+)/);
      const refreshMatch = hash.match(/refresh_token=([^&#]+)/);
      if (accessMatch && refreshMatch) {
        (async () => {
          try {
            const { error } = await sb.auth.setSession({
              access_token:  decodeURIComponent(accessMatch[1]),
              refresh_token: decodeURIComponent(refreshMatch[1]),
            });
            if (error) { setErr(translateAuthError(error.message) || "No se pudo confirmar el enlace."); return; }
            // Strip everything from the second '#' onward — keep our hash route intact
            const secondHashIdx = hash.indexOf("#", 1);
            const cleanHash = secondHashIdx > 0 ? hash.slice(0, secondHashIdx) : hash;
            window.history.replaceState({}, "", window.location.pathname + cleanHash);
            await refresh();
          } catch (x) { setErr(translateAuthError(x.message) || "No se pudo confirmar el enlace."); }
        })();
      }
    }, [session, refresh]);

    useEffect(() => {
      if (session) setStage((s) => (s === "waiting" ? "set_password" : s));
    }, [session]);

    const onSubmit = async (ev) => {
      ev.preventDefault();
      setErr(null);
      if (password.length < 8)        return setErr("Mínimo 8 caracteres.");
      if (password !== password2)      return setErr("Las contraseñas no coinciden.");
      setBusy(true);
      try {
        const upd = await sb.auth.updateUser({ password });
        if (upd.error) throw upd.error;
        if (token) {
          const { data, error } = await sb.rpc("bway_redeem_invitation", { p_token: token });
          if (error) throw error;
          await refresh();
          navigate((data && data.redirect_to) ? data.redirect_to.replace(/^#/, "/") : "/dashboard/usuario");
        } else {
          await refresh();
          navigate("/dashboard/usuario");
        }
      } catch (x) {
        setErr(x.message || "No se pudo activar la cuenta");
      } finally {
        setBusy(false);
      }
    };

    return e("div", { className: "auth-screen" },
      e("div", { className: "auth-bg", "aria-hidden": "true" },
        e("div", { className: "auth-bg-grain" }),
        e("div", { className: "auth-bg-flare" })
      ),
      e("form", { className: "auth-card", onSubmit },
        e(BwayLogo, { size: 72 }),
        e("div", { className: "auth-kicker" }, "BWAY · ACTIVAR CUENTA"),
        e("h1", { className: "auth-h" },
          "Define tu ",
          e("span", { className: "em" }, "contraseña.")
        ),
        e("p", { className: "auth-sub" },
          "Una sola vez. Después podrás entrar con tu correo y la contraseña que elijas."
        ),
        stage === "waiting" ? e(Banner, { kind: "info" },
          "Esperando confirmación del magic link. Si abriste este enlace desde el correo, recarga la página."
        ) : null,
        err ? e(Banner, { kind: "error" }, err) : null,
        e(Field, { label: "Contraseña nueva (mín. 8)" },
          e("input", { type: "password", required: true, autoComplete: "new-password",
            className: "dash-input", value: password, onChange: (ev) => setPassword(ev.target.value) })),
        e(Field, { label: "Confírmala" },
          e("input", { type: "password", required: true, autoComplete: "new-password",
            className: "dash-input", value: password2, onChange: (ev) => setPassword2(ev.target.value) })),
        e(Btn, { type: "submit", loading: busy }, "Activar y entrar")
      )
    );
  }

  // -----------------------------------------------------------
  // 6. DashboardShell — sidebar + header + outlet
  // -----------------------------------------------------------
  function DashboardShell({ role, nav, children }) {
    const { profile, signOut } = useAuth();
    const route = useRoute();
    return e("div", { className: "dash-shell role-" + role },
      e("aside", { className: "dash-side" },
        e("div", { className: "dash-side-brand" },
          e("a", { href: "#/", className: "dash-side-logo", onClick: (ev) => { ev.preventDefault(); navigate("/"); }, "aria-label": "BWAY PROD · volver al estudio" },
            e("img", {
              src: "assets/bway-logo.png",
              alt: "",
              className: "dash-side-logo-img",
              onError: (ev) => {
                ev.currentTarget.style.display = "none";
                const fallback = ev.currentTarget.nextElementSibling;
                if (fallback) fallback.style.display = "grid";
              }
            }),
            e("span", { className: "dash-side-mono", style: { display: "none" } }, "B"),
            e("span", { className: "dash-side-name" },
              "BWAY", e("span", { className: "em" }, " Prod.")
            )
          ),
          e("span", { className: "dash-side-role" }, (role === "usuario" ? "colaborador" : role).toUpperCase())
        ),
        e("nav", { className: "dash-side-nav", "aria-label": "Navegación" },
          nav.map((item) =>
            e("a", {
              key: item.to,
              href: "#" + item.to,
              "aria-current": route.path === item.to || (item.match && item.match.test(route.path)) ? "page" : undefined,
              onClick: (ev) => { ev.preventDefault(); navigate(item.to); }
            },
              e("span", { className: "dash-side-icon" }, item.icon || "·"),
              e("span", null, item.label)
            )
          )
        ),
        e("div", { className: "dash-side-foot" },
          profile ? (function () {
            const roleLabel = profile.role === "usuario" ? "colaborador" : profile.role;
            return e("div", { className: "dash-side-me" },
              e("div", { className: "dash-side-avatar" },
                (profile.full_name || roleLabel || "?").slice(0, 1).toUpperCase()),
              e("div", null,
                e("div", { className: "dash-side-me-name" }, profile.full_name || roleLabel),
                e("div", { className: "dash-side-me-role" }, roleLabel)
              )
            );
          })() : null,
          e("button", { className: "dash-side-out", onClick: signOut, type: "button" },
            "Cerrar sesión ↗"
          )
        )
      ),
      e("main", { className: "dash-main" },
        e(CinemaHUD, { role }),
        e("div", { className: "dash-grain", "aria-hidden": "true" }),
        e("div", { className: "dash-vignette", "aria-hidden": "true" }),
        children
      )
    );
  }

  // Cinematic HUD strip — frame-by-frame timecode + REC indicator
  function CinemaHUD({ role }) {
    const [tc, setTc] = useState("");
    const startRef = useRef(Date.now());
    useEffect(() => {
      let raf;
      const fmt = (n, w = 2) => String(n).padStart(w, "0");
      const tick = () => {
        const ms = Date.now() - startRef.current;
        const h = Math.floor(ms / 3600000);
        const m = Math.floor((ms % 3600000) / 60000);
        const s = Math.floor((ms % 60000) / 1000);
        const f = Math.floor((ms % 1000) / 41.67); // ~24fps
        setTc(`${fmt(h)}:${fmt(m)}:${fmt(s)}:${fmt(f)}`);
        raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => cancelAnimationFrame(raf);
    }, []);
    return e("div", { className: "cinema-hud" },
      e("div", { className: "cinema-hud-left" },
        e("span", { className: "cinema-hud-rec" }, e("i"), "REC"),
        e("span", { className: "cinema-hud-tc" }, tc),
        e("span", { className: "cinema-hud-divider" }),
        e("span", { className: "cinema-hud-scene" }, "SCN · BWAY · " + ((role === "usuario" ? "colaborador" : role) || "—").toUpperCase())
      ),
      e("div", { className: "cinema-hud-right" },
        e("span", { className: "cinema-hud-stat" }, "24FPS"),
        e("span", { className: "cinema-hud-divider" }),
        e("span", { className: "cinema-hud-stat" }, "DCP · 4K"),
        e("span", { className: "cinema-hud-divider" }),
        e("span", { className: "cinema-hud-stat" }, "v" + (window.__bway_v || "26"))
      )
    );
  }

  function DashHeader({ kicker, title, sub, actions }) {
    return e("header", { className: "dash-header" },
      e("div", null,
        e("div", { className: "dash-header-kicker" }, kicker),
        e("h1", { className: "dash-header-title" }, title),
        sub ? e("p", { className: "dash-header-sub" }, sub) : null
      ),
      actions ? e("div", { className: "dash-header-actions" }, actions) : null
    );
  }

  function KpiCard({ label, value, hint, accent }) {
    return e("div", { className: "kpi-card" + (accent ? " is-accent" : "") },
      e("span", { className: "kpi-label" }, label),
      e("span", { className: "kpi-value" }, value),
      hint ? e("span", { className: "kpi-hint" }, hint) : null,
      e("span", { className: "kpi-edge", "aria-hidden": "true" })
    );
  }

  // -----------------------------------------------------------
  // 7. ADMIN DASHBOARD
  // -----------------------------------------------------------
  function AdminDashboard() {
    const route = useRoute();
    const nav = [
      { to: "/dashboard/admin",              label: "Dashboard",   icon: "◐" },
      { to: "/dashboard/admin/stats",        label: "Métricas",    icon: "◇", match: /^\/dashboard\/admin\/stats/ },
      { to: "/dashboard/admin/requests",     label: "Solicitudes", icon: "✦", match: /^\/dashboard\/admin\/requests/ },
      { to: "/dashboard/admin/projects",     label: "Proyectos",   icon: "▤", match: /^\/dashboard\/admin\/projects/ },
      { to: "/dashboard/admin/quotes",       label: "Cotizaciones", icon: "✎", match: /^\/dashboard\/admin\/quotes/ },
      { to: "/dashboard/admin/users",        label: "Colaboradores", icon: "◇" },
      { to: "/dashboard/admin/shop",         label: "Tienda",       icon: "✿" },
      { to: "/dashboard/admin/invitations",  label: "Invitaciones", icon: "✉" }
    ];
    let view = e(AdminHome);
    if (route.path === "/dashboard/admin/projects")    view = e(AdminProjects);
    if (route.path === "/dashboard/admin/users")       view = e(AdminUsers);
    if (route.path === "/dashboard/admin/shop")        view = e(AdminShop);
    if (route.path === "/dashboard/admin/invitations") view = e(AdminInvitations);
    if (route.path === "/dashboard/admin/quotes")      view = e(AdminQuotes);
    if (route.path === "/dashboard/admin/quotes/new")  view = e(AdminQuoteEditor, { quoteId: null });
    const mEditQuote = route.path.match(/^\/dashboard\/admin\/quotes\/([0-9a-f-]{36})$/);
    if (mEditQuote)                                    view = e(AdminQuoteEditor, { quoteId: mEditQuote[1] });
    if (route.path === "/dashboard/admin/requests")    view = e(AdminRequests);
    if (route.path === "/dashboard/admin/stats")       view = e(AdminStats);
    if (/^\/dashboard\/admin\/projects\/(\d+)$/.test(route.path)) {
      const id = parseInt(route.path.split("/").pop(), 10);
      view = e(ProjectDetail, { projectId: id, role: "admin" });
    }
    return e(DashboardShell, { role: "admin", nav }, view);
  }

  // Bar chart — pure SVG, no deps. data = [{label, value}]
  function BarChart({ data, max }) {
    const m = max || Math.max(1, ...data.map((d) => d.value || 0));
    const [mounted, setMounted] = useState(false);
    const [hover, setHover] = useState(null);
    useEffect(() => {
      const t = requestAnimationFrame(() => setMounted(true));
      return () => cancelAnimationFrame(t);
    }, []);
    return e("div", { className: "bway-bars-3d" },
      e("div", { className: "bway-bars-stage" },
        e("div", { className: "bway-bars-floor" }),
        e("div", { className: "bway-bars" },
          data.map((d, i) => {
            const h = (d.value || 0) / m * 100;
            const isActive = hover === i;
            return e("div", {
              key: i,
              className: "bway-bar" + (isActive ? " is-hover" : ""),
              onMouseEnter: () => setHover(i),
              onMouseLeave: () => setHover((cur) => (cur === i ? null : cur)),
              style: { "--bar-i": i }
            },
              e("div", { className: "bway-bar-track" },
                e("div", {
                  className: "bway-bar-fill",
                  style: {
                    height: mounted ? h + "%" : "0%",
                    transitionDelay: (i * 80) + "ms",
                    "--bar-h": h + "%"
                  }
                },
                  e("span", { className: "bway-bar-face is-top" }),
                  e("span", { className: "bway-bar-face is-side" }),
                  e("span", { className: "bway-bar-shine" })
                )
              ),
              e("span", { className: "bway-bar-val" }, d.value || 0),
              e("span", { className: "bway-bar-label" }, d.label),
              isActive ? e("div", { className: "bway-bar-tip" },
                e("b", null, d.value || 0),
                e("span", null, d.tipLabel || d.label)
              ) : null
            );
          })
        )
      )
    );
  }

  function AdminHome() {
    const [data, setData] = useState(null);
    const [legacy, setLegacy] = useState(null);
    const [err,  setErr]  = useState(null);
    useEffect(() => {
      sb.rpc("bway_get_admin_overview").then(({ data, error }) => {
        if (error) { setErr(null); } else setData(data);
      });
      sb.rpc("bway_get_my_dashboard").then(({ data }) => setLegacy(data));
    }, []);
    if (!data && !legacy) return e("div", { className: "dash-page" }, e(Spinner));
    // Fallback: if the overview RPC isn't deployed yet, render the legacy view
    if (!data && legacy) {
      const k = legacy.kpis || {};
      return e("div", { className: "dash-page" },
        e(DashHeader, { kicker: "/ DASHBOARD · ADMIN", title: "Estudio en vivo", sub: "Aplica patch_dashboards.sql para activar la vista ejecutiva." }),
        e("div", { className: "kpi-grid" },
          e(KpiCard, { label: "Marcas activas", value: k.total_clients ?? "·" }),
          e(KpiCard, { label: "Proyectos activos", value: k.active_projects ?? "·", accent: true }),
          e(KpiCard, { label: "Colaboradores del estudio", value: k.active_users ?? "·" }),
          e(KpiCard, { label: "Invitaciones pendientes", value: k.pending_invitations ?? "·" })
        )
      );
    }
    const k = data.kpis || {};
    const dist = data.stage_distribution || [];
    const team = data.team_load || [];
    const projs = data.projects_health || [];
    const acts = data.recent_activity || [];

    const statusLabel = { overdue: "VENCIDO", urgent: "URGENTE", warning: "PRÓXIMO", on_track: "EN CURSO", completed: "ENTREGADO" };

    return e("div", { className: "dash-page bento-page" },
      e(DashHeader, {
        kicker: "/ DASHBOARD · ADMIN · EJECUTIVO",
        title:  "Estudio en vivo",
        sub:    "Vista 360 de operación, equipo y entregas.",
        actions: e(Btn, { onClick: () => navigate("/dashboard/admin/invitations") }, "Invitar cliente")
      }),

      e("div", { className: "bento-grid" },
        // HERO — Chart (big card top-left)
        e("section", { className: "bento-cell bento-hero" },
          e("header", { className: "bento-head" },
            e("div", null,
              e("span", { className: "bento-kicker" }, "/ CARGA OPERATIVA"),
              e("h2", null, "Distribución por etapa")
            ),
            e("div", { className: "bento-chips" },
              e("span", { className: "bento-chip" }, e("b", null, k.tasks_blocked ?? 0), " bloqueados"),
              e("span", { className: "bento-chip is-good" }, e("b", null, k.tasks_done_30d ?? 0), " ✓ 30d")
            )
          ),
          e("div", { className: "bento-chart" },
            e(BarChart, { data: dist.map(d => ({ label: String(d.stage_id).padStart(2, "0"), value: d.count, tipLabel: d.name || ("Etapa " + d.stage_id) })) })
          )
        ),

        // 4 KPI cells stacked right
        e("section", { className: "bento-cell bento-kpi is-accent", role: "button", onClick: () => navigate("/dashboard/admin/projects") },
          e("span", { className: "bento-kpi-label" }, "Proyectos activos"),
          e("strong", { className: "bento-kpi-val" }, k.active_projects ?? 0),
          e("span", { className: "bento-kpi-hint" }, "en curso")
        ),
        e("section", { className: "bento-cell bento-kpi" },
          e("span", { className: "bento-kpi-label" }, "En progreso"),
          e("strong", { className: "bento-kpi-val" }, k.tasks_in_progress ?? 0),
          e("span", { className: "bento-kpi-hint" }, "etapas vivas")
        ),
        e("section", { className: "bento-cell bento-kpi" },
          e("span", { className: "bento-kpi-label" }, "Avance medio"),
          e("strong", { className: "bento-kpi-val" }, (k.avg_progress ?? 0) + "%"),
          e("span", { className: "bento-kpi-hint" }, "portfolio")
        ),
        e("section", { className: "bento-cell bento-kpi is-warn" },
          e("span", { className: "bento-kpi-label" }, "Atrasados"),
          e("strong", { className: "bento-kpi-val" }, k.overdue_projects ?? 0),
          e("span", { className: "bento-kpi-hint" }, "vencidos")
        ),

        // TEAM — workload
        e("section", { className: "bento-cell bento-team" },
          e("header", { className: "bento-head" },
            e("div", null,
              e("span", { className: "bento-kicker" }, "/ EQUIPO"),
              e("h2", null, "Carga del staff")
            ),
            e("a", { className: "bento-link", href: "#/dashboard/admin/users", onClick: (ev) => { ev.preventDefault(); navigate("/dashboard/admin/users"); } }, "Gestionar →")
          ),
          e("ul", { className: "team-load" },
            team.length === 0 ? e("li", { className: "dash-list-empty" }, "Sin equipo activo") :
            team.slice(0, 5).map((m) =>
              e("li", { key: m.id, className: "team-load-row" },
                e("div", { className: "team-load-name" },
                  e("span", { className: "dash-side-avatar" }, (m.name || "?").slice(0, 1).toUpperCase()),
                  e("div", null,
                    e("b", null, m.name || "—"),
                    e("span", { className: "kpi-label" }, m.role)
                  )
                ),
                e("div", { className: "team-load-stats" },
                  e("span", { className: "team-pill is-progress" }, m.in_progress, " en curso"),
                  e("span", { className: "team-pill is-pending" },  m.pending, " pendientes"),
                  e("span", { className: "team-pill is-done" },     m.done_7d, " ✓ 7d")
                )
              )
            )
          )
        ),

        // ACTIVITY — LIVE feed
        e("section", { className: "bento-cell bento-activity" },
          e("header", { className: "bento-head" },
            e("div", null,
              e("span", { className: "bento-kicker" }, "/ TIEMPO REAL"),
              e("h2", null, "Actividad reciente")
            ),
            e("span", { className: "client-pulse-tag" },
              e("i", { className: "client-pulse-dot" }), "LIVE"
            )
          ),
          acts.length === 0 ? e("div", { className: "dash-list-empty" }, "Sin actividad reciente") :
          e("ul", { className: "activity-feed" },
            acts.slice(0, 6).map((a) =>
              e("li", { key: a.id },
                e("div", { className: "activity-meta" },
                  e("b", null, a.author || "—"),
                  e("span", { className: "phase-pill is-" + (a.author_role || "usuario") }, a.author_role === "usuario" ? "colaborador" : a.author_role),
                  e("span", null, a.project_title + " · " + a.stage_name),
                  e("time", null, new Date(a.created_at).toLocaleString())
                ),
                e("p", null, a.body)
              )
            )
          )
        ),

        // PROJECTS — full width health
        e("section", { className: "bento-cell bento-projects" },
          e("header", { className: "bento-head" },
            e("div", null,
              e("span", { className: "bento-kicker" }, "/ ENTREGAS"),
              e("h2", null, "Salud de proyectos")
            ),
            e("a", { className: "bento-link", href: "#/dashboard/admin/projects", onClick: (ev) => { ev.preventDefault(); navigate("/dashboard/admin/projects"); } }, "Ver todos →")
          ),
          projs.length === 0 ? e("div", { className: "dash-list-empty" }, "Sin proyectos") :
          e("div", { className: "health-grid" },
            projs.slice(0, 6).map((p) =>
              e("article", {
                key: p.id, className: "health-card is-" + p.status_label,
                onClick: () => navigate("/dashboard/admin/projects/" + p.id),
                role: "button"
              },
                e("div", { className: "health-head" },
                  e("span", { className: "health-num" }, "/ " + String(p.id).padStart(2, "0")),
                  e("span", { className: "health-status" }, statusLabel[p.status_label] || "—")
                ),
                e("h3", null, p.title),
                e("span", { className: "kpi-label" }, p.client || "—"),
                e(StageTrack, { currentStage: p.current_stage, size: "md" }),
                e("div", { className: "health-foot" },
                  e("b", null, Math.round(p.progress_pct || 0) + "%"),
                  e("span", null, p.days_left != null ? (p.days_left < 0 ? Math.abs(p.days_left) + "d vencido" : p.days_left + "d") : "—")
                )
              )
            )
          )
        )
      )
    );
  }

  function AdminProjects() {
    const [rows, setRows] = useState(null);
    const [showNew, setShowNew] = useState(false);

    const computeDaysLeft = (dateStr) => {
      if (!dateStr) return null;
      const d = new Date(dateStr + "T00:00:00");
      if (isNaN(d.getTime())) return null;
      const today = new Date(); today.setHours(0,0,0,0);
      return Math.round((d - today) / 86400000);
    };
    const statusFor = (days, pct) => {
      if ((pct ?? 0) >= 100) return "completed";
      if (days == null) return "on_track";
      if (days < 0) return "overdue";
      if (days <= 3) return "urgent";
      if (days <= 7) return "warning";
      return "on_track";
    };

    const load = async () => {
      const dash = await sb.rpc("bway_get_my_dashboard");
      let base = null;
      if (!dash.error && Array.isArray(dash.data?.recent_projects)) {
        base = dash.data.recent_projects.map(p => ({
          id: p.id, title: p.title,
          progress_pct: p.progress_pct, current_stage: p.current_stage,
          delivery_date: p.delivery_date,
          client: p.client ? { company_name: p.client } : null
        }));
      } else {
        const { data: projs, error } = await sb
          .from("projects")
          .select("id, title, progress_pct, current_stage, delivery_date, is_active, client_id")
          .order("created_at", { ascending: false });
        if (error) { setRows([]); return; }
        const ids = [...new Set((projs || []).map(p => p.client_id).filter(Boolean))];
        let map = {};
        if (ids.length) {
          const { data: cs } = await sb.from("clients").select("id, company_name").in("id", ids);
          map = Object.fromEntries((cs || []).map(c => [c.id, c]));
        }
        base = (projs || []).map(p => ({ ...p, client: map[p.client_id] }));
      }

      // Enrich with team avatars (unique assignees per project)
      const projectIds = base.map(p => p.id);
      let teamMap = {};
      if (projectIds.length) {
        const { data: stages } = await sb
          .from("project_stages")
          .select("project_id, assigned_to")
          .in("project_id", projectIds);
        const userIds = [...new Set((stages || []).map(s => s.assigned_to).filter(Boolean))];
        let users = {};
        if (userIds.length) {
          const { data: profs } = await sb
            .from("profiles")
            .select("id, full_name")
            .in("id", userIds);
          users = Object.fromEntries((profs || []).map(u => [u.id, u]));
        }
        for (const s of stages || []) {
          if (!s.assigned_to) continue;
          teamMap[s.project_id] = teamMap[s.project_id] || new Map();
          if (!teamMap[s.project_id].has(s.assigned_to)) {
            teamMap[s.project_id].set(s.assigned_to, users[s.assigned_to]?.full_name || "?");
          }
        }
      }

      const enriched = base.map(p => {
        const days = computeDaysLeft(p.delivery_date);
        return {
          ...p,
          days_left: days,
          status_label: statusFor(days, p.progress_pct),
          team: teamMap[p.id] ? Array.from(teamMap[p.id], ([id, name]) => ({ id, name })) : []
        };
      });
      setRows(enriched);
    };
    useEffect(() => { load(); }, []);

    const statusText = { overdue: "VENCIDO", urgent: "URGENTE", warning: "PRÓXIMO", on_track: "EN CURSO", completed: "ENTREGADO" };
    const daysText = (d) => d == null ? "Sin fecha" : (d < 0 ? Math.abs(d) + "d vencido" : (d === 0 ? "Hoy" : d + "d restantes"));

    const ProjectCard = ({ p, variant }) =>
      e("article", {
        className: "proj-bento-card is-" + p.status_label + (variant ? " is-" + variant : ""),
        onClick: () => navigate("/dashboard/admin/projects/" + p.id),
        role: "button", tabIndex: 0
      },
        e("header", { className: "proj-bento-head" },
          e("span", { className: "proj-bento-num" }, "/ " + String(p.id).padStart(2, "0")),
          e("span", { className: "proj-bento-status" }, statusText[p.status_label] || "—")
        ),
        e("div", { className: "proj-bento-body" },
          e("h3", { className: "proj-bento-title" }, p.title),
          e("span", { className: "proj-bento-client" }, p.client?.company_name || "—")
        ),
        e("div", { className: "proj-bento-track" },
          e(StageTrack, { currentStage: p.current_stage, size: variant === "hero" ? "lg" : "md" })
        ),
        e("footer", { className: "proj-bento-foot" },
          e("div", { className: "proj-bento-team" },
            p.team.length === 0
              ? e("span", { className: "proj-bento-team-empty" }, "Sin asignar")
              : p.team.slice(0, 4).map((m, i) =>
                  e("span", { key: m.id, className: "proj-bento-avatar", title: m.name, style: { zIndex: 10 - i } },
                    (m.name || "?").slice(0, 1).toUpperCase()
                  )),
            p.team.length > 4 ? e("span", { className: "proj-bento-avatar is-rest" }, "+" + (p.team.length - 4)) : null
          ),
          e("div", { className: "proj-bento-meta" },
            e("b", null, Math.round(p.progress_pct || 0) + "%"),
            e("span", null, daysText(p.days_left))
          )
        )
      );

    return e("div", { className: "dash-page bento-page" },
      e(DashHeader, {
        kicker: "/ PROYECTOS",
        title:  "Producciones",
        sub:    rows && rows.length ? rows.length + " producciones activas · vista bento" : null,
        actions: e(Btn, { onClick: () => setShowNew(true) }, "Nuevo proyecto")
      }),
      rows === null ? e(Spinner) :
        rows.length === 0
          ? e(EmptyState, { title: "Sin proyectos aún", body: "Crea el primero para que aparezca aquí.", action: e(Btn, { onClick: () => setShowNew(true) }, "Nuevo proyecto") })
          : e("div", { className: "proj-bento-grid" },
              rows.map((p, i) => e(ProjectCard, { key: p.id, p, variant: i === 0 ? "hero" : (i === 5 || i === 6 ? "wide" : null) }))
            ),
      showNew ? e(NewProjectModal, { onClose: () => setShowNew(false), onCreated: () => { setShowNew(false); load(); } }) : null
    );
  }

  function NewProjectModal({ onClose, onCreated }) {
    const [clients, setClients] = useState([]);
    const [title, setTitle] = useState("");
    const [description, setDescription] = useState("");
    const [clientId, setClientId] = useState("");
    const [deliveryDate, setDeliveryDate] = useState("");
    const [busy, setBusy] = useState(false);
    const [err, setErr]  = useState(null);

    useEffect(() => {
      sb.from("clients").select("id, company_name").eq("is_active", true).order("company_name")
        .then(({ data }) => setClients(data || []));
    }, []);

    const submit = async (ev) => {
      ev.preventDefault();
      setErr(null);
      if (!title || !clientId) return setErr("Título y marca son obligatorios.");
      setBusy(true);
      const { data: { user } } = await sb.auth.getUser();
      const { error } = await sb.from("projects").insert({
        title, description: description || null,
        client_id: parseInt(clientId, 10),
        start_date: new Date().toISOString().slice(0, 10),
        delivery_date: deliveryDate || null,
        created_by: user.id
      });
      setBusy(false);
      if (error) return setErr(error.message);
      onCreated();
    };

    return e("div", { className: "brand-modal", onClick: onClose },
      e("div", { className: "brand-modal-backdrop" }),
      e("div", { className: "brand-modal-card dash-modal-card", onClick: (ev) => ev.stopPropagation() },
        e("button", { className: "brand-modal-close", onClick: onClose }, e("span"), e("span")),
        e("div", { className: "brand-modal-body", style: { gridColumn: "1 / -1" } },
          e("div", { className: "brand-modal-kicker" }, "/ NUEVO PROYECTO"),
          e("h2", { className: "brand-modal-name" }, "Crear ", e("span", { className: "em" }, "producción")),
          e("form", { onSubmit: submit, className: "dash-form" },
            err ? e(Banner, { kind: "error" }, err) : null,
            e(Field, { label: "Título" }, e("input", { className: "dash-input", value: title, onChange: (ev) => setTitle(ev.target.value), required: true })),
            e(Field, { label: "Marca" },
              e("select", { className: "dash-input", value: clientId, onChange: (ev) => setClientId(ev.target.value), required: true },
                e("option", { value: "" }, "— elige —"),
                clients.map((c) => e("option", { key: c.id, value: c.id }, c.company_name))
              )
            ),
            e(Field, { label: "Descripción" }, e("textarea", { className: "dash-input dash-input-area", value: description, onChange: (ev) => setDescription(ev.target.value), rows: 3 })),
            e(Field, { label: "Fecha de entrega" }, e("input", { type: "date", className: "dash-input", value: deliveryDate, onChange: (ev) => setDeliveryDate(ev.target.value) })),
            e(Btn, { type: "submit", loading: busy }, "Crear y bootstrap etapas")
          )
        )
      )
    );
  }

  function AdminUsers() {
    const [rows, setRows] = useState(null);
    useEffect(() => {
      sb.from("profiles").select("id, role, full_name, company, is_active, activated_at").order("role")
        .then(({ data }) => setRows(data || []));
    }, []);
    return e("div", { className: "dash-page" },
      e(DashHeader, { kicker: "/ COLABORADORES", title: "Equipo y clientes" }),
      rows === null ? e(Spinner) :
        e("table", { className: "dash-table" },
          e("thead", null, e("tr", null, e("th", null, "Nombre"), e("th", null, "Rol"), e("th", null, "Empresa"), e("th", null, "Estado"), e("th", null, "Activado"))),
          e("tbody", null,
            rows.map((u) => {
              const roleLabel = u.role === "usuario" ? "colaborador" : u.role;
              return e("tr", { key: u.id },
                e("td", null, u.full_name || "—"),
                e("td", null, e("span", { className: "phase-pill is-" + u.role }, roleLabel)),
                e("td", null, u.company || "—"),
                e("td", null, u.is_active ? "Activo" : "Pendiente"),
                e("td", null, u.activated_at ? new Date(u.activated_at).toLocaleDateString() : "—")
              );
            })
          )
        )
    );
  }

  // ===========================================================
  //  SHOP — digital products catalog (admin CRUD)
  // ===========================================================

  const PRODUCT_KINDS = [
    { value: "lut",      label: "LUT"            },
    { value: "preset",   label: "Preset"         },
    { value: "overlay",  label: "Overlay"        },
    { value: "sound",    label: "Sound design"   },
    { value: "pack",     label: "Pack"           },
    { value: "template", label: "Template"       },
    { value: "tutorial", label: "Tutorial"       },
    { value: "other",    label: "Otro"           },
  ];

  function shopPublicUrl(path) {
    if (!path) return null;
    return cfg.SUPABASE_URL + "/storage/v1/object/public/bway-shop/" + path;
  }

  function AdminShop() {
    const [rows, setRows]   = useState(null);
    const [editing, setEditing] = useState(null); // null | "new" | productObject
    const load = () => sb.from("digital_products")
      .select("*").order("sort_order", { ascending: true }).order("created_at", { ascending: false })
      .then(({ data }) => setRows(data || []));
    useEffect(() => { load(); }, []);

    const remove = async (p) => {
      if (!window.confirm(`¿Eliminar "${p.name}"? Esta acción no se puede deshacer.`)) return;
      if (p.cover_path) { try { await sb.storage.from("bway-shop").remove([p.cover_path]); } catch {} }
      await sb.from("digital_products").delete().eq("id", p.id);
      load();
    };
    const toggle = async (p, field) => {
      await sb.from("digital_products").update({ [field]: !p[field] }).eq("id", p.id);
      load();
    };

    return e("div", { className: "dash-page" },
      e(DashHeader, {
        kicker: "/ TIENDA · CATÁLOGO",
        title:  "Productos digitales",
        sub:    "LUTs, presets, packs y material descargable para creators.",
        actions: e(Btn, { onClick: () => setEditing("new") }, "+ Nuevo producto")
      }),

      rows === null ? e(Spinner) :
        rows.length === 0
          ? e(EmptyState, {
              title: "Aún no hay productos.",
              body:  "Crea el primero para que aparezca en el landing público.",
              action: e(Btn, { onClick: () => setEditing("new") }, "+ Nuevo producto")
            })
          : e("div", { className: "shop-admin-grid" },
              rows.map((p) =>
                e("article", { key: p.id, className: "shop-admin-card" + (p.is_active ? "" : " is-inactive") },
                  e("div", { className: "shop-admin-cover" },
                    p.cover_path
                      ? e("img", { src: shopPublicUrl(p.cover_path), alt: p.name, loading: "lazy" })
                      : e("div", { className: "shop-admin-cover-empty" }, "Sin cover")
                  ),
                  e("div", { className: "shop-admin-body" },
                    e("div", { className: "shop-admin-head" },
                      e("span", { className: "shop-admin-kind" }, PRODUCT_KINDS.find(k => k.value === p.kind)?.label || p.kind),
                      p.is_featured ? e("span", { className: "shop-admin-featured" }, "★ destacado") : null
                    ),
                    e("h3", null, p.name),
                    p.tagline ? e("p", { className: "shop-admin-tag" }, p.tagline) : null,
                    e("div", { className: "shop-admin-price" },
                      e("b", null, (p.price_label || (p.price_currency + " " + Number(p.price_amount).toFixed(2)))),
                    ),
                    e("div", { className: "shop-admin-actions" },
                      e("button", { className: "phase-step-btn is-ghost", onClick: () => setEditing(p) }, "✏ Editar"),
                      // Status pill (info only — NOT clickable) + separate toggle button
                      e("span", {
                        className: "shop-status-pill " + (p.is_active ? "is-on" : "is-off")
                      }, p.is_active ? "● EN VIVO" : "● OCULTO"),
                      e("button", {
                        className: "phase-step-btn is-ghost",
                        onClick: () => toggle(p, "is_active"),
                        title: p.is_active ? "Ocultar del landing" : "Publicar al landing"
                      }, p.is_active ? "Ocultar" : "Publicar"),
                      e("button", {
                        className: "phase-step-btn is-ghost",
                        onClick: () => toggle(p, "is_featured"),
                        title: p.is_featured ? "Quitar de destacados" : "Destacar arriba del catálogo"
                      }, p.is_featured ? "★ Quitar destacado" : "☆ Destacar"),
                      e("button", { className: "phase-step-btn is-ghost shop-admin-del", onClick: () => remove(p) }, "🗑")
                    )
                  )
                )
              )
            ),

      editing ? e(ProductFormModal, {
        product: editing === "new" ? null : editing,
        onClose: () => setEditing(null),
        onSaved: () => { setEditing(null); load(); }
      }) : null
    );
  }

  function ProductFormModal({ product, onClose, onSaved }) {
    const isEdit = !!product;
    const emptyForm = {
      slug: "", name: "", kind: "lut",
      tagline: "", description: "",
      price_amount: 0, price_currency: "USD", price_label: "",
      cover_path: null, preview_url: "", buy_url: "",
      sort_order: 0, is_active: true, is_featured: false,
      meta_specs: {}
    };
    const [form, setForm] = useState(() => product ? { ...emptyForm, ...product, is_active: product.is_active !== false } : emptyForm);
    const [coverFile, setCoverFile] = useState(null);
    const [busy, setBusy] = useState(false);
    const [err,  setErr]  = useState(null);
    const coverRef = useRef(null);
    const setField = (k, v) => setForm((f) => ({ ...f, [k]: v }));
    const slugify = (s) => (s || "")
      .toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "")
      .replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");

    const save = async (ev) => {
      ev.preventDefault();
      setErr(null); setBusy(true);
      try {
        if (!form.name.trim()) throw new Error("El nombre es obligatorio.");
        const slug = form.slug.trim() || slugify(form.name);

        // 1. Upload cover if new file present
        let coverPath = form.cover_path;
        if (coverFile) {
          if (coverFile.size > 10 * 1024 * 1024) throw new Error("Cover excede 10 MB.");
          const cleanName = coverFile.name.replace(/[^a-zA-Z0-9.\-_]/g, "_");
          const newPath = `covers/${Date.now()}-${cleanName}`;
          const up = await sb.storage.from("bway-shop").upload(newPath, coverFile, {
            cacheControl: "3600", upsert: false, contentType: coverFile.type || undefined
          });
          if (up.error) throw up.error;
          // Drop old cover if replacing
          if (coverPath) { try { await sb.storage.from("bway-shop").remove([coverPath]); } catch {} }
          coverPath = newPath;
        }

        const payload = {
          slug,
          name:        form.name.trim(),
          kind:        form.kind,
          tagline:     form.tagline?.trim() || null,
          description: form.description?.trim() || null,
          price_amount: Number(form.price_amount) || 0,
          price_currency: form.price_currency || "USD",
          price_label: form.price_label?.trim() || null,
          cover_path:  coverPath,
          preview_url: form.preview_url?.trim() || null,
          buy_url:     form.buy_url?.trim() || null,
          sort_order:  parseInt(form.sort_order, 10) || 0,
          is_active:   !!form.is_active,
          is_featured: !!form.is_featured,
          meta_specs:  form.meta_specs || {},
        };

        let res;
        if (isEdit) {
          res = await sb.from("digital_products").update(payload).eq("id", product.id);
        } else {
          const { data: { user } } = await sb.auth.getUser();
          res = await sb.from("digital_products").insert({ ...payload, created_by: user.id });
        }
        if (res.error) throw res.error;
        onSaved();
      } catch (x) { setErr(x.message || "No se pudo guardar"); }
      finally { setBusy(false); }
    };

    return e("div", { className: "brand-modal-wrap shop-modal-wrap", onClick: (ev) => { if (ev.target === ev.currentTarget) onClose(); } },
      e("div", { className: "brand-modal-backdrop" }),
      e("form", { className: "brand-modal-card dash-modal-card shop-modal", onSubmit: save, onClick: (ev) => ev.stopPropagation() },
        e("header", { className: "shop-modal-head" },
          e("span", { className: "bento-kicker" }, isEdit ? "/ EDITAR PRODUCTO" : "/ NUEVO PRODUCTO"),
          e("h2", null, isEdit ? form.name || "Producto" : "Crear producto"),
          e("button", { type: "button", className: "shop-modal-close", onClick: onClose, "aria-label": "Cerrar" }, "×")
        ),
        err ? e(Banner, { kind: "error" }, err) : null,

        e("div", { className: "modal-form-grid" },
          e(Field, { label: "Nombre *" },
            e("input", { className: "dash-input", value: form.name,
              onChange: (ev) => setField("name", ev.target.value), required: true })
          ),
          e(Field, { label: "Slug (URL)" },
            e("input", { className: "dash-input", value: form.slug, placeholder: slugify(form.name) || "mi-producto",
              onChange: (ev) => setField("slug", ev.target.value) })
          ),
          e(Field, { label: "Tipo" },
            e("select", { className: "dash-input", value: form.kind, onChange: (ev) => setField("kind", ev.target.value) },
              PRODUCT_KINDS.map((k) => e("option", { key: k.value, value: k.value }, k.label))
            )
          ),
          e(Field, { label: "Tagline (1 línea)" },
            e("input", { className: "dash-input", value: form.tagline || "",
              placeholder: "Ej: 12 looks cinemáticos para Premiere & DaVinci",
              onChange: (ev) => setField("tagline", ev.target.value) })
          ),
          e(Field, { label: "Precio *", colSpan: 1 },
            e("input", { className: "dash-input", type: "number", step: "0.01", min: "0",
              value: form.price_amount, onChange: (ev) => setField("price_amount", ev.target.value) })
          ),
          e(Field, { label: "Moneda" },
            e("select", { className: "dash-input", value: form.price_currency, onChange: (ev) => setField("price_currency", ev.target.value) },
              ["USD", "EUR", "CRC", "MXN"].map(c => e("option", { key: c, value: c }, c))
            )
          ),
          e(Field, { label: "Etiqueta de precio (opcional)" },
            e("input", { className: "dash-input", value: form.price_label || "",
              placeholder: "Ej: desde $19 · pack completo $49",
              onChange: (ev) => setField("price_label", ev.target.value) })
          ),
          e(Field, { label: "Descripción", colSpan: 2 },
            e("textarea", { className: "dash-input dash-input-area", rows: 4, value: form.description || "",
              placeholder: "Detalles del producto, formato, qué incluye, software compatible...",
              onChange: (ev) => setField("description", ev.target.value) })
          ),
          e(Field, { label: "URL preview (Vimeo / YouTube)" },
            e("input", { className: "dash-input", type: "url", value: form.preview_url || "",
              placeholder: "https://vimeo.com/...",
              onChange: (ev) => setField("preview_url", ev.target.value) })
          ),
          e(Field, { label: "URL de compra (Gumroad / Stripe / mailto)" },
            e("input", { className: "dash-input", type: "url", value: form.buy_url || "",
              placeholder: "https://gumroad.com/...  o  mailto:hola@bway.studio",
              onChange: (ev) => setField("buy_url", ev.target.value) })
          ),
          e(Field, { label: "Cover image (≤ 10 MB, ratio 4:5 ideal)", colSpan: 2 },
            e("div", { className: "shop-cover-row" },
              form.cover_path && !coverFile
                ? e("img", { className: "shop-cover-thumb", src: shopPublicUrl(form.cover_path), alt: "" })
                : null,
              coverFile
                ? e("span", { className: "bw-composer-fileinfo" },
                    e("b", null, coverFile.name),
                    e("span", null, Math.round(coverFile.size / 1024) + " KB")
                  )
                : null,
              e("input", { ref: coverRef, type: "file", accept: "image/*",
                onChange: (ev) => setCoverFile(ev.target.files?.[0] || null), style: { display: "none" } }),
              e("button", { type: "button", className: "phase-step-btn is-ghost",
                onClick: () => coverRef.current?.click() },
                form.cover_path || coverFile ? "↻ Cambiar cover" : "📎 Subir cover")
            )
          ),
          e(Field, { label: "Sort order" },
            e("input", { className: "dash-input", type: "number", value: form.sort_order,
              onChange: (ev) => setField("sort_order", ev.target.value) })
          ),
          e(Field, { label: "Visibilidad" },
            e("label", { className: "shop-toggle" },
              e("input", { type: "checkbox", checked: !!form.is_active, onChange: (ev) => setField("is_active", ev.target.checked) }),
              " Activo (visible en landing)"
            )
          ),
          e(Field, { label: "Destacado" },
            e("label", { className: "shop-toggle" },
              e("input", { type: "checkbox", checked: !!form.is_featured, onChange: (ev) => setField("is_featured", ev.target.checked) }),
              " Destacar arriba"
            )
          )
        ),

        e("footer", { className: "shop-modal-foot" },
          e("button", { type: "button", className: "phase-step-btn is-ghost", onClick: onClose }, "Cancelar"),
          e("button", { type: "submit", className: "phase-step-btn is-gold", disabled: busy },
            busy ? "Guardando..." : (isEdit ? "Guardar cambios" : "Crear producto"))
        )
      )
    );
  }

  // ==========================================================
  // QUOTES — Admin builder + list, Client view via RPC
  // ==========================================================
  function fmtMoney(n, currency) {
    if (n == null || isNaN(n)) return "—";
    const v = Math.round(Number(n));
    if (currency === "USD") return "$" + v.toLocaleString("en-US");
    return "₡" + v.toLocaleString("es-CR");
  }

  // ==========================================================
  // PDF export para una cotización. Usa jsPDF (UMD via CDN).
  // Layout editorial con paleta BWAY: black + gold serif.
  // ==========================================================
  function downloadQuotePdf(q) {
    const jsPDF = window.jspdf?.jsPDF;
    if (!jsPDF) {
      alert("No se pudo cargar jsPDF. Recargá la página.");
      return;
    }
    const doc = new jsPDF({ unit: "pt", format: "letter" });
    const W = doc.internal.pageSize.getWidth();
    const H = doc.internal.pageSize.getHeight();
    const M = 48; // margin

    // Black background banner
    doc.setFillColor(10, 8, 5);
    doc.rect(0, 0, W, 130, "F");
    // Gold thin line
    doc.setDrawColor(244, 214, 138);
    doc.setLineWidth(0.6);
    doc.line(M, 116, W - M, 116);

    // Brand
    doc.setTextColor(244, 214, 138);
    doc.setFont("helvetica", "bold");
    doc.setFontSize(9);
    doc.text("/ COTIZACIÓN · BWAY PROD", M, 56);

    doc.setTextColor(247, 239, 223);
    doc.setFontSize(22);
    doc.setFont("times", "italic");
    doc.text("BWAY PROD", M, 85);

    doc.setTextColor(214, 178, 93);
    doc.setFont("helvetica", "normal");
    doc.setFontSize(9);
    doc.text("Cinematografía que posiciona marca", M, 102);

    // Status badge top-right
    const stMap = {
      draft: { txt: "BORRADOR", rgb: [120,120,120] },
      sent:  { txt: "ENVIADA",  rgb: [244,214,138] },
      approved: { txt: "APROBADA", rgb: [110,210,140] },
      rejected: { txt: "RECHAZADA", rgb: [220,90,90] },
      expired:  { txt: "EXPIRADA", rgb: [180,140,60] }
    };
    const st = stMap[q.status] || stMap.draft;
    doc.setFillColor(st.rgb[0], st.rgb[1], st.rgb[2]);
    doc.roundedRect(W - M - 90, 50, 90, 22, 11, 11, "F");
    doc.setTextColor(10, 8, 5);
    doc.setFont("helvetica", "bold");
    doc.setFontSize(8);
    doc.text(st.txt, W - M - 45, 65, { align: "center" });

    // ID + currency
    doc.setTextColor(214, 178, 93);
    doc.setFontSize(8);
    doc.text("#" + (q.id || "").slice(0, 8).toUpperCase() + "  ·  " + q.currency, W - M, 88, { align: "right" });

    // Title
    let y = 170;
    doc.setTextColor(20, 18, 14);
    doc.setFont("times", "normal");
    doc.setFontSize(24);
    const titleLines = doc.splitTextToSize(q.title || "Cotización", W - 2 * M);
    doc.text(titleLines, M, y);
    y += titleLines.length * 26;

    // Client + dates
    doc.setFontSize(10);
    doc.setFont("helvetica", "normal");
    doc.setTextColor(80, 80, 80);
    doc.text("PARA", M, y + 4);
    doc.setTextColor(30, 30, 30);
    doc.setFont("helvetica", "bold");
    doc.text(q.client_name || "—", M, y + 22);
    if (q.client_email) {
      doc.setFont("helvetica", "normal");
      doc.text(q.client_email, M, y + 38);
    }

    // Right column: dates
    const colR = W - M - 180;
    doc.setFont("helvetica", "normal");
    doc.setTextColor(80, 80, 80);
    doc.text("FECHA", colR, y + 4);
    doc.setTextColor(30, 30, 30);
    doc.text(new Date(q.created_at || q.updated_at || Date.now()).toLocaleDateString("es-CR", { day: "numeric", month: "long", year: "numeric" }), colR, y + 22);
    if (q.expires_at) {
      doc.setTextColor(80, 80, 80);
      doc.text("VENCE", colR, y + 42);
      doc.setTextColor(30, 30, 30);
      doc.text(new Date(q.expires_at).toLocaleDateString("es-CR", { day: "numeric", month: "long", year: "numeric" }), colR, y + 58);
    }

    y += 80;

    // Items table header
    doc.setFillColor(244, 214, 138);
    doc.rect(M, y, W - 2 * M, 24, "F");
    doc.setTextColor(20, 16, 8);
    doc.setFont("helvetica", "bold");
    doc.setFontSize(8);
    doc.text("SERVICIO", M + 14, y + 16);
    doc.text("CANT.", W - M - 200, y + 16, { align: "right" });
    doc.text("PRECIO", W - M - 110, y + 16, { align: "right" });
    doc.text("SUBTOTAL", W - M - 14, y + 16, { align: "right" });
    y += 32;

    const items = Array.isArray(q.line_items) ? q.line_items : [];
    doc.setFont("helvetica", "normal");
    doc.setFontSize(10);
    items.forEach((li, i) => {
      if (y > H - 220) {
        doc.addPage();
        y = 60;
      }
      if (i % 2 === 0) {
        doc.setFillColor(252, 248, 240);
        doc.rect(M, y - 12, W - 2 * M, 24, "F");
      }
      doc.setTextColor(30, 30, 30);
      const name = doc.splitTextToSize(li.name || "Servicio", W - 2 * M - 250);
      doc.text(name[0] || "—", M + 14, y + 4);

      doc.setTextColor(60, 60, 60);
      doc.text(String(li.qty || 1), W - M - 200, y + 4, { align: "right" });
      doc.text(fmtMoney(li.unit_price, q.currency), W - M - 110, y + 4, { align: "right" });

      doc.setTextColor(30, 30, 30);
      doc.setFont("helvetica", "bold");
      doc.text(fmtMoney((li.qty || 0) * (li.unit_price || 0), q.currency), W - M - 14, y + 4, { align: "right" });
      doc.setFont("helvetica", "normal");
      y += 24;
    });

    y += 8;
    if (y > H - 180) { doc.addPage(); y = 60; }

    // Totals box
    const totBoxX = W - M - 240;
    const subtotal = Number(q.subtotal || 0);
    const discount = subtotal * (Number(q.discount_pct) || 0) / 100;
    const tax = (subtotal - discount) * (Number(q.tax_pct) || 0) / 100;
    const total = Number(q.total || 0);

    doc.setDrawColor(220, 200, 160);
    doc.line(totBoxX, y, W - M, y);
    y += 18;

    function row(label, value, bold) {
      doc.setFont("helvetica", bold ? "bold" : "normal");
      doc.setTextColor(80, 80, 80);
      doc.setFontSize(10);
      doc.text(label, totBoxX, y);
      doc.setTextColor(20, 20, 20);
      doc.text(value, W - M, y, { align: "right" });
      y += 18;
    }
    row("Subtotal", fmtMoney(subtotal, q.currency));
    if (discount > 0) row("Descuento " + (Number(q.discount_pct) || 0) + "%", "− " + fmtMoney(discount, q.currency));
    if (tax > 0)      row("Impuesto " + (Number(q.tax_pct) || 0) + "%", "+ " + fmtMoney(tax, q.currency));

    // Final total
    y += 4;
    doc.setFillColor(10, 8, 5);
    doc.roundedRect(totBoxX - 4, y - 14, W - M - totBoxX + 4, 32, 4, 4, "F");
    doc.setTextColor(244, 214, 138);
    doc.setFont("helvetica", "bold");
    doc.setFontSize(10);
    doc.text("TOTAL", totBoxX + 4, y + 5);
    doc.setFontSize(15);
    doc.text(fmtMoney(total, q.currency), W - M - 4, y + 6, { align: "right" });
    y += 40;

    // Notes
    if (q.notes && y < H - 100) {
      doc.setTextColor(80, 80, 80);
      doc.setFont("helvetica", "bold");
      doc.setFontSize(9);
      doc.text("NOTAS", M, y);
      doc.setFont("helvetica", "normal");
      doc.setFontSize(10);
      doc.setTextColor(40, 40, 40);
      const nl = doc.splitTextToSize(q.notes, W - 2 * M);
      doc.text(nl, M, y + 16);
      y += 16 + nl.length * 14;
    }

    // Footer en cada página
    const pages = doc.internal.getNumberOfPages();
    for (let i = 1; i <= pages; i++) {
      doc.setPage(i);
      doc.setDrawColor(220, 200, 160);
      doc.setLineWidth(0.3);
      doc.line(M, H - 50, W - M, H - 50);
      doc.setTextColor(150, 150, 150);
      doc.setFont("helvetica", "italic");
      doc.setFontSize(8);
      doc.text("\"El valor no está en lo que dura el evento, está en lo que queda después.\"", M, H - 35);
      doc.setFont("helvetica", "normal");
      doc.text("BWAY PROD · bwayprod.vip", M, H - 22);
      doc.text("Página " + i + " / " + pages, W - M, H - 22, { align: "right" });
    }

    const fname = "BWAY-Cotizacion-" + (q.id || "draft").slice(0, 8) + "-" +
      (q.client_name || "cliente").replace(/[^a-z0-9]+/gi, "_").slice(0, 20) + ".pdf";
    doc.save(fname);
  }

  function quoteStatusBadge(s) {
    const map = {
      draft:    { label: "BORRADOR", cls: "qb-st-draft" },
      sent:     { label: "ENVIADA",  cls: "qb-st-sent" },
      approved: { label: "APROBADA", cls: "qb-st-ok" },
      rejected: { label: "RECHAZADA",cls: "qb-st-no" },
      expired:  { label: "EXPIRADA", cls: "qb-st-exp" }
    };
    const m = map[s] || { label: s, cls: "qb-st-draft" };
    return e("span", { className: "qb-status " + m.cls }, m.label);
  }

  function AdminQuotes() {
    const [rows, setRows] = useState([]);
    const [loading, setLoading] = useState(true);
    const [err, setErr] = useState(null);
    const [filter, setFilter] = useState("all");
    const [query, setQuery] = useState("");

    useEffect(() => {
      let alive = true;
      sb.rpc("list_quotes").then(({ data, error }) => {
        if (!alive) return;
        if (error) setErr(error.message);
        else setRows(data || []);
        setLoading(false);
      });
      return () => { alive = false; };
    }, []);

    // KPIs cinematográficos del estudio
    const totalAll  = rows.reduce((s, r) => s + Number(r.total || 0), 0);
    const totalApr  = rows.filter(r => r.status === "approved").reduce((s, r) => s + Number(r.total || 0), 0);
    const cntDraft  = rows.filter(r => r.status === "draft").length;
    const cntSent   = rows.filter(r => r.status === "sent").length;
    const cntApr    = rows.filter(r => r.status === "approved").length;
    const cntRej    = rows.filter(r => r.status === "rejected").length;
    const aprRate   = rows.length ? Math.round(cntApr / Math.max(1, cntApr + cntRej) * 100) : 0;

    const filtered = (filter === "all" ? rows : rows.filter(r => r.status === filter))
      .filter(r => {
        if (!query) return true;
        const q = query.toLowerCase();
        return (r.title || "").toLowerCase().includes(q)
          || (r.client_name || "").toLowerCase().includes(q)
          || (r.client_email || "").toLowerCase().includes(q);
      });

    return e("div", { className: "qb2-screen" },
      // ── HERO ─────────────────────────────────────────────
      e("section", { className: "qb2-hero" },
        e("div", { className: "qb2-hero-grain", "aria-hidden": "true" }),
        e("div", { className: "qb2-hero-beam", "aria-hidden": "true" }),
        e("div", { className: "qb2-slate" },
          e("span", { className: "qb2-slate-rec" }, e("i"), "REC"),
          e("span", { className: "qb2-slate-sep" }, "·"),
          e("span", null, "BWAY STUDIO"),
          e("span", { className: "qb2-slate-sep" }, "·"),
          e("span", null, "COTIZACIONES")
        ),
        e("h1", { className: "qb2-hero-title" }, "El ", e("em", null, "estudio"), " que cotiza."),
        e("p", { className: "qb2-hero-sub" }, "Componé paquetes a medida con el catálogo MUSA. Cada cotización es una escena: empieza en draft, viaja al cliente, se aprueba, se vuelve obra."),
        e("div", { className: "qb2-stats" },
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "Total facturable"),
            e("b", null, fmtMoney(totalAll, "CRC")),
            e("span", { className: "qb2-stat-hint" }, rows.length + " cotizaciones totales")
          ),
          e("div", { className: "qb2-stat qb2-stat-accent" },
            e("span", { className: "qb2-stat-kicker" }, "Aprobado"),
            e("b", null, fmtMoney(totalApr, "CRC")),
            e("span", { className: "qb2-stat-hint" }, cntApr + " cerradas")
          ),
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "Tasa de aprobación"),
            e("b", null, aprRate + "%"),
            e("span", { className: "qb2-stat-hint" }, "aprobadas vs rechazadas")
          ),
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "En vuelo"),
            e("b", null, cntSent),
            e("span", { className: "qb2-stat-hint" }, cntDraft + " borrador · esperando respuesta")
          )
        ),
        e("div", { className: "qb2-hero-actions" },
          e("button", {
            className: "qb2-cta-new",
            onClick: () => navigate("/dashboard/admin/quotes/new")
          },
            e("span", { className: "qb2-cta-icon", "aria-hidden": "true" }, "✎"),
            "Nueva cotización",
            e("span", { className: "qb2-cta-arrow" }, "→")
          )
        )
      ),

      // ── TOOLBAR ─────────────────────────────────────────
      e("div", { className: "qb2-toolbar" },
        e("div", { className: "qb2-filters" },
          ["all","draft","sent","approved","rejected"].map(f =>
            e("button", {
              key: f,
              className: "qb2-filter qb2-filter-" + f + (filter === f ? " is-active" : ""),
              onClick: () => setFilter(f)
            },
              e("i", null),
              f === "all" ? "Todas" :
              f === "draft" ? "Borradores" :
              f === "sent" ? "Enviadas" :
              f === "approved" ? "Aprobadas" :
              "Rechazadas",
              e("b", null,
                f === "all" ? rows.length :
                f === "draft" ? cntDraft :
                f === "sent" ? cntSent :
                f === "approved" ? cntApr :
                cntRej
              )
            )
          )
        ),
        e("div", { className: "qb2-search" },
          e("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2 },
            e("circle", { cx: 11, cy: 11, r: 7 }),
            e("path", { d: "m21 21-4.3-4.3" })
          ),
          e("input", {
            type: "search",
            placeholder: "Buscar por cliente, título, email…",
            value: query,
            onChange: ev => setQuery(ev.target.value)
          })
        )
      ),

      err ? e("div", { className: "dash-err" }, err) : null,

      // ── GRID / EMPTY ────────────────────────────────────
      loading ? e("div", { className: "qb2-skel" },
        [0,1,2,3].map(i => e("div", { key: i, className: "qb2-skel-card" }))
      ) :
      filtered.length === 0 ? e("div", { className: "qb2-empty" },
        e("div", { className: "qb2-empty-mark" }, "✎"),
        e("h3", null, query ? "Sin resultados" : (filter === "all" ? "Aún no hay cotizaciones" : "Nada en este filtro")),
        e("p", null, query ? "Probá con otro término o limpia la búsqueda." : "Creá la primera cotización combinando servicios del catálogo MUSA. Te toma menos de 2 minutos."),
        !query && filter === "all" ? e("button", {
          className: "qb2-cta-new",
          onClick: () => navigate("/dashboard/admin/quotes/new")
        }, "✎ Crear la primera", e("span", { className: "qb2-cta-arrow" }, "→")) : null
      ) :
      e("div", { className: "qb2-grid" },
        filtered.map(q => {
          const items = Array.isArray(q.line_items) ? q.line_items : [];
          const itemCount = items.length;
          const updated = new Date(q.updated_at);
          const daysAgo = Math.floor((Date.now() - updated.getTime()) / 86400000);
          const dateLabel = daysAgo === 0 ? "Hoy"
                          : daysAgo === 1 ? "Ayer"
                          : daysAgo < 7 ? "Hace " + daysAgo + " días"
                          : updated.toLocaleDateString("es-CR", { day: "numeric", month: "short" });
          return e("article", {
            key: q.id,
            className: "qb2-card qb2-card-" + q.status,
            onClick: () => navigate("/dashboard/admin/quotes/" + q.id),
            role: "button",
            tabIndex: 0
          },
            // Sprocket holes (cinta de film)
            e("div", { className: "qb2-card-perf qb2-card-perf-l", "aria-hidden": "true" }),
            e("div", { className: "qb2-card-perf qb2-card-perf-r", "aria-hidden": "true" }),

            // Header
            e("div", { className: "qb2-card-head" },
              e("div", { className: "qb2-card-meta" },
                e("span", { className: "qb2-card-num" }, "#" + q.id.slice(0, 8).toUpperCase()),
                e("span", { className: "qb2-card-curr" }, q.currency)
              ),
              quoteStatusBadge(q.status)
            ),

            // Title + client
            e("h3", { className: "qb2-card-title" }, q.title),
            e("div", { className: "qb2-card-client" },
              e("div", { className: "qb2-avatar" }, (q.client_name || "?").charAt(0).toUpperCase()),
              e("div", { style: { minWidth: 0 } },
                e("div", { className: "qb2-card-client-name" }, q.client_name || "Sin cliente"),
                e("div", { className: "qb2-card-client-email" }, q.client_email || "—")
              )
            ),

            // Items preview
            itemCount > 0 ? e("div", { className: "qb2-card-items" },
              items.slice(0, 2).map((li, i) => e("div", { key: i, className: "qb2-card-li" },
                e("span", { className: "qb2-card-li-dot" }),
                e("span", { className: "qb2-card-li-name" }, li.qty + "× " + li.name)
              )),
              itemCount > 2 ? e("div", { className: "qb2-card-li-more" }, "+ " + (itemCount - 2) + " más") : null
            ) : null,

            // Footer
            e("div", { className: "qb2-card-foot" },
              e("div", { className: "qb2-card-total" },
                e("span", { className: "qb2-card-total-label" }, "TOTAL"),
                e("b", null, fmtMoney(q.total, q.currency))
              ),
              e("div", { className: "qb2-card-date" }, dateLabel)
            ),

            // Corner marks
            e("span", { className: "qb2-corner qb2-corner-tl" }),
            e("span", { className: "qb2-corner qb2-corner-tr" }),
            e("span", { className: "qb2-corner qb2-corner-bl" }),
            e("span", { className: "qb2-corner qb2-corner-br" })
          );
        })
      )
    );
  }

  function AdminQuoteEditor({ quoteId }) {
    const catalog = window.BWAY_QUOTE_CATALOG;
    const [loading, setLoading] = useState(!!quoteId);
    const [saving,  setSaving]  = useState(false);
    const [err,     setErr]     = useState(null);
    const [clients, setClients] = useState([]);
    const [activeCat, setActiveCat] = useState(catalog.categories[0].id);
    const [showTemplates, setShowTemplates] = useState(false);
    const [templates, setTemplates] = useState([]);

    const [form, setForm] = useState({
      id: null,
      client_id: "",
      client_name: "",
      client_email: "",
      title: "Cotización · " + new Date().toLocaleDateString("es-CR"),
      notes: "",
      currency: "CRC",
      status: "draft",
      discount_pct: 0,
      tax_pct: 13,
      expires_at: "",
      line_items: []
    });

    // Load clients (admin sees all profiles with role usuario/cliente)
    useEffect(() => {
      sb.from("profiles").select("id, full_name, company, email").in("role", ["cliente","usuario"]).order("full_name")
        .then(({ data }) => setClients(data || []));
    }, []);

    // Load existing quote if editing
    useEffect(() => {
      if (!quoteId) return;
      sb.from("quotes").select("*").eq("id", quoteId).single().then(({ data, error }) => {
        if (error) setErr(error.message);
        else if (data) setForm({
          id: data.id,
          client_id: data.client_id || "",
          client_name: data.client_name || "",
          client_email: data.client_email || "",
          title: data.title || "",
          notes: data.notes || "",
          currency: data.currency || "CRC",
          status: data.status,
          discount_pct: Number(data.discount_pct) || 0,
          tax_pct: Number(data.tax_pct) || 0,
          expires_at: data.expires_at || "",
          line_items: data.line_items || []
        });
        setLoading(false);
      });
    }, [quoteId]);

    function set(field, value) { setForm(f => ({ ...f, [field]: value })); }

    function addItem(item) {
      const price = form.currency === "USD" ? item.usd : item.crc;
      if (price == null) {
        alert("Este ítem no tiene precio en " + form.currency + ". Cambiá la moneda en el header.");
        return;
      }
      const existing = form.line_items.find(li => li.id === item.id);
      if (existing) {
        set("line_items", form.line_items.map(li => li.id === item.id ? { ...li, qty: li.qty + 1 } : li));
      } else {
        set("line_items", [...form.line_items, {
          id: item.id, cat: item.cat, name: item.name, qty: 1, unit_price: price
        }]);
      }
    }
    function updateQty(id, qty) {
      const n = Math.max(0, Math.floor(Number(qty) || 0));
      if (n === 0) {
        set("line_items", form.line_items.filter(li => li.id !== id));
      } else {
        set("line_items", form.line_items.map(li => li.id === id ? { ...li, qty: n } : li));
      }
    }
    function updatePrice(id, price) {
      const n = Math.max(0, Number(price) || 0);
      set("line_items", form.line_items.map(li => li.id === id ? { ...li, unit_price: n } : li));
    }
    function removeItem(id) {
      set("line_items", form.line_items.filter(li => li.id !== id));
    }

    const subtotal = form.line_items.reduce((s, li) => s + (li.qty * li.unit_price), 0);
    const discount = subtotal * (Number(form.discount_pct) || 0) / 100;
    const tax      = (subtotal - discount) * (Number(form.tax_pct) || 0) / 100;
    const total    = subtotal - discount + tax;

    async function save(targetStatus) {
      setErr(null);
      setSaving(true);
      const { data: { user } } = await sb.auth.getUser();
      const payload = {
        admin_id: user.id,
        client_id: form.client_id || null,
        client_name: form.client_name,
        client_email: form.client_email || null,
        title: form.title,
        notes: form.notes || null,
        currency: form.currency,
        status: targetStatus || form.status,
        discount_pct: Number(form.discount_pct) || 0,
        tax_pct: Number(form.tax_pct) || 0,
        subtotal, total,
        line_items: form.line_items,
        expires_at: form.expires_at || null
      };
      let res;
      if (form.id) {
        res = await sb.from("quotes").update(payload).eq("id", form.id).select().single();
      } else {
        res = await sb.from("quotes").insert(payload).select().single();
      }
      setSaving(false);
      if (res.error) { setErr(res.error.message); return; }
      if (!form.id && res.data?.id) {
        navigate("/dashboard/admin/quotes/" + res.data.id);
      } else if (res.data) {
        setForm(f => ({ ...f, id: res.data.id, status: res.data.status }));
      }
    }

    async function destroy() {
      if (!form.id) return;
      if (!confirm("¿Eliminar esta cotización? No se puede deshacer.")) return;
      const { error } = await sb.from("quotes").delete().eq("id", form.id);
      if (error) { setErr(error.message); return; }
      navigate("/dashboard/admin/quotes");
    }

    async function shareQuote() {
      if (!form.id) return;
      const { data: token, error } = await sb.rpc("enable_quote_share", { p_quote_id: form.id });
      if (error) { alert(error.message); return; }
      const cfg = window.__BWAY_CONFIG__ || {};
      const baseUrl = cfg.SITE_URL || window.location.origin;
      const url = baseUrl + "/#/q/" + token;
      // Try to copy to clipboard
      try {
        await navigator.clipboard.writeText(url);
        alert("Link copiado al portapapeles ✓\n\n" + url + "\n\nSe puede compartir por WhatsApp, email, etc. No requiere login.");
      } catch (e) {
        prompt("Link público (cópialo):", url);
      }
    }

    async function loadTemplates() {
      const { data, error } = await sb.from("quote_templates")
        .select("*").eq("is_archived", false).order("usage_count", { ascending: false });
      if (!error) setTemplates(data || []);
    }
    async function saveAsTemplate() {
      const name = prompt("Nombre de la plantilla (ej. 'Plan FLOW mensual estándar'):");
      if (!name) return;
      const description = prompt("Descripción corta (opcional):") || null;
      const { data: { user } } = await sb.auth.getUser();
      const payload = {
        created_by: user.id, name: name.trim(), description,
        currency: form.currency,
        discount_pct: Number(form.discount_pct) || 0,
        tax_pct: Number(form.tax_pct) || 0,
        notes: form.notes || null,
        line_items: form.line_items
      };
      const { error } = await sb.from("quote_templates").insert(payload);
      if (error) { alert("Error al guardar plantilla: " + error.message); return; }
      alert("Plantilla guardada ✓");
      loadTemplates();
    }
    async function applyTemplate(tpl) {
      if (form.line_items.length > 0) {
        if (!confirm("Esto va a reemplazar los ítems actuales. ¿Continuar?")) return;
      }
      setForm(f => ({
        ...f,
        currency: tpl.currency || f.currency,
        discount_pct: Number(tpl.discount_pct) || 0,
        tax_pct: Number(tpl.tax_pct) || 0,
        notes: tpl.notes || f.notes,
        line_items: tpl.line_items || []
      }));
      setShowTemplates(false);
      sb.rpc("use_quote_template", { p_template_id: tpl.id }).catch(() => null);
    }
    async function archiveTemplate(tplId) {
      if (!confirm("¿Archivar esta plantilla?")) return;
      const { error } = await sb.from("quote_templates").update({ is_archived: true }).eq("id", tplId);
      if (error) { alert(error.message); return; }
      loadTemplates();
    }
    useEffect(() => { if (showTemplates) loadTemplates(); }, [showTemplates]);

    if (loading) return e("div", { className: "dash-card" }, e("div", { className: "dash-empty" }, "Cargando…"));

    return e("div", { className: "qb2-editor" },
      // ── Slate cinematográfico ─────────────────────────
      e("div", { className: "qb2-edit-head" },
        e("div", { className: "qb2-edit-grain", "aria-hidden": "true" }),
        e("div", { className: "qb2-edit-slate" },
          e("div", { className: "qb2-edit-slate-l" },
            e("span", { className: "qb2-edit-rec" }, e("i"), "REC"),
            e("span", { className: "qb2-edit-slate-sep" }, "·"),
            e("span", { className: "qb2-edit-tag" }, form.id ? "COTIZACIÓN · " + form.id.slice(0,8).toUpperCase() : "NUEVA · DRAFT"),
            e("span", { className: "qb2-edit-slate-sep" }, "·"),
            e("span", null, form.currency),
            form.status !== "draft" ? e(React.Fragment, null,
              e("span", { className: "qb2-edit-slate-sep" }, "·"),
              quoteStatusBadge(form.status)
            ) : null
          ),
          e("div", { className: "qb2-edit-slate-r" },
            e("span", null, "ITEMS"),
            e("b", null, String(form.line_items.length).padStart(2,"0"))
          )
        ),
        e("h1", { className: "qb2-edit-title" }, form.title || "Sin título"),
        e("p", { className: "qb2-edit-sub" }, form.id
          ? "Editás una cotización existente. Los cambios se guardan en draft hasta que la envíes."
          : "Combiná servicios del catálogo MUSA. El total se calcula automáticamente con descuento e impuesto."
        ),
        e("div", { className: "qb2-edit-actions" },
          e("button", { className: "qb2-btn qb2-btn-ghost", onClick: () => navigate("/dashboard/admin/quotes") }, "← Volver"),
          e("button", { className: "qb2-btn qb2-btn-ghost", onClick: () => setShowTemplates(true) }, "✦ Plantillas"),
          form.id ? e("button", { className: "qb2-btn qb2-btn-danger", onClick: destroy }, "Eliminar") : null,
          form.id && form.line_items.length > 0 ? e("button", {
            className: "qb2-btn qb2-btn-ghost",
            onClick: () => downloadQuotePdf({
              id: form.id, title: form.title, client_name: form.client_name, client_email: form.client_email,
              currency: form.currency, status: form.status, notes: form.notes, expires_at: form.expires_at,
              discount_pct: form.discount_pct, tax_pct: form.tax_pct,
              subtotal, total, line_items: form.line_items,
              created_at: new Date().toISOString()
            })
          }, "⬇ PDF") : null,
          form.line_items.length > 0 ? e("button", {
            className: "qb2-btn qb2-btn-ghost",
            onClick: saveAsTemplate
          }, "★ Guardar como plantilla") : null,
          form.id && (form.status === "sent" || form.status === "approved") ? e("button", {
            className: "qb2-btn qb2-btn-ghost",
            onClick: shareQuote
          }, "🔗 Compartir link") : null,
          e("button", { className: "qb2-btn qb2-btn-gold", onClick: () => save("draft"), disabled: saving }, saving ? "Guardando…" : "Guardar borrador"),
          form.line_items.length > 0 && form.client_name ? e("button", {
            className: "qb2-btn qb2-btn-accent",
            onClick: () => save("sent"),
            disabled: saving
          }, "Enviar al cliente ", e("span", null, "→")) : null
        )
      ),
      err ? e("div", { className: "dash-err qb3-err" }, err) : null,

      // ── HEADER FORM (floating 3D card) ────────────────
      e("div", { className: "qb3-meta" },
        e("div", { className: "qb3-meta-inner" },
          e("label", { className: "qb3-field qb3-field-wide" },
            e("span", null, "Cliente registrado"),
            e("select", {
              value: form.client_id,
              onChange: (ev) => {
                const id = ev.target.value;
                set("client_id", id);
                if (id) {
                  const c = clients.find(c => c.id === id);
                  if (c) {
                    set("client_name", c.full_name || c.company || "");
                    set("client_email", c.email || "");
                  }
                }
              }
            },
              e("option", { value: "" }, "— Sin vincular —"),
              clients.map(c => e("option", { key: c.id, value: c.id }, (c.full_name || c.company) + (c.email ? " · " + c.email : "")))
            )
          ),
          e("label", { className: "qb3-field" },
            e("span", null, "Nombre"),
            e("input", { value: form.client_name, onChange: ev => set("client_name", ev.target.value), placeholder: "Marca XYZ" })
          ),
          e("label", { className: "qb3-field" },
            e("span", null, "Email"),
            e("input", { type: "email", value: form.client_email, onChange: ev => set("client_email", ev.target.value), placeholder: "cliente@empresa.com" })
          ),
          e("label", { className: "qb3-field qb3-field-wide" },
            e("span", null, "Título de la cotización"),
            e("input", { value: form.title, onChange: ev => set("title", ev.target.value) })
          ),
          e("label", { className: "qb3-field" },
            e("span", null, "Moneda"),
            e("select", { value: form.currency, onChange: ev => set("currency", ev.target.value) },
              e("option", { value: "CRC" }, "CRC · Colones"),
              e("option", { value: "USD" }, "USD · Dólares")
            )
          ),
          e("label", { className: "qb3-field" },
            e("span", null, "Vence"),
            e("input", { type: "date", value: form.expires_at || "", onChange: ev => set("expires_at", ev.target.value) })
          )
        )
      ),

      // ── TWO PANES (3D tilt on hover) ─────────────────
      e("div", { className: "qb3-grid" },
        // ===== CATALOG =====
        e("div", { className: "qb3-catalog" },
          e("div", { className: "qb3-cats-wrap" },
            e("div", { className: "qb3-cats-label" }, "Categorías"),
            e("div", { className: "qb3-cats" },
              catalog.categories.map((c) => {
                const count = catalog.items.filter(i => i.cat === c.id).length;
                return e("button", {
                  key: c.id,
                  className: "qb3-cat" + (activeCat === c.id ? " is-active" : ""),
                  onClick: () => setActiveCat(c.id),
                  title: c.desc
                },
                  e("span", { className: "qb3-cat-label" }, c.label),
                  e("span", { className: "qb3-cat-count" }, count)
                );
              })
            )
          ),
          e("div", { className: "qb3-items" },
            (() => {
              const cat = catalog.categories.find(c => c.id === activeCat);
              const items = catalog.items.filter(i => i.cat === activeCat);
              return e(React.Fragment, null,
                e("div", { className: "qb3-cat-head" },
                  e("h4", null, cat.label),
                  e("p", null, cat.desc)
                ),
                items.map((it, i) => {
                  const price = form.currency === "USD" ? it.usd : it.crc;
                  const disabled = price == null;
                  return e("div", {
                    key: it.id,
                    className: "qb3-item" + (disabled ? " is-disabled" : ""),
                    style: { "--enter-i": i }
                  },
                    e("div", { className: "qb3-item-info" },
                      e("div", { className: "qb3-item-name" }, it.name, it.tag ? e("span", { className: "qb3-tag" }, it.tag) : null),
                      e("div", { className: "qb3-item-price" }, disabled ? "No disponible · " + form.currency : fmtMoney(price, form.currency))
                    ),
                    e("button", {
                      className: "qb3-add",
                      disabled,
                      onClick: () => addItem(it),
                      "aria-label": "Agregar " + it.name
                    },
                      e("span", { className: "qb3-add-plus" }, "+"),
                      e("span", { className: "qb3-add-label" }, "Agregar")
                    )
                  );
                })
              );
            })()
          )
        ),

        // ===== CART (3D invoice slab) =====
        e("div", { className: "qb3-cart" },
          // Cart head with line count chip
          e("div", { className: "qb3-cart-head" },
            e("div", null,
              e("div", { className: "qb3-cart-kicker" }, "Cotización en construcción"),
              e("h4", null, "Resumen")
            ),
            e("div", { className: "qb3-count-pill" },
              e("b", null, String(form.line_items.length).padStart(2,"0")),
              e("span", null, form.line_items.length === 1 ? "ítem" : "ítems")
            )
          ),

          // Category distribution chart (animated bars by category)
          form.line_items.length > 0 ? e("div", { className: "qb3-chart" },
            (() => {
              const dist = {};
              form.line_items.forEach(li => {
                const sub = li.qty * li.unit_price;
                dist[li.cat] = (dist[li.cat] || 0) + sub;
              });
              const arr = Object.entries(dist).sort((a,b) => b[1]-a[1]);
              const max = Math.max(...arr.map(x => x[1]), 1);
              return arr.map(([cat, val], i) => {
                const meta = catalog.categories.find(c => c.id === cat);
                const pct = (val / max * 100) | 0;
                return e("div", { key: cat, className: "qb3-bar", style: { "--bar-i": i } },
                  e("div", { className: "qb3-bar-label" },
                    e("span", null, meta?.label?.split(" · ")[0] || cat),
                    e("b", null, fmtMoney(val, form.currency))
                  ),
                  e("div", { className: "qb3-bar-track" },
                    e("div", { className: "qb3-bar-fill", style: { width: pct + "%" } })
                  )
                );
              });
            })()
          ) : null,

          // Line items list
          form.line_items.length === 0 ?
            e("div", { className: "qb3-cart-empty" },
              e("div", { className: "qb3-cart-empty-mark" }, "✦"),
              e("div", null, "Aún no agregaste servicios."),
              e("p", null, "Elegí del catálogo a la izquierda y los verás aparecer acá.")
            ) :
            e("div", { className: "qb3-cart-items" },
              form.line_items.map((li, i) =>
                e("div", { key: li.id, className: "qb3-li", style: { "--li-i": i } },
                  e("div", { className: "qb3-li-top" },
                    e("span", { className: "qb3-li-name" }, li.name),
                    e("button", { className: "qb3-li-x", onClick: () => removeItem(li.id), title: "Quitar" }, "×")
                  ),
                  e("div", { className: "qb3-li-row" },
                    e("div", { className: "qb3-li-qty-wrap" },
                      e("button", { className: "qb3-stepper", onClick: () => updateQty(li.id, Math.max(0, li.qty - 1)) }, "−"),
                      e("input", {
                        type: "number", min: 0,
                        className: "qb3-li-qty",
                        value: li.qty,
                        onChange: ev => updateQty(li.id, ev.target.value)
                      }),
                      e("button", { className: "qb3-stepper", onClick: () => updateQty(li.id, li.qty + 1) }, "+")
                    ),
                    e("span", { className: "qb3-li-mul" }, "×"),
                    e("input", {
                      type: "number", min: 0,
                      className: "qb3-li-price",
                      value: li.unit_price,
                      onChange: ev => updatePrice(li.id, ev.target.value)
                    }),
                    e("span", { className: "qb3-li-sub" }, fmtMoney(li.qty * li.unit_price, form.currency))
                  )
                )
              )
            ),

          // Totals — animated count-up
          e("div", { className: "qb3-totals" },
            e("div", { className: "qb3-tot-row" },
              e("span", null, "Subtotal"),
              e(CountUpMoney, { value: subtotal, currency: form.currency })
            ),
            e("div", { className: "qb3-tot-row qb3-tot-input" },
              e("span", null, "Descuento"),
              e("div", { className: "qb3-pct-wrap" },
                e("input", { type: "number", min: 0, max: 100, step: "0.01", value: form.discount_pct, onChange: ev => set("discount_pct", ev.target.value) }),
                e("span", { className: "qb3-pct-sign" }, "%")
              ),
              e("b", { className: "qb3-neg" }, "− ", e(CountUpMoney, { value: discount, currency: form.currency }))
            ),
            e("div", { className: "qb3-tot-row qb3-tot-input" },
              e("span", null, "Impuesto"),
              e("div", { className: "qb3-pct-wrap" },
                e("input", { type: "number", min: 0, max: 100, step: "0.01", value: form.tax_pct, onChange: ev => set("tax_pct", ev.target.value) }),
                e("span", { className: "qb3-pct-sign" }, "%")
              ),
              e("b", { className: "qb3-pos" }, "+ ", e(CountUpMoney, { value: tax, currency: form.currency }))
            ),
            e("div", { className: "qb3-tot-final" },
              e("div", { className: "qb3-tot-final-label" }, "TOTAL"),
              e("div", { className: "qb3-tot-final-amount" },
                e(CountUpMoney, { value: total, currency: form.currency, big: true })
              )
            )
          ),

          e("label", { className: "qb3-field qb3-notes" },
            e("span", null, "Notas para el cliente"),
            e("textarea", { rows: 3, value: form.notes, onChange: ev => set("notes", ev.target.value), placeholder: "Términos, alcance, condiciones de pago…" })
          )
        )
      ),
      showTemplates ? e("div", {
        className: "qb-tpl-backdrop",
        onClick: ev => { if (ev.target === ev.currentTarget) setShowTemplates(false); }
      },
        e("div", { className: "qb-tpl-modal" },
          e("button", { className: "qb-tpl-close", onClick: () => setShowTemplates(false), "aria-label": "Cerrar" }, "×"),
          e("div", { className: "qb-tpl-head" },
            e("span", { className: "qb-tpl-kicker" }, "/ PLANTILLAS GUARDADAS"),
            e("h3", null, "Aplicá un paquete pre-armado"),
            e("p", null, "Las plantillas reemplazan los ítems actuales por un set predefinido. Los datos del cliente se mantienen.")
          ),
          templates.length === 0 ?
            e("div", { className: "qb-tpl-empty" },
              "Aún no hay plantillas. Armá una cotización completa y guardala como plantilla para reusarla."
            ) :
            e("div", { className: "qb-tpl-list" },
              templates.map(tpl =>
                e("div", { key: tpl.id, className: "qb-tpl-item" },
                  e("div", { className: "qb-tpl-info" },
                    e("div", { className: "qb-tpl-name" }, tpl.name),
                    tpl.description ? e("div", { className: "qb-tpl-desc" }, tpl.description) : null,
                    e("div", { className: "qb-tpl-meta" },
                      e("span", null, (tpl.line_items?.length || 0) + " ítems"),
                      e("span", null, "·"),
                      e("span", null, tpl.currency),
                      e("span", null, "·"),
                      e("span", null, "Usada " + tpl.usage_count + (tpl.usage_count === 1 ? " vez" : " veces"))
                    )
                  ),
                  e("div", { className: "qb-tpl-actions" },
                    e("button", { className: "qb-tpl-apply", onClick: () => applyTemplate(tpl) }, "Aplicar"),
                    e("button", { className: "qb-tpl-arch", onClick: () => archiveTemplate(tpl.id), title: "Archivar" }, "🗄")
                  )
                )
              )
            )
        )
      ) : null
    );
  }

  // Count-up animated money display
  function CountUpMoney({ value, currency, big }) {
    const [shown, setShown] = useState(value || 0);
    const fromRef = useRef(0);
    const targetRef = useRef(value || 0);
    useEffect(() => {
      fromRef.current = shown;
      targetRef.current = value || 0;
      const dur = 380;
      const start = performance.now();
      let raf;
      const tick = (t) => {
        const p = Math.min(1, (t - start) / dur);
        const eased = 1 - Math.pow(1 - p, 3);
        const v = fromRef.current + (targetRef.current - fromRef.current) * eased;
        setShown(v);
        if (p < 1) raf = requestAnimationFrame(tick);
      };
      raf = requestAnimationFrame(tick);
      return () => raf && cancelAnimationFrame(raf);
    }, [value]);
    return e("b", {
      className: "qb3-money" + (big ? " is-big" : ""),
      key: currency
    }, fmtMoney(shown, currency));
  }

  // ==========================================================
  // CLIENT REQUESTS — Bandeja de solicitudes desde el landing CTA
  // ==========================================================
  function requestStatusBadge(s) {
    const map = {
      pending:   { label: "PENDIENTE",  cls: "qb-st-sent" },
      contacted: { label: "CONTACTADO", cls: "qb-st-draft" },
      approved:  { label: "APROBADO",   cls: "qb-st-ok" },
      rejected:  { label: "RECHAZADO",  cls: "qb-st-no" }
    };
    const m = map[s] || { label: s, cls: "qb-st-draft" };
    return e("span", { className: "qb-status " + m.cls }, m.label);
  }

  function AdminStats() {
    const [data, setData] = useState(null);
    const [err,  setErr]  = useState(null);
    useEffect(() => {
      Promise.all([
        sb.rpc("list_quotes"),
        sb.rpc("list_client_requests")
      ]).then(([qres, rres]) => {
        if (qres.error || rres.error) {
          setErr((qres.error || rres.error).message);
          return;
        }
        setData({ quotes: qres.data || [], requests: rres.data || [] });
      });
    }, []);

    if (err) return e("div", { className: "dash-card" }, e("div", { className: "dash-err" }, err));
    if (!data) return e("div", { className: "dash-card" }, e("div", { className: "dash-empty" }, "Cargando métricas…"));

    const qs = data.quotes, rs = data.requests;
    const total       = qs.length;
    const sent        = qs.filter(q => q.status === "sent").length;
    const approved    = qs.filter(q => q.status === "approved").length;
    const rejected    = qs.filter(q => q.status === "rejected").length;
    const draft       = qs.filter(q => q.status === "draft").length;
    const totalApr    = qs.filter(q => q.status === "approved").reduce((s, q) => s + Number(q.total || 0), 0);
    const totalSent   = qs.filter(q => q.status === "sent").reduce((s, q) => s + Number(q.total || 0), 0);
    const avgTicket   = approved > 0 ? totalApr / approved : 0;
    const conversion  = (approved + rejected) > 0 ? Math.round(approved / (approved + rejected) * 100) : 0;
    const reqConv     = rs.length > 0 ? Math.round(rs.filter(r => r.status === "approved").length / rs.length * 100) : 0;

    // Items by category
    const byCat = {};
    qs.forEach(q => {
      (q.line_items || []).forEach(li => {
        byCat[li.cat] = (byCat[li.cat] || 0) + (li.qty * li.unit_price);
      });
    });
    const topCats = Object.entries(byCat).sort((a,b) => b[1] - a[1]).slice(0, 6);
    const maxCat  = Math.max(...topCats.map(x => x[1]), 1);

    // Monthly evolution (last 6 months)
    const months = [];
    const now = new Date();
    for (let i = 5; i >= 0; i--) {
      const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
      const key = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
      months.push({ key, label: d.toLocaleDateString("es-CR", { month: "short" }), value: 0 });
    }
    qs.forEach(q => {
      if (q.status !== "approved") return;
      const d = new Date(q.updated_at || q.created_at);
      const key = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
      const m = months.find(x => x.key === key);
      if (m) m.value += Number(q.total || 0);
    });
    const maxMonth = Math.max(...months.map(m => m.value), 1);

    const catalog = window.BWAY_QUOTE_CATALOG;

    return e("div", { className: "qb2-screen" },
      e("section", { className: "qb2-hero" },
        e("div", { className: "qb2-hero-grain", "aria-hidden": "true" }),
        e("div", { className: "qb2-hero-beam", "aria-hidden": "true" }),
        e("div", { className: "qb2-slate" },
          e("span", { className: "qb2-slate-rec" }, e("i"), "REC"),
          e("span", { className: "qb2-slate-sep" }, "·"),
          e("span", null, "BWAY ANALYTICS"),
          e("span", { className: "qb2-slate-sep" }, "·"),
          e("span", null, "MÉTRICAS")
        ),
        e("h1", { className: "qb2-hero-title" }, "El ", e("em", null, "estudio"), " en números."),
        e("p", { className: "qb2-hero-sub" }, "Pulso del negocio en tiempo real. Cotizaciones, conversión, ticket promedio y distribución por servicio."),
        e("div", { className: "qb2-stats" },
          e("div", { className: "qb2-stat qb2-stat-accent" },
            e("span", { className: "qb2-stat-kicker" }, "Aprobado"),
            e("b", null, fmtMoney(totalApr, "CRC")),
            e("span", { className: "qb2-stat-hint" }, approved + " cotizaciones cerradas")
          ),
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "En vuelo"),
            e("b", null, fmtMoney(totalSent, "CRC")),
            e("span", { className: "qb2-stat-hint" }, sent + " enviadas esperando respuesta")
          ),
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "Ticket promedio"),
            e("b", null, fmtMoney(avgTicket, "CRC")),
            e("span", { className: "qb2-stat-hint" }, "sobre cotizaciones aprobadas")
          ),
          e("div", { className: "qb2-stat" },
            e("span", { className: "qb2-stat-kicker" }, "Conversión"),
            e("b", null, conversion + "%"),
            e("span", { className: "qb2-stat-hint" }, "aprobadas / respondidas")
          )
        )
      ),

      e("div", { className: "stats-grid" },
        e("section", { className: "stats-card" },
          e("header", { className: "stats-card-head" },
            e("span", { className: "stats-kicker" }, "/ FUNNEL DE COTIZACIONES"),
            e("h3", null, "Estado actual")
          ),
          e("div", { className: "stats-funnel" },
            [
              { label: "Borradores", value: draft, color: "#888" },
              { label: "Enviadas",   value: sent, color: "#f4d68a" },
              { label: "Aprobadas",  value: approved, color: "#7ed99a" },
              { label: "Rechazadas", value: rejected, color: "#ff8a8a" }
            ].map(stage =>
              e("div", { key: stage.label, className: "stats-funnel-row" },
                e("div", { className: "stats-funnel-label" }, stage.label),
                e("div", { className: "stats-funnel-track" },
                  e("div", {
                    className: "stats-funnel-fill",
                    style: {
                      width: (total > 0 ? (stage.value / total * 100) : 0) + "%",
                      background: stage.color
                    }
                  })
                ),
                e("b", null, stage.value)
              )
            )
          )
        ),

        e("section", { className: "stats-card" },
          e("header", { className: "stats-card-head" },
            e("span", { className: "stats-kicker" }, "/ CTA DEL LANDING"),
            e("h3", null, "Solicitudes")
          ),
          e("div", { className: "stats-funnel" },
            [
              { label: "Pendientes",  value: rs.filter(r => r.status === "pending").length,   color: "#f4d68a" },
              { label: "Contactadas", value: rs.filter(r => r.status === "contacted").length, color: "#d6b25d" },
              { label: "Aprobadas",   value: rs.filter(r => r.status === "approved").length,  color: "#7ed99a" },
              { label: "Rechazadas",  value: rs.filter(r => r.status === "rejected").length,  color: "#ff8a8a" }
            ].map(s =>
              e("div", { key: s.label, className: "stats-funnel-row" },
                e("div", { className: "stats-funnel-label" }, s.label),
                e("div", { className: "stats-funnel-track" },
                  e("div", { className: "stats-funnel-fill", style: { width: (rs.length > 0 ? (s.value / rs.length * 100) : 0) + "%", background: s.color } })
                ),
                e("b", null, s.value)
              )
            ),
            e("div", { className: "stats-extra" }, "Total recibidas: ", e("b", null, rs.length), " · Conversión a aprobado: ", e("b", null, reqConv + "%"))
          )
        )
      ),

      e("section", { className: "stats-card" },
        e("header", { className: "stats-card-head" },
          e("span", { className: "stats-kicker" }, "/ EVOLUCIÓN MENSUAL"),
          e("h3", null, "Ingresos aprobados — últimos 6 meses")
        ),
        e("div", { className: "stats-bars" },
          months.map(m =>
            e("div", { key: m.key, className: "stats-bar-col" },
              e("div", { className: "stats-bar-val" }, fmtMoney(m.value, "CRC")),
              e("div", { className: "stats-bar-track" },
                e("div", { className: "stats-bar-fill", style: { height: (m.value / maxMonth * 100) + "%" } })
              ),
              e("div", { className: "stats-bar-label" }, m.label)
            )
          )
        )
      ),

      e("section", { className: "stats-card" },
        e("header", { className: "stats-card-head" },
          e("span", { className: "stats-kicker" }, "/ TOP CATEGORÍAS"),
          e("h3", null, "Por monto cotizado")
        ),
        topCats.length === 0 ?
          e("div", { className: "dash-empty" }, "Aún sin datos — armá cotizaciones para ver el desglose por servicio.") :
          e("div", { className: "stats-cats" },
            topCats.map(([cat, val]) => {
              const meta = catalog?.categories?.find(c => c.id === cat);
              const label = meta?.label || cat;
              const pct = (val / maxCat * 100) | 0;
              return e("div", { key: cat, className: "stats-cat" },
                e("div", { className: "stats-cat-label" },
                  e("span", null, label),
                  e("b", null, fmtMoney(val, "CRC"))
                ),
                e("div", { className: "stats-cat-track" },
                  e("div", { className: "stats-cat-fill", style: { width: pct + "%" } })
                )
              );
            })
          )
      )
    );
  }

  function AdminRequests() {
    const [rows, setRows]       = useState([]);
    const [loading, setLoading] = useState(true);
    const [err, setErr]         = useState(null);
    const [filter, setFilter]   = useState("pending");
    const [busyId, setBusyId]   = useState(null);

    function load() {
      setLoading(true);
      sb.rpc("list_client_requests").then(({ data, error }) => {
        if (error) setErr(error.message);
        else setRows(data || []);
        setLoading(false);
      });
    }
    useEffect(load, []);

    const shown = filter === "all" ? rows : rows.filter(r => r.status === filter);
    const pendingCount = rows.filter(r => r.status === "pending").length;

    async function approveAndInvite(req) {
      const note = prompt("Notas internas para esta solicitud (opcional):") || "";
      setBusyId(req.id);
      try {
        // 1) Crear la invitación en la tabla invitations vía RPC existente
        const inv = await sb.rpc("bway_invite_user", {
          p_email: req.email,
          p_role: "cliente",
          p_full_name: req.full_name,
          p_company: req.company || null
        });
        if (inv.error) throw inv.error;

        // 2) Enviar el magic link con la Edge Function ya configurada
        const session = await sb.auth.getSession();
        const accessToken = session.data?.session?.access_token;
        const res = await fetch(cfg.SUPABASE_URL + "/functions/v1/admin-invite-client", {
          method: "POST",
          headers: {
            "Authorization": "Bearer " + accessToken,
            "apikey":        cfg.SUPABASE_ANON,
            "content-type":  "application/json"
          },
          body: JSON.stringify({ invitation_id: inv.data.id })
        });
        const body = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(body.error || "send_failed");

        // 3) Marcar la solicitud como aprobada (el user_id real se crea cuando
        //    el cliente acepta el magic link y define su clave).
        const upd = await sb.rpc("respond_client_request", {
          p_id: req.id,
          p_status: "approved",
          p_notes: note,
          p_invited_user_id: null
        });
        if (upd.error) throw upd.error;
        load();
      } catch (e) {
        alert("Error: " + (e.message || "no se pudo aprobar"));
      } finally {
        setBusyId(null);
      }
    }

    async function reject(req) {
      const note = prompt("Motivo del rechazo (opcional, queda interno):") || "";
      setBusyId(req.id);
      const { error } = await sb.rpc("respond_client_request", {
        p_id: req.id, p_status: "rejected", p_notes: note, p_invited_user_id: null
      });
      setBusyId(null);
      if (error) { alert(error.message); return; }
      load();
    }

    async function markContacted(req) {
      setBusyId(req.id);
      const { error } = await sb.rpc("respond_client_request", {
        p_id: req.id, p_status: "contacted", p_notes: null, p_invited_user_id: null
      });
      setBusyId(null);
      if (error) { alert(error.message); return; }
      load();
    }

    return e("div", { className: "dash-card" },
      e(DashHeader, {
        kicker: "/ SOLICITUDES " + (pendingCount > 0 ? "· " + pendingCount + " PENDIENTES" : ""),
        title: "Bandeja del CTA del landing",
        sub: "Cada vez que alguien llena el formulario 'Quiero mi cotización personalizada' aparece acá. Aprobá para enviarles automáticamente un magic link de acceso al dashboard.",
        actions: e(Btn, { variant: "ghost", onClick: load }, "Recargar")
      }),
      e("div", { className: "qb-list-filters" },
        ["pending","contacted","approved","rejected","all"].map(f =>
          e("button", {
            key: f,
            className: "qb-filter" + (filter === f ? " is-active" : ""),
            onClick: () => setFilter(f)
          }, f === "all" ? "Todas" : f.charAt(0).toUpperCase() + f.slice(1))
        )
      ),
      err ? e("div", { className: "dash-err" }, err) : null,
      loading ? e("div", { className: "dash-empty" }, "Cargando…") :
      shown.length === 0 ? e("div", { className: "dash-empty" },
        filter === "pending" ? "No hay solicitudes pendientes. Cuando alguien se registre desde el landing, aparecerá acá." : "Sin resultados en este filtro."
      ) :
      shown.map(req => e("div", { key: req.id, className: "qb-client-card" },
        e("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16, marginBottom: 14 } },
          e("div", null,
            e("div", { className: "qb-muted", style: { marginBottom: 4 } },
              new Date(req.created_at).toLocaleString("es-CR", { dateStyle: "medium", timeStyle: "short" }),
              " · ", req.source
            ),
            e("h4", { style: { margin: "0 0 6px", font: '500 18px/1.2 "Fraunces", serif', color: "#f7efdf" } },
              req.full_name, req.company ? " · " + req.company : ""
            ),
            e("div", { style: { display: "flex", gap: 14, flexWrap: "wrap", marginBottom: 6, font: '400 13px/1.4 "Inter", sans-serif', color: "rgba(232,224,205,0.75)" } },
              e("span", null, "📧 ", req.email),
              req.phone ? e("span", null, "📱 ", req.phone) : null,
              req.budget_hint ? e("span", { className: "qb-tag" }, "Presupuesto: " + req.budget_hint) : null
            ),
            requestStatusBadge(req.status)
          )
        ),
        req.project_brief ? e("div", { style: { padding: 12, background: "rgba(8,6,4,0.4)", borderRadius: 10, font: '300 13.5px/1.55 "Inter", sans-serif', color: "rgba(232,224,205,0.78)", marginBottom: 12 } }, req.project_brief) : null,
        req.admin_notes ? e("div", { className: "qb-muted", style: { marginBottom: 10, fontStyle: "italic" } }, "Notas internas: " + req.admin_notes) : null,
        req.status === "pending" || req.status === "contacted" ? e("div", { className: "qb-client-actions" },
          e("button", {
            className: "qb-approve",
            disabled: busyId === req.id,
            onClick: () => approveAndInvite(req)
          }, busyId === req.id ? "Procesando…" : "✓ Aprobar e invitar"),
          req.status === "pending" ? e("button", {
            className: "qb-reject",
            style: { borderColor: "rgba(244,214,138,0.3)", color: "#d6b25d" },
            disabled: busyId === req.id,
            onClick: () => markContacted(req)
          }, "✆ Marcar como contactado") : null,
          e("button", {
            className: "qb-reject",
            disabled: busyId === req.id,
            onClick: () => reject(req)
          }, "✕ Rechazar")
        ) : e("div", { className: "qb-muted" },
          req.reviewed_at ? "Revisado " + new Date(req.reviewed_at).toLocaleString("es-CR", { dateStyle: "medium", timeStyle: "short" }) : ""
        )
      ))
    );
  }

  function AdminInvitations() {
    const [email, setEmail]       = useState("");
    const [role,  setRole]        = useState("cliente");
    const [name,  setName]        = useState("");
    const [company, setCompany]   = useState("");
    const [busy,  setBusy]        = useState(false);
    const [err,   setErr]         = useState(null);
    const [msg,   setMsg]         = useState(null);
    const [rows,  setRows]        = useState(null);

    const load = () => sb
      .from("invitations")
      .select("id, email, role, full_name, company, status, sent_at, expires_at, created_at")
      .order("created_at", { ascending: false })
      .then(({ data }) => setRows(data || []));
    useEffect(() => { load(); }, []);

    const submit = async (ev) => {
      ev.preventDefault();
      setErr(null); setMsg(null);
      if (!email) return setErr("El correo es obligatorio.");
      setBusy(true);
      try {
        const inv = await sb.rpc("bway_invite_user", {
          p_email: email, p_role: role,
          p_full_name: name || null, p_company: company || null
        });
        if (inv.error) throw inv.error;
        const invitation = inv.data;
        // Call edge function to send magic link
        const session = await sb.auth.getSession();
        const accessToken = session.data?.session?.access_token;
        const res = await fetch(cfg.SUPABASE_URL + "/functions/v1/admin-invite-client", {
          method: "POST",
          headers: {
            "Authorization": "Bearer " + accessToken,
            "apikey":        cfg.SUPABASE_ANON,
            "content-type":  "application/json"
          },
          body: JSON.stringify({ invitation_id: invitation.id })
        });
        const body = await res.json().catch(() => ({}));
        if (!res.ok) throw new Error(body.error || "send_failed");
        setMsg("Invitación enviada a " + email);
        setEmail(""); setName(""); setCompany("");
        load();
      } catch (x) {
        setErr(x.message || "No se pudo invitar");
      } finally {
        setBusy(false);
      }
    };

    return e("div", { className: "dash-page" },
      e(DashHeader, { kicker: "/ INVITACIONES", title: "Invitar al estudio", sub: "Le llega un magic link al correo. Al primer login fija su contraseña y se activa." }),
      e("section", { className: "dash-card" },
        e("form", { onSubmit: submit, className: "dash-form dash-form-grid" },
          err ? e(Banner, { kind: "error" }, err) : null,
          msg ? e(Banner, { kind: "info" },  msg) : null,
          e(Field, { label: "Correo" }, e("input", { type: "email", className: "dash-input", value: email, onChange: (ev) => setEmail(ev.target.value), required: true })),
          e(Field, { label: "Rol" },
            e("select", { className: "dash-input", value: role, onChange: (ev) => setRole(ev.target.value) },
              e("option", { value: "cliente" }, "Cliente"),
              e("option", { value: "usuario" }, "Colaborador"),
              e("option", { value: "admin"   }, "Admin")
            )),
          e(Field, { label: "Nombre completo (opcional)" }, e("input", { className: "dash-input", value: name, onChange: (ev) => setName(ev.target.value) })),
          e(Field, { label: "Empresa (opcional)" }, e("input", { className: "dash-input", value: company, onChange: (ev) => setCompany(ev.target.value) })),
          e(Btn, { type: "submit", loading: busy, className: "dash-form-submit" }, "Enviar invitación")
        )
      ),
      e("section", { className: "dash-card" },
        e("header", { className: "dash-card-head" }, e("h2", null, "Pendientes recientes")),
        rows === null ? e(Spinner) :
          rows.length === 0
            ? e(EmptyState, { title: "Sin invitaciones aún", body: "Cuando envíes una aparecerá aquí." })
            : e("table", { className: "dash-table" },
                e("thead", null, e("tr", null, e("th", null, "Correo"), e("th", null, "Rol"), e("th", null, "Estado"), e("th", null, "Expira"))),
                e("tbody", null,
                  rows.map((r) =>
                    e("tr", { key: r.id },
                      e("td", null, r.email),
                      e("td", null, e("span", { className: "phase-pill is-" + r.role }, r.role)),
                      e("td", null, e("span", { className: "phase-pill is-" + r.status }, r.status)),
                      e("td", null, new Date(r.expires_at).toLocaleDateString())
                    )
                  )
                )
              )
      )
    );
  }

  // -----------------------------------------------------------
  // 8. CLIENTE DASHBOARD + PhaseTracker
  // -----------------------------------------------------------
  function ClienteDashboard() {
    const route = useRoute();
    const nav = [
      { to: "/dashboard/cliente",          label: "Mis proyectos", icon: "▤", match: /^\/dashboard\/cliente/ }
    ];
    let view = e(ClienteHome);
    if (/^\/dashboard\/cliente\/proyecto\/(\d+)$/.test(route.path)) {
      const id = parseInt(route.path.split("/").pop(), 10);
      view = e(ProjectDetail, { projectId: id, role: "cliente" });
    }
    return e(DashboardShell, { role: "cliente", nav }, view);
  }

  // Client panel: shows quotes addressed to the current user
  function ClientQuotesPanel() {
    const [rows, setRows] = useState([]);
    const [loading, setLoading] = useState(true);
    const [busyId, setBusyId] = useState(null);
    const [err, setErr] = useState(null);

    function reload() {
      setLoading(true);
      sb.rpc("list_quotes").then(({ data, error }) => {
        if (error) setErr(error.message);
        else setRows(data || []);
        setLoading(false);
      });
    }
    useEffect(reload, []);

    async function respond(quoteId, status) {
      const comment = status === "rejected"
        ? prompt("Motivo del rechazo (opcional):") || ""
        : prompt("Comentario al aprobar (opcional):") || "";
      setBusyId(quoteId);
      const { error } = await sb.rpc("respond_quote", { p_quote_id: quoteId, p_status: status, p_comment: comment });
      if (error) { setBusyId(null); alert(error.message); return; }

      // Fire-and-forget: avisar al admin por email. No bloquea la UX.
      try {
        const cfg = window.__BWAY_CONFIG__ || {};
        if (cfg.SUPABASE_URL && cfg.SUPABASE_ANON) {
          fetch(cfg.SUPABASE_URL + "/functions/v1/notify-quote-response", {
            method: "POST",
            headers: {
              "apikey":        cfg.SUPABASE_ANON,
              "Authorization": "Bearer " + cfg.SUPABASE_ANON,
              "content-type":  "application/json"
            },
            body: JSON.stringify({ quote_id: quoteId, action: status })
          }).catch(err => console.warn("notify-quote-response failed (non-blocking)", err));
        }
      } catch (e) { /* ignore */ }

      setBusyId(null);
      reload();
    }

    if (loading) return null;
    if (rows.length === 0) return null;

    return e("div", { className: "dash-card", style: { marginTop: 24 } },
      e(DashHeader, {
        kicker: "/ COTIZACIONES",
        title: "Propuestas para tu marca",
        sub: "Revisá las cotizaciones que te envió el equipo BWAY."
      }),
      err ? e("div", { className: "dash-err" }, err) : null,
      rows.map(q => e("div", { key: q.id, className: "qb-client-card" },
        e("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 16, marginBottom: 12 } },
          e("div", null,
            e("div", { className: "qb-muted", style: { marginBottom: 4 } }, new Date(q.updated_at).toLocaleDateString("es-CR")),
            e("h4", { style: { margin: "0 0 6px", font: '500 20px/1.2 "Fraunces", serif', color: "#f7efdf" } }, q.title),
            quoteStatusBadge(q.status)
          ),
          e("div", { style: { textAlign: "right" } },
            e("div", { className: "qb-muted", style: { marginBottom: 4 } }, "Total"),
            e("div", { style: { font: '600 28px/1 "Fraunces", serif', color: "#f4d68a" } }, fmtMoney(q.total, q.currency))
          )
        ),
        e("div", { className: "qb-cart-items", style: { maxHeight: "none", marginTop: 6 } },
          (q.line_items || []).map((li, i) => e("div", { key: i, className: "qb-li" },
            e("div", { className: "qb-li-top" },
              e("span", { className: "qb-li-name" }, li.name),
              e("span", { className: "qb-li-sub" }, fmtMoney(li.qty * li.unit_price, q.currency))
            ),
            e("div", { className: "qb-muted", style: { fontSize: 11 } }, li.qty + " × " + fmtMoney(li.unit_price, q.currency))
          ))
        ),
        q.notes ? e("div", { style: { marginTop: 16, padding: 14, background: "rgba(8,6,4,0.4)", borderRadius: 10, font: '300 13.5px/1.55 "Inter", sans-serif', color: "rgba(232,224,205,0.75)" } }, q.notes) : null,
        e("div", { className: "qb-client-actions" },
          q.status === "sent" ? e(React.Fragment, null,
            e("button", { className: "qb-approve", disabled: busyId === q.id, onClick: () => respond(q.id, "approved") }, busyId === q.id ? "Procesando…" : "✓ Aprobar"),
            e("button", { className: "qb-reject",  disabled: busyId === q.id, onClick: () => respond(q.id, "rejected") }, "✕ Rechazar")
          ) : null,
          e("button", {
            className: "qb-reject",
            style: { borderColor: "rgba(244,214,138,0.3)", color: "#f4d68a" },
            onClick: () => downloadQuotePdf(q)
          }, "⬇ Descargar PDF")
        ),
        q.client_response ? e("div", { className: "qb-muted", style: { marginTop: 12, fontStyle: "italic" } }, "Tu respuesta: " + q.client_response) : null
      ))
    );
  }

  function ClienteHome() {
    const [data, setData] = useState(null);
    const [err, setErr] = useState(null);
    useEffect(() => {
      sb.rpc("bway_get_my_dashboard").then(({ data, error }) => {
        if (error) setErr(error.message); else setData(data);
      });
    }, []);
    if (err)   return e("div", { className: "dash-page" }, e(Banner, { kind: "error" }, err));
    if (!data) return e("div", { className: "dash-page" }, e(Spinner));
    const projects = data.projects || [];
    const clientName = data.client?.company_name || "Tu marca";

    // KPIs computed from projects payload
    const totalProjects = projects.length;
    const avgProgress = totalProjects ? Math.round(projects.reduce((a, p) => a + (p.progress_pct || 0), 0) / totalProjects) : 0;
    const closestDelivery = projects
      .filter(p => p.delivery_date)
      .sort((a, b) => new Date(a.delivery_date) - new Date(b.delivery_date))[0];
    const daysToClosest = closestDelivery ? Math.ceil((new Date(closestDelivery.delivery_date) - new Date()) / 86400000) : null;
    const inProgressCount = projects.filter(p => (p.progress_pct || 0) > 0 && (p.progress_pct || 0) < 100).length;

    return e("div", { className: "dash-page" },
      e(DashHeader, {
        kicker: "/ DASHBOARD · CLIENTE",
        title:  clientName,
        sub:    "Estado en vivo de tus producciones cinematográficas."
      }),

      // KPI hero row
      projects.length > 0 ? e("div", { className: "kpi-grid client-kpi-grid" },
        e(KpiCard, { label: "Proyectos activos", value: totalProjects, accent: true }),
        e(KpiCard, { label: "Progreso promedio", value: avgProgress + "%", hint: "media ponderada" }),
        e(KpiCard, { label: "En producción", value: inProgressCount, hint: "etapas en curso" }),
        e(KpiCard, {
          label: daysToClosest != null && daysToClosest < 0 ? "Entrega vencida" : "Próxima entrega",
          value: daysToClosest != null ? Math.abs(daysToClosest) + "d" : "—",
          hint: closestDelivery?.title?.slice(0, 28) || ""
        })
      ) : null,

      // Bitácora highlight — most recent update from any project
      projects.length > 0 ? e("section", { className: "dash-card client-pulse" },
        e("header", { className: "dash-card-head" },
          e("h2", null, "Pulso del estudio"),
          e("span", { className: "client-pulse-tag" },
            e("i", { className: "client-pulse-dot" }), "EN VIVO"
          )
        ),
        e("div", { className: "client-pulse-body" },
          e("span", { className: "client-pulse-kicker" }, "Última actualización del equipo BWAY"),
          e("p", { className: "client-pulse-text" },
            "Tu producción avanza al ritmo del oficio. Cada etapa que ves marcada es una pieza del ritual MUSA que ya está hecha."
          ),
          e("div", { className: "client-pulse-meta" },
            e("span", null, "📍 San José · CR"),
            e("span", null, "⏱ Tiempo real"),
            e("span", null, "🎬 BWAY PROD")
          )
        )
      ) : null,

      // Section header for project list
      projects.length > 0 ? e("div", { className: "client-section-head" },
        e("h2", null, "Tus producciones"),
        e("span", { className: "kpi-label" }, "Click para ver el detalle por etapa")
      ) : null,

      projects.length === 0
        ? e(EmptyState, {
            title: "Aún no tienes proyectos activos.",
            body:  "Cuando tu director de proyecto en BWAY arranque tu producción, aparecerá aquí con el detalle de cada etapa."
          })
        : e("div", { className: "client-grid" },
            projects.map((p) => {
              const days = p.delivery_date ? Math.ceil((new Date(p.delivery_date) - new Date()) / 86400000) : null;
              const overdue = days != null && days < 0;
              return e("article", { key: p.id, className: "client-card client-card-rich",
                              onClick: () => navigate("/dashboard/cliente/proyecto/" + p.id),
                              role: "button", tabIndex: 0 },
                e("div", { className: "client-card-head" },
                  e("span", { className: "client-card-num" }, "/ PROYECTO " + String(p.id).padStart(2, "0")),
                  e("span", { className: "client-card-stage" }, "ETAPA " + String(p.current_stage || 1).padStart(2, "0") + " / 07")
                ),
                e("h2", { className: "client-card-title" }, p.title),
                e("p", { className: "client-card-desc" }, p.description || ""),
                e(StageTrack, { currentStage: p.current_stage, size: "md" }),
                e("div", { className: "client-card-bar" },
                  e("i", { style: { width: (p.progress_pct || 0) + "%" } })
                ),
                e("div", { className: "client-card-stats" },
                  e("div", null,
                    e("span", { className: "kpi-label" }, "AVANCE"),
                    e("b", null, Math.round(p.progress_pct || 0) + "%")
                  ),
                  e("div", null,
                    e("span", { className: "kpi-label" }, "INICIO"),
                    e("b", null, p.start_date || "—")
                  ),
                  e("div", { className: overdue ? "is-overdue" : "" },
                    e("span", { className: "kpi-label" }, overdue ? "VENCIDO" : "ENTREGA"),
                    e("b", null, days != null ? (overdue ? Math.abs(days) + " días" : days + " días") : "—")
                  )
                ),
                e("span", { className: "client-card-cta" }, "Ver detalle por etapa →")
              );
            })
          ),

      e(ClientQuotesPanel)
    );
  }

  // Tiny SVG film camera silhouette — rendered inline for the
  // current `in_progress` step. Lens spins, body has a subtle rim light.
  function CameraDolly() {
    return e("svg", { className: "phase-dolly-camera", viewBox: "0 0 56 38", "aria-hidden": "true" },
      // body
      e("rect", { x: 6, y: 10, width: 36, height: 22, rx: 3, className: "dolly-body" }),
      // viewfinder hump
      e("rect", { x: 14, y: 4, width: 14, height: 8, rx: 2, className: "dolly-hump" }),
      // film reel left
      e("circle", { cx: 18, cy: 21, r: 5, className: "dolly-reel-bg" }),
      e("g", { className: "dolly-reel-spin" },
        e("circle", { cx: 18, cy: 21, r: 5, className: "dolly-reel" }),
        e("path", { d: "M18 18 L18 24 M15 21 L21 21", className: "dolly-reel-cross" })
      ),
      // lens
      e("circle", { cx: 32, cy: 21, r: 5.5, className: "dolly-lens-outer" }),
      e("circle", { cx: 32, cy: 21, r: 3.4, className: "dolly-lens-mid" }),
      e("circle", { cx: 32, cy: 21, r: 1.6, className: "dolly-lens-inner" }),
      // lens flare
      e("circle", { cx: 33.4, cy: 19.4, r: 0.9, className: "dolly-flare" }),
      // tripod hint
      e("path", { d: "M42 21 L52 21 L52 34", className: "dolly-arm" }),
      // dolly track underneath (animated dash)
      e("path", { d: "M2 36 L54 36", className: "dolly-track" })
    );
  }

  // Inline bitácora composer per stage. label tells whether it's an internal
  // note (team) or a client comment. Uses RPC bway_add_stage_comment when
  // role=cliente (so RLS policy passes). Triggers list reload on success.
  // Auto-detect external link provider for badges
  function detectLinkProvider(url) {
    if (!url) return null;
    const u = url.toLowerCase();
    if (u.includes("frame.io"))                              return "frameio";
    if (u.includes("vimeo.com"))                             return "vimeo";
    if (u.includes("youtube.com") || u.includes("youtu.be")) return "youtube";
    if (u.includes("drive.google.com"))                      return "drive";
    if (u.includes("dropbox.com"))                           return "dropbox";
    return "external";
  }

  // Render a single attachment block (used inside an update card)
  function UpdateAttachment({ update }) {
    const [signedUrl, setSignedUrl] = useState(null);
    useEffect(() => {
      if (!update.attachment_path) return;
      let cancelled = false;
      sb.storage.from("bway-progress")
        .createSignedUrl(update.attachment_path, 3600)
        .then(({ data, error }) => {
          if (cancelled || error) return;
          setSignedUrl(data?.signedUrl || null);
        });
      return () => { cancelled = true; };
    }, [update.attachment_path]);

    const link = update.link_url ? (function () {
      const provider = update.link_provider || detectLinkProvider(update.link_url);
      const label = update.link_label || ({
        frameio: "Ver en Frame.io",
        vimeo:   "Ver en Vimeo",
        youtube: "Ver en YouTube",
        drive:   "Abrir en Drive",
        dropbox: "Abrir en Dropbox",
      }[provider] || "Abrir enlace");
      return e("a", {
        className: "bw-attach-link is-" + provider,
        href: update.link_url, target: "_blank", rel: "noopener noreferrer"
      },
        e("span", { className: "bw-attach-link-icon" },
          provider === "frameio" ? "◐"
          : provider === "vimeo"   ? "▶"
          : provider === "youtube" ? "▶"
          : provider === "drive"   ? "△"
          : provider === "dropbox" ? "□"
          : "↗"
        ),
        e("span", { className: "bw-attach-link-label" }, label),
        e("span", { className: "bw-attach-link-domain" },
          (function () { try { return new URL(update.link_url).hostname.replace(/^www\./, ""); } catch { return ""; } })()
        )
      );
    })() : null;

    const file = update.attachment_path ? (function () {
      const mime = update.attachment_mime || "";
      const name = update.attachment_name || update.attachment_path.split("/").pop();
      const size = update.attachment_size ? Math.round(update.attachment_size / 1024) + " KB" : "";
      if (!signedUrl) return e("div", { className: "bw-attach-loading" }, "Cargando archivo...");
      if (mime.startsWith("video/")) {
        return e("div", { className: "bw-attach-media is-video" },
          e("video", { src: signedUrl, controls: true, preload: "metadata" }),
          e("div", { className: "bw-attach-meta" }, e("b", null, name), e("span", null, size))
        );
      }
      if (mime.startsWith("image/")) {
        return e("div", { className: "bw-attach-media is-image" },
          e("a", { href: signedUrl, target: "_blank", rel: "noopener" },
            e("img", { src: signedUrl, alt: name, loading: "lazy" })
          ),
          e("div", { className: "bw-attach-meta" }, e("b", null, name), e("span", null, size))
        );
      }
      // PDF / others — download link with icon
      const iconForMime = mime.includes("pdf") ? "PDF" : (name.split(".").pop() || "FILE").toUpperCase().slice(0, 4);
      return e("a", {
        className: "bw-attach-doc",
        href: signedUrl, target: "_blank", rel: "noopener noreferrer", download: name
      },
        e("span", { className: "bw-attach-doc-icon" }, iconForMime),
        e("span", { className: "bw-attach-doc-body" },
          e("b", null, name),
          e("span", null, size + " · descargar")
        )
      );
    })() : null;

    if (!link && !file) return null;
    return e("div", { className: "bw-attachments" }, link, file);
  }

  // Feed item with edit + delete affordances when the viewer owns the row.
  function FeedItem({ update, currentUserId, isAdmin, compact }) {
    const u = update;
    const canModify = isAdmin || u.author_id === currentUserId || u.author_email === currentUserId;
    // author_id is not always in payload — fall back to comparing author_email if available
    const owns = isAdmin || (u.author_name && u.author_role && (u.author_id ? u.author_id === currentUserId : false));
    const showActions = canModify || owns;

    const [editing, setEditing] = useState(false);
    const [body, setBody]       = useState(u.body || "");
    const [linkUrl, setLinkUrl] = useState(u.link_url || "");
    const [linkLabel, setLinkLabel] = useState(u.link_label || "");
    const [busy, setBusy]       = useState(false);
    const [err,  setErr]        = useState(null);

    const refresh = () => window.dispatchEvent(new CustomEvent("bway:bitacora-added"));

    const saveEdit = async (ev) => {
      ev.preventDefault();
      setBusy(true); setErr(null);
      try {
        const provider = linkUrl.trim() ? detectLinkProvider(linkUrl.trim()) : null;
        const { error } = await sb.from("stage_updates")
          .update({
            body: body.trim() || null,
            link_url: linkUrl.trim() || null,
            link_label: linkLabel.trim() || null,
            link_provider: provider,
          })
          .eq("id", u.id);
        if (error) throw error;
        setEditing(false);
        refresh();
      } catch (x) { setErr(x.message || "No se pudo guardar"); }
      finally { setBusy(false); }
    };

    const remove = async () => {
      if (!window.confirm("¿Eliminar este avance? Esta acción no se puede deshacer.")) return;
      setBusy(true); setErr(null);
      try {
        // 1) Drop the file from Storage first (best-effort — ignore if missing)
        if (u.attachment_path) {
          try { await sb.storage.from("bway-progress").remove([u.attachment_path]); } catch {}
        }
        // 2) Drop the row
        const { error } = await sb.from("stage_updates").delete().eq("id", u.id);
        if (error) throw error;
        refresh();
      } catch (x) { setErr(x.message || "No se pudo eliminar"); setBusy(false); }
    };

    if (editing) {
      return e("li", { className: "proj-feed-item is-editing" },
        e("form", { onSubmit: saveEdit, className: "proj-feed-edit" },
          err ? e("div", { className: "dash-banner is-error" }, err) : null,
          e("textarea", {
            className: "dash-input dash-input-area",
            rows: 2, value: body,
            onChange: (ev) => setBody(ev.target.value),
            placeholder: "Nota del avance..."
          }),
          e("div", { className: "bw-composer-row" },
            e("input", {
              className: "dash-input", type: "url",
              placeholder: "Enlace externo",
              value: linkUrl, onChange: (ev) => setLinkUrl(ev.target.value)
            }),
            linkUrl ? e("input", {
              className: "dash-input bw-composer-label",
              placeholder: "Etiqueta",
              value: linkLabel, onChange: (ev) => setLinkLabel(ev.target.value)
            }) : null
          ),
          u.attachment_path ? e("div", { className: "proj-feed-edit-note" },
            "El archivo adjunto no se puede modificar inline. Para cambiarlo, elimina este avance y publica uno nuevo."
          ) : null,
          e("div", { className: "phase-bitacora-actions" },
            e("button", { type: "button", className: "phase-step-btn is-ghost", onClick: () => { setEditing(false); setBody(u.body || ""); setLinkUrl(u.link_url || ""); setLinkLabel(u.link_label || ""); setErr(null); } }, "Cancelar"),
            e("button", { type: "submit", className: "phase-step-btn is-gold", disabled: busy }, busy ? "Guardando..." : "Guardar cambios")
          )
        )
      );
    }

    return e("li", { className: "proj-feed-item" + (compact ? " is-compact" : "") },
      e("div", { className: "proj-feed-meta" },
        e("b", null, u.author_name || "—"),
        u.author_role ? e("span", { className: "phase-pill is-" + u.author_role }, u.author_role === "usuario" ? "Colaborador" : u.author_role) : null,
        e("time", null, new Date(u.created_at).toLocaleString())
      ),
      u.body ? e("p", { className: "proj-feed-body" }, u.body) : null,
      e(UpdateAttachment, { update: u }),
      err ? e("div", { className: "dash-banner is-error" }, err) : null,
      showActions ? e("div", { className: "proj-feed-actions" },
        e("button", {
          type: "button", className: "proj-feed-act",
          onClick: () => setEditing(true), disabled: busy,
          title: "Editar avance"
        }, "✏ Editar"),
        e("button", {
          type: "button", className: "proj-feed-act is-danger",
          onClick: remove, disabled: busy,
          title: "Eliminar avance"
        }, busy ? "Eliminando..." : "🗑 Eliminar")
      ) : null
    );
  }

  function BitacoraInline({ stage, projectId, asCliente }) {
    const [open, setOpen]       = useState(false);
    const [body, setBody]       = useState("");
    const [linkUrl, setLinkUrl] = useState("");
    const [linkLabel, setLinkLabel] = useState("");
    const [file, setFile]       = useState(null);
    const [progress, setProgress] = useState(0);
    const [busy, setBusy]       = useState(false);
    const [err, setErr]         = useState(null);
    const fileInputRef = useRef(null);

    const ctaLabel = asCliente ? "+ Comentar esta entrega" : "+ Compartir avance / nota";
    if (!open) {
      return e("button", {
        className: "phase-step-btn is-ghost phase-bitacora-toggle",
        onClick: () => setOpen(true)
      }, ctaLabel);
    }

    const reset = () => {
      setOpen(false); setBody(""); setLinkUrl(""); setLinkLabel("");
      setFile(null); setProgress(0); setErr(null);
    };

    const submit = async (ev) => {
      ev.preventDefault();
      if (!body.trim() && !linkUrl.trim() && !file) return;
      setBusy(true); setErr(null);
      try {
        // 1. Upload file first if present
        let attachmentPath = null, attachmentMime = null, attachmentName = null, attachmentSize = null;
        if (file) {
          if (file.size > 50 * 1024 * 1024) {
            throw new Error("El archivo excede 50 MB. Subilo a Frame.io o Vimeo y pega el enlace.");
          }
          const project = projectId || stage.project_id || stage.project?.id;
          if (!project) throw new Error("Falta el ID del proyecto para subir el adjunto.");
          const cleanName = file.name.replace(/[^a-zA-Z0-9.\-_]/g, "_");
          const path = `${project}/${stage.id}/${Date.now()}-${cleanName}`;
          const up = await sb.storage.from("bway-progress").upload(path, file, {
            cacheControl: "3600", upsert: false, contentType: file.type || undefined
          });
          if (up.error) throw up.error;
          attachmentPath = path;
          attachmentMime = file.type || null;
          attachmentName = file.name;
          attachmentSize = file.size;
        }

        const provider = linkUrl.trim() ? detectLinkProvider(linkUrl.trim()) : null;
        const payload = {
          project_stage_id: stage.id,
          body: body.trim() || null,
          link_url: linkUrl.trim() || null,
          link_label: linkLabel.trim() || null,
          link_provider: provider,
          attachment_path: attachmentPath,
          attachment_mime: attachmentMime,
          attachment_name: attachmentName,
          attachment_size: attachmentSize,
          is_client_visible: true,
        };

        let result;
        if (asCliente) {
          // Cliente uses the comment RPC for the body, but attachments require direct insert
          // and the cliente policy already allows insert when is_client_visible=true
          const { data: { user } } = await sb.auth.getUser();
          result = await sb.from("stage_updates").insert({ ...payload, author_id: user.id });
        } else {
          const { data: { user } } = await sb.auth.getUser();
          result = await sb.from("stage_updates").insert({ ...payload, author_id: user.id });
        }
        if (result.error) throw result.error;

        reset();
        window.dispatchEvent(new CustomEvent("bway:bitacora-added"));
      } catch (x) {
        setErr(x.message || "No se pudo publicar el avance");
      } finally {
        setBusy(false);
      }
    };

    return e("form", { className: "phase-bitacora-form bw-composer", onSubmit: submit },
      err ? e("div", { className: "dash-banner is-error" }, err) : null,

      e("textarea", {
        className: "dash-input dash-input-area",
        rows: 2,
        placeholder: asCliente ? "Tu comentario al equipo BWAY..." : "Nota del avance (qué se entregó, qué falta...)",
        value: body,
        onChange: (ev) => setBody(ev.target.value)
      }),

      // External link row
      e("div", { className: "bw-composer-row" },
        e("input", {
          className: "dash-input",
          type: "url",
          placeholder: "Enlace externo (Frame.io, Vimeo, Drive…)",
          value: linkUrl,
          onChange: (ev) => setLinkUrl(ev.target.value)
        }),
        linkUrl ? e("input", {
          className: "dash-input bw-composer-label",
          placeholder: "Etiqueta (opcional)",
          value: linkLabel,
          onChange: (ev) => setLinkLabel(ev.target.value)
        }) : null
      ),

      // File upload row
      e("div", { className: "bw-composer-row bw-composer-file" },
        e("input", {
          ref: fileInputRef,
          type: "file",
          accept: "image/*,video/mp4,video/quicktime,video/webm,application/pdf",
          onChange: (ev) => setFile(ev.target.files?.[0] || null),
          style: { display: "none" }
        }),
        e("button", {
          type: "button",
          className: "phase-step-btn is-ghost",
          onClick: () => fileInputRef.current?.click()
        }, file ? "↻ Cambiar archivo" : "📎 Adjuntar archivo (≤ 50 MB)"),
        file ? e("span", { className: "bw-composer-fileinfo" },
          e("b", null, file.name),
          e("span", null, Math.round(file.size / 1024) + " KB")
        ) : null,
        file ? e("button", {
          type: "button", className: "bw-composer-remove",
          onClick: () => { setFile(null); if (fileInputRef.current) fileInputRef.current.value = ""; }
        }, "✕") : null
      ),

      e("div", { className: "phase-bitacora-actions" },
        e("button", { type: "button", className: "phase-step-btn is-ghost", onClick: reset }, "Cancelar"),
        e("button", { type: "submit", className: "phase-step-btn is-gold", disabled: busy },
          busy ? "Publicando..." : (asCliente ? "Comentar" : "Publicar avance")
        )
      )
    );
  }

  function PhaseTracker({ stages, onAdvance, canEdit, assignableUsers, onAssign, clienteCanComment }) {
    if (!stages || stages.length === 0) return null;
    const currentIdx = stages.findIndex((s) => s.status === "in_progress");
    const showAssign = canEdit && Array.isArray(assignableUsers) && assignableUsers.length > 0;
    return e("ol", { className: "phase-tracker", "aria-label": "Etapas de producción", "data-current": currentIdx },
      stages.map((s, i) => {
        const cls = "phase-step is-" + (s.status || "pending");
        return e("li", { key: s.id, className: cls },
          e("div", { className: "phase-step-rail" },
            e("span", { className: "phase-step-dot" },
              s.status === "in_progress" ? e(CameraDolly) : e("i")
            ),
            // Scan-line that travels through the rail when this is current
            s.status === "in_progress" ? e("span", { className: "phase-step-scan" }) : null,
            i < stages.length - 1 ? e("span", { className: "phase-step-line" }) : null
          ),
          e("div", { className: "phase-step-body" },
            e("div", { className: "phase-step-head" },
              e("span", { className: "phase-step-num" }, String(s.stage_order || s.stage_id).padStart(2, "0")),
              e("h3", { className: "phase-step-name" }, s.stage_name),
              e(StatusPill, { status: s.status })
            ),
            s.stage_description ? e("p", { className: "phase-step-desc" }, s.stage_description) : null,
            e("div", { className: "phase-step-meta" },
              s.started_at   ? e("span", null, "Inicio: " + new Date(s.started_at).toLocaleDateString()) : null,
              s.completed_at ? e("span", null, "Completado: " + new Date(s.completed_at).toLocaleDateString()) : null,
              s.assignee_name ? e("span", null, "Asignado: " + s.assignee_name) : null
            ),
            s.notes ? e("p", { className: "phase-step-notes" }, "Notas: " + s.notes) : null,
            showAssign ? e("div", { className: "phase-step-assign" },
              e("label", null, "Asignar a"),
              e("select", {
                value: s.assigned_to || "",
                onChange: (ev) => onAssign(s, ev.target.value || null)
              },
                e("option", { value: "" }, "— sin asignar —"),
                assignableUsers.map((u) =>
                  e("option", { key: u.id, value: u.id }, (u.full_name || u.id.slice(0, 8)) + " · " + (u.role === "usuario" ? "colaborador" : u.role))
                )
              ),
              s.assignee_name ? e("span", { className: "phase-assign-status" }, "✓") : null
            ) : (s.assignee_name ? e("div", { className: "phase-step-assign" },
              e("label", null, "Asignado a"),
              e("span", null, s.assignee_name)
            ) : null),
            canEdit ? e("div", { className: "phase-step-actions" },
              s.status !== "in_progress" && s.status !== "done"
                ? e("button", { className: "phase-step-btn", onClick: () => onAdvance(s, "in_progress") }, "Marcar en progreso")
                : null,
              s.status !== "done"
                ? e("button", { className: "phase-step-btn is-gold", onClick: () => onAdvance(s, "done") }, "Marcar completada")
                : null,
              s.status !== "blocked"
                ? e("button", { className: "phase-step-btn is-ghost", onClick: () => onAdvance(s, "blocked") }, "Bloqueada")
                : null,
              // Regress: only admin can move done→pending or unblock
              (canEdit && (s.status === "done" || s.status === "blocked"))
                ? e("button", {
                    className: "phase-step-btn is-ghost",
                    onClick: () => {
                      if (window.confirm("¿Devolver esta etapa a 'pendiente'? Se borrarán las marcas de inicio y completado.")) onAdvance(s, "pending");
                    }
                  }, "↶ Devolver a pendiente")
                : null
            ) : null,
            // Bitácora: admin or assigned usuario can add notes
            canEdit ? e(BitacoraInline, { stage: s }) : null,
            // Cliente can comment only on stages that are in_progress, done, or blocked
            (!canEdit && clienteCanComment && s.status !== "pending")
              ? e(BitacoraInline, { stage: s, asCliente: true }) : null
          )
        );
      })
    );
  }

  function ProjectDetail({ projectId, role }) {
    const [data, setData] = useState(null);
    const [err,  setErr]  = useState(null);
    const [assignable, setAssignable] = useState([]);
    const [selectedId, setSelectedId] = useState(null);

    const load = useCallback(() => {
      sb.rpc("bway_get_project_detail", { p_project_id: projectId }).then(({ data, error }) => {
        if (error) setErr(error.message); else setData(data);
      });
    }, [projectId]);
    useEffect(() => { load(); }, [load]);
    useEffect(() => {
      const onAdded = () => load();
      window.addEventListener("bway:bitacora-added", onAdded);
      return () => window.removeEventListener("bway:bitacora-added", onAdded);
    }, [load]);
    useEffect(() => {
      if (role !== "admin") return;
      sb.rpc("bway_list_assignable_users").then(({ data, error }) => {
        if (!error) setAssignable(data || []);
      });
    }, [role]);

    // Auto-select the active stage when data first arrives or selection becomes stale
    useEffect(() => {
      if (!data) return;
      const stages = data.stages || [];
      if (selectedId && stages.some(s => s.id === selectedId)) return;
      const inProg = stages.find(s => s.status === "in_progress");
      const blocked = stages.find(s => s.status === "blocked");
      const firstPending = stages.find(s => s.status === "pending");
      const target = inProg || blocked || firstPending || stages[0];
      if (target) setSelectedId(target.id);
    }, [data, selectedId]);

    const advance = async (stage, status) => {
      const { error } = await sb.rpc("bway_update_stage_status", {
        p_project_stage_id: stage.id, p_status: status, p_notes: null
      });
      if (error) { alert(error.message); return; }
      load();
    };
    const assign = async (stage, userId) => {
      const { error } = await sb.rpc("bway_assign_stage", {
        p_project_stage_id: stage.id, p_user_id: userId
      });
      if (error) { alert(error.message); return; }
      load();
    };

    if (err)   return e("div", { className: "dash-page" }, e(Banner, { kind: "error" }, err));
    if (!data) return e("div", { className: "dash-page" }, e(Spinner));

    const p = data.project || {};
    const stages = data.stages || [];
    const updates = data.updates || [];
    const selected = stages.find(s => s.id === selectedId) || stages[0];
    const canEdit = role !== "cliente";
    const clienteCanComment = role === "cliente";
    const { session } = useAuth();
    const currentUid = session?.user?.id || null;
    const back = role === "admin" ? "/dashboard/admin/projects"
              : role === "cliente" ? "/dashboard/cliente"
              : "/dashboard/usuario";

    const daysLeft = p.delivery_date
      ? Math.ceil((new Date(p.delivery_date) - new Date()) / 86400000)
      : null;
    const overdue = daysLeft != null && daysLeft < 0;
    const doneCount = stages.filter(s => s.status === "done").length;
    const blockedCount = stages.filter(s => s.status === "blocked").length;

    return e("div", { className: "dash-page proj-detail-page" },
      e("a", { className: "dash-crumb", href: "#" + back,
               onClick: (ev) => { ev.preventDefault(); navigate(back); } }, "← Volver"),

      e("header", { className: "proj-detail-head" },
        e("div", { className: "proj-detail-head-left" },
          e("span", { className: "bento-kicker" }, "/ PROYECTO · " + String(p.id || projectId).padStart(2, "0")),
          e("h1", null, p.title || ""),
          p.description ? e("p", { className: "proj-detail-desc" }, p.description) : null
        ),
        e("div", { className: "proj-detail-head-right" },
          e("span", { className: "proj-bento-status is-" + (overdue ? "overdue" : "on_track") }, p.client?.company_name || "—")
        )
      ),

      e("div", { className: "proj-detail-grid" },

        // === LEFT: project summary card ===
        e("aside", { className: "proj-detail-summary bento-cell" },
          e("div", { className: "proj-summary-ring" },
            e(ProgressRing, { pct: p.progress_pct || 0, size: 168, label: "completo" })
          ),
          e("ul", { className: "proj-summary-meta" },
            e("li", null, e("span", null, "Marca"), e("b", null, p.client?.company_name || "—")),
            e("li", null, e("span", null, "Inicio"), e("b", null, p.start_date || "—")),
            e("li", { className: overdue ? "is-overdue" : "" },
              e("span", null, overdue ? "Vencido" : "Entrega"),
              e("b", null, p.delivery_date || "—")
            ),
            daysLeft != null ? e("li", { className: overdue ? "is-overdue" : (daysLeft <= 3 ? "is-urgent" : "") },
              e("span", null, overdue ? "Días pasados" : "Restantes"),
              e("b", null, overdue ? Math.abs(daysLeft) + "d" : daysLeft + "d")
            ) : null
          ),
          e("div", { className: "proj-summary-pulse" },
            e("div", { className: "proj-summary-pulse-row" },
              e("b", null, doneCount), e("span", null, "completadas")
            ),
            e("div", { className: "proj-summary-pulse-row" },
              e("b", null, stages.length - doneCount - blockedCount), e("span", null, "pendientes")
            ),
            blockedCount > 0 ? e("div", { className: "proj-summary-pulse-row is-warn" },
              e("b", null, blockedCount), e("span", null, "bloqueadas")
            ) : null
          )
        ),

        // === CENTER: selected stage workspace ===
        e("section", { className: "proj-detail-workspace bento-cell" },
          selected ? (function () {
            const s = selected;
            return e(React.Fragment, null,
              e("header", { className: "proj-workspace-head" },
                e("span", { className: "bento-kicker" }, "ETAPA " + String(s.stage_order || s.stage_id).padStart(2, "0") + " / 07"),
                e("h2", null, s.stage_name),
                e(StatusPill, { status: s.status })
              ),
              s.stage_description ? e("p", { className: "proj-workspace-desc" }, s.stage_description) : null,
              e("dl", { className: "proj-workspace-meta" },
                e("div", null, e("dt", null, "Asignado"), e("dd", null, s.assignee_name || "—")),
                e("div", null, e("dt", null, "Inicio"), e("dd", null, s.started_at ? new Date(s.started_at).toLocaleDateString() : "—")),
                e("div", null, e("dt", null, "Completado"), e("dd", null, s.completed_at ? new Date(s.completed_at).toLocaleDateString() : "—"))
              ),
              s.notes ? e("p", { className: "proj-workspace-notes" }, "Notas: " + s.notes) : null,

              // Admin: assign control
              (role === "admin" && Array.isArray(assignable) && assignable.length > 0)
                ? e("div", { className: "proj-workspace-assign" },
                    e("label", null, "Asignar a"),
                    e("select", {
                      value: s.assigned_to || "",
                      onChange: (ev) => assign(s, ev.target.value || null)
                    },
                      e("option", { value: "" }, "— sin asignar —"),
                      assignable.map((u) =>
                        e("option", { key: u.id, value: u.id }, (u.full_name || u.id.slice(0, 8)) + " · " + (u.role === "usuario" ? "colaborador" : u.role))
                      )
                    )
                  )
                : null,

              // Action buttons
              canEdit ? e("div", { className: "proj-workspace-actions" },
                s.status !== "in_progress" && s.status !== "done"
                  ? e("button", { className: "phase-step-btn", onClick: () => advance(s, "in_progress") }, "Marcar en progreso") : null,
                s.status !== "done"
                  ? e("button", { className: "phase-step-btn is-gold", onClick: () => advance(s, "done") }, "Marcar completada") : null,
                s.status !== "blocked"
                  ? e("button", { className: "phase-step-btn is-ghost", onClick: () => advance(s, "blocked") }, "Bloquear") : null,
                (s.status === "done" || s.status === "blocked")
                  ? e("button", {
                      className: "phase-step-btn is-ghost",
                      onClick: () => {
                        if (window.confirm("¿Devolver esta etapa a 'pendiente'? Se borrarán las marcas de inicio y completado.")) advance(s, "pending");
                      }
                    }, "↶ Devolver a pendiente") : null
              ) : null,

              // Stage feed — avances + comentarios de esta etapa específica
              (function () {
                const stageUpdates = (updates || []).filter(u => u.project_stage_id === s.id);
                if (stageUpdates.length === 0) return null;
                return e("section", { className: "proj-workspace-feed" },
                  e("header", { className: "proj-workspace-feed-head" },
                    e("span", { className: "bento-kicker" }, "/ AVANCES DE ESTA ETAPA"),
                    e("span", { className: "proj-workspace-feed-count" }, stageUpdates.length + " entrada" + (stageUpdates.length === 1 ? "" : "s"))
                  ),
                  e("ul", { className: "proj-workspace-feed-list" },
                    stageUpdates.slice(0, 6).map((u) =>
                      e(FeedItem, {
                        key: u.id, update: u,
                        currentUserId: currentUid,
                        isAdmin: role === "admin"
                      })
                    )
                  )
                );
              })(),

              // Bitácora composer
              canEdit ? e(BitacoraInline, { stage: s, projectId: p.id })
                : (clienteCanComment && s.status !== "pending")
                  ? e(BitacoraInline, { stage: s, projectId: p.id, asCliente: true })
                  : null
            );
          })() : e("div", { className: "dash-list-empty" }, "Selecciona una etapa")
        ),

        // === RIGHT: stage rail + recent activity ===
        e("aside", { className: "proj-detail-rail" },
          e("section", { className: "bento-cell proj-rail-card" },
            e("header", { className: "bento-head" },
              e("div", null,
                e("span", { className: "bento-kicker" }, "/ TIMELINE"),
                e("h2", null, "7 etapas")
              )
            ),
            e("ol", { className: "proj-rail-list" },
              stages.map((s, i) => {
                const active = s.id === selectedId;
                return e("li", {
                  key: s.id,
                  className: "proj-rail-item is-" + (s.status || "pending") + (active ? " is-active" : ""),
                  onClick: () => setSelectedId(s.id),
                  role: "button", tabIndex: 0
                },
                  e("span", { className: "proj-rail-dot" }, String(s.stage_order || s.stage_id).padStart(2, "0")),
                  e("div", { className: "proj-rail-body" },
                    e("b", null, s.stage_name),
                    e("span", null,
                      s.assignee_name ? "→ " + s.assignee_name :
                        (s.status === "done" ? "completada" : s.status === "blocked" ? "bloqueada" : "sin asignar")
                    )
                  ),
                  i < stages.length - 1 ? e("span", { className: "proj-rail-line" }) : null
                );
              })
            )
          ),
          updates.length > 0 ? e("section", { className: "bento-cell proj-activity-card" },
            e("header", { className: "bento-head" },
              e("div", null,
                e("span", { className: "bento-kicker" }, "/ BITÁCORA"),
                e("h2", null, "Últimas notas")
              )
            ),
            e("ul", { className: "updates-list" },
              updates.slice(0, 4).map((u) =>
                e("li", { key: u.id },
                  e("span", { className: "updates-meta" }, (u.author_name || "—") + " · " + new Date(u.created_at).toLocaleDateString()),
                  u.body ? e("p", null, u.body) : null,
                  e(UpdateAttachment, { update: u })
                )
              )
            )
          ) : null
        )
      )
    );
  }

  // -----------------------------------------------------------
  // 9. USUARIO DASHBOARD
  // -----------------------------------------------------------
  function UsuarioDashboard() {
    const route = useRoute();
    const nav = [
      { to: "/dashboard/usuario", label: "Mis tareas", icon: "▣" }
    ];
    let view = e(UsuarioHome);
    if (/^\/dashboard\/usuario\/proyecto\/(\d+)$/.test(route.path)) {
      const id = parseInt(route.path.split("/").pop(), 10);
      view = e(ProjectDetail, { projectId: id, role: "usuario" });
    }
    return e(DashboardShell, { role: "usuario", nav }, view);
  }

  function UsuarioHome() {
    const [data, setData] = useState(null);
    const [overview, setOverview] = useState(null);
    const [err,  setErr]  = useState(null);
    const load = () => {
      sb.rpc("bway_get_my_dashboard").then(({ data, error }) => {
        if (error) setErr(error.message); else setData(data);
      });
      sb.rpc("bway_get_usuario_overview").then(({ data, error }) => {
        if (!error) setOverview(data);
      });
    };
    useEffect(() => { load(); }, []);
    const advance = async (task, status) => {
      const { error } = await sb.rpc("bway_update_stage_status", {
        p_project_stage_id: task.project_stage_id, p_status: status, p_notes: null
      });
      if (error) { alert(error.message); return; }
      load();
    };
    if (err)   return e("div", { className: "dash-page" }, e(Banner, { kind: "error" }, err));
    if (!data) return e("div", { className: "dash-page" }, e(Spinner));
    const tasks = data.tasks || [];

    // KPIs
    const pendingCount = tasks.filter(t => t.status === "pending").length;
    const inProgCount  = tasks.filter(t => t.status === "in_progress").length;
    const doneCount    = tasks.filter(t => t.status === "done").length;
    const blockedCount = tasks.filter(t => t.status === "blocked").length;
    const overdueTasks = tasks.filter(t => t.delivery_date && new Date(t.delivery_date) < new Date() && t.status !== "done");
    const urgentTasks  = tasks.filter(t => t.delivery_date && t.status !== "done" && t.status !== "blocked")
      .sort((a, b) => new Date(a.delivery_date) - new Date(b.delivery_date)).slice(0, 3);

    const focus = overview?.focus;
    const recent = overview?.recent_completed || [];

    return e("div", { className: "dash-page" },
      e(DashHeader, {
        kicker: "/ DASHBOARD · COLABORADOR",
        title: "Mi mesa de trabajo",
        sub:   "Tu foco actual, tus próximas entregas y tu producción de la semana."
      }),

      // FOCUS CARD — what to do right now
      focus ? e("section", { className: "dash-card usuario-focus" },
        e("div", { className: "usuario-focus-tag" },
          e("i", { className: "client-pulse-dot" }),
          "FOCO AHORA"
        ),
        e("h2", { className: "usuario-focus-title" },
          e("i", null, "0" + focus.stage_id), " · ", focus.stage_name
        ),
        e("p", { className: "usuario-focus-project" }, "Proyecto: ", e("b", null, focus.project_title)),
        e("div", { className: "usuario-focus-meta" },
          focus.delivery_date ? e("span", null, "Entrega: " + new Date(focus.delivery_date).toLocaleDateString()) : null,
          focus.days_left != null ? e("span", { className: focus.days_left < 0 ? "is-overdue" : focus.days_left <= 3 ? "is-warn" : "" },
            focus.days_left < 0 ? Math.abs(focus.days_left) + "d vencido" : focus.days_left + "d restantes") : null,
          focus.started_at ? e("span", null, "Iniciado " + new Date(focus.started_at).toLocaleDateString()) : null
        ),
        e("div", { className: "usuario-focus-actions" },
          e("button", { className: "phase-step-btn is-gold", onClick: () => advance({ project_stage_id: focus.id }, "done") }, "Marcar completada ✓"),
          e("button", { className: "phase-step-btn", onClick: () => navigate("/dashboard/usuario/proyecto/" + focus.project_id) }, "Ver proyecto →")
        )
      ) : null,

      // KPI hero row
      tasks.length > 0 ? e("div", { className: "kpi-grid usuario-kpi-grid" },
        e(KpiCard, { label: "En progreso", value: inProgCount, accent: true, hint: inProgCount === 1 ? "tarea ahora" : "tareas ahora" }),
        e(KpiCard, { label: "Pendientes", value: pendingCount, hint: "por iniciar" }),
        e(KpiCard, { label: "Completadas", value: doneCount, hint: "lifetime" }),
        e(KpiCard, {
          label: overdueTasks.length > 0 ? "⚠ Vencidas" : "Bloqueadas",
          value: overdueTasks.length > 0 ? overdueTasks.length : blockedCount,
          hint: overdueTasks.length > 0 ? "requieren atención" : "esperando"
        })
      ) : null,

      // Urgentes
      urgentTasks.length > 0 ? e("section", { className: "dash-card usuario-urgent" },
        e("header", { className: "dash-card-head" },
          e("h2", null, "Próximas a entregar"),
          e("span", { className: "kpi-label" }, urgentTasks.length + " tareas en cola")
        ),
        e("ul", { className: "usuario-urgent-list" },
          urgentTasks.map((t) => {
            const days = Math.ceil((new Date(t.delivery_date) - new Date()) / 86400000);
            return e("li", { key: t.project_stage_id,
                              onClick: () => navigate("/dashboard/usuario/proyecto/" + t.project_id),
                              role: "button", tabIndex: 0 },
              e("span", { className: "usuario-urgent-stage" }, "0" + t.stage_id),
              e("div", { className: "usuario-urgent-info" },
                e("strong", null, t.stage_name),
                e("span", null, t.project_title)
              ),
              e("span", { className: "usuario-urgent-status" },
                e(StatusPill, { status: t.status })
              ),
              e("span", { className: "usuario-urgent-days" + (days < 0 ? " is-overdue" : days <= 3 ? " is-warn" : "") },
                days < 0 ? Math.abs(days) + "d vencido" : days + "d restantes"
              )
            );
          })
        )
      ) : null,

      // All tasks header
      tasks.length > 0 ? e("div", { className: "client-section-head" },
        e("h2", null, "Todas tus etapas"),
        e("span", { className: "kpi-label" }, tasks.length + " tareas en cola")
      ) : null,

      tasks.length === 0
        ? e(EmptyState, { title: "No tienes tareas asignadas todavía.", body: "Cuando un admin te asigne una etapa, aparecerá aquí lista para trabajar." })
        : e("div", { className: "tasks-grid" },
            tasks.map((t) =>
              e("article", { key: t.project_stage_id, className: "task-card is-" + t.status },
                e("div", { className: "task-head" },
                  e("span", { className: "task-num" }, "ETAPA 0" + t.stage_id + " / 07"),
                  e(StatusPill, { status: t.status })
                ),
                e("h2", { className: "task-title" }, t.stage_name),
                e("a", { className: "task-project",
                        href: "#/dashboard/usuario/proyecto/" + t.project_id,
                        onClick: (ev) => { ev.preventDefault(); navigate("/dashboard/usuario/proyecto/" + t.project_id); } },
                  "Proyecto: ", e("b", null, t.project_title)),
                e("div", { className: "task-meta" },
                  t.delivery_date ? e("span", null, "Entrega: " + new Date(t.delivery_date).toLocaleDateString()) : null,
                  t.started_at    ? e("span", null, "Inicio: "  + new Date(t.started_at).toLocaleDateString())    : null
                ),
                t.notes ? e("p", { className: "task-notes" }, t.notes) : null,
                e("div", { className: "task-actions" },
                  t.status !== "in_progress" && t.status !== "done"
                    ? e("button", { className: "phase-step-btn", onClick: () => advance(t, "in_progress") }, "Iniciar")
                    : null,
                  t.status !== "done"
                    ? e("button", { className: "phase-step-btn is-gold", onClick: () => advance(t, "done") }, "Completar")
                    : null,
                  t.status === "done"
                    ? e("span", { className: "task-done-tag" }, "✓ Hecho")
                    : null
                )
              )
            )
          )
    );
  }

  function EmptyState({ title, body, action }) {
    return e("div", { className: "dash-empty" },
      e("div", { className: "dash-empty-mark" }, "∅"),
      e("h3", null, title),
      body ? e("p", null, body) : null,
      action || null
    );
  }

  // -----------------------------------------------------------
  // 10. Route gate — picks the right screen
  // -----------------------------------------------------------
  function AuthedShell() {
    const { status, profile } = useAuth();
    const route = useRoute();

    if (status === "no_config") return e(NoConfigScreen);
    if (status === "loading")   return e(Spinner, { label: "Cargando sesión" });

    if (route.path === "/login")    return e(LoginScreen);
    if (route.path === "/activate") return e(ActivateScreen);

    if (status === "anon") { navigate("/login"); return e(Spinner, { label: "Redirigiendo" }); }

    if (!profile)             return e(Spinner, { label: "Cargando perfil" });
    if (!profile.is_active)   return e(InactiveScreen);

    if (route.path.startsWith("/dashboard/admin")   && profile.role === "admin")   return e(AdminDashboard);
    if (route.path.startsWith("/dashboard/cliente") && profile.role === "cliente") return e(ClienteDashboard);
    if (route.path.startsWith("/dashboard/usuario") && profile.role === "usuario") return e(UsuarioDashboard);
    if (route.path.startsWith("/dashboard"))         return e(WrongRoleScreen, { role: profile.role });

    // Anything else under a route prefix: bounce to the right home
    if (route.path.startsWith("/dashboard")) {
      navigate("/dashboard/" + profile.role);
      return e(Spinner);
    }

    // Fallback (should be unreachable from this shell)
    return null;
  }

  function NoConfigScreen() {
    return e("div", { className: "auth-screen" },
      e("div", { className: "auth-bg", "aria-hidden": "true" }, e("div", { className: "auth-bg-grain" })),
      e("div", { className: "auth-card", style: { maxWidth: 560 } },
        e("div", { className: "auth-kicker" }, "BWAY · CONFIGURACIÓN"),
        e("h1", { className: "auth-h" }, "Falta conectar ", e("span", { className: "em" }, "Supabase")),
        e("p", { className: "auth-sub" },
          "Abre ", e("code", null, "config.js"),
          " y pega los valores de tu proyecto de Supabase (URL + anon key). Luego recarga."
        )
      )
    );
  }
  function InactiveScreen() {
    const { signOut } = useAuth();
    return e("div", { className: "auth-screen" },
      e("div", { className: "auth-card" },
        e("div", { className: "auth-kicker" }, "BWAY · CUENTA"),
        e("h1", { className: "auth-h" }, "Cuenta ", e("span", { className: "em" }, "pendiente")),
        e("p", { className: "auth-sub" }, "Tu cuenta aún no está activada. Espera la confirmación del admin o sigue el link del correo de invitación."),
        e(Btn, { onClick: signOut, kind: "ghost" }, "Cerrar sesión")
      )
    );
  }
  function WrongRoleScreen({ role }) {
    const { signOut } = useAuth();
    useEffect(() => {
      const t = setTimeout(() => navigate("/dashboard/" + role), 1500);
      return () => clearTimeout(t);
    }, [role]);
    return e("div", { className: "auth-screen" },
      e("div", { className: "auth-card" },
        e("div", { className: "auth-kicker" }, "BWAY · REDIRIGIENDO"),
        e("h1", { className: "auth-h" }, "Te llevamos a tu ", e("span", { className: "em" }, "panel"), "."),
        e(Btn, { onClick: signOut, kind: "ghost" }, "Cerrar sesión")
      )
    );
  }

  // -----------------------------------------------------------
  // 11. Mount: hijack rendering when the URL is an /dashboard or /login/...
  // The landing is rendered by app.jsx. We coexist: only render when route
  // is auth-related.
  // -----------------------------------------------------------
  function AuthRouter() {
    const route = useRoute();
    const isAuth = route.path === "/login"
                || route.path === "/activate"
                || route.path.startsWith("/dashboard");
    // Defensive: pause landing videos AND fully destroy Lenis when entering
    // dashboard. Lenis hijacks wheel events globally; even after .stop() its
    // listener can intercept wheel before .dash-main receives it. Destroying
    // releases the listener entirely. We restart Lenis on return to landing.
    useEffect(() => {
      if (isAuth) {
        document.querySelectorAll("video").forEach((v) => { try { v.pause(); } catch {} });
        document.documentElement.classList.add("bway-dash-active");
        try {
          if (window.bwayLenis) {
            window.bwayLenis.destroy();
            window.bwayLenis = null;
          }
        } catch {}
      } else {
        document.documentElement.classList.remove("bway-dash-active");
        document.querySelectorAll("video").forEach((v) => { try { v.play().catch(() => {}); } catch {} });
        // Recreate Lenis if it was destroyed
        try {
          const reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
          const fine    = window.matchMedia && window.matchMedia("(pointer: fine)").matches;
          if (!window.bwayLenis && typeof Lenis !== "undefined" && !reduced && fine) {
            const l = new Lenis({
              duration: 1.15,
              easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
              smoothWheel: true,
              anchors: { offset: -20 },
            });
            const raf = (time) => { l.raf(time); requestAnimationFrame(raf); };
            requestAnimationFrame(raf);
            window.bwayLenis = l;
          }
        } catch {}
      }
    }, [isAuth]);
    if (!isAuth) return null;
    return e(AuthProvider, null, e(AuthedShell));
  }

  // Mount into a dedicated root that overlays above the landing.
  const mount = document.createElement("div");
  mount.id = "bway-auth-root";
  document.body.appendChild(mount);
  ReactDOM.createRoot(mount).render(e(AuthRouter));

  // Expose helpers for tests
  window.bwayAuth = { navigate, parseHash, supabase: sb };
})();
