// ===== Root app: auth, routing, state =====
const { useState, useEffect } = React;
const LS_AUTH = "casepoint.auth.v1";
const LS_ORDERS = "casepoint.orders.v1";
const LS_ROUTE = "casepoint.route.v1";
const LS_INV = "casepoint.inventory.v1";
const LS_EXP = "casepoint.expenses.v1";
const LS_ATT = "casepoint.attendance.v1";
const LS_NOTIF = "casepoint.notifications.v1";
const LS_TPL = "casepoint.templates.v1";
const LS_SALES = "casepoint.sales.v1";
const LS_CLIENTS = "casepoint.clients.v1";
const LS_SETTINGS = "casepoint.settings.v1";

function loadJSON(k, fallback){ try{ const v = localStorage.getItem(k); return v?JSON.parse(v):fallback; }catch(e){ return fallback; } }

// ===== Sincronización con el backend (Cloudflare) =====
const LS = { orders: LS_ORDERS, users: LS_USERS, inventory: LS_INV, expenses: LS_EXP,
  attendance: LS_ATT, notifications: LS_NOTIF, templates: LS_TPL, sales: LS_SALES, clients: LS_CLIENTS };
// ¿Hay backend activo y ya cargamos el estado inicial?
function apiOn(){ return !!(window.CP_API && CP_API.online && CP_API.ready); }
// Datos del taller por defecto (aparecen en los tickets). Editables en Configuración.
const DEFAULT_SHOP = { name:"Taller Casepoint", phone:"+503 2200-0000", address:"Col. Escalón, San Salvador", email:"contacto@casepoint.com" };
// Mapea el usuario que devuelve el servidor a la forma que usa la app.
function mapServerUser(u){ return { name:u.name, role:(ROLES[u.roleId]||{}).label||u.roleId, roleId:u.roleId, techId:u.id, username:u.username, initials:u.initials, hue:u.hue }; }
// Cachea en localStorage y, en modo online, sincroniza solo lo que cambió (upsert/remove por id).
function useSynced(name, state){
  const prev = React.useRef(undefined);
  React.useEffect(()=>{
    try{ localStorage.setItem(LS[name], JSON.stringify(state)); }catch(e){}
    const before = prev.current; prev.current = state;
    if(before === undefined) return;
    if(!apiOn()) return;
    const aMap = new Map(state.map(x=>[x.id, x]));
    const bMap = new Map((before||[]).map(x=>[x.id, x]));
    const upsert = state.filter(x=> JSON.stringify(x)!==JSON.stringify(bMap.get(x.id)));
    const remove = (before||[]).filter(x=>!aMap.has(x.id)).map(x=>x.id);
    if(upsert.length || remove.length) CP_API.sync(name, { upsert, remove });
  }, [state]);
}

// ===== Pantalla de fichaje obligatorio al ingresar =====
function CheckInGate({ user, onClockIn, onLogout }){
  const [now, setNow] = useState(Date.now());
  const [done, setDone] = useState(false);
  useEffect(()=>{ const id = setInterval(()=>setNow(Date.now()), 1000); return ()=>clearInterval(id); }, []);
  const t = new Date(now);
  const clockStr = t.toLocaleTimeString("es-ES",{ hour:"2-digit", minute:"2-digit", second:"2-digit" });
  const dateStr = t.toLocaleDateString("es-ES",{ weekday:"long", day:"numeric", month:"long" });
  const hour = t.getHours();
  const greet = hour<12 ? "Buenos días" : hour<19 ? "Buenas tardes" : "Buenas noches";

  function mark(){ setDone(true); setTimeout(onClockIn, 850); }

  return (
    <div style={{ minHeight:"100%", display:"flex", alignItems:"center", justifyContent:"center", padding:24,
      background:"linear-gradient(150deg, oklch(0.42 0.13 262) 0%, oklch(0.5 0.16 256) 55%, oklch(0.56 0.15 248) 100%)" }}>
      <div style={{ position:"absolute", inset:0, opacity:0.08,
        backgroundImage:"linear-gradient(#fff 1px, transparent 1px), linear-gradient(90deg, #fff 1px, transparent 1px)", backgroundSize:"46px 46px" }} />
      <div style={{ position:"relative", width:"100%", maxWidth:440, background:"var(--surface)", borderRadius:20, boxShadow:"var(--shadow-lg)", padding:"40px 38px", textAlign:"center", animation:"popIn .25s ease" }}>
        <div style={{ display:"flex", justifyContent:"center", marginBottom:20 }}><Logo /></div>

        <div style={{ display:"flex", alignItems:"center", justifyContent:"center", gap:11, marginBottom:6 }}>
          <Avatar name={user.name} initials={user.initials} hue={user.hue||256} size={46} />
          <div style={{ textAlign:"left", lineHeight:1.25 }}>
            <div style={{ fontSize:16, fontWeight:800, letterSpacing:"-0.01em" }}>{greet}, {user.name.split(" ")[0]}</div>
            <div style={{ fontSize:13, color:"var(--muted)" }}>{user.role}</div>
          </div>
        </div>

        <div className="mono" style={{ fontSize:46, fontWeight:800, letterSpacing:"-0.02em", marginTop:18, color:"var(--ink)" }}>{clockStr}</div>
        <div style={{ fontSize:13.5, color:"var(--muted)", textTransform:"capitalize", marginBottom:26 }}>{dateStr}</div>

        {!done ? (
          <>
            <div style={{ display:"flex", alignItems:"flex-start", gap:9, padding:"12px 14px", borderRadius:11, background:"var(--accent-soft)", textAlign:"left", marginBottom:18 }}>
              <Icon name="clock" size={18} style={{ color:"var(--accent-ink)", flexShrink:0, marginTop:1 }} />
              <div style={{ fontSize:13, color:"var(--ink-2)", lineHeight:1.5 }}>
                Para empezar tu jornada debes <strong>marcar tu entrada</strong>. Tu hora de ingreso quedará registrada en Asistencia.
              </div>
            </div>
            <Btn size="lg" icon="clock" style={{ width:"100%" }} onClick={mark}>Marcar entrada e ingresar</Btn>
            <button onClick={onLogout} style={{ marginTop:14, border:"none", background:"transparent", color:"var(--muted)", fontSize:13, fontWeight:600, cursor:"pointer" }}>
              No soy {user.name.split(" ")[0]} · Cerrar sesión
            </button>
          </>
        ) : (
          <div style={{ display:"flex", flexDirection:"column", alignItems:"center", gap:12, padding:"10px 0", animation:"fadeIn .2s ease" }}>
            <div style={{ width:56, height:56, borderRadius:"50%", background:"var(--st-listo-soft)", display:"flex", alignItems:"center", justifyContent:"center" }}>
              <Icon name="check" size={30} style={{ color:"var(--st-listo)" }} />
            </div>
            <div style={{ fontSize:15.5, fontWeight:700, color:"var(--st-listo)" }}>¡Entrada registrada!</div>
            <div style={{ fontSize:13, color:"var(--muted)" }}>Ingresando al sistema…</div>
          </div>
        )}
      </div>
    </div>
  );
}

