// ═══ GENESIS BUILD inventario-f7.3-ean-cockpit · 2026-07-12 ═══
// Inventário 📦 · Paiol → Container (QR) → Item (EAN) · contagem bipada com divergências
const INV_TK = () => localStorage.getItem('genesis_token');
// carrega o leitor de planilhas: do próprio NAS primeiro (satélite-proof), CDN só de reserva
// badge de alerta do item: 🔴 vencido · 🟡 vencendo 90d · 📉 abaixo do mínimo
const invBadgeAlerta = (it) => {
  const bits = [];
  if (it.validade) {
    const dias = Math.floor((new Date(String(it.validade).split('T')[0] + 'T12:00') - Date.now()) / 864e5);
    if (dias < 0) bits.push(<span key="v" style={{ color: 'var(--rose)', fontWeight: 700 }}>🔴 vencido </span>);
    else if (dias <= 90) bits.push(<span key="v" style={{ color: 'var(--sun)', fontWeight: 700 }}>🟡 vence em {dias}d </span>);
  }
  if (it.qtd_min != null && Number(it.qtd_esperada) < Number(it.qtd_min)) bits.push(<span key="m" style={{ color: 'var(--rose)', fontWeight: 700 }}>📉 mín {Number(it.qtd_min)} </span>);
  return bits.length ? <React.Fragment>{bits}</React.Fragment> : null;
};

const invLoadXLSX = (onOk, onErr) => {
  if (window.XLSX) return onOk();
  const tenta = (src, depois) => {
    const sc = document.createElement('script');
    sc.src = src;
    sc.onload = () => window.XLSX ? onOk() : (depois ? depois() : (onErr && onErr()));
    sc.onerror = () => depois ? depois() : (onErr && onErr());
    document.head.appendChild(sc);
  };
  tenta('vendor/xlsx.full.min.js', () => tenta('https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js', null));
};
const INV_HDR = () => ({ Authorization: `Bearer ${INV_TK()}` });
const INV_JSON = (m, b) => ({ method: m, headers: { ...INV_HDR(), 'Content-Type': 'application/json' }, body: JSON.stringify(b) });
const invCard = { background: 'var(--surface)', border: '1px solid var(--line-soft)', borderRadius: 16, padding: '15px 17px', boxShadow: 'var(--shadow-sm)' };
// botão sólido no tema (fundo colorido + fonte branca, padrão do Bipar)
const invSolido = (cor) => ({ background: `var(--${cor})`, color: '#fff', borderColor: 'transparent' });
// botão-ícone quadrado com dica no hover (padrão Gangway 26–30px)
const InvIco = ({ ico, dica, onClick, cor, tam, disabled }) => (
  <button title={dica} aria-label={dica} onClick={onClick} disabled={disabled}
    style={{ height: tam || 30, minWidth: tam || 30, padding: '0 7px', flexShrink: 0, borderRadius: 9, border: cor ? 'none' : '1px solid var(--line-soft)', background: cor ? `var(--${cor})` : 'var(--surface)', color: cor ? '#fff' : 'var(--ink)', cursor: disabled ? 'default' : 'pointer', opacity: disabled ? 0.45 : 1, fontSize: 13.5, lineHeight: 1, display: 'grid', placeItems: 'center', fontFamily: 'var(--f)' }}>{ico}</button>
);

// ─── leitor de códigos: BarcodeDetector nativo (EAN+QR) · fallback digitação ───
const InvScanner = ({ titulo, onCode, onClose }) => {
  const videoRef = React.useRef(null);
  const [erro, setErro] = useState(null);
  const [manual, setManual] = useState('');
  const [suportado, setSuportado] = useState('checando');
  useEffect(() => {
    let vivo = true, stream = null, timer = null;
    (async () => {
      if (!('BarcodeDetector' in window)) { setSuportado('nao'); return; }
      try {
        const det = new window.BarcodeDetector({ formats: ['ean_13', 'ean_8', 'upc_a', 'code_128', 'qr_code'] });
        stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } });
        if (!vivo) { stream.getTracks().forEach(t => t.stop()); return; }
        setSuportado('sim');
        if (videoRef.current) { videoRef.current.srcObject = stream; await videoRef.current.play(); }
        const varrer = async () => {
          if (!vivo || !videoRef.current) return;
          try {
            const codes = await det.detect(videoRef.current);
            if (codes.length && codes[0].rawValue) { onCode(codes[0].rawValue.trim()); return; }
          } catch (e) {}
          timer = setTimeout(varrer, 220);
        };
        varrer();
      } catch (e) { setErro('Câmera indisponível — digita o código.'); setSuportado('nao'); }
    })();
    return () => { vivo = false; if (timer) clearTimeout(timer); if (stream) stream.getTracks().forEach(t => t.stop()); };
  }, []);
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9500, background: 'rgba(16,22,25,0.92)', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 16 }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 440, textAlign: 'center' }}>
        <div style={{ color: '#fff', fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 12 }}>{titulo || 'Bipar código'}</div>
        {suportado === 'sim' ? (
          <div style={{ position: 'relative', borderRadius: 18, overflow: 'hidden', background: '#000' }}>
            <video ref={videoRef} muted playsInline style={{ width: '100%', display: 'block', maxHeight: '46vh', objectFit: 'cover' }} />
            <div style={{ position: 'absolute', inset: '18% 12%', border: '2px solid rgba(255,255,255,0.75)', borderRadius: 12, pointerEvents: 'none' }} />
          </div>
        ) : suportado === 'nao' ? (
          <div style={{ color: 'rgba(255,255,255,0.75)', fontSize: 13, background: 'rgba(255,255,255,0.08)', borderRadius: 14, padding: '14px 16px' }}>
            {erro || 'Este navegador não lê códigos pela câmera — digita o número abaixo (fica impresso sob as barras).'}
          </div>
        ) : <div style={{ color: 'rgba(255,255,255,0.6)', fontFamily: 'var(--fm)', fontSize: 12 }}>abrindo a câmera…</div>}
        <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
          <input className="input" placeholder="ou digita: EAN · GEN-ITEM · INV-C" value={manual} onChange={e => setManual(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && manual.trim()) onCode(manual.trim()); }}
            style={{ flex: 1, boxSizing: 'border-box', textAlign: 'center', fontFamily: 'var(--fm)' }} />
          <button className="btn btn-primary" disabled={!manual.trim()} onClick={() => onCode(manual.trim())}>→</button>
        </div>
        <button onClick={onClose} style={{ marginTop: 12, background: 'rgba(255,255,255,0.14)', color: '#fff', border: 'none', borderRadius: 999, padding: '9px 22px', cursor: 'pointer', fontFamily: 'var(--fm)', fontSize: 13 }}>✕ fechar</button>
      </div>
    </div>
  );
};

// ─── modal de item (criar/editar) ───
const InvItemModal = ({ item, containerId, onClose, onSaved, toast }) => {
  const [d, setD] = useState(item ? { ...item, validade: item.validade ? String(item.validade).split('T')[0] : '' } : { nome: '', ean: '', qtd_esperada: '', unidade: 'un', tm_codigo: '', descricao: '', validade: '', lote: '', qtd_min: '' });
  const [scan, setScan] = useState(false);
  const [buscaEan, setBuscaEan] = useState(false);
  const [saving, setSaving] = useState(false);
  const up = (k, v) => setD(s => ({ ...s, [k]: v }));
  const salvar = async () => {
    if (!d.nome.trim()) { toast.push({ icon: 'x', title: 'Nome obrigatório' }); return; }
    setSaving(true);
    const corpo = { nome: d.nome, ean: d.ean, qtd_esperada: d.qtd_esperada === '' ? 0 : d.qtd_esperada, unidade: d.unidade, tm_codigo: d.tm_codigo, descricao: d.descricao, container_id: containerId, validade: d.validade || null, lote: d.lote || '', qtd_min: d.qtd_min };
    const r = item && item.id
      ? await fetch(`/api/inventario/itens/${item.id}`, INV_JSON('PUT', corpo))
      : await fetch('/api/inventario/itens', INV_JSON('POST', corpo));
    const resp = await r.json().catch(() => ({}));
    setSaving(false);
    if (r.ok) { toast.push({ icon: 'check', title: item ? 'Item atualizado' : 'Item cadastrado 📦' }); onSaved(); onClose(); }
    else toast.push({ icon: 'x', title: resp.error || 'Erro ao salvar' });
  };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '5vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 460, background: 'var(--cream)', borderRadius: 20, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--accent)', boxShadow: 'var(--shadow-lg)', padding: '20px 22px', marginBottom: 40 }}>
        <div style={{ fontFamily: 'var(--fs)', fontSize: 21, marginBottom: 14 }}>{item ? '✎ Editar item' : '＋ Novo item'}</div>

        <label className="label">Nome</label>
        <input className="input" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 12 }} value={d.nome} onChange={e => up('nome', e.target.value)} placeholder="Caneta azul BIC" />

        <label className="label">Código de barras (EAN) — opcional</label>
        <div style={{ display: 'flex', gap: 6, marginBottom: 12, alignItems: 'center' }}>
          <input className="input" style={{ flex: 1, minWidth: 0, boxSizing: 'border-box', fontFamily: 'var(--fm)' }} value={d.ean || ''} onChange={e => up('ean', e.target.value)} placeholder="789…" />
          <InvIco ico="📷" dica="Bipar o código de barras" tam={38} onClick={() => setScan(true)} />
          <InvIco ico="🔎" dica="Buscar EAN pelo nome (bases abertas)" tam={38} onClick={() => setBuscaEan(true)} />
        </div>

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 12px', marginBottom: 12 }}>
          <div style={{ minWidth: 0 }}>
            <label className="label">Qtd. em estoque</label>
            <input className="input" type="number" inputMode="decimal" style={{ width: '100%', boxSizing: 'border-box' }} value={d.qtd_esperada} onChange={e => up('qtd_esperada', e.target.value)} />
          </div>
          <div style={{ minWidth: 0 }}>
            <label className="label">Unidade</label>
            <input className="input" style={{ width: '100%', boxSizing: 'border-box' }} value={d.unidade || 'un'} onChange={e => up('unidade', e.target.value)} placeholder="un · cx · m" />
          </div>
          <div style={{ minWidth: 0 }}>
            <label className="label">Cód. TM Master</label>
            <input className="input" style={{ width: '100%', boxSizing: 'border-box', fontFamily: 'var(--fm)' }} value={d.tm_codigo || ''} onChange={e => up('tm_codigo', e.target.value)} />
          </div>
          <div style={{ minWidth: 0 }}>
            <label className="label">Lote</label>
            <input className="input" style={{ width: '100%', boxSizing: 'border-box', fontFamily: 'var(--fm)' }} value={d.lote || ''} onChange={e => up('lote', e.target.value)} />
          </div>
          <div style={{ minWidth: 0 }}>
            <label className="label">Validade</label>
            <input className="input" type="date" style={{ width: '100%', boxSizing: 'border-box' }} value={d.validade || ''} onChange={e => up('validade', e.target.value)} />
          </div>
          <div style={{ minWidth: 0 }}>
            <label className="label">Estoque mínimo ⚠</label>
            <input className="input" type="number" style={{ width: '100%', boxSizing: 'border-box' }} value={d.qtd_min == null ? '' : d.qtd_min} onChange={e => up('qtd_min', e.target.value)} placeholder="alerta 📉" />
          </div>
        </div>

        <label className="label">Descrição — opcional</label>
        <textarea className="input" rows={2} style={{ width: '100%', boxSizing: 'border-box', marginBottom: 12, resize: 'vertical', fontFamily: 'var(--f)' }} value={d.descricao || ''} onChange={e => up('descricao', e.target.value)} placeholder="detalhes, referência do fabricante…" />

        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, borderTop: '1px solid var(--line-soft)', paddingTop: 14 }}>
          <button className="btn" onClick={onClose}>Cancelar</button>
          <button className="btn btn-primary" onClick={salvar} disabled={saving}>{saving ? '…' : 'Salvar item'}</button>
        </div>
        {scan ? <InvScanner titulo="Bipar código de barras do produto" onCode={c => { up('ean', c); setScan(false); }} onClose={() => setScan(false)} /> : null}
        {buscaEan ? <InvEanBusca nomeInicial={d.nome} toast={toast} onClose={() => setBuscaEan(false)} onEscolher={p => {
          up('ean', String(p.gtin));
          if (!d.nome.trim() || confirm(`Usar o nome oficial?\n\n"${p.descricao}"`)) up('nome', p.descricao);
        }} /> : null}
      </div>
    </div>
  );
};

// ─── impressão de QRs do paiol ───
const InvQRPrint = ({ paiol, onClose }) => (
  <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '5vh 14px', overflowY: 'auto' }}>
    <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 700, background: '#fff', borderRadius: 20, padding: '22px', marginBottom: 40 }} id="inv-qr-print">
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }} className="no-print">
        <div style={{ fontFamily: 'var(--fs)', fontSize: 20 }}>🏷 Etiquetas · {paiol.nome}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button className="btn btn-primary btn-sm" onClick={() => window.print()}>🖨 Imprimir</button>
          <button className="btn btn-sm" onClick={onClose}>✕</button>
        </div>
      </div>
      <style>{`@media print { body * { visibility: hidden; } #inv-qr-print, #inv-qr-print * { visibility: visible; } #inv-qr-print { position: absolute; inset: 0; } .no-print { display: none !important; } }`}</style>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 18 }}>
        {(paiol.containers || []).map(c => (
          <div key={c.id} style={{ border: '2px solid #1F2A2E', borderRadius: 14, padding: '14px 12px', textAlign: 'center' }}>
            <QRImg url={c.codigo} size={140} />
            <div style={{ fontWeight: 700, fontSize: 15, marginTop: 8, color: '#1F2A2E' }}>{c.nome}</div>
            <div style={{ fontFamily: 'var(--fm)', fontSize: 12, color: '#666' }}>{c.codigo} · {paiol.nome}</div>
          </div>
        ))}
      </div>
    </div>
  </div>
);

// ─── seletor de coluna do import (módulo-escopo: regra de ouro) ───
const InvImportSel = ({ campo, rot, mapa, setMapa, colunas }) => (
  <div style={{ minWidth: 0 }}>
    <label className="label" style={{ fontSize: 11 }}>{rot}</label>
    <select className="select" style={{ width: '100%', boxSizing: 'border-box' }} value={mapa[campo]} onChange={e => setMapa(s => ({ ...s, [campo]: e.target.value === '' ? '' : Number(e.target.value) }))}>
      <option value="">—</option>
      {colunas.map(c => <option key={c.i} value={c.i}>{c.rot}</option>)}
    </select>
  </div>
);

