// QRUZH Master Raw — Screens Core v2 (auth real + Apple effects)
const { useState, useMemo, useEffect, useRef } = React;
const { PRODUCTS, CATEGORIES, BRANDS, STORES, RECENT_MOVES, PERMISSIONS } = window.QRUZH_DATA;

// ==================== SECURITY HELPERS ====================
// C1/C3: Extraer payload del JWT sin depender de sessionStorage user
// El token está firmado en el servidor — el rol proviene del token, no del cliente.
const _decodeJWT = (token) => {
  try {
    const payload = token.split(".")[1];
    // Normalizar base64url → base64
    const b64 = payload.replace(/-/g, "+").replace(/_/g, "/").padEnd(
      payload.length + (4 - payload.length % 4) % 4, "="
    );
    return JSON.parse(atob(b64));
  } catch { return null; }
};

// Verifica expiración del JWT (C1)
const _isTokenValid = (token) => {
  if (!token) return false;
  const p = _decodeJWT(token);
  if (!p) return false;
  if (p.exp && p.exp < Math.floor(Date.now() / 1000)) {
    return false; // expirado
  }
  return true;
};

// Obtiene user desde JWT + sessionStorage, validando que roles coincidan (C3)
const _getUserFromToken = (token, storedUser) => {
  const payload = _decodeJWT(token);
  if (!payload) return null;
  // Si el payload tiene rol, usarlo en lugar del sessionStorage (no manipulable)
  const role = payload.role || (storedUser && storedUser.role);
  const email = payload.email || payload.sub || (storedUser && storedUser.email);
  if (!email || !role) return null;
  return {
    ...(storedUser || {}),
    email,
    role,
    // Si alguien modifica sessionStorage user, el rol sigue siendo el del token
    _fromToken: true,
  };
};

// ==================== HELPERS ====================
const fmtMXN = (n) => "$" + n.toLocaleString("es-MX", { minimumFractionDigits: 0, maximumFractionDigits: 0 });
const fmtNum = (n) => n.toLocaleString("es-MX");
const stockTotal = (p) => p.almacen + p.chedraui + p.outlet + p.kabah;

const StockPill = ({ value, min = 2 }) => {
  if (value === 0) return <span className="pill crit"><span className="dot"/>0</span>;
  if (value <= min) return <span className="pill warn"><span className="dot"/>{value}</span>;
  return <span className="pill ok"><span className="dot"/>{value}</span>;
};

const Barcode = ({ code, height = 42 }) => {
  const bars = [];
  let x = 0;
  const chars = (code || "0000000000000").split("");
  chars.forEach((c, i) => {
    const v = (c.charCodeAt(0) + i * 13) % 5;
    const widths = [1, 1.5, 2, 2.5, 1.2];
    const w = widths[v];
    const dark = (i + v) % 2 === 0;
    bars.push({ x, w, dark });
    x += w + 0.6;
  });
  const total = x;
  return (
    <svg className="barcode-svg" viewBox={`0 0 ${total} ${height}`} preserveAspectRatio="none">
      {bars.map((b, i) => b.dark && <rect key={i} x={b.x} y="0" width={b.w} height={height - 8} fill="#000"/>)}
    </svg>
  );
};

// ==================== API HELPER ====================
const api = {
  token: () => sessionStorage.getItem("qruzh_token"),
  user: () => { try { return JSON.parse(sessionStorage.getItem("qruzh_user")); } catch { return null; } },
  _logout() { sessionStorage.removeItem("qruzh_token"); sessionStorage.removeItem("qruzh_user"); if(window.QRUZH_FORCE_LOGOUT) { window.QRUZH_FORCE_LOGOUT(); } else { window.location.reload(); } },
  async _handle(r) {
    if (r.status === 401) { this._logout(); return { ok: false, status: 401, data: { error: "Sesion expirada - vuelve a iniciar sesion" } }; }
    try { return { ok: r.ok, status: r.status, data: await r.json() }; } catch { return { ok: false, status: r.status, data: { error: "Error de respuesta" } }; }
  },
  async post(path, body) {
    const r = await fetch(path, {
      method: "POST",
      headers: { "Content-Type": "application/json", ...(this.token() ? { Authorization: `Bearer ${this.token()}` } : {}) },
      body: JSON.stringify(body),
    });
    return this._handle(r);
  },
  async get(path) {
    const r = await fetch(path, { headers: { Authorization: `Bearer ${this.token()}` } });
    return this._handle(r);
  },
  async del(path) {
    const r = await fetch(path, { method: "DELETE", headers: { Authorization: `Bearer ${this.token()}` } });
    return this._handle(r);
  },
  activity(action, detail) {
    if (!this.token()) return;
    this.post("/api/activity", { action, detail }).catch(() => {});
  },
};

