// Afiliados → Carteira de afiliados (spec 2026-10-08-carteira-afiliados §3, PR 2): ranking
// por LÍQUIDO na régua do Master Affiliates da Buygoods (bruto − comissão − taxes −
// devolvido), mês a mês, com concentração (top 10 / top N = 80%) e quebra por família ao
// clicar na linha. Molde self-contained (LoadingScreen no 1º load, FullLoadingOverlay nas
// trocas de filtro, offline honesto com useAutoRetry) — dado operacional, sem mock.
// TODAS as contas vêm prontas do backend; aqui é só formatação. Prefixo `ctr`/`Ctr`/`CTR_`
// (escopo Babel compartilhado entre .jsx).

const CTR_LIMITE = 100;   // linhas renderizadas antes do "mostrar todos"

const CTR_MES_NOMES = ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez'];
const ctrMesLabel = (ym) => { const [y, m] = ym.split('-'); return `${CTR_MES_NOMES[+m - 1]}/${y.slice(2)}`; };

const ctrPct = (v) => (v == null ? '—' : PCT(v));
// USD com 2 casas (EPC) — USD() arredonda para inteiro. Mesmo símbolo/câmbio do resto do dash.
const ctrUsd2 = (n) => (n == null ? '—'
  : (n < 0 ? '-' : '') + _sym() + Math.abs(n * FX.rate).toLocaleString(_loc(), { minimumFractionDigits: 2, maximumFractionDigits: 2 }));
// Lista de meses 'YYYY-MM' (ordenada) → "abr/26–ago/26" se contíguos (3+); senão "abr/26, mai/26, ago/26".
const ctrMesIdx = (ym) => +ym.slice(0, 4) * 12 + +ym.slice(5, 7);
const ctrFaixaMeses = (ms) => {
  if (!ms || !ms.length) return '';
  const contiguos = ms.every((m, i) => i === 0 || ctrMesIdx(m) === ctrMesIdx(ms[i - 1]) + 1);
  return contiguos && ms.length > 2 ? `${ctrMesLabel(ms[0])}–${ctrMesLabel(ms[ms.length - 1])}`
    : ms.map(ctrMesLabel).join(', ');
};

// Líquido negativo em vermelho; zero apagado. `cor` opcional para o valor positivo.
const ctrMoney = (v, cor) => (
  v === 0 ? <span style={ctr.dim}>—</span>
  : <span style={v < 0 ? { color: 'var(--negative)' } : (cor ? { color: cor } : undefined)}>{USD(v)}</span>
);
// Refund null-safe (cor e texto).
const ctrRefundCor = (v) => (v == null ? 'var(--fg-4)' : refundHealth(v).color);

// Limites de "último contato" quando o estado da carteira não informa (constante = deps estáveis).
const CTR_LIM_PADRAO = { alerta: 7, critico: 14 };
// Larguras das 2 colunas fixas na rolagem horizontal (# e Afiliado).
const CTR_W_POS = 44;
const CTR_W_NOME = 240;

// Bloco mês a mês: só os N meses mais recentes com movimento (o "Resultado do período" segue
// cobrindo o período inteiro).
const CTR_MESES_VISIVEIS = 4;

// Grupo "Período" — logo depois do último mês.
const CTR_COLS_PERIODO = [
  { key: 'refundRate', label: 'Refund' },
  { key: 'aov',        label: 'AOV' },
  // Tráfego (PR 4): export Master Affiliates importado, só meses importados dentro da janela.
  { key: 'visitantes', label: 'Visitantes' },
  { key: 'conversao',  label: 'Conversão' },
  { key: 'epc',        label: 'EPC' },
];
// Grupo "Resultado do período" — secundário (a decisão de contato olha os meses).
const CTR_COLS_RESULT = [
  { key: 'bruto',     label: 'Bruto' },
  { key: 'comissao',  label: 'Comissão' },
  { key: 'taxes',     label: 'Taxes' },
  { key: 'devolvido', label: 'Devolvido' },
  { key: 'liquido',   label: 'Líquido' },
  { key: 'pct',       label: '% líquido' },
  { key: 'pctAcum',   label: '% acum.' },
];

// --- Mapa de cor mês a mês: cada mês contra o mês anterior do mesmo afiliado, pelo ritmo
// diário (valor ÷ dias do mês dentro da janela) — mês parcial compara justo com mês cheio.
const ctrDia = (iso) => (iso ? Date.UTC(+iso.slice(0, 4), +iso.slice(5, 7) - 1, +iso.slice(8, 10)) : null);
const ctrDiasNoMes = (ym, ini, fim) => {
  const [y, m] = ym.split('-').map(Number);
  const a = Date.UTC(y, m - 1, 1), b = Date.UTC(y, m, 0);
  const lo = ini != null ? Math.max(a, ini) : a;
  const hi = fim != null ? Math.min(b, fim) : b;
  return hi < lo ? 0 : Math.round((hi - lo) / 86400000) + 1;
};
// Puro: devolve { bg, v } — v = variação do ritmo diário (null = sem base de comparação).
const ctrHeat = (cur, prev, diasCur, diasPrev) => {
  if (prev == null || !(prev > 0) || !diasCur || !diasPrev) return { bg: null, v: null };
  const v = ((cur || 0) / diasCur) / (prev / diasPrev) - 1;
  const a = Math.abs(v), pos = v > 0;
  if (a < 0.02) return { bg: null, v };
  const bg = a < 0.10 ? (pos ? 'var(--positive-bg)' : 'var(--negative-bg)')
    : a < 0.30 ? (pos ? 'rgba(31,157,107,.22)' : 'rgba(216,69,59,.22)')
    : (pos ? 'rgba(31,157,107,.40)' : 'rgba(216,69,59,.40)');
  return { bg, v };
};
const ctrHeatTitulo = (v, mesPrev, prev) => (mesPrev == null || prev == null ? 'sem mês anterior para comparar'
  : prev < 0 ? 'mês anterior com líquido negativo — sem comparação'
  : v == null ? 'mês anterior sem venda — sem comparação'
  : `${v > 0 ? '+' : ''}${Math.round(v * 100)}% vs ${ctrMesLabel(mesPrev)} (ritmo diário)`);

const ctrIrAfiliado = (r) => {
  if (typeof gotoAffiliate === 'function') gotoAffiliate({ platform: r.plataforma, name: r.afiliado });
};