function App(){
  const [user, setUser] = useState(()=>{
    const a = loadJSON(LS_AUTH, null);
    if(a && !a.roleId){
      const u = loadUsers().find(x => x.username === a.username);
      if(u) return { ...a, roleId:u.roleId, role:ROLES[u.roleId].label, initials:u.initials, hue:u.hue };
    }
    return a;
  });
  const [orders, setOrders] = useState(()=> loadJSON(LS_ORDERS, SEED_ORDERS));
  const [users, setUsers] = useState(()=> loadUsers());
  const [inventory, setInventory] = useState(()=> loadJSON(LS_INV, SEED_INVENTORY));
  const [expenses, setExpenses] = useState(()=> loadJSON(LS_EXP, SEED_EXPENSES));
  const [attendance, setAttendance] = useState(()=> loadJSON(LS_ATT, SEED_ATTENDANCE));
  const [notifications, setNotifications] = useState(()=> loadJSON(LS_NOTIF, SEED_NOTIFICATIONS));
  const [templates, setTemplates] = useState(()=> loadJSON(LS_TPL, NOTIF_TEMPLATES));
  const [sales, setSales] = useState(()=> loadJSON(LS_SALES, SEED_SALES));
  const [invoice, setInvoice] = useState(null);
  // Estado de red: checked=ya se probó el backend; online=hay backend. Reactivo para el Login.
  const [net, setNet] = useState({ checked:false, online:false });
  const [showProfile, setShowProfile] = useState(false);
  const [settings, setSettings] = useState(()=> loadJSON(LS_SETTINGS, { requireCheckIn: true, shop: DEFAULT_SHOP }));
  // clients are kept in the global CLIENTS array (source of truth for getClient);
  // this tick forces re-renders when the client DB changes, and loads any saved DB on first run.
  const [clientsTick, setClientsTick] = useState(()=>{
    const saved = loadJSON(LS_CLIENTS, null);
    if(Array.isArray(saved) && saved.length) CLIENTS.splice(0, CLIENTS.length, ...saved);
    return 0;
  });
  function persistClients(){ try{ localStorage.setItem(LS_CLIENTS, JSON.stringify(CLIENTS)); }catch(e){} }
  function saveClient(data){
    if(data.id){
      const idx = CLIENTS.findIndex(c=>c.id===data.id);
      if(idx>=0) CLIENTS[idx] = { ...CLIENTS[idx], ...data };
      persistClients(); setClientsTick(t=>t+1);
      if(apiOn() && idx>=0) CP_API.sync("clients", { upsert:[CLIENTS[idx]] });
      return CLIENTS[idx];
    }
    const client = { id:"c_"+Date.now(), name:(data.name||"").trim()||"Cliente de mostrador",
      cedula:(data.cedula||"").trim(), phone:(data.phone||"").trim(), email:(data.email||"").trim(), orders:0 };
    CLIENTS.unshift(client);
    persistClients(); setClientsTick(t=>t+1);
    if(apiOn()) CP_API.sync("clients", { upsert:[client] });
    return client;
  }
  const [route, setRoute] = useState(()=> loadJSON(LS_ROUTE, "dashboard"));
  const [openId, setOpenId] = useState(null);
  const [query, setQuery] = useState("");
  const [toast, setToast] = useState(null);

  // ===== Persistencia: cache local + sincronización con el servidor (modo online) =====
  useSynced("orders", orders);
  useSynced("users", users);
  useSynced("inventory", inventory);
  useSynced("expenses", expenses);
  useSynced("attendance", attendance);
  useSynced("notifications", notifications);
  useSynced("templates", templates);
  useSynced("sales", sales);
  useEffect(()=>{ try{ localStorage.setItem(LS_ROUTE, JSON.stringify(route)); }catch(e){} }, [route]);
  useEffect(()=>{ if(user) try{ localStorage.setItem(LS_AUTH, JSON.stringify(user)); }catch(e){} }, [user]);
  useEffect(()=>{ try{ localStorage.setItem(LS_SETTINGS, JSON.stringify(settings)); }catch(e){} window.CP_SHOP = settings.shop || DEFAULT_SHOP; if(apiOn()) CP_API.sync("settings", { settings }); }, [settings]);

  // Trae todo el estado del servidor y lo vuelca en la app. ready=false mientras
  // se reemplaza para que useSynced no reenvíe esos datos como si fueran cambios.
  async function loadAll(opts){
    const data = await CP_API.bootstrap(opts);
    if(!data) return;
    CP_API.ready = false;
    if(Array.isArray(data.orders)) setOrders(data.orders);
    if(Array.isArray(data.inventory)) setInventory(data.inventory);
    if(Array.isArray(data.expenses)) setExpenses(data.expenses);
    if(Array.isArray(data.attendance)) setAttendance(data.attendance);
    if(Array.isArray(data.notifications)) setNotifications(data.notifications);
    if(Array.isArray(data.templates)) setTemplates(data.templates);
    if(Array.isArray(data.sales)) setSales(data.sales);
    if(Array.isArray(data.users)) setUsers(data.users);
    if(Array.isArray(data.clients)){ CLIENTS.splice(0, CLIENTS.length, ...data.clients); setClientsTick(t=>t+1); }
    if(data.settings) setSettings(data.settings);
    setTimeout(()=>{ CP_API.ready = true; }, 0);
  }

  // Al arrancar: detecta backend; si hay sesión, restaura usuario y carga datos.
  useEffect(()=>{
    let stop = false;
    (async()=>{
      const ok = await CP_API.detect();
      setNet({ checked:true, online: ok });
      if(!ok) return;
      const me = await CP_API.me();
      if(stop) return;
      if(me){ setUser(mapServerUser(me)); await loadAll(); }
      else { setUser(null); }
    })();
    return ()=>{ stop = true; };
  }, []);

  // Polling: refresca los cambios de otros empleados cada 12s (solo pestaña visible).
  useEffect(()=>{
    if(!user || !window.CP_API || !CP_API.online) return;
    const id = setInterval(()=>{ if(document.visibilityState==="visible") loadAll({ skip:"clients" }); }, 12000);
    return ()=>clearInterval(id);
  }, [user]);

  function login(u){ setUser(u); setRoute("dashboard"); if(window.CP_API && CP_API.online) loadAll(); }
  function logout(){ localStorage.removeItem(LS_AUTH); if(window.CP_API && CP_API.online) CP_API.logout(); setUser(null); }

  function nav(r){ setQuery(""); if(r==="new"){ setRoute("new"); } else { setRoute(r); } window.scrollTo(0,0); }
  function openOrder(id){ setOpenId(id); setRoute("detail"); window.scrollTo(0,0); }

  function showToast(msg, tone="ok"){ setToast({ msg, tone }); setTimeout(()=>setToast(null), 3200); }

  function createOrder(o){
    setOrders(prev => [o, ...prev]);
    setOpenId(o.id); setRoute("detail");
    showToast(`Orden #${o.num} creada — ticket enviado a impresión`);
    window.scrollTo(0,0);
  }
  function updateOrder(o){
    const prevOrder = orders.find(x=>x.id===o.id);
    setOrders(prev => prev.map(x => x.id===o.id ? o : x));
    // auto-notify on status change if a template is set to auto
    if(prevOrder && o.status && o.status!==prevOrder.status){
      const tpl = templates.find(t => t.trigger===o.status && t.auto);
      if(tpl) sendAutoNotif(o, tpl);
    }
  }
  function sendAutoNotif(o, tpl){
    const cl = getClient(o.clientId); const dev = getDevice(o.device);
    const ctx = { cliente: cl.name.split(" ")[0], equipo:`${o.brand} ${o.model}`, num:o.num, presupuesto:fmtMoney(o.estimate), saldo:fmtMoney(orderBalance(o)) };
    const text = fillTemplate(tpl.body, ctx);
    setNotifications(prev => [{ id:"n_"+Date.now(), orderNum:o.num, clientName:cl.name, channel:tpl.channel, template:tpl.id, t:new Date().toISOString(), status:"enviado", preview:text.slice(0,90)+(text.length>90?"…":"") }, ...prev]);
    const chName = tpl.channel==="both"?"Email + WhatsApp":tpl.channel==="email"?"Email":"WhatsApp";
    showToast(`Aviso automático enviado a ${cl.name.split(" ")[0]} (${chName})`);
  }
  function sendNotif(n){
    setNotifications(prev => [n, ...prev]);
    const chName = n.channel==="both"?"Email + WhatsApp":n.channel==="email"?"Email":"WhatsApp";
    showToast(`Mensaje enviado a ${n.clientName.split(" ")[0]} (${chName})`);
  }
  // attendance quick punch from topbar
  const myShift = attendance.find(r => user && r.userId===user.techId && !r.out);
  function checkoutSale(sale){
    setInventory(prev => prev.map(it => {
      const line = sale.items.find(x=>x.sku===it.sku);
      return line ? { ...it, stock: Math.max(0, it.stock - line.qty) } : it;
    }));
    setSales(prev => [sale, ...prev]);
    setInvoice(sale);
    showToast(`Venta #${sale.num} cobrada — ${fmtMoney(sale.total)}`);
  }
  function restoreStockFor(sale){
    setInventory(prev => prev.map(it => {
      const line = sale.items.find(x=>x.sku===it.sku);
      return line ? { ...it, stock: it.stock + line.qty } : it;
    }));
  }
  function voidSale(saleId){
    const sale = sales.find(s=>s.id===saleId);
    if(!sale || sale.voided) return;
    restoreStockFor(sale);
    setSales(prev => prev.map(s => s.id===saleId ? { ...s, voided:true, voidedBy:user.name, voidedAt:new Date().toISOString() } : s));
    showToast(`Venta #${sale.num} anulada — stock devuelto`);
  }
  function deleteSale(saleId){
    const sale = sales.find(s=>s.id===saleId);
    if(!sale) return;
    if(!sale.voided) restoreStockFor(sale); // restore stock if it wasn't already voided
    setSales(prev => prev.filter(s => s.id!==saleId));
    showToast(`Venta #${sale.num} eliminada`);
  }
  function updateSaleClient(saleId, cl){
    let client = cl;
    if(client && !client.id && (client.name || client.cedula || client.phone || client.email)){
      client = saveClient(client);
    }
    setSales(prev => prev.map(s => s.id===saleId ? {
      ...s,
      clientId: client?.id || null,
      clientName: client?.name || "Cliente de mostrador",
      clientCedula: client?.cedula || "", clientPhone: client?.phone || "", clientEmail: client?.email || "",
    } : s));
    showToast("Cliente de la venta actualizado");
  }
  function punch(){
    if(!user) return;
    if(myShift){
      setAttendance(prev => prev.map(r => (r.userId===user.techId && !r.out) ? { ...r, out:new Date().toISOString() } : r));
      showToast("Salida registrada");
    } else {
      setAttendance(prev => [{ id:"at_"+Date.now(), userId:user.techId, in:new Date().toISOString(), out:null }, ...prev]);
      showToast("Entrada registrada");
    }
  }
  function consumePart(invId, qty){
    setInventory(prev => prev.map(it => it.id===invId ? { ...it, stock: Math.max(0, it.stock - qty) } : it));
  }
  function returnPart(invId, qty){
    setInventory(prev => prev.map(it => it.id===invId ? { ...it, stock: it.stock + qty } : it));
  }
  function restock(item){
    const qty = item.min*2 || 5;
    setInventory(prev => prev.map(it => it.id===item.id ? { ...it, stock: it.stock + qty } : it));
    setExpenses(prev => [{ id:"e_"+Date.now(), date:new Date().toISOString(), cat:"repuestos", concept:`Reabastecimiento: ${qty}× ${item.name}`, supplier:item.supplier, amount:+(item.cost*qty).toFixed(2) }, ...prev]);
    showToast(`Compra registrada: ${qty}× ${item.name} — ${fmtMoney(item.cost*qty)}`);
  }

  if(!user) return <Login onLogin={login} showDemo={net.checked && !net.online} />;

  // Fichaje obligatorio: si está activado y el usuario no ha marcado entrada hoy, bloquear hasta fichar.
  const checkedInToday = attendance.some(r => r.userId===user.techId && sameDay(r.in));
  function clockInNow(){
    setAttendance(prev => [{ id:"at_"+Date.now(), userId:user.techId, in:new Date().toISOString(), out:null }, ...prev]);
    showToast("¡Entrada registrada! Buen turno.");
  }
  if(settings.requireCheckIn && !checkedInToday){
    return <CheckInGate user={user} onClockIn={clockInNow} onLogout={logout} />;
  }

  const current = orders.find(o => o.id === openId);

  return (
    <div style={{ display:"flex", height:"100%", overflow:"hidden" }}>
      <Sidebar route={route} onNav={nav} orders={orders} user={user} />
      <div style={{ flex:1, display:"flex", flexDirection:"column", minWidth:0 }}>
        <Topbar route={route} user={user} onLogout={logout} onNav={nav} query={query} setQuery={setQuery} myShift={myShift} onPunch={punch} onProfile={()=>setShowProfile(true)} />
        {showProfile && <ProfileModal user={user} users={users} setUsers={setUsers} onClose={()=>setShowProfile(false)} />}
        <main style={{ flex:1, overflowY:"auto", padding:"24px 28px 40px" }}>
          <div style={{ maxWidth:1280, margin:"0 auto" }}>
            {route==="dashboard" && <Dashboard orders={orders} user={user} onOpen={openOrder} onNav={nav} />}
            {route==="orders" && <Orders orders={orders} query={query} setQuery={setQuery} onOpen={openOrder} onNav={nav} />}
            {route==="new" && <NewOrder orders={orders} user={user} onCreate={createOrder} onCancel={()=>nav("orders")} />}
            {route==="detail" && current && <OrderDetail order={current} user={user} onBack={()=>nav("orders")} onUpdate={updateOrder} inventory={inventory} onConsumePart={consumePart} onReturnPart={returnPart} templates={templates} onNotify={sendNotif} />}
            {route==="clientes" && <ClientsView orders={orders} onNav={nav} tick={clientsTick} onSaveClient={saveClient} />}
            {route==="equipos" && <EquiposView orders={orders} onOpen={openOrder} />}
            {route==="inventario" && <Inventario inventory={inventory} setInventory={setInventory} currentUser={user} onRestock={restock} />}
            {route==="finanzas" && <Finanzas orders={orders} expenses={expenses} setExpenses={setExpenses} sales={sales} inventory={inventory} attendance={attendance} users={users} currentUser={user} />}
            {route==="pos" && <POS inventory={inventory} sales={sales} onCheckout={checkoutSale} onSaveClient={saveClient} clientsTick={clientsTick} currentUser={user} onVoidSale={voidSale} onDeleteSale={deleteSale} onUpdateSaleClient={updateSaleClient} onViewInvoice={setInvoice} />}
            {route==="asistencia" && <Asistencia attendance={attendance} setAttendance={setAttendance} currentUser={user} users={users} />}
            {route==="notif" && <Notificaciones notifications={notifications} templates={templates} setTemplates={setTemplates} orders={orders} />}
            {route==="stats" && <StatsView orders={orders} />}
            {route==="config" && <ConfigView user={user} users={users} setUsers={setUsers} settings={settings} setSettings={setSettings} />}
          </div>
        </main>
      </div>

      {/* toast */}
      {invoice && <InvoiceModal sale={invoice} onClose={()=>setInvoice(null)} />}
      {toast && (
        <div style={{ position:"fixed", bottom:24, left:"50%", transform:"translateX(-50%)", zIndex:80,
          display:"flex", alignItems:"center", gap:10, padding:"13px 18px", borderRadius:12,
          background:"var(--ink)", color:"#fff", fontSize:14, fontWeight:600, boxShadow:"var(--shadow-lg)", animation:"popIn .2s ease" }}>
          <span style={{ width:20, height:20, borderRadius:"50%", background:"var(--st-listo)", display:"flex", alignItems:"center", justifyContent:"center" }}><Icon name="check" size={14} /></span>
          {toast.msg}
        </div>
      )}
    </div>
  );
}