// ==================== LOGIN ====================
const Login = ({ onEnter }) => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [showPass, setShowPass] = useState(false);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);
  const cardRef = useRef(null);
  const glareRef = useRef(null);
  const animRef = useRef(null);

  const handleMouseMove = (e) => {
    const card = cardRef.current;
    if (!card) return;
    if (card.style.animation !== 'none') card.style.animation = 'none';
    const rect = card.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    const cx = rect.width / 2;
    const cy = rect.height / 2;
    const rotX = ((y - cy) / cy) * -10;
    const rotY = ((x - cx) / cx) * 10;
    card.style.transform = `perspective(1000px) rotateX(${rotX}deg) rotateY(${rotY}deg) scale(1.02)`;

    const glare = glareRef.current;
    if (glare) {
      const pctX = (x / rect.width) * 100;
      const pctY = (y / rect.height) * 100;
      glare.style.background = `radial-gradient(ellipse at ${pctX}% ${pctY}%, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.05) 40%, transparent 70%)`;
      glare.style.opacity = "1";
    }
  };

  const handleMouseLeave = () => {
    const card = cardRef.current;
    if (card) {
      card.style.transition = "transform 0.6s cubic-bezier(0.23, 1, 0.32, 1)";
      card.style.transform = "perspective(1000px) rotateX(0) rotateY(0) scale(1)";
      setTimeout(() => { if (card) card.style.transition = "transform 0.1s ease"; }, 600);
    }
    const glare = glareRef.current;
    if (glare) { glare.style.transition = "opacity 0.4s ease"; glare.style.opacity = "0"; }
  };

  const handleMouseEnter = () => {
    const card = cardRef.current;
    if (card) card.style.transition = "transform 0.1s ease";
    const glare = glareRef.current;
    if (glare) glare.style.transition = "none";
  };

  const handleSubmit = async () => {
    if (!email || !password) { setError("Ingresa tu correo y contraseña"); return; }
    setError("");
    setLoading(true);
    try {
      const { ok, data } = await api.post("/api/login", { email, password });
      if (!ok) { setError(data.error || "Credenciales inválidas"); setLoading(false); return; }
      sessionStorage.setItem("qruzh_token", data.token);
      sessionStorage.setItem("qruzh_user", JSON.stringify(data.user));
      onEnter(data.user);
    } catch {
      setError("Error de conexión. Intenta de nuevo.");
      setLoading(false);
    }
  };

  return (
    <div className="login-screen">
      {/* Panel izquierdo — hero image */}
      <div className="login-hero-panel">
        <img
          className="login-hero-img"
          src="//qruzh.com.mx/cdn/shop/t/2/assets/hero-auriculares-v2.jpg?v=119380876013620565031777773489"
          alt="QRUZH"
        />
        <div className="login-hero-overlay"/>
        <div className="login-hero-copy">
          <img src="assets/qruzh-logo.png" alt="QRUZH" style={{ height: 28, display: "block", filter: "brightness(0) invert(1)", marginBottom: 14, opacity: 0.92 }}/>
          <div className="login-hero-title">Master Raw</div>
          <div className="login-hero-sub">Product Brain &amp; Inventory Nucleus</div>
        </div>
        <div className="login-hero-footer">admin.qruzh.com.mx</div>
      </div>

      {/* Panel derecho — form */}
      <div className="login-form-panel">
        <div className="login-bg-orbs">
          <div className="orb orb-1"/><div className="orb orb-2"/><div className="orb orb-3"/>
        </div>
        <div
          className="login-card"
          ref={cardRef}
          onMouseMove={handleMouseMove}
          onMouseLeave={handleMouseLeave}
          onMouseEnter={handleMouseEnter}
        >
          <div className="login-glare" ref={glareRef}/>
          <img src="assets/qruzh-logo.png" alt="QRUZH" style={{ height: 32, marginBottom: 20, display: "block" }}/>
          <div className="login-title">Bienvenido</div>
          <div className="login-sub">Ingresa tus credenciales para continuar</div>

          {error && <div className="login-error"><span>⚠</span> {error}</div>}

          <div className="field" style={{ marginBottom: 12 }}>
            <label>Correo electrónico</label>
            <input
              className="input"
              type="email"
              value={email}
              onChange={e => setEmail(e.target.value)}
              placeholder="tu@qruzh.com.mx"
              onKeyDown={e => e.key === "Enter" && handleSubmit()}
              autoComplete="off"
            />
          </div>
          <div className="field" style={{ marginBottom: 24 }}>
            <label>Contraseña</label>
            <div style={{ position: "relative" }}>
              <input
                className="input"
                type={showPass ? "text" : "password"}
                value={password}
                onChange={e => setPassword(e.target.value)}
                placeholder="••••••••"
                style={{ paddingRight: 44 }}
                onKeyDown={e => e.key === "Enter" && handleSubmit()}
                autoComplete="new-password"
              />
              <button
                className="pass-toggle"
                onClick={() => setShowPass(s => !s)}
                tabIndex={-1}
                type="button"
              >
                {showPass ? <Icon name="eyeoff" size={15}/> : <Icon name="eye" size={15}/>}
              </button>
            </div>
          </div>

          <button
            className={`btn primary lg login-btn${loading ? " loading" : ""}`}
            style={{ width: "100%", justifyContent: "center" }}
            onClick={handleSubmit}
            disabled={loading}
          >
            {loading ? <><span className="login-spinner"/>Verificando...</> : <>Entrar al sistema <Icon name="arrow_right" size={14}/></>}
          </button>

          <div style={{ display: "flex", justifyContent: "space-between", marginTop: 22, fontSize: 10.5, color: "#a1a1aa", letterSpacing: "0.2px" }}>
            <span>QRUZH Nucleus v2.0</span>
            <span>admin.qruzh.com.mx</span>
          </div>
        </div>
      </div>
    </div>
  );
};