// --- Acompanhamento da equipe (PR 3): responsável, status, último contato, observações ---
const CTR_STATUS_COR = {
  ativo:         { fg: 'var(--fenix-blue)', bg: 'var(--fenix-blue-100)' },
  em_reativacao: { fg: 'var(--warning)',    bg: 'var(--warning-bg)' },
  sem_retorno:   { fg: 'var(--negative)',   bg: 'var(--negative-bg)' },
  encerrado:     { fg: 'var(--fg-3)',       bg: 'var(--bg-sunken)' },
};
const ctrChave = (plataforma, afiliado) => `${plataforma}|${afiliado}`;
const ctrDias = (iso) => (iso ? Math.max(0, Math.floor((Date.now() - new Date(iso).getTime()) / 86400000)) : null);
const ctrQuando = (iso) => new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
const ctrContatoCor = (dias, lim) => (dias == null ? 'var(--fg-4)' : dias >= lim.critico ? 'var(--negative)' : dias >= lim.alerta ? 'var(--warning)' : 'var(--positive)');
const ctrContatoTxt = (dias) => (dias == null ? 'nunca' : dias === 0 ? 'hoje' : dias === 1 ? 'há 1 dia' : `há ${dias} dias`);

// Painel lateral fixo (não fica preso na rolagem da tabela, como um menu dentro dela ficaria).
const CtrPainel = ({ linha, item, info, onClose, onSalvo }) => {
  const [eventos, setEventos] = React.useState(null);
  const [texto, setTexto] = React.useState('');
  const [salvando, setSalvando] = React.useState(false);
  const [erro, setErro] = React.useState(null);
  const pode = info.podeEditar && info.disponivel;
  const chave = ctrChave(linha.plataforma, linha.afiliado);
  const chaveAtual = React.useRef(chave);       // resposta de outro afiliado (troca rápida) é descartada
  chaveAtual.current = chave;
  const asideRef = React.useRef(null);
  const onCloseRef = React.useRef(onClose);
  onCloseRef.current = onClose;

  const carregar = React.useCallback(() => {
    const k = ctrChave(linha.plataforma, linha.afiliado);
    fetchCarteiraEventos(linha.plataforma, linha.afiliado)
      .then(d => { if (chaveAtual.current === k) setEventos(d.eventos); })
      .catch(() => { if (chaveAtual.current === k) setEventos([]); });
  }, [linha.plataforma, linha.afiliado]);
  React.useEffect(() => {
    setEventos(null); setTexto(''); setErro(null); setSalvando(false); carregar();
    return () => { chaveAtual.current = null; };   // desmontou/trocou: ignora o que chegar depois
  }, [carregar]);

  // Acessibilidade: foco no painel ao abrir, Escape fecha.
  React.useEffect(() => {
    if (asideRef.current) asideRef.current.focus();
    const tecla = (e) => { if (e.key === 'Escape') onCloseRef.current(); };
    window.addEventListener('keydown', tecla);
    return () => window.removeEventListener('keydown', tecla);
  }, []);

  const escrever = (caminho, method, body) => {
    const k = chave;
    setSalvando(true); setErro(null);
    return carteiraEscrever(caminho, method, { plataforma: linha.plataforma, afiliado: linha.afiliado, ...body })
      .then(() => { onSalvo(); if (chaveAtual.current !== k) return false; carregar(); return true; })
      .catch(e => { if (chaveAtual.current === k) setErro(e.message || 'Falha ao salvar'); return false; })
      .finally(() => { if (chaveAtual.current === k) setSalvando(false); });
  };
  const registrar = (tipo) => {
    if (!texto.trim()) { setErro('Escreva o que aconteceu antes de salvar.'); return; }
    escrever('eventos', 'POST', { tipo, texto }).then(ok => { if (ok) setTexto(''); });
  };

  const respAtual = item?.responsavel?.id ?? 'nenhum';
  const statusAtual = item?.status || 'ativo';
  return (
    <div style={ctr.painelFundo} onMouseDown={e => { if (e.target === e.currentTarget) onClose(); }}>
      <aside style={ctr.painel} ref={asideRef} tabIndex={-1} role="dialog" aria-modal="true"
             aria-label={'Acompanhamento de ' + linha.afiliado}>
        <div style={ctr.painelHead}>
          <div>
            <div style={ctr.eye}>Acompanhamento</div>
            <div style={ctr.painelNome}>{linha.afiliado}</div>
            <div style={ctr.painelSub}>
              posição {linha.posicao} por líquido · líquido {USD(linha.liquido)} · bruto {USDk(linha.bruto)}
            </div>
            <button style={ctr.linkBtn} onClick={() => { onClose(); ctrIrAfiliado(linha); }}>
              Abrir página do afiliado →
            </button>
          </div>
          <button style={ctr.fechar} onClick={onClose} title="Fechar" aria-label="Fechar"><Icon name="x" size={18}/></button>
        </div>

        {!info.disponivel && (
          <div style={ctr.notice}><Icon name="alert" size={15}/> Carteira ainda não instalada no banco — só leitura do ranking.</div>
        )}
        {item?.ticketSop && (
          <div style={ctr.sopNote}>Ticket do SOP aberto: caso #{item.ticketSop} (SOP - 3 Gatilhos → Alertas).</div>
        )}

        <div style={ctr.painelCampos}>
          {pode ? (
            <>
              <Dropdown icon="users" label="Responsável" value={respAtual}
                onChange={v => escrever('item', 'PUT', { mudarResponsavel: true, responsavelId: v === 'nenhum' ? null : v })}
                options={[{ key: 'nenhum', label: 'Ninguém' }, ...info.responsaveis.map(r => ({ key: r.id, label: r.nome }))]}/>
              <Dropdown icon="target" label="Status" value={statusAtual}
                onChange={v => escrever('item', 'PUT', { status: v })} options={info.status}/>
            </>
          ) : (
            <div style={ctr.painelLeitura}>
              Responsável: <b>{item?.responsavel?.nome || 'ninguém'}</b> · Status: <b>{(info.status.find(x => x.key === statusAtual) || {}).label || statusAtual}</b>
            </div>
          )}
        </div>

        {pode && (
          <div style={ctr.form}>
            <textarea style={ctr.textarea} rows={3} maxLength={2000} value={texto}
              aria-label="Registro de acompanhamento"
              placeholder="O que aconteceu? Ex.: contato via PV, aguardando retorno sobre as métricas."
              onChange={e => setTexto(e.target.value)}/>
            <div style={ctr.formBtns}>
              <button style={ctr.btnPri} disabled={salvando} onClick={() => registrar('contato')}>Registrar contato</button>
              <button style={ctr.btnSec} disabled={salvando} onClick={() => registrar('nota')}>Só observação</button>
            </div>
            <div style={ctr.formAjuda}>"Registrar contato" atualiza o último contato. "Só observação" anota sem contar como contato.</div>
          </div>
        )}
        {erro && <div style={{ ...ctr.notice, color: 'var(--negative)', background: 'var(--negative-bg)' }}>{erro}</div>}

        <div style={ctr.eye}>Linha do tempo</div>
        <div style={ctr.timeline}>
          {eventos == null && <div style={ctr.dimTxt}>Carregando…</div>}
          {eventos && eventos.length === 0 && <div style={ctr.dimTxt}>Nenhum registro ainda.</div>}
          {eventos && eventos.map(e => (
            <div key={e.id} style={ctr.evento}>
              <div style={ctr.eventoHead}>
                <span style={{ ...ctr.eventoTipo, ...(e.tipo === 'contato' ? { color: 'var(--positive)', background: 'var(--positive-bg)' } : {}) }}>
                  {{ nota: 'Observação', contato: 'Contato', responsavel: 'Responsável', status: 'Status' }[e.tipo] || e.tipo}
                </span>
                <span style={ctr.dimTxt}>{e.autor_nome} · {ctrQuando(e.criado_em)}</span>
              </div>
              <div style={ctr.eventoTxt}>{e.texto}</div>
            </div>
          ))}
        </div>
      </aside>
    </div>
  );
};

