// ═══════════════════════════════════════════════════════
// BAUKO CRM — Página de Configurações (Onda 1 · MVP)
// ═══════════════════════════════════════════════════════
// Centraliza listas, validações e preferências do CRM.
// Lê/escreve via CRM_CONFIG (crm-config-api.js) que persiste em
// 2 listas SP: ConfigCRM (chave/valor) e ConfigCRM_Filiais.
//
// Padrão React/Babel (sem build step) — mesma stack do resto do CRM.
// Estilo visual segue CRM/index.html (NÃO o design-system.md do Hub).
// ═══════════════════════════════════════════════════════
/* global React, CRM_CONFIG */

const { useState: useCfgState, useEffect: useCfgEffect, useCallback: useCfgCb } = React;

// ── Constantes ─────────────────────────────────────────
const CFG_TABS = [
  { id: 'geral',     label: 'Geral',          icon: 'settings'    },
  { id: 'pipeline',  label: 'Pipeline',       icon: 'kanban'      },
  { id: 'propostas', label: 'Propostas',      icon: 'file-text'   },
  { id: 'pedidos',   label: 'Pedidos & Filiais', icon: 'package'  },
  { id: 'clientes',  label: 'Clientes',       icon: 'building-2'  },
  { id: 'motivos',   label: 'Motivos de Perda', icon: 'frown'     },
  { id: 'regioes',   label: 'Territórios',    icon: 'map-pin'     },
  { id: 'komtrax',   label: 'Komtrax',        icon: 'cpu'         },
  { id: 'frota',     label: 'Frota Concorrente', icon: 'truck'    }
];

// ── Helpers de nome p/ match fuzzy (AbaKomtrax + AbaFrotaConcorrente) ──────────
const _CFG_STOP_NOMES = new Set([
  'LTDA','EIRELI','ME','EPP','MEI','SA','S/A','SOCIEDADE','EMPRESARIAL','EMPRESA',
  'DE','DA','DO','DOS','DAS','E','A','O','OS','AS'
]);
function _cfgNormNome(s) {
  if (!s) return '';
  return String(s)
    .normalize('NFD').replace(/[̀-ͯ]/g, '')
    .toUpperCase().replace(/[^\sA-Z0-9]/g, ' ')
    .replace(/\s+/g, ' ').trim();
}
function _cfgTokensNome(s) {
  return _cfgNormNome(s).split(' ').filter(function(t) {
    return t.length >= 2 && !_CFG_STOP_NOMES.has(t);
  });
}
function _cfgSimNomes(tokensA, tokensB) {
  if (!tokensA.length || !tokensB.length) return 0;
  if (tokensA[0] !== tokensB[0]) return 0;
  var setA = new Set(tokensA), setB = new Set(tokensB);
  var inter = 0;
  setA.forEach(function(t) { if (setB.has(t)) inter++; });
  return (2 * inter) / (setA.size + setB.size);
}
function _cfgVpRowKey(r) {
  var norm = _cfgNormNome(r.cliente).replace(/\s+/g, '').slice(0, 40);
  return [r.fabricante, r.modelo, String(r.ano || ''), norm].join('|');
}
const FROTA_SUGESTAO_MIN = 0.55;

// ───────────────────────────────────────────────────────
// Helpers visuais reutilizáveis
// ───────────────────────────────────────────────────────

function CfgBanner({ kind = 'info', children }) {
  const colors = {
    info: { bg: 'var(--info-050, #e4eef8)', border: '#c1d6ec', color: 'var(--info, #3370b7)', icon: 'info' },
    warn: { bg: 'var(--warn-050, #fff5df)', border: '#f4dba3', color: '#8a6500',              icon: 'alert-triangle' }
  };
  const c = colors[kind] || colors.info;
  return (
    <div style={{
      display: 'flex', alignItems: 'flex-start', gap: 10,
      padding: '12px 16px', borderRadius: 'var(--r-md)',
      font: '400 12px/1.5 var(--ff-body)', marginBottom: 16,
      background: c.bg, color: c.color, border: '1px solid ' + c.border
    }}>
      <i data-lucide={c.icon} style={{ width: 16, height: 16, flexShrink: 0, marginTop: 1 }}></i>
      <div>{children}</div>
    </div>
  );
}

function CfgPanel({ titulo, sub, icon, headerRight, children }) {
  return (
    <div style={{
      background: 'var(--surface)', border: '1px solid var(--border)',
      borderRadius: 'var(--r-lg)', marginBottom: 16, boxShadow: 'var(--shadow-1)'
    }}>
      <div style={{
        padding: '14px 20px', borderBottom: '1px solid var(--border)',
        display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12
      }}>
        <div>
          <div style={{ font: '600 14px/1.2 var(--ff-body)', color: 'var(--tx)', display: 'flex', alignItems: 'center', gap: 8 }}>
            {icon && <i data-lucide={icon} style={{ color: 'var(--grn)', width: 16, height: 16 }}></i>}
            {titulo}
          </div>
          {sub && <div style={{ font: '400 12px/1.4 var(--ff-body)', color: 'var(--tx-3)', marginTop: 2 }}>{sub}</div>}
        </div>
        {headerRight}
      </div>
      <div style={{ padding: 20 }}>{children}</div>
    </div>
  );
}

function CfgTag({ children, variant = 'default' }) {
  const styles = {
    default:  { bg: 'var(--surface-2)', color: 'var(--tx-3)',   border: 'var(--border)' },
    new:      { bg: 'var(--warn-050, #fff5df)', color: '#8a6500', border: '#f4dba3' },
    readonly: { bg: 'var(--gry-100, #eef1ef)', color: 'var(--tx-3)', border: 'var(--border)' }
  };
  const s = styles[variant] || styles.default;
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', padding: '3px 8px',
      background: s.bg, color: s.color, border: '1px solid ' + s.border,
      borderRadius: 'var(--r-pill)', font: '500 10px/1 var(--ff-body)',
      textTransform: 'uppercase', letterSpacing: '.04em', marginLeft: 8
    }}>{children}</span>
  );
}

function CfgToggle({ checked, onChange, label, disabled = false }) {
  return (
    <label style={{ display: 'inline-flex', alignItems: 'center', gap: 10, cursor: disabled ? 'not-allowed' : 'pointer', userSelect: 'none', opacity: disabled ? 0.5 : 1 }}>
      <input type="checkbox" checked={!!checked} disabled={disabled}
        onChange={e => onChange && onChange(e.target.checked)}
        style={{ display: 'none' }} />
      <span style={{
        width: 36, height: 20,
        background: checked ? 'var(--grn)' : 'var(--gry-300, #c8ccc9)',
        borderRadius: 999, position: 'relative', transition: 'background .15s', flexShrink: 0
      }}>
        <span style={{
          position: 'absolute', left: 2, top: 2, width: 16, height: 16,
          background: '#fff', borderRadius: '50%', transition: 'transform .15s',
          transform: checked ? 'translateX(16px)' : 'translateX(0)',
          boxShadow: '0 1px 2px rgba(0,0,0,.2)'
        }}></span>
      </span>
      <span style={{ font: '500 13px/1.3 var(--ff-body)', color: 'var(--tx-2)' }}>{label}</span>
    </label>
  );
}

// Lista editável de strings (origens, motivos, segmentos, etc.)
function CfgListaStrings({ items, onChange, novos = [], placeholder = 'Novo item' }) {
  const [draft, setDraft] = useCfgState(items || []);
  useCfgEffect(() => { setDraft(items || []); }, [items]);
  useCfgEffect(() => {
    setTimeout(() => { try { window.lucide && lucide.createIcons({ attrs: { 'stroke-width': 1.75 } }); } catch(e) {} }, 0);
  }, [draft]);

  function update(idx, valor) {
    const novo = draft.slice();
    novo[idx] = valor;
    setDraft(novo);
    onChange && onChange(novo);
  }
  function remove(idx) {
    const novo = draft.slice(); novo.splice(idx, 1);
    setDraft(novo); onChange && onChange(novo);
  }
  function adicionar() {
    const novo = draft.concat(['']);
    setDraft(novo); onChange && onChange(novo);
  }

  return (
    <div>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
        {draft.map((item, i) => (
          <div key={i} style={{
            display: 'flex', alignItems: 'center', gap: 10,
            padding: '8px 12px', background: 'var(--surface-2)',
            border: '1px solid var(--border)', borderRadius: 'var(--r-sm)'
          }}>
            <i data-lucide="grip-vertical" style={{ width: 14, height: 14, color: 'var(--tx-3)', flexShrink: 0 }}></i>
            <input type="text" value={item}
              onChange={e => update(i, e.target.value)}
              style={{
                flex: 1, background: 'transparent', border: 0, padding: '4px 0',
                font: '400 13px/1 var(--ff-body)', color: 'var(--tx)', outline: 'none'
              }} />
            {novos.indexOf(item) >= 0 && <CfgTag variant="new">Novo</CfgTag>}
            <button onClick={() => remove(i)} title="Remover"
              style={{ background: 'transparent', border: 0, color: 'var(--tx-3)', cursor: 'pointer', padding: 4, borderRadius: 'var(--r-xs)' }}>
              <i data-lucide="trash-2" style={{ width: 14, height: 14 }}></i>
            </button>
          </div>
        ))}
      </div>
      <button onClick={adicionar} style={{
        display: 'inline-flex', alignItems: 'center', gap: 6, marginTop: 10,
        padding: '7px 12px', background: 'transparent',
        border: '1px dashed var(--border-2)', borderRadius: 'var(--r-sm)',
        color: 'var(--tx-3)', font: '500 12px/1 var(--ff-body)', cursor: 'pointer'
      }}>
        <i data-lucide="plus" style={{ width: 13, height: 13 }}></i>
        {placeholder}
      </button>
    </div>
  );
}

// Toast de sucesso/erro temporário (5s)
function CfgToast({ message, kind = 'ok', onClose }) {
  useCfgEffect(() => {
    if (!message) return;
    const t = setTimeout(() => onClose && onClose(), 5000);
    return () => clearTimeout(t);
  }, [message]);
  if (!message) return null;
  const colors = { ok: 'var(--ok, #2e8b57)', err: 'var(--danger, #c94a4a)' };
  return (
    <div style={{
      position: 'fixed', bottom: 90, right: 24, zIndex: 999,
      padding: '12px 18px', background: colors[kind], color: '#fff',
      borderRadius: 'var(--r-md)', font: '500 13px/1.3 var(--ff-body)',
      boxShadow: 'var(--shadow-2)', display: 'flex', alignItems: 'center', gap: 8
    }}>
      <i data-lucide={kind === 'ok' ? 'check-circle' : 'alert-circle'} style={{ width: 16, height: 16 }}></i>
      {message}
    </div>
  );
}