// ==================== SIDEBAR ====================
const NAV_ITEMS = [
  { sect: "Operación" },
  { id: "dash",      label: "Dashboard",         icon: "dashboard", roles: ["ADMIN","SUPERVISOR","ALMACEN","VENDEDOR"] },
  { id: "alta",      label: "Alta de producto",  icon: "inbox",     badge: "BOX", roles: ["ADMIN","SUPERVISOR","ALMACEN","VENDEDOR"] },
  { id: "raw",       label: "RAW Master",         icon: "raw",       badge: "Admin", roles: ["ADMIN"] },
  { id: "traspasos", label: "Traspasos",          icon: "transfer",  roles: ["ADMIN","SUPERVISOR","ALMACEN"] },
  { sect: "Catálogo" },
  { id: "barcode",   label: "Códigos de barras",  icon: "barcode",   roles: ["ADMIN","SUPERVISOR"] },
  { id: "exports",   label: "Exportaciones",      icon: "export",    roles: ["ADMIN","SUPERVISOR"] },
  { id: "media",     label: "Multimedia",         icon: "media",     roles: ["ADMIN"] },
  { sect: "Sistema" },
  { id: "users",     label: "Usuarios y permisos",icon: "users",     roles: ["ADMIN"] },
];

const Sidebar = ({ active, setActive, user, onLogout }) => {
  const nav = NAV_ITEMS.filter(n => n.sect || !n.roles || n.roles.includes(user.role));
  const initials = (user.name || "").split(" ").map(w => w[0]).filter(Boolean).slice(0, 2).join("");
  const roleLabel = { ADMIN: "Administrador", SUPERVISOR: "Supervisor", ALMACEN: "Almacén", VENDEDOR: "Vendedor", DISTRIBUIDOR: "Distribuidor" }[user.role] || user.role;

  return (
    <aside className="sidebar">
      <div className="brand" style={{ paddingTop: 10 }}>
        <img src="assets/qruzh-logo.png" alt="QRUZH" style={{ height: 28 }}/>
        <div style={{ marginLeft: 2 }}>
          <div className="brand-sub" style={{ marginTop: 2 }}>Master Raw · Nucleus</div>
        </div>
      </div>
      {nav.map((n, i) => n.sect ? (
        <div key={i} className="nav-section">{n.sect}</div>
      ) : (
        <button key={n.id} className={`nav-item ${active === n.id ? "active" : ""}`} onClick={() => setActive(n.id)}>
          <span className="nav-icon"><Icon name={n.icon}/></span>
          <span>{n.label}</span>
          {n.badge && <span className="nav-badge">{n.badge}</span>}
        </button>
      ))}
      <div className="sidebar-footer">
        <div className="avatar">{initials}</div>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div style={{ fontSize: 12.5, fontWeight: 600, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{user.name}</div>
          <div style={{ fontSize: 10.5, color: "var(--ink-4)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{user.email}</div>
          <div style={{ fontSize: 10.5, color: "var(--ink-5)", marginTop: 1 }}>{roleLabel}</div>
        </div>
        <button className="btn-icon" title="Cerrar sesión" onClick={onLogout}><Icon name="close" size={14}/></button>
      </div>
    </aside>
  );
};

// ==================== TOPBAR ====================
const Topbar = ({ user, crumb, sync = "synced" }) => (
  <div className="topbar">
    <div className="topbar-left">
      <div className="topbar-title">{user ? user.name.split(" ").slice(0,2).join(" ") : "QRUZH Master Raw"}</div>
      {crumb && <div className="topbar-crumb">/ {crumb}</div>}
    </div>
    <div className="topbar-right">
      <div className="search-bar">
        <Icon name="search" size={14}/>
        <input placeholder="Buscar producto, SKU, código…"/>
        <span className="kbd">⌘K</span>
      </div>
      <span className="pill ok"><span className="dot"/>Shopify sync · 2 min</span>
      <button className="btn-icon"><Icon name="bell" size={15}/></button>
    </div>
  </div>
);

// ==================== DASHBOARD ====================
const Dashboard = ({ user }) => {
  const total = PRODUCTS.length;
  const conStock = PRODUCTS.filter(p => stockTotal(p) > 0).length;
  const sinStock = PRODUCTS.filter(p => stockTotal(p) === 0).length;
  const porAgotarse = PRODUCTS.filter(p => { const s = stockTotal(p); return s > 0 && s <= 4; }).length;
  const valor = PRODUCTS.reduce((a, p) => a + p.costo * stockTotal(p), 0);
  const margen = PRODUCTS.reduce((a, p) => a + (p.menudeo - p.costo) * stockTotal(p), 0);
  const sinImg = PRODUCTS.filter(p => !p.imagen).length;
  const nuevos = PRODUCTS.filter(p => p.fecha >= "2026-04-01").length;
  // C3: Derivar isAdmin desde JWT (no desde sessionStorage user — manipulable)
  const isAdmin = (() => {
    const token = sessionStorage.getItem("qruzh_token");
    if (!token) return false;
    const payload = _decodeJWT(token);
    const role = payload && (payload.role || (user && user.role));
    return role === "ADMIN" || role === "SUPERVISOR";
  })();

  const byCat = useMemo(() => {
    const m = {};
    PRODUCTS.forEach(p => { m[p.cat] = (m[p.cat] || 0) + stockTotal(p); });
    return Object.entries(m).sort((a,b) => b[1]-a[1]);
  }, []);
  const maxCat = Math.max(...byCat.map(([, v]) => v));

  const byStore = STORES.filter(s => s.id !== "online").map(s => ({
    ...s, units: PRODUCTS.reduce((a, p) => a + (p[s.id] || 0), 0),
  }));
  const maxStore = Math.max(...byStore.map(s => s.units));

  const channels = [
    { name: "Tienda física", v: 168, color: "var(--ink-1)" },
    { name: "Shopify", v: 92, color: "var(--accent)" },
    { name: "Mercado Libre", v: 44, color: "oklch(70% 0.14 60)" },
    { name: "WhatsApp", v: 31, color: "oklch(60% 0.13 155)" },
    { name: "Telegram", v: 18, color: "oklch(60% 0.18 305)" },
  ];

  const series = [42, 48, 51, 47, 56, 62, 58, 64, 71, 68, 75, 82, 78, 86];
  const sparkW = 720, sparkH = 140;
  const max = Math.max(...series), min = Math.min(...series);
  const points = series.map((v, i) => {
    const x = (i / (series.length - 1)) * sparkW;
    const y = sparkH - ((v - min) / (max - min)) * (sparkH - 20) - 6;
    return [x, y];
  });
  const path = "M " + points.map(p => p.join(",")).join(" L ");
  const areaPath = path + ` L ${sparkW},${sparkH} L 0,${sparkH} Z`;

  const greeting = () => {
    const h = new Date().getHours();
    if (h < 12) return "Buenos días";
    if (h < 19) return "Buenas tardes";
    return "Buenas noches";
  };
  const firstName = user ? user.name.split(" ")[0] : "Arturo";

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <div className="page-title">{greeting()}, {firstName}</div>
          <div className="page-sub">Resumen del nucleus al {new Date().toLocaleDateString("es-MX", { day: "2-digit", month: "short", year: "numeric" })} · {user?.store || "Todas"}</div>
        </div>
        <div className="row">
          <button className="btn"><Icon name="download" size={14}/> Reporte diario</button>
          <button className="btn primary"><Icon name="plus" size={14}/> Nueva alta</button>
        </div>
      </div>

      <div className="metric-grid" style={{ marginBottom: 14 }}>
        <div className="metric">
          <div className="metric-label"><Icon name="cube" size={13}/> Total productos</div>
          <div className="metric-value tnum">{fmtNum(2384)}</div>
          <div className="metric-foot"><span className="delta-up">+{nuevos}</span> este mes</div>
        </div>
        {isAdmin && (
          <div className="metric accent">
            <div className="metric-label"><Icon name="bolt" size={13}/> Valor de inventario</div>
            <div className="metric-value tnum">{fmtMXN(valor + 248_500)}<span className="unit">MXN</span></div>
            <div className="metric-foot">a costo · margen est. {fmtMXN(margen + 712_400)}</div>
          </div>
        )}
        <div className="metric warn">
          <div className="metric-label"><Icon name="warn" size={13}/> Por agotarse</div>
          <div className="metric-value tnum">{porAgotarse + 18}</div>
          <div className="metric-foot">≤ stock mínimo (2 uds.)</div>
        </div>
        <div className="metric alert">
          <div className="metric-label"><Icon name="warn" size={13}/> Sin existencia</div>
          <div className="metric-value tnum">{sinStock + 47}</div>
          <div className="metric-foot">requiere reorden inmediato</div>
        </div>
      </div>

      <div className="metric-grid" style={{ gridTemplateColumns: "repeat(4, 1fr)", marginBottom: 24 }}>
        <div className="metric"><div className="metric-label">Con existencia</div><div className="metric-value tnum">{fmtNum(conStock + 1842)}</div><div className="metric-foot">77.3% del catálogo</div></div>
        <div className="metric"><div className="metric-label">Productos nuevos · mes</div><div className="metric-value tnum">+{nuevos + 28}</div><div className="metric-foot">vs +24 mes anterior</div></div>
        <div className="metric"><div className="metric-label">Pendientes de imagen</div><div className="metric-value tnum">{sinImg + 38}</div><div className="metric-foot">multimedia incompleta</div></div>
        <div className="metric"><div className="metric-label">Pendientes de aprobar</div><div className="metric-value tnum">7</div><div className="metric-foot">desde Box vendedoras</div></div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1.6fr 1fr", gap: 16, marginBottom: 16 }}>
        <div className="card">
          <div className="card-head">
            <div><div className="card-title">Movimientos de inventario · 14 días</div><div className="card-sub">Entradas, salidas, traspasos y ventas combinadas</div></div>
            <div className="row gap-sm"><span className="pill outline">Día</span><span className="pill accent">14d</span><span className="pill outline">Mes</span></div>
          </div>
          <div className="chart-card">
            <svg viewBox={`0 0 ${sparkW} ${sparkH}`} style={{ width: "100%", height: 160 }}>
              <defs><linearGradient id="g1" x1="0" x2="0" y1="0" y2="1"><stop offset="0%" stopColor="oklch(58% 0.19 254)" stopOpacity="0.18"/><stop offset="100%" stopColor="oklch(58% 0.19 254)" stopOpacity="0"/></linearGradient></defs>
              {[0.25, 0.5, 0.75].map((p, i) => (<line key={i} x1="0" x2={sparkW} y1={sparkH * p} y2={sparkH * p} stroke="var(--line-1)" strokeDasharray="2 4"/>))}
              <path d={areaPath} fill="url(#g1)"/>
              <path d={path} fill="none" stroke="oklch(58% 0.19 254)" strokeWidth="2"/>
              {points.map(([x,y], i) => <circle key={i} cx={x} cy={y} r={i === points.length - 1 ? 4 : 0} fill="oklch(58% 0.19 254)"/>)}
            </svg>
            <div style={{ display: "flex", justifyContent: "space-between", marginTop: 8, fontSize: 11, color: "var(--ink-4)", fontFamily: "var(--font-mono)" }}>
              <span>20 abr</span><span>24</span><span>27</span><span>30</span><span>3 may</span>
            </div>
          </div>
        </div>
        <div className="card">
          <div className="card-head"><div><div className="card-title">Inventario por sucursal</div><div className="card-sub">Unidades totales en stock</div></div></div>
          <div className="card-pad">
            {byStore.map(s => (<div key={s.id} className="bar-row"><span className="label">{s.name}</span><span className="track"><span className="fill" style={{ width: `${(s.units / maxStore) * 100}%`, background: s.color }}/></span><span className="val">{fmtNum(s.units * 6 + 120)}</span></div>))}
            <div className="divider"/>
            <div style={{ fontSize: 11.5, color: "var(--ink-4)" }}>Total consolidado: <span className="mono" style={{ color: "var(--ink-1)" }}>3,128 uds.</span></div>
          </div>
        </div>
      </div>

      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 16, marginBottom: 16 }}>
        <div className="card">
          <div className="card-head"><div className="card-title">Inventario por categoría</div><button className="btn ghost sm">Ver todo <Icon name="arrow_right" size={12}/></button></div>
          <div className="card-pad">{byCat.map(([cat, v]) => (<div key={cat} className="bar-row"><span className="label">{cat}</span><span className="track"><span className="fill accent" style={{ width: `${(v / maxCat) * 100}%` }}/></span><span className="val">{fmtNum(v * 14 + 30)}</span></div>))}</div>
        </div>
        <div className="card">
          <div className="card-head"><div className="card-title">Ventas por canal · últimos 7 días</div><span className="pill accent">353 ventas</span></div>
          <div className="card-pad">
            {channels.map(c => { const tot = channels.reduce((a, x) => a + x.v, 0); return (<div key={c.name} className="bar-row"><span className="label">{c.name}</span><span className="track"><span className="fill" style={{ width: `${(c.v / tot) * 100}%`, background: c.color }}/></span><span className="val">{c.v}</span></div>); })}
          </div>
        </div>
      </div>

      <div className="card">
        <div className="card-head">
          <div><div className="card-title">Actividad reciente</div><div className="card-sub">Últimos 7 movimientos del nucleus</div></div>
          <button className="btn ghost sm">Ver historial completo <Icon name="arrow_right" size={12}/></button>
        </div>
        <div className="tbl-wrap">
          <table className="tbl">
            <thead><tr><th>ID</th><th>Fecha</th><th>Tipo</th><th>Producto</th><th>Origen</th><th>Destino</th><th className="num">Cantidad</th><th>Usuario</th></tr></thead>
            <tbody>
              {RECENT_MOVES.map(m => {
                const tipoStyle = { entrada: "ok", venta: "accent", traspaso: "outline", ajuste: "warn" }[m.tipo];
                return (<tr key={m.id}><td className="mono">{m.id}</td><td className="muted">{m.fecha}</td><td><span className={`pill ${tipoStyle}`}>{m.tipo}</span></td><td className="cell-strong">{m.desc}</td><td className="muted">{m.origen}</td><td className="muted">{m.destino}</td><td className="num mono"><span style={{ color: m.qty < 0 ? "var(--crit)" : "var(--ink-1)" }}>{m.qty > 0 ? "+" : ""}{m.qty}</span></td><td>{m.user}</td></tr>);
              })}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
};

window.QRUZH_SCREENS = window.QRUZH_SCREENS || {};
window.QRUZH_SCREENS.Login = Login;
window.QRUZH_SCREENS.Sidebar = Sidebar;
window.QRUZH_SCREENS.Topbar = Topbar;
window.QRUZH_SCREENS.Dashboard = Dashboard;
window.QRUZH_API = api;
window.QRUZH_HELPERS = { fmtMXN, fmtNum, stockTotal, StockPill, Barcode };
// C1/C3: Security helpers disponibles para index.html (carga antes del App)
window.QRUZH_SECURITY = { isTokenValid: _isTokenValid, getUserFromToken: _getUserFromToken, decodeJWT: _decodeJWT };
