
/* ===== js-ext/components.jsx ===== */

// ─── Launcher buttons component ─────────────────────────────────────────────
// Usage: <Launchers user={user} /> or <Launchers ip="192.168.1.1" />

function Launchers({ user, company, compact=false }) {
  const items = [];
  if (user?.anydesk) items.push({ kind:'anydesk', target: user.anydesk, label: 'AnyDesk', color:'#e21221', icon:'link' });
  if (user?.rustdesk) items.push({ kind:'rustdesk', target: user.rustdesk, label: 'Infinitos Remote', color:'#2A6CF6', icon:'monitor' });
  if (user?.equipment?.ip) items.push({ kind:'rdp', target: user.equipment.ip, label: 'RDP', color:'#0078d4', icon:'monitor' });
  if (user?.equipment?.ip) items.push({ kind:'ssh', target: user.equipment.ip, label: 'SSH', color:'#111', icon:'cpu' });
  if (user?.email) items.push({ kind:'mailto', target: user.email, label: 'Email', color:'#ea4335', icon:'copy' });
  if (user?.phone || user?.mobile) items.push({ kind:'whatsapp', target: user.phone || user.mobile, label: 'WhatsApp', color:'#25d366', icon:'alert' });
  if (company?.network?.publicIp) items.push({ kind:'https', target: company.network.publicIp, label: 'Router', color:'#5856d6', icon:'wifi' });

  if (items.length === 0) return null;

  return (
    <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
      {items.map(it => (
        <button key={it.kind+it.target}
          onClick={(e)=>{e.stopPropagation(); window.Features.launch(it.kind, it.target);}}
          title={`${it.label}: ${it.target}`}
          style={{
            display:'inline-flex', alignItems:'center', gap:6,
            padding: compact ? '5px 10px' : '7px 12px',
            background: it.color, color:'#fff',
            border:'none', borderRadius:999, cursor:'pointer',
            fontSize: compact ? 11 : 12, fontWeight:600,
            boxShadow:'0 1px 3px rgba(0,0,0,0.15)',
            transition:'transform 0.1s ease, filter 0.1s ease'
          }}
          onMouseEnter={e=>{e.currentTarget.style.transform='scale(1.05)';e.currentTarget.style.filter='brightness(1.1)';}}
          onMouseLeave={e=>{e.currentTarget.style.transform='scale(1)';e.currentTarget.style.filter='none';}}>
          <Icon name={it.icon} size={compact?11:13} color="#fff"/>
          {it.label}
        </button>
      ))}
    </div>
  );
}

window.Launchers = Launchers;