const CarteiraAfiliadosView = ({ families = [] }) => {
  const [period, setPeriod] = React.useState('ytd');
  const [customRange, setCustomRange] = React.useState(null);
  const [platform, setPlatform] = React.useState('buygoods');
  const [product, setProduct] = React.useState('todos');
  const [funnel, setFunnel] = React.useState('todos');
  const [refundBy, setRefundBy] = React.useState('cancelamento');
  const [metricaMes, setMetricaMes] = React.useState('bruto');
  const [busca, setBusca] = React.useState('');
  const [sortKey, setSortKey] = React.useState(null);   // null = último mês mostrado
  const [dir, setDir] = React.useState(-1);
  const [aberto, setAberto] = React.useState(null);
  const [todos, setTodos] = React.useState(false);
  const [data, setData] = React.useState(null);
  const [err, setErr] = React.useState(false);
  const [estado, setEstado] = React.useState(null);      // /carteira/estado (sem cache)
  const [painel, setPainel] = React.useState(null);      // chave da linha aberta no painel
  const [fResp, setFResp] = React.useState('todos');
  const [fStatus, setFStatus] = React.useState('todos');
  const [fContato, setFContato] = React.useState('todos');
  const [recarga, setRecarga] = React.useState(0);       // bump = refetch do ranking (pós-import)
  const [importando, setImportando] = React.useState(false);
  const [importMsg, setImportMsg] = React.useState(null); // { ok, texto }
  const arquivoRef = React.useRef(null);
  const fullLoading = useFullLoading();
  const withLoad = (fn) => (...a) => { fullLoading.mark(); fn(...a); };
  const retry = useAutoRetry(err);

  const handleProductChange = (p) => { withLoad(setProduct)(p); setFunnel('todos'); };

  React.useEffect(() => {
    const began = fullLoading.begin();
    let cancelled = false;
    fetchCarteiraAPI({ period, product, funnel, refundBy, platform, start: customRange?.start, end: customRange?.end })
      .then(d => { if (cancelled) return; setData(d); setErr(false); setAberto(null); setPainel(null); })
      .catch(() => { if (!cancelled) setErr(true); })
      .finally(() => { fullLoading.end(began); });
    return () => { cancelled = true; };
  }, [period, customRange, product, funnel, refundBy, platform, retry, recarga]);

  const importarTrafego = (e) => {
    const file = e.target.files && e.target.files[0];
    e.target.value = '';                                  // mesmo arquivo de novo dispara onChange
    if (!file) return;
    setImportando(true); setImportMsg(null);
    carteiraImportarTrafego(file)
      .then(d => {
        setImportMsg({ ok: true, texto: `Importado: ${ctrFaixaMeses(d.meses)} · ${NUM(d.afiliados)} afiliados` });
        setRecarga(x => x + 1);
      })
      .catch(err2 => {
        // Timeout/abort: o servidor pode ter gravado mesmo assim — recarrega para conferir.
        if (err2 && (err2.name === 'TimeoutError' || err2.name === 'AbortError')) {
          setImportMsg({ ok: false, texto: 'A importação demorou além do esperado — recarregando para conferir se entrou.' });
          setRecarga(x => x + 1);
          return;
        }
        setImportMsg({ ok: false, texto: err2.message || 'Falha ao importar' });
      })
      .finally(() => setImportando(false));
  };

  const recarregarEstado = React.useCallback(() => {
    fetchCarteiraEstado().then(setEstado).catch(() => {});
  }, []);
  React.useEffect(() => { recarregarEstado(); }, [recarregarEstado, retry]);

  const itens = React.useMemo(() => {
    const m = {};
    (estado?.itens || []).forEach(i => { m[ctrChave(i.plataforma, i.afiliado)] = i; });
    return m;
  }, [estado]);
  const lim = estado?.contatoDias || CTR_LIM_PADRAO;
  const comCarteira = !!estado?.disponivel;

  const sortedFamilies = React.useMemo(() => [...families].sort((a, b) => a.name.localeCompare(b.name)), [families]);
  const variants = product !== 'todos' ? (families.find(f => f.name === product)?.variants || []) : [];

  // Mês sem nenhum movimento (ex.: jan–mar no YTD — o banco começa em abr/26) não vira coluna;
  // dos que sobram, só os CTR_MESES_VISIVEIS mais recentes.
  const { mesesComDado, meses } = React.useMemo(() => {
    const s = new Set();
    if (data) [...data.rows, ...data.foraDoRanking].forEach(r => r.meses.forEach(m => {
      if (m.bruto || m.liquido) s.add(m.mes);
    }));
    const todosMeses = data ? data.meses.filter(m => s.has(m.mes)) : [];
    return { mesesComDado: todosMeses.length, meses: todosMeses.slice(-CTR_MESES_VISIVEIS) };
  }, [data]);
  const ultimoMes = meses.length ? meses[meses.length - 1].mes : null;
  // Chave de mês que não está mais entre os mostrados (troca de filtro) volta ao padrão.
  const sortValida = sortKey && !(sortKey.startsWith('mes:') && !meses.some(m => `mes:${m.mes}` === sortKey));
  const sortEf = sortValida ? sortKey : (ultimoMes ? `mes:${ultimoMes}` : 'bruto');

  const linhas = React.useMemo(() => {
    if (!data) return [];
    const q = busca.trim().toLowerCase();
    const base = data.rows.filter(r => {
      if (q && !r.afiliado.toLowerCase().includes(q)) return false;
      const it = itens[ctrChave(r.plataforma, r.afiliado)];
      if (fResp !== 'todos' && (fResp === 'nenhum' ? it?.responsavel : String(it?.responsavel?.id) !== fResp)) return false;
      if (fStatus !== 'todos' && (it?.status || 'ativo') !== fStatus) return false;
      if (fContato === 'atrasado') { const d = ctrDias(it?.ultimoContato); if (d != null && d < lim.alerta) return false; }
      return true;
    });
    const encerrado = (r) => (itens[ctrChave(r.plataforma, r.afiliado)]?.status === 'encerrado' ? 1 : 0);
    const val = (r) => (sortEf.startsWith('mes:')
      ? (r.meses.find(m => m.mes === sortEf.slice(4)) || {})[metricaMes]
      : sortEf === 'afiliado' ? r.afiliado.toLowerCase() : r[sortEf]);
    return [...base].sort((a, b) => {
      const ea = encerrado(a), eb = encerrado(b);
      if (ea !== eb) return ea - eb;                          // encerrado sempre no fim
      const va = val(a), vb = val(b);
      if (va == null && vb == null) return 0;
      if (va == null) return 1;
      if (vb == null) return -1;
      return (va > vb ? 1 : va < vb ? -1 : 0) * dir;
    });
  }, [data, busca, sortEf, dir, metricaMes, itens, fResp, fStatus, fContato, lim.alerta]);

  const sortBy = (k) => {
    setSortKey(k);
    if (k === sortEf) setDir(-dir);
    else setDir(k === 'afiliado' ? 1 : -1);
  };
  const ind = (k) => (sortEf === k ? (dir < 0 ? ' ▾' : ' ▴') : '');

  if (err && !data) return (
    <>
      <LoadingScreen conn="offline"/>
      <div style={ctr.offNote}>A carteira volta sozinha quando a API responder.</div>
    </>
  );
  if (!data) return <LoadingScreen conn="connecting"/>;

  const k = data.kpis;
  const visiveis = todos ? linhas : linhas.slice(0, CTR_LIMITE);
  const nCar = comCarteira ? 4 : 0;
  // Acompanhamento (2 + carteira) | meses | Período (refund, AOV, tráfego) | Resultado do período (7)
  const nCols = 2 + nCar + meses.length + CTR_COLS_PERIODO.length + CTR_COLS_RESULT.length;
  const mesesVis = new Set(meses.map(m => m.mes));
  const iniDia = ctrDia(data.start), fimDia = ctrDia(data.end);
  const diasMes = {};
  data.meses.forEach(m => { diasMes[m.mes] = ctrDiasNoMes(m.mes, iniDia, fimDia); });
  const diasDe = (ym) => (diasMes[ym] != null ? diasMes[ym] : ctrDiasNoMes(ym, iniDia, fimDia));
  const trf = data.trafego || { mesesImportados: [], ultimoImportado: null };
  const trafegoAviso = !trf.ultimoImportado ? 'Nenhum mês de tráfego importado ainda — Visitantes, Conversão e EPC ficam “—”.'
    : trf.mesesImportados.length ? `Tráfego importado: ${ctrFaixaMeses(trf.mesesImportados)} · meses sem import ficam “—”.`
    : `Nenhum mês do período tem tráfego importado (último import: ${ctrMesLabel(trf.ultimoImportado)}) — Visitantes, Conversão e EPC ficam “—”.`;
  const rotuloMeses = (meses.length === 1 ? 'Último mês' : `Últimos ${meses.length} meses`)
    + (metricaMes === 'bruto' ? ' · Bruto' : ' · Líquido');

  const th = (key, label, left, extra) => (
    <th key={key} style={{ ...ctr.th, textAlign: left ? 'left' : 'right', ...extra }} onClick={() => sortBy(key)}>
      {label}{ind(key)}
    </th>
  );

  const celCarteira = (r) => {
    if (!comCarteira) return [];
    const it = itens[ctrChave(r.plataforma, r.afiliado)];
    const st = it?.status || 'ativo';
    const cor = CTR_STATUS_COR[st] || CTR_STATUS_COR.ativo;
    const dias = ctrDias(it?.ultimoContato);
    const abrir = (e) => { e.stopPropagation(); setPainel(ctrChave(r.plataforma, r.afiliado)); };
    return [
      <td key="resp" style={{ ...ctr.tdCar, color: it?.responsavel ? 'var(--fg-1)' : 'var(--fg-4)' }} onClick={abrir}>
        {it?.responsavel?.nome || '—'}
      </td>,
      <td key="status" style={ctr.tdCar} onClick={abrir}>
        <span style={{ ...ctr.chip, color: cor.fg, background: cor.bg }}>
          {(estado.status.find(x => x.key === st) || {}).label || st}
        </span>
        {it?.ticketSop && <span style={ctr.sop} title={`Ticket do SOP aberto: caso #${it.ticketSop}`}>SOP</span>}
      </td>,
      <td key="contato" style={{ ...ctr.tdCar, color: ctrContatoCor(dias, lim), fontWeight: 600 }} onClick={abrir}>
        {ctrContatoTxt(dias)}
      </td>,
      <td key="obs" style={{ ...ctr.tdCar, ...ctr.tdObs }} onClick={abrir} title={it?.ultimaObs ? `${it.ultimaObs.autor}: ${it.ultimaObs.texto}` : 'Abrir acompanhamento'}>
        <span style={ctr.ellip}>
          {it?.ultimaObs ? it.ultimaObs.texto : <span style={{ color: 'var(--fg-4)' }}>+ acompanhar</span>}
        </span>
      </td>,
    ];
  };

  // Célula de mês: fundo = variação do ritmo diário contra o mês anterior em r.meses (calendário,
  // mesmo que oculto); o último mês mostrado é a âncora (negrito, maior).
  // Encerrado: texto apagado e mapa de cor só no passo fraco (sem opacity — vazaria nas colunas fixas).
  const celMes = (r, m, i, enc) => {
    const ant = i > 0 ? r.meses[i - 1] : null;
    const cur = m[metricaMes];
    const h = ant ? ctrHeat(cur, ant[metricaMes], diasDe(m.mes), diasDe(ant.mes)) : { bg: null, v: null };
    const bg = h.bg && enc ? (h.v > 0 ? 'var(--positive-bg)' : 'var(--negative-bg)') : h.bg;
    const ult = m.mes === ultimoMes;
    return (
      <td key={m.mes} className="fenix-num" title={ctrHeatTitulo(h.v, ant?.mes, ant ? ant[metricaMes] : null)}
          style={{ ...ctr.tdNum, ...(bg ? { background: bg } : {}), ...(ult ? ctr.tdUlt : {}), ...(enc ? ctr.tdEnc : {}) }}>
        {ctrMoney(cur)}
      </td>
    );
  };

  const celulas = (r, opts = {}) => [
    ...r.meses.map((m, i) => (mesesVis.has(m.mes) ? celMes(r, m, i, opts.enc) : null)).filter(Boolean),
    <td key="refundRate" className="fenix-num" style={{ ...ctr.tdNum, color: opts.enc ? 'var(--fg-3)' : ctrRefundCor(r.refundRate) }}>{ctrPct(r.refundRate)}</td>,
    <td key="aov" className="fenix-num" style={{ ...ctr.tdNum, ...(opts.enc ? ctr.tdEnc : {}) }}>{r.aov ? USD(r.aov) : '—'}</td>,
    <td key="visitantes" className="fenix-num" style={{ ...ctr.tdNum, ...(opts.enc ? ctr.tdEnc : {}) }}>{r.visitantes == null ? <span style={ctr.dim}>—</span> : NUM(r.visitantes)}</td>,
    <td key="conversao" className="fenix-num" style={{ ...ctr.tdNum, ...(opts.enc ? ctr.tdEnc : {}) }}>{r.conversao == null ? <span style={ctr.dim}>—</span> : ctrPct(r.conversao)}</td>,
    <td key="epc" className="fenix-num" style={{ ...ctr.tdNum, ...(opts.enc ? ctr.tdEnc : r.epc < 0 ? { color: 'var(--negative)' } : {}) }}>{r.epc == null ? <span style={ctr.dim}>—</span> : ctrUsd2(r.epc)}</td>,
    <td key="bruto" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes, ...ctr.divRes }}>{ctrMoney(r.bruto, 'var(--fg-3)')}</td>,
    <td key="comissao" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes }}>{ctrMoney(r.comissao, 'var(--fg-3)')}</td>,
    <td key="taxes" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes }}>{ctrMoney(r.taxes, 'var(--fg-3)')}</td>,
    <td key="devolvido" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes }}>{ctrMoney(r.devolvido, 'var(--fg-3)')}</td>,
    <td key="liquido" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes, fontWeight: 700 }}>{ctrMoney(r.liquido, 'var(--fg-3)')}</td>,
    <td key="pct" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes }}>{opts.semPct ? '—' : ctrPct(r.pct)}</td>,
    <td key="pctAcum" className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdRes }}>{opts.semPct ? '—' : ctrPct(r.pctAcum)}</td>,
  ];

  return (
    <div style={{ position: 'relative' }}>
      <div style={ctr.header}>
        <div>
          <div style={ctr.eye}>Afiliados</div>
          <h1 style={ctr.title}>Carteira de afiliados</h1>
        </div>
        <div style={ctr.filterRow}>
          <PeriodSelector period={period} setPeriod={withLoad(setPeriod)} customRange={customRange} setCustomRange={withLoad(setCustomRange)} />
          <Dropdown icon="cart" label="Produto" value={product} onChange={handleProductChange} options={[
            { key: 'todos', label: 'Todos' },
            ...sortedFamilies.map(f => ({ key: f.name, label: f.name })),
          ]} />
          {product !== 'todos' && (
            <Dropdown icon="layers" label="Funil" value={funnel} onChange={withLoad(setFunnel)} options={[
              { key: 'todos', label: 'Todas variações' },
              ...variants.map(v => ({ key: v, label: (v.startsWith(product) ? v.slice(product.length).replace(/^\s*[|\-]\s*/, '').trim() : v) || v })),
            ]} />
          )}
          <PlatformDropdown value={platform} onChange={withLoad(setPlatform)}/>
          <Dropdown icon="layers" label="Reembolso" value={refundBy} onChange={withLoad(setRefundBy)} options={REFUND_BY_OPTIONS} />
        </div>
      </div>

      <div style={ctr.note}>
        Líquido na régua do Master Affiliates da Buygoods: bruto − comissão − taxes − devolvido
        (chargebacks + refunds). Bruto, comissão e taxes pela data da venda; devolvido pela régua
        do filtro Reembolso. Sem frete e sem pedidos de teste. Não desconta custo de produto — a
        margem completa está na DRE.
      </div>
      <RefundByNote refundBy={refundBy} />
      {err && (
        <div style={ctr.notice}><Icon name="alert" size={15}/> Falha ao atualizar — mostrando o último dado carregado.</div>
      )}

      <div style={{ ...ctr.kpiRow, gridTemplateColumns: 'repeat(5,minmax(0,1fr))' }}>
        <DotKpi eye="Afiliados ativos" color="var(--fenix-blue)" value={NUM(k.ativos)} sub="com venda no período"/>
        <DotKpi eye="Líquido total" color="var(--fenix-orange)" value={USD(k.liquido)} sub={`bruto ${USDk(k.bruto)}`}/>
        <DotKpi eye="Top 10" color="var(--positive)" value={ctrPct(k.top10Pct)} sub="do líquido total"/>
        <DotKpi eye="80% do líquido" color="var(--fenix-navy)" value={k.top80N ? `${NUM(k.top80N)} afiliados` : '—'} sub="fazem 80% do líquido"/>
        <DotKpi eye="No negativo" color="var(--negative)" value={NUM(k.negativosN)} sub={`${USD(k.negativosLiquido)} de líquido`} subColor="var(--negative)"/>
      </div>

      <div style={ctr.toolRow}>
        <div style={ctr.search}>
          <Icon name="search" size={15} color="var(--fg-3)"/>
          <input style={ctr.searchInput} placeholder="Buscar afiliado…" value={busca} onChange={e => setBusca(e.target.value)}/>
        </div>
        <span style={ctr.toolLabel}>Meses mostram</span>
        <Segmented value={metricaMes} onChange={setMetricaMes} options={[
          { key: 'liquido', label: 'Líquido' }, { key: 'bruto', label: 'Bruto' },
        ]}/>
        {comCarteira && (
          <>
            <Dropdown icon="users" label="Responsável" value={fResp} onChange={setFResp} options={[
              { key: 'todos', label: 'Todos' }, { key: 'nenhum', label: 'Sem responsável' },
              ...estado.responsaveis.map(x => ({ key: String(x.id), label: x.nome })),
            ]}/>
            <Dropdown icon="target" label="Status" value={fStatus} onChange={setFStatus}
              options={[{ key: 'todos', label: 'Todos' }, ...estado.status]}/>
            <Dropdown icon="calendar" label="Contato" value={fContato} onChange={setFContato} options={[
              { key: 'todos', label: 'Todos' }, { key: 'atrasado', label: `Sem contato há ${lim.alerta}+ dias` },
            ]}/>
          </>
        )}
        {estado?.podeEditar && estado?.disponivel && (
          <>
            <input ref={arquivoRef} type="file" accept=".xls,.xlsx" style={{ display: 'none' }} onChange={importarTrafego}/>
            <button style={{ ...ctr.btnSec, display: 'inline-flex', alignItems: 'center', gap: 6, ...(importando ? { opacity: .6, cursor: 'wait' } : {}) }}
                    disabled={importando} onClick={() => arquivoRef.current && arquivoRef.current.click()}
                    title="Master Affiliates de um mês (ou o consolidado com a aba Base). Reimportar um mês substitui o mês.">
              <Icon name="download" size={14} style={{ transform: 'rotate(180deg)' }}/>
              {importando ? 'Importando…' : 'Importar export da Buygoods'}
            </button>
          </>
        )}
        <span style={ctr.count}>{NUM(linhas.length)} afiliados</span>
      </div>
      {importMsg && (
        <div style={{ ...ctr.notice, ...(importMsg.ok ? { color: 'var(--positive)', background: 'var(--positive-bg)' } : { color: 'var(--negative)', background: 'var(--negative-bg)' }) }}>
          {!importMsg.ok && <Icon name="alert" size={15}/>} {importMsg.texto}
          <button style={{ ...ctr.fechar, marginLeft: 'auto', padding: 0 }} onClick={() => setImportMsg(null)} title="Fechar" aria-label="Fechar"><Icon name="x" size={14}/></button>
        </div>
      )}
      <div style={{ ...ctr.foot, marginTop: 0, marginBottom: 8 }}>
        {trafegoAviso}
      </div>

      <div style={ctr.wrap}>
        <div style={ctr.scroll}>
          <table style={ctr.table}>
            <thead>
              <tr>
                <th colSpan={2} style={{ ...ctr.thGrupo, ...ctr.thFixGrupo }}>Acompanhamento</th>
                {nCar > 0 && <th colSpan={nCar} style={ctr.thGrupo}/>}
                {meses.length > 0 && <th colSpan={meses.length} style={{ ...ctr.thGrupo, ...ctr.thGrupoSep }}>{rotuloMeses}</th>}
                <th colSpan={CTR_COLS_PERIODO.length} style={{ ...ctr.thGrupo, ...ctr.thGrupoSep }}>Período</th>
                <th colSpan={CTR_COLS_RESULT.length} style={{ ...ctr.thGrupo, ...ctr.divRes }}>Resultado do período</th>
              </tr>
              <tr>
                <th style={{ ...ctr.th, ...ctr.th2, ...ctr.fixPos, ...ctr.thFix, textAlign: 'right', cursor: 'default' }}>#</th>
                {th('afiliado', 'Afiliado', true, { ...ctr.th2, ...ctr.fixNome, ...ctr.thFix })}
                {comCarteira && ['Responsável', 'Status', 'Último contato', 'Observação'].map(l => (
                  <th key={l} style={{ ...ctr.th, ...ctr.th2, textAlign: 'left', cursor: 'default' }}>{l}</th>
                ))}
                {meses.map(m => th(`mes:${m.mes}`, ctrMesLabel(m.mes) + (m.parcial ? '*' : ''), false,
                  { ...ctr.th2, ...(m.mes === ultimoMes ? ctr.thUlt : {}) }))}
                {CTR_COLS_PERIODO.map(c => th(c.key, c.label, false, ctr.th2))}
                {CTR_COLS_RESULT.map((c, i) => th(c.key, c.label, false, { ...ctr.th2, color: 'var(--fg-4)', ...(i === 0 ? ctr.divRes : {}) }))}
              </tr>
            </thead>
            <tbody>
              {visiveis.map((r, idx) => {
                const chave = ctrChave(r.plataforma, r.afiliado);
                const aberta = aberto === chave;
                const enc = itens[chave]?.status === 'encerrado';
                const fundo = enc ? 'var(--gray-50)' : '#fff';
                return (
                  <React.Fragment key={chave}>
                    {/* fundo sempre opaco no <tr>: as 2 colunas fixas herdam (hover/aberta inclusos) */}
                    <tr style={{ ...ctr.tr, background: aberta ? 'var(--fenix-cream-soft, #FBF4EE)' : fundo }}
                        onClick={() => setAberto(aberta ? null : chave)}
                        onMouseEnter={e => { if (!aberta) e.currentTarget.style.background = enc ? 'var(--gray-100)' : 'var(--gray-50)'; }}
                        onMouseLeave={e => { if (!aberta) e.currentTarget.style.background = fundo; }}>
                      <td className="fenix-num" style={{ ...ctr.tdNum, color: 'var(--fg-3)', ...ctr.fixPos }}>{idx + 1}</td>
                      <td style={{ ...ctr.tdName, ...ctr.fixNome, ...(enc ? ctr.tdEnc : {}) }} title={r.afiliado}>
                        <span style={{ ...ctr.ellip, maxWidth: CTR_W_NOME - 24 }}>
                          <span style={{ ...ctr.chev, transform: aberta ? 'rotate(90deg)' : 'none' }}>›</span>
                          {r.afiliado}
                          {platform === 'todas' && PLATFORM[r.plataforma] && (
                            <span style={{ ...ctr.plat, color: PLATFORM[r.plataforma].color }}>{PLATFORM[r.plataforma].label}</span>
                          )}
                        </span>
                      </td>
                      {celCarteira(r)}
                      {celulas(r, { enc })}
                    </tr>
                    {aberta && (
                      <tr key={`${chave}|__link`} style={ctr.trSub}>
                        <td colSpan={nCols} style={{ ...ctr.tdSub, paddingLeft: 34 }}>
                          <button style={{ ...ctr.linkBtn, marginTop: 0, position: 'sticky', left: 34 }} onClick={e => { e.stopPropagation(); ctrIrAfiliado(r); }}>
                            Abrir página do afiliado →
                          </button>
                        </td>
                      </tr>
                    )}
                    {aberta && r.familias.map(f => (
                      <tr key={`${chave}|${f.familia}`} style={ctr.trSub}>
                        <td style={{ ...ctr.tdSub, ...ctr.fixPos }}/>
                        <td style={{ ...ctr.tdSub, paddingLeft: 34, ...ctr.fixNome }} title={f.familia}>
                          <span style={{ ...ctr.ellip, maxWidth: CTR_W_NOME - 46 }}>{f.familia}</span>
                        </td>
                        {nCar > 0 && <td colSpan={nCar} style={ctr.tdSub}/>}
                        {meses.length > 0 && <td colSpan={meses.length} style={ctr.tdSub}/>}
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, color: ctrRefundCor(f.refundRate) }}>{ctrPct(f.refundRate)}</td>
                        <td colSpan={CTR_COLS_PERIODO.length - 1} style={ctr.tdSub}/>
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, ...ctr.tdRes, ...ctr.divRes }}>{ctrMoney(f.bruto, 'var(--fg-3)')}</td>
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, ...ctr.tdRes }}>{ctrMoney(f.comissao, 'var(--fg-3)')}</td>
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, ...ctr.tdRes }}>{ctrMoney(f.taxes, 'var(--fg-3)')}</td>
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, ...ctr.tdRes }}>{ctrMoney(f.devolvido, 'var(--fg-3)')}</td>
                        <td className="fenix-num" style={{ ...ctr.tdNum, ...ctr.tdSub, ...ctr.tdRes, fontWeight: 700 }}>{ctrMoney(f.liquido, 'var(--fg-3)')}</td>
                        <td colSpan={2} style={ctr.tdSub}/>
                      </tr>
                    ))}
                  </React.Fragment>
                );
              })}
              {linhas.length === 0 && (
                <tr><td colSpan={nCols} style={ctr.empty}>Nenhum afiliado com venda no filtro.</td></tr>
              )}
              {!todos && linhas.length > CTR_LIMITE && (
                <tr><td colSpan={nCols} style={ctr.more}>
                  <button style={ctr.moreBtn} onClick={() => setTodos(true)}>
                    Mostrar todos os {NUM(linhas.length)} afiliados
                  </button>
                </td></tr>
              )}
              {data.foraDoRanking.length > 0 && (
                <tr><td colSpan={nCols} style={ctr.band}>Fora do ranking — não são afiliados</td></tr>
              )}
              {data.foraDoRanking.map(r => (
                <tr key={`fora|${ctrChave(r.plataforma, r.afiliado)}`} style={{ color: 'var(--fg-3)', background: '#fff' }}>
                  <td style={{ ...ctr.tdNum, ...ctr.fixPos }}/>
                  <td style={{ ...ctr.tdName, color: 'var(--fg-3)', ...ctr.fixNome }} title={r.afiliado}>
                    <span style={{ ...ctr.ellip, maxWidth: CTR_W_NOME - 24 }}>{r.afiliado}</span>
                  </td>
                  {nCar > 0 && <td colSpan={nCar} style={ctr.tdNum}/>}
                  {celulas(r, { semPct: true })}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
      {mesesComDado > meses.length && (
        <div style={ctr.foot}>
          O mês a mês mostra só os últimos {meses.length} meses com movimento; o Resultado do período cobre o período inteiro.
        </div>
      )}
      {meses.some(m => m.parcial) && (
        <div style={ctr.foot}>* mês parcial: o período não cobre o mês inteiro.</div>
      )}
      {meses.length > 0 && (
        <div style={ctr.foot}>
          Cor de cada mês: variação do ritmo diário contra o mês anterior do mesmo afiliado (verde sobe, vermelho cai; abaixo de 2% sem cor).
        </div>
      )}

      {painel && estado && (() => {
        const linha = data.rows.find(r => ctrChave(r.plataforma, r.afiliado) === painel);
        return linha ? (
          <CtrPainel linha={linha} item={itens[painel]} info={estado}
            onClose={() => setPainel(null)} onSalvo={recarregarEstado}/>
        ) : null;
      })()}

      <FullLoadingOverlay show={fullLoading.show && !!data} conn="connecting"/>
    </div>
  );
};

const ctr = {
  header: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 12, flexWrap: 'wrap' },
  eye: { font: 'var(--text-label)', letterSpacing: '.05em', textTransform: 'uppercase', color: 'var(--fg-3)' },
  title: { font: '800 22px/1.1 var(--font-sans)', color: 'var(--fg-1)', letterSpacing: '-.01em', marginTop: 4 },
  filterRow: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
  note: { font: 'var(--text-body-sm)', color: 'var(--fg-3)', marginBottom: 12, maxWidth: 980 },
  notice: { display: 'flex', alignItems: 'center', gap: 6, font: '600 12px/1.4 var(--font-sans)', color: 'var(--warning)', background: 'var(--warning-bg)', padding: '10px 16px', borderRadius: 'var(--r-md)', marginBottom: 12 },
  offNote: { textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-3)', marginTop: -40 },
  kpiRow: { display: 'grid', gap: 14, marginBottom: 16 },
  toolRow: { display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 12 },
  toolLabel: { font: 'var(--text-caption)', color: 'var(--fg-3)', marginLeft: 8 },
  count: { font: 'var(--text-caption)', color: 'var(--fg-3)', marginLeft: 'auto' },
  search: { display: 'flex', alignItems: 'center', gap: 8, border: '1px solid var(--border-2)', borderRadius: 'var(--r-md)', padding: '8px 12px', width: 240, background: '#fff' },
  searchInput: { border: 0, outline: 0, font: 'var(--text-body-sm)', color: 'var(--fg-1)', flex: 1, background: 'transparent' },
  wrap: { background: '#fff', border: '1px solid var(--border-1)', borderRadius: 'var(--r-lg)', boxShadow: 'var(--shadow-sm)', overflow: 'hidden' },
  scroll: { maxHeight: 640, overflow: 'auto' },
  table: { width: '100%', borderCollapse: 'collapse' },
  th: { font: '700 10.5px/1 var(--font-sans)', letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--fg-3)', padding: '12px 12px', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border-2)', whiteSpace: 'nowrap', userSelect: 'none', cursor: 'pointer', position: 'sticky', top: 0, zIndex: 2 },
  th2: { top: 28 },
  // Colunas fixas na rolagem horizontal: corpo z1 (fundo herdado do <tr>), cabeçalho acima de tudo.
  fixPos: { position: 'sticky', left: 0, zIndex: 1, background: 'inherit', width: CTR_W_POS, minWidth: CTR_W_POS, maxWidth: CTR_W_POS, boxSizing: 'border-box' },
  fixNome: { position: 'sticky', left: CTR_W_POS, zIndex: 1, background: 'inherit', width: CTR_W_NOME, minWidth: CTR_W_NOME, maxWidth: CTR_W_NOME, boxSizing: 'border-box' },
  thFix: { zIndex: 4, background: 'var(--bg-sunken)' },
  thFixGrupo: { position: 'sticky', left: 0, zIndex: 5, width: CTR_W_POS + CTR_W_NOME, minWidth: CTR_W_POS + CTR_W_NOME, maxWidth: CTR_W_POS + CTR_W_NOME },
  thGrupo: { font: '700 9.5px/1 var(--font-sans)', letterSpacing: '.06em', textTransform: 'uppercase', color: 'var(--fg-4)', height: 28, boxSizing: 'border-box', padding: '0 12px', textAlign: 'left', background: 'var(--bg-sunken)', borderBottom: '1px solid var(--border-1)', whiteSpace: 'nowrap', userSelect: 'none', cursor: 'default', position: 'sticky', top: 0, zIndex: 3 },
  thGrupoSep: { borderLeft: '1px solid var(--border-1)' },
  thUlt: { color: 'var(--fg-1)', borderBottom: '2px solid var(--fenix-orange)' },
  tdUlt: { fontWeight: 700, fontSize: 13.5 },
  tdRes: { color: 'var(--fg-3)' },
  tdEnc: { color: 'var(--fg-3)' },
  divRes: { borderLeft: '2px solid var(--border-2)' },
  linkBtn: { border: 0, background: 'transparent', padding: 0, marginTop: 6, color: 'var(--fenix-orange)', font: '700 12px/1.2 var(--font-sans)', cursor: 'pointer' },
  tr: { cursor: 'pointer' },
  tdName: { padding: '10px 12px', borderBottom: '1px solid var(--border-1)', font: '600 13px/1.3 var(--font-sans)', color: 'var(--fg-1)', whiteSpace: 'nowrap' },
  tdNum: { padding: '10px 12px', borderBottom: '1px solid var(--border-1)', font: '500 12.5px/1.3 var(--font-num)', fontFamily: 'var(--font-num)', fontVariantNumeric: 'tabular-nums', color: 'var(--fg-1)', textAlign: 'right', whiteSpace: 'nowrap' },
  trSub: { background: 'var(--fenix-cream-soft, #FBF4EE)' },
  tdSub: { padding: '7px 12px', borderBottom: '1px solid var(--border-1)', font: '500 12px/1.3 var(--font-sans)', color: 'var(--fg-2)' },
  chev: { display: 'inline-block', width: 14, color: 'var(--fg-4)', transition: 'transform .15s' },
  plat: { font: '700 10px/1 var(--font-sans)', marginLeft: 8, textTransform: 'uppercase', letterSpacing: '.04em' },
  dim: { color: 'var(--fg-4)' },
  band: { padding: '9px 12px', background: 'var(--bg-sunken)', font: '700 10.5px/1 var(--font-sans)', letterSpacing: '.04em', textTransform: 'uppercase', color: 'var(--fg-3)', borderBottom: '1px solid var(--border-2)' },
  empty: { padding: '34px 22px', textAlign: 'center', font: 'var(--text-body-sm)', color: 'var(--fg-4)' },
  more: { padding: '12px', textAlign: 'center', borderBottom: '1px solid var(--border-1)' },
  moreBtn: { border: '1px solid var(--border-2)', borderRadius: 'var(--r-md)', padding: '8px 14px', background: '#fff', color: 'var(--fg-2)', font: '600 12px/1 var(--font-sans)', cursor: 'pointer' },
  foot: { font: 'var(--text-caption)', color: 'var(--fg-3)', marginTop: 8 },
  tdCar: { padding: '10px 12px', borderBottom: '1px solid var(--border-1)', font: '500 12.5px/1.3 var(--font-sans)', whiteSpace: 'nowrap' },
  tdObs: { color: 'var(--fg-2)' },
  ellip: { display: 'block', maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
  chip: { font: '700 11px/1 var(--font-sans)', padding: '4px 9px', borderRadius: 'var(--r-pill)', whiteSpace: 'nowrap' },
  sop: { font: '700 10px/1 var(--font-sans)', padding: '3px 6px', borderRadius: 'var(--r-xs)', background: 'var(--fenix-orange-100)', color: 'var(--fenix-orange-600)', marginLeft: 6, letterSpacing: '.04em' },
  painelFundo: { position: 'fixed', inset: 0, background: 'rgba(1,27,54,.18)', zIndex: 60, display: 'flex', justifyContent: 'flex-end' },
  painel: { width: 440, maxWidth: '100%', height: '100%', background: 'var(--bg-surface)', boxShadow: 'var(--shadow-lg)', padding: '22px 24px', overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 14, outline: 'none' },
  painelHead: { display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },
  painelNome: { font: '800 19px/1.2 var(--font-sans)', color: 'var(--fg-1)', marginTop: 4 },
  painelSub: { font: 'var(--text-caption)', color: 'var(--fg-3)', marginTop: 4 },
  painelCampos: { display: 'flex', gap: 8, flexWrap: 'wrap' },
  painelLeitura: { font: 'var(--text-body-sm)', color: 'var(--fg-2)' },
  fechar: { border: 0, background: 'transparent', color: 'var(--fg-3)', cursor: 'pointer', padding: 4 },
  sopNote: { font: '600 12px/1.4 var(--font-sans)', color: 'var(--fenix-orange-600)', background: 'var(--fenix-orange-100)', padding: '8px 12px', borderRadius: 'var(--r-md)' },
  form: { display: 'flex', flexDirection: 'column', gap: 8 },
  textarea: { font: 'var(--text-body-sm)', color: 'var(--fg-1)', border: '1px solid var(--border-2)', borderRadius: 'var(--r-md)', padding: '10px 12px', resize: 'vertical', outline: 0 },
  formBtns: { display: 'flex', gap: 8 },
  formAjuda: { font: 'var(--text-caption)', color: 'var(--fg-3)' },
  btnPri: { border: 0, borderRadius: 'var(--r-md)', padding: '9px 14px', background: 'var(--fenix-orange)', color: '#fff', font: '700 12px/1 var(--font-sans)', cursor: 'pointer' },
  btnSec: { border: '1px solid var(--border-2)', borderRadius: 'var(--r-md)', padding: '9px 14px', background: '#fff', color: 'var(--fg-2)', font: '600 12px/1 var(--font-sans)', cursor: 'pointer' },
  timeline: { display: 'flex', flexDirection: 'column', gap: 10 },
  evento: { borderLeft: '2px solid var(--border-1)', paddingLeft: 12 },
  eventoHead: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 3 },
  eventoTipo: { font: '700 10.5px/1 var(--font-sans)', padding: '3px 7px', borderRadius: 'var(--r-xs)', background: 'var(--bg-sunken)', color: 'var(--fg-2)' },
  eventoTxt: { font: 'var(--text-body-sm)', color: 'var(--fg-1)', whiteSpace: 'pre-wrap' },
  dimTxt: { font: 'var(--text-caption)', color: 'var(--fg-3)' },
};

window.CarteiraAfiliadosView = CarteiraAfiliadosView;