// ─── importação com mapeamento de colunas ───
const InvImportModal = ({ containers, onClose, onDone, toast }) => {
  const [linhas, setLinhas] = useState(null);   // matriz crua
  const [mapa, setMapa] = useState({ nome: '', qtd: '', ean: '', tm: '', unidade: '' });
  const [contId, setContId] = useState(containers[0] ? containers[0].id : '');
  const [temCabecalho, setTemCabecalho] = useState(true);
  const [importando, setImportando] = useState(false);
  const lerArquivo = (e) => {
    const fs = Array.from(e.target.files || []);   // materializar ANTES (WebKit!)
    e.target.value = '';
    if (!fs[0]) return;
    const f = fs[0];
    const prosseguir = (wb) => {
      const ws = wb.Sheets[wb.SheetNames[0]];
      const m = window.XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' });
      setLinhas(m.filter(r => r.some(c => String(c).trim() !== '')));
    };
    const carregar = () => {
      const reader = new FileReader();
      reader.onload = ev => { try { prosseguir(window.XLSX.read(ev.target.result, { type: 'array' })); } catch (err) { toast.push({ icon: 'x', title: 'Não consegui ler o arquivo' }); } };
      reader.readAsArrayBuffer(f);
    };
    invLoadXLSX(carregar, () => toast.push({ icon: 'x', title: 'Não consegui carregar o leitor de planilhas' }));
  };
  const colunas = linhas && linhas[0] ? linhas[0].map((c, i) => ({ i, rot: temCabecalho ? String(c) : `Coluna ${i + 1}` })) : [];
  const corpo = linhas ? (temCabecalho ? linhas.slice(1) : linhas) : [];
  const importar = async () => {
    if (mapa.nome === '' || mapa.qtd === '') { toast.push({ icon: 'x', title: 'Aponta pelo menos Nome e Quantidade' }); return; }
    setImportando(true);
    const payload = corpo.map(r => ({
      nome: String(r[mapa.nome] ?? '').trim(),
      qtd: parseFloat(String(r[mapa.qtd] ?? '').replace(',', '.')) || 0,
      ean: mapa.ean !== '' ? String(r[mapa.ean] ?? '').trim() : '',
      tm_codigo: mapa.tm !== '' ? String(r[mapa.tm] ?? '').trim() : '',
      unidade: mapa.unidade !== '' ? String(r[mapa.unidade] ?? '').trim() : '',
    }));
    const r = await fetch('/api/inventario/import', INV_JSON('POST', { container_id: contId, linhas: payload }));
    const d = await r.json().catch(() => ({}));
    setImportando(false);
    if (r.ok) { toast.push({ icon: 'check', title: `Importado: ${d.criados} novos · ${d.atualizados} atualizados${d.pulados ? ` · ${d.pulados} pulados` : ''}` }); onDone(); onClose(); }
    else toast.push({ icon: 'x', title: d.error || 'Erro na importação' });
  };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '5vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 620, background: 'var(--cream)', borderRadius: 20, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--teal)', boxShadow: 'var(--shadow-lg)', padding: '20px 22px', marginBottom: 40 }}>
        <div style={{ fontFamily: 'var(--fs)', fontSize: 21, marginBottom: 4 }}>Importar planilha</div>
        <div style={{ fontSize: 12.5, color: 'var(--muted)', marginBottom: 14 }}>Qualquer xlsx/csv serve (inclusive a exportação do TM Master, quando chegar) — tu apontas qual coluna é o quê.</div>
        {!linhas ? (
          <label className="btn btn-primary" style={{ display: 'inline-flex', cursor: 'pointer' }}>
            📄 Escolher arquivo
            <input type="file" accept=".xlsx,.xls,.csv" style={{ display: 'none' }} onChange={lerArquivo} />
          </label>
        ) : (
          <>
            <div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap', marginBottom: 12 }}>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 12, color: 'var(--muted)' }}>{corpo.length} linha(s)</span>
              <label style={{ fontSize: 12.5, display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
                <input type="checkbox" checked={temCabecalho} onChange={e => setTemCabecalho(e.target.checked)} /> 1ª linha é cabeçalho
              </label>
            </div>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(110px,1fr))', gap: 8, marginBottom: 12 }}>
              <InvImportSel campo="nome" rot="Nome *" mapa={mapa} setMapa={setMapa} colunas={colunas} /><InvImportSel campo="qtd" rot="Quantidade *" mapa={mapa} setMapa={setMapa} colunas={colunas} /><InvImportSel campo="ean" rot="Cód. barras" mapa={mapa} setMapa={setMapa} colunas={colunas} /><InvImportSel campo="tm" rot="Cód. TM" mapa={mapa} setMapa={setMapa} colunas={colunas} /><InvImportSel campo="unidade" rot="Unidade" mapa={mapa} setMapa={setMapa} colunas={colunas} />
            </div>
            <label className="label">Destino (container)</label>
            <select className="select" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 14 }} value={contId} onChange={e => setContId(Number(e.target.value))}>
              {containers.map(c => <option key={c.id} value={c.id}>{c.nome} · {c.codigo}</option>)}
            </select>
            {mapa.nome !== '' ? (
              <div style={{ background: 'var(--cream-2)', borderRadius: 12, padding: '10px 12px', marginBottom: 14, fontSize: 12, fontFamily: 'var(--fm)', maxHeight: 120, overflowY: 'auto' }}>
                {corpo.slice(0, 5).map((r, i) => <div key={i}>→ {String(r[mapa.nome])}{mapa.qtd !== '' ? ` · ${r[mapa.qtd]}` : ''}{mapa.ean !== '' && r[mapa.ean] ? ` · ${r[mapa.ean]}` : ''}</div>)}
                {corpo.length > 5 ? <div style={{ color: 'var(--muted)' }}>… +{corpo.length - 5}</div> : null}
              </div>
            ) : null}
          </>
        )}
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, borderTop: '1px solid var(--line-soft)', paddingTop: 14 }}>
          <button className="btn" onClick={onClose}>Cancelar</button>
          {linhas ? <button className="btn btn-primary" onClick={importar} disabled={importando}>{importando ? 'Importando…' : `Importar ${corpo.length} linha(s)`}</button> : null}
        </div>
      </div>
    </div>
  );
};

// ─── a página ───
// ─── 🌳 InvTree — árvore expansível estilo TM Master/Windows ───
// nodes: [{nome, filhos:[], meta?:{itens, status: 'casado'|'aprox'|'nao_casado', extra?}, key}]
const InvTreeNo = ({ no, nivel, abertos, setAbertos, renderExtra, selecao }) => {
  const tem = no.filhos && no.filhos.length > 0;
  const aberto = abertos.has(no.key);
  const cor = no.meta && no.meta.status === 'nao_casado' ? 'var(--rose)' : no.meta && no.meta.status === 'aprox' ? 'var(--sun)' : 'var(--ink)';
  const marcado = selecao ? selecao.incluido(no.key) : true;
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 5, padding: '3px 0', paddingLeft: nivel * 18, minWidth: 0, opacity: marcado ? 1 : 0.45 }}>
        <button onClick={() => { if (!tem) return; const n = new Set(abertos); aberto ? n.delete(no.key) : n.add(no.key); setAbertos(n); }}
          style={{ border: 'none', background: 'transparent', cursor: tem ? 'pointer' : 'default', width: 16, padding: 0, color: 'var(--muted)', fontSize: 10 }}>
          {tem ? (aberto ? '▾' : '▸') : ''}
        </button>
        {selecao ? <input type="checkbox" checked={marcado} onChange={() => selecao.alternar(no.key)} style={{ accentColor: 'var(--teal)', cursor: 'pointer', flexShrink: 0 }} /> : null}
        <span style={{ flexShrink: 0 }}>{no.meta && no.meta.status === 'nao_casado' ? '🔴' : no.meta && no.meta.status === 'aprox' ? '🟡' : '📁'}</span>
        <span style={{ fontSize: 13, fontWeight: tem ? 600 : 500, color: cor, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0 }}>{no.nome}</span>
        {no.meta && no.meta.itens ? <span style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', flexShrink: 0 }}>{no.meta.itens} it</span> : null}
        {renderExtra ? renderExtra(no) : null}
      </div>
      {aberto && tem ? no.filhos.map(f => <InvTreeNo key={f.key} no={f} nivel={nivel + 1} abertos={abertos} setAbertos={setAbertos} renderExtra={renderExtra} selecao={selecao} />) : null}
    </div>
  );
};
const InvTree = ({ nodes, renderExtra, abrirTudo, selecao }) => {
  const [abertos, setAbertos] = useState(() => {
    if (!abrirTudo) return new Set();
    const all = new Set();
    const anda = ns => ns.forEach(n => { all.add(n.key); anda(n.filhos || []); });
    anda(nodes); return all;
  });
  return <div style={{ fontFamily: 'var(--f)' }}>{nodes.map(n => <InvTreeNo key={n.key} no={n} nivel={0} abertos={abertos} setAbertos={setAbertos} renderExtra={renderExtra} selecao={selecao} />)}</div>;
};

// monta a árvore a partir das linhas parseadas (caminho[] → nós aninhados com contagem)
const invMontarArvore = (linhas, metaPorKey) => {
  const raiz = { filhos: {}, };
  for (const l of linhas) {
    let no = raiz;
    let key = '';
    for (const nivel of (l.caminho || [])) {
      key = key ? key + '/' + nivel : nivel;
      if (!no.filhos[nivel]) no.filhos[nivel] = { nome: nivel, key, filhos: {}, itens: 0 };
      no = no.filhos[nivel];
    }
    no.itens = (no.itens || 0) + 1;
  }
  const converter = obj => Object.values(obj.filhos).map(n => ({
    nome: n.nome, key: n.key, filhos: converter(n),
    meta: { itens: n.itens || 0, ...((metaPorKey && metaPorKey[n.key]) || {}) },
  })).sort((a, b) => a.nome.localeCompare(b.nome));
  return converter(raiz);
};