// ===== Clientes =====
function ClientsView({ orders, onNav, tick, onSaveClient }){
  const [modal, setModal] = useState(false);
  const [q, setQ] = useState("");
  const CAP = 60;
  const query = q.trim().toLowerCase();
  const filtered = query
    ? CLIENTS.filter(c =>
        (c.name||"").toLowerCase().includes(query) ||
        (c.phone||"").toLowerCase().includes(query) ||
        (c.cedula||"").toLowerCase().includes(query) ||
        (c.email||"").toLowerCase().includes(query))
    : CLIENTS;
  const shown = filtered.slice(0, CAP);
  return (
    <div style={{ animation:"fadeUp .35s ease" }}>
      <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:16, gap:14, flexWrap:"wrap" }}>
        <div style={{ fontSize:13.5, color:"var(--muted)" }}><strong style={{ color:"var(--ink)" }}>{CLIENTS.length.toLocaleString("es")}</strong> clientes en tu base de datos</div>
        <div style={{ display:"flex", alignItems:"center", gap:10 }}>
          <div style={{ position:"relative" }}>
            <span style={{ position:"absolute", left:11, top:"50%", transform:"translateY(-50%)", color:"var(--faint)", pointerEvents:"none" }}><Icon name="search" size={16} /></span>
            <TextInput value={q} onChange={e=>setQ(e.target.value)} placeholder="Buscar por nombre, teléfono, cédula…" style={{ paddingLeft:34, width:300 }} />
          </div>
          <Btn icon="plus" size="sm" onClick={()=>setModal(true)}>Nuevo cliente</Btn>
        </div>
      </div>
      {filtered.length>CAP && (
        <div style={{ fontSize:12.5, color:"var(--muted)", marginBottom:14 }}>
          Mostrando {CAP} de {filtered.length.toLocaleString("es")} {query?"resultados":"clientes"} — {query?"refina tu búsqueda":"usa el buscador"} para ver más.
        </div>
      )}
      {filtered.length===0 && (
        <div style={{ padding:"40px 20px", textAlign:"center", color:"var(--muted)", fontSize:14 }}>Sin clientes que coincidan con “{q}”.</div>
      )}
      <div style={{ display:"grid", gridTemplateColumns:"repeat(auto-fill, minmax(280px, 1fr))", gap:16 }}>
        {shown.map(c=>{
          const co = orders.filter(o=>o.clientId===c.id);
          const open = co.filter(o=>STATUSES[o.status].stage!=="salida").length;
          return (
            <Card key={c.id} style={{ display:"flex", flexDirection:"column", gap:13 }}>
              <div style={{ display:"flex", alignItems:"center", gap:13 }}>
                <Avatar name={c.name} hue={256} size={46} />
                <div style={{ minWidth:0 }}>
                  <div style={{ fontSize:15.5, fontWeight:700, whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis" }}>{c.name}</div>
                  <div style={{ fontSize:12.5, color:"var(--muted)" }} className="mono">{c.phone || "sin teléfono"}</div>
                </div>
              </div>
              <div style={{ display:"flex", flexDirection:"column", gap:5, fontSize:12.5 }}>
                <div style={{ display:"flex", alignItems:"center", gap:8, color:"var(--ink-2)" }}><Icon name="doc" size={14} style={{ color:"var(--faint)" }} /><span className="mono">{c.cedula || "—"}</span></div>
                <div style={{ display:"flex", alignItems:"center", gap:8, color:"var(--ink-2)" }}><Icon name="sms" size={14} style={{ color:"var(--faint)" }} /><span style={{ overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap" }}>{c.email || "—"}</span></div>
              </div>
              <div style={{ display:"flex", gap:18, paddingTop:12, borderTop:"1px solid var(--line-2)" }}>
                <div><div style={{ fontSize:20, fontWeight:800 }} className="mono">{c.orders}</div><div style={{ fontSize:12, color:"var(--muted)" }}>órdenes</div></div>
                <div><div style={{ fontSize:20, fontWeight:800, color: open?"var(--accent-ink)":"var(--ink)" }} className="mono">{open}</div><div style={{ fontSize:12, color:"var(--muted)" }}>activas</div></div>
              </div>
            </Card>
          );
        })}
      </div>
      {modal && <POSClientModal startMode="nuevo" onPick={(c)=>{ onSaveClient(c); setModal(false); }} onClose={()=>setModal(false)} />}
    </div>
  );
}

// ===== Equipos (historial) =====
function EquiposView({ orders, onOpen }){
  const sorted = [...orders].sort((a,b)=> new Date(b.createdAt)-new Date(a.createdAt));
  return (
    <Card pad={0} style={{ animation:"fadeUp .35s ease" }}>
      {sorted.map((o,i)=>{
        const c = getClient(o.clientId);
        return (
          <button key={o.id} onClick={()=>onOpen(o.id)} style={{ display:"flex", alignItems:"center", gap:14, width:"100%", padding:"14px 20px",
            border:"none", borderBottom: i<sorted.length-1?"1px solid var(--line-2)":"none", background:"transparent", textAlign:"left" }}
            onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"} onMouseLeave={e=>e.currentTarget.style.background="transparent"}>
            <DeviceGlyph type={o.device} size={42} />
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ fontSize:14.5, fontWeight:700 }}>{o.brand} {o.model}</div>
              <div style={{ fontSize:12.5, color:"var(--muted)" }}>{c.name} · <span className="mono">{o.serial||"sin serie"}</span></div>
            </div>
            <span className="mono" style={{ fontSize:12.5, color:"var(--faint)" }}>#{o.num}</span>
            <StatusBadge status={o.status} size="sm" />
            <span style={{ fontSize:12.5, color:"var(--muted)", width:80, textAlign:"right" }}>{fmtDate(o.createdAt)}</span>
          </button>
        );
      })}
    </Card>
  );
}