// Botão de salvar padrão pra cada aba
function CfgSaveBar({ dirty, saving, onSave, onReset }) {
  return (
    <div style={{
      position: 'sticky', bottom: 0, marginTop: 24, padding: '14px 0',
      background: 'var(--bg)', borderTop: '1px solid var(--border)',
      display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12
    }}>
      <div style={{ font: '400 12px/1.3 var(--ff-body)', color: dirty ? 'var(--warn)' : 'var(--tx-3)' }}>
        {saving ? 'Salvando...' : dirty ? '⚠ Alterações não salvas' : '✓ Tudo salvo'}
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <button onClick={onReset} disabled={!dirty || saving} className="btn btn-secondary"
          style={{ opacity: (!dirty || saving) ? 0.5 : 1 }}>
          Descartar
        </button>
        <button onClick={onSave} disabled={!dirty || saving} className="btn btn-primary"
          style={{ opacity: (!dirty || saving) ? 0.5 : 1 }}>
          <i data-lucide="save" style={{ width: 14, height: 14 }}></i>
          {saving ? 'Salvando...' : 'Salvar'}
        </button>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 1 — Geral (preferências do usuário, localStorage)
// ═══════════════════════════════════════════════════════

function AbaGeral({ onToast }) {
  const [tema, setTema] = useCfgState(localStorage.getItem('bauko_crm_theme') || 'light');
  const [vendedorPadrao, setVendedorPadrao] = useCfgState(localStorage.getItem('bauko_crm_vendedor_padrao') || '');
  const [mostrarMargem, setMostrarMargem] = useCfgState(localStorage.getItem('bauko_crm_mostrar_margem') !== 'false');
  const [assinatura, setAssinatura] = useCfgState(localStorage.getItem('bauko_crm_assinatura_email') || '');

  function salvar(key, val) {
    try {
      if (typeof val === 'boolean') localStorage.setItem(key, val ? 'true' : 'false');
      else localStorage.setItem(key, val);
      onToast('Preferência salva.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    }
  }

  const usuarios = (window.CRM_DATA && CRM_DATA.usuarios) || [];

  return (
    <div>
      <CfgBanner kind="info">
        Esta aba é <strong>individual</strong> — preferências ficam só no seu navegador. Não afeta outros usuários.
      </CfgBanner>

      <CfgPanel titulo="Aparência" icon="palette" sub="Tema visual">
        <div className="f">
          <label>Tema</label>
          <select className="inp" value={tema}
            onChange={e => { setTema(e.target.value); salvar('bauko_crm_theme', e.target.value);
              document.documentElement.setAttribute('data-theme', e.target.value); }}>
            <option value="light">Claro</option>
            <option value="dark">Escuro</option>
          </select>
        </div>
      </CfgPanel>

      <CfgPanel titulo="Padrões pessoais" icon="user" sub="Comportamentos default ao abrir o CRM">
        <div className="f">
          <label>Vendedor padrão (Nova Oportunidade)</label>
          <select className="inp" value={vendedorPadrao}
            onChange={e => { setVendedorPadrao(e.target.value); salvar('bauko_crm_vendedor_padrao', e.target.value); }}>
            <option value="">— Usar usuário logado —</option>
            {usuarios.map(u => <option key={u.id} value={u.id}>{u.nome}</option>)}
          </select>
        </div>
        <div className="f">
          <label>Dashboard</label>
          <CfgToggle checked={mostrarMargem}
            onChange={v => { setMostrarMargem(v); salvar('bauko_crm_mostrar_margem', v); }}
            label="Mostrar margem nos KPIs" />
        </div>
      </CfgPanel>

      <CfgPanel titulo={<>Assinatura de e-mail <CfgTag>Onda 2</CfgTag></>} icon="mail"
        sub="Texto que vai no rodapé dos e-mails enviados pelo CRM">
        <div className="f">
          <label>Texto da assinatura</label>
          <textarea className="inp" rows={5} value={assinatura}
            onChange={e => { setAssinatura(e.target.value); salvar('bauko_crm_assinatura_email', e.target.value); }}
            placeholder="Bernardo Uliana&#10;Gerente Comercial · Bauko Máquinas&#10;bernardo.uliana@bauko.com.br" />
        </div>
        <div style={{ background: 'var(--info-050, #e4eef8)', border: '1px solid #c1d6ec', borderRadius: 'var(--r-sm)', padding: '10px 14px', marginTop: 10 }}>
          <div style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--info)', marginBottom: 8 }}>
            Variáveis disponíveis (clique pra inserir no fim)
          </div>
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
            {['{{vendedor_nome}}','{{vendedor_cargo}}','{{vendedor_email}}','{{vendedor_fone}}','{{unidade_bauko}}'].map(p => (
              <button key={p} onClick={() => { const v = assinatura + ' ' + p; setAssinatura(v); salvar('bauko_crm_assinatura_email', v); }}
                style={{ padding: '3px 8px', background: 'var(--surface)', border: '1px solid #c1d6ec', borderRadius: 'var(--r-xs)', font: '500 11px/1 var(--ff-mono)', color: 'var(--info)', cursor: 'pointer' }}>
                {p}
              </button>
            ))}
          </div>
        </div>
      </CfgPanel>
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 2 — Pipeline & Oportunidades (CRM_CONFIG)
// ═══════════════════════════════════════════════════════

function AbaPipeline({ onToast }) {
  const [origens, setOrigens]         = useCfgState(CRM_CONFIG.get('pipeline_origens') || []);
  const [etapas, setEtapas]           = useCfgState(CRM_CONFIG.get('pipeline_etapas') || []);
  const [prioridades, setPrioridades] = useCfgState(CRM_CONFIG.get('pipeline_prioridades') || []);
  const [autoAvanco, setAutoAvanco]   = useCfgState(CRM_CONFIG.get('pipeline_auto_avanco') || {});
  const [dirty, setDirty]   = useCfgState(false);
  const [saving, setSaving] = useCfgState(false);

  const ORIGEM_BASE = CRM_CONFIG.DEFAULTS.pipeline_origens || [];
  const NOVAS_ORIGENS = ['Licitação pública','Financiamento BKB','Projeto especial'];

  function atualizarEtapa(idx, campo, valor) {
    const novo = etapas.slice();
    novo[idx] = Object.assign({}, novo[idx], { [campo]: campo === 'prob' ? Number(valor) : valor });
    setEtapas(novo); setDirty(true);
  }

  async function salvar() {
    setSaving(true);
    try {
      await CRM_CONFIG.set('pipeline_origens',      origens.filter(o => o && o.trim()));
      await CRM_CONFIG.set('pipeline_etapas',       etapas);
      await CRM_CONFIG.set('pipeline_prioridades',  prioridades.filter(p => p && p.label));
      await CRM_CONFIG.set('pipeline_auto_avanco',  autoAvanco);
      setDirty(false);
      onToast('Pipeline atualizado com sucesso.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    } finally {
      setSaving(false);
    }
  }

  function reset() {
    setOrigens(CRM_CONFIG.get('pipeline_origens') || []);
    setEtapas(CRM_CONFIG.get('pipeline_etapas') || []);
    setPrioridades(CRM_CONFIG.get('pipeline_prioridades') || []);
    setAutoAvanco(CRM_CONFIG.get('pipeline_auto_avanco') || {});
    setDirty(false);
  }

  return (
    <div>
      <CfgPanel titulo="Origens da oportunidade" icon="tag"
        sub="Fonte única — usado no Nova Oportunidade e Editar Oportunidade"
        headerRight={<CfgTag>{origens.length} itens</CfgTag>}>
        <CfgListaStrings items={origens} novos={NOVAS_ORIGENS}
          onChange={v => { setOrigens(v); setDirty(true); }}
          placeholder="Adicionar origem" />
      </CfgPanel>

      <CfgPanel titulo="Etapas do pipeline" icon="git-branch"
        sub="Renomear etapa e ajustar probabilidade · IDs não editáveis">
        <CfgBanner kind="warn">
          IDs internos das etapas <strong>não editáveis</strong>. Adicionar/remover etapa não é permitido pela config — afeta auto-avanço e KPIs.
        </CfgBanner>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
          <thead>
            <tr style={{ background: 'var(--surface-2)' }}>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)', width: '32%' }}>ID</th>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)' }}>Label</th>
              <th style={{ padding: '8px 10px', textAlign: 'right', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)', width: '20%' }}>Prob %</th>
            </tr>
          </thead>
          <tbody>
            {etapas.map((e, i) => (
              <tr key={e.id}>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <span style={{ font: '500 12px/1 var(--ff-mono)', color: 'var(--tx-3)' }}>{e.id}</span> <CfgTag variant="readonly">ID</CfgTag>
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <input className="inp" value={e.label} style={{ width: '100%', padding: '5px 8px', fontSize: 12 }}
                    onChange={ev => atualizarEtapa(i, 'label', ev.target.value)} />
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)', textAlign: 'right' }}>
                  <input className="inp" type="number" min="0" max="100" value={e.prob}
                    style={{ width: 80, padding: '5px 8px', fontSize: 12, fontFamily: 'var(--ff-mono)', textAlign: 'right' }}
                    onChange={ev => atualizarEtapa(i, 'prob', ev.target.value)} />
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </CfgPanel>

      <CfgPanel titulo="Auto-avanço de etapa" icon="zap" sub="Quando o sistema deve avançar a etapa automaticamente">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {[
            { key: 'primeira_maquina_em_lead', label: 'Adicionar primeira máquina em lead → avança para qualificação' },
            { key: 'proposta_enviada',         label: 'Proposta vira enviada/em_negociacao → avança para proposta' },
            { key: 'proposta_vira_pedido',     label: 'Proposta vira pedido → avança para pedido' },
            { key: 'cliente_assina',           label: 'Cliente assina pedido → avança para crédito' }
          ].map(r => (
            <CfgToggle key={r.key} checked={!!autoAvanco[r.key]}
              onChange={v => { setAutoAvanco(Object.assign({}, autoAvanco, { [r.key]: v })); setDirty(true); }}
              label={r.label} />
          ))}
        </div>
      </CfgPanel>

      <CfgPanel titulo="Prioridades" icon="flag" sub="Níveis de prioridade da oportunidade">
        {prioridades.map((p, i) => (
          <div key={p.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '6px 0' }}>
            <span style={{ font: '500 12px/1 var(--ff-mono)', color: 'var(--tx-3)', width: 80 }}>{p.id}</span>
            <input className="inp" value={p.label}
              onChange={ev => {
                const novo = prioridades.slice();
                novo[i] = Object.assign({}, novo[i], { label: ev.target.value });
                setPrioridades(novo); setDirty(true);
              }} style={{ flex: 1, maxWidth: 200 }} />
          </div>
        ))}
      </CfgPanel>

      <CfgSaveBar dirty={dirty} saving={saving} onSave={salvar} onReset={reset} />
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 3 — Propostas
// ═══════════════════════════════════════════════════════

function AbaPropostas({ onToast }) {
  const [validade, setValidade]       = useCfgState(CRM_CONFIG.get('proposta_validade_default_dias') || 10);
  const [descontoMax, setDescontoMax] = useCfgState(CRM_CONFIG.get('proposta_desconto_max_default') || 50);
  const [minChars, setMinChars]       = useCfgState(CRM_CONFIG.get('proposta_min_chars_motivo_cancel') || 10);
  const [dirty, setDirty]   = useCfgState(false);
  const [saving, setSaving] = useCfgState(false);

  async function salvar() {
    setSaving(true);
    try {
      await CRM_CONFIG.set('proposta_validade_default_dias',    Number(validade));
      await CRM_CONFIG.set('proposta_desconto_max_default',     Number(descontoMax));
      await CRM_CONFIG.set('proposta_min_chars_motivo_cancel',  Number(minChars));
      setDirty(false);
      onToast('Configurações da proposta salvas.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    } finally { setSaving(false); }
  }

  function reset() {
    setValidade(CRM_CONFIG.get('proposta_validade_default_dias') || 10);
    setDescontoMax(CRM_CONFIG.get('proposta_desconto_max_default') || 50);
    setMinChars(CRM_CONFIG.get('proposta_min_chars_motivo_cancel') || 10);
    setDirty(false);
  }

  return (
    <div>
      <CfgBanner kind="info">
        <strong>Regra de aprovação gerencial</strong> não é editável aqui. Sempre que o valor com desconto ficar <strong>abaixo do T1 do simulador</strong>, a proposta vai pra aprovação gerencial. Sem alçada por R$ ou %.
      </CfgBanner>

      <CfgPanel titulo="Validade" icon="calendar" sub="Período padrão de validade da proposta">
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <label style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)' }}>Validade padrão</label>
          <input className="inp" type="number" min="1" value={validade}
            onChange={e => { setValidade(e.target.value); setDirty(true); }}
            style={{ width: 100, textAlign: 'right', fontFamily: 'var(--ff-mono)' }} />
          <span style={{ font: '400 13px/1 var(--ff-body)', color: 'var(--tx-3)' }}>dias</span>
        </div>
        <div style={{ font: '400 11px/1.4 var(--ff-body)', color: 'var(--tx-3)', marginTop: 8 }}>
          Substitui o "10 dias" hardcoded no rodapé do PDF.
        </div>
      </CfgPanel>

      <CfgPanel titulo="Descontos" icon="percent" sub="Limite máximo de desconto antes de aprovação gerencial">
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <label style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)' }}>Cap default de desconto_max</label>
          <input className="inp" type="number" min="0" max="100" value={descontoMax}
            onChange={e => { setDescontoMax(e.target.value); setDirty(true); }}
            style={{ width: 100, textAlign: 'right', fontFamily: 'var(--ff-mono)' }} />
          <span style={{ font: '400 13px/1 var(--ff-body)', color: 'var(--tx-3)' }}>%</span>
        </div>
      </CfgPanel>

      <CfgPanel titulo="Cancelamento" icon="x-octagon" sub="Validação ao cancelar oportunidade/proposta/pedido">
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <label style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)' }}>Mín. caracteres do motivo</label>
          <input className="inp" type="number" min="0" value={minChars}
            onChange={e => { setMinChars(e.target.value); setDirty(true); }}
            style={{ width: 100, textAlign: 'right', fontFamily: 'var(--ff-mono)' }} />
        </div>
      </CfgPanel>

      <CfgSaveBar dirty={dirty} saving={saving} onSave={salvar} onReset={reset} />
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 4 — Pedidos & Filiais
// ═══════════════════════════════════════════════════════

function AbaPedidosFiliais({ onToast }) {
  const [filiais, setFiliais] = useCfgState(CRM_CONFIG.getFiliais() || []);
  const [dirtyFil, setDirtyFil] = useCfgState({});
  const [saving, setSaving] = useCfgState(false);

  function atualizar(idx, campo, valor) {
    const novo = filiais.slice();
    novo[idx] = Object.assign({}, novo[idx], { [campo]: valor });
    setFiliais(novo);
    setDirtyFil(Object.assign({}, dirtyFil, { [novo[idx].Title]: true }));
  }

  async function salvarFilial(filial) {
    setSaving(true);
    try {
      await CRM_CONFIG.setFilial(filial.Title, {
        cnpj: filial.cnpj || '',
        ie: filial.ie || '',
        endereco_completo: filial.endereco_completo || '',
        ativa: filial.ativa !== false,
        ordem: filial.ordem || 0
      });
      const novoDirty = Object.assign({}, dirtyFil); delete novoDirty[filial.Title];
      setDirtyFil(novoDirty);
      onToast('Filial ' + filial.Title + ' salva.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar filial: ' + e.message, 'err');
    } finally { setSaving(false); }
  }

  return (
    <div>
      <CfgPanel titulo="Filiais Bauko" icon="building"
        sub="Fonte única — usado no Emitir Pedido, no PDF da proposta e na página pública de assinatura"
        headerRight={<CfgTag>{filiais.length} filiais</CfgTag>}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
          <thead>
            <tr style={{ background: 'var(--surface-2)' }}>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)' }}>Nome</th>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)', width: '20%' }}>CNPJ</th>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)', width: '18%' }}>IE</th>
              <th style={{ padding: '8px 10px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)' }}>Endereço completo</th>
              <th style={{ padding: '8px 10px', textAlign: 'center', font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', borderBottom: '1px solid var(--border)', width: '70px' }}>Ativa</th>
              <th style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)', width: '80px' }}></th>
            </tr>
          </thead>
          <tbody>
            {filiais.map((f, i) => (
              <tr key={f.Title}>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <span style={{ font: '500 13px/1 var(--ff-body)', color: 'var(--tx)' }}>{f.Title}</span>
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <input className="inp" value={f.cnpj || ''} placeholder="—"
                    onChange={e => atualizar(i, 'cnpj', e.target.value)}
                    style={{ width: '100%', padding: '5px 8px', fontSize: 12, fontFamily: 'var(--ff-mono)' }} />
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <input className="inp" value={f.ie || ''} placeholder="—"
                    onChange={e => atualizar(i, 'ie', e.target.value)}
                    style={{ width: '100%', padding: '5px 8px', fontSize: 12, fontFamily: 'var(--ff-mono)' }} />
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)' }}>
                  <input className="inp" value={f.endereco_completo || ''} placeholder="—"
                    onChange={e => atualizar(i, 'endereco_completo', e.target.value)}
                    style={{ width: '100%', padding: '5px 8px', fontSize: 12 }} />
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)', textAlign: 'center' }}>
                  <CfgToggle checked={f.ativa !== false} onChange={v => atualizar(i, 'ativa', v)} label="" />
                </td>
                <td style={{ padding: '8px 10px', borderBottom: '1px solid var(--border)', textAlign: 'right' }}>
                  <button onClick={() => salvarFilial(f)} disabled={!dirtyFil[f.Title] || saving}
                    className="btn btn-primary"
                    style={{ padding: '5px 10px', fontSize: 11, opacity: (!dirtyFil[f.Title] || saving) ? 0.5 : 1 }}>
                    Salvar
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        <div style={{ font: '400 11px/1.4 var(--ff-body)', color: 'var(--tx-3)', marginTop: 12 }}>
          Sem botão "Adicionar filial" — Bernardo confirmou que não pretende abrir filial nova em 12 meses.
        </div>
      </CfgPanel>
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 5 — Clientes
// ═══════════════════════════════════════════════════════

// ── InputCidadeIBGE ── autocomplete de municípios IBGE ───────────────────────
function InputCidadeIBGE({ muniLista, onSelect }) {
  var [txt,    setTxt]    = useCfgState('');
  var [sugs,   setSugs]   = useCfgState([]);
  var [aberta, setAberta] = useCfgState(false);

  function normStr(s) {
    return (s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
  }

  function handleChange(e) {
    var v = e.target.value;
    setTxt(v);
    if (v.length < 2) { setSugs([]); setAberta(false); return; }
    var vn = normStr(v);
    var starts = [], contains = [];
    (muniLista || []).forEach(function(m) {
      var mn = normStr(m.nome);
      if (mn.indexOf(vn) === 0) starts.push(m);
      else if (mn.indexOf(vn) > 0) contains.push(m);
    });
    var res = starts.slice(0, 8).concat(contains.slice(0, Math.max(0, 8 - starts.length)));
    setSugs(res);
    setAberta(res.length > 0);
  }

  function selecionar(m) {
    setTxt('');
    setSugs([]);
    setAberta(false);
    onSelect(m.nome, m.uf);
  }

  return (
    <div style={{ position: 'relative', minWidth: 190 }}>
      <input className="inp" value={txt} onChange={handleChange}
        onBlur={function() { setTimeout(function() { setAberta(false); }, 160); }}
        onFocus={function() { if (sugs.length) setAberta(true); }}
        placeholder="Buscar cidade…"
        style={{ width: '100%', padding: '3px 7px', fontSize: 11 }} />
      {aberta && sugs.length > 0 && (
        <div style={{ position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 9999,
                      background: 'var(--surface)', border: '1px solid var(--border)',
                      borderRadius: 'var(--r-sm)', boxShadow: '0 4px 12px rgba(0,0,0,.14)',
                      maxHeight: 176, overflowY: 'auto' }}>
          {sugs.map(function(m, i) {
            return (
              <div key={i} onMouseDown={function() { selecionar(m); }}
                style={{ padding: '5px 10px', cursor: 'pointer', fontSize: 11,
                         borderBottom: i < sugs.length - 1 ? '1px solid var(--border)' : 'none',
                         display: 'flex', gap: 6, alignItems: 'baseline' }}
                onMouseEnter={function(e) { e.currentTarget.style.background = 'var(--surface-2)'; }}
                onMouseLeave={function(e) { e.currentTarget.style.background = ''; }}>
                <span style={{ fontWeight: 600 }}>{m.nome}</span>
                <span style={{ color: 'var(--tx-3)', fontSize: 10 }}>{m.uf}</span>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── NormalizacaoCidades ── sub-componente de AbaClientes ─────────────────────
function NormalizacaoCidades({ onToast }) {
  var [estado,      setEstado]      = useCfgState('idle'); // idle | loading | ok
  var [divs,        setDivs]        = useCfgState([]);
  var [sel,         setSel]         = useCfgState(new Set());
  var [aplicando,   setAplicando]   = useCfgState(false);
  var [progresso,   setProgresso]   = useCfgState({ feitos: 0, total: 0 });
  var [muniLista,   setMuniLista]   = useCfgState([]);
  var cancelarRef = React.useRef(false);

  function normStr(s) {
    return (s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
  }

  async function analisar() {
    setEstado('loading');
    try {
      // Carrega municipios.json (reusa cache do localStorage)
      var municipios = null;
      try {
        var cached = localStorage.getItem('bauko_municipios_v1');
        if (cached) municipios = JSON.parse(cached);
      } catch(e) {}
      if (!municipios) {
        var r = await fetch('municipios.json');
        municipios = await r.json();
        try { localStorage.setItem('bauko_municipios_v1', JSON.stringify(municipios)); } catch(e) {}
      }

      // Constrói índices
      // lookup: normalize(nome)+"|"+UF → { nome, uf }
      // byNome: normalize(nome)        → [{ nome, uf }, ...]
      var lookup = {};
      var byNome = {};
      Object.keys(municipios).forEach(function(key) {
        var parts = key.split('|');
        var nome  = parts[0];
        var uf    = parts[1];
        var kn    = normStr(nome);
        lookup[kn + '|' + uf] = { nome: nome, uf: uf };
        if (!byNome[kn]) byNome[kn] = [];
        byNome[kn].push({ nome: nome, uf: uf });
      });

      // Salva lista ordenada para autocomplete manual
      var listaAll = Object.keys(municipios).map(function(k) {
        var p = k.split('|'); return { nome: p[0], uf: p[1] };
      }).sort(function(a, b) { return a.nome < b.nome ? -1 : a.nome > b.nome ? 1 : 0; });
      setMuniLista(listaAll);

      // Analisa cada cliente
      var resultado = [];
      (CRM_DATA.clientes || []).forEach(function(cli) {
        var cidadeRaw = (cli.cidade || '').trim();
        var ufRaw     = (cli.uf || '').trim().toUpperCase();
        var cidadeN   = normStr(cidadeRaw);

        if (!cidadeN && !ufRaw) return; // sem dados — pula

        var tipo = null, nomeIBGE = null, ufIBGE = null, candidatos = null;

        if (cidadeN) {
          // 1. Tenta match exato com UF
          if (ufRaw) {
            var m = lookup[cidadeN + '|' + ufRaw];
            if (m) {
              // Grafia idêntica ao IBGE?
              if (m.nome === cidadeRaw && m.uf === ufRaw) {
                tipo = 'ok';
              } else {
                tipo     = 'grafia';
                nomeIBGE = m.nome;
                ufIBGE   = m.uf;
              }
            }
          }

          // 2. Se não achou com UF, tenta só pelo nome
          if (!tipo) {
            var matches = byNome[cidadeN] || [];
            if (matches.length === 1) {
              tipo     = 'uf_errada';
              nomeIBGE = matches[0].nome;
              ufIBGE   = matches[0].uf;
            } else if (matches.length > 1) {
              tipo       = 'ambiguo';
              candidatos = matches.slice(0, 5); // top 5 UFs
            } else {
              tipo = 'nao_encontrado';
            }
          }
        } else {
          tipo = 'sem_cidade';
        }

        if (tipo !== 'ok') {
          resultado.push({
            id: cli.id, _spId: cli._spId,
            razao: cli.razao,
            cidadeAtual: cidadeRaw, ufAtual: ufRaw,
            tipo: tipo, nomeIBGE: nomeIBGE, ufIBGE: ufIBGE,
            candidatos: candidatos,
          });
        }
      });

      setDivs(resultado);
      // Auto-seleciona grafia e uf_errada (correções claras e sem ambiguidade)
      var autoSel = new Set();
      resultado.forEach(function(d) {
        if (d.tipo === 'grafia' || d.tipo === 'uf_errada') autoSel.add(d.id);
      });
      setSel(autoSel);
      setEstado('ok');
    } catch(e) {
      setEstado('idle');
      onToast('Erro ao analisar: ' + e.message, 'err');
    }
  }

  function corrigirManual(id, nome, uf) {
    setDivs(function(prev) {
      return prev.map(function(d) {
        return d.id !== id ? d : Object.assign({}, d, { nomeIBGE: nome || null, ufIBGE: uf || null });
      });
    });
    setSel(function(prev) {
      var n = new Set(prev);
      if (nome) n.add(id); else n.delete(id);
      return n;
    });
  }

  async function aplicar() {
    var alvos = divs.filter(function(d) { return sel.has(d.id) && d.nomeIBGE; });
    if (!alvos.length) return;
    cancelarRef.current = false;
    setAplicando(true);
    setProgresso({ feitos: 0, total: alvos.length });
    var ok = 0, erros = 0;

    // Processa em lotes de 4 requests paralelos (SP aguenta bem, evita throttle)
    var LOTE = 4;
    for (var i = 0; i < alvos.length; i += LOTE) {
      if (cancelarRef.current) break;
      var lote = alvos.slice(i, i + LOTE);
      await Promise.all(lote.map(async function(d) {
        try {
          await CRM_API.updateItem('Clientes', d._spId, { cidade: d.nomeIBGE, uf: d.ufIBGE });
          var cli = (CRM_DATA.clientes || []).find(function(c) { return c.id === d.id; });
          if (cli) { cli.cidade = d.nomeIBGE; cli.uf = d.ufIBGE; }
          ok++;
        } catch(e) { erros++; }
        setProgresso(function(prev) { return { feitos: prev.feitos + 1, total: prev.total }; });
      }));
    }

    var cancelado = cancelarRef.current;
    cancelarRef.current = false;
    setAplicando(false);
    setProgresso({ feitos: 0, total: 0 });
    var msg = ok + ' cliente(s) normalizados.' + (erros ? ' ' + erros + ' com erro.' : '');
    if (cancelado) msg += ' (cancelado — ' + (alvos.length - ok - erros) + ' restantes não enviados)';
    onToast(msg, (cancelado || erros) ? 'warn' : 'ok');
    analisar();
  }

  function cancelar() {
    cancelarRef.current = true;
  }

  // Exporta a lista de divergências em CSV para análise externa (Claude, planilha etc.)
  // Inclui ID do cliente, razão, cidade/UF atuais, tipo do problema, sugestão IBGE atual
  // (quando houver) e candidatos (quando ambíguo).
  function exportarCsv() {
    var header = ['id', 'razao', 'cidade_atual', 'uf_atual', 'tipo', 'sugestao_nome', 'sugestao_uf', 'candidatos'];
    var rows = [header];
    divs.forEach(function(d) {
      var cands = (d.candidatos || []).map(function(c) { return c.nome + '/' + c.uf; }).join(' | ');
      rows.push([
        d.id || '',
        d.razao || '',
        d.cidadeAtual || '',
        d.ufAtual || '',
        d.tipo || '',
        d.nomeIBGE || '',
        d.ufIBGE || '',
        cands
      ]);
    });
    var csv = rows.map(function(r) {
      return r.map(function(v) {
        var s = String(v == null ? '' : v);
        // CSV: escapa aspas duplas e envolve em aspas se contiver vírgula/aspa/quebra
        if (s.indexOf(',') >= 0 || s.indexOf('"') >= 0 || s.indexOf('\n') >= 0) {
          return '"' + s.replace(/"/g, '""') + '"';
        }
        return s;
      }).join(',');
    }).join('\n');

    // Download via Blob — BOM UTF-8 para Excel não estragar acentos
    var BOM  = '﻿';
    var blob = new Blob([BOM + csv], { type: 'text/csv;charset=utf-8;' });
    var url  = URL.createObjectURL(blob);
    var a    = document.createElement('a');
    var data = new Date().toISOString().slice(0, 10);
    a.href     = url;
    a.download = 'crm-cidades-divergencias-' + data + '.csv';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
    onToast(divs.length + ' divergência(s) exportada(s).', 'ok');
  }

  function toggleSel(id) {
    setSel(function(prev) {
      var next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  }
  function toggleAll(lista) {
    var ids = lista.filter(function(d) { return d.nomeIBGE; }).map(function(d) { return d.id; });
    setSel(function(prev) {
      var temTodos = ids.every(function(id) { return prev.has(id); });
      var next = new Set(prev);
      ids.forEach(function(id) { temTodos ? next.delete(id) : next.add(id); });
      return next;
    });
  }

  // Contadores para o resumo
  var cOk           = (CRM_DATA.clientes || []).length - divs.length;
  var cGrafia       = divs.filter(function(d) { return d.tipo === 'grafia'; }).length;
  var cUfErrada     = divs.filter(function(d) { return d.tipo === 'uf_errada'; }).length;
  var cAmbiguo      = divs.filter(function(d) { return d.tipo === 'ambiguo'; }).length;
  var cNaoEncontrado= divs.filter(function(d) { return d.tipo === 'nao_encontrado'; }).length;
  var cSemCidade    = divs.filter(function(d) { return d.tipo === 'sem_cidade'; }).length;
  var corrigiveis   = divs.filter(function(d) { return d.nomeIBGE && sel.has(d.id); }).length;

  var TIPO_LABEL = {
    grafia:        { txt: 'Grafia',         cor: '#b45309', bg: '#fef3c7' },
    uf_errada:     { txt: 'UF errada',      cor: '#b91c1c', bg: '#fee2e2' },
    ambiguo:       { txt: 'Ambíguo',        cor: '#6d28d9', bg: '#ede9fe' },
    nao_encontrado:{ txt: 'Não encontrado', cor: '#374151', bg: '#f3f4f6' },
    sem_cidade:    { txt: 'Sem cidade',     cor: '#6b7280', bg: '#f9fafb' },
  };

  if (estado === 'idle') {
    return (
      <div style={{ textAlign: 'center', padding: '24px 0' }}>
        <button className="btn btn-primary" onClick={analisar}
          style={{ fontSize: 13, gap: 6 }}>
          <i data-lucide="search" style={{ width: 14, height: 14 }}></i>
          Analisar {(CRM_DATA.clientes || []).length} clientes
        </button>
        <div style={{ marginTop: 8, font: '400 11px/1.4 var(--ff-body)', color: 'var(--tx-3)' }}>
          Compara cidade e UF de cada cliente com os 5.571 municípios do IBGE.
        </div>
      </div>
    );
  }

  if (estado === 'loading') {
    return (
      <div style={{ textAlign: 'center', padding: '24px 0', color: 'var(--tx-3)',
                    font: '400 12px/1.4 var(--ff-body)' }}>
        Analisando…
      </div>
    );
  }

  return (
    <div>
      {/* Resumo em chips */}
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 16 }}>
        <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                       background: '#dcfce7', color: '#15803d', fontWeight: 600 }}>
          ✅ {cOk} corretos
        </span>
        {cGrafia > 0 && (
          <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                         background: '#fef3c7', color: '#b45309', fontWeight: 600 }}>
            ✏️ {cGrafia} grafia
          </span>
        )}
        {cUfErrada > 0 && (
          <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                         background: '#fee2e2', color: '#b91c1c', fontWeight: 600 }}>
            🔄 {cUfErrada} UF errada
          </span>
        )}
        {cAmbiguo > 0 && (
          <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                         background: '#ede9fe', color: '#6d28d9', fontWeight: 600 }}>
            ❓ {cAmbiguo} ambíguo
          </span>
        )}
        {cNaoEncontrado > 0 && (
          <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                         background: '#f3f4f6', color: '#374151', fontWeight: 600 }}>
            ❌ {cNaoEncontrado} não encontrado
          </span>
        )}
        {cSemCidade > 0 && (
          <span style={{ padding: '3px 10px', borderRadius: 999, fontSize: 11,
                         background: '#f9fafb', color: '#6b7280', fontWeight: 600 }}>
            — {cSemCidade} sem cidade
          </span>
        )}
      </div>

      {divs.length === 0 ? (
        <div style={{ textAlign: 'center', padding: '20px 0', color: 'var(--tx-3)',
                      font: '400 12px/1.4 var(--ff-body)' }}>
          🎉 Todos os clientes estão normalizados!
        </div>
      ) : (
        <>
          {/* Tabela de divergências */}
          <div style={{ overflow: 'auto', maxHeight: 420, border: '1px solid var(--border)',
                        borderRadius: 'var(--r-sm)', marginBottom: 12 }}>
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
              <thead>
                <tr style={{ background: 'var(--surface-2)', position: 'sticky', top: 0, zIndex: 1 }}>
                  <th style={{ padding: '7px 8px', textAlign: 'left', width: 32 }}>
                    <input type="checkbox"
                      onChange={function() { toggleAll(divs); }}
                      checked={divs.filter(function(d){ return d.nomeIBGE; }).every(function(d){ return sel.has(d.id); })}
                      style={{ cursor: 'pointer' }} />
                  </th>
                  <th style={{ padding: '7px 8px', textAlign: 'left', fontWeight: 600, color: 'var(--tx-2)' }}>Cliente</th>
                  <th style={{ padding: '7px 8px', textAlign: 'left', fontWeight: 600, color: 'var(--tx-2)' }}>Atual</th>
                  <th style={{ padding: '7px 8px', textAlign: 'left', fontWeight: 600, color: 'var(--tx-2)' }}>Tipo</th>
                  <th style={{ padding: '7px 8px', textAlign: 'left', fontWeight: 600, color: 'var(--tx-2)' }}>Corrigir para</th>
                </tr>
              </thead>
              <tbody>
                {divs.map(function(d, i) {
                  var tl = TIPO_LABEL[d.tipo] || { txt: d.tipo, cor: '#6b7280', bg: '#f9fafb' };
                  var temCorrecao = !!d.nomeIBGE;
                  return (
                    <tr key={d.id}
                      style={{ borderTop: '1px solid var(--border)',
                               background: sel.has(d.id) ? 'var(--surface-2)' : 'transparent' }}>
                      <td style={{ padding: '6px 8px', textAlign: 'center' }}>
                        {temCorrecao ? (
                          <input type="checkbox" checked={sel.has(d.id)}
                            onChange={function() { toggleSel(d.id); }}
                            style={{ cursor: 'pointer' }} />
                        ) : (
                          <span style={{ color: 'var(--tx-3)', fontSize: 11 }}>—</span>
                        )}
                      </td>
                      <td style={{ padding: '6px 8px', fontWeight: 500, maxWidth: 180,
                                   overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
                          title={d.razao}>
                        {d.razao}
                      </td>
                      <td style={{ padding: '6px 8px', fontFamily: 'var(--ff-mono)',
                                   color: 'var(--tx-2)' }}>
                        {d.cidadeAtual || <em style={{ color: 'var(--tx-3)' }}>vazia</em>}
                        {d.ufAtual ? ' / ' + d.ufAtual : ''}
                      </td>
                      <td style={{ padding: '6px 8px' }}>
                        <span style={{ padding: '2px 7px', borderRadius: 999, fontSize: 10,
                                       fontWeight: 600, background: tl.bg, color: tl.cor }}>
                          {tl.txt}
                        </span>
                      </td>
                      <td style={{ padding: '6px 8px', color: 'var(--grn)', fontWeight: 500 }}>
                        {d.nomeIBGE ? (
                          <span style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                            <span>{d.nomeIBGE} / {d.ufIBGE}</span>
                            {(d.tipo === 'nao_encontrado' || d.tipo === 'ambiguo' || d.tipo === 'sem_cidade') && (
                              <button onClick={function() { corrigirManual(d.id, null, null); }}
                                title="Limpar correção manual"
                                style={{ background: 'none', border: 'none', cursor: 'pointer',
                                         color: 'var(--tx-3)', fontSize: 11, padding: '0 2px',
                                         lineHeight: 1 }}>✕</button>
                            )}
                          </span>
                        ) : d.candidatos ? (
                          <select className="inp"
                            style={{ fontSize: 11, padding: '3px 6px', minWidth: 190 }}
                            defaultValue=""
                            onChange={function(e) {
                              if (!e.target.value) return;
                              var p = e.target.value.split('|||');
                              corrigirManual(d.id, p[0], p[1]);
                            }}>
                            <option value="">Selecionar UF…</option>
                            {d.candidatos.map(function(c, ci) {
                              return (
                                <option key={ci} value={c.nome + '|||' + c.uf}>
                                  {c.nome} / {c.uf}
                                </option>
                              );
                            })}
                          </select>
                        ) : (
                          muniLista.length > 0 ? (
                            <InputCidadeIBGE
                              muniLista={muniLista}
                              onSelect={function(nome, uf) { corrigirManual(d.id, nome, uf); }} />
                          ) : (
                            <span style={{ color: 'var(--tx-3)', fontStyle: 'italic', fontSize: 11 }}>
                              Analisar para habilitar busca
                            </span>
                          )
                        )}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>

          {/* Legenda */}
          <div style={{ font: '400 11px/1.6 var(--ff-body)', color: 'var(--tx-3)', marginBottom: 14 }}>
            <strong>Grafia</strong> — acento ou capitalização diferente do IBGE. &nbsp;
            <strong>UF errada</strong> — cidade existe no IBGE mas sob outra UF. &nbsp;
            <strong>Ambíguo</strong> — mesmo nome em várias UFs, requer seleção manual. &nbsp;
            <strong>Não encontrado</strong> — sem match no IBGE; verificar digitação.
          </div>

          {/* Ações */}
          <div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
            <button className="btn btn-primary" onClick={aplicar}
              disabled={aplicando || corrigiveis === 0}
              style={{ fontSize: 13, minWidth: 200 }}>
              {aplicando
                ? 'Aplicando… ' + progresso.feitos + ' / ' + progresso.total
                : 'Aplicar ' + corrigiveis + ' correção(ões)'}
            </button>
            {aplicando && (
              <>
                {progresso.total > 0 && (
                  <div style={{ flex: 1, height: 4, background: 'var(--border)', borderRadius: 2, overflow: 'hidden' }}>
                    <div style={{
                      height: '100%', borderRadius: 2,
                      background: 'var(--grn)',
                      width: Math.round(progresso.feitos / progresso.total * 100) + '%',
                      transition: 'width .2s ease',
                    }} />
                  </div>
                )}
                <button className="btn btn-secondary" onClick={cancelar}
                  style={{ fontSize: 13, color: '#b91c1c', borderColor: '#fca5a5' }}>
                  ✕ Cancelar
                </button>
              </>
            )}
            <button className="btn btn-secondary" onClick={exportarCsv}
              disabled={aplicando || divs.length === 0} style={{ fontSize: 13 }}
              title="Baixar divergências em CSV (para analisar/normalizar externamente)">
              <i data-lucide="download" style={{ width: 13, height: 13 }}></i> Exportar CSV
            </button>
            <button className="btn btn-secondary" onClick={analisar}
              disabled={aplicando} style={{ fontSize: 13 }}>
              <i data-lucide="refresh-cw" style={{ width: 13, height: 13 }}></i> Re-analisar
            </button>
          </div>
        </>
      )}
    </div>
  );
}

function AbaClientes({ onToast }) {
  const [segmentos, setSegmentos] = useCfgState(CRM_CONFIG.get('cliente_segmentos') || []);
  const [status, setStatus]       = useCfgState(CRM_CONFIG.get('cliente_status') || []);
  const [tipos, setTipos]         = useCfgState(CRM_CONFIG.get('cliente_tipos') || { aceita_pj: true, aceita_pf: true });
  const [dirty, setDirty]   = useCfgState(false);
  const [saving, setSaving] = useCfgState(false);

  async function salvar() {
    setSaving(true);
    try {
      await CRM_CONFIG.set('cliente_segmentos', segmentos.filter(s => s && s.trim()));
      await CRM_CONFIG.set('cliente_status',    status.filter(s => s && s.trim()));
      await CRM_CONFIG.set('cliente_tipos',     tipos);
      setDirty(false);
      onToast('Configurações de cliente salvas.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    } finally { setSaving(false); }
  }

  function reset() {
    setSegmentos(CRM_CONFIG.get('cliente_segmentos') || []);
    setStatus(CRM_CONFIG.get('cliente_status') || []);
    setTipos(CRM_CONFIG.get('cliente_tipos') || { aceita_pj: true, aceita_pf: true });
    setDirty(false);
  }

  return (
    <div>
      <CfgPanel titulo="Segmentos de cliente" icon="briefcase"
        sub="Lista usada no ClienteModal · campo Segmento"
        headerRight={<CfgTag>{segmentos.length} itens</CfgTag>}>
        <CfgListaStrings items={segmentos}
          onChange={v => { setSegmentos(v); setDirty(true); }}
          placeholder="Adicionar segmento" />
      </CfgPanel>

      <CfgPanel titulo={<>Status de cliente <CfgTag>Onda 2</CfgTag></>} icon="check-circle"
        sub="Estados possíveis do cliente">
        <CfgListaStrings items={status}
          onChange={v => { setStatus(v); setDirty(true); }}
          placeholder="Adicionar status" />
      </CfgPanel>

      <CfgPanel titulo="Tipos de cliente aceitos" icon="user-check"
        sub="PJ (empresa) e/ou PF (pessoa física)">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <CfgToggle checked={tipos.aceita_pj !== false}
            onChange={v => { setTipos(Object.assign({}, tipos, { aceita_pj: v })); setDirty(true); }}
            label="Aceitar PJ (Pessoa Jurídica) — exige CNPJ + IE" />
          <CfgToggle checked={tipos.aceita_pf !== false}
            onChange={v => { setTipos(Object.assign({}, tipos, { aceita_pf: v })); setDirty(true); }}
            label="Aceitar PF (Pessoa Física) — exige CPF + atividade/profissão" />
        </div>
      </CfgPanel>

      <CfgPanel titulo="Normalização de Cidades / UF" icon="map-pin"
        sub="Valida cidade e UF de cada cliente contra os 5.571 municípios do IBGE · corrige grafia e UF errada em lote">
        <NormalizacaoCidades onToast={onToast} />
      </CfgPanel>

      <CfgSaveBar dirty={dirty} saving={saving} onSave={salvar} onReset={reset} />
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 6 — Motivos de Perda
// ═══════════════════════════════════════════════════════

function AbaMotivos({ onToast }) {
  const [motivos, setMotivos]           = useCfgState(CRM_CONFIG.get('op_motivos_perda') || []);
  const [exigeTextoOutros, setExigeTO]  = useCfgState(CRM_CONFIG.get('op_motivo_outros_exige_texto') !== false);
  const [dirty, setDirty]   = useCfgState(false);
  const [saving, setSaving] = useCfgState(false);

  async function salvar() {
    setSaving(true);
    try {
      await CRM_CONFIG.set('op_motivos_perda',              motivos.filter(m => m && m.trim()));
      await CRM_CONFIG.set('op_motivo_outros_exige_texto',  !!exigeTextoOutros);
      setDirty(false);
      onToast('Motivos de perda salvos.', 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    } finally { setSaving(false); }
  }

  function reset() {
    setMotivos(CRM_CONFIG.get('op_motivos_perda') || []);
    setExigeTO(CRM_CONFIG.get('op_motivo_outros_exige_texto') !== false);
    setDirty(false);
  }

  return (
    <div>
      <CfgBanner kind="info">
        Lista <strong>padrão Komatsu</strong> — alterar com cuidado, afeta relatórios de vendas perdidas e o cruzamento com Inteligência Competitiva.
      </CfgBanner>

      <CfgPanel titulo="Motivos de perda" icon="x-circle"
        sub="Lista usada no FechamentoModal quando máquina é marcada como 'perdido'"
        headerRight={<CfgTag>{motivos.length} itens</CfgTag>}>
        <CfgListaStrings items={motivos}
          onChange={v => { setMotivos(v); setDirty(true); }}
          placeholder="Adicionar motivo" />
      </CfgPanel>

      <CfgPanel titulo="Validação" icon="message-square" sub="Regra adicional pro motivo 'Outros'">
        <CfgToggle checked={exigeTextoOutros}
          onChange={v => { setExigeTO(v); setDirty(true); }}
          label={<>Quando o motivo for <strong>"Outros"</strong>, exigir texto adicional explicando o motivo real</>} />
        <div style={{ font: '400 11px/1.4 var(--ff-body)', color: 'var(--tx-3)', marginTop: 10 }}>
          Sem isso, relatórios ficam cheios de "Outros" sem contexto.
        </div>
      </CfgPanel>

      <CfgSaveBar dirty={dirty} saving={saving} onSave={salvar} onReset={reset} />
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 7 — Territórios (edição da Lista SP "Regioes")
// ═══════════════════════════════════════════════════════

function AbaRegioes({ onToast }) {
  var [regioes,    setRegioes]    = useCfgState([]);
  var [carregando, setCarregando] = useCfgState(true);
  var [filtroQ,    setFiltroQ]    = useCfgState('');
  var [filtroUf,   setFiltroUf]   = useCfgState('');
  var [filtroVend, setFiltroVend] = useCfgState('');
  var [pagina,     setPagina]     = useCfgState(0);
  var [saving,     setSaving]     = useCfgState(null);  // _spId em progresso | '__nova__'
  var [excluindo,  setExcluindo]  = useCfgState(null);  // _spId sendo excluído
  var [novaLinha,  setNovaLinha]  = useCfgState(null);  // null | { cidade, uf, vendedor_atual, regiao_nome }

  var POR_PAGINA = 60;

  // ── Carrega da lista SP (ou CRM_DATA se já carregado) ──────────────────
  useCfgEffect(function() {
    async function carregar() {
      try {
        var dados = (window.CRM_DATA && CRM_DATA.regioes && CRM_DATA.regioes.length)
          ? CRM_DATA.regioes
          : await CRM_API.loadRegioes();
        setRegioes(dados.slice());
      } catch(e) {
        onToast('Erro ao carregar territórios: ' + e.message, 'err');
      } finally {
        setCarregando(false);
      }
    }
    carregar();
  }, []);

  // ── Lucide refresh ──────────────────────────────────────────────────────
  useCfgEffect(function() {
    setTimeout(function() { try { window.lucide && lucide.createIcons({ attrs: { 'stroke-width': 1.75 } }); } catch(e) {} }, 0);
  });

  // ── Reset paginação ao filtrar ──────────────────────────────────────────
  useCfgEffect(function() { setPagina(0); }, [filtroQ, filtroUf, filtroVend]);

  // ── Usuários ativos (para selects de vendedor) ──────────────────────────
  var ativos = ((window.CRM_DATA && CRM_DATA.usuarios) || []).filter(function(u) { return u.ativo !== false; });

  function nomeVend(email) {
    var u = ativos.find(function(u) { return u.id === email || u.email === email; });
    return u ? u.nome : (email ? email.split('@')[0] : '—');
  }

  // ── UFs disponíveis para filtro ─────────────────────────────────────────
  var ufsDisp = [];
  var _ufSet  = {};
  regioes.forEach(function(r) { if (r.uf && !_ufSet[r.uf]) { _ufSet[r.uf] = true; ufsDisp.push(r.uf); } });
  ufsDisp.sort();

  // ── Filtragem e paginação ───────────────────────────────────────────────
  function normQ(s) {
    return (s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase().trim();
  }
  var qN = normQ(filtroQ);
  var filtradas = regioes.filter(function(r) {
    if (qN && normQ(r.cidade).indexOf(qN) < 0 && normQ(r.regiao_nome || '').indexOf(qN) < 0) return false;
    if (filtroUf && r.uf !== filtroUf) return false;
    if (filtroVend && r.vendedor_atual !== filtroVend) return false;
    return true;
  });
  filtradas.sort(function(a, b) { return (a.cidade || '').localeCompare(b.cidade || '', 'pt-BR'); });
  var totalPaginas = Math.ceil(filtradas.length / POR_PAGINA);
  var pAtual       = Math.min(pagina, Math.max(0, totalPaginas - 1));
  var exibidas     = filtradas.slice(pAtual * POR_PAGINA, (pAtual + 1) * POR_PAGINA);

  // ── KPIs por UF ────────────────────────────────────────────────────────
  var kpis = {};
  regioes.forEach(function(r) { var k = r.uf || '?'; kpis[k] = (kpis[k] || 0) + 1; });

  // ── Salvar vendedor inline (select onChange) ────────────────────────────
  async function salvarVendedor(r, novoVend) {
    if (novoVend === r.vendedor_atual) return;
    setSaving(r._spId);
    try {
      await CRM_API.updateItem('Regioes', r._spId, {
        vendedor_antigo: r.vendedor_atual || '',
        vendedor_atual:  novoVend,
        data_alteracao:  new Date().toISOString().slice(0, 10)
      });
      var patch = { vendedor_antigo: r.vendedor_atual || '', vendedor_atual: novoVend, data_alteracao: new Date().toISOString().slice(0, 10) };
      setRegioes(function(prev) {
        return prev.map(function(x) { return x._spId === r._spId ? Object.assign({}, x, patch) : x; });
      });
      // Mantém CRM_DATA sincronizado (fonte do mapa territorial)
      if (window.CRM_DATA && CRM_DATA.regioes) {
        var idx = CRM_DATA.regioes.findIndex(function(x) { return x._spId === r._spId; });
        if (idx >= 0) CRM_DATA.regioes[idx] = Object.assign({}, CRM_DATA.regioes[idx], patch);
      }
      onToast(r.cidade + '/' + r.uf + ' → ' + (nomeVend(novoVend) || 'sem vendedor'), 'ok');
    } catch(e) {
      onToast('Erro ao salvar: ' + e.message, 'err');
    } finally { setSaving(null); }
  }

  // ── Adicionar nova linha ────────────────────────────────────────────────
  async function adicionarLinha() {
    if (!novaLinha) return;
    var cidade = (novaLinha.cidade || '').trim();
    var uf     = (novaLinha.uf || '').trim().toUpperCase();
    if (!cidade || !uf) { onToast('Cidade e UF são obrigatórios.', 'err'); return; }
    // Verifica duplicata local
    var jaExiste = regioes.some(function(r) {
      return normQ(r.cidade) === normQ(cidade) && r.uf === uf;
    });
    if (jaExiste) { onToast(cidade + '/' + uf + ' já existe na lista.', 'err'); return; }
    setSaving('__nova__');
    try {
      var item = {
        chave:           cidade + '|' + uf,
        cidade:          cidade,
        uf:              uf,
        vendedor_atual:  novaLinha.vendedor_atual || '',
        regiao_nome:     novaLinha.regiao_nome || ''
      };
      var created = await CRM_API.addItem('Regioes', item);
      var novo = Object.assign({}, item, { _spId: created && created.id ? String(created.id) : '__' + Date.now() });
      setRegioes(function(prev) { return prev.concat([novo]); });
      if (window.CRM_DATA && CRM_DATA.regioes) CRM_DATA.regioes.push(novo);
      setNovaLinha(null);
      onToast(cidade + '/' + uf + ' adicionado.', 'ok');
    } catch(e) {
      onToast('Erro ao adicionar: ' + e.message, 'err');
    } finally { setSaving(null); }
  }

  // ── Excluir linha ───────────────────────────────────────────────────────
  async function excluirLinha(r) {
    if (!await window.CRM_CONFIRM('Excluir ' + r.cidade + '/' + r.uf + '?\n\nEssa ação não pode ser desfeita.', { danger: true })) return;
    setExcluindo(r._spId);
    try {
      await CRM_API.deleteItem('Regioes', r._spId);
      setRegioes(function(prev) { return prev.filter(function(x) { return x._spId !== r._spId; }); });
      if (window.CRM_DATA && CRM_DATA.regioes) {
        CRM_DATA.regioes = CRM_DATA.regioes.filter(function(x) { return x._spId !== r._spId; });
      }
      onToast(r.cidade + '/' + r.uf + ' excluído.', 'ok');
    } catch(e) {
      onToast('Erro ao excluir: ' + e.message, 'err');
    } finally { setExcluindo(null); }
  }

  // ── Loading ─────────────────────────────────────────────────────────────
  if (carregando) return (
    <div style={{ textAlign: 'center', padding: '48px 0', color: 'var(--tx-3)', font: '400 13px/1.4 var(--ff-body)' }}>
      <i data-lucide="loader" style={{ width: 20, height: 20 }}></i>
      <div style={{ marginTop: 10 }}>Carregando territórios...</div>
    </div>
  );

  return (
    <div>
      <CfgBanner kind="info">
        Lista SP <strong>"Regioes"</strong> — fonte única do mapa territorial e do preenchimento automático
        de vendedor nas Vendas Perdidas. Alterações aqui refletem no mapa após recarregar a página do CRM.
      </CfgBanner>

      {/* KPIs por UF */}
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 16 }}>
        {Object.keys(kpis).sort().map(function(uf) {
          var ativo = filtroUf === uf;
          return (
            <div key={uf} onClick={function() { setFiltroUf(ativo ? '' : uf); }}
              style={{ padding: '10px 18px', background: ativo ? 'var(--grn-050, rgba(0,124,68,.08))' : 'var(--surface-2)',
                       border: '1px solid ' + (ativo ? 'var(--grn)' : 'var(--border)'),
                       borderRadius: 'var(--r-md)', textAlign: 'center', minWidth: 72,
                       cursor: 'pointer', transition: 'all .12s' }}>
              <div style={{ font: '700 22px/1 var(--ff-display)', color: ativo ? 'var(--grn)' : 'var(--tx)' }}>
                {kpis[uf]}
              </div>
              <div style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', color: 'var(--tx-3)', marginTop: 5 }}>
                {uf}
              </div>
            </div>
          );
        })}
        <div style={{ padding: '10px 18px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', textAlign: 'center', minWidth: 72 }}>
          <div style={{ font: '700 22px/1 var(--ff-display)', color: 'var(--tx)' }}>{regioes.length}</div>
          <div style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', color: 'var(--tx-3)', marginTop: 5 }}>Total</div>
        </div>
      </div>

      {/* Barra de busca + filtros + botão Adicionar */}
      <div style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 12, flexWrap: 'wrap' }}>
        <input className="inp" value={filtroQ}
          onChange={function(e) { setFiltroQ(e.target.value); }}
          placeholder="Buscar cidade ou região…"
          style={{ flex: 1, minWidth: 180, padding: '7px 12px', fontSize: 13 }} />
        <select className="inp" value={filtroUf}
          onChange={function(e) { setFiltroUf(e.target.value); }}
          style={{ width: 90, padding: '7px 8px', fontSize: 12 }}>
          <option value="">Todas UFs</option>
          {ufsDisp.map(function(uf) { return <option key={uf} value={uf}>{uf}</option>; })}
        </select>
        <select className="inp" value={filtroVend}
          onChange={function(e) { setFiltroVend(e.target.value); }}
          style={{ width: 190, padding: '7px 8px', fontSize: 12 }}>
          <option value="">Todos vendedores</option>
          {ativos.map(function(u) { return <option key={u.id} value={u.id}>{u.nome}</option>; })}
        </select>
        <button className="btn btn-primary"
          onClick={function() { setNovaLinha({ cidade: '', uf: filtroUf || '', vendedor_atual: '', regiao_nome: '' }); }}
          style={{ whiteSpace: 'nowrap', padding: '8px 14px', fontSize: 12 }}>
          <i data-lucide="plus" style={{ width: 13, height: 13 }}></i> Adicionar
        </button>
      </div>

      {/* Formulário nova linha */}
      {novaLinha && (
        <div style={{ padding: '12px 16px', background: 'rgba(0,124,68,.05)', marginBottom: 12,
                      border: '1px solid var(--grn)', borderRadius: 'var(--r-md)',
                      display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
          <i data-lucide="map-pin-plus" style={{ width: 16, height: 16, color: 'var(--grn)', flexShrink: 0 }}></i>
          <input className="inp" value={novaLinha.cidade} placeholder="Cidade *"
            onChange={function(e) { setNovaLinha(Object.assign({}, novaLinha, { cidade: e.target.value })); }}
            style={{ width: 190, padding: '6px 10px', fontSize: 12 }} />
          <select className="inp" value={novaLinha.uf}
            onChange={function(e) { setNovaLinha(Object.assign({}, novaLinha, { uf: e.target.value })); }}
            style={{ width: 80, padding: '6px 8px', fontSize: 12 }}>
            <option value="">UF *</option>
            {['BA','ES','RJ','SP'].map(function(u) { return <option key={u} value={u}>{u}</option>; })}
          </select>
          <select className="inp" value={novaLinha.vendedor_atual}
            onChange={function(e) { setNovaLinha(Object.assign({}, novaLinha, { vendedor_atual: e.target.value })); }}
            style={{ width: 190, padding: '6px 8px', fontSize: 12 }}>
            <option value="">— Sem vendedor —</option>
            {ativos.map(function(u) { return <option key={u.id} value={u.id}>{u.nome}</option>; })}
          </select>
          <input className="inp" value={novaLinha.regiao_nome} placeholder="Nome da região"
            onChange={function(e) { setNovaLinha(Object.assign({}, novaLinha, { regiao_nome: e.target.value })); }}
            style={{ flex: 1, minWidth: 130, padding: '6px 10px', fontSize: 12 }} />
          <button className="btn btn-primary" onClick={adicionarLinha} disabled={saving === '__nova__'}
            style={{ padding: '7px 14px', fontSize: 12 }}>
            {saving === '__nova__' ? 'Salvando…' : 'Salvar'}
          </button>
          <button className="btn btn-secondary" onClick={function() { setNovaLinha(null); }}
            style={{ padding: '7px 12px', fontSize: 12 }}>Cancelar</button>
        </div>
      )}

      {/* Contagem + paginação */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, flexWrap: 'wrap', gap: 8 }}>
        <div style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>
          {filtradas.length !== regioes.length
            ? filtradas.length + ' de ' + regioes.length + ' municípios'
            : regioes.length + ' municípios'}
          {totalPaginas > 1 && ' · pág. ' + (pAtual + 1) + ' / ' + totalPaginas}
        </div>
        {totalPaginas > 1 && (
          <div style={{ display: 'flex', gap: 4 }}>
            <button onClick={function() { setPagina(Math.max(0, pAtual - 1)); }} disabled={pAtual === 0}
              style={{ padding: '4px 12px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                       background: 'var(--surface-2)', cursor: pAtual === 0 ? 'not-allowed' : 'pointer',
                       font: '500 12px/1 var(--ff-body)', opacity: pAtual === 0 ? 0.4 : 1 }}>← Ant.</button>
            <button onClick={function() { setPagina(Math.min(totalPaginas - 1, pAtual + 1)); }} disabled={pAtual >= totalPaginas - 1}
              style={{ padding: '4px 12px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                       background: 'var(--surface-2)', cursor: pAtual >= totalPaginas - 1 ? 'not-allowed' : 'pointer',
                       font: '500 12px/1 var(--ff-body)', opacity: pAtual >= totalPaginas - 1 ? 0.4 : 1 }}>Próx. →</button>
          </div>
        )}
      </div>

      {/* Tabela */}
      <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', overflow: 'hidden' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
          <thead>
            <tr style={{ background: 'var(--surface-2)' }}>
              {['Cidade', 'UF', 'Vendedor Atual', 'Região', 'Histórico', ''].map(function(h, i) {
                return (
                  <th key={i} style={{ padding: '8px 12px', textAlign: 'left',
                    font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase',
                    letterSpacing: '.06em', color: 'var(--tx-3)',
                    borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap' }}>{h}</th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {exibidas.length === 0 ? (
              <tr>
                <td colSpan={6} style={{ padding: '32px', textAlign: 'center', color: 'var(--tx-3)', font: '400 13px/1.4 var(--ff-body)' }}>
                  {(filtroQ || filtroUf || filtroVend) ? 'Nenhum município encontrado.' : 'Nenhum território cadastrado.'}
                </td>
              </tr>
            ) : exibidas.map(function(r) {
              var isSaving   = saving === r._spId;
              var isExcluindo = excluindo === r._spId;
              return (
                <tr key={r._spId} style={{ borderBottom: '1px solid var(--border)', opacity: (isSaving || isExcluindo) ? 0.45 : 1 }}>
                  <td style={{ padding: '7px 12px', font: '500 12px/1 var(--ff-body)', color: 'var(--tx)' }}>
                    {r.cidade}
                  </td>
                  <td style={{ padding: '7px 12px', font: '600 11px/1 var(--ff-mono)', color: 'var(--tx-3)' }}>
                    {r.uf}
                  </td>
                  <td style={{ padding: '5px 12px' }}>
                    <select className="inp" value={r.vendedor_atual || ''} disabled={isSaving}
                      onChange={function(e) { salvarVendedor(r, e.target.value); }}
                      style={{ width: '100%', maxWidth: 210, padding: '5px 8px', fontSize: 11 }}>
                      <option value="">— Sem vendedor —</option>
                      {ativos.map(function(u) { return <option key={u.id} value={u.id}>{u.nome}</option>; })}
                    </select>
                  </td>
                  <td style={{ padding: '7px 12px', font: '400 11px/1 var(--ff-body)', color: 'var(--tx-2)', maxWidth: 200 }}>
                    {r.regiao_nome || <span style={{ color: 'var(--tx-3)', fontStyle: 'italic' }}>—</span>}
                  </td>
                  <td style={{ padding: '7px 12px', font: '400 10px/1.5 var(--ff-body)', color: 'var(--tx-3)', whiteSpace: 'nowrap' }}>
                    {r.vendedor_antigo ? <div>Ant.: {nomeVend(r.vendedor_antigo)}</div> : null}
                    {r.data_alteracao  ? <div>{String(r.data_alteracao).slice(0, 10)}</div> : null}
                  </td>
                  <td style={{ padding: '5px 12px', textAlign: 'right' }}>
                    <button onClick={function() { excluirLinha(r); }} disabled={isExcluindo}
                      title={'Excluir ' + r.cidade + '/' + r.uf}
                      style={{ background: 'transparent', border: 0, cursor: isExcluindo ? 'not-allowed' : 'pointer',
                               color: 'var(--tx-3)', padding: '4px', borderRadius: 'var(--r-xs)',
                               opacity: isExcluindo ? 0.3 : 1 }}>
                      <i data-lucide="trash-2" style={{ width: 13, height: 13 }}></i>
                    </button>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      {/* Paginação inferior */}
      {totalPaginas > 1 && (
        <div style={{ display: 'flex', justifyContent: 'center', gap: 4, marginTop: 12 }}>
          <button onClick={function() { setPagina(Math.max(0, pAtual - 1)); }} disabled={pAtual === 0}
            style={{ padding: '6px 16px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                     background: 'var(--surface-2)', cursor: pAtual === 0 ? 'not-allowed' : 'pointer',
                     font: '500 12px/1 var(--ff-body)', opacity: pAtual === 0 ? 0.4 : 1 }}>← Anterior</button>
          <span style={{ padding: '6px 14px', font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>
            {pAtual + 1} / {totalPaginas}
          </span>
          <button onClick={function() { setPagina(Math.min(totalPaginas - 1, pAtual + 1)); }} disabled={pAtual >= totalPaginas - 1}
            style={{ padding: '6px 16px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                     background: 'var(--surface-2)', cursor: pAtual >= totalPaginas - 1 ? 'not-allowed' : 'pointer',
                     font: '500 12px/1 var(--ff-body)', opacity: pAtual >= totalPaginas - 1 ? 0.4 : 1 }}>Próxima →</button>
        </div>
      )}
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// ABA 8 — Komtrax (importação de frota via CSV do SharePoint)
// ═══════════════════════════════════════════════════════

function AbaKomtrax({ onToast }) {
  const [fase, setFase]       = useCfgState('idle'); // idle | loading | preview | importing | done
  const [preview, setPreview] = useCfgState([]);
  const [stats, setStats]     = useCfgState(null);
  const [filtro, setFiltro]   = useCfgState('todos');
  const [log, setLog]         = useCfgState([]);
  const [aprovarTh, setAprovarTh] = useCfgState(80); // threshold % para aprovação em lote

  // ── Conciliação de órfãs (estado independente do import principal) ───────
  const [orfasFase,      setOrfasFase]      = useCfgState('idle'); // idle | loading | preview | aplicando
  const [orfasMatches,   setOrfasMatches]   = useCfgState([]);
  const [orfasThreshold, setOrfasThreshold] = useCfgState(70);
  const [orfasFiltro,    setOrfasFiltro]    = useCfgState('todos'); // todos | matchAuto | sugestao | semMatch
  const [orfasBusca,     setOrfasBusca]     = useCfgState(''); // texto livre filtra Cliente Komtrax + Sugestão CRM
  const [orfasLimit,     setOrfasLimit]     = useCfgState(200); // paginação (carregar mais 200)

  // ── Toggles do detector (persistidos em localStorage) ────────────────────
  // preservarVinculo: quando true, ignora divergência de cliente_id se o SP já tem um vínculo.
  //                   Preserva curadoria manual. Excepção: sugestões aprovadas no painel (B) sempre passam.
  // modoConservador:  quando true, só detecta mudança de SMR. Modelo/série/cliente_id são ignorados.
  //                   Útil pra rodadas mensais "leves" depois que a base está curada.
  const [preservarVinculo, _setPreservarVinculo] = useCfgState(function() {
    try { return localStorage.getItem('bauko_crm_komtrax_preservarVinculo') !== 'false'; } catch(e) { return true; }
  });
  function setPreservarVinculo(v) {
    _setPreservarVinculo(v);
    try { localStorage.setItem('bauko_crm_komtrax_preservarVinculo', v ? 'true' : 'false'); } catch(e) {}
  }
  const [modoConservador, _setModoConservador] = useCfgState(function() {
    try { return localStorage.getItem('bauko_crm_komtrax_modoConservador') === 'true'; } catch(e) { return false; }
  });
  function setModoConservador(v) {
    _setModoConservador(v);
    try { localStorage.setItem('bauko_crm_komtrax_modoConservador', v ? 'true' : 'false'); } catch(e) {}
  }

  useCfgEffect(() => {
    setTimeout(() => { try { window.lucide && lucide.createIcons({ attrs: { 'stroke-width': 1.75 } }); } catch(e) {} }, 0);
  }, [fase, preview.length, log.length]);

  function cleanCnpj(s) {
    return String(s || '').replace(/\D/g, '');
  }

  function unq(s) {
    // Remove surrounding double quotes: "valor" → valor
    var t = String(s || '').trim();
    return (t.charAt(0) === '"' && t.charAt(t.length - 1) === '"') ? t.slice(1, -1) : t;
  }

  function parseKomtraxCsv(arrayBuffer) {
    try {
      const decoder = new TextDecoder('utf-16le');
      let text = decoder.decode(arrayBuffer);
      // Strip BOM (U+FEFF) se presente
      if (text.charCodeAt(0) === 0xFEFF) text = text.slice(1);
      const lines = text.split(/\r?\n/).filter(l => l.trim());
      if (lines.length < 2) return [];
      // Headers: strip aspas externas e espaços
      const headers = lines[0].split('\t').map(h => unq(h));
      const rows = [];
      for (let i = 1; i < lines.length; i++) {
        const vals = lines[i].split('\t');
        const row = {};
        headers.forEach((h, j) => { row[h] = unq(vals[j] || ''); });
        const serie = row['Nº De Série'] || row['N\xBA De S\xe9rie'] || '';
        if (serie) { row._serie = serie; rows.push(row); }
      }
      return rows;
    } catch(e) {
      console.error('[Komtrax] parseKomtraxCsv:', e);
      return [];
    }
  }

  async function carregarCsv() {
    setFase('loading');
    setLog([]);
    setPreview([]);
    setStats(null);
    try {
      const token  = await CRM_API.getToken();
      const csvUrl = window.BAUKO_AUTH.spFileUrl('komtrax', '/content');

      const [respCsv, frota] = await Promise.all([
        fetch(csvUrl, { headers: { Authorization: 'Bearer ' + token } }),
        CRM_API.loadFrotaCliente()
      ]);
      if (!respCsv.ok) throw new Error('HTTP ' + respCsv.status + ' ao buscar Komtrax.csv');

      const buf     = await respCsv.arrayBuffer();
      const csvRows = parseKomtraxCsv(buf);
      if (!csvRows.length) throw new Error('CSV vazio ou não foi possível parsear');

      // Clientes: usa CRM_DATA se já carregado, senão busca do SP
      const clientes = (window.CRM_DATA && CRM_DATA.clientes && CRM_DATA.clientes.length)
        ? CRM_DATA.clientes : await CRM_API.loadClientes();

      // Índices por CNPJ e CPF (só dígitos) → cliente
      const porCnpj = {};
      const porCpf  = {};
      clientes.forEach(c => {
        const cnpj = cleanCnpj(c.cnpj);
        if (cnpj) {
          porCnpj[cnpj] = c;
          // também sem zeros à esquerda (ex: CNPJ com menos de 14 dígitos no SP)
          const sem0 = cnpj.replace(/^0+/, '') || cnpj;
          if (!porCnpj[sem0]) porCnpj[sem0] = c;
        }
        // CPF para clientes PF
        const cpf = cleanCnpj(c.cpf || '');
        if (cpf) porCpf[cpf] = c;
      });

      // ── Match por NOME — fallback quando doc não bate ───────────────────────
      // (A) Match exato: nome normalizado sem sufixos jurídicos coincide
      // (B) Similaridade Dice + 1ª palavra exigida — gera sugestões pra revisão manual
      const _STOP_NOMES = new Set([
        'LTDA','EIRELI','ME','EPP','MEI','SA','S/A','SOCIEDADE','EMPRESARIAL','EMPRESA',
        'DE','DA','DO','DOS','DAS','E','A','O','OS','AS'
      ]);
      function _normNome(s) {
        if (!s) return '';
        return String(s)
          .normalize('NFD').replace(/[̀-ͯ]/g, '')
          .toUpperCase()
          .replace(/[^\sA-Z0-9]/g, ' ')
          .replace(/\s+/g, ' ')
          .trim();
      }
      function _tokensNome(s) {
        return _normNome(s).split(' ').filter(function(t) {
          return t.length >= 2 && !_STOP_NOMES.has(t);
        });
      }
      function _chaveNome(s) { return _tokensNome(s).join(' '); }
      function _simNomes(tokensA, tokensB) {
        if (!tokensA.length || !tokensB.length) return 0;
        // Primeira palavra significativa DEVE bater (anti-falso-positivo)
        if (tokensA[0] !== tokensB[0]) return 0;
        var setA = new Set(tokensA);
        var setB = new Set(tokensB);
        var inter = 0;
        setA.forEach(function(t) { if (setB.has(t)) inter++; });
        return (2 * inter) / (setA.size + setB.size);  // Dice coefficient
      }

      // Índice (A): nome normalizado completo → cliente(s)
      // Índice (B): primeira palavra → [{cli, tokens}] — para limitar busca de similaridade
      const porNomeExato = {};
      const porPrimaria  = {};
      clientes.forEach(c => {
        var tokens = _tokensNome(c.razao);
        if (!tokens.length) return;
        var chave = tokens.join(' ');
        if (!porNomeExato[chave]) porNomeExato[chave] = [];
        porNomeExato[chave].push(c);
        var prim = tokens[0];
        if (!porPrimaria[prim]) porPrimaria[prim] = [];
        porPrimaria[prim].push({ cli: c, tokens: tokens });
      });
      const SIM_THRESHOLD = 0.6;  // ajustar empíricamente

      // Helper: extrai o CÓDIGO do modelo (palavras com dígitos) — formato Komatsu.
      // Ex: "Escavadeira hidráulica PC350LC" → "PC350LC"
      //     "Trator de esteiras D51EX-15"   → "D51EX-15"
      //     "Carregadeira de rodas WA320-6" → "WA320-6"
      // Antes truncava em 15 chars e colidia ("ESCAVADEIRAHIDR" para qualquer
      // escavadeira), causando confusão entre PC130/PC210/PC350 etc. com mesmo nº de série.
      function _slugM(s) {
        if (!s) return '';
        var palavras = String(s).split(/\s+/).filter(function(p) { return /\d/.test(p); });
        if (palavras.length === 0) {
          // Fallback raro (modelo sem dígito): usa slug antigo
          return String(s).replace(/\s+/g,'').replace(/[^A-Za-z0-9]/g,'').toUpperCase().slice(0,15);
        }
        return palavras.join('-').replace(/[^A-Za-z0-9-]/g, '').toUpperCase();
      }

      // Índice duplo de frota KOM existente:
      // - frotaPorId:          id → máquina (todos)
      // - frotaPorSerieModelo: "serie|modeloSlug" → máquina (só KOM)
      // Nota: o mesmo nº de série pode existir em modelos distintos na Komatsu.
      const frotaPorId          = {};
      const frotaPorSerieModelo = {};
      frota.forEach(f => {
        if (f.id) frotaPorId[f.id.toLowerCase()] = f;
        if (f.serie && f.id && f.id.toLowerCase().startsWith('kom-')) {
          const mn  = _slugM(f.modelo);
          const key = (f.serie + '|' + mn).toLowerCase();
          frotaPorSerieModelo[key] = f;
        }
      });

      const result = csvRows.map(row => {
        const serie  = row._serie;
        const smr    = parseFloat(String(row['SMR[H]'] || '0').replace(',', '.')) || 0;
        const modelo = (row['Modelo'] || '').trim();
        const tipo   = (row['Tipo De Máquina'] || row['Tipo De Maquina'] || '').trim();

        // Chave única: serie + slug do modelo completo (tipo + modelo)
        // Ex: B20715 + "HYDRAULICEXCAVATO" → KOM-B20715-HYDRAULICEXCAVATO
        // Sem esse slug, mesmo série em modelos diferentes colidiria no mesmo ID.
        const modeloSlug = _slugM([tipo, modelo].filter(Boolean).join(' '));
        const komId      = 'KOM-' + serie + (modeloSlug ? '-' + modeloSlug : '');

        const docRaw = cleanCnpj(row['Identificação do Cliente'] || row['Identificacao do Cliente'] || '');
        // CPF = 11 dígitos, CNPJ = 14 dígitos; tenta os dois para cobrir clientes PF
        let clienteMatch = null;
        let matchTipo    = '';  // 'doc' | 'nome_exato' | '' (sem match) — origem do clienteMatch
        if (docRaw) {
          if (docRaw.length === 11) {
            clienteMatch = porCpf[docRaw] || null;
          } else {
            clienteMatch = porCnpj[docRaw] || porCnpj[docRaw.replace(/^0+/, '') || docRaw] || null;
          }
          if (clienteMatch) matchTipo = 'doc';
        }
        const cnpjRaw     = docRaw;
        const nomeKomtrax = (row['Nome do Cliente'] || '').trim();

        // Fallback (A) — match por NOME EXATO normalizado (sem sufixos jurídicos)
        // Só aplica se houver UM ÚNICO cliente com esse nome — ambíguos vão pra sugestão
        if (!clienteMatch && nomeKomtrax) {
          var chave = _chaveNome(nomeKomtrax);
          if (chave && porNomeExato[chave] && porNomeExato[chave].length === 1) {
            clienteMatch = porNomeExato[chave][0];
            matchTipo    = 'nome_exato';
          }
        }

        // (B) Sugestões — só quando ainda não há clienteMatch e há nome no Komtrax
        // Limita busca a clientes com mesma primeira palavra (otimização — sem isso seria 4k*8k=32M ops)
        var sugestoes = [];
        if (!clienteMatch && nomeKomtrax) {
          var tokensK = _tokensNome(nomeKomtrax);
          var prim    = tokensK[0];
          var pool    = prim ? (porPrimaria[prim] || []) : [];
          var scores  = [];
          pool.forEach(function(ct) {
            var s = _simNomes(tokensK, ct.tokens);
            if (s >= SIM_THRESHOLD) scores.push({ cliente: ct.cli, score: s });
          });
          scores.sort(function(a, b) { return b.score - a.score; });
          sugestoes = scores.slice(0, 3);
        }

        // Lookup do registro existente na frota (mesma lógica anterior)
        const _bySerieOnly = frotaPorId[('KOM-' + serie).toLowerCase()] || null;
        const existente =
          frotaPorSerieModelo[(serie + '|' + modeloSlug).toLowerCase()] ||
          (_bySerieOnly && _slugM(_bySerieOnly.modelo) === modeloSlug ? _bySerieOnly : null) ||
          null;

        let ano = '';
        const dd = row['Data De Entrega'];
        if (dd) { const m = String(dd).match(/(\d{4})/); if (m) ano = m[1]; }

        let action, changes = [];
        if (existente) {
          // Já existe na frota — comparar para detectar mudanças, respeitando toggles
          const modeloCompleto = [tipo, modelo].filter(Boolean).join(' ');
          const smrExist       = parseFloat(String(existente.horimetro || '0').replace(',', '.')) || 0;
          const clienteIdNovo  = clienteMatch ? clienteMatch.id : null;
          const clienteAtual   = existente.cliente_id || '';
          // SMR — sempre verifica
          if (Math.abs(smr - smrExist) > 0.5)
            changes.push('SMR: ' + smrExist.toLocaleString('pt-BR') + ' → ' + smr.toLocaleString('pt-BR') + 'h');
          // Modo conservador suprime todos os outros campos (só SMR conta)
          if (!modoConservador) {
            if (modeloCompleto && modeloCompleto !== (existente.modelo || ''))
              changes.push('Modelo');
            if (serie && serie !== (existente.serie || ''))
              changes.push('Série');
            // Vínculo de cliente: preservarVinculo só protege quando SP JÁ TEM um cliente vinculado
            // (órfãs continuam sendo preenchidas). Sugestões aprovadas (B) sempre passam.
            if (clienteIdNovo && clienteIdNovo !== clienteAtual) {
              var ehManual = (matchTipo === 'nome_sugestao');
              if (!preservarVinculo || !clienteAtual || ehManual) {
                changes.push('Vínculo de cliente');
              }
            }
          }
          action = changes.length > 0 ? 'update' : 'same';
        } else if (clienteMatch) {
          // Cliente vinculado (por doc ou nome exato) — pode adicionar
          action = 'add';
        } else if (sugestoes.length > 0) {
          // Sugestões disponíveis para revisão manual — não importa automaticamente
          action = 'sugestao';
        } else {
          // Nenhum cliente identificado
          action = 'skip';
        }

        return {
          serie, komId, modelo, tipo, smr, ano, cnpjRaw, nomeKomtrax,
          clienteMatch, matchTipo, existente, action, changes,
          sugestoes,                  // array [{cliente, score}]
          sugestaoAprovada: null,     // cliente selecionado pelo usuário (vira clienteMatch ao aprovar)
        };
      });

      setPreview(result);
      setStats({
        total:       result.length,
        toAdd:       result.filter(r => r.action === 'add').length,
        toUpdate:    result.filter(r => r.action === 'update').length,
        toSkip:      result.filter(r => r.action === 'skip').length,
        toSame:      result.filter(r => r.action === 'same').length,
        toSugestao:  result.filter(r => r.action === 'sugestao').length,
      });
      setFase('preview');
      onToast(result.length + ' equipamentos carregados do Komtrax.csv', 'ok');
    } catch(e) {
      onToast('Erro: ' + e.message, 'err');
      setFase('idle');
    }
  }

  async function importar() {
    const toProcess = preview.filter(r => r.action !== 'skip' && r.action !== 'same');
    if (!toProcess.length) { onToast('Nenhum equipamento para importar.', 'err'); return; }
    setFase('importing');
    const newLog = [];
    const successKomIds = new Set();  // rastreia quem foi gravado com sucesso (vira 'same')
    let ok = 0, errs = 0;
    var frotaArr = CRM_DATA.frotaCliente;
    for (const row of toProcess) {
      try {
        const modeloCompleto = [row.tipo, row.modelo].filter(Boolean).join(' ');
        if (row.action === 'add') {
          const novaMaq = {
            id:         row.komId,
            cliente_id: row.clienteMatch.id,
            fabricante: 'Komatsu',
            modelo:     modeloCompleto,
            ano:        row.ano,
            horimetro:  String(row.smr),
            serie:      row.serie,
          };
          const created = await CRM_API.addItem('FrotaCliente', novaMaq);
          // Mutação local: adiciona ao array em memória, sem recarregar tudo
          frotaArr.push(Object.assign({}, novaMaq, {
            _spId: created && created.id ? String(created.id) : undefined,
          }));
          newLog.push('✅ ' + row.komId + ' → ' + (row.clienteMatch.razao || row.clienteMatch.id));
        } else {
          // Monta updFields respeitando os toggles (mesma lógica do detector)
          const updFields = { horimetro: String(row.smr) };
          if (!modoConservador) {
            updFields.modelo = modeloCompleto;
            updFields.serie  = row.serie;
            // cliente_id: só atualiza se SP está órfão OU sugestão aprovada manualmente OU preservar desligado
            if (row.clienteMatch) {
              var clienteAtual = row.existente.cliente_id || '';
              var ehManual     = (row.matchTipo === 'nome_sugestao');
              if (!preservarVinculo || !clienteAtual || ehManual) {
                updFields.cliente_id = row.clienteMatch.id;
              }
            }
          }
          await CRM_API.updateItem('FrotaCliente', row.existente._spId, updFields);
          // Mutação local: atualiza objeto no array em memória
          var idxExist = frotaArr.findIndex(function(m){ return m._spId === row.existente._spId; });
          if (idxExist >= 0) {
            frotaArr[idxExist] = Object.assign({}, frotaArr[idxExist], updFields);
          }
          const semVinculo = !row.existente.cliente_id && row.clienteMatch && updFields.cliente_id;
          newLog.push('🔄 ' + row.komId + ' atualizado — SMR ' + row.smr + 'h'
            + (semVinculo ? ' · vinculado a ' + (row.clienteMatch.razao || row.clienteMatch.id) : ''));
        }
        ok++;
        successKomIds.add(row.komId);
      } catch(e) {
        newLog.push('❌ ' + row.komId + ': ' + e.message.slice(0, 100));
        errs++;
      }
    }
    setLog(newLog);
    setFase('done');
    if (ok > 0) {
      CRM_API.invalidateCache();
      // NOTA: removido reload via CRM_API.loadFrotaCliente() — mutação local incremental
      // foi feita por linha dentro do loop acima, então CRM_DATA.frotaCliente já reflete
      // o estado correto sem precisar baixar a lista inteira do SharePoint.

      // Atualiza preview/stats: itens que foram gravados ('add' ou 'update') agora
      // viram 'same' (já estão sincronizados com o SP). Itens com falha permanecem
      // em 'add'/'update' para permitir retry. Evita reimport redundante.
      setPreview(function(prev) {
        var updated = prev.map(function(r) {
          if (successKomIds.has(r.komId) && (r.action === 'add' || r.action === 'update')) {
            return Object.assign({}, r, { action: 'same', changes: [] });
          }
          return r;
        });
        setStats({
          total:      updated.length,
          toAdd:      updated.filter(function(r){ return r.action === 'add';      }).length,
          toUpdate:   updated.filter(function(r){ return r.action === 'update';   }).length,
          toSkip:     updated.filter(function(r){ return r.action === 'skip';     }).length,
          toSame:     updated.filter(function(r){ return r.action === 'same';     }).length,
          toSugestao: updated.filter(function(r){ return r.action === 'sugestao'; }).length,
        });
        return updated;
      });

      onToast(ok + ' equipamento(s) importado(s)' + (errs ? ', ' + errs + ' erro(s).' : '.'), errs ? 'err' : 'ok');
    } else {
      onToast('Importação falhou. Ver log abaixo.', 'err');
    }
  }

  const statusIcon  = a => a === 'add' ? '➕' : a === 'update' ? '🔄' : a === 'same' ? '✓' : a === 'sugestao' ? '≈' : '—';
  const statusLabel = a => a === 'add' ? 'Adicionar' : a === 'update' ? 'Atualizar' : a === 'same' ? 'Sem alteração' : a === 'sugestao' ? 'Sugestão' : 'Ignorar';
  const statusColor = a => a === 'add' ? 'var(--grn)' : a === 'update' ? '#3370b7' : a === 'same' ? '#64748b' : a === 'sugestao' ? '#a855f7' : 'var(--tx-3)';

  // Aprovar/rejeitar sugestão: vincula o cliente escolhido e re-classifica como add (ou same/update se existente)
  function aprovarSugestao(komId, clienteSugerido) {
    setPreview(function(prev) {
      var novo = prev.map(function(r) {
        if (r.komId !== komId) return r;
        // Cliente aprovado vira o clienteMatch — action recalculada
        var novaAction = r.existente ? 'update' : 'add';
        var novosChanges = r.changes || [];
        if (r.existente && clienteSugerido.id !== (r.existente.cliente_id || '')) {
          if (novosChanges.indexOf('Vínculo de cliente') === -1) novosChanges = novosChanges.concat(['Vínculo de cliente']);
        }
        return Object.assign({}, r, {
          clienteMatch:     clienteSugerido,
          matchTipo:        'nome_sugestao',
          sugestaoAprovada: clienteSugerido,
          action:           novaAction,
          changes:          novosChanges,
        });
      });
      // Recalcula stats com base no novo array
      setStats({
        total:      novo.length,
        toAdd:      novo.filter(function(r){ return r.action === 'add';      }).length,
        toUpdate:   novo.filter(function(r){ return r.action === 'update';   }).length,
        toSkip:     novo.filter(function(r){ return r.action === 'skip';     }).length,
        toSame:     novo.filter(function(r){ return r.action === 'same';     }).length,
        toSugestao: novo.filter(function(r){ return r.action === 'sugestao'; }).length,
      });
      return novo;
    });
  }
  function rejeitarSugestao(komId) {
    setPreview(function(prev) {
      var novo = prev.map(function(r) {
        if (r.komId !== komId) return r;
        return Object.assign({}, r, { action: 'skip', sugestoes: [], sugestaoAprovada: null });
      });
      setStats({
        total:      novo.length,
        toAdd:      novo.filter(function(r){ return r.action === 'add';      }).length,
        toUpdate:   novo.filter(function(r){ return r.action === 'update';   }).length,
        toSkip:     novo.filter(function(r){ return r.action === 'skip';     }).length,
        toSame:     novo.filter(function(r){ return r.action === 'same';     }).length,
        toSugestao: novo.filter(function(r){ return r.action === 'sugestao'; }).length,
      });
      return novo;
    });
  }

  // ── Ações em LOTE para sugestões ─────────────────────────────────────────
  // Aprova a 1ª sugestão (sempre a de maior score) de cada linha cujo score
  // do top candidato seja >= threshold. Usuário ajusta o threshold com slider.
  function aprovarLote(thresholdPct) {
    var th = thresholdPct / 100;
    setPreview(function(prev) {
      var aprovadas = 0;
      var novo = prev.map(function(r) {
        if (r.action !== 'sugestao' || !r.sugestoes || !r.sugestoes.length) return r;
        var top = r.sugestoes[0];
        if (top.score < th) return r;
        var novaAction = r.existente ? 'update' : 'add';
        var novosChanges = r.changes || [];
        if (r.existente && top.cliente.id !== (r.existente.cliente_id || '')) {
          if (novosChanges.indexOf('Vínculo de cliente') === -1) novosChanges = novosChanges.concat(['Vínculo de cliente']);
        }
        aprovadas++;
        return Object.assign({}, r, {
          clienteMatch:     top.cliente,
          matchTipo:        'nome_sugestao',
          sugestaoAprovada: top.cliente,
          action:           novaAction,
          changes:          novosChanges,
        });
      });
      setStats({
        total:      novo.length,
        toAdd:      novo.filter(function(r){ return r.action === 'add';      }).length,
        toUpdate:   novo.filter(function(r){ return r.action === 'update';   }).length,
        toSkip:     novo.filter(function(r){ return r.action === 'skip';     }).length,
        toSame:     novo.filter(function(r){ return r.action === 'same';     }).length,
        toSugestao: novo.filter(function(r){ return r.action === 'sugestao'; }).length,
      });
      if (aprovadas > 0) onToast(aprovadas + ' sugestão(ões) aprovada(s) (score ≥ ' + thresholdPct + '%).', 'ok');
      else               onToast('Nenhuma sugestão com score ≥ ' + thresholdPct + '%.', 'warn');
      return novo;
    });
  }

  function rejeitarTodasSugestoes() {
    setPreview(function(prev) {
      var rejeitadas = 0;
      var novo = prev.map(function(r) {
        if (r.action !== 'sugestao') return r;
        rejeitadas++;
        return Object.assign({}, r, { action: 'skip', sugestoes: [], sugestaoAprovada: null });
      });
      setStats({
        total:      novo.length,
        toAdd:      novo.filter(function(r){ return r.action === 'add';      }).length,
        toUpdate:   novo.filter(function(r){ return r.action === 'update';   }).length,
        toSkip:     novo.filter(function(r){ return r.action === 'skip';     }).length,
        toSame:     novo.filter(function(r){ return r.action === 'same';     }).length,
        toSugestao: novo.filter(function(r){ return r.action === 'sugestao'; }).length,
      });
      if (rejeitadas > 0) onToast(rejeitadas + ' sugestão(ões) rejeitada(s).', 'ok');
      return novo;
    });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CONCILIAÇÃO DE ÓRFÃS — busca sugestões para máquinas KOM sem cliente_id
  // ═══════════════════════════════════════════════════════════════════════
  async function buscarSugestoesOrfas() {
    setOrfasFase('loading');
    try {
      // 1. Recupera órfãs em memória
      var orfas = (CRM_DATA.frotaCliente || []).filter(function(m) {
        return m && m.id && m.id.toLowerCase().indexOf('kom-') === 0 && !m.cliente_id;
      });
      if (!orfas.length) {
        onToast('Não há máquinas órfãs.', 'ok');
        setOrfasFase('idle');
        setOrfasMatches([]);
        return;
      }
      // 2. Lê CSV Komtrax (mesma lógica de carregarCsv)
      const token  = await CRM_API.getToken();
      const csvUrl = window.BAUKO_AUTH.spFileUrl('komtrax', '/content');
      const respCsv = await fetch(csvUrl, { headers: { Authorization: 'Bearer ' + token } });
      if (!respCsv.ok) throw new Error('HTTP ' + respCsv.status + ' ao buscar Komtrax.csv');
      const buf = await respCsv.arrayBuffer();
      const csvRows = parseKomtraxCsv(buf);
      if (!csvRows.length) throw new Error('CSV vazio');

      // 3. Indexa CSV por número de série
      const csvBySerie = {};
      csvRows.forEach(function(r) { if (r._serie) csvBySerie[r._serie] = r; });

      // 4. Constrói índices de clientes (CNPJ, CPF, nome exato, primeira palavra)
      const clientes = (window.CRM_DATA && CRM_DATA.clientes) || [];
      const porCnpj = {}, porCpf = {};
      clientes.forEach(function(c) {
        var cnpj = cleanCnpj(c.cnpj);
        if (cnpj) {
          porCnpj[cnpj] = c;
          var sem0 = cnpj.replace(/^0+/, '') || cnpj;
          if (!porCnpj[sem0]) porCnpj[sem0] = c;
        }
        var cpf = cleanCnpj(c.cpf || '');
        if (cpf) porCpf[cpf] = c;
      });

      // Helpers de nome (mesmos do carregarCsv)
      const _STOP_NOMES = new Set([
        'LTDA','EIRELI','ME','EPP','MEI','SA','S/A','SOCIEDADE','EMPRESARIAL','EMPRESA',
        'DE','DA','DO','DOS','DAS','E','A','O','OS','AS'
      ]);
      function _normNome(s) {
        if (!s) return '';
        return String(s)
          .normalize('NFD').replace(/[̀-ͯ]/g, '')
          .toUpperCase().replace(/[^\sA-Z0-9]/g, ' ')
          .replace(/\s+/g, ' ').trim();
      }
      function _tokensNome(s) {
        return _normNome(s).split(' ').filter(function(t) {
          return t.length >= 2 && !_STOP_NOMES.has(t);
        });
      }
      function _chaveNome(s) { return _tokensNome(s).join(' '); }
      function _simNomes(tokensA, tokensB) {
        if (!tokensA.length || !tokensB.length) return 0;
        if (tokensA[0] !== tokensB[0]) return 0;
        var setA = new Set(tokensA), setB = new Set(tokensB);
        var inter = 0;
        setA.forEach(function(t) { if (setB.has(t)) inter++; });
        return (2 * inter) / (setA.size + setB.size);
      }

      const porNomeExato = {}, porPrimaria = {};
      clientes.forEach(function(c) {
        var tokens = _tokensNome(c.razao);
        if (!tokens.length) return;
        var chave = tokens.join(' ');
        if (!porNomeExato[chave]) porNomeExato[chave] = [];
        porNomeExato[chave].push(c);
        var prim = tokens[0];
        if (!porPrimaria[prim]) porPrimaria[prim] = [];
        porPrimaria[prim].push({ cli: c, tokens: tokens });
      });

      // 5. Para cada órfã, descobre nome/doc no CSV + roda matching
      const matches = orfas.map(function(orfa) {
        var serie = orfa.serie || '';
        if (!serie && orfa.id && orfa.id.indexOf('KOM-') === 0) {
          // Extrai serie do id KOM-{serie} ou KOM-{serie}-{slug}
          serie = orfa.id.substring(4).split('-')[0];
        }
        var csvRow = csvBySerie[serie];
        var nomeKomtrax = '';
        var docKomtrax  = '';
        if (csvRow) {
          nomeKomtrax = (csvRow['Nome do Cliente'] || '').trim();
          docKomtrax  = cleanCnpj(csvRow['Identificação do Cliente'] || csvRow['Identificacao do Cliente'] || '');
        }

        var clienteMatch = null;
        var matchTipo    = '';

        // (1) Match por documento
        if (docKomtrax) {
          if (docKomtrax.length === 11) {
            clienteMatch = porCpf[docKomtrax] || null;
          } else {
            clienteMatch = porCnpj[docKomtrax] || porCnpj[docKomtrax.replace(/^0+/, '') || docKomtrax] || null;
          }
          if (clienteMatch) matchTipo = 'doc';
        }

        // (2) Match por nome exato normalizado
        if (!clienteMatch && nomeKomtrax) {
          var chave = _chaveNome(nomeKomtrax);
          if (chave && porNomeExato[chave] && porNomeExato[chave].length === 1) {
            clienteMatch = porNomeExato[chave][0];
            matchTipo    = 'nome_exato';
          }
        }

        // (3) Sugestões por similaridade
        var sugestoes = [];
        if (!clienteMatch && nomeKomtrax) {
          var tokensK = _tokensNome(nomeKomtrax);
          var prim    = tokensK[0];
          var pool    = prim ? (porPrimaria[prim] || []) : [];
          var scores  = [];
          pool.forEach(function(ct) {
            var s = _simNomes(tokensK, ct.tokens);
            if (s >= 0.5) scores.push({ cliente: ct.cli, score: s });  // limite mais baixo aqui pq usuário vai revisar
          });
          scores.sort(function(a, b) { return b.score - a.score; });
          sugestoes = scores.slice(0, 5);  // top 5 (mais opções pra revisão)
        }

        return {
          orfa: orfa,
          nomeKomtrax: nomeKomtrax,
          docKomtrax:  docKomtrax,
          clienteMatch:    clienteMatch,
          matchTipo:       matchTipo,
          sugestoes:       sugestoes,
          sugestaoSelecionada: null,  // setado pelo usuário no dropdown
          status:          'pendente', // 'pendente' | 'aplicado' | 'rejeitado'
        };
      });

      setOrfasMatches(matches);
      setOrfasFase('preview');
      onToast(matches.length + ' órfã(s) analisada(s).', 'ok');
    } catch(e) {
      console.warn('[Órfãs] erro:', e);
      onToast('Erro: ' + e.message, 'err');
      setOrfasFase('idle');
    }
  }

  function selecionarSugestaoOrfa(orfaId, cliente) {
    setOrfasMatches(function(prev) {
      return prev.map(function(item) {
        if (item.orfa.id !== orfaId) return item;
        return Object.assign({}, item, { sugestaoSelecionada: cliente });
      });
    });
  }

  async function aplicarOrfaIndividual(orfaId) {
    var item = orfasMatches.find(function(x){ return x.orfa.id === orfaId; });
    if (!item) return;
    var cli = item.clienteMatch || item.sugestaoSelecionada;
    if (!cli) { onToast('Selecione uma sugestão primeiro.', 'warn'); return; }
    try {
      await CRM_API.updateItem('FrotaCliente', item.orfa._spId, { cliente_id: cli.id });
      item.orfa.cliente_id = cli.id;
      // Marca como aplicado e remove da lista
      setOrfasMatches(function(prev){
        return prev.map(function(x) {
          if (x.orfa.id !== orfaId) return x;
          return Object.assign({}, x, { status: 'aplicado', clienteMatch: cli });
        });
      });
      onToast('✓ ' + item.orfa.id + ' → ' + (cli.razao || cli.id), 'ok');
    } catch(e) {
      onToast('Erro: ' + e.message, 'err');
    }
  }

  function rejeitarOrfaIndividual(orfaId) {
    setOrfasMatches(function(prev) {
      return prev.map(function(x) {
        if (x.orfa.id !== orfaId) return x;
        return Object.assign({}, x, { status: 'rejeitado' });
      });
    });
  }

  async function aplicarLoteOrfasAuto() {
    var alvos = orfasMatches.filter(function(x){
      return x.status === 'pendente' && x.clienteMatch &&
             (x.matchTipo === 'doc' || x.matchTipo === 'nome_exato');
    });
    if (!alvos.length) { onToast('Nenhum match automático disponível.', 'warn'); return; }
    if (!await window.CRM_CONFIRM('Aplicar ' + alvos.length + ' match(es) automáticos (CPF/CNPJ + nome exato)?', { danger: false, confirmLabel: 'Aplicar' })) return;
    setOrfasFase('aplicando');
    var ok = 0, err = 0;
    var LOTE = 4;
    for (var i = 0; i < alvos.length; i += LOTE) {
      var lote = alvos.slice(i, i + LOTE);
      await Promise.all(lote.map(async function(item) {
        try {
          await CRM_API.updateItem('FrotaCliente', item.orfa._spId, { cliente_id: item.clienteMatch.id });
          item.orfa.cliente_id = item.clienteMatch.id;
          item.status = 'aplicado';
          ok++;
        } catch(e) { err++; }
      }));
    }
    setOrfasMatches(orfasMatches.slice());  // força re-render
    setOrfasFase('preview');
    onToast(ok + ' aplicado(s)' + (err ? ', ' + err + ' erro(s)' : '') + '.', err ? 'err' : 'ok');
  }

  async function aplicarLoteOrfasSugestoes() {
    var th = orfasThreshold / 100;
    var alvos = orfasMatches.filter(function(x){
      if (x.status !== 'pendente') return false;
      if (x.clienteMatch) return false;
      if (!x.sugestoes || !x.sugestoes.length) return false;
      return x.sugestoes[0].score >= th;
    });
    if (!alvos.length) { onToast('Nenhuma sugestão com score ≥ ' + orfasThreshold + '%.', 'warn'); return; }
    if (!await window.CRM_CONFIRM('Aplicar ' + alvos.length + ' sugestões com score ≥ ' + orfasThreshold + '% (top 1 de cada)?', { danger: false, confirmLabel: 'Aplicar' })) return;
    setOrfasFase('aplicando');
    var ok = 0, err = 0;
    var LOTE = 4;
    for (var i = 0; i < alvos.length; i += LOTE) {
      var lote = alvos.slice(i, i + LOTE);
      await Promise.all(lote.map(async function(item) {
        var cli = item.sugestoes[0].cliente;
        try {
          await CRM_API.updateItem('FrotaCliente', item.orfa._spId, { cliente_id: cli.id });
          item.orfa.cliente_id = cli.id;
          item.status = 'aplicado';
          item.clienteMatch = cli;
          ok++;
        } catch(e) { err++; }
      }));
    }
    setOrfasMatches(orfasMatches.slice());
    setOrfasFase('preview');
    onToast(ok + ' aplicada(s)' + (err ? ', ' + err + ' erro(s)' : '') + '.', err ? 'err' : 'ok');
  }

  // Filtros: 'todos' | action específica | 'orphan' (independente de action)
  const filtrados = preview.filter(function(r) {
    if (filtro === 'todos') return true;
    if (filtro === 'orphan') return r.existente && !r.existente.cliente_id;
    return r.action === filtro;
  });
  const exibidos  = filtrados.slice(0, 200);
  const canImport = fase === 'preview' && stats && (stats.toAdd + stats.toUpdate) > 0;
  // Contagem dinâmica de órfãs (no SP, sem cliente_id) — útil para o filtro
  const nOrfas = preview.filter(function(r){ return r.existente && !r.existente.cliente_id; }).length;

  return (
    <div>
      <CfgBanner kind="info">
        Importa equipamentos Komatsu do relatório Komtrax para a frota dos clientes CRM.
        O CSV é lido direto do SharePoint (Chamados/Komtrax/Komtrax.csv). O cruzamento é feito por:
        (1) <strong>CNPJ/CPF</strong> exato, (2) <strong>nome exato</strong> (após normalização — remove
        acentos, sufixos jurídicos), (3) <strong>similaridade de nome</strong> (≥ 60% de palavras em
        comum, mesma 1ª palavra) — itens nesse 3º nível ficam como <strong>Sugestões</strong> para você
        aprovar individualmente antes do import. <strong>Atualização incremental:</strong> só envia PATCH
        para as máquinas onde algo mudou.
      </CfgBanner>

      {/* Toggles do detector (persistidos em localStorage) */}
      <div style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap', marginBottom: 16,
                    padding: '12px 16px', background: 'var(--surface-2)',
                    border: '1px solid var(--border)', borderRadius: 'var(--r-md)' }}>
        <span style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase',
                       letterSpacing: '.06em', color: 'var(--tx-3)' }}>
          Detector
        </span>
        <CfgToggle checked={preservarVinculo} onChange={setPreservarVinculo}
          label={<>Preservar vínculo de cliente <span style={{ color: 'var(--tx-3)', fontWeight: 400, fontSize: 11 }}>— não altera cliente_id quando o SP já tem um (curadoria manual protegida)</span></>} />
        <CfgToggle checked={modoConservador} onChange={setModoConservador}
          label={<>Modo conservador <span style={{ color: 'var(--tx-3)', fontWeight: 400, fontSize: 11 }}>— atualiza somente SMR; ignora modelo, série e vínculo</span></>} />
      </div>

      <CfgPanel titulo="Importar Frota Komtrax" icon="cpu"
        sub={'Fonte: SharePoint · Chamados/Komtrax/Komtrax.csv'}
        headerRight={
          <button onClick={carregarCsv}
            disabled={fase === 'loading' || fase === 'importing'}
            className="btn btn-primary"
            style={{ opacity: (fase === 'loading' || fase === 'importing') ? 0.6 : 1 }}>
            <i data-lucide="download-cloud" style={{ width: 14, height: 14 }}></i>
            {fase === 'loading' ? 'Carregando...' : 'Carregar CSV'}
          </button>
        }>

        {/* Loading state */}
        {fase === 'loading' && (
          <div style={{ padding: '32px', textAlign: 'center', color: 'var(--tx-3)' }}>
            <i data-lucide="loader" style={{ width: 22, height: 22 }}></i>
            <div style={{ font: '400 13px/1.4 var(--ff-body)', marginTop: 10 }}>
              Carregando Komtrax.csv do SharePoint...
            </div>
          </div>
        )}

        {/* Stats */}
        {stats && (
          <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
            {[
              { label: 'Total',         val: stats.total,            color: 'var(--tx)'    },
              { label: 'Adicionar',     val: stats.toAdd,            color: 'var(--grn)'   },
              { label: 'Atualizar',     val: stats.toUpdate,         color: '#3370b7'      },
              { label: 'Sugestões',     val: stats.toSugestao || 0,  color: '#a855f7'      },
              { label: 'Sem alteração', val: stats.toSame,           color: '#64748b'      },
              { label: 'Ignorar',       val: stats.toSkip,           color: 'var(--tx-3)'  },
            ].map(s => (
              <div key={s.label} style={{
                padding: '10px 18px', background: 'var(--surface-2)',
                border: '1px solid var(--border)', borderRadius: 'var(--r-md)', textAlign: 'center', minWidth: 96
              }}>
                <div style={{ font: '700 24px/1 var(--ff-display)', color: s.color }}>{s.val}</div>
                <div style={{ font: '400 11px/1 var(--ff-body)', color: 'var(--tx-3)', marginTop: 5 }}>{s.label}</div>
              </div>
            ))}
          </div>
        )}

        {/* Toolbar de aprovação em lote (só se houver sugestões pendentes) */}
        {stats && stats.toSugestao > 0 && (
          <div style={{ padding: '12px 16px', background: 'rgba(168,85,247,0.08)',
                        border: '1px solid #d8b4fe', borderRadius: 'var(--r-md)',
                        marginBottom: 12, display: 'flex', alignItems: 'center',
                        gap: 12, flexWrap: 'wrap' }}>
            <i data-lucide="sparkles" style={{ width: 16, height: 16, color: '#a855f7', flexShrink: 0 }}></i>
            <span style={{ font: '500 12px/1.4 var(--ff-body)', color: '#581c87' }}>
              {stats.toSugestao} sugestão{stats.toSugestao > 1 ? 'ões' : ''} pendente{stats.toSugestao > 1 ? 's' : ''} de revisão.
            </span>

            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
              <span style={{ font: '400 11px/1 var(--ff-body)', color: '#581c87' }}>Score mínimo:</span>
              <input type="number" min="50" max="100" step="5" value={aprovarTh}
                onChange={function(e) { setAprovarTh(Math.max(50, Math.min(100, parseInt(e.target.value) || 80))); }}
                style={{ width: 60, padding: '4px 6px', font: '500 12px/1 var(--ff-mono)',
                         border: '1px solid #c4b5fd', borderRadius: 'var(--r-xs)', background: '#fff',
                         textAlign: 'center' }} />
              <span style={{ font: '400 11px/1 var(--ff-body)', color: '#581c87' }}>%</span>
              <button onClick={function(){ aprovarLote(aprovarTh); }}
                style={{ padding: '6px 12px', background: '#a855f7', color: '#fff', border: 'none',
                         borderRadius: 'var(--r-sm)', cursor: 'pointer',
                         font: '600 12px/1 var(--ff-body)', display: 'inline-flex',
                         alignItems: 'center', gap: 6 }}>
                <i data-lucide="check-check" style={{ width: 13, height: 13 }}></i>
                Aprovar ≥ {aprovarTh}%
              </button>
              <button onClick={function(){
                  window.CRM_CONFIRM('Rejeitar TODAS as ' + stats.toSugestao + ' sugestões pendentes?\n\nElas vão pra "Ignorar" e não serão importadas.', { danger: true, confirmLabel: 'Rejeitar todas' })
                    .then(function(ok){ if (ok) rejeitarTodasSugestoes(); });
                }}
                style={{ padding: '6px 12px', background: 'transparent', color: '#581c87',
                         border: '1px solid #c4b5fd', borderRadius: 'var(--r-sm)', cursor: 'pointer',
                         font: '500 12px/1 var(--ff-body)' }}>
                ✗ Rejeitar todas
              </button>
            </div>
          </div>
        )}

        {/* Preview table */}
        {preview.length > 0 && (
          <div>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8 }}>
              <div style={{ font: '500 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>
                {filtrados.length !== preview.length
                  ? filtrados.length + ' de ' + preview.length + ' equipamentos'
                  : preview.length + ' equipamentos'}
                {filtrados.length > 200 && <span> · mostrando 200 primeiros</span>}
              </div>
              <div style={{ display: 'flex', gap: 4 }}>
                {[
                  { key: 'todos',    label: 'Todos' },
                  { key: 'add',      label: 'Adicionar' },
                  { key: 'update',   label: 'Atualizar' },
                  { key: 'sugestao', label: 'Sugestões' },
                  { key: 'orphan',   label: 'Órfãs' + (nOrfas ? ' (' + nOrfas + ')' : '') },
                  { key: 'same',     label: 'Sem alteração' },
                  { key: 'skip',     label: 'Ignorar' },
                ].map(f => (
                  <button key={f.key} onClick={() => setFiltro(f.key)}
                    style={{
                      padding: '4px 10px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                      cursor: 'pointer', font: '500 11px/1 var(--ff-body)',
                      background: filtro === f.key ? 'var(--grn)' : 'var(--surface-2)',
                      color:      filtro === f.key ? '#fff' : 'var(--tx-3)'
                    }}>
                    {f.label}
                  </button>
                ))}
              </div>
            </div>

            <div style={{ overflow: 'auto', maxHeight: 380, border: '1px solid var(--border)', borderRadius: 'var(--r-sm)' }}>
              <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                <thead>
                  <tr style={{ background: 'var(--surface-2)' }}>
                    {['', 'Série', 'Tipo · Modelo', 'SMR (h)', 'Cliente Komtrax', 'Cliente CRM', 'Ação'].map(h => (
                      <th key={h} style={{
                        padding: '8px 10px', textAlign: 'left',
                        font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase',
                        letterSpacing: '.06em', color: 'var(--tx-3)',
                        borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap',
                        position: 'sticky', top: 0, background: 'var(--surface-2)'
                      }}>{h}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {exibidos.map((row, i) => (
                    <tr key={i} style={{
                      borderBottom: '1px solid var(--border)',
                      opacity:    (row.action === 'skip' || row.action === 'same') ? 0.45 : 1,
                      background: row.action === 'sugestao' ? 'rgba(168,85,247,0.06)' : 'transparent',
                    }}>
                      <td style={{ padding: '7px 10px', fontSize: 14, lineHeight: 1 }}>{statusIcon(row.action)}</td>
                      <td style={{ padding: '7px 10px', font: '500 11px/1 var(--ff-mono)', color: 'var(--tx-2)', whiteSpace: 'nowrap' }}>{row.serie}</td>
                      <td style={{ padding: '7px 10px', color: 'var(--tx-2)', maxWidth: 200, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {[row.tipo, row.modelo].filter(Boolean).join(' · ') || '—'}
                      </td>
                      <td style={{ padding: '7px 10px', color: 'var(--tx-2)', whiteSpace: 'nowrap' }}>
                        {row.smr > 0 ? row.smr.toLocaleString('pt-BR') : '—'}
                      </td>
                      <td style={{ padding: '7px 10px', color: 'var(--tx-3)', maxWidth: 180, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {row.nomeKomtrax || '—'}
                      </td>
                      <td style={{ padding: '7px 10px', maxWidth: 220, whiteSpace: 'normal',
                                   color: row.clienteMatch ? 'var(--grn)' : (row.existente ? '#3370b7' : 'var(--tx-3)') }}>
                        {row.action === 'sugestao' && row.sugestoes && row.sugestoes.length > 0 ? (
                          <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                            <select className="inp"
                              style={{ fontSize: 11, padding: '3px 6px', maxWidth: 200 }}
                              defaultValue=""
                              onChange={function(e) {
                                if (!e.target.value) return;
                                var cli = row.sugestoes.find(function(s) { return s.cliente.id === e.target.value; });
                                if (cli) aprovarSugestao(row.komId, cli.cliente);
                              }}>
                              <option value="">Selecionar candidato…</option>
                              {row.sugestoes.map(function(s) {
                                return (
                                  <option key={s.cliente.id} value={s.cliente.id}>
                                    {s.cliente.razao} ({Math.round(s.score * 100)}%)
                                  </option>
                                );
                              })}
                            </select>
                            <button onClick={function(){ rejeitarSugestao(row.komId); }}
                              style={{ background: 'transparent', border: 'none', color: 'var(--tx-3)',
                                       font: '400 10px/1 var(--ff-body)', cursor: 'pointer', textAlign: 'left', padding: 0 }}>
                              ✗ Nenhum desses (ignorar)
                            </button>
                          </div>
                        ) : (
                          <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', display: 'block' }}>
                            {row.clienteMatch
                              ? row.clienteMatch.razao
                              : row.existente
                                ? 'já na frota'
                                : '— não encontrado'}
                            {row.matchTipo === 'nome_exato' && (
                              <span style={{ marginLeft: 4, font: '600 9px/1 var(--ff-body)', color: '#a855f7', background: '#f3e8ff', padding: '2px 5px', borderRadius: 999 }}>
                                ≈ nome
                              </span>
                            )}
                            {row.matchTipo === 'nome_sugestao' && (
                              <span style={{ marginLeft: 4, font: '600 9px/1 var(--ff-body)', color: '#a855f7', background: '#f3e8ff', padding: '2px 5px', borderRadius: 999 }}>
                                ✓ aprovado
                              </span>
                            )}
                          </span>
                        )}
                      </td>
                      <td style={{ padding: '7px 10px', font: '600 11px/1 var(--ff-body)', color: statusColor(row.action), whiteSpace: 'nowrap' }}>
                        {statusLabel(row.action)}
                        {row.action === 'update' && row.changes && row.changes.length > 0 && (
                          <span style={{ font: '400 10px/1.4 var(--ff-body)', color: 'var(--tx-3)', display: 'block', marginTop: 2 }}>
                            {row.changes.join(' · ')}
                          </span>
                        )}
                        {row.action === 'sugestao' && (
                          <span style={{ font: '400 10px/1.4 var(--ff-body)', color: 'var(--tx-3)', display: 'block', marginTop: 2 }}>
                            {row.sugestoes.length} candidato{row.sugestoes.length > 1 ? 's' : ''}
                          </span>
                        )}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>

            {/* Botão de importar */}
            {canImport && (
              <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 14, gap: 8, alignItems: 'center' }}>
                <span style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>
                  {stats.toAdd} para adicionar · {stats.toUpdate} para atualizar
                  {stats.toSame > 0 && <span style={{ marginLeft: 6, color: '#64748b' }}>· {stats.toSame} sem alteração</span>}
                </span>
                <button onClick={importar} className="btn btn-primary">
                  <i data-lucide="upload-cloud" style={{ width: 14, height: 14 }}></i>
                  Importar {stats.toAdd + stats.toUpdate} equipamentos
                </button>
              </div>
            )}

            {fase === 'importing' && (
              <div style={{ padding: '14px', textAlign: 'center', color: 'var(--tx-3)', font: '400 12px/1.4 var(--ff-body)', marginTop: 10 }}>
                <i data-lucide="loader" style={{ width: 16, height: 16 }}></i>
                Importando... aguarde.
              </div>
            )}
          </div>
        )}

        {/* Log de resultado */}
        {log.length > 0 && (
          <div style={{ marginTop: 16 }}>
            <div style={{ font: '600 11px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--tx-3)', marginBottom: 6 }}>
              Log de importação ({log.length} operações)
            </div>
            <div style={{
              maxHeight: 220, overflow: 'auto', background: 'var(--surface-2)',
              border: '1px solid var(--border)', borderRadius: 'var(--r-sm)', padding: '10px 12px'
            }}>
              {log.map((l, i) => (
                <div key={i} style={{ font: '400 11px/1.7 var(--ff-mono)', color: 'var(--tx-2)' }}>{l}</div>
              ))}
            </div>
          </div>
        )}

        {/* Estado vazio / instrução */}
        {fase === 'idle' && (
          <div style={{ padding: '24px 0', color: 'var(--tx-3)', font: '400 13px/1.6 var(--ff-body)' }}>
            Clique em <strong>Carregar CSV</strong> para ler o relatório Komtrax mais recente do SharePoint,
            ver o resumo de equipamentos por cliente e confirmar a importação para a frota CRM.
          </div>
        )}
      </CfgPanel>

      {/* ═══════════════════════════════════════════════════════════════ */}
      {/* SEÇÃO — Conciliação de órfãs                                     */}
      {/* ═══════════════════════════════════════════════════════════════ */}
      {(function() {
        var orfasTotal = (CRM_DATA.frotaCliente || []).filter(function(m){
          return m && m.id && m.id.toLowerCase().indexOf('kom-') === 0 && !m.cliente_id;
        }).length;
        if (orfasTotal === 0 && orfasFase === 'idle') return null;

        // Stats pós-busca
        var nMatchAuto = orfasMatches.filter(function(x){ return x.status === 'pendente' && x.clienteMatch; }).length;
        var nSug       = orfasMatches.filter(function(x){ return x.status === 'pendente' && !x.clienteMatch && x.sugestoes && x.sugestoes.length; }).length;
        var nSem       = orfasMatches.filter(function(x){ return x.status === 'pendente' && !x.clienteMatch && (!x.sugestoes || !x.sugestoes.length); }).length;
        var nApl       = orfasMatches.filter(function(x){ return x.status === 'aplicado'; }).length;

        var qBusca = (orfasBusca || '').trim().toLowerCase();
        var matchesView = orfasMatches.filter(function(x) {
          if (x.status === 'aplicado' || x.status === 'rejeitado') return false;
          // Aba
          var passaAba = true;
          if (orfasFiltro === 'matchAuto') passaAba = !!x.clienteMatch;
          else if (orfasFiltro === 'sugestao')  passaAba = !x.clienteMatch && x.sugestoes && x.sugestoes.length > 0;
          else if (orfasFiltro === 'semMatch')  passaAba = !x.clienteMatch && (!x.sugestoes || !x.sugestoes.length);
          if (!passaAba) return false;
          // Busca livre (Cliente Komtrax + Sugestão CRM + máquina)
          if (qBusca) {
            var alvo = [
              x.orfa && x.orfa.id || '',
              x.orfa && x.orfa.modelo || '',
              x.nomeKomtrax || '',
              x.docKomtrax || '',
              x.clienteMatch && x.clienteMatch.razao || '',
              x.sugestaoSelecionada && x.sugestaoSelecionada.razao || '',
              (x.sugestoes || []).map(function(s){ return s.cliente.razao; }).join(' '),
            ].join(' ').toLowerCase();
            if (alvo.indexOf(qBusca) < 0) return false;
          }
          return true;
        });
        var exibidosOrfas = matchesView.slice(0, orfasLimit);

        return (
          <CfgPanel titulo="Conciliação de máquinas órfãs"
            icon="link"
            sub="Vincular máquinas KOM no SP sem cliente_id usando o CSV Komtrax como ponte"
            headerRight={
              <button onClick={buscarSugestoesOrfas}
                disabled={orfasFase === 'loading' || orfasFase === 'aplicando'}
                className="btn btn-primary"
                style={{ opacity: (orfasFase === 'loading' || orfasFase === 'aplicando') ? 0.6 : 1 }}>
                <i data-lucide={orfasFase === 'loading' ? 'loader' : 'search'} style={{ width: 14, height: 14 }}></i>
                {orfasFase === 'loading' ? 'Buscando...' : 'Buscar sugestões'}
              </button>
            }>

            {/* Estado vazio */}
            {orfasFase === 'idle' && orfasMatches.length === 0 && (
              <div style={{ padding: '16px 0', font: '400 13px/1.6 var(--ff-body)', color: 'var(--tx-3)' }}>
                Existem <strong>{orfasTotal.toLocaleString('pt-BR')}</strong> máquina(s) KOM no SP sem cliente vinculado.
                Clique em <strong>Buscar sugestões</strong> — o sistema vai cruzar com o CSV Komtrax atual e propor matches
                por documento (CNPJ/CPF), nome exato e similaridade de razão social.
              </div>
            )}

            {orfasFase === 'loading' && (
              <div style={{ padding: '24px 0', textAlign: 'center', color: 'var(--tx-3)',
                            font: '400 12px/1.4 var(--ff-body)' }}>
                <i data-lucide="loader" style={{ width: 18, height: 18 }}></i>
                <div style={{ marginTop: 6 }}>Cruzando órfãs com CSV Komtrax e base CRM…</div>
              </div>
            )}

            {(orfasFase === 'preview' || orfasFase === 'aplicando') && orfasMatches.length > 0 && (
              <div>
                {/* Stats */}
                <div style={{ display: 'flex', gap: 12, flexWrap: 'wrap', marginBottom: 16 }}>
                  {[
                    { label: 'Total analisadas', val: orfasMatches.length, color: 'var(--tx)' },
                    { label: 'Match automático', val: nMatchAuto,           color: 'var(--grn)' },
                    { label: 'Com sugestão',     val: nSug,                  color: '#a855f7' },
                    { label: 'Sem match',        val: nSem,                  color: 'var(--tx-3)' },
                    { label: 'Aplicadas',        val: nApl,                  color: '#3370b7' },
                  ].map(function(s) {
                    return (
                      <div key={s.label} style={{
                        padding: '10px 18px', background: 'var(--surface-2)',
                        border: '1px solid var(--border)', borderRadius: 'var(--r-md)',
                        textAlign: 'center', minWidth: 96,
                      }}>
                        <div style={{ font: '700 24px/1 var(--ff-display)', color: s.color }}>{s.val}</div>
                        <div style={{ font: '400 11px/1 var(--ff-body)', color: 'var(--tx-3)', marginTop: 5 }}>{s.label}</div>
                      </div>
                    );
                  })}
                </div>

                {/* Ações em lote */}
                <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 14, flexWrap: 'wrap',
                              padding: '12px 14px', background: 'var(--info-050, #e4eef8)',
                              border: '1px solid #c1d6ec', borderRadius: 'var(--r-md)' }}>
                  <span style={{ font: '500 12px/1.4 var(--ff-body)', color: '#1e3a8a' }}>
                    Ações em lote:
                  </span>
                  <button onClick={aplicarLoteOrfasAuto}
                    disabled={nMatchAuto === 0 || orfasFase === 'aplicando'}
                    className="btn btn-secondary"
                    style={{ fontSize: 12, padding: '5px 12px',
                             opacity: (nMatchAuto === 0 || orfasFase === 'aplicando') ? 0.5 : 1 }}>
                    ✓ Aplicar {nMatchAuto} match automático(s)
                  </button>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginLeft: 'auto' }}>
                    <span style={{ font: '400 11px/1 var(--ff-body)', color: '#1e3a8a' }}>Score mínimo:</span>
                    <input type="number" min="50" max="100" step="5" value={orfasThreshold}
                      onChange={function(e) { setOrfasThreshold(Math.max(50, Math.min(100, parseInt(e.target.value) || 70))); }}
                      style={{ width: 56, padding: '4px 6px', font: '500 12px/1 var(--ff-mono)',
                               border: '1px solid #c4b5fd', borderRadius: 'var(--r-xs)', background: '#fff',
                               textAlign: 'center' }} />
                    <span style={{ font: '400 11px/1 var(--ff-body)', color: '#1e3a8a' }}>%</span>
                    <button onClick={aplicarLoteOrfasSugestoes}
                      disabled={orfasFase === 'aplicando'}
                      className="btn btn-secondary"
                      style={{ fontSize: 12, padding: '5px 12px',
                               opacity: orfasFase === 'aplicando' ? 0.5 : 1 }}>
                      ✓ Aplicar sugestões ≥ {orfasThreshold}%
                    </button>
                  </div>
                </div>

                {/* Filtros */}
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 8, gap: 12, flexWrap: 'wrap' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10, flex: '1 1 260px' }}>
                    <div style={{ font: '500 12px/1 var(--ff-body)', color: 'var(--tx-3)', whiteSpace: 'nowrap' }}>
                      {matchesView.length !== orfasMatches.length
                        ? matchesView.length + ' de ' + orfasMatches.length
                        : matchesView.length + ' pendentes'}
                      {exibidosOrfas.length < matchesView.length && <span> · exibindo {exibidosOrfas.length}</span>}
                    </div>
                    <input
                      type="text"
                      value={orfasBusca}
                      onChange={function(e){ setOrfasBusca(e.target.value); setOrfasLimit(200); }}
                      placeholder="🔍 Filtrar Cliente Komtrax / Sugestão CRM…"
                      style={{
                        flex: 1, minWidth: 200, maxWidth: 340,
                        padding: '5px 10px', fontSize: 12,
                        border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                        background: 'var(--surface-1)', color: 'var(--tx)',
                      }}
                    />
                    {orfasBusca && (
                      <button onClick={function(){ setOrfasBusca(''); setOrfasLimit(200); }}
                        title="Limpar busca"
                        style={{ padding: '3px 8px', fontSize: 11, border: '1px solid var(--border)',
                                 borderRadius: 'var(--r-xs)', background: 'var(--surface-2)',
                                 color: 'var(--tx-3)', cursor: 'pointer' }}>✕</button>
                    )}
                  </div>
                  <div style={{ display: 'flex', gap: 4 }}>
                    {[
                      { key: 'todos',     label: 'Todas' },
                      { key: 'matchAuto', label: 'Match auto (' + nMatchAuto + ')' },
                      { key: 'sugestao',  label: 'Com sugestão (' + nSug + ')' },
                      { key: 'semMatch',  label: 'Sem match (' + nSem + ')' },
                    ].map(function(f) {
                      return (
                        <button key={f.key} onClick={function(){ setOrfasFiltro(f.key); setOrfasLimit(200); }}
                          style={{
                            padding: '4px 10px', border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                            cursor: 'pointer', font: '500 11px/1 var(--ff-body)',
                            background: orfasFiltro === f.key ? 'var(--grn)' : 'var(--surface-2)',
                            color:      orfasFiltro === f.key ? '#fff' : 'var(--tx-3)'
                          }}>
                          {f.label}
                        </button>
                      );
                    })}
                  </div>
                </div>

                {/* Tabela de órfãs */}
                <div style={{ overflow: 'auto', maxHeight: 460, border: '1px solid var(--border)', borderRadius: 'var(--r-sm)' }}>
                  <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
                    <thead>
                      <tr style={{ background: 'var(--surface-2)' }}>
                        {['Máquina', 'Cliente Komtrax', 'Doc', 'Sugestão CRM', 'Ação'].map(function(h) {
                          return (
                            <th key={h} style={{
                              padding: '8px 10px', textAlign: 'left',
                              font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase',
                              letterSpacing: '.06em', color: 'var(--tx-3)',
                              borderBottom: '1px solid var(--border)', whiteSpace: 'nowrap',
                              position: 'sticky', top: 0, background: 'var(--surface-2)',
                            }}>{h}</th>
                          );
                        })}
                      </tr>
                    </thead>
                    <tbody>
                      {exibidosOrfas.map(function(item, i) {
                        var orfa = item.orfa;
                        var cliFinal = item.clienteMatch || item.sugestaoSelecionada;
                        return (
                          <tr key={orfa.id} style={{ borderBottom: '1px solid var(--border)' }}>
                            <td style={{ padding: '7px 10px', maxWidth: 200 }}>
                              <div style={{ font: '500 11px/1.2 var(--ff-mono)', color: 'var(--tx-2)' }}>{orfa.id}</div>
                              <div style={{ font: '400 11px/1 var(--ff-body)', color: 'var(--tx-3)', marginTop: 2,
                                            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                                {orfa.modelo || '—'}
                              </div>
                            </td>
                            <td style={{ padding: '7px 10px', color: 'var(--tx-2)', maxWidth: 200,
                                         overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                              {item.nomeKomtrax || <span style={{ color: 'var(--tx-3)' }}>—</span>}
                            </td>
                            <td style={{ padding: '7px 10px', font: '400 11px/1 var(--ff-mono)', color: 'var(--tx-3)', whiteSpace: 'nowrap' }}>
                              {item.docKomtrax || '—'}
                            </td>
                            <td style={{ padding: '7px 10px', maxWidth: 240 }}>
                              {item.clienteMatch ? (
                                <div>
                                  <span style={{ font: '500 12px/1.2 var(--ff-body)', color: 'var(--grn)' }}>
                                    ✓ {item.clienteMatch.razao}
                                  </span>
                                  <div style={{ font: '600 9px/1 var(--ff-body)', color: 'var(--grn)',
                                                background: 'var(--grn-050, #ecfdf5)', display: 'inline-block',
                                                padding: '2px 6px', borderRadius: 999, marginTop: 4, marginLeft: 4 }}>
                                    {item.matchTipo === 'doc' ? 'CPF/CNPJ' : 'nome exato'}
                                  </div>
                                </div>
                              ) : (
                                <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
                                  {item.sugestoes && item.sugestoes.length > 0 ? (
                                    <select className="inp"
                                      style={{ fontSize: 11, padding: '3px 6px', maxWidth: 220 }}
                                      value={item.sugestaoSelecionada && item.sugestoes.find(function(s){ return s.cliente.id === item.sugestaoSelecionada.id; }) ? item.sugestaoSelecionada.id : ''}
                                      onChange={function(e) {
                                        if (!e.target.value) return;
                                        var s = item.sugestoes.find(function(x){ return x.cliente.id === e.target.value; });
                                        if (s) selecionarSugestaoOrfa(orfa.id, s.cliente);
                                      }}>
                                      <option value="">Escolher candidato…</option>
                                      {item.sugestoes.map(function(s) {
                                        return (
                                          <option key={s.cliente.id} value={s.cliente.id}>
                                            {s.cliente.razao} ({Math.round(s.score * 100)}%)
                                          </option>
                                        );
                                      })}
                                    </select>
                                  ) : (
                                    <span style={{ font: '400 11px/1 var(--ff-body)', color: 'var(--tx-3)', fontStyle: 'italic' }}>
                                      Sem sugestão
                                    </span>
                                  )}
                                  {/* Busca manual na base de clientes */}
                                  {(function(){
                                    var selManual = item.sugestaoSelecionada && (!item.sugestoes || !item.sugestoes.find(function(s){ return s.cliente.id === item.sugestaoSelecionada.id; }));
                                    return (
                                      <div>
                                        <input
                                          list="orfas-cli-datalist"
                                          placeholder="🔍 buscar outro cliente…"
                                          defaultValue={selManual ? item.sugestaoSelecionada.razao : ''}
                                          onChange={function(e){
                                            var v = e.target.value;
                                            if (!v) return;
                                            var sep = v.lastIndexOf(' — ');
                                            var idTry = sep > -1 ? v.slice(sep + 3).trim() : null;
                                            var raz = sep > -1 ? v.slice(0, sep).trim() : v.trim();
                                            var lista = (window.CRM_DATA && CRM_DATA.clientes) || [];
                                            var c = null;
                                            if (idTry) c = lista.find(function(x){ return x.id === idTry; });
                                            if (!c) c = lista.find(function(x){ return (x.razao||'').toLowerCase() === raz.toLowerCase(); });
                                            if (c) selecionarSugestaoOrfa(orfa.id, c);
                                          }}
                                          style={{
                                            width: '100%', maxWidth: 220, fontSize: 11,
                                            padding: '3px 6px', border: '1px solid var(--border)',
                                            borderRadius: 'var(--r-xs)', background: 'var(--surface-1)', color: 'var(--tx)',
                                          }}
                                        />
                                        {selManual && (
                                          <div style={{ font: '500 10px/1.2 var(--ff-body)', color: 'var(--grn)', marginTop: 3 }}>
                                            ✓ {item.sugestaoSelecionada.razao} <span style={{color:'var(--tx-3)'}}>(busca manual)</span>
                                          </div>
                                        )}
                                      </div>
                                    );
                                  })()}
                                </div>
                              )}
                            </td>
                            <td style={{ padding: '7px 10px', whiteSpace: 'nowrap' }}>
                              <button onClick={function(){ aplicarOrfaIndividual(orfa.id); }}
                                disabled={!cliFinal || orfasFase === 'aplicando'}
                                title={cliFinal ? 'Vincular esta órfã ao cliente' : 'Selecione um cliente primeiro'}
                                style={{
                                  padding: '4px 8px', fontSize: 11, marginRight: 4,
                                  background: cliFinal ? 'var(--grn)' : 'var(--surface-2)',
                                  color: cliFinal ? '#fff' : 'var(--tx-3)',
                                  border: 'none', borderRadius: 'var(--r-xs)',
                                  cursor: cliFinal ? 'pointer' : 'not-allowed',
                                  opacity: orfasFase === 'aplicando' ? 0.5 : 1,
                                }}>
                                ✓
                              </button>
                              <button onClick={function(){ rejeitarOrfaIndividual(orfa.id); }}
                                title="Ocultar (rejeitar sugestão)"
                                style={{ padding: '4px 8px', fontSize: 11,
                                         background: 'transparent', color: 'var(--tx-3)',
                                         border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                                         cursor: 'pointer' }}>
                                ✕
                              </button>
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>

                {/* Datalist global com toda a base de clientes (busca manual por linha) */}
                <datalist id="orfas-cli-datalist">
                  {((window.CRM_DATA && CRM_DATA.clientes) || []).map(function(c){
                    return <option key={c.id} value={(c.razao || '') + ' — ' + c.id} />;
                  })}
                </datalist>

                {/* Carregar mais */}
                {matchesView.length > exibidosOrfas.length && (
                  <div style={{ textAlign: 'center', padding: '10px 0 4px' }}>
                    <button onClick={function(){ setOrfasLimit(orfasLimit + 200); }}
                      style={{
                        padding: '6px 16px', fontSize: 12, font: '500 12px/1 var(--ff-body)',
                        border: '1px solid var(--border)', borderRadius: 'var(--r-xs)',
                        background: 'var(--surface-2)', color: 'var(--tx-2)', cursor: 'pointer',
                      }}>
                      ↓ Carregar mais 200 (restam {matchesView.length - exibidosOrfas.length})
                    </button>
                  </div>
                )}

                {nApl > 0 && (
                  <div style={{ marginTop: 12, padding: '10px 14px',
                                background: 'var(--grn-050, #ecfdf5)', border: '1px solid var(--grn-100, #d1fae5)',
                                borderRadius: 'var(--r-sm)', font: '400 12px/1.4 var(--ff-body)', color: '#14532d' }}>
                    ✓ {nApl} órfã(s) vinculada(s) nesta sessão. As atualizações já estão no SP. Recarregue (F5) se for ver no Dashboard de Frota.
                  </div>
                )}
              </div>
            )}
          </CfgPanel>
        );
      })()}
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// AbaFrotaConcorrente — importação via Vendas Perdidas
// ═══════════════════════════════════════════════════════

// Ícones SVG inline para AbaFrotaConcorrente — evita conflito Lucide+React (removeChild crash).
// Lucide.createIcons() substitui <i data-lucide> por <svg> fora do controle do React;
// quando fase muda e React tenta unmount, não encontra mais o <i> → NotFoundError.
function _frotaIcon(name, size) {
  size = size || 16;
  var sw = '1.75';
  var paths = {
    'refresh-cw': '<polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/>',
    'download':   '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>',
  };
  var html = '<svg xmlns="http://www.w3.org/2000/svg" width="'+size+'" height="'+size+'" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="'+sw+'" stroke-linecap="round" stroke-linejoin="round">'+paths[name]+'</svg>';
  return { __html: html };
}

function AbaFrotaConcorrente({ onToast }) {
  const [fase, setFase]      = useCfgState('idle');  // idle | loading | preview | importing
  const [rows, setRows]      = useCfgState([]);
  const [progresso, setProg] = useCfgState({ atual: 0, total: 0 });
  const [falhas, setFalhas]  = useCfgState([]);   // linhas que falharam na última importação
  const [ignoradosKom, setIgnoradosKom] = useCfgState(0); // linhas Komatsu puladas (frota vem do Komtrax)
  const [threshold, _setThr] = useCfgState(function() {
    var v = parseInt(localStorage.getItem('crm_frota_threshold') || '70', 10);
    return isNaN(v) ? 70 : v;
  });

  function setThreshold(v) {
    _setThr(v);
    localStorage.setItem('crm_frota_threshold', String(v));
  }

  function carregar() {
    setFase('loading');
    setRows([]);
    setFalhas([]);
    setIgnoradosKom(0);
    var thrFrac = threshold / 100;

    // Atualiza a frota do SP antes de cruzar — reflete exclusões/inclusões feitas direto no SharePoint
    window.CRM_API.loadFrotaCliente().then(function(frotaFresca) {
      if (Array.isArray(frotaFresca) && frotaFresca.length) window.CRM_DATA.frotaCliente = frotaFresca;
    }).catch(function() {}).then(function() {
      return window.CRM_API.loadVendasPerdidasRows();
    }).then(function(vpRows) {
      var frota    = window.CRM_DATA.frotaCliente || [];
      var clientes = window.CRM_DATA.clientes || [];

      // Rowkeys já importados
      var jaImportados = new Set();
      frota.forEach(function(m) { if (m.vp_rowkey) jaImportados.add(m.vp_rowkey); });

      // Índice de clientes por nome
      var porNomeExato = {}, porPrimaria = {};
      clientes.forEach(function(c) {
        if (!c.razao) return;
        var tokens = _cfgTokensNome(c.razao);
        if (!tokens.length) return;
        var chave = tokens.join(' ');
        if (!porNomeExato[chave]) porNomeExato[chave] = [];
        porNomeExato[chave].push(c);
        var prim = tokens[0];
        if (!porPrimaria[prim]) porPrimaria[prim] = [];
        porPrimaria[prim].push({ cli: c, tokens: tokens });
      });

      // Processar linhas VP
      var processed = [];
      var komatsuPulados = 0;
      vpRows.forEach(function(r) {
        if (!r.fabricante || !r.modelo || !r.cliente) return;
        // Komatsu de construção não entra: a frota Komatsu já vem do Komtrax (evita duplicar).
        // Exceção: "Komatsu Forest" (florestal) NÃO vem do Komtrax -> mantém como concorrente.
        var _fab = String(r.fabricante).toLowerCase();
        if (_fab.indexOf('komatsu') !== -1 && _fab.indexOf('forest') === -1) { komatsuPulados++; return; }
        var rowKey = _cfgVpRowKey(r);

        if (jaImportados.has(rowKey)) {
          processed.push({ key: rowKey, vpRow: r, acao: 'ja_importado', acaoOrig: 'ja_importado', cliente: null, score: 0, sugestoes: [], clienteSel: null });
          return;
        }

        // Match exato
        var tokensVP   = _cfgTokensNome(r.cliente);
        var chaveVP    = tokensVP.join(' ');
        var matchExato = chaveVP && porNomeExato[chaveVP] ? porNomeExato[chaveVP][0] : null;
        if (matchExato) {
          processed.push({ key: rowKey, vpRow: r, acao: 'importar', acaoOrig: 'importar', cliente: matchExato, score: 1, sugestoes: [], clienteSel: matchExato });
          return;
        }

        // Similaridade Dice
        var prim   = tokensVP[0];
        var pool   = prim ? (porPrimaria[prim] || []) : [];
        var scores = [];
        pool.forEach(function(ct) {
          var s = _cfgSimNomes(tokensVP, ct.tokens);
          if (s >= FROTA_SUGESTAO_MIN) scores.push({ cli: ct.cli, score: s });
        });
        scores.sort(function(a, b) { return b.score - a.score; });

        if (scores.length && scores[0].score >= thrFrac) {
          processed.push({ key: rowKey, vpRow: r, acao: 'importar', acaoOrig: 'importar', cliente: scores[0].cli, score: scores[0].score, sugestoes: scores.slice(0, 3), clienteSel: scores[0].cli });
        } else if (scores.length) {
          processed.push({ key: rowKey, vpRow: r, acao: 'sugestao', acaoOrig: 'sugestao', cliente: null, score: scores[0].score, sugestoes: scores.slice(0, 3), clienteSel: scores[0].cli });
        } else {
          processed.push({ key: rowKey, vpRow: r, acao: 'sem_match', acaoOrig: 'sem_match', cliente: null, score: 0, sugestoes: [], clienteSel: null });
        }
      });

      setRows(processed);
      setIgnoradosKom(komatsuPulados);
      setFase('preview');
    }).catch(function(err) {
      console.error('[FrotaConcorrente] erro ao carregar', err);
      onToast('Erro ao carregar Vendas Perdidas: ' + (err.message || String(err)), 'error');
      setFase('idle');
    });
  }

  function alternarAcao(idx) {
    setRows(function(prev) {
      return prev.map(function(r, i) {
        if (i !== idx) return r;
        if (r.acao === 'ignorar') {
          // Restaurar para ação original
          return Object.assign({}, r, { acao: r.acaoOrig, cliente: r.acaoOrig === 'importar' ? r.clienteSel : null });
        }
        if (r.acao === 'importar' || r.acao === 'sugestao') {
          return Object.assign({}, r, { acao: 'ignorar' });
        }
        return r;
      });
    });
  }

  function aceitarSugestao(idx, cliSel) {
    setRows(function(prev) {
      return prev.map(function(r, i) {
        if (i !== idx) return r;
        var cli = cliSel || (r.sugestoes[0] && r.sugestoes[0].cli);
        return Object.assign({}, r, { acao: 'importar', cliente: cli, clienteSel: cli });
      });
    });
  }

  function aceitarTodasSugestoes() {
    var elegiveis = rows.filter(function(r) {
      return r.acao === 'sugestao' && (r.clienteSel || (r.sugestoes[0] && r.sugestoes[0].cli));
    });
    if (!elegiveis.length) { onToast('Nenhuma sugestão com candidato para aceitar.', 'warn'); return; }
    if (!window.confirm('Aceitar o melhor candidato para ' + elegiveis.length + ' sugestão(ões)?\n\nVocê ainda pode revisar e clicar em "Ignorar" nas linhas erradas antes de importar.')) return;
    setRows(function(prev) {
      return prev.map(function(r) {
        if (r.acao !== 'sugestao') return r;
        var cli = r.clienteSel || (r.sugestoes[0] && r.sugestoes[0].cli);
        return cli ? Object.assign({}, r, { acao: 'importar', cliente: cli, clienteSel: cli }) : r;
      });
    });
  }

  function importarTodos() {
    var paraImportar = rows.filter(function(r) { return r.acao === 'importar' && r.cliente; });
    if (!paraImportar.length) { onToast('Nada selecionado para importar.', 'warn'); return; }
    setFase('importing');
    setFalhas([]);
    setProg({ atual: 0, total: paraImportar.length });
    var idx = 0;
    var okKeys = [];     // só as que o SP confirmou
    var falhasLocais = []; // { key, fabricante, modelo, cliente, msg }

    function next() {
      if (idx >= paraImportar.length) {
        setFase('preview');
        if (falhasLocais.length) {
          onToast(okKeys.length + ' importadas; ' + falhasLocais.length + ' falharam (detalhes na tela).', 'warn');
        } else {
          onToast(okKeys.length + ' máquinas importadas como frota concorrente.', 'ok');
        }
        setFalhas(falhasLocais);
        window.CRM_API.loadFrotaCliente().then(function(data) {
          if (Array.isArray(data) && data.length) window.CRM_DATA.frotaCliente = data;
        }).catch(function(){});
        // Marca como 'ja_importado' SOMENTE as que realmente entraram; as que falharam
        // permanecem como 'importar' para nova tentativa.
        var okSet = new Set(okKeys);
        setRows(function(prev) {
          return prev.map(function(r) {
            return okSet.has(r.key) ? Object.assign({}, r, { acao: 'ja_importado' }) : r;
          });
        });
        return;
      }
      var r = paraImportar[idx++];
      setProg({ atual: idx, total: paraImportar.length });
      var payload = {
        cliente_id: r.cliente.id,
        fabricante: r.vpRow.fabricante,
        modelo:     r.vpRow.modelo,
        ano:        r.vpRow.ano ? String(r.vpRow.ano) : '',
        horimetro:  null,
        serie:      '',
        tipo:       'concorrente',
        vp_rowkey:  r.key,
      };
      window.CRM_API.addItem('FrotaCliente', payload).then(function() {
        okKeys.push(r.key);
        next();
      }).catch(function(err) {
        console.error('[FrotaConcorrente] falha', r.key, err);
        falhasLocais.push({
          key:        r.key,
          fabricante: r.vpRow.fabricante,
          modelo:     r.vpRow.modelo,
          cliente:    (r.cliente && r.cliente.razao) || '',
          msg:        (err && err.message) ? err.message : String(err),
        });
        next();
      });
    }
    next();
  }

  // Stats
  var stats = { importar: 0, sugestao: 0, sem_match: 0, ja_importado: 0, ignorar: 0 };
  rows.forEach(function(r) { if (Object.prototype.hasOwnProperty.call(stats, r.acao)) stats[r.acao]++; });

  // ── idle ──────────────────────────────────────────────
  if (fase === 'idle') {
    return (
      <div>
        <CfgBanner kind="info">
          Importa máquinas da planilha <strong>Vendas Perdidas</strong> como frota <code>concorrente</code> dos
          clientes no CRM. O match é feito pelo nome do cliente (normalização + coeficiente Dice).
          Registros já importados (por <code>vp_rowkey</code>) são ignorados automaticamente.
          Máquinas <strong>Komatsu</strong> (construção) também são ignoradas — a frota Komatsu vem do <strong>Komtrax</strong>. Já a <strong>Komatsu Forest</strong> (florestal) entra como concorrente.
        </CfgBanner>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 20, marginBottom: 24 }}>
          <div style={{ flex: 1 }}>
            <label style={{ font: '500 13px/1 var(--ff-body)', color: 'var(--tx-2)', display: 'block', marginBottom: 8 }}>
              Limiar para importação automática: <strong>{threshold}%</strong>
            </label>
            <input type="range" min={55} max={95} step={5} value={threshold}
              onChange={function(e){ setThreshold(Number(e.target.value)); }}
              style={{ width: 240, accentColor: 'var(--grn)' }} />
            <div style={{ font: '400 11px/1.5 var(--ff-body)', color: 'var(--tx-3)', marginTop: 6 }}>
              ≥ {threshold}% → importado automaticamente &nbsp;|&nbsp; 55–{threshold - 1}% → sugestão para revisão manual
            </div>
          </div>
          <button onClick={carregar}
            style={{ padding: '10px 22px', borderRadius: 'var(--r-md)', background: 'var(--grn)', color: '#fff', border: 'none', font: '600 14px/1 var(--ff-body)', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8, flexShrink: 0 }}>
            <span dangerouslySetInnerHTML={_frotaIcon('refresh-cw', 16)} style={{ display: 'inline-flex' }} />
            Carregar e analisar
          </button>
        </div>
      </div>
    );
  }

  // ── loading ────────────────────────────────────────────
  if (fase === 'loading') {
    return (
      <div style={{ padding: '60px 0', textAlign: 'center', color: 'var(--tx-3)' }}>
        <div style={{ font: '400 14px/1.4 var(--ff-body)', marginTop: 16 }}>
          Atualizando frota e carregando Vendas Perdidas, cruzando com {(window.CRM_DATA.clientes || []).length} clientes CRM…
        </div>
      </div>
    );
  }

  // ── preview / importing ────────────────────────────────
  var BADGE = {
    importar:     { label: 'Importar',      color: 'var(--grn)',  bg: 'var(--grn-050)',  border: 'var(--grn-100)'  },
    ignorar:      { label: 'Ignorado',      color: 'var(--tx-3)', bg: 'var(--bg-2)',     border: 'var(--border)'   },
    sugestao:     { label: 'Sugestão',      color: '#a86300',     bg: '#fff7e6',         border: '#f4d87a'         },
    sem_match:    { label: 'Sem match',     color: 'var(--tx-3)', bg: 'var(--bg-2)',     border: 'var(--border)'   },
    ja_importado: { label: 'Já importado',  color: 'var(--info)', bg: 'var(--info-050)', border: 'var(--info-100)' },
  };

  return (
    <div>
      {/* Cartões de stats */}
      <div style={{ display: 'flex', gap: 10, flexWrap: 'wrap', marginBottom: 20 }}>
        {[
          { label: 'Para importar', v: stats.importar,     color: 'var(--grn)',  bg: 'var(--grn-050)',  border: 'var(--grn-100)'  },
          { label: 'Sugestão',      v: stats.sugestao,     color: '#a86300',     bg: '#fff7e6',         border: '#f4d87a'         },
          { label: 'Sem match',     v: stats.sem_match,    color: 'var(--tx-3)', bg: 'var(--bg-2)',     border: 'var(--border)'   },
          { label: 'Já importado',  v: stats.ja_importado, color: 'var(--info)', bg: 'var(--info-050)', border: 'var(--info-100)' },
          { label: 'Ignorados',     v: stats.ignorar,      color: 'var(--tx-3)', bg: 'var(--bg-2)',     border: 'var(--border)'   },
        ].map(function(s) {
          return (
            <div key={s.label} style={{ padding: '12px 16px', borderRadius: 'var(--r-md)', background: s.bg, border: '1px solid ' + s.border, minWidth: 100, textAlign: 'center' }}>
              <div style={{ font: '700 24px/1 var(--ff-display)', color: s.color }}>{s.v}</div>
              <div style={{ font: '400 11px/1.4 var(--ff-body)', color: s.color, marginTop: 4 }}>{s.label}</div>
            </div>
          );
        })}
      </div>

      {/* Barra de progresso */}
      {fase === 'importing' && (
        <div style={{ marginBottom: 12, background: 'var(--bg-2)', borderRadius: 100, height: 6, overflow: 'hidden' }}>
          <div style={{ height: '100%', background: 'var(--grn)', width: (progresso.total ? (progresso.atual / progresso.total * 100) : 0) + '%', transition: 'width .25s' }} />
        </div>
      )}

      {/* Barra de ações */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
          <span style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>{rows.length} linhas da planilha VP</span>
          {ignoradosKom > 0 && (
            <>
              <span style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>|</span>
              <span title="Komatsu de construção não é importada aqui (vem do Komtrax); Komatsu Forest entra normalmente"
                style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>
                {ignoradosKom} Komatsu puladas (Komtrax)
              </span>
            </>
          )}
          <span style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>|</span>
          <span style={{ font: '400 12px/1 var(--ff-body)', color: 'var(--tx-3)' }}>Limiar: {threshold}%</span>
          <input type="range" min={55} max={95} step={5} value={threshold}
            onChange={function(e){ setThreshold(Number(e.target.value)); }}
            style={{ width: 100, accentColor: 'var(--grn)' }} />
          <button onClick={carregar} disabled={fase === 'importing'}
            style={{ padding: '4px 10px', borderRadius: 'var(--r-sm)', background: 'transparent', border: '1px solid var(--border)', color: 'var(--tx-2)', font: '500 11px/1 var(--ff-body)', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 5 }}>
            <span dangerouslySetInnerHTML={_frotaIcon('refresh-cw', 12)} style={{ display: 'inline-flex' }} /> Re-analisar
          </button>
          {stats.sugestao > 0 && fase !== 'importing' && (
            <button onClick={aceitarTodasSugestoes}
              title="Promove todas as sugestões para 'Importar' usando o melhor candidato de cada uma"
              style={{ padding: '4px 10px', borderRadius: 'var(--r-sm)', background: '#fff7e6', border: '1px solid #f4d87a', color: '#a86300', font: '500 11px/1 var(--ff-body)', cursor: 'pointer' }}>
              Aceitar {stats.sugestao} sugestões
            </button>
          )}
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <button onClick={function(){ setFase('idle'); setRows([]); }} disabled={fase === 'importing'}
            style={{ padding: '7px 14px', borderRadius: 'var(--r-md)', background: 'transparent', border: '1px solid var(--border)', color: 'var(--tx-2)', font: '500 13px/1 var(--ff-body)', cursor: 'pointer' }}>
            Voltar
          </button>
          <button onClick={importarTodos} disabled={fase === 'importing' || stats.importar === 0}
            style={{ padding: '7px 18px', borderRadius: 'var(--r-md)', background: stats.importar === 0 ? 'var(--bg-2)' : 'var(--grn)', color: stats.importar === 0 ? 'var(--tx-3)' : '#fff', border: 'none', font: '600 13px/1 var(--ff-body)', cursor: stats.importar === 0 ? 'default' : 'pointer', display: 'flex', alignItems: 'center', gap: 6 }}>
            {fase === 'importing'
              ? <span>{progresso.atual}/{progresso.total}…</span>
              : <><span dangerouslySetInnerHTML={_frotaIcon('download', 14)} style={{ display: 'inline-flex' }} /> Importar {stats.importar} máquinas</>}
          </button>
        </div>
      </div>

      {/* Painel de erros da última importação */}
      {falhas.length > 0 && fase !== 'importing' && (
        <div style={{ marginBottom: 16, padding: '12px 16px', borderRadius: 'var(--r-md)', background: '#fdecec', border: '1px solid #f5b5b5' }}>
          <div style={{ font: '600 13px/1.4 var(--ff-body)', color: '#c0392b', marginBottom: 6 }}>
            {falhas.length} linha(s) falharam ao importar — continuam como “Importar” para você tentar de novo:
          </div>
          <ul style={{ margin: 0, paddingLeft: 18, font: '400 12px/1.6 var(--ff-body)', color: 'var(--tx-2)', maxHeight: 150, overflowY: 'auto' }}>
            {falhas.slice(0, 50).map(function(f, i) {
              return (
                <li key={i}>
                  <strong>{f.fabricante} {f.modelo}</strong>{f.cliente ? ' → ' + f.cliente : ''}: <span style={{ color: '#c0392b' }}>{f.msg}</span>
                </li>
              );
            })}
            {falhas.length > 50 && <li>… e mais {falhas.length - 50}.</li>}
          </ul>
        </div>
      )}

      {/* Tabela de preview */}
      <div style={{ overflowX: 'auto', borderRadius: 'var(--r-md)', border: '1px solid var(--border)' }}>
        <table style={{ width: '100%', borderCollapse: 'collapse', font: '400 12px/1.4 var(--ff-body)' }}>
          <thead>
            <tr style={{ background: 'var(--bg-2)', borderBottom: '1px solid var(--border)' }}>
              {['Fabricante','Modelo','Ano','Cliente (VP)','Match CRM','Sim.','Ação'].map(function(h) {
                return <th key={h} style={{ padding: '9px 12px', textAlign: 'left', font: '600 10px/1 var(--ff-body)', color: 'var(--tx-3)', letterSpacing: '.07em', textTransform: 'uppercase', whiteSpace: 'nowrap' }}>{h}</th>;
              })}
            </tr>
          </thead>
          <tbody>
            {rows.map(function(r, i) {
              var badge    = BADGE[r.acao] || BADGE.sem_match;
              var canToggle = r.acao !== 'sem_match' && r.acao !== 'ja_importado';
              var pct      = r.score > 0 ? Math.round(r.score * 100) + '%' : '—';
              var rowBg    = r.acao === 'ignorar' ? 'var(--bg-2)' : r.acao === 'sugestao' ? '#fffdf4' : 'transparent';
              return (
                <tr key={r.key + i} style={{ background: rowBg, borderBottom: '1px solid var(--border)', opacity: r.acao === 'ignorar' ? 0.45 : 1 }}>
                  <td style={{ padding: '7px 12px', whiteSpace: 'nowrap' }}>{r.vpRow.fabricante}</td>
                  <td style={{ padding: '7px 12px', whiteSpace: 'nowrap' }}>{r.vpRow.modelo}</td>
                  <td style={{ padding: '7px 12px', whiteSpace: 'nowrap' }}>{r.vpRow.ano || '—'}</td>
                  <td style={{ padding: '7px 12px', maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={r.vpRow.cliente}>{r.vpRow.cliente}</td>
                  <td style={{ padding: '7px 12px', maxWidth: 200 }}>
                    {r.acao === 'sugestao' && r.sugestoes.length ? (
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
                        {r.sugestoes.map(function(sg, si) {
                          return (
                            <button key={si} onClick={function(){ aceitarSugestao(i, sg.cli); }}
                              title={'Aceitar: ' + (sg.cli.razao || '')}
                              style={{ padding: '3px 7px', borderRadius: 'var(--r-sm)', background: '#fffae8', border: '1px solid #f4d87a', color: '#a86300', font: '500 11px/1.3 var(--ff-body)', cursor: 'pointer', textAlign: 'left' }}>
                              {(sg.cli.razao || '').slice(0, 28)}{sg.cli.razao && sg.cli.razao.length > 28 ? '…' : ''}
                              {' '}<span style={{ color: '#c07800' }}>({Math.round(sg.score * 100)}%)</span>
                            </button>
                          );
                        })}
                      </div>
                    ) : r.cliente ? (
                      <span title={r.cliente.razao}>{(r.cliente.razao || '').slice(0, 35)}{r.cliente.razao && r.cliente.razao.length > 35 ? '…' : ''}</span>
                    ) : (
                      <span style={{ color: 'var(--tx-3)' }}>—</span>
                    )}
                  </td>
                  <td style={{ padding: '7px 12px', textAlign: 'center', fontWeight: r.score >= (threshold / 100) ? 600 : 400, color: r.score >= (threshold / 100) ? 'var(--grn)' : r.score >= FROTA_SUGESTAO_MIN ? '#a86300' : 'var(--tx-3)' }}>{pct}</td>
                  <td style={{ padding: '7px 12px' }}>
                    <div style={{ display: 'flex', gap: 5, alignItems: 'center' }}>
                      <span style={{ padding: '2px 8px', borderRadius: 'var(--r-pill)', background: badge.bg, color: badge.color, border: '1px solid ' + badge.border, font: '500 11px/1.4 var(--ff-body)', whiteSpace: 'nowrap' }}>{badge.label}</span>
                      {canToggle && fase !== 'importing' && (
                        <button onClick={function(){ alternarAcao(i); }}
                          style={{ padding: '2px 7px', borderRadius: 'var(--r-sm)', background: 'transparent', border: '1px solid var(--border)', color: 'var(--tx-3)', font: '400 10px/1.4 var(--ff-body)', cursor: 'pointer' }}>
                          {r.acao === 'ignorar' ? 'Restaurar' : 'Ignorar'}
                        </button>
                      )}
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════
// COMPONENTE PRINCIPAL — Configuracoes
// ═══════════════════════════════════════════════════════

function Configuracoes({ onNav }) {
  // Permissão por papel: gerência e administrativo veem todas as abas;
  // comercial e BDR só veem "Geral" (preferências pessoais).
  const _podeVerTudoCfg = !!(window.CRM_USER && (window.CRM_USER.isGerente || window.CRM_USER.isAdmin));
  const _tabsVisiveis   = _podeVerTudoCfg ? CFG_TABS : CFG_TABS.filter(function(t){ return t.id === 'geral'; });

  const [activeTab, setActiveTab] = useCfgState('geral');
  const [toast, setToast]         = useCfgState({ msg: '', kind: 'ok' });

  // Fallback de segurança: se activeTab apontar para aba sem permissão, volta para Geral
  useCfgEffect(() => {
    if (!_tabsVisiveis.some(function(t){ return t.id === activeTab; })) {
      setActiveTab('geral');
    }
  }, [activeTab, _podeVerTudoCfg]);

  useCfgEffect(() => {
    setTimeout(() => { try { window.lucide && lucide.createIcons({ attrs: { 'stroke-width': 1.75 } }); } catch(e) {} }, 0);
  }, [activeTab]);

  function emitToast(msg, kind) { setToast({ msg, kind: kind || 'ok' }); }

  return (
    <div style={{ maxWidth: 1100, margin: '0 auto', padding: '24px 24px 40px' }}>

      {/* Header */}
      <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 20, marginBottom: 24, paddingBottom: 20, borderBottom: '1px solid var(--border)' }}>
        <div>
          <div style={{ font: '600 10px/1 var(--ff-body)', textTransform: 'uppercase', letterSpacing: '.14em', color: 'var(--tx-3)', marginBottom: 8 }}>
            CRM · SISTEMA
          </div>
          <div style={{ font: '400 32px/1 var(--ff-display)', letterSpacing: '.04em', color: 'var(--tx)' }}>
            CONFIGURAÇÕES
          </div>
          <div style={{ font: '400 13px/1.4 var(--ff-body)', color: 'var(--tx-3)', marginTop: 6, maxWidth: 600 }}>
            Centralize listas, validações e preferências do CRM. Mudanças sem deploy.
          </div>
        </div>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '4px 10px', borderRadius: 'var(--r-pill)', background: 'var(--grn-050)', color: 'var(--grn)', border: '1px solid var(--grn-100)', font: '600 10px/1 var(--ff-body)', letterSpacing: '.04em', textTransform: 'uppercase' }}>
          <i data-lucide="layers" style={{ width: 13, height: 13 }}></i> Onda 1 — MVP
        </span>
      </div>

      {/* Tabs */}
      <div style={{ display: 'flex', gap: 2, marginBottom: 24, borderBottom: '1px solid var(--border)', overflowX: 'auto' }}>
        {_tabsVisiveis.map(t => {
          const active = activeTab === t.id;
          return (
            <button key={t.id} onClick={() => setActiveTab(t.id)}
              style={{
                display: 'inline-flex', alignItems: 'center', gap: 8,
                padding: '12px 18px', background: 'transparent', border: 'none',
                borderBottom: '2px solid ' + (active ? 'var(--grn)' : 'transparent'),
                color: active ? 'var(--grn)' : 'var(--tx-3)',
                font: (active ? '600' : '500') + ' 13px/1 var(--ff-body)',
                cursor: 'pointer', whiteSpace: 'nowrap', marginBottom: -1
              }}>
              <i data-lucide={t.icon} style={{ width: 15, height: 15 }}></i>
              {t.label}
            </button>
          );
        })}
      </div>

      {/* Conteúdo da aba */}
      <div>
        {activeTab === 'geral'     && <AbaGeral onToast={emitToast} />}
        {activeTab === 'pipeline'  && <AbaPipeline onToast={emitToast} />}
        {activeTab === 'propostas' && <AbaPropostas onToast={emitToast} />}
        {activeTab === 'pedidos'   && <AbaPedidosFiliais onToast={emitToast} />}
        {activeTab === 'clientes'  && <AbaClientes onToast={emitToast} />}
        {activeTab === 'motivos'   && <AbaMotivos onToast={emitToast} />}
        {activeTab === 'regioes'   && <AbaRegioes onToast={emitToast} />}
        {activeTab === 'komtrax'   && <AbaKomtrax onToast={emitToast} />}
        {activeTab === 'frota'     && <AbaFrotaConcorrente onToast={emitToast} />}
      </div>

      {/* Toast */}
      <CfgToast message={toast.msg} kind={toast.kind} onClose={() => setToast({ msg: '', kind: 'ok' })} />
    </div>
  );
}

window.Configuracoes = Configuracoes;