// ─── 🚢 Import TM Master — lê o "Stock by Location" (.xls) direto do TM e povoa o paiol ───
const InvTmModal = ({ paiols, onClose, onDone, toast }) => {
  const [paiolId, setPaiolId] = useState(paiols[0] ? paiols[0].id : '');
  const [dados, setDados] = useState(null);      // {linhas, locais}
  const [progresso, setProgresso] = useState(null); // {feito, total, lote}
  const [resumo, setResumo] = useState(null);
  const [status, setStatus] = useState(null);    // narrador: cada etapa visível
  const [erro, setErro] = useState(null);        // erro exato, em vermelho, na tela
  const [conf, setConf] = useState(null);        // resultado do bater estoque
  const [ignorados, setIgnorados] = useState(new Set());
  const [excluidos, setExcluidos] = useState(new Set());   // seleção da prévia: nós desmarcados (subárvore fora)
  const temExcluidoAcima = (key) => {
    const partes = key.split('/');
    for (let i = 1; i <= partes.length; i++) if (excluidos.has(partes.slice(0, i).join('/'))) return true;
    return false;
  };
  const selecao = {
    incluido: key => !temExcluidoAcima(key),
    alternar: key => setExcluidos(prev => {
      const n = new Set(prev);
      if (temExcluidoAcima(key)) {   // religar: limpa este e ancestrais/descendentes excluídos no caminho
        const partes = key.split('/');
        for (let i = 1; i <= partes.length; i++) n.delete(partes.slice(0, i).join('/'));
        [...n].forEach(k => { if (k.startsWith(key + '/')) n.delete(k); });
      } else {
        n.add(key);
        [...n].forEach(k => { if (k.startsWith(key + '/')) n.delete(k); });   // um excluído basta pro ramo
      }
      return n;
    }),
  };
  const linhaIncluida = l => !temExcluidoAcima((l.caminho || []).join('/'));
  const linhasSelecionadas = () => (dados ? dados.linhas.filter(linhaIncluida) : []);
  const fileRef = React.useRef(null);
  const falha = (titulo, e) => {
    const msg = (e && (e.message || String(e))) || '';
    console.error('[TM import]', titulo, e);
    setStatus(null); setErro(`${titulo}${msg ? ` — ${msg}` : ''}`);
  };

  // ── autodiagnóstico na abertura: leitor, vendor, paióis ──
  const [diag, setDiag] = useState({ leitor: '⏳ verificando…', vendor: '⏳', paiois: paiols.length });
  useEffect(() => {
    let vivo = true;
    // o arquivo local existe? (HTTP status exato)
    fetch('vendor/xlsx.full.min.js', { method: 'HEAD' })
      .then(r => vivo && setDiag(d => ({ ...d, vendor: r.ok ? `✓ no NAS (HTTP ${r.status})` : `✗ HTTP ${r.status} — não está em public/vendor/` })))
      .catch(e => vivo && setDiag(d => ({ ...d, vendor: `✗ inacessível (${e.message})` })));
    // o leitor carrega?
    invLoadXLSX(
      () => vivo && setDiag(d => ({ ...d, leitor: `✓ carregado (v${window.XLSX && window.XLSX.version || '?'})` })),
      () => vivo && setDiag(d => ({ ...d, leitor: '✗ FALHOU — local e reserva' }))
    );
    return () => { vivo = false; };
  }, []);
  const criarPaiolTm = async () => {
    const r = await fetch('/api/inventario/paiols', INV_JSON('POST', { nome: 'TM Master' }));
    if (r.ok) { const p = await r.json(); toast.push({ icon: 'check', title: 'Paiol TM Master criado' }); setPaiolId(p.id); onDone && onDone(); setDiag(d => ({ ...d, paiois: d.paiois + 1 })); paiols.push({ id: p.id, nome: p.nome }); }
    else { const d = await r.json().catch(() => ({})); falha('Não consegui criar o paiol', { message: d.error }); }
  };

  const parseDevEx = (rows) => {
    // formato agrupado: linha com 'USD' = localização; linhas seguintes = itens (qtd nas cols ~7/10)
    const linhas = [];
    let local = null;
    for (const row of rows) {
      const cels = Array.isArray(row) ? row : [];
      const nome = String(cels[0] ?? '').trim();
      if (!nome) continue;
      const temUSD = cels.some(v => String(v).trim() === 'USD');
      if (temUSD) { local = (nome === '.' ? null : nome); continue; }
      if (!local || nome.startsWith('Item name')) continue;
      const num = c => { const v = parseFloat(cels[c]); return isNaN(v) ? 0 : v; };
      let qtd = num(10) || num(7);
      if (!qtd) { for (let c = 4; c <= 11; c++) { if (num(c) > 0) { qtd = num(c); break; } } }
      const m = nome.match(/\(([^()]*)\)\s*$/);
      const tm = m ? m[1].trim() : '';
      const limpo = nome.replace(/\s*\([^()]*\)\s*$/, '').trim() || nome;
      const caminho = local.split('/').map(x => x.trim()).filter(Boolean);
      linhas.push({ local, caminho, nome: limpo, tm_codigo: tm, qtd });
    }
    return linhas;
  };

  const parseCsvTm = (rows) => {
    // fallback: CSV com colunas nomeadas local/nome/tm_codigo/qtd
    const head = (rows[0] || []).map(h => String(h).toLowerCase().trim());
    const ix = c => head.indexOf(c);
    if (ix('local') < 0 || ix('nome') < 0 || ix('qtd') < 0) return null;
    return rows.slice(1).filter(r => String(r[ix('local')] || '').trim()).map(r => ({
      local: String(r[ix('local')]).trim(),
      caminho: String(r[ix('local')]).split('/').map(x => x.trim()).filter(Boolean),
      nome: String(r[ix('nome')] || '').trim(),
      tm_codigo: String(ix('tm_codigo') >= 0 ? (r[ix('tm_codigo')] || '') : '').trim(),
      qtd: parseFloat(r[ix('qtd')]) || 0,
    }));
  };

  const agregar = (linhas) => {
    // duplicatas (mesmo local+nome+tm) somam as quantidades
    const mapa = {};
    for (const l of linhas) {
      if (!l.nome) continue;
      const k = `${(l.caminho || [l.local]).join('/')}␟${l.nome.toLowerCase()}␟${l.tm_codigo}`;
      if (mapa[k]) mapa[k].qtd += l.qtd; else mapa[k] = { ...l };
    }
    return Object.values(mapa);
  };

  const lerArquivo = (e) => {
    const fs = Array.from(e.target.files || []);   // WebKit: materializar ANTES
    e.target.value = '';
    const f = fs[0]; if (!f) return;
    setErro(null); setDados(null);
    setStatus(`📄 ${f.name} (${(f.size / 1024).toFixed(0)} KB) — carregando o leitor…`);
    const prosseguir = (wb) => {
      try {
        setStatus('🧮 Interpretando as linhas…');
        const ws = wb.Sheets[wb.SheetNames[0]];
        if (!ws) return falha('A planilha veio sem abas');
        const rows = window.XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' });
        setStatus(`🧮 ${rows.length} linhas na planilha — separando locais e itens…`);
        let linhas = parseCsvTm(rows);
        if (!linhas) linhas = parseDevEx(rows);
        linhas = agregar(linhas.filter(l => l.local && l.nome));
        if (!linhas.length) return falha('Não reconheci o formato', { message: 'esperado o Stock by Location do TM (.xls) ou CSV com colunas local/nome/tm_codigo/qtd' });
        setStatus(null);
        setDados({ linhas, locais: new Set(linhas.map(l => l.local)).size });
      } catch (err) { falha('Erro ao interpretar a planilha', err); }
    };
    const ir = () => {
      setStatus('📖 Lendo o arquivo…');
      const reader = new FileReader();
      reader.onerror = () => falha('Erro ao ler o arquivo do disco', reader.error);
      reader.onload = ev => {
        try { prosseguir(window.XLSX.read(new Uint8Array(ev.target.result), { type: 'array' })); }
        catch (err) { falha('O leitor não entendeu o arquivo', err); }
      };
      reader.readAsArrayBuffer(f);
    };
    invLoadXLSX(ir, () => falha('Leitor de planilhas indisponível', { message: 'confere se vendor/xlsx.full.min.js está no NAS (e a reserva na internet também falhou)' }));
  };

  const importar = async () => {
    if (!dados || !paiolId) return;
    setErro(null);
    const LOTE = 400;
    const sel = linhasSelecionadas();
    const total = sel.length;
    const lotes = Math.ceil(total / LOTE);
    const acc = { containers_criados: 0, criados: 0, atualizados: 0, pulados: 0 };
    setProgresso({ feito: 0, total, lote: `preparando ${lotes} lotes…` });
    for (let i = 0; i < total; i += LOTE) {
      const n = Math.floor(i / LOTE) + 1;
      setProgresso({ feito: i, total, lote: `lote ${n}/${lotes} — enviando…` });
      let r, d;
      try {
        r = await fetch('/api/inventario/import-tm', INV_JSON('POST', { paiol_id: paiolId, linhas: sel.slice(i, i + LOTE) }));
      } catch (err) { setProgresso(null); return falha(`Rede caiu no lote ${n}/${lotes} — o que entrou ficou salvo; roda de novo que continua de onde parou`, err); }
      d = await r.json().catch(() => ({}));
      if (!r.ok) { setProgresso(null); return falha(`Servidor recusou o lote ${n}/${lotes} (HTTP ${r.status})`, { message: d.error || 'sem detalhe' }); }
      acc.containers_criados += d.containers_criados; acc.criados += d.criados; acc.atualizados += d.atualizados; acc.pulados += d.pulados;
      setProgresso({ feito: Math.min(i + LOTE, total), total, lote: `lote ${n}/${lotes} ✓ · ${acc.criados} criados · ${acc.atualizados} atualizados` });
    }
    setProgresso(null); setResumo(acc);
    toast.push({ icon: 'check', title: '🚢 Importação concluída!', body: `${acc.criados} criados · ${acc.atualizados} atualizados` });
    onDone && onDone();
  };

  const bater = async () => {
    if (!dados || !paiolId) return;
    setErro(null); setStatus('🔍 Batendo a árvore do TM com a nossa…');
    try {
      const r = await fetch('/api/inventario/conferir-tm', INV_JSON('POST', { paiol_id: paiolId, linhas: linhasSelecionadas() }));
      const d = await r.json().catch(() => ({}));
      setStatus(null);
      if (!r.ok) return falha('Conferência recusada', { message: d.error });
      setConf(d);
    } catch (e) { falha('Rede falhou na conferência', e); }
  };

  const criarLocal = async (loc) => {
    const key = loc.caminho.join('/');
    const linhasDoLocal = linhasSelecionadas().filter(l => (l.caminho || []).join('/') === key);
    setStatus(`＋ Criando ${key} e importando ${linhasDoLocal.length} itens…`);
    try {
      const r = await fetch('/api/inventario/import-tm', INV_JSON('POST', { paiol_id: paiolId, linhas: linhasDoLocal }));
      setStatus(null);
      if (r.ok) { toast.push({ icon: 'check', title: `📁 ${key} criado com ${linhasDoLocal.length} itens` }); bater(); onDone && onDone(); }
      else { const d = await r.json().catch(() => ({})); falha('Não consegui criar', { message: d.error }); }
    } catch (e) { setStatus(null); falha('Rede falhou', e); }
  };

  const metaConf = conf ? Object.fromEntries(conf.locais.map(l => [l.caminho.join('/'), { status: l.status }])) : null;

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '6vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: conf ? 640 : 520, background: 'var(--cream)', borderRadius: 20, boxShadow: 'var(--shadow-lg)', border: '1px solid var(--line-soft)', borderTop: '3px solid var(--teal)', padding: '20px 22px', marginBottom: 40 }}>
        <div className="t-eyebrow" style={{ color: 'var(--teal)', marginBottom: 3 }}>{conf ? 'bater estoque' : 'importação oficial'}</div>
        <div style={{ fontFamily: 'var(--fs)', fontSize: 21, marginBottom: 6 }}>{conf ? '🔍 Conferência TM × Genesis' : '🚢 TM Master · Stock by Location'}</div>
        {conf ? (
          <div>
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 12 }}>
              {[['✓ casados', conf.resumo.casados, 'var(--leaf)'], ['≈ aproximados', conf.resumo.aprox, 'var(--sun)'], ['✗ não casados', conf.resumo.nao_casados, 'var(--rose)'],
                ['± divergências', conf.resumo.divergencias, 'var(--sun)'], ['só no TM', conf.resumo.so_no_tm, 'var(--sky)'], ['só no Genesis', conf.resumo.so_no_genesis, 'var(--teal)']].map(([rot, n, cor]) => (
                <span key={rot} style={{ background: `color-mix(in srgb, ${cor} 12%, var(--surface))`, border: `1px solid color-mix(in srgb, ${cor} 30%, transparent)`, color: cor, borderRadius: 10, padding: '4px 10px', fontFamily: 'var(--fm)', fontSize: 11.5, fontWeight: 700 }}>{n} {rot}</span>
              ))}
            </div>
            <div style={{ background: 'var(--surface)', border: '1px solid var(--line-soft)', borderRadius: 12, padding: '10px 12px', maxHeight: 240, overflowY: 'auto', marginBottom: 10 }}>
              <InvTree nodes={invMontarArvore(linhasSelecionadas(), metaConf)} abrirTudo={conf.resumo.nao_casados > 0 && conf.resumo.nao_casados <= 15} renderExtra={(no) => {
                const loc = conf.locais.find(l => l.caminho.join('/') === no.key);
                if (!loc || loc.status !== 'nao_casado' || ignorados.has(no.key)) return null;
                return (
                  <span style={{ display: 'flex', gap: 4, flexShrink: 0, marginLeft: 6 }}>
                    <button className="btn btn-sm" style={{ padding: '2px 8px', fontSize: 10.5 }} onClick={() => criarLocal(loc)}>＋ criar</button>
                    <button className="btn ghost btn-sm" style={{ padding: '2px 8px', fontSize: 10.5, color: 'var(--muted)' }} onClick={() => setIgnorados(new Set([...ignorados, no.key]))}>ignorar</button>
                  </span>
                );
              }} />
            </div>
            {conf.locais.some(l => l.divergencias.length) ? (
              <div style={{ background: 'var(--surface)', border: '1px solid var(--line-soft)', borderTop: '3px solid var(--sun)', borderRadius: 12, padding: '10px 14px', maxHeight: 200, overflowY: 'auto', marginBottom: 10 }}>
                <div style={{ fontFamily: 'var(--fm)', fontSize: 10.5, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--sun)', marginBottom: 6 }}>± quantidades divergentes (atualizar no TM)</div>
                {conf.locais.filter(l => l.divergencias.length).map(l => l.divergencias.map((dv, i) => (
                  <div key={l.caminho.join('/') + i} style={{ display: 'flex', gap: 8, alignItems: 'baseline', fontSize: 12.5, padding: '4px 0', borderTop: '1px dashed var(--line-soft)', minWidth: 0 }}>
                    <b style={{ flexShrink: 0, maxWidth: 170, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{dv.nome}</b>
                    <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--muted)', fontFamily: 'var(--fm)', fontSize: 11 }}>{l.caminho.join(' › ')}</span>
                    <span style={{ fontFamily: 'var(--fm)', fontWeight: 700, flexShrink: 0 }}><span style={{ color: 'var(--teal)' }}>{dv.genesis}</span> × <span style={{ color: 'var(--sun)' }}>{dv.tm}</span> {dv.unidade || ''}</span>
                  </div>
                )))}
              </div>
            ) : null}
            {status ? <div style={{ background: 'var(--cream-2)', borderRadius: 10, padding: '8px 12px', fontFamily: 'var(--fm)', fontSize: 11.5, marginBottom: 8 }}>{status}</div> : null}
            {erro ? <div style={{ background: 'color-mix(in srgb, var(--rose) 12%, var(--surface))', border: '1px solid var(--rose)', borderRadius: 10, padding: '8px 12px', fontSize: 12, color: 'var(--rose)', marginBottom: 8 }}>⚠ {erro}</div> : null}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
              <button className="btn" onClick={() => setConf(null)}>← voltar</button>
              <button className="btn btn-primary" onClick={onClose}>Fechar</button>
            </div>
          </div>
        ) : resumo ? (
          <div>
            <div style={{ background: 'var(--cream-2)', borderRadius: 12, padding: '14px 16px', fontSize: 13.5, lineHeight: 1.8 }}>
              📦 <b>{resumo.containers_criados}</b> locais novos viraram containers<br/>
              ＋ <b>{resumo.criados}</b> itens criados · ✎ <b>{resumo.atualizados}</b> atualizados{resumo.pulados ? <React.Fragment><br/>⤼ {resumo.pulados} linhas puladas</React.Fragment> : null}
            </div>
            <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 14 }}>
              <button className="btn btn-primary" onClick={onClose}>Fechar</button>
            </div>
          </div>
        ) : progresso ? (
          <div style={{ padding: '10px 0' }}>
            <div style={{ fontSize: 13, marginBottom: 4 }}>Importando… {progresso.feito} / {progresso.total}</div>
            {progresso.lote ? <div style={{ fontFamily: 'var(--fm)', fontSize: 11, color: 'var(--muted)', marginBottom: 8 }}>{progresso.lote}</div> : null}
            <div style={{ height: 10, background: 'var(--cream-3)', borderRadius: 999 }}>
              <div style={{ height: '100%', width: `${Math.round(100 * progresso.feito / progresso.total)}%`, background: 'var(--teal)', borderRadius: 999, transition: 'width 0.3s' }} />
            </div>
          </div>
        ) : (
          <div>
            <div style={{ fontSize: 13, color: 'var(--muted)', marginBottom: 10 }}>Exporta o relatório <b>Stock by Location</b> do TM Master (.xls) e sobe aqui — cada localização vira um container e os itens chegam com part number e quantidade. Rodar de novo só atualiza (não duplica).</div>
            <div style={{ background: 'var(--cream-2)', borderRadius: 12, padding: '10px 14px', marginBottom: 12, fontFamily: 'var(--fm)', fontSize: 11.5, lineHeight: 1.9 }}>
              <div>leitor de planilhas: {diag.leitor}</div>
              <div>vendor/xlsx.full.min.js: {diag.vendor}</div>
              <div>paióis disponíveis: {diag.paiois}{diag.paiois === 0 ? <button className="btn btn-sm" style={{ marginLeft: 8, padding: '3px 10px' }} onClick={criarPaiolTm}>＋ criar "TM Master"</button> : null}</div>
              <div style={{ color: 'var(--muted)' }}>build: tm-cockpit-v8</div>
            </div>
            <label className="label">Paiol de destino</label>
            <select className="select" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 12 }} value={paiolId} onChange={e => setPaiolId(Number(e.target.value))}>
              {paiols.map(p => <option key={p.id} value={p.id}>{p.nome}</option>)}
            </select>
            <input ref={fileRef} type="file" accept=".xls,.xlsx,.csv" style={{ display: 'none' }} onChange={lerArquivo} />
            <button className="btn btn-sm" onClick={() => fileRef.current && fileRef.current.click()}>📄 Escolher arquivo…</button>
            {status ? <div style={{ marginTop: 12, background: 'var(--cream-2)', borderRadius: 12, padding: '10px 14px', fontSize: 12.5, fontFamily: 'var(--fm)' }}>{status}</div> : null}
            {erro ? <div style={{ marginTop: 12, background: 'color-mix(in srgb, var(--rose) 12%, var(--surface))', border: '1px solid var(--rose)', borderRadius: 12, padding: '10px 14px', fontSize: 12.5, color: 'var(--rose)', wordBreak: 'break-word' }}>⚠ {erro}</div> : null}
            {dados ? (
              <div style={{ marginTop: 12 }}>
                <div style={{ background: 'var(--cream-2)', borderRadius: 12, padding: '10px 14px', fontSize: 13, marginBottom: 8 }}>
                  ✓ <b>{dados.linhas.length}</b> itens em <b>{dados.locais}</b> localizações — a estrutura detectada (o "/" vira subpasta):
                </div>
                <div style={{ background: 'var(--surface)', border: '1px solid var(--line-soft)', borderRadius: 12, padding: '10px 12px', maxHeight: 260, overflowY: 'auto' }}>
                  <InvTree nodes={invMontarArvore(dados.linhas)} selecao={selecao} />
                </div>
                <div style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', marginTop: 6 }}>desmarca um 📁 pra deixar o ramo inteiro de fora — importa e bate só o que está marcado ({linhasSelecionadas().length} de {dados.linhas.length} itens)</div>
              </div>
            ) : null}
            <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14, borderTop: '1px solid var(--line-soft)', paddingTop: 14, flexWrap: 'wrap' }}>
              <button className="btn" onClick={onClose}>Cancelar</button>
              {dados ? <button className="btn btn-sm" onClick={bater} disabled={!paiolId || !linhasSelecionadas().length}>🔍 Bater estoque ({linhasSelecionadas().length})</button> : null}
              {dados ? <button className="btn btn-primary" onClick={importar} disabled={!paiolId || !linhasSelecionadas().length}>🚢 Importar {linhasSelecionadas().length} itens</button> : null}
            </div>
          </div>
        )}
      </div>
    </div>
  );
};

// ─── 🔎 Busca de EAN no Cosmos (banco de produtos do Brasil) ───
const INV_EAN_FONTES = [
  { id: 'off', rot: '🍎 Open Food Facts' },
  { id: 'opf', rot: '🖊 Open Products Facts' },
  { id: 'upc', rot: '🌎 UPCitemdb' },
  { id: 'cosmos', rot: '🇧🇷 Cosmos' },
];