// ===== Stats =====
function StatsView({ orders }){
  const byDevice = DEVICE_TYPES.map(d=>({ ...d, n: orders.filter(o=>o.device===d.id).length })).filter(d=>d.n>0).sort((a,b)=>b.n-a.n);
  const maxD = Math.max(...byDevice.map(d=>d.n),1);
  const total = orders.length;
  const ingresos = orders.reduce((s,o)=>s+orderPaid(o),0);
  const entregadas = orders.filter(o=>o.status==="entregado").length;
  const tickets = [
    { l:"Órdenes totales", v:total, hue:256 },
    { l:"Entregadas", v:entregadas, hue:155 },
    { l:"Ingresos cobrados", v:fmtMoney(ingresos), hue:70, mono:true },
    { l:"Ticket promedio", v:fmtMoney(orders.length?ingresos/Math.max(entregadas,1):0), hue:320, mono:true },
  ];
  return (
    <div style={{ display:"flex", flexDirection:"column", gap:16, animation:"fadeUp .35s ease" }}>
      <div style={{ display:"grid", gridTemplateColumns:"repeat(4,1fr)", gap:16 }}>
        {tickets.map(t=>(
          <Card key={t.l} pad={18}>
            <div style={{ width:36, height:36, borderRadius:10, display:"flex", alignItems:"center", justifyContent:"center", background:`oklch(0.95 0.04 ${t.hue})`, color:`oklch(0.5 0.13 ${t.hue})` }}><Icon name="chart" size={19} /></div>
            <div style={{ fontSize:28, fontWeight:800, marginTop:13 }} className={t.mono?"mono":""}>{t.v}</div>
            <div style={{ fontSize:13, color:"var(--muted)", fontWeight:600 }}>{t.l}</div>
          </Card>
        ))}
      </div>
      <Card>
        <h3 style={{ fontSize:15.5, fontWeight:700, margin:"0 0 18px" }}>Órdenes por tipo de dispositivo</h3>
        <div style={{ display:"flex", flexDirection:"column", gap:14 }}>
          {byDevice.map(d=>(
            <div key={d.id} style={{ display:"flex", alignItems:"center", gap:13 }}>
              <span style={{ display:"flex", alignItems:"center", gap:9, width:170, color:"var(--ink-2)" }}><Icon name={d.icon} size={19} /><span style={{ fontSize:13.5, fontWeight:600 }}>{d.label}</span></span>
              <div style={{ flex:1, height:26, borderRadius:7, background:"var(--surface-2)", overflow:"hidden" }}>
                <div style={{ width:`${(d.n/maxD)*100}%`, height:"100%", borderRadius:7, background:"linear-gradient(90deg, var(--accent), oklch(0.62 0.15 248))", display:"flex", alignItems:"center", justifyContent:"flex-end", paddingRight:9 }}>
                  <span style={{ fontSize:12, fontWeight:700, color:"#fff" }} className="mono">{d.n}</span>
                </div>
              </div>
            </div>
          ))}
        </div>
      </Card>
    </div>
  );
}

