// =========================================================================
// REVIN CRM — ROOT APP
// View switcher (Desktop / Mobile / Portal / Admin) + nav + modals
// =========================================================================

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "theme": "light",
  "dashboardVariant": "A",
  "pipelineVariant": "A"
}/*EDITMODE-END*/;

// =========================================================================
// ERROR BOUNDARY — evita que un fallo en cualquier vista/detalle tumbe TODO
// el CRM con pantalla blanca. Muestra un mensaje y permite seguir navegando.
// =========================================================================
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }
  static getDerivedStateFromError(error) {
    return { error };
  }
  componentDidCatch(error, info) {
    console.error("[REVIN] ErrorBoundary capturó un error de render:", error, info);
  }
  componentDidUpdate(prevProps) {
    // Al navegar (cambia resetKey) limpiamos el error para reintentar el render.
    if (this.state.error && prevProps.resetKey !== this.props.resetKey) {
      this.setState({ error: null });
    }
  }
  render() {
    if (this.state.error) {
      const Icons = window.Icons || {};
      const Warn = Icons.AlertTriangle || Icons.Flame || (() => null);
      return (
        <div className="empty">
          <div className="icon-wrap"><Warn/></div>
          <div style={{fontSize: 14, fontWeight: 600, marginBottom: 6}}>
            Algo falló al mostrar esta sección.
          </div>
          <div style={{fontSize: 12, color: "var(--fg-3)", marginBottom: 16, maxWidth: 420}}>
            El resto del CRM sigue funcionando. Puedes volver e intentarlo de nuevo o cambiar de sección.
            {this.props.detail ? <div style={{marginTop: 8, fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-4)"}}>{String(this.state.error && this.state.error.message || this.state.error)}</div> : null}
          </div>
          {this.props.onReset && (
            <button className="btn secondary" onClick={this.props.onReset}>
              {Icons.ChevronLeft ? <Icons.ChevronLeft/> : null} Volver
            </button>
          )}
        </div>
      );
    }
    return this.props.children;
  }
}
window.ErrorBoundary = ErrorBoundary;

// Destino al que ir DESPUÉS de un alta. Vive FUERA de React a propósito:
// recargar los datos remonta todo el árbol (`key={dataVersion}` en view-stage),
// así que cualquier estado de React se perdería justo al guardar. El formulario
// llama a onSave ANTES de recargar, esto se queda apuntado, y el DesktopApp
// recién montado lo lee para abrir la ficha del registro creado.
let _postAlta = null;

const DesktopApp = ({ isAdmin, tweaks }) => {
  const { Icons, DATA, Sidebar, TopBar } = window;
  const [active, setActive] = React.useState(_postAlta?.active || "dashboard");
  const [openProperty, setOpenProperty] = React.useState(_postAlta?.openProperty || null);
  const [openClient, setOpenClient] = React.useState(null);
  const [openProject, setOpenProject] = React.useState(_postAlta?.openProject || null);
  const [creating, setCreating] = React.useState(false);
  // Edicion de un inmueble: guarda el uuid de la fila, no el id de pantalla.
  const [editing, setEditing] = React.useState(null);
  const [creatingProject, setCreatingProject] = React.useState(null); // null | {inmuebleUid}
  // Aviso de éxito tras un alta. Se limpia solo al cambiar de pantalla.
  const [altaOk, setAltaOk] = React.useState(_postAlta?.mensaje || null);
  // Consumido: que no reaparezca en el siguiente montaje.
  React.useEffect(() => { _postAlta = null; }, []);
  const [showCmdK, setShowCmdK] = React.useState(false);
  const [showNotif, setShowNotif] = React.useState(false);

  // Keyboard shortcut for ⌘K
  React.useEffect(() => {
    const onKey = (e) => {
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
        e.preventDefault();
        setShowCmdK(s => !s);
      }
      if (e.key === "Escape") {
        setShowCmdK(false);
        setShowNotif(false);
      }
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const goTo = (id) => {
    setOpenProperty(null);
    setOpenClient(null);
    setOpenProject(null);
    setCreating(false);
    setCreatingProject(null);
    setAltaOk(null);
    if (id === "alta") { setCreating(true); return; }
    if (id === "alta-proyecto") { setCreatingProject({ inmuebleUid: null }); return; }
    setActive(id);
  };
  const openProp = (id) => { setOpenProject(null); setOpenProperty(id); };

  /**
   * Despues de captar desde el mapa: abrir la ficha del inmueble recien
   * creado para rellenarlo.
   *
   * Carlos, 28-ago-2026: "esto me deberia llevar a algun sitio donde metamos
   * datos del inmueble. No crear captacion y ya esta". Tenia razon: el mapa
   * creaba el registro con lo que sabe el Catastro —direccion, superficie,
   * año, uso— y ahi se quedaba, con un aviso pidiendo recargar. Todo lo que
   * de verdad importa (precio, propietario, estado, notas) no tenia donde
   * escribirse.
   *
   * Mismo mecanismo que el alta: `_postAlta` es de modulo, asi que sobrevive
   * al remontaje que provoca la recarga. Por eso se fija ANTES de recargar.
   */
  const trasCaptar = async (creado) => {
    if (!creado) return;
    _postAlta = {
      active: "inmuebles",
      openProperty: creado.codigo_largo || creado.ref || creado.id,
      mensaje: `Captación ${creado.ref} creada desde el Catastro. Completa lo que el Catastro no sabe: precio, propietario y estado.`,
    };
    if (window.RevinData?.reload) await window.RevinData.reload();
  };
  const openCli = (id) => setOpenClient(id);
  const openProj = (id) => setOpenProject(id);

  // Punto de entrada ÚNICO para acciones de navegación/creación, compartido por
  // CUALQUIER vista montada (Dashboard, Pipeline y —cuando gane escritura— Mobile).
  // No se enhebra por props: Desktop y Mobile son subárboles HERMANOS del
  // view-switcher, así que un Context no cruzaría de uno a otro; un registro en
  // window sí. La vista activa registra su implementación al montarse (mismo
  // patrón que window.RevinData) y la limpia al desmontarse. `goTo` solo llama a
  // setters de React (identidad estable), así que capturarlo una vez es correcto.
  React.useEffect(() => {
    window.RevinNav = {
      crearInmueble: () => goTo("alta"),
      crearProyecto: () => goTo("alta-proyecto"),
      ir: (id) => goTo(id),
    };
    return () => { window.RevinNav = null; };
  }, []);

  const crumbs = React.useMemo(() => {
    if (creating) return ["Inmuebles", "Alta de inmueble"];
    if (creatingProject) return ["Proyectos", "Nuevo proyecto"];
    if (openProperty) {
      const p = DATA.INMUEBLES.find(x => x.id === openProperty);
      return ["Inmuebles", p?.id || openProperty];
    }
    if (openClient) {
      const c = DATA.CLIENTS.find(x => x.id === openClient);
      const fromProv = c?.typeKey === "proveedor";
      return [fromProv ? "Proveedores" : "Clientes", c?.name || openClient];
    }
    if (openProject) {
      const pr = (DATA.PROYECTOS || []).find(x => x.id === openProject);
      return ["Proyectos", pr?.nombre || openProject];
    }
    const map = {
      dashboard: isAdmin ? "Dashboard equipo" : "Hoy",
      pipeline: "Pipeline",
      inmuebles: "Inmuebles",
      proyectos: "Proyectos",
      clientes: "Clientes / Leads",
      proveedores: "Proveedores",
      agenda: "Agenda",
      tareas: "Tareas",
      mapa: "Mapa",
      contabilidad: "Contabilidad",
      documentos: "Documentos",
      facturas: isAdmin ? "Facturación global" : "Mis comisiones",
      equipo: "Comerciales",
      ajustes: "Ajustes",
    };
    return [isAdmin ? "Admin" : "Comercial", map[active] || "—"];
  }, [active, openProperty, openClient, openProject, creating, creatingProject, isAdmin]);

  const renderContent = () => {
    // Tras un alta: recargar DATA (lo hace el propio formulario) y abrir la ficha
    // del registro recién creado, para que el usuario vea que existe de verdad.
    if (creating) return (
      <window.AltaInmueble
        onCancel={() => setCreating(false)}
        onSave={(creado) => {
          setCreating(false);
          if (creado) {
            _postAlta = {
              active: "inmuebles",
              openProperty: creado.codigo_largo || creado.ref || creado.id,
              mensaje: `Inmueble ${creado.ref} creado correctamente.`,
            };
          }
        }}
      />
    );
    if (creatingProject) return (
      <window.AltaProyecto
        inmuebleUid={creatingProject.inmuebleUid}
        onCancel={() => setCreatingProject(null)}
        onSave={(creado) => {
          setCreatingProject(null);
          if (creado) {
            _postAlta = {
              active: "proyectos",
              openProject: creado.id,
              mensaje: `Proyecto «${creado.nombre}» creado correctamente.`,
            };
          }
        }}
      />
    );
    // Editar va ANTES que la ficha: si no, la ficha gana y el formulario no
    // llega a pintarse nunca.
    if (editing) return (
      <window.AltaInmueble
        inmuebleId={editing}
        onCancel={() => setEditing(null)}
        onSave={(guardado) => {
          setEditing(null);
          if (guardado) {
            _postAlta = {
              active: "inmuebles",
              openProperty: guardado.codigo_largo || guardado.ref || guardado.id,
              mensaje: `Inmueble ${guardado.ref} actualizado.`,
            };
          }
        }}
      />
    );
    if (openProperty) return <window.PropertyDetail propId={openProperty} onBack={() => setOpenProperty(null)} onEdit={setEditing}/>;
    if (openClient) return <window.ClientDetail clientId={openClient} onBack={() => setOpenClient(null)}/>;
    if (openProject) return <window.ProjectDetail projectId={openProject} onBack={() => setOpenProject(null)} onOpenProperty={openProp}/>;

    if (active === "dashboard") {
      if (isAdmin) return <window.AdminDashboard goTo={goTo}/>;
      if (tweaks.dashboardVariant === "B") return <window.DashboardB goTo={goTo}/>;
      if (tweaks.dashboardVariant === "C") return <window.DashboardC goTo={goTo}/>;
      return <window.DashboardA goTo={goTo}/>;
    }
    if (active === "pipeline") {
      if (tweaks.pipelineVariant === "B") return <window.PipelineB onOpenProperty={openProp}/>;
      if (tweaks.pipelineVariant === "C") return <window.PipelineC onOpenProperty={openProp}/>;
      return <window.PipelineA onOpenProperty={openProp}/>;
    }
    if (active === "inmuebles") return <window.InventoryView onOpenProperty={openProp} onCreate={() => setCreating(true)} onEditProperty={setEditing}/>;
    if (active === "proyectos") return <window.ProjectsView onOpenProject={openProj} onCreate={() => setCreatingProject({ inmuebleUid: null })}/>;
    if (active === "clientes") return <window.ClientsView onOpenClient={openCli} kind="clientes"/>;
    if (active === "proveedores") return <window.ClientsView onOpenClient={openCli} kind="proveedores"/>;
    if (active === "agenda") return <window.AgendaView/>;
    if (active === "tareas") return <window.TasksView/>;
    if (active === "mapa") return <window.MapView onCaptado={trasCaptar}/>;
    // Ocultar la entrada del sidebar no basta: `active` se puede quedar en
    // "contabilidad" al recargar o al volver de una ficha, y entonces la vista
    // se montaría igual sin que nadie la haya pedido.
    if (active === "contabilidad") {
      if (!window.veLaCaja()) return <window.SinPermiso que="la contabilidad"/>;
      return <window.ContabilidadView onOpenProperty={openProp} onOpenProject={openProj}/>;
    }
    if (active === "documentos") return <window.DocumentsView/>;
    if (active === "facturas") return <window.FinanceView isAdmin={isAdmin}/>;
    if (active === "equipo") return <window.EquipoView/>;
    return (
      <div className="empty">
        <div className="icon-wrap"><Icons.Settings/></div>
        Sección "{active}" — pendiente para próxima iteración.
      </div>
    );
  };

  const isMapaFull = active === "mapa" && !openProperty && !creating;

  return (
    <div className="app">
      <Sidebar active={active} onNavigate={goTo} isAdmin={isAdmin}/>
      <div className="main">
        <TopBar
          crumbs={crumbs}
          onSearch={() => setShowCmdK(true)}
          onNotif={() => setShowNotif(true)}
          notifCount={DATA.NOTIFICATIONS.filter(n => n.unread).length}
          isAdmin={isAdmin}
        />
        <div className="content" style={isMapaFull ? {padding: 0, overflow: "hidden"} : {}}>
          <ErrorBoundary
            resetKey={`${active}|${openProperty}|${openClient}|${openProject}|${creating}|${!!creatingProject}`}
            onReset={() => { setOpenProperty(null); setOpenClient(null); setOpenProject(null); setCreating(false); setCreatingProject(null); setActive("dashboard"); }}
          >
            {altaOk && (
              <div className="alta-banner ok" onClick={() => setAltaOk(null)} style={{ cursor: "pointer" }}>
                <Icons.Check/><span>{altaOk}</span>
              </div>
            )}
            {renderContent()}
          </ErrorBoundary>
        </div>
      </div>

      {showCmdK && <window.CommandPalette onClose={() => setShowCmdK(false)} onNavigate={goTo} onOpenProperty={openProp}/>}
      {showNotif && <window.NotificationsDrawer onClose={() => setShowNotif(false)}/>}
    </div>
  );
};

// =========================================================================
// ROOT — View switcher
// =========================================================================

const App = () => {
  const { Icons } = window;
  const [view, setView] = React.useState("desktop"); // desktop | mobile | portal | admin

  // Tweaks
  const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // ---- Supabase connection state ----
  const [connection, setConnection] = React.useState("mock"); // mock | connecting | live | error
  const [userEmail, setUserEmail] = React.useState(null);
  const [loadError, setLoadError] = React.useState(null);
  const [showLogin, setShowLogin] = React.useState(false);
  const [dataVersion, setDataVersion] = React.useState(0); // bump → re-render tree

  // Reset to mock data
  const useMock = React.useCallback(() => {
    window.DATA = window._MOCK_DATA;
    setConnection("mock");
    setUserEmail(null);
    setDataVersion(v => v + 1);
  }, []);

  // Carga los datos reales asumiendo que YA hay sesión. Si falla, deja el estado
  // en "error" y NUNCA cae a mocks (invariante: sesión ⇒ datos reales o error).
  const loadLive = React.useCallback(async () => {
    setConnection("connecting");
    try {
      const data = await window.RevinSupabase.loadDataFromSupabase();
      window.DATA = data;
      setConnection("live");
      setLoadError(null);
      setDataVersion(v => v + 1);
    } catch (e) {
      console.error("Error cargando datos en vivo:", e);
      setLoadError(e.message || "Error desconocido");
      setConnection("error");
      setDataVersion(v => v + 1);
    }
  }, []);

  // Try to restore existing Supabase session on mount
  React.useEffect(() => {
    if (!window.RevinSupabase) return;
    (async () => {
      let session = null;
      try { session = await window.RevinSupabase.getSession(); }
      catch (e) { console.warn("No se pudo leer la sesión Supabase:", e.message); }
      // Sin sesión → datos de prueba (mock) es el estado legítimo por defecto.
      if (!(session && session.user)) return;
      // Con sesión → SIEMPRE datos reales (o error). Sin fallback silencioso a mock.
      setUserEmail(session.user.email);
      await loadLive();
    })();
  }, [loadLive]);

  // Apply theme
  React.useEffect(() => {
    document.documentElement.setAttribute("data-theme", tweaks.theme || "light");
  }, [tweaks.theme]);

  // Exponer estado de conexión + recarga de datos para escrituras desde módulos
  // (p.ej. crear tarea → reload). Solo recarga en vivo.
  React.useEffect(() => {
    window.RevinData = {
      isLive: connection === "live",
      reload: async () => {
        if (connection !== "live" || !window.RevinSupabase) return;
        const data = await window.RevinSupabase.loadDataFromSupabase();
        window.DATA = data;
        setDataVersion(v => v + 1);
      },
    };
  }, [connection]);

  // After successful login
  const onLoginSuccess = async () => {
    setShowLogin(false);
    setConnection("live");
    setLoadError(null);
    try {
      const session = await window.RevinSupabase.getSession();
      setUserEmail(session?.user?.email || null);
    } catch (_) {}
    setDataVersion(v => v + 1);
  };

  const disconnect = async () => {
    try { await window.RevinSupabase.signOut(); } catch (_) {}
    useMock();
  };

  /**
   * ¿Estamos en un telefono de verdad?
   *
   * Hasta el 28-ago-2026 no habia NINGUNA deteccion: la vista se elegia
   * pinchando una pestaña, pensada para enseñar la maqueta desde un ordenador.
   * Abriendo el CRM en un movil salia la vista de escritorio, con la barra
   * lateral comiendose dos tercios de la pantalla; y la pestaña "Movil"
   * dibujaba dos iPhones de 390 px dentro de una pantalla de 375.
   *
   * matchMedia y no innerWidth: se entera tambien al girar el telefono.
   */
  const [esMovil, setEsMovil] = React.useState(
    () => typeof window !== "undefined" && window.matchMedia("(max-width: 767px)").matches,
  );
  React.useEffect(() => {
    const mq = window.matchMedia("(max-width: 767px)");
    const alCambiar = (e) => setEsMovil(e.matches);
    // Se vuelve a mirar AQUÍ y no solo al escuchar: entre que se calcula el
    // estado inicial y se engancha el listener, el ancho puede haber cambiado
    // —al girar el teléfono mientras carga, o en un panel que se dimensiona
    // tarde— y ese cambio no lo cuenta nadie. Medido: con el panel a 0 px al
    // montar y 1400 después, se quedaba en vista móvil a 1400 de ancho.
    setEsMovil(mq.matches);
    mq.addEventListener("change", alCambiar);
    return () => mq.removeEventListener("change", alCambiar);
  }, []);

  // ---- QUIÉN ERES MANDA SOBRE QUÉ VES -----------------------------------
  // El conmutador de cuatro vistas nació como instrumental de diseño: sirve
  // para enseñar la maqueta, no para repartir permisos. Mientras el único que
  // entraba era el fundador (admin) daba igual; en cuanto entra una comercial,
  // "Admin · Desktop" es un botón que le abre la vista de dirección.
  //
  // Con SESIÓN VIVA manda `usuarios.rol`, que viene de la base de datos.
  // Sin sesión (datos de demostración) el conmutador sigue siendo lo que era.
  //
  // Ojo: esto es la capa de PRESENTACIÓN. Lo que de verdad protege los datos
  // es RLS en Postgres — si esto fallara, la comercial vería pantallas de
  // admin vacías, no datos ajenos. Son dos capas y hacen falta las dos.
  const rolReal = (connection === "live" && window.DATA && window.DATA.USER && window.DATA.USER.rol) || null;
  const mandaElRol = connection === "live" && !!rolReal;
  const esAdminReal = rolReal === "admin";
  // Una comercial no elige vista: se le da la suya. Da igual en qué estado se
  // hubiera quedado `view` antes de entrar.
  const vistaEfectiva = (mandaElRol && !esAdminReal) ? "desktop" : view;

  const Tabs = () => (
    <div className="vb-tabs">
      <button className={"vb-tab" + (view === "desktop" ? " active" : "")} onClick={() => setView("desktop")}>
        <Icons.Dash size={12}/> Comercial · Desktop
      </button>
      <button className={"vb-tab" + (view === "mobile" ? " active" : "")} onClick={() => setView("mobile")}>
        <Icons.Phone size={12}/> Comercial · Móvil
      </button>
      <button className={"vb-tab" + (view === "portal" ? " active" : "")} onClick={() => setView("portal")}>
        <Icons.Users size={12}/> Portal Cliente
      </button>
      <button className={"vb-tab" + (view === "admin" ? " active" : "")} onClick={() => setView("admin")}>
        <Icons.Trophy size={12}/> Admin · Desktop
      </button>
    </div>
  );

  // En un telefono no hay conmutador de vistas ni panel de tweaks: eso es
  // instrumental de diseño. Lo que si viaja es el acceso, porque desde la calle
  // hay que poder entrar.
  if (esMovil) {
    return (
      <div className="app-movil">
        <div className="mv-barra">
          <img src={(window.__resources && window.__resources.logoWhite) || "revin/assets/logo-wordmark-white.png"} alt="REVIN"/>
          <span className="hueco"/>
          <div className="vb-connection">
            {connection === "mock" && (
              <button className="vb-conn-btn" onClick={() => setShowLogin(true)}>
                <span className="vb-conn-dot mock"/> <b>Entrar</b>
              </button>
            )}
            {connection === "connecting" && (
              <span className="vb-conn-btn"><span className="vb-conn-dot connecting"/> Cargando…</span>
            )}
            {connection === "error" && (
              <button className="vb-conn-btn" onClick={loadLive}>
                <span className="vb-conn-dot" style={{background: "var(--danger)"}}/> <b>Reintentar</b>
              </button>
            )}
            {connection === "live" && (
              <button className="vb-conn-btn live" onClick={disconnect}>
                <span className="vb-conn-dot live"/> {userEmail?.split("@")[0]}
              </button>
            )}
          </div>
        </div>

        {connection === "error" ? (
          <div className="empty" style={{flex: 1, justifyContent: "center", padding: 24}}>
            <div className="icon-wrap"><Icons.Alert/></div>
            <div style={{fontSize: 15, fontWeight: 700, marginBottom: 8}}>No se pudieron cargar los datos</div>
            <div style={{fontSize: 12, color: "var(--fg-3)", marginBottom: 16, textAlign: "center"}}>
              Hay sesión activa, así que no se enseñan datos de demostración.
            </div>
            <button className="btn primary" onClick={loadLive}>Reintentar</button>
          </div>
        ) : (
          <ErrorBoundary resetKey={`movil|${dataVersion}`}>
            <window.MobileReal key={dataVersion}/>
          </ErrorBoundary>
        )}

        {showLogin && window.RevinSupabase && (
          <window.RevinSupabase.SupabaseLoginModal
            onSuccess={onLoginSuccess}
            onCancel={() => setShowLogin(false)}
          />
        )}
      </div>
    );
  }

  return (
    <>
      <div className="view-bar">
        <div className="vb-brand">
          <img src={(window.__resources && window.__resources.logoWhite) || "revin/assets/logo-wordmark-white.png"} alt="REVIN"/>
          <span className="vb-tag">CRM · v1.0</span>
        </div>
        {(!mandaElRol || esAdminReal) && <Tabs/>}
        <div className="vb-spacer"/>
        <div className="vb-meta">
          {mandaElRol && !esAdminReal ? (
            // Sin conmutador que explicar, el hueco dice quién eres. No es
            // decorativo: es la única señal de con qué cuenta estás dentro.
            (window.DATA && window.DATA.USER && window.DATA.USER.name) || "Sesión activa"
          ) : (<>
            {vistaEfectiva === "desktop" && "Vista del día a día del comercial"}
            {vistaEfectiva === "mobile" && "Captaciones, pipeline y clientes en la calle"}
            {vistaEfectiva === "portal" && "Lo que ve el propietario"}
            {vistaEfectiva === "admin" && "Operaciones, equipo y finanzas globales"}
          </>)}
        </div>
        <div className="vb-connection">
          {connection === "mock" && (
            <button className="vb-conn-btn" onClick={() => setShowLogin(true)}>
              <span className="vb-conn-dot mock"/> Datos de prueba · <b>Conectar a Supabase</b>
            </button>
          )}
          {connection === "connecting" && (
            <span className="vb-conn-btn"><span className="vb-conn-dot connecting"/> Cargando datos…</span>
          )}
          {connection === "error" && (
            <button className="vb-conn-btn" onClick={loadLive} title={loadError || ""}>
              <span className="vb-conn-dot" style={{background: "var(--danger)"}}/> Error de carga · <b>Reintentar</b>
            </button>
          )}
          {connection === "live" && (
            <button className="vb-conn-btn live" onClick={disconnect} title={userEmail || ""}>
              <span className="vb-conn-dot live"/> En vivo · {userEmail?.split("@")[0]} ·
              <span style={{opacity: 0.7, marginLeft: 4}}>Cerrar sesión</span>
            </button>
          )}
        </div>
      </div>

      <div className="view-stage" key={dataVersion}>
        {connection === "error" ? (
          // Hay sesión activa pero la carga falló: mostramos error, NO datos de
          // demostración (window.DATA sigue siendo el mock: no debe pintarse).
          <div className="empty" style={{minHeight: "70vh", justifyContent: "center"}}>
            <div className="icon-wrap"><Icons.Alert/></div>
            <div style={{fontSize: 16, fontWeight: 700, marginBottom: 8}}>No se pudieron cargar los datos en vivo</div>
            <div style={{fontSize: 13, color: "var(--fg-3)", maxWidth: 480, marginBottom: 20, lineHeight: 1.5}}>
              Hay una sesión activa, así que no mostramos datos de demostración.
              {loadError && <div style={{marginTop: 8, fontFamily: "var(--font-mono)", fontSize: 11, color: "var(--fg-4)"}}>{loadError}</div>}
            </div>
            <div style={{display: "flex", gap: 8}}>
              <button className="btn primary" onClick={loadLive}><Icons.ArrowRight/> Reintentar</button>
              <button className="btn secondary" onClick={disconnect}>Cerrar sesión y usar datos de prueba</button>
            </div>
          </div>
        ) : (
          <ErrorBoundary resetKey={`${vistaEfectiva}|${dataVersion}`}>
            {vistaEfectiva === "desktop" && <DesktopApp isAdmin={mandaElRol ? esAdminReal : false} tweaks={tweaks}/>}
            {vistaEfectiva === "mobile" && <window.MobileView/>}
            {vistaEfectiva === "portal" && <window.PortalCliente/>}
            {/* La vista de admin no se monta nunca para quien no lo es: sin
                sesión sigue siendo maqueta, y con sesión exige el rol real. */}
            {vistaEfectiva === "admin" && (!mandaElRol || esAdminReal) && <DesktopApp isAdmin={true} tweaks={tweaks}/>}
          </ErrorBoundary>
        )}
      </div>

      {showLogin && window.RevinSupabase && (
        <window.RevinSupabase.SupabaseLoginModal
          onSuccess={onLoginSuccess}
          onCancel={() => setShowLogin(false)}
        />
      )}

      <window.TweaksPanel title="Tweaks">
        <window.TweakSection label="Tema">
          <window.TweakRadio
            label="Modo"
            value={tweaks.theme}
            onChange={(v) => setTweak("theme", v)}
            options={[
              { value: "light", label: "Claro" },
              { value: "dark", label: "Oscuro" },
            ]}
          />
        </window.TweakSection>

        {(view === "desktop" || view === "admin") && (
          <>
            <window.TweakSection label="Variante de Dashboard">
              <window.TweakSelect
                label="Dashboard"
                value={tweaks.dashboardVariant}
                onChange={(v) => setTweak("dashboardVariant", v)}
                options={[
                  { value: "A", label: "A · Mi día (lista priorizada)" },
                  { value: "B", label: "B · Embudo de operaciones" },
                  { value: "C", label: "C · Hero brutalista" },
                ]}
              />
            </window.TweakSection>

            <window.TweakSection label="Variante de Pipeline">
              <window.TweakSelect
                label="Pipeline"
                value={tweaks.pipelineVariant}
                onChange={(v) => setTweak("pipelineVariant", v)}
                options={[
                  { value: "A", label: "A · Kanban horizontal (9 cols)" },
                  { value: "B", label: "B · Stepper + checklist por fase" },
                  { value: "C", label: "C · Timeline vertical por inmueble" },
                ]}
              />
            </window.TweakSection>
          </>
        )}
      </window.TweaksPanel>
    </>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