const InvEanBusca = ({ nomeInicial, onEscolher, onClose, toast }) => {
  const [q, setQ] = useState(nomeInicial || '');
  const [fonte, setFonte] = useState('');            // '' = todas
  const [status, setStatus] = useState({});          // id → {fase:'buscando'|'ok'|'erro'|'sem_token', n, erro}
  const [resultados, setResultados] = useState(null);
  const [buscando, setBuscando] = useState(false);
  const [tokenBox, setTokenBox] = useState(false);
  const [token, setToken] = useState('');
  const [diag, setDiag] = useState(null);            // null · 'carregando' · objeto
  const idBusca = React.useRef(0);

  // dispara UMA consulta por servidor, em paralelo — dá pra ver cada um respondendo
  const buscar = (fonteAgora) => {
    const f = fonteAgora !== undefined ? fonteAgora : fonte;
    const termo = q.trim();
    if (termo.length < 3) return;
    const id = ++idBusca.current;
    const alvos = f ? INV_EAN_FONTES.filter(x => x.id === f) : INV_EAN_FONTES;
    let pendentes = alvos.length;
    setResultados([]); setDiag(null); setBuscando(true);
    setStatus(Object.fromEntries(alvos.map(x => [x.id, { fase: 'buscando' }])));
    alvos.forEach(al => {
      fetch(`/api/inventario/ean-busca?q=${encodeURIComponent(termo)}&fonte=${al.id}`, { headers: INV_HDR() })
        .then(async r => {
          if (id !== idBusca.current) return;
          const d = await r.json().catch(() => ({}));
          if (r.status === 428) { setStatus(st => ({ ...st, [al.id]: { fase: 'sem_token' } })); return; }
          if (!r.ok) { setStatus(st => ({ ...st, [al.id]: { fase: 'erro', erro: d.error || `HTTP ${r.status}` } })); return; }
          const prods = d.produtos || [];
          setStatus(st => ({ ...st, [al.id]: d.erro ? { fase: 'erro', erro: d.erro } : { fase: 'ok', n: prods.length } }));
          if (prods.length) setResultados(rs => {
            const base = rs || [];
            const vistos = new Set(base.map(p => p.fonte + '|' + p.gtin));
            return [...base, ...prods.filter(p => !vistos.has(p.fonte + '|' + p.gtin))];
          });
        })
        .catch(() => { if (id === idBusca.current) setStatus(st => ({ ...st, [al.id]: { fase: 'erro', erro: 'rede falhou — o satélite piscou?' } })); })
        .finally(() => { if (id === idBusca.current) { pendentes -= 1; if (pendentes <= 0) setBuscando(false); } });
    });
  };

  const salvarToken = async () => {
    if (!token.trim()) return;
    const r = await fetch('/api/inventario/config-ean', INV_JSON('PUT', { token: token.trim() }));
    if (r.ok) { toast.push({ icon: 'check', title: 'Cosmos configurado 🔌' }); setTokenBox(false); setToken(''); if (q.trim().length >= 3) buscar(); }
    else toast.push({ icon: 'x', title: 'Não consegui salvar o token' });
  };

  const rodarDiag = () => {
    setDiag('carregando'); setStatus({}); setResultados(null);
    fetch('/api/inventario/ean-diag', { headers: INV_HDR() })
      .then(r => r.ok ? r.json() : null).then(d => setDiag(d || { falhou: true })).catch(() => setDiag({ falhou: true }));
  };

  const faseIcone = (st) => {
    if (!st) return null;
    if (st.fase === 'buscando') return <span style={{ color: 'var(--sun)' }}>⏳ buscando…</span>;
    if (st.fase === 'sem_token') return (
      <span style={{ color: 'var(--muted)' }}>🔒 não configurado
        <button onClick={() => setTokenBox(v => !v)} style={{ marginLeft: 6, border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--sky)', fontSize: 10.5, fontFamily: 'var(--fm)', textDecoration: 'underline', padding: 0 }}>configurar</button>
      </span>
    );
    if (st.fase === 'erro') return <span style={{ color: 'var(--rose)' }}>⚠ {st.erro}</span>;
    return st.n > 0 ? <span style={{ color: 'var(--leaf)', fontWeight: 700 }}>✅ {st.n} resultado{st.n > 1 ? 's' : ''}</span> : <span style={{ color: 'var(--muted)' }}>○ nada encontrado</span>;
  };

  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9400, background: 'rgba(31,42,46,0.5)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '6vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 560, background: 'var(--cream)', borderRadius: 18, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--sky)', padding: '18px 20px', boxShadow: 'var(--shadow-lg)', marginBottom: 40, maxHeight: '84vh', display: 'flex', flexDirection: 'column' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 2 }}>
          <div style={{ fontFamily: 'var(--fs)', fontSize: 19, flex: 1 }}>🔎 Buscar EAN pelo nome</div>
          <InvIco ico="🩺" dica="Diagnóstico: testar a conexão com todos os servidores" onClick={rodarDiag} />
          <InvIco ico="✕" dica="Fechar" onClick={onClose} />
        </div>
        <div style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', marginBottom: 8 }}>escolhe o servidor ou busca em todos — grátis, com cache de bordo</div>

        <div style={{ display: 'flex', gap: 5, flexWrap: 'wrap', marginBottom: 8 }}>
          {[['', '🌐 todas'], ...INV_EAN_FONTES.map(x => [x.id, x.rot])].map(([val, rot]) => (
            <button key={val} onClick={() => { setFonte(val); if (q.trim().length >= 3) buscar(val); }}
              style={{ cursor: 'pointer', border: `1px solid ${fonte === val ? 'var(--sky)' : 'var(--line-soft)'}`, background: fonte === val ? 'color-mix(in srgb, var(--sky) 14%, var(--surface))' : 'var(--surface)', color: fonte === val ? 'var(--sky)' : 'var(--muted)', borderRadius: 999, padding: '4px 10px', fontSize: 10.5, fontWeight: 600, fontFamily: 'var(--f)' }}>{rot}</button>
          ))}
        </div>

        <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
          <input className="input" autoFocus style={{ flex: 1, boxSizing: 'border-box' }} placeholder="ex.: caneta bic azul…" value={q}
            onChange={e => setQ(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') buscar(); }} />
          <button className="btn btn-primary btn-sm" onClick={() => buscar()} disabled={buscando || q.trim().length < 3}>{buscando ? '…' : '🔎 Buscar'}</button>
        </div>

        {tokenBox ? (
          <div style={{ background: 'var(--cream-2)', borderRadius: 12, padding: '10px 12px', fontSize: 12, marginBottom: 8 }}>
            O Cosmos é a turbina opcional (plano pago em <span style={{ fontFamily: 'var(--fm)' }}>cosmos.bluesoft.com.br</span>). Cola o token de API aqui — configura uma vez, vale pra todo mundo a bordo.
            <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
              <input className="input" style={{ flex: 1, boxSizing: 'border-box', fontFamily: 'var(--fm)' }} placeholder="Token do Cosmos…" value={token} onChange={e => setToken(e.target.value)} />
              <button className="btn btn-sm" style={invSolido('teal')} onClick={salvarToken} disabled={!token.trim()}>Salvar</button>
            </div>
          </div>
        ) : null}

        {/* ── painel de acompanhamento da busca, servidor a servidor ── */}
        {Object.keys(status).length ? (
          <div style={{ background: 'var(--cream-2)', border: '1px solid var(--line-soft)', borderRadius: 12, padding: '7px 12px', marginBottom: 8 }}>
            {INV_EAN_FONTES.filter(x => status[x.id]).map(x => (
              <div key={x.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '3px 0', fontSize: 11.5, minWidth: 0 }}>
                <span style={{ width: 158, flexShrink: 0, fontWeight: 600 }}>{x.rot}</span>
                <span style={{ flex: 1, minWidth: 0, fontFamily: 'var(--fm)', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{faseIcone(status[x.id])}</span>
              </div>
            ))}
          </div>
        ) : null}

        {/* ── diagnóstico dos servidores ── */}
        {diag ? (
          <div style={{ background: 'var(--cream-2)', border: '1px solid var(--line-soft)', borderRadius: 12, padding: '9px 12px', marginBottom: 8 }}>
            {diag === 'carregando' ? <div className="empty" style={{ padding: 10 }}>🩺 testando os servidores (pode levar uns segundos no satélite)…</div>
              : diag.falhou ? <div style={{ fontSize: 12, color: 'var(--rose)' }}>⚠ o diagnóstico falhou — o backend está atualizado (build f7.3)?</div>
              : (
              <React.Fragment>
                <div style={{ fontFamily: 'var(--fm)', fontSize: 10, letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--teal)', marginBottom: 5 }}>🩺 diagnóstico de conexão</div>
                {(diag.servidores || []).map(sv => (
                  <div key={sv.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '3px 0', fontSize: 11.5, minWidth: 0 }}>
                    <span style={{ width: 148, flexShrink: 0, fontWeight: 600 }}>{sv.nome}</span>
                    <span style={{ flex: 1, minWidth: 0, fontFamily: 'var(--fm)', fontSize: 10.5, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                      {sv.configurado === false ? <span style={{ color: 'var(--muted)' }}>🔒 {sv.erro}</span>
                        : sv.alcancavel ? <span style={{ color: 'var(--leaf)', fontWeight: 700 }}>✓ no ar · {sv.ms}ms · HTTP {sv.http}</span>
                        : <span style={{ color: 'var(--rose)' }}>✗ {sv.erro || 'fora do ar'}</span>}
                    </span>
                  </div>
                ))}
                <div style={{ fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)', marginTop: 6, borderTop: '1px dashed var(--line-soft)', paddingTop: 5 }}>
                  Node {diag.node} · fetch nativo: {diag.fetch_nativo ? 'sim ✓' : 'não — usando fallback https ⚠'} · 📦 {diag.cache_fichas} ficha{diag.cache_fichas === 1 ? '' : 's'} no cache de bordo
                </div>
              </React.Fragment>
            )}
          </div>
        ) : null}

        <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
          {resultados === null ? null
            : resultados.length === 0 && !buscando ? <div className="empty" style={{ padding: 20 }}>nada encontrado — tenta outro nome ou outro servidor.</div>
            : resultados.map(p => (
              <button key={p.fonte + '|' + p.gtin} onClick={() => { onEscolher(p); onClose(); }} style={{ width: '100%', textAlign: 'left', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 10, padding: '9px 10px', background: 'var(--surface)', border: '1px solid var(--line-soft)', borderRadius: 12, marginBottom: 6, minWidth: 0 }}>
                {p.thumb ? <img src={p.thumb} alt="" style={{ width: 38, height: 38, objectFit: 'contain', flexShrink: 0, background: '#fff', borderRadius: 8 }} /> : <span style={{ width: 38, textAlign: 'center', flexShrink: 0 }}>📦</span>}
                <span style={{ flex: 1, minWidth: 0 }}>
                  <span style={{ display: 'block', fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{p.descricao}</span>
                  <span style={{ display: 'block', fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)' }}>{p.marca ? p.marca + ' · ' : ''}EAN {p.gtin}{p.fonte ? ' · ' + p.fonte : ''}</span>
                </span>
                <span style={{ fontFamily: 'var(--fm)', fontSize: 11, color: 'var(--sky)', flexShrink: 0 }}>usar ›</span>
              </button>
            ))}
        </div>
      </div>
    </div>
  );
};

// ─── 📦 F2: entrada/saída rápida (sheet) ───
const InvMovSheet = ({ item, sentido, onClose, onDone, toast }) => {
  const [qtd, setQtd] = useState('');
  const [nota, setNota] = useState('');
  const [saving, setSaving] = useState(false);
  const entrada = sentido === 'entrada';
  const confirmar = async () => {
    const q = parseFloat(String(qtd).replace(',', '.'));
    if (!q || q <= 0 || saving) return;
    setSaving(true);
    const delta = entrada ? q : -q;
    const motivo = `${entrada ? '📥 entrada' : '📤 saída'}${nota.trim() ? ' — ' + nota.trim() : ''}`;
    try {
      const r = await fetch(`/api/inventario/itens/${item.id}/ajuste`, INV_JSON('POST', { delta, motivo }));
      const d = await r.json().catch(() => ({}));
      setSaving(false);
      if (r.ok) { toast.push({ icon: 'check', title: `${entrada ? '📥 +' : '📤 −'}${q} ${item.unidade || ''} · saldo ${d.saldo}` }); onDone && onDone(); onClose(); }
      else toast.push({ icon: 'x', title: d.error || 'Erro na movimentação' });
    } catch (e) {
      setSaving(false);
      toast.push({ icon: 'x', title: 'Rede falhou — nada foi registrado', body: 'Tenta de novo' });
    }
  };
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, background: 'var(--cream)', borderRadius: '20px 20px 0 0', borderTop: `3px solid ${entrada ? 'var(--leaf)' : 'var(--rose)'}`, padding: '18px 20px 26px', boxShadow: 'var(--shadow-lg)' }}>
        <div style={{ fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 2 }}>{entrada ? '📥 Entrada' : '📤 Saída'} · {item.nome}</div>
        <div style={{ fontFamily: 'var(--fm)', fontSize: 11.5, color: 'var(--muted)', marginBottom: 14 }}>saldo atual: {Number(item.qtd_esperada)} {item.unidade || ''}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <input className="input" type="number" inputMode="decimal" autoFocus placeholder="Qtd" style={{ width: 110, boxSizing: 'border-box', fontSize: 18, textAlign: 'center', fontFamily: 'var(--fm)' }} value={qtd} onChange={e => setQtd(e.target.value)} />
          <input className="input" placeholder={entrada ? 'De onde veio? (opcional)' : 'Pra onde foi? (opcional)'} style={{ flex: 1, boxSizing: 'border-box' }} value={nota} onChange={e => setNota(e.target.value)} />
        </div>
        <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, marginTop: 14 }}>
          <button className="btn" onClick={onClose}>Cancelar</button>
          <button className="btn btn-primary" onClick={confirmar} disabled={saving || !parseFloat(String(qtd).replace(',', '.'))} style={{ background: entrada ? 'var(--leaf)' : 'var(--rose)', borderColor: 'transparent' }}>{saving ? '…' : (entrada ? '📥 Dar entrada' : '📤 Dar saída')}</button>
        </div>
      </div>
    </div>
  );
};

// ─── 📦 F2: trilha do item (razão) ───
const InvRazaoModal = ({ item, onClose }) => {
  const [razao, setRazao] = useState(null);
  useEffect(() => {
    fetch(`/api/inventario/itens/${item.id}/razao`, { headers: INV_HDR() })
      .then(r => r.ok ? r.json() : []).then(d => setRazao(Array.isArray(d) ? d : [])).catch(() => setRazao([]));
  }, [item.id]);
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '8vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 460, background: 'var(--cream)', borderRadius: 18, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--teal)', padding: '18px 20px', boxShadow: 'var(--shadow-lg)', marginBottom: 40 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
          <div style={{ fontFamily: 'var(--fs)', fontSize: 18 }}>🕘 {item.nome}</div>
          <button className="btn ghost btn-sm" onClick={onClose}>✕</button>
        </div>
        {razao === null ? <div className="empty" style={{ padding: 20 }}>carregando…</div>
          : razao.length === 0 ? <div className="empty" style={{ padding: 20 }}>sem movimentações ainda.</div>
          : razao.map((rz, i) => (
            <div key={rz.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '7px 0', borderTop: i === 0 ? 'none' : '1px dashed var(--line-soft)', fontSize: 12.5, minWidth: 0 }}>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', flexShrink: 0 }}>{new Date(rz.ts).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })}</span>
              <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{rz.motivo || '—'}</span>
              <span style={{ fontFamily: 'var(--fm)', fontWeight: 700, color: Number(rz.delta) > 0 ? 'var(--leaf)' : 'var(--rose)', flexShrink: 0 }}>{Number(rz.delta) > 0 ? '+' : ''}{Number(rz.delta)}</span>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', flexShrink: 0 }}>= {Number(rz.saldo_apos)}</span>
            </div>
          ))}
      </div>
    </div>
  );
};

// ─── ⚠ F4: alertas de validade e estoque mínimo (colapsável, só aparece se houver) ───
const InvAlertas = () => {
  const [alertas, setAlertas] = useState([]);
  const [aberto, setAberto] = useState(false);
  useEffect(() => {
    fetch('/api/inventario/alertas', { headers: INV_HDR() })
      .then(r => r.ok ? r.json() : []).then(d => setAlertas(Array.isArray(d) ? d : [])).catch(() => {});
  }, []);
  if (!alertas.length) return null;
  const vencidos = alertas.filter(a => a.alerta_validade === 'vencido').length;
  const cor = vencidos ? 'var(--rose)' : 'var(--sun)';
  return (
    <div style={{ marginTop: 16, background: 'var(--surface)', border: `1px solid ${cor}`, borderTop: `3px solid ${cor}`, borderRadius: 16, overflow: 'hidden', boxShadow: 'var(--shadow-sm)' }}>
      <button onClick={() => setAberto(a => !a)} style={{ width: '100%', border: 'none', background: 'transparent', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '12px 16px' }}>
        <span style={{ fontFamily: 'var(--fm)', fontSize: 11, letterSpacing: 1.2, textTransform: 'uppercase', color: cor }}>⚠ {alertas.length} alerta{alertas.length > 1 ? 's' : ''} no paiol{vencidos ? ` · ${vencidos} vencido${vencidos > 1 ? 's' : ''}` : ''}</span>
        <span style={{ color: 'var(--muted)', fontSize: 12 }}>{aberto ? '▲' : '▼'}</span>
      </button>
      {aberto ? (
        <div style={{ padding: '0 16px 14px' }}>
          {alertas.map((a, i) => (
            <div key={a.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '6px 0', borderTop: i === 0 ? 'none' : '1px dashed var(--line-soft)', fontSize: 12.5, minWidth: 0 }}>
              <span style={{ flexShrink: 0 }}>{a.alerta_validade === 'vencido' ? '🔴' : a.alerta_validade === 'vencendo' ? '🟡' : '📉'}</span>
              <b style={{ flexShrink: 0, maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{a.nome}</b>
              <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--muted)', fontFamily: 'var(--fm)', fontSize: 11 }}>
                {a.alerta_validade === 'vencido' ? `venceu ${new Date(a.validade + 'T12:00').toLocaleDateString('pt-BR')}` : a.alerta_validade === 'vencendo' ? `vence em ${a.dias_pra_vencer}d` : ''}
                {a.abaixo_minimo ? `${a.alerta_validade ? ' · ' : ''}${Number(a.qtd_esperada)} de mín ${Number(a.qtd_min)}` : ''}
                {a.lote ? ` · lote ${a.lote}` : ''} · {a.paiol_nome || ''}{a.container_nome ? ` › ${a.container_nome}` : ''}
              </span>
            </div>
          ))}
        </div>
      ) : null}
    </div>
  );
};

// ─── 📦 F2: feed de movimentações do paiol (colapsável na árvore) ───
// ─── 🕘 histórico de movimentações: modal com filtros (texto · tipo · período) ───
const InvMovHistModal = ({ onClose }) => {
  const [feed, setFeed] = useState(null);
  const [q, setQ] = useState('');
  const [qAplicado, setQAplicado] = useState('');
  const [tipo, setTipo] = useState('');          // '' · entrada · saida · mover
  const [dias, setDias] = useState('30');        // 7 · 30 · 90 · '' (tudo)
  useEffect(() => {
    setFeed(null);
    const p = new URLSearchParams({ limit: '120' });
    if (qAplicado) p.set('q', qAplicado);
    if (tipo) p.set('tipo', tipo);
    if (dias) p.set('dias', dias);
    fetch(`/api/inventario/mov?${p.toString()}`, { headers: INV_HDR() })
      .then(r => r.ok ? r.json() : []).then(d => setFeed(Array.isArray(d) ? d : [])).catch(() => setFeed([]));
  }, [qAplicado, tipo, dias]);
  const chip = (val, rot, cor) => (
    <button key={val} onClick={() => setTipo(tipo === val ? '' : val)} style={{ cursor: 'pointer', border: `1px solid ${tipo === val ? cor : 'var(--line-soft)'}`, background: tipo === val ? `color-mix(in srgb, ${cor} 14%, var(--surface))` : 'var(--surface)', color: tipo === val ? cor : 'var(--muted)', borderRadius: 999, padding: '4px 11px', fontSize: 11, fontWeight: 600, fontFamily: 'var(--f)' }}>{rot}</button>
  );
  return (
    <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '6vh 14px', overflowY: 'auto' }}>
      <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 620, background: 'var(--cream)', borderRadius: 18, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--sun)', padding: '18px 20px', boxShadow: 'var(--shadow-lg)', marginBottom: 40, display: 'flex', flexDirection: 'column', maxHeight: '82vh' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
          <div style={{ fontFamily: 'var(--fs)', fontSize: 19 }}>🕘 Movimentações</div>
          <button className="btn ghost btn-sm" onClick={onClose}>✕</button>
        </div>
        <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
          <input className="input" style={{ flex: 1, boxSizing: 'border-box', padding: '8px 11px', fontSize: 12.5 }} placeholder="🔍 item ou motivo…" value={q}
            onChange={e => setQ(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') setQAplicado(q.trim()); }} />
          <button className="btn btn-primary btn-sm" onClick={() => setQAplicado(q.trim())}>🔎</button>
        </div>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center', marginBottom: 10 }}>
          {chip('entrada', '📥 entradas', 'var(--leaf)')}
          {chip('saida', '📤 saídas', 'var(--rose)')}
          {chip('mover', '📦 mudanças', 'var(--sun)')}
          <select className="select" value={dias} onChange={e => setDias(e.target.value)} style={{ marginLeft: 'auto', padding: '4px 8px', fontSize: 11.5 }}>
            <option value="7">7 dias</option>
            <option value="30">30 dias</option>
            <option value="90">90 dias</option>
            <option value="">tudo</option>
          </select>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}>
          {feed === null ? <div className="empty" style={{ padding: 18 }}>carregando…</div>
            : feed.length === 0 ? <div className="empty" style={{ padding: 18 }}>nada com esses filtros.</div>
            : feed.map((m, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '7px 2px', borderTop: i === 0 ? 'none' : '1px dashed var(--line-soft)', fontSize: 12, minWidth: 0 }}>
                <span style={{ fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)', flexShrink: 0 }}>{new Date(m.ts).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })}</span>
                <b style={{ flexShrink: 0, maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{m.item_nome}</b>
                <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--muted)' }}>{m.descr}{m.container_nome ? ` · 📁 ${m.container_nome}` : ''}</span>
                {m.delta != null ? <span style={{ fontFamily: 'var(--fm)', fontWeight: 700, color: Number(m.delta) > 0 ? 'var(--leaf)' : 'var(--rose)', flexShrink: 0 }}>{Number(m.delta) > 0 ? '+' : ''}{Number(m.delta)}</span> : null}
                {m.por_nome ? <span style={{ fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)', flexShrink: 0 }}>{String(m.por_nome).split(' ')[0]}</span> : null}
              </div>
            ))}
        </div>
      </div>
    </div>
  );
};

// ─── 🌲 Explorador estilo TM Master: linha da árvore ───
const InvExpLinha = ({ icone, rotulo, meta, nivel, ativo, temSeta, aberto, onSeta, onClick }) => (
  <div style={{ display: 'flex', alignItems: 'stretch', minWidth: 0 }}>
    <button onClick={onSeta} disabled={!temSeta} style={{ width: 18, flexShrink: 0, marginLeft: nivel * 13, border: 'none', background: 'transparent', cursor: temSeta ? 'pointer' : 'default', color: 'var(--muted)', fontSize: 9, padding: 0 }}>{temSeta ? (aberto ? '▾' : '▸') : ''}</button>
    <button onClick={onClick} style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 6, border: 'none', textAlign: 'left', cursor: 'pointer', padding: '5px 7px', borderRadius: 8, fontFamily: 'var(--f)', background: ativo ? 'color-mix(in srgb, var(--teal) 13%, transparent)' : 'transparent', color: ativo ? 'var(--teal)' : 'var(--ink)', fontWeight: ativo ? 700 : 500, fontSize: 12.5 }}>
      <span style={{ flexShrink: 0, fontSize: 12 }}>{icone}</span>
      <span style={{ minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{rotulo}</span>
      {meta ? <span style={{ marginLeft: 'auto', flexShrink: 0, fontFamily: 'var(--fm)', fontSize: 9.5, color: 'var(--muted)', fontWeight: 400 }}>{meta}</span> : null}
    </button>
  </div>
);

// ─── 🌲 nó de container (recursivo, lazy) ───
const InvExpCont = ({ c, nivel, abertos, galhos, sel, aoToggle, aoSelecionar }) => {
  const key = 'c' + c.id;
  const aberto = abertos.has(key);
  const filhos = galhos[c.id];
  const temFilhos = Number(c.subcontainers) > 0 || (filhos || []).length > 0;
  const ativo = !!(sel && sel.tipo === 'cont' && sel.dados && sel.dados.id === c.id);
  return (
    <React.Fragment>
      <InvExpLinha icone="📁" rotulo={c.nome} meta={`${c.itens} it`} nivel={nivel} ativo={ativo} temSeta={temFilhos} aberto={aberto}
        onSeta={() => aoToggle(key, c)} onClick={() => aoSelecionar(c)} />
      {aberto && temFilhos ? (
        filhos ? filhos.map(f => <InvExpCont key={f.id} c={f} nivel={nivel + 1} abertos={abertos} galhos={galhos} sel={sel} aoToggle={aoToggle} aoSelecionar={aoSelecionar} />)
          : <div style={{ padding: `3px 0 3px ${nivel * 13 + 32}px`, fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)' }}>carregando…</div>
      ) : null}
    </React.Fragment>
  );
};

// ─── 📋 acordeão do item: abre ENTRE as linhas da tabela (o "Stock history" do TM, embutido) ───
const InvItemDetalhe = ({ item, podeGerir, onAcao, onFechar }) => {
  const [aba, setAba] = useState('ficha');
  const [razao, setRazao] = useState(null);
  useEffect(() => { setAba('ficha'); setRazao(null); }, [item.id]);
  useEffect(() => {
    if (aba !== 'hist' || razao !== null) return;
    fetch(`/api/inventario/itens/${item.id}/razao`, { headers: INV_HDR() })
      .then(r => r.ok ? r.json() : []).then(d => setRazao(Array.isArray(d) ? d : [])).catch(() => setRazao([]));
  }, [aba, item.id, razao]);
  const dias = item.validade ? Math.floor((new Date(String(item.validade).split('T')[0] + 'T12:00') - Date.now()) / 864e5) : null;
  const campo = (rot, val) => (val == null || val === '') ? null : (
    <div key={rot} style={{ display: 'flex', gap: 8, padding: '3px 0', fontSize: 12, minWidth: 0, borderBottom: '1px solid color-mix(in srgb, var(--line) 45%, transparent)' }}>
      <span style={{ fontFamily: 'var(--fm)', fontSize: 9.5, letterSpacing: 0.6, textTransform: 'uppercase', color: 'var(--muted)', width: 96, flexShrink: 0, paddingTop: 2 }}>{rot}</span>
      <span style={{ minWidth: 0, fontWeight: 500 }}>{val}</span>
    </div>
  );
  const abaBtn = (id, rot) => (
    <button key={id} onClick={() => setAba(id)} style={{ border: 'none', cursor: 'pointer', padding: '6px 12px', fontSize: 11.5, fontFamily: 'var(--f)', fontWeight: aba === id ? 700 : 500, background: 'transparent', color: aba === id ? 'var(--teal)' : 'var(--muted)', borderBottom: aba === id ? '2px solid var(--teal)' : '2px solid transparent', marginBottom: -1 }}>{rot}</button>
  );
  return (
    <div style={{ background: 'var(--cream-2)', border: '1px solid var(--line-soft)', borderLeft: '3px solid var(--sky)', borderRadius: 10, margin: '2px 0 6px', overflow: 'hidden' }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, padding: '2px 10px 0', borderBottom: '1px solid var(--line-soft)' }}>
        {abaBtn('ficha', '📋 Ficha')}{abaBtn('hist', '🕘 Histórico')}
        <button className="btn ghost btn-sm" onClick={onFechar} style={{ marginLeft: 'auto', padding: '2px 8px', marginBottom: 2 }}>✕</button>
      </div>
      <div style={{ padding: '8px 14px 11px', maxHeight: 250, overflowY: 'auto' }}>
        {aba === 'ficha' ? (
          <React.Fragment>
            {campo('EAN', item.ean)}
            {campo('Cód. interno', item.codigo_interno)}
            {campo('Cód. TM', item.tm_codigo)}
            {campo('Lote', item.lote)}
            {item.validade ? campo('Validade', <span style={{ color: dias < 0 ? 'var(--rose)' : dias <= 90 ? 'var(--sun)' : 'inherit', fontWeight: 700 }}>{new Date(String(item.validade).split('T')[0] + 'T12:00').toLocaleDateString('pt-BR')}{dias < 0 ? ' · 🔴 vencido' : dias <= 90 ? ` · 🟡 em ${dias}d` : ''}</span>) : null}
            {item.qtd_min != null ? campo('Estoque mín.', <span style={{ color: Number(item.qtd_esperada) < Number(item.qtd_min) ? 'var(--rose)' : 'inherit', fontWeight: 700 }}>{Number(item.qtd_min)}{Number(item.qtd_esperada) < Number(item.qtd_min) ? ' · 📉 abaixo!' : ''}</span>) : null}
            {campo('Descrição', item.descricao)}
            <div style={{ display: 'flex', gap: 7, flexWrap: 'wrap', marginTop: 9 }}>
              <button className="btn btn-sm" style={{ background: 'var(--leaf)', color: '#fff', borderColor: 'transparent' }} onClick={() => onAcao('entrada')}>📥 Entrada</button>
              <button className="btn btn-sm" style={{ background: 'var(--rose)', color: '#fff', borderColor: 'transparent' }} onClick={() => onAcao('saida')}>📤 Saída</button>
              <button className="btn btn-sm" style={{ background: 'var(--sky)', color: '#fff', borderColor: 'transparent' }} onClick={() => onAcao('mover')}>⇄ Mover</button>
              {podeGerir ? <button className="btn btn-sm" style={{ background: 'var(--teal)', color: '#fff', borderColor: 'transparent' }} onClick={() => onAcao('editar')}>✎ Editar</button> : null}
            </div>
          </React.Fragment>
        ) : (
          razao === null ? <div className="empty" style={{ padding: 14 }}>carregando…</div>
            : razao.length === 0 ? <div className="empty" style={{ padding: 14 }}>sem movimentações ainda.</div>
            : razao.map((rz, i) => (
              <div key={rz.id} style={{ display: 'flex', alignItems: 'baseline', gap: 8, padding: '6px 0', borderTop: i === 0 ? 'none' : '1px dashed var(--line-soft)', fontSize: 12, minWidth: 0 }}>
                <span style={{ fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)', flexShrink: 0 }}>{new Date(rz.ts).toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })}</span>
                <span style={{ flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{rz.motivo || '—'}{rz.por_nome ? ` · ${rz.por_nome}` : ''}</span>
                <span style={{ fontFamily: 'var(--fm)', fontWeight: 700, color: Number(rz.delta) > 0 ? 'var(--leaf)' : 'var(--rose)', flexShrink: 0 }}>{Number(rz.delta) > 0 ? '+' : ''}{Number(rz.delta)}</span>
                <span style={{ fontFamily: 'var(--fm)', fontSize: 10, color: 'var(--muted)', flexShrink: 0 }}>= {Number(rz.saldo_apos)}</span>
              </div>
            ))
        )}
      </div>
    </div>
  );
};

const Inventario = ({ state, setState, setPage }) => {
  const toast = useToast();
  const isMobile = useMobile();
  const me = state.currentUser || {};
  const chaves = me.permissoes || [];
  const podeGerir = me.role === 'admin' || me.is_staff === true || chaves.includes('inventario.gerir');

  const [arvore, setArvore] = useState([]);
  const [tela, setTela] = useState('arvore');            // arvore (explorador) · contagem · relatorio
  const [sel, setSel] = useState(null);                  // seleção da árvore: {tipo:'paiol', id} · {tipo:'cont', dados}
  const [galhos, setGalhos] = useState({});              // containerId → filhos[] (lazy, pra árvore)
  const [abertos, setAbertos] = useState(() => new Set());// nós expandidos ('p1', 'c34'…)
  const [itemSel, setItemSel] = useState(null);          // id do item da ficha (painel de baixo)
  const [sessao, setSessao] = useState(null);            // sessão ativa (com contagens)
  const [paiolSel, setPaiolSel] = useState(null);
  const [relatorio, setRelatorio] = useState(null);
  const [historico, setHistorico] = useState([]);
  const [busca, setBusca] = useState('');
  const [resultados, setResultados] = useState(null);
  const [scan, setScan] = useState(null);                // null · 'global' · 'destino'
  const [modal, setModal] = useState(null);              // {tipo:'item', item?} · {tipo:'import'} · {tipo:'qr', paiol} · {tipo:'duplicado', ...} · {tipo:'mover', item}
  const [aviso, setAviso] = useState(null);              // resultado de bipagem de item (onde mora)

  const carregarArvore = () => fetch('/api/inventario/arvore', { headers: INV_HDR() })
    .then(r => r.ok ? r.json() : []).then(d => setArvore(Array.isArray(d) ? d : [])).catch(() => {});
  useEffect(() => { carregarArvore(); }, []);

  const selecionarCont = async (codigoOuId, itemFoco) => {
    try {
      const r = await fetch(`/api/inventario/containers/${codigoOuId}`, { headers: INV_HDR() });
      if (!r.ok) return;
      const c = await r.json();
      setSel({ tipo: 'cont', dados: c });
      setGalhos(g => ({ ...g, [c.id]: c.filhos || [] }));
      setAbertos(a => { const n = new Set(a); n.add('p' + c.paiol_id); (c.trilha || []).forEach(t => n.add('c' + t.id)); n.add('c' + c.id); return n; });
      setItemSel(itemFoco != null ? itemFoco : null);
      if (tela !== 'arvore') return; // contagem/relatório não trocam de tela
    } catch (e) {}
  };
  const abrirContainer = selecionarCont; // compat (bipagem, contagem)
  const selecionarPaiol = (p) => { setSel({ tipo: 'paiol', id: p.id }); setItemSel(null); };
  const toggleNo = (key, c) => {
    setAbertos(a => { const n = new Set(a); if (n.has(key)) n.delete(key); else n.add(key); return n; });
    if (c && !galhos[c.id] && Number(c.subcontainers) > 0) {
      fetch(`/api/inventario/containers/${c.id}`, { headers: INV_HDR() })
        .then(r => r.ok ? r.json() : null).then(d => { if (d) setGalhos(g => ({ ...g, [c.id]: d.filhos || [] })); }).catch(() => {});
    }
  };
  const atualizarGalhos = () => {
    Object.keys(galhos).forEach(id => {
      fetch(`/api/inventario/containers/${id}`, { headers: INV_HDR() })
        .then(r => r.ok ? r.json() : null).then(d => { if (d) setGalhos(g => ({ ...g, [id]: d.filhos || [] })); }).catch(() => {});
    });
  };
  const recarregar = () => {
    carregarArvore(); atualizarGalhos();
    if (sel && sel.tipo === 'cont') selecionarCont(sel.dados.codigo, itemSel);
  };

  // ── bipagem global: INV-C → abre container · EAN/GEN-ITEM → mostra onde mora ──
  const resolverCodigo = async (code) => {
    setScan(null);
    const cod = code.trim();
    if (/^INV-C-/i.test(cod)) { abrirContainer(cod); return; }
    const r = await fetch(`/api/inventario/itens?ean=${encodeURIComponent(cod)}`, { headers: INV_HDR() });
    const d = r.ok ? await r.json() : [];
    if (d.length) setAviso(d[0]);
    else setAviso({ desconhecido: true, codigo: cod });
  };

  // ── contagem ──
  const iniciarInventario = async (paiol) => {
    const r = await fetch('/api/inventario/sessoes', INV_JSON('POST', { paiol_id: paiol.id }));
    if (!r.ok) { toast.push({ icon: 'x', title: 'Erro ao abrir sessão' }); return; }
    const s = await r.json();
    const ativa = await fetch(`/api/inventario/sessoes/ativa?paiol_id=${paiol.id}`, { headers: INV_HDR() }).then(x => x.json());
    setPaiolSel(paiol); setSessao(ativa || { ...s, contagens: [] }); setTela('contagem');
  };
  const registrarContagem = async (item, qtd, modo) => {
    const r = await fetch(`/api/inventario/sessoes/${sessao.id}/contagem`, INV_JSON('POST', { item_id: item.id, container_id: item.container_id, qtd, modo }));
    if (r.status === 409) {
      const d = await r.json();
      setModal({ tipo: 'duplicado', item, qtd, existente: d.contagem });
      return false;
    }
    if (!r.ok) { toast.push({ icon: 'x', title: 'Erro ao contar' }); return false; }
    const c = await r.json();
    setSessao(s => ({ ...s, contagens: [...s.contagens.filter(x => x.item_id !== c.item_id), c] }));
    return true;
  };
  const finalizar = async () => {
    if (!confirm('Encerrar a contagem e gerar o relatório?')) return;
    const r = await fetch(`/api/inventario/sessoes/${sessao.id}/finalizar`, INV_JSON('POST', {}));
    if (!r.ok) { const d = await r.json().catch(() => ({})); toast.push({ icon: 'x', title: d.error || 'Erro ao finalizar' }); return; }
    const d = await r.json();
    setRelatorio({ ...d, paiol_nome: paiolSel.nome }); setSessao(null); setTela('relatorio');
    toast.push({ icon: 'check', title: 'Inventário concluído 📦' });
    recarregar();
  };
  const abrirRelatorio = async (sid) => {
    const r = await fetch(`/api/inventario/sessoes/${sid}/relatorio`, { headers: INV_HDR() });
    if (r.ok) { setRelatorio(await r.json()); setTela('relatorio'); }
  };
  const carregarHistorico = () => fetch('/api/inventario/sessoes', { headers: INV_HDR() })
    .then(r => r.ok ? r.json() : []).then(d => setHistorico(Array.isArray(d) ? d : [])).catch(() => {});

  const exportarRelatorio = async () => {
    const carregarLib = () => new Promise(res => invLoadXLSX(() => res(true), () => res(false)));
    if (!(await carregarLib())) { toast.push({ icon: 'x', title: 'Não consegui carregar o exportador' }); return; }
    const ws = window.XLSX.utils.json_to_sheet(relatorio.linhas.map(l => ({
      Container: l.container_nome, Item: l.nome, 'Cód. TM': l.tm_codigo || '', Unid: l.unidade,
      Esperado: l.esperado, Contado: l.contado != null ? l.contado : 'NÃO CONTADO', Diferença: l.diff != null ? l.diff : '',
    })));
    const wb = window.XLSX.utils.book_new();
    window.XLSX.utils.book_append_sheet(wb, ws, 'Inventário');
    window.XLSX.writeFile(wb, `inventario-${(relatorio.paiol_nome || 'paiol').replace(/\s+/g, '-')}-${new Date().toISOString().split('T')[0]}.xlsx`);
  };

  const buscar = async (q) => {
    setBusca(q);
    if (q.trim().length < 2) { setResultados(null); return; }
    const r = await fetch(`/api/inventario/itens?busca=${encodeURIComponent(q)}`, { headers: INV_HDR() });
    setResultados(r.ok ? await r.json() : []);
  };

  const contagemDe = (itemId) => (sessao && sessao.contagens || []).find(c => c.item_id === itemId);

  // ══ TELA: relatório ══
  if (tela === 'relatorio' && relatorio) {
    const rs = relatorio.resumo || {
      itens_total: relatorio.linhas.length,
      contados: relatorio.linhas.filter(l => l.contado != null).length,
      nao_contados: relatorio.linhas.filter(l => l.contado == null).length,
      divergentes: relatorio.linhas.filter(l => l.diff != null && l.diff !== 0).length,
      faltas: relatorio.linhas.filter(l => l.diff != null && l.diff < 0).length,
      sobras: relatorio.linhas.filter(l => l.diff != null && l.diff > 0).length,
    };
    const tile = (rot, val, cor) => (
      <div key={rot} style={{ background: `color-mix(in srgb, ${cor} 10%, var(--surface))`, border: `1px solid color-mix(in srgb, ${cor} 26%, transparent)`, borderRadius: 14, padding: '10px 14px', minWidth: 88, textAlign: 'center' }}>
        <div style={{ fontFamily: 'var(--fs)', fontSize: 22, fontWeight: 700 }}>{val}</div>
        <div style={{ fontFamily: 'var(--fm)', fontSize: 9.5, letterSpacing: 0.8, textTransform: 'uppercase', color: cor, marginTop: 3 }}>{rot}</div>
      </div>
    );
    return (
      <main style={{ padding: isMobile ? '16px 16px 80px' : '26px 20px 56px', flex: 1, maxWidth: 1180, margin: '0 auto', width: '100%', boxSizing: 'border-box' }}>
        <button className="btn btn-sm" onClick={() => { setRelatorio(null); setTela('arvore'); carregarArvore(); }} style={{ marginBottom: 14 }}>← paióis</button>
        <div className="t-eyebrow" style={{ color: 'var(--leaf)' }}>relatório de inventário</div>
        <h1 style={{ fontFamily: 'var(--fs)', fontSize: 28, letterSpacing: '-0.6px', fontWeight: 400, margin: '4px 0 16px' }}>{relatorio.paiol_nome}</h1>
        <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 18 }}>
          {tile('itens', rs.itens_total, 'var(--teal)')}
          {tile('contados', rs.contados, 'var(--leaf)')}
          {tile('pendentes', rs.nao_contados, 'var(--sun)')}
          {tile('faltas', rs.faltas, 'var(--rose)')}
          {tile('sobras', rs.sobras, 'var(--sky)')}
        </div>
        <div style={{ marginBottom: 16 }}>
          <button className="btn btn-primary btn-sm" onClick={exportarRelatorio}>📄 Exportar Excel (pro TM Master)</button>
        </div>
        <div style={{ ...invCard, borderTop: '3px solid var(--leaf)', padding: '8px 14px' }}>
          {relatorio.linhas.map((l, i) => (
            <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 2px', borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)', minWidth: 0 }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{l.nome}</div>
                <div style={{ fontSize: 10.5, color: 'var(--muted)', fontFamily: 'var(--fm)' }}>{l.container_nome}{l.tm_codigo ? ` · TM ${l.tm_codigo}` : ''}</div>
              </div>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 12, color: 'var(--muted)', flexShrink: 0 }}>{l.esperado} {l.unidade}</span>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 13, fontWeight: 700, flexShrink: 0, minWidth: 56, textAlign: 'right',
                color: l.contado == null ? 'var(--sun)' : l.diff === 0 ? 'var(--leaf)' : 'var(--rose)' }}>
                {l.contado == null ? 'pend.' : `${l.contado}${l.diff !== 0 ? ` (${l.diff > 0 ? '+' : ''}${l.diff})` : ' ✓'}`}
              </span>
            </div>
          ))}
        </div>
      </main>
    );
  }

  // ══ TELA: contagem ══
  if (tela === 'contagem' && sessao && paiolSel) {
    const paiol = arvore.find(p => p.id === paiolSel.id) || paiolSel;
    return (
      <main style={{ padding: isMobile ? '16px 16px 80px' : '26px 20px 56px', flex: 1, maxWidth: 1180, margin: '0 auto', width: '100%', boxSizing: 'border-box' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 10, flexWrap: 'wrap', marginBottom: 16 }}>
          <div>
            <button className="btn btn-sm" onClick={() => { setTela('arvore'); carregarArvore(); }} style={{ marginBottom: 8 }}>← pausar (a sessão fica aberta)</button>
            <div className="t-eyebrow" style={{ color: 'var(--accent)' }}>inventário em andamento</div>
            <h1 style={{ fontFamily: 'var(--fs)', fontSize: 26, letterSpacing: '-0.6px', fontWeight: 400, margin: '2px 0 0' }}>{paiol.nome}</h1>
          </div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-sm" onClick={() => setScan('global')}>📷 Bipar</button>
            {podeGerir ? <button className="btn btn-primary btn-sm" onClick={finalizar}>🏁 Finalizar</button> : null}
          </div>
        </div>
        <InvContagemLista paiol={paiol} sessao={sessao} contagemDe={contagemDe} registrar={registrarContagem} abrirContainer={abrirContainer} />
        {scan === 'global' ? <InvScanner titulo="Bipar prateleira ou produto" onCode={resolverCodigo} onClose={() => setScan(null)} /> : null}
        {aviso ? <InvAvisoItem aviso={aviso} onClose={() => setAviso(null)} /> : null}
        {modal && modal.tipo === 'duplicado' ? (
          <InvDuplicadoSheet modal={modal} onEscolha={async (modo) => { const m = modal; setModal(null); await registrarContagem(m.item, m.qtd, modo); }} onClose={() => setModal(null)} />
        ) : null}
      </main>
    );
  }

  // ══ TELA ÚNICA: explorador estilo TM Master (árvore ‹› conteúdo) ══
  const cont = sel && sel.tipo === 'cont' ? sel.dados : null;
  const paiolAtivo = sel && sel.tipo === 'paiol' ? arvore.find(p => p.id === sel.id) : null;
  const mostrarArvore = !isMobile || !sel;
  const mostrarConteudo = !isMobile || !!sel;

  const acaoDoItem = (tipo, item) => {
    if (tipo === 'entrada' || tipo === 'saida') setModal({ tipo: 'mov', item, sentido: tipo });
    else if (tipo === 'mover') { setModal({ tipo: 'mover', item }); setScan('destino'); }
    else if (tipo === 'editar') setModal({ tipo: 'item', item });
  };

  return (
    <main style={{ padding: isMobile ? '16px 12px 80px' : '26px 20px 56px', flex: 1, maxWidth: 1180, margin: '0 auto', width: '100%', boxSizing: 'border-box' }}>
      {/* ── cabeçalho ── */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
        <div>
          <div className="t-eyebrow">módulo de operação</div>
          <h1 style={{ fontFamily: 'var(--fs)', fontSize: 32, letterSpacing: '-0.8px', fontWeight: 400, margin: 0 }}>Inventá<em style={{ fontStyle: 'italic', color: 'var(--accent)' }}>rio.</em></h1>
        </div>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', alignItems: 'center' }}>
          <button className="btn btn-primary btn-sm" onClick={() => setScan('global')} style={{ padding: '10px 16px' }}>📷 Bipar</button>
          {podeGerir ? <button className="btn btn-sm" style={invSolido('teal')} onClick={async () => {
            const nome = prompt('Nome do novo paiol:'); if (!nome) return;
            const r = await fetch('/api/inventario/paiols', INV_JSON('POST', { nome }));
            if (r.ok) { toast.push({ icon: 'check', title: 'Paiol criado' }); recarregar(); }
          }}>＋ Paiol</button> : null}
          {podeGerir ? <button className="btn btn-sm" style={invSolido('sky')} onClick={() => setModal({ tipo: 'tm' })}>🚢 TM Master</button> : null}
          <InvIco ico="🕘" dica="Histórico de movimentações" onClick={() => setModal({ tipo: 'movfeed' })} />
          <InvIco ico="📜" dica="Sessões de inventário" onClick={() => { carregarHistorico(); setModal({ tipo: 'hist' }); }} />
        </div>
      </div>

      {/* ── painel duplo: árvore ‹› conteúdo ── */}
      <div style={{ display: 'flex', gap: 14, alignItems: 'flex-start' }}>
        {mostrarArvore ? (
          <div style={{ ...invCard, borderTop: '3px solid var(--teal)', width: isMobile ? '100%' : 268, flexShrink: 0, padding: '11px 9px 13px', boxSizing: 'border-box', position: isMobile ? 'static' : 'sticky', top: 16, maxHeight: isMobile ? 'none' : 'calc(100vh - 110px)', overflowY: 'auto' }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '0 5px 8px' }}>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 10, letterSpacing: 1, textTransform: 'uppercase', color: 'var(--teal)', flex: 1 }}>🌲 locais de bordo</span>
              <button className="btn ghost btn-sm" title="Atualizar árvore" onClick={recarregar} style={{ padding: '2px 7px' }}>↻</button>
            </div>
            <input className="input" style={{ width: '100%', boxSizing: 'border-box', marginBottom: 8, padding: '8px 10px', fontSize: 12.5 }} placeholder="🔍 onde tem…?" value={busca} onChange={e => buscar(e.target.value)} />
            {resultados ? (
              !resultados.length ? <div className="empty" style={{ padding: 16, fontSize: 12 }}>nada com "{busca}".</div> :
                resultados.map(it => (
                  <button key={it.id} onClick={() => { if (it.container_codigo) selecionarCont(it.container_codigo, it.id); setBusca(''); setResultados(null); }}
                    style={{ width: '100%', textAlign: 'left', border: 'none', background: 'transparent', cursor: 'pointer', padding: '6px 7px', borderRadius: 8, minWidth: 0 }}>
                    <div style={{ fontSize: 12.5, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.nome}</div>
                    <div style={{ fontSize: 10, color: 'var(--muted)', fontFamily: 'var(--fm)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.paiol_nome || 'sem lugar'}{it.container_nome ? ` › ${it.container_nome}` : ''} · {Number(it.qtd_esperada)} {it.unidade}</div>
                  </button>
                ))
            ) : !arvore.length ? (
              <div className="empty" style={{ padding: '22px 8px', textAlign: 'center', fontSize: 12 }}>📦 nenhum paiol ainda{podeGerir ? ' — cria no ＋ Paiol.' : '.'}</div>
            ) : arvore.map(p => {
              const key = 'p' + p.id;
              const aberto = abertos.has(key);
              const conts = p.containers || [];
              return (
                <React.Fragment key={p.id}>
                  <InvExpLinha icone="🏬" rotulo={p.nome} meta={`${conts.length} 📁`} nivel={0}
                    ativo={!!(paiolAtivo && paiolAtivo.id === p.id)} temSeta={conts.length > 0} aberto={aberto}
                    onSeta={() => toggleNo(key, null)} onClick={() => { selecionarPaiol(p); toggleNo(key, null); }} />
                  {aberto ? conts.map(c => <InvExpCont key={c.id} c={c} nivel={1} abertos={abertos} galhos={galhos} sel={sel} aoToggle={toggleNo} aoSelecionar={(x) => selecionarCont(x.codigo)} />) : null}
                </React.Fragment>
              );
            })}
          </div>
        ) : null}

        {mostrarConteudo ? (
          <div style={{ flex: 1, minWidth: 0 }}>
            {isMobile && sel ? <button className="btn btn-sm" onClick={() => { setSel(null); setItemSel(null); }} style={{ marginBottom: 10 }}>← árvore</button> : null}

            {/* ── nada selecionado ── */}
            {!sel ? (
              <div className="empty" style={{ ...invCard, textAlign: 'center', padding: '54px 20px', color: 'var(--muted)' }}>
                ‹ escolhe um paiol ou pasta na árvore<div style={{ fontSize: 11, fontFamily: 'var(--fm)', marginTop: 6 }}>ou bipa um QR / código de barras 📷</div>
              </div>
            ) : null}

            {/* ── PAIOL selecionado ── */}
            {paiolAtivo ? (
              <div style={{ ...invCard, borderTop: '3px solid var(--teal)', boxSizing: 'border-box', maxHeight: isMobile ? 'none' : 'calc(100vh - 110px)', display: 'flex', flexDirection: 'column' }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap', marginBottom: 10 }}>
                  <div style={{ minWidth: 0 }}>
                    <div className="t-eyebrow" style={{ color: 'var(--teal)' }}>paiol</div>
                    <div style={{ fontFamily: 'var(--fs)', fontSize: 23, fontWeight: 400 }}>{paiolAtivo.nome}</div>
                  </div>
                  <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                    <InvIco ico="🏷" dica="Etiquetas QR" onClick={() => setModal({ tipo: 'qr', paiol: paiolAtivo })} />
                    {podeGerir ? <InvIco ico="✎" dica="Renomear paiol (QRs não mudam)" onClick={async () => {
                      const nome = prompt('Novo nome do paiol (os QRs continuam os mesmos):', paiolAtivo.nome);
                      if (!nome || nome.trim() === paiolAtivo.nome) return;
                      const r = await fetch(`/api/inventario/paiols/${paiolAtivo.id}`, INV_JSON('PUT', { nome: nome.trim() }));
                      if (r.ok) { toast.push({ icon: 'check', title: 'Paiol renomeado ✎' }); recarregar(); }
                    }} /> : null}
                    {podeGerir ? <InvIco ico="🗑" dica="Excluir paiol" cor="rose" onClick={async () => {
                      let r = await fetch(`/api/inventario/paiols/${paiolAtivo.id}`, { method: 'DELETE', headers: INV_HDR() });
                      let d = await r.json().catch(() => ({}));
                      if (r.ok) { toast.push({ icon: 'check', title: 'Paiol excluído' }); setSel(null); recarregar(); return; }
                      if (d.precisa_forcar) {
                        const ok = confirm(`⚠ "${paiolAtivo.nome}" tem ${d.containers} containers e ${d.itens} itens.\n\nExcluir TUDO (containers, itens e histórico)? Essa ação não tem volta.`);
                        if (!ok) return;
                        r = await fetch(`/api/inventario/paiols/${paiolAtivo.id}?forcar=1`, { method: 'DELETE', headers: INV_HDR() });
                        d = await r.json().catch(() => ({}));
                        if (r.ok) { toast.push({ icon: 'check', title: `Paiol e ${d.apagados.itens} itens excluídos 🗑` }); setSel(null); recarregar(); }
                        else toast.push({ icon: 'x', title: d.error || 'Erro ao excluir' });
                      } else toast.push({ icon: 'x', title: d.error || 'Erro ao excluir' });
                    }} /> : null}
                    {podeGerir ? <InvIco ico="＋📁" dica="Novo container/prateleira" onClick={async () => {
                      const nome = prompt('Nome do container/prateleira:'); if (!nome) return;
                      const r = await fetch('/api/inventario/containers', INV_JSON('POST', { paiol_id: paiolAtivo.id, nome }));
                      if (r.ok) { toast.push({ icon: 'check', title: 'Container criado' }); recarregar(); }
                    }} /> : null}
                    <button className="btn btn-primary btn-sm" onClick={() => iniciarInventario(paiolAtivo)} disabled={!(paiolAtivo.containers || []).some(c => c.itens > 0)}>📦 Inventariar</button>
                  </div>
                </div>
                {!(paiolAtivo.containers || []).length ? <div className="empty" style={{ padding: 20 }}>sem containers ainda.</div> : (
                  <div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'repeat(auto-fill, minmax(200px, 1fr))', gap: 8, overflowY: 'auto', minHeight: 0, alignContent: 'start' }}>
                    {paiolAtivo.containers.map(c => (
                      <button key={c.id} onClick={() => selecionarCont(c.codigo)} style={{ textAlign: 'left', cursor: 'pointer', background: 'var(--cream-2)', border: '1px solid var(--line-soft)', borderRadius: 12, padding: '10px 12px', minWidth: 0 }}>
                        <div style={{ fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>📁 {c.nome}</div>
                        <div style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', marginTop: 3 }}>{Number(c.subcontainers) ? `📁 ${c.subcontainers} · ` : ''}{c.itens} it · {Number(c.unidades)} un</div>
                      </button>
                    ))}
                  </div>
                )}
              </div>
            ) : null}

            {/* ── CONTAINER selecionado ── */}
            {cont ? (
              <React.Fragment>
                <div style={{ ...invCard, borderTop: '3px solid var(--teal)', padding: '13px 16px', boxSizing: 'border-box', maxHeight: isMobile ? 'none' : 'calc(100vh - 110px)', display: 'flex', flexDirection: 'column' }}>
                  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 10, flexWrap: 'wrap', marginBottom: 8 }}>
                    <div style={{ minWidth: 0 }}>
                      <div className="t-eyebrow" style={{ color: 'var(--teal)', display: 'flex', alignItems: 'center', gap: 5, flexWrap: 'wrap' }}>
                        <button onClick={() => selecionarPaiol({ id: cont.paiol_id })} style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--teal)', fontFamily: 'inherit', fontSize: 'inherit', letterSpacing: 'inherit', textTransform: 'inherit', padding: 0, textDecoration: 'underline' }}>{cont.paiol_nome}</button>
                        {(cont.trilha || []).map(t => (
                          <React.Fragment key={t.id}>
                            <span style={{ opacity: 0.5 }}>›</span>
                            <button onClick={() => selecionarCont(t.codigo)} style={{ border: 'none', background: 'transparent', cursor: 'pointer', color: 'var(--teal)', fontFamily: 'inherit', fontSize: 'inherit', letterSpacing: 'inherit', textTransform: 'inherit', padding: 0, textDecoration: 'underline' }}>{t.nome}</button>
                          </React.Fragment>
                        ))}
                        <span style={{ opacity: 0.5 }}>·</span><span>{cont.codigo}</span>
                      </div>
                      <div style={{ fontFamily: 'var(--fs)', fontSize: 22, fontWeight: 400, margin: '2px 0 0' }}>📁 {cont.nome}</div>
                    </div>
                    {podeGerir ? (
                      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
                        <InvIco ico="✎" dica="Renomear (o QR não muda!)" onClick={async () => {
                          const nome = prompt('Novo nome do container (o QR ' + cont.codigo + ' continua o mesmo):', cont.nome);
                          if (!nome || nome.trim() === cont.nome) return;
                          const r = await fetch(`/api/inventario/containers/${cont.id}`, INV_JSON('PUT', { nome: nome.trim() }));
                          if (r.ok) { toast.push({ icon: 'check', title: 'Renomeado — QR intacto 🏷' }); recarregar(); }
                        }} />
                        <InvIco ico="📦⇄" dica="Mover este container inteiro (tudo dentro vai junto)" onClick={() => setModal({ tipo: 'movergrupo' })} />
                        <InvIco ico="＋📁" dica="Criar container dentro (gaveta, caixa…)" onClick={async () => {
                          const nome = prompt('Nome do container DENTRO de "' + cont.nome + '" (gaveta, caixa…):');
                          if (!nome) return;
                          const r = await fetch('/api/inventario/containers', INV_JSON('POST', { parent_id: cont.id, nome: nome.trim() }));
                          if (r.ok) { toast.push({ icon: 'check', title: 'Container criado dentro 📁' }); recarregar(); }
                        }} />
                        <InvIco ico="📄" dica="Importar planilha de itens" onClick={() => setModal({ tipo: 'import' })} />
                        <button className="btn btn-primary btn-sm" onClick={() => setModal({ tipo: 'item' })}>＋ Item</button>
                      </div>
                    ) : null}
                  </div>

                  {!cont.itens.length ? <div className="empty" style={{ padding: 26 }}>prateleira vazia — cadastra ou importa itens.</div> : (
                    <div style={{ flex: 1, minHeight: 0, overflowY: 'auto', position: 'relative' }}>
                      <div style={{ display: 'flex', gap: 8, padding: '4px 2px 6px', borderBottom: '1px solid var(--line-soft)', fontFamily: 'var(--fm)', fontSize: 9.5, letterSpacing: 0.8, textTransform: 'uppercase', color: 'var(--muted)', position: 'sticky', top: 0, background: 'var(--surface)', zIndex: 1 }}>
                        <span style={{ flex: 1 }}>item</span><span style={{ flexShrink: 0 }}>em estoque</span><span style={{ width: isMobile ? 60 : 128, flexShrink: 0 }}></span>
                      </div>
                      {cont.itens.map((it, i) => (
                        <React.Fragment key={it.id}>
                        <div onClick={() => setItemSel(itemSel === it.id ? null : it.id)}
                          style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 6px', borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)', minWidth: 0, cursor: 'pointer', borderRadius: 8, background: itemSel === it.id ? 'color-mix(in srgb, var(--sky) 10%, transparent)' : 'transparent' }}>
                          <div style={{ flex: 1, minWidth: 0 }}>
                            <div style={{ fontSize: 13, fontWeight: itemSel === it.id ? 700 : 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.nome}</div>
                            <div style={{ fontSize: 10.5, color: 'var(--muted)', fontFamily: 'var(--fm)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                              {invBadgeAlerta(it)}{it.ean ? `EAN ${it.ean}` : it.codigo_interno}{it.tm_codigo ? ` · TM ${it.tm_codigo}` : ''}{it.lote ? ` · lote ${it.lote}` : ''}
                            </div>
                          </div>
                          <span style={{ fontFamily: 'var(--fm)', fontSize: 13, fontWeight: 700, flexShrink: 0 }}>{Number(it.qtd_esperada)} <span style={{ fontWeight: 400, color: 'var(--muted)', fontSize: 11 }}>{it.unidade}</span></span>
                          <button title="Dar entrada" onClick={e => { e.stopPropagation(); setModal({ tipo: 'mov', item: it, sentido: 'entrada' }); }} style={{ width: 26, height: 26, borderRadius: 8, border: 'none', background: 'var(--leaf)', color: '#fff', cursor: 'pointer', fontSize: 14, lineHeight: 1, display: 'grid', placeItems: 'center', flexShrink: 0 }}>＋</button>
                          <button title="Dar saída" onClick={e => { e.stopPropagation(); setModal({ tipo: 'mov', item: it, sentido: 'saida' }); }} style={{ width: 26, height: 26, borderRadius: 8, border: 'none', background: 'var(--rose)', color: '#fff', cursor: 'pointer', fontSize: 14, lineHeight: 1, display: 'grid', placeItems: 'center', flexShrink: 0 }}>−</button>
                          {!isMobile ? <InvIco ico="⇄" dica="Mover de prateleira" tam={26} onClick={e => { e.stopPropagation(); setModal({ tipo: 'mover', item: it }); setScan('destino'); }} /> : null}
                          {!isMobile && podeGerir ? <InvIco ico="✎" dica="Editar item" tam={26} onClick={e => { e.stopPropagation(); setModal({ tipo: 'item', item: it }); }} /> : null}
                        </div>
                        {itemSel === it.id ? <InvItemDetalhe item={it} podeGerir={podeGerir} onFechar={() => setItemSel(null)} onAcao={(tipo) => acaoDoItem(tipo, it)} /> : null}
                        </React.Fragment>
                      ))}
                    </div>
                  )}
                </div>
              </React.Fragment>
            ) : null}
          </div>
        ) : null}
      </div>

      <InvAlertas />

      {/* ── scanners e modais ── */}
      {scan === 'global' ? <InvScanner titulo="Bipar prateleira ou produto" onCode={resolverCodigo} onClose={() => setScan(null)} /> : null}
      {aviso ? <InvAvisoItem aviso={aviso} onClose={() => setAviso(null)}
        onMov={(item, sentido) => { setAviso(null); setModal({ tipo: 'mov', item, sentido }); }}
        onAbrir={(a) => { setAviso(null); if (a.container_codigo) selecionarCont(a.container_codigo, a.id); }} /> : null}
      {modal && modal.tipo === 'movfeed' ? <InvMovHistModal onClose={() => setModal(null)} /> : null}
      {modal && modal.tipo === 'mov' ? <InvMovSheet item={modal.item} sentido={modal.sentido} toast={toast} onClose={() => setModal(null)} onDone={recarregar} /> : null}
      {modal && modal.tipo === 'qr' ? <InvQRPrint paiol={modal.paiol} onClose={() => setModal(null)} /> : null}
      {modal && modal.tipo === 'tm' ? <InvTmModal paiols={arvore.map(p => ({ id: p.id, nome: p.nome }))} toast={toast} onClose={() => setModal(null)} onDone={recarregar} /> : null}
      {modal && modal.tipo === 'item' && cont ? <InvItemModal item={modal.item} containerId={cont.id} toast={toast} onClose={() => setModal(null)} onSaved={recarregar} /> : null}
      {modal && modal.tipo === 'import' && cont ? <InvImportModal containers={[{ id: cont.id, nome: cont.nome, codigo: cont.codigo }]} toast={toast} onClose={() => setModal(null)} onDone={recarregar} /> : null}
      {scan === 'destino' && modal && modal.tipo === 'mover' ? (
        <InvScanner titulo={`Bipar a prateleira destino de "${modal.item.nome}"`} onCode={async (c) => {
          setScan(null); const item = modal.item; setModal(null);
          const r = await fetch(`/api/inventario/itens/${item.id}/mover`, INV_JSON('PUT', { container_codigo: c }));
          const d = await r.json().catch(() => ({}));
          if (r.ok) { toast.push({ icon: 'check', title: `Movido ⇄` }); recarregar(); }
          else toast.push({ icon: 'x', title: d.error || 'Erro ao mover' });
        }} onClose={() => { setScan(null); setModal(null); }} />
      ) : null}
      {modal && modal.tipo === 'movergrupo' && cont ? (
        <div onClick={() => setModal(null)} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
          <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, background: 'var(--cream)', borderRadius: '20px 20px 0 0', borderTop: '3px solid var(--sun)', padding: '18px 20px 26px', boxShadow: 'var(--shadow-lg)' }}>
            <div style={{ fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 4 }}>📦⇄ Mover "{cont.nome}" inteiro</div>
            <div style={{ fontSize: 12.5, color: 'var(--muted)', marginBottom: 14 }}>Tudo que está dentro (itens e containers) vai junto. O QR {cont.codigo} continua o mesmo.</div>
            <button className="btn" style={{ width: '100%', marginBottom: 8 }} onClick={() => { setModal(null); setScan('destinogrupo'); }}>📷 Bipar o QR do container destino</button>
            <div style={{ fontFamily: 'var(--fm)', fontSize: 10.5, color: 'var(--muted)', textAlign: 'center', margin: '4px 0 8px' }}>— ou virar container de topo em —</div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6, maxHeight: 200, overflowY: 'auto' }}>
              {arvore.map(p => (
                <button key={p.id} className="btn btn-sm" style={{ justifyContent: 'flex-start' }} onClick={async () => {
                  setModal(null);
                  const r = await fetch(`/api/inventario/containers/${cont.id}/mover`, INV_JSON('PUT', { paiol_id: p.id }));
                  const d = await r.json().catch(() => ({}));
                  if (r.ok) { toast.push({ icon: 'check', title: `📦 Movido pra ${p.nome}` }); recarregar(); }
                  else toast.push({ icon: 'x', title: d.error || 'Erro ao mover' });
                }}>🏬 {p.nome}</button>
              ))}
            </div>
          </div>
        </div>
      ) : null}
      {scan === 'destinogrupo' && cont ? (
        <InvScanner titulo={`Bipar o destino de "${cont.nome}" (grupo inteiro)`} onCode={async (c) => {
          setScan(null);
          const r = await fetch(`/api/inventario/containers/${cont.id}/mover`, INV_JSON('PUT', { parent_codigo: c }));
          const d = await r.json().catch(() => ({}));
          if (r.ok) { toast.push({ icon: 'check', title: `📦 Grupo movido ⇄` }); recarregar(); }
          else toast.push({ icon: 'x', title: d.error || 'Erro ao mover' });
        }} onClose={() => setScan(null)} />
      ) : null}
      {modal && modal.tipo === 'hist' ? (
        <div onClick={() => setModal(null)} style={{ position: 'fixed', inset: 0, zIndex: 9000, background: 'rgba(31,42,46,0.45)', display: 'flex', alignItems: 'flex-start', justifyContent: 'center', padding: '5vh 14px', overflowY: 'auto' }}>
          <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 520, background: 'var(--cream)', borderRadius: 20, border: '1px solid var(--line-soft)', borderTop: '3px solid var(--leaf)', padding: '20px 22px', marginBottom: 40 }}>
            <div style={{ fontFamily: 'var(--fs)', fontSize: 20, marginBottom: 12 }}>📜 Sessões de inventário</div>
            {!historico.length ? <div className="empty" style={{ padding: 20 }}>nenhuma ainda.</div> :
              historico.map(h => (
                <button key={h.id} onClick={() => { setModal(null); abrirRelatorio(h.id); }} style={{ width: '100%', textAlign: 'left', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8, padding: '10px 12px', background: 'var(--cream-2)', border: '1px solid var(--line-soft)', borderRadius: 10, marginBottom: 6 }}>
                  <span style={{ fontSize: 13 }}>{h.paiol_nome} · {new Date(h.inicio).toLocaleDateString('pt-BR')}</span>
                  <span style={{ fontFamily: 'var(--fm)', fontSize: 11, color: h.status === 'ativa' ? 'var(--sun)' : 'var(--leaf)' }}>{h.status === 'ativa' ? '● em andamento' : '✓ concluída'}</span>
                </button>
              ))}
          </div>
        </div>
      ) : null}
    </main>
  );
};