// ===== Mi perfil: cambio de contraseña de autoservicio (todos los usuarios) =====
function ProfileModal({ user, users, setUsers, onClose }){
  const [cur, setCur] = useState("");
  const [next, setNext] = useState("");
  const [conf, setConf] = useState("");
  const [err, setErr] = useState("");
  const [ok, setOk] = useState(false);
  const [busy, setBusy] = useState(false);
  const online = !!(window.CP_API && CP_API.online);

  async function save(){
    setErr(""); setOk(false);
    if(!cur){ setErr("Ingresa tu contraseña actual."); return; }
    if(next.trim().length < 4){ setErr("La nueva contraseña debe tener al menos 4 caracteres."); return; }
    if(next !== conf){ setErr("Las contraseñas nuevas no coinciden."); return; }
    setBusy(true);
    if(online){
      try{ await CP_API.changePassword(cur, next); }
      catch(e){ setBusy(false); setErr(String(e.message)==="bad_current" ? "La contraseña actual es incorrecta." : "No se pudo cambiar la contraseña."); return; }
    } else {
      const me = users.find(u=>u.username===user.username);
      if(!me || me.pass !== cur){ setBusy(false); setErr("La contraseña actual es incorrecta."); return; }
      setUsers(prev=>prev.map(u=>u.username===user.username?{ ...u, pass:next }:u));
    }
    setBusy(false); setOk(true); setCur(""); setNext(""); setConf("");
    setTimeout(onClose, 1100);
  }

  return (
    <div onClick={onClose} style={{ position:"fixed", inset:0, zIndex:90, background:"oklch(0.2 0.03 262 / 0.42)", display:"flex", alignItems:"center", justifyContent:"center", padding:24, animation:"fadeIn .15s ease" }}>
      <div onClick={e=>e.stopPropagation()} style={{ width:"100%", maxWidth:430, background:"var(--surface)", borderRadius:16, boxShadow:"var(--shadow-lg)", animation:"popIn .18s ease", overflow:"hidden" }}>
        <div style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"18px 22px", borderBottom:"1px solid var(--line)" }}>
          <h3 style={{ fontSize:17, fontWeight:800, margin:0 }}>Mi perfil</h3>
          <IconBtn icon="x" size={18} onClick={onClose} />
        </div>

        <div style={{ padding:"20px 22px", display:"flex", flexDirection:"column", gap:15 }}>
          <div style={{ display:"flex", alignItems:"center", gap:13 }}>
            <Avatar name={user.name} initials={user.initials} hue={user.hue||256} size={48} />
            <div>
              <div style={{ fontSize:15.5, fontWeight:700 }}>{user.name}</div>
              <div style={{ fontSize:12.5, color:"var(--muted)" }}><span className="mono">@{user.username}</span> · {user.role}</div>
            </div>
          </div>

          <div style={{ height:1, background:"var(--line)" }} />
          <div style={{ fontSize:13.5, fontWeight:700 }}>Cambiar contraseña</div>

          <Field label="Contraseña actual">
            <TextInput type="password" value={cur} onChange={e=>setCur(e.target.value)} placeholder="••••••••" autoComplete="current-password" />
          </Field>
          <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:14 }}>
            <Field label="Nueva contraseña">
              <TextInput type="password" value={next} onChange={e=>setNext(e.target.value)} placeholder="Mín. 4 caracteres" autoComplete="new-password" />
            </Field>
            <Field label="Confirmar">
              <TextInput type="password" value={conf} onChange={e=>setConf(e.target.value)} placeholder="Repite la nueva" autoComplete="new-password" />
            </Field>
          </div>

          {err && <div style={{ fontSize:13, fontWeight:600, color:"var(--danger)" }}>{err}</div>}
          {ok && <div style={{ fontSize:13, fontWeight:600, color:"var(--st-listo)" }}>Contraseña actualizada ✓</div>}
        </div>

        <div style={{ display:"flex", alignItems:"center", gap:10, padding:"16px 22px", borderTop:"1px solid var(--line)", justifyContent:"flex-end" }}>
          <Btn variant="secondary" onClick={onClose}>Cancelar</Btn>
          <Btn icon="check" onClick={save} disabled={busy}>{busy?"Guardando…":"Cambiar contraseña"}</Btn>
        </div>
      </div>
    </div>
  );
}