// ─── Warranty / Expiry alerts banner ────────────────────────────────────────
function ExpiryBanner({ data, navigate }) {
  const alerts = window.Features.checkExpiries(data);
  if (alerts.length === 0) return null;
  const expired = alerts.filter(a => a.severity === 'expired');
  const warn = alerts.filter(a => a.severity === 'warn');
  const [open, setOpen] = React.useState(false);

  return (
    <div style={{margin:'16px 32px 0'}}>
      <div onClick={()=>setOpen(!open)}
        style={{padding:'12px 16px', borderRadius:14,
          background: expired.length>0 ? 'rgba(255,59,48,0.1)' : 'rgba(255,149,0,0.1)',
          border: `1px solid ${expired.length>0 ? 'rgba(255,59,48,0.3)' : 'rgba(255,149,0,0.3)'}`,
          display:'flex', alignItems:'center', gap:12, cursor:'pointer'}}>
        <Icon name="alert" size={18} color={expired.length>0?'#ff3b30':'#ff9500'}/>
        <div style={{flex:1, fontSize:13, fontWeight:500}}>
          {expired.length > 0 && <span style={{color:'#ff3b30', fontWeight:700}}>{expired.length} vencido{expired.length!==1?'s':''}</span>}
          {expired.length > 0 && warn.length > 0 && ' · '}
          {warn.length > 0 && <span style={{color:'#ff9500', fontWeight:700}}>{warn.length} próximo{warn.length!==1?'s':' a vencer'}</span>}
          <span style={{color:'var(--label-3,#666)', marginLeft:8, fontWeight:400}}>— garantías y licencias</span>
        </div>
        <Icon name={open?'chevron-down':'chevron-right'} size={14} color="#666"/>
      </div>
      {open && (
        <div style={{marginTop:8, padding:'8px 0', maxHeight:280, overflowY:'auto'}}>
          {alerts.map((a,i)=>(
            <div key={i} style={{padding:'8px 16px', display:'flex', alignItems:'center', gap:12, fontSize:13}}>
              <div style={{width:8, height:8, borderRadius:999, background: a.severity==='expired'?'#ff3b30':'#ff9500', flexShrink:0}}/>
              <div style={{flex:1}}>
                <div style={{fontWeight:500}}>{a.subject}</div>
                <div style={{fontSize:11, color:'var(--label-3,#666)'}}>{a.type === 'warranty' ? 'Garantía' : 'Licencia'} · {new Date(a.date).toLocaleDateString('es-MX',{day:'2-digit',month:'short',year:'numeric'})}</div>
              </div>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

window.ExpiryBanner = ExpiryBanner;

// ─── Signature pad (canvas drawing) ────────────────────────────────────────
function SignaturePad({ onSave, onCancel }) {
  const canvasRef = React.useRef();
  const [drawing, setDrawing] = React.useState(false);
  const [hasContent, setHasContent] = React.useState(false);

  const getPos = (e) => {
    const rect = canvasRef.current.getBoundingClientRect();
    const t = e.touches ? e.touches[0] : e;
    return { x: t.clientX - rect.left, y: t.clientY - rect.top };
  };
  const start = (e) => {
    e.preventDefault();
    setDrawing(true);
    const ctx = canvasRef.current.getContext('2d');
    const p = getPos(e);
    ctx.beginPath(); ctx.moveTo(p.x, p.y);
  };
  const draw = (e) => {
    if (!drawing) return;
    e.preventDefault();
    const ctx = canvasRef.current.getContext('2d');
    const p = getPos(e);
    ctx.lineTo(p.x, p.y);
    ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round';
    ctx.stroke();
    setHasContent(true);
  };
  const end = () => setDrawing(false);
  const clear = () => {
    const ctx = canvasRef.current.getContext('2d');
    ctx.clearRect(0,0, canvasRef.current.width, canvasRef.current.height);
    setHasContent(false);
  };
  const save = () => {
    if (!hasContent) return;
    onSave(canvasRef.current.toDataURL('image/png'));
  };

  return (
    <div>
      <div style={{border:'1.5px dashed #ccc', borderRadius:12, background:'#fff', marginBottom:12}}>
        <canvas ref={canvasRef} width={500} height={200}
          style={{display:'block', width:'100%', height:200, cursor:'crosshair', touchAction:'none'}}
          onMouseDown={start} onMouseMove={draw} onMouseUp={end} onMouseLeave={end}
          onTouchStart={start} onTouchMove={draw} onTouchEnd={end}/>
      </div>
      <div style={{display:'flex', gap:8, justifyContent:'space-between'}}>
        <Btn size="sm" variant="ghost" onClick={clear}>Limpiar</Btn>
        <div style={{display:'flex', gap:8}}>
          <Btn size="sm" variant="secondary" onClick={onCancel}>Cancelar</Btn>
          <Btn size="sm" variant="primary" onClick={save} disabled={!hasContent}>Guardar firma</Btn>
        </div>
      </div>
    </div>
  );
}

window.SignaturePad = SignaturePad;

// ─── Photo upload helper ───────────────────────────────────────────────────
function PhotoUpload({ photos=[], onChange, max=10 }) {
  const inputRef = React.useRef();

  const handleFiles = (files) => {
    const newPhotos = [...photos];
    Array.from(files).slice(0, max - photos.length).forEach(file => {
      if (!file.type.startsWith('image/')) return;
      const reader = new FileReader();
      reader.onload = e => {
        // compress
        const img = new Image();
        img.onload = () => {
          const maxW = 1200;
          const scale = Math.min(1, maxW / img.width);
          const canvas = document.createElement('canvas');
          canvas.width = img.width * scale;
          canvas.height = img.height * scale;
          const ctx = canvas.getContext('2d');
          ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
          const compressed = canvas.toDataURL('image/jpeg', 0.75);
          newPhotos.push({ id: Math.random().toString(36).slice(2,9), data: compressed, name: file.name, uploadedAt: new Date().toISOString() });
          onChange([...newPhotos]);
        };
        img.src = e.target.result;
      };
      reader.readAsDataURL(file);
    });
  };

  const removePhoto = (id) => {
    onChange(photos.filter(p => p.id !== id));
  };

  return (
    <div>
      <div style={{display:'flex', flexWrap:'wrap', gap:8, marginBottom:10}}>
        {photos.map(p => (
          <div key={p.id} style={{position:'relative', width:80, height:80, borderRadius:10, overflow:'hidden', border:'1px solid rgba(0,0,0,0.1)'}}>
            <img src={p.data} alt={p.name} style={{width:'100%', height:'100%', objectFit:'cover', cursor:'pointer'}}
              onClick={()=>window.open(p.data, '_blank')}/>
            <button onClick={()=>removePhoto(p.id)}
              style={{position:'absolute', top:2, right:2, width:20, height:20, borderRadius:999, background:'rgba(0,0,0,0.7)', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', border:'none', cursor:'pointer'}}>
              <Icon name="x" size={10} color="#fff"/>
            </button>
          </div>
        ))}
        {photos.length < max && (
          <button onClick={()=>inputRef.current.click()}
            style={{width:80, height:80, borderRadius:10, border:'1.5px dashed #ccc', background:'rgba(120,120,128,0.05)', display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center', gap:4, cursor:'pointer', color:'#888', fontSize:11}}>
            <Icon name="plus" size={18} color="#888"/>
            Foto
          </button>
        )}
      </div>
      <input ref={inputRef} type="file" accept="image/*" multiple capture="environment"
        style={{display:'none'}} onChange={e=>handleFiles(e.target.files)}/>
      <div style={{fontSize:11, color:'var(--label-3,#666)'}}>{photos.length}/{max} fotos · click en una para abrirla</div>
    </div>
  );
}

window.PhotoUpload = PhotoUpload;

// ─── Timeline component ────────────────────────────────────────────────────
function Timeline({ events }) {
  if (!events || events.length === 0) {
    return <div style={{padding:20, textAlign:'center', color:'var(--label-3,#888)', fontSize:13}}>Sin historial</div>;
  }
  return (
    <div style={{position:'relative', paddingLeft:24}}>
      <div style={{position:'absolute', left:8, top:8, bottom:8, width:2, background:'rgba(0,0,0,0.08)'}}/>
      {events.map((e,i) => {
        const color = e.type === 'ticket' ? (e.priority==='urgent'?'#ff3b30':e.priority==='high'?'#ff9500':'#007aff') : '#5856d6';
        return (
          <div key={i} style={{position:'relative', marginBottom:16}}>
            <div style={{position:'absolute', left:-20, top:2, width:14, height:14, borderRadius:999, background:color, border:'3px solid #fff', boxShadow:'0 0 0 1px rgba(0,0,0,0.1)'}}/>
            <div style={{fontSize:11, color:'var(--label-3,#888)', marginBottom:2}}>
              {new Date(e.date).toLocaleDateString('es-MX',{day:'2-digit',month:'short',year:'numeric'})}
            </div>
            <div style={{fontSize:13, fontWeight:600}}>{e.title}</div>
            <div style={{fontSize:11, color:'var(--label-3,#888)'}}>
              {e.type === 'ticket' ? 'Ticket' : 'Mantenimiento'} · {e.status}
              {e.priority && ` · ${e.priority}`}
            </div>
          </div>
        );
      })}
    </div>
  );
}

window.Timeline = Timeline;

// ─── Backup/Restore panel ─────────────────────────────────────────────────
function BackupPanel({ onRestore, toast }) {
  const fileRef = React.useRef();
  const handleImport = (e) => {
    const file = e.target.files[0];
    if (!file) return;
    if (!confirm('¿Reemplazar todos los datos actuales con los del backup? (Exporta tu backup actual primero)')) return;
    window.Features.importBackup(file, (ok, data, err) => {
      if (ok) { onRestore(data); toast?.('Backup restaurado','success'); }
      else toast?.('Error: '+err,'error');
    });
  };
  return (
    <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
      <Btn size="sm" variant="secondary" icon="copy" onClick={()=>window.Features.exportBackup()}>Exportar backup</Btn>
      <Btn size="sm" variant="outline" icon="link" onClick={()=>fileRef.current.click()}>Importar backup</Btn>
      <input ref={fileRef} type="file" accept="application/json" style={{display:'none'}} onChange={handleImport}/>
    </div>
  );
}

window.BackupPanel = BackupPanel;


/* ===== js-ext/advanced.jsx ===== */

// ─── Network Topology Diagram ─────────────────────────────────────────────
// Auto-draws: Internet → Router → Switches → Devices

function NetworkTopology({ company, users = [] }) {
  const net = company?.network || {};
  const companyUsers = users.filter(u => u.companyId === company?.id);

  const svgWidth = 800;
  const svgHeight = 480;

  // Positions
  const internetY = 40;
  const routerY = 130;
  const switchY = 240;
  const devicesY = 370;
  const centerX = svgWidth / 2;

  const switches = net.switches || [{ name: 'Switch principal', ports: 24 }];
  const switchCount = switches.length;
  const switchSpacing = Math.min(260, (svgWidth - 100) / Math.max(switchCount, 1));
  const switchStartX = centerX - (switchCount - 1) * switchSpacing / 2;

  const deviceCount = companyUsers.length;
  const deviceSpacing = Math.min(110, (svgWidth - 60) / Math.max(deviceCount, 1));
  const deviceStartX = centerX - (deviceCount - 1) * deviceSpacing / 2;

  return (
    <div style={{background:'#fff', border:'1px solid rgba(0,0,0,0.08)', borderRadius:16, padding:20, overflow:'auto'}}>
      <div style={{fontSize:12, textTransform:'uppercase', letterSpacing:0.5, fontWeight:700, color:'var(--label-3,#666)', marginBottom:12}}>Topología de red — {company?.name}</div>
      <svg viewBox={`0 0 ${svgWidth} ${svgHeight}`} style={{width:'100%', maxWidth:svgWidth, height:'auto', display:'block'}}>
        {/* Lines */}
        <line x1={centerX} y1={internetY+30} x2={centerX} y2={routerY} stroke="rgba(0,0,0,0.2)" strokeWidth="2" strokeDasharray="4 3"/>
        {switches.map((_, i) => {
          const sx = switchStartX + i * switchSpacing;
          return <line key={'sl'+i} x1={centerX} y1={routerY+40} x2={sx} y2={switchY} stroke="rgba(0,0,0,0.15)" strokeWidth="1.5"/>;
        })}
        {companyUsers.map((_, i) => {
          const dx = deviceStartX + i * deviceSpacing;
          const nearestSwitch = switches.length > 0 ? switchStartX + (Math.floor(i * switchCount / Math.max(deviceCount,1))) * switchSpacing : centerX;
          return <line key={'dl'+i} x1={nearestSwitch} y1={switchY+30} x2={dx} y2={devicesY} stroke="rgba(0,0,0,0.1)" strokeWidth="1"/>;
        })}

        {/* Internet */}
        <g>
          <rect x={centerX-60} y={internetY-20} width="120" height="50" rx="12" fill="#007aff"/>
          <text x={centerX} y={internetY+2} textAnchor="middle" fill="#fff" fontWeight="700" fontSize="13">Internet</text>
          <text x={centerX} y={internetY+18} textAnchor="middle" fill="rgba(255,255,255,0.8)" fontSize="10">{net.isp || 'ISP'} · {net.publicIp || '—'}</text>
        </g>

        {/* Router */}
        <g>
          <rect x={centerX-80} y={routerY} width="160" height="50" rx="12" fill="#5856d6"/>
          <text x={centerX} y={routerY+22} textAnchor="middle" fill="#fff" fontWeight="700" fontSize="13">Router</text>
          <text x={centerX} y={routerY+38} textAnchor="middle" fill="rgba(255,255,255,0.8)" fontSize="10">{net.routerIp || '192.168.1.1'} · {net.routerModel || 'Router'}</text>
        </g>

        {/* Switches */}
        {switches.map((sw, i) => {
          const sx = switchStartX + i * switchSpacing;
          return (
            <g key={'sw'+i}>
              <rect x={sx-70} y={switchY} width="140" height="40" rx="10" fill="#34c759"/>
              <text x={sx} y={switchY+18} textAnchor="middle" fill="#fff" fontWeight="700" fontSize="12">{sw.name || `Switch ${i+1}`}</text>
              <text x={sx} y={switchY+32} textAnchor="middle" fill="rgba(255,255,255,0.85)" fontSize="10">{sw.ports || 24} puertos</text>
            </g>
          );
        })}

        {/* Devices */}
        {companyUsers.map((u, i) => {
          const dx = deviceStartX + i * deviceSpacing;
          const isOnline = u.status === 'active';
          return (
            <g key={'d'+i}>
              <rect x={dx-45} y={devicesY} width="90" height="60" rx="10" fill="#fff" stroke="rgba(0,0,0,0.15)" strokeWidth="1"/>
              <circle cx={dx+30} cy={devicesY+8} r="4" fill={isOnline ? '#34c759' : '#8e8e93'}/>
              <text x={dx} y={devicesY+22} textAnchor="middle" fontWeight="600" fontSize="10" fill="#111">
                {(u.name||'').split(' ')[0].slice(0,10)}
              </text>
              <text x={dx} y={devicesY+36} textAnchor="middle" fontSize="9" fill="#666">
                {u.equipment?.ip || '—'}
              </text>
              <text x={dx} y={devicesY+50} textAnchor="middle" fontSize="8" fill="#999">
                {u.equipment?.type || 'PC'}
              </text>
            </g>
          );
        })}

        {/* Legend */}
        <g transform={`translate(20, ${svgHeight-30})`}>
          <circle cx="8" cy="0" r="4" fill="#34c759"/>
          <text x="18" y="3" fontSize="10" fill="#666">En línea</text>
          <circle cx="78" cy="0" r="4" fill="#8e8e93"/>
          <text x="88" y="3" fontSize="10" fill="#666">Inactivo</text>
          <text x="170" y="3" fontSize="10" fill="#999">{companyUsers.length} equipos · {switches.length} switch(es)</text>
        </g>
      </svg>
    </div>
  );
}

window.NetworkTopology = NetworkTopology;

// ─── Message templates picker ─────────────────────────────────────────────
function MessageTemplatesPicker({ ticket, maintenance, company, user, toast, onClose }) {
  const isTicket = !!ticket;
  const templates = isTicket ? [
    ['ticket-received','Recibido'],
    ['ticket-in-progress','En progreso'],
    ['ticket-resolved','Resuelto']
  ] : [
    ['maintenance-scheduled','Visita agendada'],
    ['maintenance-completed','Mantenimiento listo']
  ];

  const buildMessage = (key) => {
    const fn = window.Features.messageTemplates[key];
    return isTicket ? fn(ticket, company, user) : fn(maintenance, company);
  };

  const [selected, setSelected] = React.useState(templates[0][0]);
  const [customText, setCustomText] = React.useState('');

  React.useEffect(() => {
    setCustomText(buildMessage(selected));
  }, [selected]);

  const copy = () => {
    window.Features.copyMessage(customText);
    toast?.('Mensaje copiado','success');
  };
  const whatsapp = () => {
    const phone = (user?.phone || user?.mobile || company?.phone || '').replace(/[^0-9]/g,'');
    if (!phone) { toast?.('Sin teléfono','error'); return; }
    window.open(`https://wa.me/${phone}?text=${encodeURIComponent(customText)}`, '_blank');
  };
  const email = () => {
    const to = user?.email || company?.email || '';
    const subject = isTicket ? `Ticket #${ticket.id.slice(-5)} — ${ticket.title}` : `Mantenimiento — ${maintenance.title}`;
    window.open(`mailto:${to}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(customText)}`, '_blank');
  };

  return (
    <div>
      <div style={{display:'flex', gap:6, flexWrap:'wrap', marginBottom:12}}>
        {templates.map(([key, label]) => (
          <button key={key} onClick={()=>setSelected(key)}
            style={{padding:'6px 12px', borderRadius:999, fontSize:12, fontWeight:600, cursor:'pointer',
              border: selected===key ? '1.5px solid #007aff' : '1px solid rgba(0,0,0,0.15)',
              background: selected===key ? 'rgba(0,122,255,0.1)' : '#fff',
              color: selected===key ? '#007aff' : '#333'}}>
            {label}
          </button>
        ))}
      </div>
      <textarea value={customText} onChange={e=>setCustomText(e.target.value)}
        style={{width:'100%', minHeight:120, padding:12, borderRadius:12, border:'1px solid rgba(0,0,0,0.12)', fontSize:13, fontFamily:'inherit', resize:'vertical'}}/>
      <div style={{display:'flex', gap:8, marginTop:12, justifyContent:'space-between', flexWrap:'wrap'}}>
        <Btn size="sm" variant="ghost" onClick={onClose}>Cerrar</Btn>
        <div style={{display:'flex', gap:8, flexWrap:'wrap'}}>
          <Btn size="sm" variant="secondary" icon="copy" onClick={copy}>Copiar</Btn>
          <button onClick={email} style={{padding:'7px 14px', borderRadius:999, border:'none', background:'#ea4335', color:'#fff', fontSize:12, fontWeight:600, cursor:'pointer'}}>Email</button>
          <button onClick={whatsapp} style={{padding:'7px 14px', borderRadius:999, border:'none', background:'#25d366', color:'#fff', fontSize:12, fontWeight:600, cursor:'pointer'}}>WhatsApp</button>
        </div>
      </div>
    </div>
  );
}

window.MessageTemplatesPicker = MessageTemplatesPicker;

// ─── Checklist component ──────────────────────────────────────────────────
function ChecklistEditor({ checklist = [], onChange, templateKey, onTemplateChange }) {
  const addTask = () => onChange([...checklist, { id: Math.random().toString(36).slice(2,9), text: '', done: false }]);
  const toggleTask = (id) => onChange(checklist.map(t => t.id === id ? {...t, done: !t.done, doneAt: !t.done ? new Date().toISOString() : null} : t));
  const updateText = (id, text) => onChange(checklist.map(t => t.id === id ? {...t, text} : t));
  const removeTask = (id) => onChange(checklist.filter(t => t.id !== id));

  const applyTemplate = (key) => {
    if (key === 'custom') { onTemplateChange?.(key); return; }
    const tpl = window.Features.checklistTemplates[key];
    if (!tpl) return;
    const tasks = tpl.tasks.map(text => ({ id: Math.random().toString(36).slice(2,9), text, done: false }));
    onChange(tasks);
    onTemplateChange?.(key);
  };

  const done = checklist.filter(t => t.done).length;
  const pct = checklist.length > 0 ? Math.round(done/checklist.length*100) : 0;

  return (
    <div>
      <div style={{display:'flex', gap:8, alignItems:'center', marginBottom:10, flexWrap:'wrap'}}>
        <select value={templateKey || ''} onChange={e=>applyTemplate(e.target.value)}
          style={{padding:'6px 10px', borderRadius:10, border:'1px solid rgba(0,0,0,0.15)', fontSize:12, background:'#fff'}}>
          <option value="">— Aplicar plantilla —</option>
          {Object.entries(window.Features.checklistTemplates).map(([k, v]) => (
            <option key={k} value={k}>{v.name}</option>
          ))}
        </select>
        {checklist.length > 0 && (
          <div style={{fontSize:11, color:'var(--label-3,#666)', display:'flex', alignItems:'center', gap:8}}>
            <div style={{width:60, height:6, borderRadius:999, background:'rgba(0,0,0,0.08)', overflow:'hidden'}}>
              <div style={{width:`${pct}%`, height:'100%', background:pct===100?'#34c759':'#007aff', transition:'width .3s'}}/>
            </div>
            {done}/{checklist.length}
          </div>
        )}
      </div>
      <div style={{border:'1px solid rgba(0,0,0,0.08)', borderRadius:12, overflow:'hidden'}}>
        {checklist.map(task => (
          <div key={task.id} style={{display:'flex', alignItems:'center', gap:10, padding:'8px 12px', borderBottom:'1px solid rgba(0,0,0,0.05)', background: task.done ? 'rgba(52,199,89,0.05)' : '#fff'}}>
            <input type="checkbox" checked={task.done} onChange={()=>toggleTask(task.id)}
              style={{width:18, height:18, accentColor:'#007aff', cursor:'pointer'}}/>
            <input value={task.text} onChange={e=>updateText(task.id, e.target.value)}
              placeholder="Tarea..."
              style={{flex:1, border:'none', background:'transparent', fontSize:13, outline:'none',
                textDecoration: task.done ? 'line-through' : 'none',
                color: task.done ? '#999' : '#222'}}/>
            <button onClick={()=>removeTask(task.id)} style={{background:'none', border:'none', cursor:'pointer', color:'#ff3b30', padding:4}}>
              <Icon name="x" size={14} color="#ff3b30"/>
            </button>
          </div>
        ))}
        <button onClick={addTask} style={{width:'100%', padding:'10px', border:'none', background:'rgba(0,122,255,0.05)', color:'#007aff', cursor:'pointer', fontSize:12, fontWeight:600, display:'flex', alignItems:'center', justifyContent:'center', gap:6}}>
          <Icon name="plus" size={14} color="#007aff"/> Agregar tarea
        </button>
      </div>
    </div>
  );
}

window.ChecklistEditor = ChecklistEditor;

// ─── Internal Notes with @ mentions ───────────────────────────────────────
function NotesThread({ notes = [], onAdd, users = [], currentUser = 'Tú' }) {
  const [text, setText] = React.useState('');
  const [showMentions, setShowMentions] = React.useState(false);
  const [mentionQuery, setMentionQuery] = React.useState('');
  const textareaRef = React.useRef();

  const handleInput = (e) => {
    const val = e.target.value;
    setText(val);
    const match = val.slice(0, e.target.selectionStart).match(/@(\w*)$/);
    if (match) { setShowMentions(true); setMentionQuery(match[1].toLowerCase()); }
    else setShowMentions(false);
  };

  const insertMention = (name) => {
    setText(text.replace(/@\w*$/, `@${name.split(' ')[0]} `));
    setShowMentions(false);
    textareaRef.current?.focus();
  };

  const submit = () => {
    if (!text.trim()) return;
    onAdd({ id: Math.random().toString(36).slice(2,9), text: text.trim(), author: currentUser, at: new Date().toISOString() });
    setText('');
  };

  const filteredUsers = users.filter(u => u.name.toLowerCase().includes(mentionQuery)).slice(0,5);

  const renderText = (txt) => {
    const parts = txt.split(/(@\w+)/g);
    return parts.map((p, i) => p.startsWith('@')
      ? <span key={i} style={{color:'#007aff', fontWeight:600, background:'rgba(0,122,255,0.1)', padding:'0 4px', borderRadius:4}}>{p}</span>
      : <span key={i}>{p}</span>);
  };

  return (
    <div>
      <div style={{marginBottom:12, maxHeight:280, overflowY:'auto'}}>
        {notes.length === 0 && <div style={{padding:16, textAlign:'center', color:'var(--label-3,#888)', fontSize:12}}>Sin notas internas</div>}
        {notes.map(n => (
          <div key={n.id} style={{padding:'10px 12px', background:'rgba(120,120,128,0.06)', borderRadius:12, marginBottom:8}}>
            <div style={{display:'flex', justifyContent:'space-between', marginBottom:4}}>
              <div style={{fontSize:12, fontWeight:600}}>{n.author}</div>
              <div style={{fontSize:11, color:'var(--label-3,#888)'}}>{DB.timeAgo(n.at)}</div>
            </div>
            <div style={{fontSize:13, lineHeight:1.5}}>{renderText(n.text)}</div>
          </div>
        ))}
      </div>
      <div style={{position:'relative'}}>
        <textarea ref={textareaRef} value={text} onChange={handleInput} placeholder="Escribe una nota... usa @ para mencionar"
          style={{width:'100%', minHeight:60, padding:10, borderRadius:10, border:'1px solid rgba(0,0,0,0.12)', fontSize:13, fontFamily:'inherit', resize:'vertical'}}/>
        {showMentions && filteredUsers.length > 0 && (
          <div style={{position:'absolute', bottom:'100%', left:0, right:0, background:'#fff', borderRadius:12, boxShadow:'0 10px 40px rgba(0,0,0,0.15)', border:'1px solid rgba(0,0,0,0.08)', marginBottom:4, overflow:'hidden', zIndex:10}}>
            {filteredUsers.map(u => (
              <div key={u.id} onClick={()=>insertMention(u.name)} style={{padding:'8px 12px', cursor:'pointer', fontSize:13, display:'flex', alignItems:'center', gap:8}}
                onMouseEnter={e=>e.currentTarget.style.background='rgba(0,122,255,0.08)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                <div style={{width:24, height:24, borderRadius:999, background:'#007aff', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:11, fontWeight:700}}>
                  {u.name[0]}
                </div>
                {u.name}
              </div>
            ))}
          </div>
        )}
        <div style={{display:'flex', justifyContent:'flex-end', marginTop:6}}>
          <Btn size="sm" variant="primary" onClick={submit} disabled={!text.trim()}>Agregar nota</Btn>
        </div>
      </div>
    </div>
  );
}

window.NotesThread = NotesThread;


/* ===== js-ext/TicketTools.jsx ===== */

// ─── Ticket tools panel: photos, checklist, time, signature, templates ────

function TicketToolsPanel({ ticket, setData, toast, company, user }) {
  const [section, setSection] = React.useState(null);
  const [timerRunning, setTimerRunning] = React.useState(false);
  const [timerStart, setTimerStart] = React.useState(null);
  const [timerTick, setTimerTick] = React.useState(0);

  React.useEffect(() => {
    if (!timerRunning) return;
    const i = setInterval(() => setTimerTick(t => t+1), 1000);
    return () => clearInterval(i);
  }, [timerRunning]);

  const updateTicket = (patch) => {
    setData(d => {
      const nd = { ...d, tickets: d.tickets.map(t => t.id === ticket.id ? {...t, ...patch, updatedAt: new Date().toISOString()} : t) };
      DB.saveData(nd);
      return nd;
    });
  };

  // Timer
  const startTimer = () => { setTimerStart(Date.now()); setTimerRunning(true); };
  const stopTimer = () => {
    if (!timerStart) return;
    const elapsedMin = Math.round((Date.now() - timerStart) / 60000);
    const total = (ticket.timeSpent || 0) + elapsedMin;
    updateTicket({ timeSpent: total });
    setTimerRunning(false); setTimerStart(null); setTimerTick(0);
    toast?.(`+${elapsedMin} min registrados (total: ${total} min)`, 'success');
  };

  const liveElapsedSec = timerStart ? Math.floor((Date.now() - timerStart) / 1000) + timerTick*0 : 0;
  const liveDisplay = timerStart ? `${Math.floor(liveElapsedSec/60).toString().padStart(2,'0')}:${(liveElapsedSec%60).toString().padStart(2,'0')}` : '00:00';

  const hours = ((ticket.timeSpent||0)/60).toFixed(2);
  const rate = parseFloat(localStorage.getItem('it_billing_rate') || '25');
  const cost = ((ticket.timeSpent||0)/60 * rate).toFixed(2);

  const photos = ticket.photos || [];
  const checklist = ticket.checklist || [];
  const signature = ticket.signature;

  const tools = [
    { id:'photos', label:`Fotos (${photos.length})`, icon:'monitor', color:'#007aff' },
    { id:'checklist', label:`Checklist (${checklist.filter(t=>t.done).length}/${checklist.length})`, icon:'check', color:'#34c759' },
    { id:'time', label:`Tiempo (${Math.floor((ticket.timeSpent||0)/60)}h ${(ticket.timeSpent||0)%60}m)`, icon:'clock', color:'#ff9500' },
    { id:'signature', label: signature ? 'Firma ✓' : 'Firmar cierre', icon:'pencil', color: signature ? '#34c759' : '#5856d6' },
    { id:'templates', label:'Mensaje al cliente', icon:'copy', color:'#ff3b30' },
  ];

  return (
    <div style={{padding:'12px 18px', borderTop:'1px solid #f1f5f9', background:'#fafbfc'}}>
      <div style={{fontSize:11,fontWeight:700,color:'#94a3b8',textTransform:'uppercase',letterSpacing:'0.06em',marginBottom:10}}>Herramientas del ticket</div>

      <div style={{display:'flex', flexWrap:'wrap', gap:6, marginBottom: section ? 14 : 0}}>
        {tools.map(t => (
          <button key={t.id} onClick={()=>setSection(section===t.id?null:t.id)}
            style={{
              display:'inline-flex', alignItems:'center', gap:6,
              padding:'7px 12px', borderRadius:999,
              border: section===t.id ? `1.5px solid ${t.color}` : '1px solid #e2e8f0',
              background: section===t.id ? `${t.color}15` : '#fff',
              color: section===t.id ? t.color : '#555',
              fontSize:12, fontWeight:600, cursor:'pointer',
              transition:'all 0.15s'
            }}>
            <Icon name={t.icon} size={12} color={section===t.id?t.color:'#888'}/>
            {t.label}
          </button>
        ))}
      </div>

      {section === 'photos' && (
        <div style={{padding:12, background:'#fff', borderRadius:10, border:'1px solid #e2e8f0'}}>
          <PhotoUpload photos={photos} onChange={p=>updateTicket({photos:p})}/>
        </div>
      )}

      {section === 'checklist' && (
        <div style={{padding:12, background:'#fff', borderRadius:10, border:'1px solid #e2e8f0'}}>
          <ChecklistEditor
            checklist={checklist}
            onChange={c=>updateTicket({checklist:c})}
            templateKey={ticket.checklistTemplate}
            onTemplateChange={k=>updateTicket({checklistTemplate:k})}/>
        </div>
      )}

      {section === 'time' && (
        <div style={{padding:16, background:'#fff', borderRadius:10, border:'1px solid #e2e8f0'}}>
          <div style={{display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16, flexWrap:'wrap', gap:10}}>
            <div>
              <div style={{fontSize:11, color:'#888', textTransform:'uppercase', letterSpacing:0.5, fontWeight:700}}>Tiempo acumulado</div>
              <div style={{fontSize:28, fontWeight:700, fontVariantNumeric:'tabular-nums'}}>
                {Math.floor((ticket.timeSpent||0)/60)}h {(ticket.timeSpent||0)%60}m
              </div>
              <div style={{fontSize:11, color:'#888'}}>{hours}h · ${cost} USD (${rate}/h)</div>
            </div>
            <div style={{textAlign:'center'}}>
              {timerRunning ? (
                <>
                  <div style={{fontSize:28, fontWeight:700, color:'#ff3b30', fontVariantNumeric:'tabular-nums', fontFamily:'monospace'}}>{liveDisplay}</div>
                  <button onClick={stopTimer} style={{marginTop:4, padding:'8px 20px', borderRadius:999, border:'none', background:'#ff3b30', color:'#fff', fontWeight:700, fontSize:12, cursor:'pointer'}}>■ Detener</button>
                </>
              ) : (
                <button onClick={startTimer} style={{padding:'10px 24px', borderRadius:999, border:'none', background:'#34c759', color:'#fff', fontWeight:700, fontSize:13, cursor:'pointer'}}>▶ Iniciar timer</button>
              )}
            </div>
          </div>
          <div style={{display:'flex', gap:8, alignItems:'center'}}>
            <span style={{fontSize:12, color:'#555'}}>Agregar manualmente:</span>
            {[15, 30, 60, 120].map(m => (
              <button key={m} onClick={()=>{
                updateTicket({timeSpent: (ticket.timeSpent||0) + m});
                toast?.(`+${m} min agregados`, 'success');
              }} style={{padding:'4px 10px', borderRadius:999, border:'1px solid #e2e8f0', background:'#fff', fontSize:11, fontWeight:600, cursor:'pointer'}}>
                +{m<60?`${m}m`:`${m/60}h`}
              </button>
            ))}
            {ticket.timeSpent > 0 && (
              <button onClick={()=>{if(confirm('¿Resetear tiempo?')){updateTicket({timeSpent:0});toast?.('Reseteado');}}} style={{marginLeft:'auto', padding:'4px 10px', borderRadius:999, border:'1px solid #fca5a5', background:'#fff', fontSize:11, fontWeight:600, cursor:'pointer', color:'#ff3b30'}}>Reset</button>
            )}
          </div>
        </div>
      )}

      {section === 'signature' && (
        <div style={{padding:12, background:'#fff', borderRadius:10, border:'1px solid #e2e8f0'}}>
          {signature ? (
            <div>
              <div style={{fontSize:11, color:'#888', textTransform:'uppercase', letterSpacing:0.5, fontWeight:700, marginBottom:6}}>Firma del cliente · {DB.formatDateTime(signature.at)}</div>
              <img src={signature.data} alt="Firma" style={{maxWidth:'100%', maxHeight:180, borderRadius:8, border:'1px solid #e2e8f0', background:'#fff'}}/>
              <div style={{marginTop:10}}>
                <Btn size="sm" variant="danger" onClick={()=>{if(confirm('¿Eliminar firma?')){updateTicket({signature:null});toast?.('Firma eliminada');}}}>Eliminar firma</Btn>
              </div>
            </div>
          ) : (
            <div>
              <div style={{fontSize:12, color:'#555', marginBottom:10}}>El cliente debe firmar para confirmar el cierre. Use el mouse o el dedo (en pantallas táctiles).</div>
              <SignaturePad
                onSave={(dataUrl)=>{
                  updateTicket({signature:{data:dataUrl, at:new Date().toISOString()}, status:'resolved'});
                  toast?.('Firma guardada','success');
                  setSection(null);
                }}
                onCancel={()=>setSection(null)}/>
            </div>
          )}
        </div>
      )}

      {section === 'templates' && (
        <div style={{padding:12, background:'#fff', borderRadius:10, border:'1px solid #e2e8f0'}}>
          <MessageTemplatesPicker
            ticket={ticket} company={company} user={user} toast={toast}
            onClose={()=>setSection(null)}/>
        </div>
      )}
    </div>
  );
}

window.TicketToolsPanel = TicketToolsPanel;


/* ===== js-ext/Tools.jsx ===== */

// ─── Tools view: Backup · Alerts · Access log · Billing ────────────────────

function Tools({ data, setData, navigate, toast }) {
  const [tab, setTab] = React.useState('alerts');
  const alerts = window.Features.checkExpiries(data);
  const log = window.Features.getCredentialLog();

  // Billing overview
  const tickets = data.tickets || [];
  const billingByCompany = {};
  tickets.forEach(t => {
    const min = t.timeSpent || 0;
    const cid = t.companyId;
    if (!billingByCompany[cid]) billingByCompany[cid] = { minutes: 0, tickets: 0 };
    billingByCompany[cid].minutes += min;
    billingByCompany[cid].tickets += 1;
  });
  const rate = parseFloat(localStorage.getItem('it_billing_rate') || '25');
  const [hourlyRate, setHourlyRate] = React.useState(rate);

  const saveRate = (v) => {
    setHourlyRate(v);
    localStorage.setItem('it_billing_rate', String(v));
  };

  const expired = alerts.filter(a=>a.severity==='expired');
  const warn = alerts.filter(a=>a.severity==='warn');

  const tabs = [
    { id:'alerts', label:'Alertas', icon:'alert', count: alerts.length },
    { id:'backup', label:'Backup', icon:'copy' },
    { id:'billing', label:'Facturación', icon:'chart-bar' },
    { id:'log', label:'Accesos', icon:'shield', count: log.length }
  ];

  return (
    <div style={{padding:'32px 36px', maxWidth:1100}}>
      <div style={{marginBottom:20}}>
        <div className="caption" style={{fontSize:11, textTransform:'uppercase', letterSpacing:0.5, fontWeight:700, color:'var(--label-3,#666)', marginBottom:4}}>Panel de control</div>
        <h1 style={{margin:0, fontSize:30, fontWeight:700, letterSpacing:'-0.02em'}}>Herramientas</h1>
      </div>

      {/* Tabs */}
      <div style={{display:'flex', gap:4, marginBottom:20, borderBottom:'1px solid rgba(0,0,0,0.08)'}}>
        {tabs.map(t => (
          <button key={t.id} onClick={()=>setTab(t.id)}
            style={{padding:'10px 14px', border:'none', background:'none', cursor:'pointer',
              borderBottom: tab===t.id ? '2px solid #007aff' : '2px solid transparent',
              color: tab===t.id ? '#007aff' : '#666',
              fontSize:13, fontWeight:600, display:'flex', alignItems:'center', gap:6,
              marginBottom:-1}}>
            <Icon name={t.icon} size={14} color={tab===t.id?'#007aff':'#666'}/>
            {t.label}
            {t.count > 0 && <span style={{fontSize:10, background: tab===t.id?'#007aff':'#999', color:'#fff', padding:'1px 6px', borderRadius:999, fontWeight:700}}>{t.count}</span>}
          </button>
        ))}
      </div>

      {/* ALERTS */}
      {tab==='alerts' && (
        <div>
          <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(180px,1fr))', gap:12, marginBottom:20}}>
            <div style={{background:'rgba(255,59,48,0.08)', border:'1px solid rgba(255,59,48,0.25)', borderRadius:14, padding:'16px 18px'}}>
              <div style={{fontSize:12, color:'#ff3b30', fontWeight:700, textTransform:'uppercase', letterSpacing:0.5}}>Vencidos</div>
              <div style={{fontSize:32, fontWeight:700, color:'#ff3b30', marginTop:4}}>{expired.length}</div>
            </div>
            <div style={{background:'rgba(255,149,0,0.08)', border:'1px solid rgba(255,149,0,0.25)', borderRadius:14, padding:'16px 18px'}}>
              <div style={{fontSize:12, color:'#ff9500', fontWeight:700, textTransform:'uppercase', letterSpacing:0.5}}>Por vencer (30d)</div>
              <div style={{fontSize:32, fontWeight:700, color:'#ff9500', marginTop:4}}>{warn.length}</div>
            </div>
            <div style={{background:'rgba(52,199,89,0.08)', border:'1px solid rgba(52,199,89,0.25)', borderRadius:14, padding:'16px 18px'}}>
              <div style={{fontSize:12, color:'#34c759', fontWeight:700, textTransform:'uppercase', letterSpacing:0.5}}>Vigentes</div>
              <div style={{fontSize:32, fontWeight:700, color:'#34c759', marginTop:4}}>
                {(data.users||[]).filter(u=>u.equipment?.warrantyUntil && new Date(u.equipment.warrantyUntil) >= new Date(Date.now()+30*86400000)).length +
                 (data.credentials||[]).filter(c=>c.expiresAt && new Date(c.expiresAt) >= new Date(Date.now()+30*86400000)).length}
              </div>
            </div>
          </div>

          {alerts.length === 0 && (
            <div style={{padding:40, textAlign:'center', background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)'}}>
              <Icon name="check" size={32} color="#34c759"/>
              <div style={{marginTop:12, fontSize:16, fontWeight:600}}>Todo al día</div>
              <div style={{fontSize:13, color:'#888', marginTop:4}}>No hay garantías ni licencias próximas a vencer</div>
            </div>
          )}

          {alerts.length > 0 && (
            <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', overflow:'hidden'}}>
              <div style={{padding:'12px 16px', background:'rgba(0,0,0,0.02)', fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:0.5, color:'#666'}}>Detalle · {alerts.length} elemento{alerts.length!==1?'s':''}</div>
              {alerts.map((a,i) => (
                <div key={i} style={{padding:'12px 16px', display:'flex', alignItems:'center', gap:12, borderTop: i>0 ? '1px solid rgba(0,0,0,0.05)' : 'none'}}>
                  <div style={{width:8, height:40, borderRadius:4, background: a.severity==='expired'?'#ff3b30':'#ff9500'}}/>
                  <div style={{flex:1}}>
                    <div style={{fontSize:14, fontWeight:600}}>{a.subject}</div>
                    <div style={{fontSize:11, color:'#888', marginTop:2}}>
                      {a.type === 'warranty' ? '🛡 Garantía' : '🔑 Licencia'} · vence {new Date(a.date).toLocaleDateString('es-MX',{day:'2-digit',month:'long',year:'numeric'})}
                    </div>
                  </div>
                  <div style={{fontSize:11, fontWeight:700, textTransform:'uppercase', letterSpacing:0.5, color: a.severity==='expired'?'#ff3b30':'#ff9500', padding:'3px 8px', background: a.severity==='expired'?'rgba(255,59,48,0.1)':'rgba(255,149,0,0.1)', borderRadius:999}}>
                    {a.severity === 'expired' ? 'Vencido' : 'Por vencer'}
                  </div>
                  {a.userId && <Btn size="sm" variant="ghost" onClick={()=>navigate('user-detail',{userId:a.userId})}>Ver</Btn>}
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      {/* BACKUP */}
      {tab==='backup' && (
        <div style={{display:'grid', gridTemplateColumns:'repeat(auto-fit, minmax(280px,1fr))', gap:16}}>
          <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', padding:20}}>
            <div style={{display:'flex', alignItems:'center', gap:10, marginBottom:12}}>
              <div style={{width:36, height:36, borderRadius:10, background:'#007aff', display:'flex', alignItems:'center', justifyContent:'center'}}>
                <Icon name="copy" size={18} color="#fff"/>
              </div>
              <div>
                <div style={{fontSize:15, fontWeight:700}}>Exportar backup</div>
                <div style={{fontSize:11, color:'#888'}}>Descarga toda tu base en un JSON</div>
              </div>
            </div>
            <p style={{fontSize:13, color:'#555', margin:'0 0 14px', lineHeight:1.5}}>
              Incluye empresas, usuarios, tickets, mantenimientos, credenciales y registros.
              Guárdalo en un lugar seguro o mándalo por email.
            </p>
            <Btn size="sm" variant="primary" icon="copy" onClick={()=>{window.Features.exportBackup(); toast?.('Backup descargado','success');}}>Descargar backup</Btn>
          </div>

          <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', padding:20}}>
            <div style={{display:'flex', alignItems:'center', gap:10, marginBottom:12}}>
              <div style={{width:36, height:36, borderRadius:10, background:'#34c759', display:'flex', alignItems:'center', justifyContent:'center'}}>
                <Icon name="link" size={18} color="#fff"/>
              </div>
              <div>
                <div style={{fontSize:15, fontWeight:700}}>Restaurar backup</div>
                <div style={{fontSize:11, color:'#888'}}>Reemplaza los datos con un archivo</div>
              </div>
            </div>
            <p style={{fontSize:13, color:'#555', margin:'0 0 14px', lineHeight:1.5}}>
              ⚠️ Reemplazará <strong>todos</strong> los datos actuales. Exporta tu backup actual antes por seguridad.
            </p>
            <BackupPanel onRestore={(d)=>{setData(d);}} toast={toast}/>
          </div>

          <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', padding:20}}>
            <div style={{display:'flex', alignItems:'center', gap:10, marginBottom:12}}>
              <div style={{width:36, height:36, borderRadius:10, background:'#ff3b30', display:'flex', alignItems:'center', justifyContent:'center'}}>
                <Icon name="trash" size={18} color="#fff"/>
              </div>
              <div>
                <div style={{fontSize:15, fontWeight:700}}>Reset a demo</div>
                <div style={{fontSize:11, color:'#888'}}>Volver a los datos iniciales</div>
              </div>
            </div>
            <p style={{fontSize:13, color:'#555', margin:'0 0 14px', lineHeight:1.5}}>
              Borra todo y regresa al set de datos de demostración inicial.
            </p>
            <Btn size="sm" variant="danger" icon="trash" onClick={()=>{
              if(confirm('¿Borrar TODO y volver a demo?')){
                const d = DB.resetData(); setData(d); toast?.('Datos reseteados','success');
              }
            }}>Resetear a demo</Btn>
          </div>

          <div style={{background:'linear-gradient(135deg, #007aff, #5856d6)', borderRadius:14, padding:20, color:'#fff'}}>
            <div style={{fontSize:12, textTransform:'uppercase', letterSpacing:0.5, fontWeight:700, opacity:0.8}}>Resumen</div>
            <div style={{fontSize:13, marginTop:10, lineHeight:1.9}}>
              <div>📊 {data.companies?.length||0} empresas</div>
              <div>👥 {data.users?.length||0} usuarios/equipos</div>
              <div>🎫 {data.tickets?.length||0} tickets</div>
              <div>🔧 {data.maintenances?.length||0} mantenimientos</div>
              <div>🔐 {data.credentials?.length||0} credenciales</div>
            </div>
          </div>
        </div>
      )}

      {/* BILLING */}
      {tab==='billing' && (
        <div>
          <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', padding:18, marginBottom:16, display:'flex', alignItems:'center', gap:12, flexWrap:'wrap'}}>
            <div style={{fontSize:13, fontWeight:600}}>Tarifa por hora:</div>
            <div style={{display:'flex', alignItems:'center', gap:4, background:'rgba(0,0,0,0.04)', borderRadius:10, padding:'6px 12px'}}>
              <span style={{fontSize:16, fontWeight:700, color:'#34c759'}}>$</span>
              <input type="number" value={hourlyRate} onChange={e=>saveRate(e.target.value)}
                style={{width:60, border:'none', background:'none', fontSize:16, fontWeight:700, outline:'none'}}/>
              <span style={{fontSize:12, color:'#888'}}>/h</span>
            </div>
            <div style={{fontSize:11, color:'#888', marginLeft:'auto'}}>Se aplica a todos los tickets. Configura tiempo en minutos en cada ticket.</div>
          </div>

          <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', overflow:'hidden'}}>
            <div style={{padding:'12px 16px', background:'rgba(0,0,0,0.02)', fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:0.5, color:'#666'}}>Facturación por empresa</div>
            {Object.keys(billingByCompany).length === 0 && (
              <div style={{padding:30, textAlign:'center', color:'#888', fontSize:13}}>Sin tickets con tiempo registrado</div>
            )}
            {Object.entries(billingByCompany).sort((a,b)=>b[1].minutes-a[1].minutes).map(([cid, b]) => {
              const company = (data.companies||[]).find(c => c.id === cid);
              const hours = b.minutes / 60;
              const total = hours * parseFloat(hourlyRate);
              return (
                <div key={cid} style={{padding:'14px 16px', borderTop:'1px solid rgba(0,0,0,0.05)', display:'flex', alignItems:'center', gap:12}}>
                  <div style={{width:40, height:40, borderRadius:10, background: '#007aff', color:'#fff', display:'flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:14}}>
                    {(company?.name||'?')[0]}
                  </div>
                  <div style={{flex:1}}>
                    <div style={{fontSize:14, fontWeight:600}}>{company?.name || 'Sin empresa'}</div>
                    <div style={{fontSize:11, color:'#888'}}>{b.tickets} tickets · {hours.toFixed(2)} horas</div>
                  </div>
                  <div style={{textAlign:'right'}}>
                    <div style={{fontSize:18, fontWeight:700, color:'#34c759'}}>${total.toFixed(2)}</div>
                    <div style={{fontSize:10, color:'#888'}}>USD</div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* LOG */}
      {tab==='log' && (
        <div style={{background:'#fff', borderRadius:14, border:'1px solid rgba(0,0,0,0.08)', overflow:'hidden'}}>
          <div style={{padding:'12px 16px', background:'rgba(0,0,0,0.02)', display:'flex', alignItems:'center', justifyContent:'space-between'}}>
            <div style={{fontSize:12, fontWeight:700, textTransform:'uppercase', letterSpacing:0.5, color:'#666'}}>Registro de accesos a credenciales</div>
            {log.length > 0 && <button onClick={()=>{if(confirm('¿Borrar registro?')){localStorage.removeItem('credential_access_log'); toast?.('Registro limpiado','success');}}} style={{fontSize:11, color:'#ff3b30', background:'none', border:'none', cursor:'pointer', fontWeight:600}}>Limpiar</button>}
          </div>
          {log.length === 0 && <div style={{padding:30, textAlign:'center', color:'#888', fontSize:13}}>Sin accesos registrados aún</div>}
          {log.slice(0,100).map((l, i) => {
            const cred = (data.credentials||[]).find(c => c.id === l.credentialId);
            return (
              <div key={i} style={{padding:'10px 16px', borderTop:'1px solid rgba(0,0,0,0.05)', display:'flex', alignItems:'center', gap:12, fontSize:13}}>
                <Icon name={l.action==='view'?'eye':l.action==='copy'?'copy':'shield'} size={14} color="#888"/>
                <div style={{flex:1}}>
                  <div style={{fontWeight:600}}>{cred?.service || '(eliminada)'}</div>
                  <div style={{fontSize:11, color:'#888'}}>{l.action} · por {l.user}</div>
                </div>
                <div style={{fontSize:11, color:'#888'}}>{DB.timeAgo(l.at)}</div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

window.Tools = Tools;


/* ===== js-ext/AppleDashboard.jsx ===== */

// ─── Apple Dashboard with widgets + charts ───────────────────────────────────

function sparkPath(values, w, h, pad=4) {
  if (!values.length) return '';
  const max = Math.max(...values, 1), min = Math.min(...values, 0);
  const range = max - min || 1;
  const step = (w - pad*2) / Math.max(values.length-1, 1);
  return values.map((v, i) => {
    const x = pad + i*step;
    const y = h - pad - ((v - min)/range) * (h - pad*2);
    return `${i===0?'M':'L'}${x.toFixed(1)},${y.toFixed(1)}`;
  }).join(' ');
}

function Sparkline({ values, color='#007aff', width=280, height=60, fill=true }) {
  const path = sparkPath(values, width, height);
  const areaPath = path + ` L${width-4},${height-4} L4,${height-4} Z`;
  const gradId = 'sparkGrad_' + color.replace('#','');
  return (
    <svg width={width} height={height} style={{display:'block',width:'100%'}} viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none">
      <defs>
        <linearGradient id={gradId} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={color} stopOpacity="0.4"/>
          <stop offset="100%" stopColor={color} stopOpacity="0"/>
        </linearGradient>
      </defs>
      {fill && <path d={areaPath} fill={`url(#${gradId})`} />}
      <path d={path} fill="none" stroke={color} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"/>
    </svg>
  );
}

function Donut({ segments, size=120, thickness=14 }) {
  const total = segments.reduce((s,x)=>s+x.value,0) || 1;
  const r = (size-thickness)/2;
  const c = 2*Math.PI*r;
  let acc = 0;
  return (
    <svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
      <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="#f0f0f3" strokeWidth={thickness}/>
      {segments.map((s,i)=>{
        const dash = (s.value/total)*c;
        const off = c - acc;
        acc += dash;
        return (
          <circle key={i} cx={size/2} cy={size/2} r={r} fill="none"
            stroke={s.color} strokeWidth={thickness}
            strokeDasharray={`${dash} ${c-dash}`}
            strokeDashoffset={off}
            transform={`rotate(-90 ${size/2} ${size/2})`}
            strokeLinecap="round"/>
        );
      })}
    </svg>
  );
}

function BarChart({ data, color='#007aff', height=80 }) {
  const max = Math.max(...data.map(d=>d.value), 1);
  return (
    <div style={{display:'flex', alignItems:'flex-end', gap:6, height, padding:'0 2px'}}>
      {data.map((d,i)=>(
        <div key={i} style={{flex:1, display:'flex', flexDirection:'column', alignItems:'center', gap:4}}>
          <div style={{
            width:'100%', background:`linear-gradient(180deg, ${color}, ${color}cc)`,
            height: `${(d.value/max)*100}%`, minHeight:2, borderRadius:'6px 6px 2px 2px',
            transition:'height 0.6s ease'
          }}/>
          <div style={{fontSize:9, color:'#86868b', fontWeight:500}}>{d.label}</div>
        </div>
      ))}
    </div>
  );
}

function AppleDashboard({ data, navigate, toast }) {
  const { companies, users, tickets, credentials, maintenances: allMaint = [], devices: allDevices = [] } = data;

  const openTickets = tickets.filter(t => t.status === 'open').length;
  const inProgress = tickets.filter(t => t.status === 'in_progress').length;
  const resolved = tickets.filter(t => t.status === 'resolved').length;
  const urgentTickets = tickets.filter(t => t.priority === 'urgent' && t.status !== 'resolved');
  const recentTickets = [...tickets].sort((a,b) => new Date(b.updatedAt)-new Date(a.updatedAt)).slice(0,5);
  const today = new Date();
  const upcomingMaint = allMaint.filter(m => m.status === 'pending' && new Date(m.scheduledDate) >= today)
    .sort((a,b) => new Date(a.scheduledDate) - new Date(b.scheduledDate)).slice(0,4);
  const overdueMaint = allMaint.filter(m => m.status === 'pending' && new Date(m.scheduledDate) < today);

  // Ticket history - last 14 days
  const ticketsByDay = React.useMemo(() => {
    const days = [];
    for (let i = 13; i >= 0; i--) {
      const d = new Date(); d.setDate(d.getDate()-i); d.setHours(0,0,0,0);
      const dNext = new Date(d); dNext.setDate(dNext.getDate()+1);
      const count = tickets.filter(t => {
        const ct = new Date(t.createdAt || t.updatedAt);
        return ct >= d && ct < dNext;
      }).length;
      days.push({ date: d, value: count });
    }
    return days;
  }, [tickets]);

  // Ticket by priority
  const byPriority = [
    { label:'Urgente', value: tickets.filter(t=>t.priority==='urgent').length, color:'#ff3b30' },
    { label:'Alta', value: tickets.filter(t=>t.priority==='high').length, color:'#ff9500' },
    { label:'Media', value: tickets.filter(t=>t.priority==='medium').length, color:'#ffcc00' },
    { label:'Baja', value: tickets.filter(t=>t.priority==='low').length, color:'#30d158' },
  ];

  // Tickets by company (top 6)
  const byCompany = companies.map(c => ({
    label: c.name.slice(0,8),
    value: tickets.filter(t => t.companyId === c.id).length
  })).sort((a,b)=>b.value-a.value).slice(0,6);

  const hour = new Date().getHours();
  const greeting = hour < 12 ? 'Buenos días' : hour < 19 ? 'Buenas tardes' : 'Buenas noches';

  const priorityColor = { urgent:'red', high:'orange', medium:'yellow', low:'slate' };
  const priorityLabel = { urgent:'Urgente', high:'Alta', medium:'Media', low:'Baja' };
  const statusColor = { open:'orange', in_progress:'blue', resolved:'green' };
  const statusLabel = { open:'Abierto', in_progress:'En progreso', resolved:'Resuelto' };

  function getCompanyName(id) { return companies.find(c=>c.id===id)?.name || '—'; }
  function getUserName(id) { return users.find(u=>u.id===id)?.name || 'General'; }

  const totalTicketsWeek = ticketsByDay.slice(-7).reduce((s,d)=>s+d.value,0);
  const prevWeek = ticketsByDay.slice(0,7).reduce((s,d)=>s+d.value,0);
  const trend = prevWeek ? Math.round(((totalTicketsWeek-prevWeek)/prevWeek)*100) : 0;

  return (
    <div className="apple-bg" style={{padding:'36px 40px', minHeight:'100%'}}>
      {/* Hero greeting */}
      <div style={{marginBottom:28}}>
        <div style={{fontSize:13, color:'#86868b', fontWeight:500, letterSpacing:'-0.01em', marginBottom:4}}>
          {new Date().toLocaleDateString('es-ES',{weekday:'long', day:'numeric', month:'long'})}
        </div>
        <h1 style={{margin:0, fontSize:38, fontWeight:700, color:'#1d1d1f', letterSpacing:'-0.03em', lineHeight:1.1}}>
          {greeting}.
        </h1>
        <p style={{margin:'6px 0 0', fontSize:17, color:'#424245', letterSpacing:'-0.01em'}}>
          Tienes <span style={{color:'#007aff', fontWeight:600}}>{openTickets+inProgress} tickets activos</span> y <span style={{color:'#ff9500', fontWeight:600}}>{upcomingMaint.length+overdueMaint.length} mantenimientos pendientes</span>.
        </p>
      </div>

      {/* Warranty banner */}
      {window.ExpiryBanner && <div style={{marginBottom:20}}><ExpiryBanner data={data} navigate={navigate}/></div>}

      {/* Widget grid */}
      <div style={{display:'grid', gridTemplateColumns:'2fr 1fr 1fr', gap:16, marginBottom:16}}>
        {/* LARGE: tickets trend */}
        <div className="apple-tile" onClick={()=>navigate('tickets')} style={{
          background:'linear-gradient(135deg, #007aff 0%, #5856d6 100%)',
          color:'#fff', padding:'22px 24px', cursor:'pointer', minHeight:200, display:'flex', flexDirection:'column'
        }}>
          <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start'}}>
            <div>
              <div style={{fontSize:12, opacity:0.8, fontWeight:600, textTransform:'uppercase', letterSpacing:'0.04em'}}>Tickets · últimos 14 días</div>
              <div style={{fontSize:44, fontWeight:700, letterSpacing:'-0.03em', lineHeight:1, marginTop:6}}>{tickets.length}</div>
              <div style={{fontSize:13, opacity:0.9, marginTop:2}}>
                {trend>=0?'↑':'↓'} {Math.abs(trend)}% vs semana anterior
              </div>
            </div>
            <div style={{width:42, height:42, borderRadius:12, background:'rgba(255,255,255,0.2)', display:'flex', alignItems:'center', justifyContent:'center'}}>
              <Icon name="ticket" size={20} color="#fff"/>
            </div>
          </div>
          <div style={{flex:1, display:'flex', alignItems:'flex-end', marginTop:12, marginLeft:-8, marginRight:-8, marginBottom:-8}}>
            <Sparkline values={ticketsByDay.map(d=>d.value)} color="#fff" width={480} height={80}/>
          </div>
        </div>

        {/* Empresas */}
        <div className="apple-tile" onClick={()=>navigate('companies')} style={{
          background:'#fff', padding:'22px 24px', cursor:'pointer', minHeight:200,
          border:'1px solid rgba(0,0,0,0.06)', boxShadow:'0 1px 2px rgba(0,0,0,0.04)'
        }}>
          <div style={{width:42, height:42, borderRadius:12, background:'#5856d615', display:'flex', alignItems:'center', justifyContent:'center', marginBottom:14}}>
            <Icon name="building" size={20} color="#5856d6"/>
          </div>
          <div style={{fontSize:13, color:'#86868b', fontWeight:500}}>Empresas cliente</div>
          <div style={{fontSize:40, fontWeight:700, color:'#1d1d1f', letterSpacing:'-0.03em', lineHeight:1}}>{companies.length}</div>
          <div style={{fontSize:12, color:'#86868b', marginTop:4}}>
            {companies.filter(c=>c.plan==='Enterprise').length} Enterprise · {companies.filter(c=>c.plan==='Premium').length} Premium
          </div>
        </div>

        {/* Usuarios */}
        <div className="apple-tile" onClick={()=>navigate('users')} style={{
          background:'linear-gradient(135deg, #30d158 0%, #00c896 100%)',
          color:'#fff', padding:'22px 24px', cursor:'pointer', minHeight:200
        }}>
          <div style={{width:42, height:42, borderRadius:12, background:'rgba(255,255,255,0.2)', display:'flex', alignItems:'center', justifyContent:'center', marginBottom:14}}>
            <Icon name="users" size={20} color="#fff"/>
          </div>
          <div style={{fontSize:13, opacity:0.9, fontWeight:500}}>Usuarios / Equipos</div>
          <div style={{fontSize:40, fontWeight:700, letterSpacing:'-0.03em', lineHeight:1}}>{users.length}</div>
          <div style={{fontSize:12, opacity:0.9, marginTop:4}}>
            {users.filter(u=>u.anydesk).length} con AnyDesk activo
          </div>
        </div>
      </div>

      {/* Row 2: Priority donut + By company bar */}
      <div style={{display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:16, marginBottom:16}}>
        {/* Priority donut */}
        <div className="apple-card" style={{padding:'20px 22px'}}>
          <div style={{fontSize:13, color:'#86868b', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.04em', marginBottom:14}}>Por prioridad</div>
          <div style={{display:'flex', alignItems:'center', gap:16}}>
            <div style={{position:'relative'}}>
              <Donut segments={byPriority.filter(p=>p.value>0)} size={120} thickness={14}/>
              <div style={{position:'absolute', inset:0, display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center'}}>
                <div style={{fontSize:22, fontWeight:700, color:'#1d1d1f', letterSpacing:'-0.02em'}}>{tickets.length}</div>
                <div style={{fontSize:10, color:'#86868b'}}>total</div>
              </div>
            </div>
            <div style={{flex:1, display:'flex', flexDirection:'column', gap:6}}>
              {byPriority.map(p=>(
                <div key={p.label} style={{display:'flex', alignItems:'center', gap:8}}>
                  <div style={{width:8, height:8, borderRadius:2, background:p.color}}/>
                  <span style={{fontSize:12, color:'#1d1d1f', flex:1}}>{p.label}</span>
                  <span style={{fontSize:12, fontWeight:600, color:'#86868b'}}>{p.value}</span>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* By company bars */}
        <div className="apple-card" style={{padding:'20px 22px'}}>
          <div style={{fontSize:13, color:'#86868b', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.04em', marginBottom:14}}>Tickets por empresa</div>
          {byCompany.length > 0 ? (
            <BarChart data={byCompany} color="#007aff" height={100}/>
          ) : (
            <div style={{fontSize:12, color:'#86868b', textAlign:'center', padding:'30px 0'}}>Sin datos</div>
          )}
        </div>

        {/* Status progress */}
        <div className="apple-card" style={{padding:'20px 22px'}}>
          <div style={{fontSize:13, color:'#86868b', fontWeight:600, textTransform:'uppercase', letterSpacing:'0.04em', marginBottom:14}}>Estado de tickets</div>
          {[
            { label:'Abiertos', count:openTickets, color:'#ff9500' },
            { label:'En progreso', count:inProgress, color:'#007aff' },
            { label:'Resueltos', count:resolved, color:'#30d158' },
          ].map(row=>(
            <div key={row.label} style={{marginBottom:10}}>
              <div style={{display:'flex', justifyContent:'space-between', marginBottom:4}}>
                <span style={{fontSize:12, color:'#1d1d1f', fontWeight:500}}>{row.label}</span>
                <span style={{fontSize:12, fontWeight:700, color:row.color}}>{row.count}</span>
              </div>
              <div style={{height:6, background:'#f0f0f3', borderRadius:999, overflow:'hidden'}}>
                <div style={{
                  height:'100%', background:row.color, borderRadius:999,
                  width: tickets.length ? `${(row.count/tickets.length)*100}%` : '0%',
                  transition:'width 0.8s cubic-bezier(0.4, 0, 0.2, 1)'
                }}/>
              </div>
            </div>
          ))}
        </div>
      </div>

      {/* Urgent alert */}
      {urgentTickets.length > 0 && (
        <div style={{
          background:'linear-gradient(135deg, #ff3b3015, #ff950015)',
          border:'1px solid #ff3b3030', borderRadius:14, padding:'14px 18px', marginBottom:16,
          display:'flex', alignItems:'center', gap:12
        }}>
          <div style={{width:34, height:34, borderRadius:10, background:'#ff3b30', display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0}}>
            <Icon name="bolt" size={16} color="#fff"/>
          </div>
          <div style={{flex:1}}>
            <div style={{fontSize:13, fontWeight:700, color:'#1d1d1f'}}>
              {urgentTickets.length} ticket{urgentTickets.length>1?'s':''} urgente{urgentTickets.length>1?'s':''}
            </div>
            <div style={{fontSize:12, color:'#424245'}}>{urgentTickets.map(t=>t.title).join(' · ')}</div>
          </div>
          <button onClick={()=>navigate('tickets')} className="apple-btn" style={{
            padding:'8px 16px', borderRadius:999, border:'none', background:'#1d1d1f', color:'#fff',
            fontSize:12, fontWeight:600, cursor:'pointer', fontFamily:'inherit'
          }}>Ver tickets</button>
        </div>
      )}

      {/* Row 3: Recent tickets + sidebar */}
      <div style={{display:'grid', gridTemplateColumns:'1fr 320px', gap:16}}>
        <div className="apple-card" style={{overflow:'hidden'}}>
          <div style={{padding:'16px 22px', borderBottom:'1px solid rgba(0,0,0,0.06)', display:'flex', alignItems:'center', justifyContent:'space-between'}}>
            <div style={{fontSize:15, fontWeight:700, color:'#1d1d1f', letterSpacing:'-0.01em'}}>Actividad reciente</div>
            <button onClick={()=>navigate('tickets')} style={{background:'none', border:'none', color:'#007aff', fontSize:13, fontWeight:500, cursor:'pointer', fontFamily:'inherit'}}>Ver todo →</button>
          </div>
          {recentTickets.length === 0 ? (
            <div style={{padding:'60px 20px', textAlign:'center', color:'#86868b'}}>Sin tickets</div>
          ) : recentTickets.map((t,i) => (
            <div key={t.id} onClick={()=>navigate('tickets',{ticketId:t.id})}
              style={{padding:'14px 22px', borderBottom:i<recentTickets.length-1?'1px solid rgba(0,0,0,0.04)':'none', display:'flex', alignItems:'center', gap:12, cursor:'pointer'}}
              onMouseEnter={e=>e.currentTarget.style.background='rgba(0,122,255,0.04)'}
              onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
              <div style={{width:36, height:36, borderRadius:10, background:`${t.priority==='urgent'?'#ff3b3015':t.priority==='high'?'#ff950015':'#007aff15'}`, display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0}}>
                <Icon name="ticket" size={16} color={t.priority==='urgent'?'#ff3b30':t.priority==='high'?'#ff9500':'#007aff'}/>
              </div>
              <div style={{flex:1, minWidth:0}}>
                <div style={{fontSize:13, fontWeight:600, color:'#1d1d1f', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{t.title}</div>
                <div style={{fontSize:11, color:'#86868b'}}>{getCompanyName(t.companyId)} · {getUserName(t.userId)}</div>
              </div>
              <Badge label={priorityLabel[t.priority]} color={priorityColor[t.priority]}/>
              <Badge label={statusLabel[t.status]} color={statusColor[t.status]}/>
              <div style={{fontSize:11, color:'#86868b', whiteSpace:'nowrap', minWidth:60, textAlign:'right'}}>{DB.timeAgo(t.updatedAt)}</div>
            </div>
          ))}
        </div>

        <div style={{display:'flex', flexDirection:'column', gap:16}}>
          {/* Companies */}
          <div className="apple-card" style={{overflow:'hidden'}}>
            <div style={{padding:'14px 18px', borderBottom:'1px solid rgba(0,0,0,0.06)', fontSize:13, fontWeight:700, color:'#1d1d1f'}}>Empresas</div>
            {companies.slice(0,4).map((c,i)=>(
              <div key={c.id} onClick={()=>navigate('company-detail',{companyId:c.id})}
                style={{padding:'11px 18px', borderBottom:i<Math.min(companies.length,4)-1?'1px solid rgba(0,0,0,0.04)':'none', display:'flex', alignItems:'center', gap:10, cursor:'pointer'}}
                onMouseEnter={e=>e.currentTarget.style.background='rgba(0,122,255,0.04)'}
                onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                <div style={{width:30, height:30, borderRadius:9, background:'#007aff15', color:'#007aff', display:'flex', alignItems:'center', justifyContent:'center', fontSize:12, fontWeight:700, flexShrink:0}}>{c.name.charAt(0)}</div>
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontSize:12, fontWeight:600, color:'#1d1d1f', whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{c.name}</div>
                  <div style={{fontSize:10, color:'#86868b'}}>{users.filter(u=>u.companyId===c.id).length} usuarios</div>
                </div>
              </div>
            ))}
          </div>

          {/* Upcoming */}
          <div className="apple-card" style={{overflow:'hidden'}}>
            <div style={{padding:'14px 18px', borderBottom:'1px solid rgba(0,0,0,0.06)', display:'flex', alignItems:'center', justifyContent:'space-between'}}>
              <div style={{fontSize:13, fontWeight:700, color:'#1d1d1f'}}>Agenda</div>
              <button onClick={()=>navigate('maintenances')} style={{background:'none', border:'none', color:'#007aff', fontSize:11, fontWeight:500, cursor:'pointer', fontFamily:'inherit'}}>Ver →</button>
            </div>
            {overdueMaint.length > 0 && (
              <div style={{padding:'8px 14px', background:'#ff3b3010', borderBottom:'1px solid #ff3b3020', fontSize:11, fontWeight:700, color:'#ff3b30', display:'flex', alignItems:'center', gap:6}}>
                <Icon name="alert" size={11} color="#ff3b30"/>
                {overdueMaint.length} vencido{overdueMaint.length!==1?'s':''}
              </div>
            )}
            {upcomingMaint.length === 0 && overdueMaint.length === 0 ? (
              <div style={{padding:'20px 16px', fontSize:12, color:'#86868b', textAlign:'center'}}>Sin mantenimientos</div>
            ) : upcomingMaint.map((m,i)=>{
              const comp = companies.find(c=>c.id===m.companyId);
              const daysUntil = Math.ceil((new Date(m.scheduledDate) - new Date()) / (1000*60*60*24));
              return (
                <div key={m.id} onClick={()=>navigate('maintenances')}
                  style={{padding:'11px 18px', borderBottom:i<upcomingMaint.length-1?'1px solid rgba(0,0,0,0.04)':'none', cursor:'pointer'}}
                  onMouseEnter={e=>e.currentTarget.style.background='rgba(0,122,255,0.04)'}
                  onMouseLeave={e=>e.currentTarget.style.background='transparent'}>
                  <div style={{fontSize:12, fontWeight:600, color:'#1d1d1f', marginBottom:2, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{m.title}</div>
                  <div style={{display:'flex', alignItems:'center', gap:6}}>
                    <span style={{fontSize:11, color:'#86868b'}}>{comp?.name}</span>
                    <span style={{fontSize:11, color:daysUntil<=3?'#ff9500':'#86868b', fontWeight:daysUntil<=3?700:500}}>
                      · {daysUntil===0?'Hoy':daysUntil===1?'Mañana':`En ${daysUntil}d`}
                    </span>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

window.AppleDashboard = AppleDashboard;
// Replace the default Dashboard with the Apple edition
window.Dashboard = AppleDashboard;



/* ===== Base de Conocimiento (KB) ===== */
function KnowledgeBase({ data, setData, navigate, toast }) {
  const articles = data.knowledge || [];
  const [search, setSearch]   = React.useState('');
  const [cat, setCat]         = React.useState('');
  const [openId, setOpenId]   = React.useState(null);
  const [showForm, setShowForm] = React.useState(false);
  const [editId, setEditId]   = React.useState(null);
  const blank = { title:'', category:'General', tags:'', body:'' };
  const [form, setForm] = React.useState(blank);
  const set = k => v => setForm(f=>({...f,[k]:v}));

  const CATS = ['General','Redes','Hardware','Software','Seguridad','Procedimientos'];
  const catColor = { General:'slate', Redes:'blue', Hardware:'orange', Software:'purple', Seguridad:'red', Procedimientos:'green' };

  const filtered = articles.filter(a=>{
    const q = search.toLowerCase();
    const mq = !q || a.title.toLowerCase().includes(q) || (a.body||'').toLowerCase().includes(q) || (a.tags||[]).join(' ').toLowerCase().includes(q);
    const mc = !cat || a.category===cat;
    return mq && mc;
  }).sort((a,b)=> (b.updatedAt||'').localeCompare(a.updatedAt||''));

  function openNew(){ setForm(blank); setEditId(null); setShowForm(true); }
  function openEdit(a){ setForm({ title:a.title, category:a.category, tags:(a.tags||[]).join(', '), body:a.body }); setEditId(a.id); setShowForm(true); }

  function save(){
    if(!form.title.trim()) return;
    const tags = form.tags.split(',').map(s=>s.trim()).filter(Boolean);
    const now = new Date().toISOString();
    if(editId){
      setData(d=>{ const nd={...d, knowledge:(d.knowledge||[]).map(a=>a.id===editId?{...a,...form,tags,updatedAt:now}:a)}; DB.saveData(nd); return nd; });
      toast('Artículo actualizado');
    } else {
      const art = { id: DB.generateId('kb'), ...form, tags, createdAt:now, updatedAt:now };
      setData(d=>{ const nd={...d, knowledge:[...(d.knowledge||[]), art]}; DB.saveData(nd); return nd; });
      toast('Artículo creado');
    }
    setShowForm(false);
  }
  function remove(id){
    setData(d=>{ const nd={...d, knowledge:(d.knowledge||[]).filter(a=>a.id!==id)}; DB.saveData(nd); return nd; });
    setOpenId(null); toast('Artículo eliminado','error');
  }

  const open = articles.find(a=>a.id===openId);

  return (
    <div style={{padding:'32px 36px',maxWidth:1100}}>
      <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:24}}>
        <div>
          <h1 style={{margin:0,fontSize:22,fontWeight:800,color:'#0f172a'}}>Base de conocimiento</h1>
          <p style={{margin:'4px 0 0',fontSize:13,color:'#94a3b8'}}>{articles.length} artículo{articles.length!==1?'s':''} · procedimientos y soluciones reutilizables</p>
        </div>
        <Btn icon="plus" onClick={openNew}>Nuevo artículo</Btn>
      </div>

      <div style={{display:'flex',gap:10,marginBottom:18}}>
        <div style={{flex:1,position:'relative'}}>
          <span style={{position:'absolute',left:10,top:'50%',transform:'translateY(-50%)',color:'#94a3b8',pointerEvents:'none'}}><Icon name="search" size={15} /></span>
          <input value={search} onChange={e=>setSearch(e.target.value)} placeholder="Buscar título, contenido, etiqueta..."
            style={{width:'100%',paddingLeft:34,paddingRight:12,paddingTop:9,paddingBottom:9,border:'1.5px solid #e2e8f0',borderRadius:9,fontSize:13,fontFamily:'inherit',outline:'none',background:'#fff',boxSizing:'border-box'}} />
        </div>
        <Select value={cat} onChange={setCat} placeholder="Todas las categorías" options={CATS.map(c=>({value:c,label:c}))} />
      </div>

      {filtered.length===0 ? (
        <EmptyState icon="copy" title="Sin artículos" desc="Crea el primer procedimiento o solución reutilizable." action={<Btn icon="plus" onClick={openNew}>Nuevo artículo</Btn>} />
      ) : (
        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(320px,1fr))',gap:14}}>
          {filtered.map(a=>(
            <Card key={a.id} style={{padding:'16px 18px',cursor:'pointer'}} onClick={()=>setOpenId(a.id)}>
              <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8}}>
                <Badge label={a.category} color={catColor[a.category]||'slate'} />
                <span style={{fontSize:11,color:'#94a3b8',marginLeft:'auto'}}>{DB.timeAgo(a.updatedAt)}</span>
              </div>
              <div style={{fontSize:14,fontWeight:700,color:'#0f172a',marginBottom:4}}>{a.title}</div>
              <div style={{fontSize:12,color:'#64748b',lineHeight:1.5,maxHeight:54,overflow:'hidden'}}>{(a.body||'').slice(0,160)}</div>
              {(a.tags||[]).length>0 && <div style={{display:'flex',gap:4,flexWrap:'wrap',marginTop:10}}>{a.tags.map(t=><span key={t} style={{fontSize:10,color:'#64748b',background:'#f1f5f9',borderRadius:6,padding:'2px 7px'}}>{t}</span>)}</div>}
            </Card>
          ))}
        </div>
      )}

      {/* Lector */}
      <Modal open={!!open} onClose={()=>setOpenId(null)} title={open?.title || ''} width={680}>
        {open && (
          <div>
            <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:14}}>
              <Badge label={open.category} color={catColor[open.category]||'slate'} />
              {(open.tags||[]).map(t=><span key={t} style={{fontSize:10,color:'#64748b',background:'#f1f5f9',borderRadius:6,padding:'2px 7px'}}>{t}</span>)}
              <span style={{fontSize:11,color:'#94a3b8',marginLeft:'auto'}}>Actualizado {DB.timeAgo(open.updatedAt)}</span>
            </div>
            <div style={{fontSize:13,color:'#374151',lineHeight:1.7,whiteSpace:'pre-wrap'}}>{open.body}</div>
            <div style={{display:'flex',justifyContent:'flex-end',gap:8,marginTop:20,paddingTop:14,borderTop:'1px solid #f1f5f9'}}>
              <Btn variant="secondary" icon="trash" onClick={()=>remove(open.id)}>Eliminar</Btn>
              <Btn variant="secondary" icon="pencil" onClick={()=>{setOpenId(null);openEdit(open);}}>Editar</Btn>
            </div>
          </div>
        )}
      </Modal>

      {/* Form */}
      <Modal open={showForm} onClose={()=>setShowForm(false)} title={editId?'Editar artículo':'Nuevo artículo'} width={640}>
        <div style={{display:'flex',flexDirection:'column',gap:12}}>
          <Input label="Título" value={form.title} onChange={set('title')} required placeholder="Ej: Reconfigurar impresora de red" />
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
            <Select label="Categoría" value={form.category} onChange={set('category')} options={CATS.map(c=>({value:c,label:c}))} />
            <Input label="Etiquetas (coma)" value={form.tags} onChange={set('tags')} placeholder="impresora, red, drivers" />
          </div>
          <Textarea label="Contenido" value={form.body} onChange={set('body')} rows={10} placeholder="Pasos, solución, notas..." />
          <div style={{display:'flex',justifyContent:'flex-end',gap:8,paddingTop:8,borderTop:'1px solid #f1f5f9'}}>
            <Btn variant="secondary" onClick={()=>setShowForm(false)}>Cancelar</Btn>
            <Btn variant="primary" icon="check" onClick={save}>{editId?'Guardar':'Crear artículo'}</Btn>
          </div>
        </div>
      </Modal>
    </div>
  );
}
window.KnowledgeBase = KnowledgeBase;


/* ===== Facturacion por empresa ===== */
function Billing({ data, setData, navigate, toast }) {
  const { companies, tickets } = data;
  const [companyId, setCompanyId] = React.useState(companies[0]?.id || '');
  const now = new Date();
  const [period, setPeriod] = React.useState(now.toISOString().slice(0,7)); // YYYY-MM

  const company = companies.find(c=>c.id===companyId);
  const rate = Number(company?.hourlyRate ?? 0);
  const monthlyFee = Number(company?.monthlyFee ?? 0);

  const periodTickets = tickets.filter(t=>{
    if (t.companyId !== companyId) return false;
    const d = (t.createdAt||'').slice(0,7);
    return d === period;
  });

  const totalHours = periodTickets.reduce((s,t)=> s + (Number(t.billableHours)||0), 0);
  const laborTotal = totalHours * rate;
  const grandTotal = laborTotal + monthlyFee;
  const MXN = (n)=> new Intl.NumberFormat('es-MX',{style:'currency',currency:'MXN'}).format(n||0);

  function patchCompany(patch){
    setData(d=>{ const nd={...d, companies:d.companies.map(c=>c.id===companyId?{...c,...patch}:c)}; DB.saveData(nd); return nd; });
  }
  function patchTicket(id, patch){
    setData(d=>{ const nd={...d, tickets:d.tickets.map(t=>t.id===id?{...t,...patch}:t)}; DB.saveData(nd); return nd; });
  }

  return (
    <div style={{padding:'32px 36px',maxWidth:1000}}>
      <div style={{marginBottom:24}}>
        <h1 style={{margin:0,fontSize:22,fontWeight:800,color:'#0f172a'}}>Facturación</h1>
        <p style={{margin:'4px 0 0',fontSize:13,color:'#94a3b8'}}>Estado de cuenta por empresa y período (horas facturables + cuota mensual).</p>
      </div>

      <div style={{display:'flex',gap:10,marginBottom:18,flexWrap:'wrap'}}>
        <Select value={companyId} onChange={setCompanyId} options={companies.map(c=>({value:c.id,label:c.name}))} placeholder="Empresa" />
        <input type="month" value={period} onChange={e=>setPeriod(e.target.value)}
          style={{padding:'9px 12px',border:'1.5px solid #e2e8f0',borderRadius:9,fontSize:13,fontFamily:'inherit',background:'#fff'}} />
        <button onClick={()=>window.print()} className="" style={{marginLeft:'auto',display:'inline-flex',alignItems:'center',gap:6,padding:'9px 14px',background:'#f1f5f9',border:'none',borderRadius:9,cursor:'pointer',fontSize:13,fontWeight:600,color:'#334155',fontFamily:'inherit'}}>
          <Icon name="copy" size={14}/> Imprimir
        </button>
      </div>

      {!company ? <EmptyState icon="building" title="Selecciona una empresa" desc="Elige empresa y período para ver el estado de cuenta." /> : (
        <>
          <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12,marginBottom:16}}>
            <Card style={{padding:'14px 16px'}}>
              <label style={{fontSize:11,fontWeight:600,color:'#94a3b8',textTransform:'uppercase',letterSpacing:'0.05em'}}>Tarifa por hora (MXN)</label>
              <input type="number" min="0" step="0.01" defaultValue={rate} onBlur={e=>patchCompany({hourlyRate:Number(e.target.value)})}
                style={{display:'block',marginTop:6,width:'100%',padding:'8px 10px',border:'1.5px solid #e2e8f0',borderRadius:8,fontSize:14,fontFamily:'inherit',boxSizing:'border-box'}} />
            </Card>
            <Card style={{padding:'14px 16px'}}>
              <label style={{fontSize:11,fontWeight:600,color:'#94a3b8',textTransform:'uppercase',letterSpacing:'0.05em'}}>Cuota mensual fija (MXN)</label>
              <input type="number" min="0" step="0.01" defaultValue={monthlyFee} onBlur={e=>patchCompany({monthlyFee:Number(e.target.value)})}
                style={{display:'block',marginTop:6,width:'100%',padding:'8px 10px',border:'1.5px solid #e2e8f0',borderRadius:8,fontSize:14,fontFamily:'inherit',boxSizing:'border-box'}} />
            </Card>
          </div>

          <Card style={{padding:0,marginBottom:16}}>
            <div style={{padding:'12px 16px',borderBottom:'1px solid #f1f5f9',fontWeight:700,fontSize:14,color:'#0f172a'}}>
              Tickets del período <span style={{color:'#94a3b8',fontWeight:400}}>({periodTickets.length})</span>
            </div>
            {periodTickets.length===0 ? (
              <div style={{padding:'18px 16px',fontSize:13,color:'#94a3b8'}}>Sin tickets en {period}.</div>
            ) : periodTickets.map((t,i)=>(
              <div key={t.id} style={{padding:'10px 16px',borderBottom:i<periodTickets.length-1?'1px solid #f8fafc':'none',display:'flex',alignItems:'center',gap:12}}>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontSize:13,fontWeight:600,color:'#0f172a',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{t.title}</div>
                  <div style={{fontSize:11,color:'#94a3b8'}}>{(t.createdAt||'').slice(0,10)} · {t.status}</div>
                </div>
                <div style={{display:'flex',alignItems:'center',gap:6}}>
                  <input type="number" min="0" step="0.25" defaultValue={t.billableHours||0} onBlur={e=>patchTicket(t.id,{billableHours:Number(e.target.value)})}
                    style={{width:72,padding:'6px 8px',border:'1.5px solid #e2e8f0',borderRadius:7,fontSize:13,fontFamily:'inherit',textAlign:'right'}} />
                  <span style={{fontSize:11,color:'#94a3b8'}}>hrs</span>
                </div>
                <div style={{width:90,textAlign:'right',fontSize:13,fontWeight:600,color:'#0f172a'}}>{MXN((Number(t.billableHours)||0)*rate)}</div>
              </div>
            ))}
          </Card>

          <Card style={{padding:'16px 18px'}}>
            <div style={{display:'flex',justifyContent:'space-between',fontSize:13,color:'#64748b',marginBottom:6}}><span>Horas facturables · {totalHours} hrs × {MXN(rate)}</span><span>{MXN(laborTotal)}</span></div>
            <div style={{display:'flex',justifyContent:'space-between',fontSize:13,color:'#64748b',marginBottom:6}}><span>Cuota mensual fija</span><span>{MXN(monthlyFee)}</span></div>
            <div style={{display:'flex',justifyContent:'space-between',fontSize:18,fontWeight:800,color:'#0f172a',marginTop:8,paddingTop:10,borderTop:'1px solid #e2e8f0'}}><span>Total a facturar</span><span>{MXN(grandTotal)}</span></div>
          </Card>
        </>
      )}
    </div>
  );
}
window.Billing = Billing;


/* ===== Dashboard ejecutivo ===== */
function ExecutiveDashboard({ data, navigate, toast }) {
  const { companies, tickets } = data;
  const [tele, setTele] = React.useState([]);
  React.useEffect(()=>{
    let alive=true;
    const load=()=>fetch('/api/agent/telemetry',{credentials:'include'}).then(r=>r.ok?r.json():[]).then(d=>{if(alive)setTele(Array.isArray(d)?d:[])}).catch(()=>{});
    load(); const t=setInterval(load,30000); return ()=>{alive=false;clearInterval(t);};
  },[]);

  const SLA_HOURS = { urgent:4, high:8, medium:24, low:72 };
  const open = tickets.filter(t=>t.status!=='resolved');
  const resolved = tickets.filter(t=>t.status==='resolved');
  const slaMet = resolved.filter(t=>{
    const c=new Date(t.createdAt).getTime(); if(!c) return true;
    return (new Date(t.updatedAt).getTime()-c) <= (SLA_HOURS[t.priority]??24)*3600e3;
  }).length;
  const slaPct = resolved.length ? Math.round(slaMet/resolved.length*100) : 100;
  const breaching = open.filter(t=>{
    const c=new Date(t.createdAt).getTime(); if(!c) return false;
    return (Date.now()-c) > (SLA_HOURS[t.priority]??24)*3600e3;
  }).length;

  const onlineN = tele.filter(d=>d.online).length;
  const offlineN = tele.length - onlineN;
  const diskAlerts = tele.filter(d=>d.disk_total && (Number(d.disk_used)/Number(d.disk_total)*100)>=90).length;

  const trend = (()=>{ const days=[]; for(let i=13;i>=0;i--){ const d=new Date(Date.now()-i*86400000); const key=d.toISOString().slice(0,10); days.push({key, label:String(d.getDate()), n: tickets.filter(t=>(t.createdAt||'').slice(0,10)===key).length}); } return days; })();
  const trendMax = Math.max(1, ...trend.map(d=>d.n));
  const byCompany = companies.map(c=>({
    name:c.name,
    open: tickets.filter(t=>t.companyId===c.id && t.status!=='resolved').length,
    total: tickets.filter(t=>t.companyId===c.id).length,
  })).filter(x=>x.total>0).sort((a,b)=>b.open-a.open).slice(0,6);

  const kpi = (label,value,sub,color,icon)=>(
    <Card style={{padding:'16px 18px'}}>
      <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:8}}>
        <div style={{width:32,height:32,borderRadius:9,background:color+'1a',display:'flex',alignItems:'center',justifyContent:'center',color}}><Icon name={icon} size={16}/></div>
        <span style={{fontSize:11,fontWeight:700,color:'#94a3b8',textTransform:'uppercase',letterSpacing:'0.05em'}}>{label}</span>
      </div>
      <div style={{fontSize:26,fontWeight:800,color:'#0f172a',lineHeight:1}}>{value}</div>
      {sub && <div style={{fontSize:11,color:'#94a3b8',marginTop:4}}>{sub}</div>}
    </Card>
  );

  return (
    <div style={{padding:'32px 36px',maxWidth:1100}}>
      <div style={{marginBottom:24}}>
        <h1 style={{margin:0,fontSize:22,fontWeight:800,color:'#0f172a'}}>Dashboard ejecutivo</h1>
        <p style={{margin:'4px 0 0',fontSize:13,color:'#94a3b8'}}>Salud de flota, cumplimiento de SLA y carga por empresa — en vivo.</p>
      </div>

      <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fit,minmax(180px,1fr))',gap:14,marginBottom:16}}>
        {kpi('Equipos online', onlineN+'/'+tele.length, offlineN+' offline', '#22c55e','monitor')}
        {kpi('Cumplimiento SLA', slaPct+'%', resolved.length+' resueltos', slaPct>=90?'#22c55e':slaPct>=70?'#f59e0b':'#ef4444','check')}
        {kpi('Tickets abiertos', open.length, breaching+' fuera de SLA', breaching>0?'#ef4444':'#2563eb','ticket')}
        {kpi('Alertas de disco', diskAlerts, diskAlerts>0?'requieren atención':'todo OK', diskAlerts>0?'#f59e0b':'#22c55e','alert')}
      </div>

      <Card style={{padding:'16px 18px',marginBottom:14}}>
        <div style={{fontSize:14,fontWeight:700,color:'#0f172a',marginBottom:12}}>Tickets creados (últimos 14 días)</div>
        <div style={{display:'flex',alignItems:'flex-end',gap:4,height:72}}>
          {trend.map((d,i)=>(
            <div key={i} title={d.key+': '+d.n+' tickets'} style={{flex:1,display:'flex',flexDirection:'column',alignItems:'center',gap:4}}>
              <div style={{width:'100%',height:Math.round(d.n/trendMax*54)+2,background:'#2563eb',borderRadius:3,opacity:d.n?1:0.25}}></div>
              <span style={{fontSize:9,color:'#94a3b8'}}>{d.label}</span>
            </div>
          ))}
        </div>
      </Card>

      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:14}}>
        <Card style={{padding:0}}>
          <div style={{padding:'12px 16px',borderBottom:'1px solid #f1f5f9',fontWeight:700,fontSize:14,color:'#0f172a'}}>Carga por empresa</div>
          {byCompany.length===0 ? <div style={{padding:'16px',fontSize:13,color:'#94a3b8'}}>Sin tickets.</div> : byCompany.map((c,i)=>(
            <div key={i} style={{padding:'10px 16px',borderBottom:i<byCompany.length-1?'1px solid #f8fafc':'none',display:'flex',alignItems:'center',gap:10}}>
              <div style={{flex:1,fontSize:13,color:'#0f172a',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{c.name}</div>
              <span style={{fontSize:11,color:'#94a3b8'}}>{c.total} total</span>
              <Badge label={c.open+' abiertos'} color={c.open>0?'orange':'green'} />
            </div>
          ))}
        </Card>

        <Card style={{padding:0}}>
          <div style={{padding:'12px 16px',borderBottom:'1px solid #f1f5f9',fontWeight:700,fontSize:14,color:'#0f172a'}}>Equipos con alerta</div>
          {tele.filter(d=>!d.online || (d.disk_total&&Number(d.disk_used)/Number(d.disk_total)*100>=90)).slice(0,8).map((d,i)=>(
            <div key={i} style={{padding:'10px 16px',borderBottom:'1px solid #f8fafc',display:'flex',alignItems:'center',gap:10}}>
              <span style={{width:8,height:8,borderRadius:999,background:d.online?'#f59e0b':'#cbd5e1',flexShrink:0}}></span>
              <div style={{flex:1,fontSize:13,color:'#0f172a',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{d.hostname||d.device_id}</div>
              <span style={{fontSize:11,color:'#94a3b8'}}>{!d.online?'offline':(Math.round(Number(d.disk_used)/Number(d.disk_total)*100)+'% disco')}</span>
            </div>
          ))}
          {tele.filter(d=>!d.online || (d.disk_total&&Number(d.disk_used)/Number(d.disk_total)*100>=90)).length===0 && <div style={{padding:'16px',fontSize:13,color:'#94a3b8'}}>Toda la flota saludable ✓</div>}
        </Card>
      </div>
    </div>
  );
}
window.ExecutiveDashboard = ExecutiveDashboard;