// ─── lista de contagem (containers com progresso → itens com input) ───
const InvContagemLista = ({ paiol, sessao, contagemDe, registrar, abrirContainer }) => {
  const [aberto, setAberto] = useState(null);
  const [qtds, setQtds] = useState({});
  const [contDetalhe, setContDetalhe] = useState({});   // id → itens
  const carregarItens = (c) => fetch(`/api/inventario/containers/${c.codigo}`, { headers: INV_HDR() })
    .then(r => r.ok ? r.json() : null).then(d => { if (d) setContDetalhe(s => ({ ...s, [c.id]: d.itens })); });
  const toggle = (c) => { const novo = aberto === c.id ? null : c.id; setAberto(novo); if (novo && !contDetalhe[c.id]) carregarItens(c); };
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {(paiol.containers || []).map(c => {
        const itens = contDetalhe[c.id] || [];
        const contados = itens.filter(i => contagemDe(i.id)).length;
        return (
          <div key={c.id} style={{ ...invCard, borderTop: '3px solid var(--accent)', padding: 0, overflow: 'hidden' }}>
            <button onClick={() => toggle(c)} style={{ width: '100%', border: 'none', background: 'transparent', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '13px 16px', gap: 8 }}>
              <span style={{ fontSize: 14, fontWeight: 600, minWidth: 0, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.nome}</span>
              <span style={{ fontFamily: 'var(--fm)', fontSize: 11, color: 'var(--muted)', flexShrink: 0 }}>
                {itens.length ? `${contados}/${itens.length} contados` : `${c.itens} itens`} {aberto === c.id ? '▲' : '▼'}
              </span>
            </button>
            {aberto === c.id ? (
              <div style={{ padding: '0 16px 14px' }}>
                {!itens.length ? <div style={{ fontSize: 12, color: 'var(--muted)', fontFamily: 'var(--fm)' }}>carregando…</div> :
                  itens.map((it, i) => {
                    const ct = contagemDe(it.id);
                    const val = qtds[it.id] !== undefined ? qtds[it.id] : '';
                    return (
                      <div key={it.id} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 0', borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)', minWidth: 0 }}>
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ fontSize: 13, fontWeight: 500, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{it.nome}</div>
                          <div style={{ fontSize: 10.5, color: 'var(--muted)', fontFamily: 'var(--fm)' }}>esperado: {Number(it.qtd_esperada)} {it.unidade}</div>
                        </div>
                        {ct ? (
                          <span style={{ fontFamily: 'var(--fm)', fontSize: 13, fontWeight: 700, flexShrink: 0,
                            color: Number(ct.qtd) === Number(it.qtd_esperada) ? 'var(--leaf)' : 'var(--rose)' }}>
                            {Number(ct.qtd)} {Number(ct.qtd) === Number(it.qtd_esperada) ? '✓' : `(${Number(ct.qtd) - Number(it.qtd_esperada) > 0 ? '+' : ''}${Number(ct.qtd) - Number(it.qtd_esperada)})`}
                          </span>
                        ) : null}
                        <input className="input" type="number" inputMode="decimal" placeholder="qtd"
                          value={val} onChange={e => setQtds(s => ({ ...s, [it.id]: e.target.value }))}
                          onKeyDown={async e => { if (e.key === 'Enter' && val !== '') { const ok = await registrar(it, Number(val)); if (ok !== false) setQtds(s => ({ ...s, [it.id]: '' })); } }}
                          style={{ width: 74, boxSizing: 'border-box', textAlign: 'center', fontFamily: 'var(--fm)', flexShrink: 0, padding: '8px 6px' }} />
                        <button className="btn btn-primary btn-sm" disabled={val === ''} onClick={async () => { const ok = await registrar(it, Number(val)); if (ok !== false) setQtds(s => ({ ...s, [it.id]: '' })); }} style={{ flexShrink: 0, width: 34, padding: '8px 0' }}>✓</button>
                      </div>
                    );
                  })}
              </div>
            ) : null}
          </div>
        );
      })}
    </div>
  );
};