// ===== Config (tabbed) =====
function ShopSettingsCard({ settings, setSettings }){
  const base = settings.shop || DEFAULT_SHOP;
  const [draft, setDraft] = useState(base);
  const [saved, setSaved] = useState(false);
  useEffect(()=>{ setDraft(settings.shop || DEFAULT_SHOP); }, [settings.shop]);
  const dirty = JSON.stringify(draft) !== JSON.stringify(base);
  function set(k, v){ setDraft(d=>({ ...d, [k]:v })); setSaved(false); }
  function save(){ setSettings(s=>({ ...s, shop: { ...DEFAULT_SHOP, ...draft } })); setSaved(true); setTimeout(()=>setSaved(false), 2600); }
  return (
    <Card>
      <h3 style={{ fontSize:15.5, fontWeight:700, margin:"0 0 4px" }}>Datos del taller</h3>
      <p style={{ fontSize:13.5, color:"var(--muted)", margin:"0 0 18px" }}>Información que aparece en los tickets de ingreso y entrega. Se aplica para todo el equipo.</p>
      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:14 }}>
        <Field label="Nombre del taller"><TextInput value={draft.name||""} onChange={e=>set("name", e.target.value)} /></Field>
        <Field label="Teléfono"><TextInput value={draft.phone||""} onChange={e=>set("phone", e.target.value)} style={{ fontFamily:"var(--mono)" }} /></Field>
        <Field label="Dirección" style={{ gridColumn:"1 / -1" }}><TextInput value={draft.address||""} onChange={e=>set("address", e.target.value)} /></Field>
        <Field label="Correo / contacto" style={{ gridColumn:"1 / -1" }}><TextInput value={draft.email||""} onChange={e=>set("email", e.target.value)} /></Field>
      </div>
      <div style={{ display:"flex", alignItems:"center", gap:12, marginTop:16 }}>
        <Btn variant="primary" size="sm" icon="check" onClick={save} disabled={!dirty}>Guardar cambios</Btn>
        {saved && <span style={{ fontSize:13, fontWeight:600, color:"var(--st-listo)" }}>Guardado ✓</span>}
      </div>
    </Card>
  );
}