// ─── sheet do item duplicado ───
const InvDuplicadoSheet = ({ modal, onEscolha, onClose }) => (
  <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.5)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
    <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, background: 'var(--cream)', borderRadius: '20px 20px 0 0', padding: '20px 22px 26px', borderTop: '3px solid var(--sun)' }}>
      <div style={{ fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 4 }}>Item já contado nesta sessão</div>
      <div style={{ fontSize: 13, color: 'var(--muted)', marginBottom: 16 }}>
        <b style={{ color: 'var(--ink)' }}>{modal.item.nome}</b> já tem <b style={{ color: 'var(--ink)' }}>{Number(modal.existente.qtd)}</b> registrado. Tu contou <b style={{ color: 'var(--ink)' }}>{modal.qtd}</b> agora — o que fazemos?
      </div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
        <button className="btn btn-primary" onClick={() => onEscolha('somar')}>➕ Somar ({Number(modal.existente.qtd)} + {modal.qtd} = {Number(modal.existente.qtd) + Number(modal.qtd)}) — achei mais uma caixa</button>
        <button className="btn" onClick={() => onEscolha('substituir')}>✏️ Substituir por {modal.qtd} — é recontagem</button>
        <button className="btn ghost" onClick={onClose}>↩️ Ignorar</button>
      </div>
    </div>
  </div>
);

// ─── aviso da bipagem de produto (onde mora) ───
const InvAvisoItem = ({ aviso, onClose, onMov, onAbrir }) => (
  <div onClick={onClose} style={{ position: 'fixed', inset: 0, zIndex: 9200, background: 'rgba(31,42,46,0.5)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center' }}>
    <div onClick={e => e.stopPropagation()} style={{ width: '100%', maxWidth: 480, background: 'var(--cream)', borderRadius: '20px 20px 0 0', padding: '20px 22px 26px', borderTop: `3px solid ${aviso.desconhecido ? 'var(--sun)' : 'var(--teal)'}` }}>
      {aviso.desconhecido ? (
        <>
          <div style={{ fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 4 }}>Código desconhecido 🤔</div>
          <div style={{ fontSize: 13, color: 'var(--muted)', marginBottom: 14 }}><span style={{ fontFamily: 'var(--fm)' }}>{aviso.codigo}</span> ainda não está no inventário. Cadastra o item numa prateleira (o botão ＋ Item dentro do container) e cola esse código no campo EAN — batismo feito, nunca mais esquece.</div>
        </>
      ) : (
        <>
          <div style={{ fontFamily: 'var(--fs)', fontSize: 19, marginBottom: 4 }}>{aviso.nome}</div>
          <div style={{ fontSize: 13.5, marginBottom: 14 }}>
            mora em <b>{aviso.paiol_nome || '—'}{aviso.container_nome ? ` · ${aviso.container_nome}` : ''}</b> · esperado: <b>{Number(aviso.qtd_esperada)} {aviso.unidade}</b>
          </div>
          {onMov ? (
            <div style={{ display: 'flex', gap: 8, marginBottom: 10 }}>
              <button className="btn" style={{ flex: 1, background: 'var(--leaf)', color: '#fff', borderColor: 'transparent' }} onClick={() => onMov(aviso, 'entrada')}>📥 Entrada</button>
              <button className="btn" style={{ flex: 1, background: 'var(--rose)', color: '#fff', borderColor: 'transparent' }} onClick={() => onMov(aviso, 'saida')}>📤 Saída</button>
            </div>
          ) : null}
          {onAbrir && aviso.container_codigo ? (
            <button className="btn" style={{ width: '100%', marginBottom: 10 }} onClick={() => onAbrir(aviso)}>📍 Abrir a prateleira</button>
          ) : null}
        </>
      )}
      <button className="btn" style={{ width: '100%' }} onClick={onClose}>ok ⚓</button>
    </div>
  </div>
);