function ConfigView({ user, users, setUsers, settings, setSettings }){
  const [tab, setTab] = useState("general");
  const canUsers = ROLES[user.roleId]?.perms.includes("users");
  const tabs = [
    { id:"general", label:"General", icon:"settings" },
    { id:"usuarios", label:"Usuarios y roles", icon:"clients" },
    { id:"notif", label:"Notificaciones", icon:"sms" },
  ];
  return (
    <div style={{ animation:"fadeUp .35s ease" }}>
      <div style={{ display:"flex", gap:4, marginBottom:20, borderBottom:"1px solid var(--line)" }}>
        {tabs.map(t=>{
          const active = tab===t.id;
          return (
            <button key={t.id} onClick={()=>setTab(t.id)}
              style={{ display:"flex", alignItems:"center", gap:8, padding:"11px 16px", border:"none", background:"transparent",
                fontSize:14, fontWeight: active?700:600, color: active?"var(--accent-ink)":"var(--muted)", position:"relative" }}>
              <Icon name={t.icon} size={17} /> {t.label}
              {t.id==="usuarios" && <span style={{ minWidth:19, height:19, padding:"0 5px", borderRadius:99, fontSize:11, fontWeight:700, display:"flex", alignItems:"center", justifyContent:"center", background: active?"var(--accent-soft)":"var(--surface-2)", color: active?"var(--accent-ink)":"var(--muted)" }}>{users.length}</span>}
              {active && <span style={{ position:"absolute", left:8, right:8, bottom:-1, height:3, borderRadius:99, background:"var(--accent)" }} />}
            </button>
          );
        })}
      </div>

      {tab==="general" && (
        <div style={{ maxWidth:680, display:"flex", flexDirection:"column", gap:16 }}>
          <ShopSettingsCard settings={settings} setSettings={setSettings} />
          <Card>
            <div style={{ display:"flex", alignItems:"flex-start", gap:12 }}>
              <span style={{ width:38, height:38, borderRadius:10, flexShrink:0, display:"flex", alignItems:"center", justifyContent:"center", background:"var(--accent-soft)", color:"var(--accent-ink)" }}><Icon name="clock" size={20} /></span>
              <div style={{ flex:1 }}>
                <h3 style={{ fontSize:15.5, fontWeight:700, margin:"0 0 4px" }}>Asistencia obligatoria al ingresar</h3>
                <p style={{ fontSize:13.5, color:"var(--muted)", margin:"0 0 14px", lineHeight:1.5 }}>
                  Si está activo, cada empleado debe <strong>marcar su entrada</strong> al iniciar sesión antes de poder usar el sistema (una vez al día). Así registras la asistencia matutina automáticamente.
                </p>
                <label style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"12px 14px", borderRadius:10, background:"var(--surface-2)", border:"1px solid var(--line)" }}>
                  <span style={{ fontSize:13.5, fontWeight:600, color:"var(--ink-2)" }}>{settings.requireCheckIn ? "Activado — se exige fichar entrada" : "Desactivado — fichaje opcional"}</span>
                  <button type="button" onClick={()=>setSettings(s=>({ ...s, requireCheckIn: !s.requireCheckIn }))}
                    style={{ width:46, height:26, borderRadius:99, border:"none", padding:2, cursor:"pointer",
                      background: settings.requireCheckIn?"var(--accent)":"var(--line)", transition:"background .2s", display:"flex", justifyContent: settings.requireCheckIn?"flex-end":"flex-start" }}>
                    <span style={{ width:22, height:22, borderRadius:"50%", background:"#fff", boxShadow:"var(--shadow-sm)" }} />
                  </button>
                </label>
              </div>
            </div>
          </Card>
        </div>
      )}

      {tab==="usuarios" && <UsersAndRoles users={users} setUsers={setUsers} currentUser={user} />}

      {tab==="notif" && (
        <div style={{ maxWidth:680, display:"flex", flexDirection:"column", gap:16 }}>
          <Card>
            <h3 style={{ fontSize:15.5, fontWeight:700, margin:"0 0 14px" }}>Notificaciones</h3>
            {[["SMS al recibir el equipo",true],["WhatsApp cuando esté listo",true],["Recordatorio de retiro a los 7 días",false]].map(([l,on])=>(
              <label key={l} style={{ display:"flex", alignItems:"center", justifyContent:"space-between", padding:"11px 0", borderBottom:"1px solid var(--line-2)" }}>
                <span style={{ fontSize:14, fontWeight:500, color:"var(--ink-2)" }}>{l}</span>
                <Toggle defaultOn={on} />
              </label>
            ))}
          </Card>
          <Card>
            <h3 style={{ fontSize:15.5, fontWeight:700, margin:"0 0 14px" }}>Tu sesión</h3>
            <div style={{ display:"flex", alignItems:"center", gap:12 }}>
              <Avatar name={user.name} initials={user.initials} hue={user.hue||256} size={42} />
              <div><div style={{ fontSize:14.5, fontWeight:700 }}>{user.name}</div><div style={{ fontSize:12.5, color:"var(--muted)" }}>{user.role} · @{user.username}</div></div>
            </div>
          </Card>
        </div>
      )}
    </div>
  );
}

function Toggle({ defaultOn }){
  const [on, setOn] = useState(defaultOn);
  return (
    <button onClick={()=>setOn(o=>!o)} style={{ width:42, height:24, borderRadius:99, border:"none", padding:2, cursor:"pointer",
      background: on?"var(--accent)":"var(--line)", transition:"background .2s", display:"flex", justifyContent: on?"flex-end":"flex-start" }}>
      <span style={{ width:20, height:20, borderRadius:"50%", background:"#fff", boxShadow:"var(--shadow-sm)", transition:"all .2s" }} />
    </button>
  );
}

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