/* =========================================================================
   Sistema KeS — Controle de Clientes
   Implementação do design "Sistema KeS.dc.html" (Claude Design)
   React + Babel locais · dados salvos no navegador (localStorage)
   ========================================================================= */

/* ---------- utilidades ---------- */

const S_CACHE = {};
function sx(css) {
  if (!css) return undefined;
  if (S_CACHE[css]) return S_CACHE[css];
  const o = {};
  String(css).split(';').forEach(d => {
    const i = d.indexOf(':'); if (i < 0) return;
    const k = d.slice(0, i).trim(); if (!k) return;
    o[k.replace(/-([a-z])/g, (m, c) => c.toUpperCase())] = d.slice(i + 1).trim();
  });
  S_CACHE[css] = o; return o;
}

const MESES = ['Janeiro','Fevereiro','Março','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'];
const MESES_AB = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez'];
const HOJE = new Date();
const pad2 = n => String(n).padStart(2, '0');
const todayStr = () => pad2(HOJE.getDate()) + '/' + pad2(HOJE.getMonth() + 1) + '/' + HOJE.getFullYear();
const MES_NOME = MESES[HOJE.getMonth()];
const MES_LOWER = MES_NOME.toLowerCase();
const MES_AB = MESES_AB[HOJE.getMonth()].toLowerCase();
const MM = pad2(HOJE.getMonth() + 1);
const ANO = HOJE.getFullYear();
const LAST6 = (() => { const a = []; for (let i = 5; i >= 0; i--) { const d = new Date(ANO, HOJE.getMonth() - i, 1); a.push(MESES_AB[d.getMonth()]); } return a; })();

/* ---------- persistência (localStorage) ---------- */

const STORE_KEY = 'kes_sistema_clientes_v1';
const SESSION_KEY = 'kes_clientes_sessao_v1';
const PERSIST = ['clients','charges','expenses','receipts','categories','nfs','nfSeq','openingBalance','flow','users','settings','empresa'];
function loadSaved() {
  try { const raw = localStorage.getItem(STORE_KEY); return raw ? JSON.parse(raw) : null; }
  catch (e) { return null; }
}
function normalizeFlow(fl) {
  return LAST6.map(lbl => (fl || []).find(f => f.month === lbl) || { month: lbl, inC: 0, inV: 0, outC: 0, outV: 0 });
}

/* ---------- login / permissões ---------- */

async function hashSenha(s) {
  const b = await crypto.subtle.digest('SHA-256', new TextEncoder().encode('kes:' + s));
  return [...new Uint8Array(b)].map(x => x.toString(16).padStart(2, '0')).join('');
}
const PERM_TELAS = [['dashboard', 'Visão Geral'], ['financeiro', 'Financeiro'], ['clientes', 'Clientes'], ['contratos', 'Contratos'], ['notas', 'Notas Fiscais']];
const PERM_ACOES = [['editar', 'Pode editar (cadastrar, pagar, emitir)'], ['exportar', 'Pode exportar dados'], ['admin', 'Administrador(a) — acesso total + usuários']];
const PERMS_TODAS = { admin: true, dashboard: true, financeiro: true, clientes: true, contratos: true, notas: true, editar: true, exportar: true };
const PERMS_PADRAO = { admin: false, dashboard: true, financeiro: false, clientes: true, contratos: false, notas: false, editar: false, exportar: false };

/* ---------- datas / exportação ---------- */

function parseBR(d) { const p = String(d || '').split('/'); if (p.length !== 3 || !+p[2]) return null; return new Date(+p[2], (+p[1]) - 1, +p[0]); }
function isoParaBR(v) { return v ? v.split('-').reverse().join('/') : ''; }
function brParaIso(v) { const p = String(v || '').split('/'); return p.length === 3 ? p[2] + '-' + pad2(p[1]) + '-' + pad2(p[0]) : ''; }
function isoDaqui(dias) { const d = new Date(HOJE.getFullYear(), HOJE.getMonth(), HOJE.getDate() + dias); return d.getFullYear() + '-' + pad2(d.getMonth() + 1) + '-' + pad2(d.getDate()); }
function baixarArquivo(nome, conteudo, mime) {
  const blob = new Blob([conteudo], { type: mime });
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob); a.download = nome;
  document.body.appendChild(a); a.click(); a.remove();
  setTimeout(() => URL.revokeObjectURL(a.href), 2000);
}
function csvGerar(headers, rows) {
  const esc = v => { v = String(v == null ? '' : v); return /[;"\n]/.test(v) ? '"' + v.replace(/"/g, '""') + '"' : v; };
  return '﻿' + [headers.join(';'), ...rows.map(r => r.map(esc).join(';'))].join('\r\n');
}
const numBR = n => Number(n).toFixed(2).replace('.', ',');

/* ---------- validações e máscaras (exigências Asaas / Autentique / NFS-e) ---------- */

function validaCPF(v) {
  const c = String(v).replace(/\D/g, '');
  if (c.length !== 11 || /^(\d)\1+$/.test(c)) return false;
  let s = 0; for (let i = 0; i < 9; i++) s += +c[i] * (10 - i);
  let d = (s * 10) % 11 % 10; if (d !== +c[9]) return false;
  s = 0; for (let i = 0; i < 10; i++) s += +c[i] * (11 - i);
  d = (s * 10) % 11 % 10; return d === +c[10];
}
function validaCNPJ(v) {
  const c = String(v).replace(/\D/g, '');
  if (c.length !== 14 || /^(\d)\1+$/.test(c)) return false;
  const calc = (base) => { let m = base.length - 7, s = 0; for (let i = 0; i < base.length; i++) { s += +base[i] * m--; if (m < 2) m = 9; } const r = s % 11; return r < 2 ? 0 : 11 - r; };
  return calc(c.slice(0, 12)) === +c[12] && calc(c.slice(0, 13)) === +c[13];
}
const validaEmail = v => /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(v).trim());
const mCPF = v => { const d = String(v).replace(/\D/g, '').slice(0, 11); return d.replace(/^(\d{3})(\d)/, '$1.$2').replace(/^(\d{3})\.(\d{3})(\d)/, '$1.$2.$3').replace(/^(\d{3})\.(\d{3})\.(\d{3})(\d)/, '$1.$2.$3-$4'); };
const mCNPJ = v => { const d = String(v).replace(/\D/g, '').slice(0, 14); return d.replace(/^(\d{2})(\d)/, '$1.$2').replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3').replace(/^(\d{2})\.(\d{3})\.(\d{3})(\d)/, '$1.$2.$3/$4').replace(/\/(\d{4})(\d)/, '/$1-$2'); };
const mCEP = v => String(v).replace(/\D/g, '').slice(0, 8).replace(/^(\d{5})(\d)/, '$1-$2');
const mFone = v => { const d = String(v).replace(/\D/g, '').slice(0, 11); if (d.length <= 10) return d.replace(/^(\d{2})(\d)/, '($1) $2').replace(/(\d{4})(\d{1,4})$/, '$1-$2'); return d.replace(/^(\d{2})(\d)/, '($1) $2').replace(/^(\(\d{2}\) \d)(\d{4})(\d{4})$/, '$1 $2-$3'); };

/* dados da empresa (prestador — usados na NFS-e e nos contratos) */
const EMPRESA_PADRAO = {
  razao: 'KeS Assessoria de Marketing', cnpj: '', im: '', regime: 'MEI',
  codTrib: '', aliquota: '', cep: '', endereco: '', numero: '', bairro: '',
  cidade: 'Caxias do Sul', uf: 'RS', codIbge: '4305108', email: 'kesassessoria@kesassessoria.com.br',
};

/* ---------- dados iniciais (exemplo — substitua pelo uso real) ---------- */

const FLOW_DEMO = [
  { inC: 1, inV: 2800, outC: 0, outV: 0 },
  { inC: 1, inV: 1900, outC: 1, outV: 1500 },
  { inC: 1, inV: 2400, outC: 0, outV: 0 },
  { inC: 1, inV: 1200, outC: 1, outV: 1800 },
  { inC: 0, inV: 0,    outC: 1, outV: 2200 },
  { inC: 1, inV: 6500, outC: 0, outV: 0 },
];
const SEED = {
  nfSeq: 142,
  openingBalance: 12500,
  flow: LAST6.map((m, i) => ({ month: m, ...FLOW_DEMO[i] })),
  categories: [
    { id: 1, name: 'Mensalidade de clientes', type: 'entrada' },
    { id: 2, name: 'Projetos avulsos', type: 'entrada' },
    { id: 3, name: 'Outros recebimentos', type: 'entrada' },
    { id: 4, name: 'Pró-labore / Folha', type: 'saida' },
    { id: 5, name: 'Tráfego / Mídia paga', type: 'saida' },
    { id: 6, name: 'Ferramentas / Software', type: 'saida' },
    { id: 7, name: 'Terceirizados', type: 'saida' },
    { id: 8, name: 'Impostos', type: 'saida' },
    { id: 9, name: 'Escritório', type: 'saida' },
  ],
  expenses: [
    { id: 1, desc: 'Pró-labore — Ketherly', catId: 4, value: 4000, date: '05/06/2026', status: 'pago', supplier: '—', recur: 'mensal' },
    { id: 2, desc: 'Meta Ads — campanhas de clientes', catId: 5, value: 3200, date: '10/06/2026', status: 'pago', supplier: 'Meta', recur: 'mensal' },
    { id: 3, desc: 'Assinaturas (Canva, Adobe)', catId: 6, value: 280, date: '03/06/2026', status: 'pago', supplier: 'Adobe', recur: 'mensal' },
    { id: 4, desc: 'Designer freelancer', catId: 7, value: 1500, date: '08/06/2026', status: 'pago', supplier: 'Freelancer', recur: 'unica' },
    { id: 5, desc: 'Honorários contábeis', catId: 7, value: 600, date: '07/06/2026', status: 'pago', supplier: 'Contabilize', recur: 'mensal' },
    { id: 6, desc: 'Internet + telefonia', catId: 9, value: 320, date: '12/06/2026', status: 'pago', supplier: 'Vivo', recur: 'mensal' },
    { id: 7, desc: 'Simples Nacional (DAS)', catId: 8, value: 1850, date: '20/06/2026', status: 'pendente', supplier: 'Receita Federal', recur: 'mensal' },
  ],
  receipts: [
    { id: 1, desc: 'Projeto site institucional — Clínica Odonto', catId: 2, value: 3500, date: '06/06/2026', status: 'pago', recur: 'unica' },
  ],
  clients: [
    { id: 1, name: 'Studio Bloom Arquitetura', type: 'PJ', doc: 'CNPJ 18.402.115/0001-22', service: 'Gestão de Redes Sociais', monthly: 2800, payDay: 5, entry: '12/01/2026', status: 'ativo', contract: 'assinado', sentAt: '10/01/2026' },
    { id: 2, name: 'Dra. Marina Couto', type: 'PF', doc: 'CPF 042.118.330-55', service: 'Tráfego Pago', monthly: 1900, payDay: 10, entry: '03/02/2026', status: 'ativo', contract: 'assinado', sentAt: '01/02/2026' },
    { id: 3, name: 'Mercado Vista Verde', type: 'PJ', doc: 'CNPJ 27.115.880/0001-09', service: 'Gestão + Tráfego Pago', monthly: 4200, payDay: 15, entry: '20/11/2025', status: 'ativo', contract: 'assinado', sentAt: '18/11/2025' },
    { id: 4, name: 'Academia Pulse', type: 'PJ', doc: 'CNPJ 33.901.244/0001-71', service: 'Gestão de Redes Sociais', monthly: 2400, payDay: 8, entry: '05/03/2026', status: 'pendente', contract: 'assinado', sentAt: '02/03/2026' },
    { id: 5, name: 'João Pedro Salgados', type: 'PF', doc: 'CPF 118.554.220-08', service: 'Consultoria de Marketing', monthly: 1200, payDay: 20, entry: '14/04/2026', status: 'ativo', contract: 'assinado', sentAt: '11/04/2026' },
    { id: 6, name: 'Clínica Odonto Sorriso', type: 'PJ', doc: 'CNPJ 41.220.337/0001-50', service: 'Gestão de Redes Sociais', monthly: 3100, payDay: 5, entry: '08/12/2025', status: 'ativo', contract: 'assinado', sentAt: '05/12/2025' },
    { id: 7, name: 'Bella Moda Boutique', type: 'PJ', doc: 'CNPJ 22.880.114/0001-33', service: 'Tráfego Pago', monthly: 2200, payDay: 25, entry: '02/09/2025', exit: '28/05/2026', status: 'inativo', contract: 'encerrado', sentAt: '30/08/2025' },
    { id: 8, name: 'Construtora Horizonte', type: 'PJ', doc: 'CNPJ 09.337.652/0001-18', service: 'Estratégia Completa', monthly: 6500, payDay: 1, entry: '25/06/2026', status: 'ativo', contract: 'aguardando', sentAt: '24/06/2026' },
  ],
  charges: [
    { id: 1, clientId: 1, status: 'pago', nfId: '000142', value: 2800, paidDate: '03/06/2026' },
    { id: 2, clientId: 2, status: 'pago', nfId: null, value: 1900, paidDate: '05/06/2026' },
    { id: 3, clientId: 3, status: 'pago', nfId: '000141', value: 4200, paidDate: '02/06/2026' },
    { id: 6, clientId: 6, status: 'pago', nfId: null, value: 3100, paidDate: '06/06/2026' },
    { id: 5, clientId: 5, status: 'pendente', nfId: null },
    { id: 8, clientId: 8, status: 'pendente', nfId: null },
    { id: 4, clientId: 4, status: 'atrasado', nfId: null },
  ],
  nfs: [
    { number: '000142', clientId: 1, date: '03/06/2026', value: 2800 },
    { number: '000141', clientId: 3, date: '02/06/2026', value: 4200 },
    { number: '000138', clientId: 6, date: '06/05/2026', value: 3100 },
    { number: '000135', clientId: 2, date: '04/05/2026', value: 1900 },
  ],
};

/* ---------- ícones (SVG) ---------- */

const IcDash = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="3" width="7" height="9"/><rect x="14" y="3" width="7" height="5"/><rect x="14" y="12" width="7" height="9"/><rect x="3" y="16" width="7" height="5"/></svg>);
const IcFin = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="12" y1="20" x2="12" y2="10"/><line x1="18" y1="20" x2="18" y2="4"/><line x1="6" y1="20" x2="6" y2="16"/></svg>);
const IcCli = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>);
const IcCon = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><path d="m9 15 2 2 4-4"/></svg>);
const IcNf = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M4 2v20l2-1 2 1 2-1 2 1 2-1 2 1 2-1 2 1V2l-2 1-2-1-2 1-2-1-2 1-2-1-2 1Z"/><path d="M8 7h8M8 11h8M8 15h5"/></svg>);
const IcPlus = (p) => (<svg width={p.s || 16} height={p.s || 16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>);
const IcCheck = (p) => (<svg width={p.s || 14} height={p.s || 14} viewBox="0 0 24 24" fill="none" stroke={p.c || 'currentColor'} strokeWidth={p.w || 2.5} strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>);
const IcX = (p) => (<svg width={p.s || 17} height={p.s || 17} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>);
const IcTrash = (p) => (<svg width={p.s || 15} height={p.s || 15} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>);
const IcTrashSimple = (p) => (<svg width={p.s || 15} height={p.s || 15} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/></svg>);
const IcDoc = (p) => (<svg width={p.s || 15} height={p.s || 15} viewBox="0 0 24 24" fill="none" stroke={p.c || 'currentColor'} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>);
const IcSearch = () => (<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>);
const IcMoney = () => (<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 1v22"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>);
const IcPen = () => (<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#E03C31" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>);
const IcShield = () => (<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#2BB673" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10Z"/><path d="m9 12 2 2 4-4"/></svg>);
const IcDown = () => (<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><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"/></svg>);
const IcUpload = () => (<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>);
const IcUsers = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/><circle cx="19" cy="11" r="0"/></svg>);
const IcPlug = () => (<svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8v5a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V8Z"/></svg>);
const IcLogout = () => (<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>);
const IcLock = () => (<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>);
const IcEdit = (p) => (<svg width={p.s || 14} height={p.s || 14} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/></svg>);

/* estilos base repetidos */
const IN = 'background:#0E1318;border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:11px 13px;color:#fff;font-size:13.5px;outline:none';
const IN13 = 'background:#0E1318;border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:11px 13px;color:#fff;font-size:13px;outline:none';
const LBL = 'font-size:12px;font-weight:600;color:#B7C0CB';
const COL = 'display:flex;flex-direction:column;gap:7px';
const COL6 = 'display:flex;flex-direction:column;gap:6px';
const CARD = 'background:#131B23;border:1px solid rgba(255,255,255,.08);border-radius:16px';
const OVERLAY = 'position:fixed;inset:0;background:rgba(8,12,16,.7);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center;padding:24px;animation:kesFade .2s ease both';
const BTN_CANCEL = 'flex:1;background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:11px;font-size:13px;font-weight:600;cursor:pointer';

/* =========================================================================
   Componente principal
   ========================================================================= */

class App extends React.Component {
  constructor(props) {
    super(props);
    const saved = loadSaved();
    this.state = {
      screen: 'dashboard',
      clientFilter: 'todos',
      wizard: false,
      step: 1,
      sendState: 'idle',
      form: this.blankForm(),
      nf: null,
      toast: null,
      finTab: 'fluxo',
      cob: null,
      despesa: null,
      receb: null,
      saldoModal: null,
      catForm: { name: '', type: 'saida' },
      search: '',
      nfSeq: saved ? saved.nfSeq : SEED.nfSeq,
      openingBalance: saved ? saved.openingBalance : SEED.openingBalance,
      flow: saved ? normalizeFlow(saved.flow) : SEED.flow,
      categories: saved ? saved.categories : SEED.categories,
      expenses: saved ? saved.expenses : SEED.expenses,
      receipts: saved ? saved.receipts : SEED.receipts,
      clients: saved ? saved.clients : SEED.clients,
      charges: saved ? saved.charges : SEED.charges,
      nfs: saved ? saved.nfs : SEED.nfs,
      users: (saved && saved.users) ? saved.users : [],
      settings: (saved && saved.settings) ? saved.settings : { autentiqueToken: '', asaasToken: '', nfseObs: '' },
      session: localStorage.getItem(SESSION_KEY) || null,
      period: { from: '', to: '' },
      exportOpen: false,
      authForm: { nome: 'Ketherly Schmidt', user: '', pass: '', pass2: '', erro: '' },
      userModal: null,
      apiStatus: undefined,   // undefined = verificando · null = servidor sem API · objeto = online
      intForm: { asaasChave: '', asaasAmbiente: 'sandbox', autentiqueToken: '', nfsePfx: '', nfseSenha: '', nfseAmbiente: 'homologacao' },
      intTest: {},
      empresa: (saved && saved.empresa) ? { ...EMPRESA_PADRAO, ...saved.empresa } : { ...EMPRESA_PADRAO },
      wizErro: null,
      clienteModal: null,
      confirmModal: null,
      recuperar: null,           // tela "esqueci minha senha" (versão publicada)
      pagamento: null,   // { chargeId, mode:'total'|'parcial', valor }
      pagarDesp: null,   // { expenseId, mode:'total'|'parcial', valor }
      bdStatus: 'verificando',   // verificando | ok | offline
      phpPrimeiroAcesso: false,  // versão publicada: banco ainda sem conta criada
      phpUser: null,             // quem sou eu, segundo o servidor (versão publicada)
    };
    this._dadosProntos = false;  // só grava no servidor depois da sincronização inicial
    this._modo = null;           // node | php | offline
  }

  setEmp(k, v) { this.setState(st => ({ empresa: { ...st.empresa, [k]: v } })); }

  componentDidMount() { this.iniciar(); }

  /* =====================================================================
     Banco de dados — dois modos, detectados sozinho:
       • 'node' → servidor local (servidor.js + SQLite), no seu computador
       • 'php'  → versão publicada (api.php + Neon), em financeiro.kesassessoria.com.br
     ===================================================================== */
  async iniciar() {
    // 1) servidor local? (confere que respondeu JSON de verdade, não uma página de erro)
    try {
      const r = await fetch('/api/status');
      if (r.ok) {
        const s = await r.json();
        if (s && s.servidor) {
          this._modo = 'node';
          await this.sincronizarDados();
          this.carregarStatus();
          return;
        }
      }
    } catch (e) { /* sem servidor Node — tenta a versão publicada */ }

    // 2) versão publicada (PHP + Neon)
    try {
      const r = await fetch('api.php?acao=status');
      if (r.ok) {
        const s = await r.json();
        this._modo = 'php';
        this.setState({ apiStatus: null, phpPrimeiroAcesso: !!s.primeiroAcesso, bdStatus: 'ok' });
        if (s.logado && s.usuario) {
          localStorage.setItem(SESSION_KEY, String(s.usuario.id));
          this.setState({ session: String(s.usuario.id), phpUser: s.usuario }, () => this.sincronizarDados());
        } else {
          localStorage.removeItem(SESSION_KEY);
          this.setState({ session: null });
        }
        return;
      }
    } catch (e) { /* nem um nem outro */ }

    // 3) sem backend — segue só no navegador
    this._modo = 'offline';
    this._dadosProntos = true;
    this.setState({ bdStatus: 'offline', apiStatus: null });
  }

  urlDados() { return this._modo === 'php' ? 'api.php?acao=dados' : '/api/dados'; }

  async sincronizarDados() {
    try {
      const r = await fetch(this.urlDados());
      if (r.status === 200) {
        // o banco do servidor é a fonte oficial — substitui o cache do navegador
        const dados = await r.json();
        const novo = {};
        PERSIST.forEach(k => { if (dados[k] !== undefined) novo[k] = dados[k]; });
        if (novo.flow) novo.flow = normalizeFlow(novo.flow);
        if (novo.empresa) novo.empresa = { ...EMPRESA_PADRAO, ...novo.empresa };
        this._dadosProntos = true;
        this.setState({ ...novo, bdStatus: 'ok' });
      } else if (r.status === 401) {
        // publicado e ainda sem login — a tela de login cuida
        this._dadosProntos = false;
        this.setState({ bdStatus: 'ok', session: null });
      } else {
        // servidor ok, banco ainda vazio → grava o estado atual (migra o que existia no navegador)
        this._dadosProntos = true;
        this.setState({ bdStatus: 'ok' }, () => this.salvarNoServidor(true));
      }
    } catch (e) {
      // servidor simples (sem API) ou fechado → segue só no navegador
      this._dadosProntos = true;
      this.setState({ bdStatus: 'offline' });
    }
  }

  salvarNoServidor(imediato) {
    if (!this._dadosProntos || this.state.bdStatus !== 'ok') return;
    if (this._modo === 'php' && !this.state.session) return;   // só grava logado
    clearTimeout(this._svTimer);
    this._svTimer = setTimeout(async () => {
      const out = {};
      PERSIST.forEach(k => { out[k] = this.state[k]; });
      try {
        const r = await fetch(this.urlDados(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(out) });
        if (!r.ok) throw new Error('gravação recusada');
      } catch (e) {
        if (this.state.bdStatus !== 'offline') this.setState({ bdStatus: 'offline' });
      }
    }, imediato ? 0 : 800);
  }

  /* ---------- servidor de integrações ---------- */
  async carregarStatus() {
    try {
      const r = await fetch('/api/status');
      if (!r.ok) throw new Error('sem api');
      const s = await r.json();
      this.setState(st => ({ apiStatus: s, intForm: { ...st.intForm, asaasAmbiente: s.asaas.ambiente, nfseAmbiente: s.nfse.ambiente, nfsePfx: s.nfse.pfx || '' } }));
    } catch (e) { this.setState({ apiStatus: null }); }
  }
  setInt(k, v) { this.setState(st => ({ intForm: { ...st.intForm, [k]: v } })); }
  async salvarIntegracao(campos) {
    try {
      const r = await fetch('/api/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(campos) });
      const j = await r.json();
      this.flash(j.ok ? 'Configuração salva no servidor (só neste computador)' : ('Erro: ' + j.mensagem));
      if (j.ok) { this.setState(st => ({ intForm: { ...st.intForm, asaasChave: '', autentiqueToken: '', nfseSenha: '' } })); this.carregarStatus(); }
    } catch (e) { this.flash('Servidor de integrações não respondeu'); }
  }
  async testarIntegracao(qual) {
    this.setState(st => ({ intTest: { ...st.intTest, [qual]: { ok: null, mensagem: 'Testando…' } } }));
    try {
      const r = await fetch('/api/' + qual + '/testar');
      const j = await r.json();
      this.setState(st => ({ intTest: { ...st.intTest, [qual]: j } }));
    } catch (e) {
      this.setState(st => ({ intTest: { ...st.intTest, [qual]: { ok: false, mensagem: 'Servidor de integrações não respondeu.' } } }));
    }
  }

  /* ---------- sessão / permissões ---------- */
  currentUser() {
    const u = this.state.users.find(x => String(x.id) === String(this.state.session));
    if (u) return u;
    // versão publicada: logo após o login o servidor já devolveu quem sou, antes dos dados chegarem
    if (this._modo === 'php' && this.state.session && this.state.phpUser) return this.state.phpUser;
    return null;
  }
  can(k) { const u = this.currentUser(); if (!u || !u.perms) return false; if (u.perms.admin) return true; return !!u.perms[k]; }
  firstAllowed(u) { const order = ['dashboard', 'financeiro', 'clientes', 'contratos', 'notas']; if (u.perms.admin) return 'dashboard'; return order.find(k => u.perms[k]) || 'dashboard'; }
  setAuth(k, v) { this.setState(st => ({ authForm: { ...st.authForm, [k]: v, erro: '' } })); }

  async criarAdmin() {
    const f = this.state.authForm;
    if (!f.user.trim()) { this.setState(st => ({ authForm: { ...st.authForm, erro: 'Informe o nome de usuário.' } })); return; }
    const minimo = this._modo === 'php' ? 6 : 4;
    if (f.pass.length < minimo) { this.setState(st => ({ authForm: { ...st.authForm, erro: 'A senha precisa ter pelo menos ' + minimo + ' caracteres.' } })); return; }
    if (f.pass !== f.pass2) { this.setState(st => ({ authForm: { ...st.authForm, erro: 'As senhas não conferem.' } })); return; }

    if (this._modo === 'php') {   // versão publicada: a conta é criada no servidor
      try {
        const r = await fetch('api.php?acao=criar-admin', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nome: f.nome.trim(), user: f.user.trim().toLowerCase(), senha: f.pass }) });
        const j = await r.json();
        if (!r.ok || !j.ok) { this.setState(st => ({ authForm: { ...st.authForm, erro: j.mensagem || 'Não consegui criar a conta.' } })); return; }
        localStorage.setItem(SESSION_KEY, String(j.usuario.id));
        this.setState({ session: String(j.usuario.id), phpUser: j.usuario, phpPrimeiroAcesso: false, authForm: { nome: '', user: '', pass: '', pass2: '', erro: '' } }, () => this.sincronizarDados());
        this.flash('Conta da administradora criada');
      } catch (e) {
        this.setState(st => ({ authForm: { ...st.authForm, erro: 'Servidor não respondeu.' } }));
      }
      return;
    }

    const h = await hashSenha(f.pass);
    const u = { id: 1, nome: f.nome.trim() || 'Administradora', user: f.user.trim().toLowerCase(), hash: h, cargo: 'Administradora', perms: { ...PERMS_TODAS } };
    localStorage.setItem(SESSION_KEY, '1');
    this.setState({ users: [u], session: '1', authForm: { nome: '', user: '', pass: '', pass2: '', erro: '' } });
    this.flash('Conta da administradora criada');
  }

  async entrar() {
    const f = this.state.authForm;

    if (this._modo === 'php') {   // versão publicada: quem confere a senha é o servidor
      try {
        const r = await fetch('api.php?acao=login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user: f.user.trim().toLowerCase(), senha: f.pass }) });
        const j = await r.json();
        if (!r.ok || !j.ok) { this.setState(st => ({ authForm: { ...st.authForm, erro: j.mensagem || 'Não consegui entrar.' } })); return; }
        localStorage.setItem(SESSION_KEY, String(j.usuario.id));
        this.setState({ session: String(j.usuario.id), phpUser: j.usuario, screen: this.firstAllowed(j.usuario), authForm: { nome: '', user: '', pass: '', pass2: '', erro: '' } }, () => this.sincronizarDados());
      } catch (e) {
        this.setState(st => ({ authForm: { ...st.authForm, erro: 'Servidor não respondeu.' } }));
      }
      return;
    }

    const u = this.state.users.find(x => x.user === f.user.trim().toLowerCase());
    if (!u) { this.setState(st => ({ authForm: { ...st.authForm, erro: 'Usuário não encontrado.' } })); return; }
    const h = await hashSenha(f.pass);
    if (h !== u.hash) { this.setState(st => ({ authForm: { ...st.authForm, erro: 'Senha incorreta.' } })); return; }
    localStorage.setItem(SESSION_KEY, String(u.id));
    this.setState({ session: String(u.id), screen: this.firstAllowed(u), authForm: { nome: '', user: '', pass: '', pass2: '', erro: '' } });
  }

  sair() {
    if (this._modo === 'php') { try { fetch('api.php?acao=sair', { method: 'POST' }); } catch (e) {} }
    localStorage.removeItem(SESSION_KEY);
    this.setState({ session: null, recuperar: null, authForm: { nome: '', user: '', pass: '', pass2: '', erro: '' } });
  }

  /* ---------- esqueci minha senha (versão publicada) ---------- */
  abrirRecuperar() { this.setState({ recuperar: { user: this.state.authForm.user || '', codigo: '', senha: '', senha2: '', erro: '', ok: '' } }); }
  fecharRecuperar() { this.setState({ recuperar: null }); }
  setRec(k, v) { this.setState(st => ({ recuperar: { ...st.recuperar, [k]: v, erro: '', ok: '' } })); }
  async redefinirSenha() {
    const r = this.state.recuperar;
    if (!r.user.trim()) { this.setRec('erro', 'Informe o usuário.'); return; }
    if (r.senha.length < 6) { this.setRec('erro', 'A nova senha precisa ter pelo menos 6 caracteres.'); return; }
    if (r.senha !== r.senha2) { this.setRec('erro', 'As senhas não conferem.'); return; }
    try {
      const resp = await fetch('api.php?acao=recuperar', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user: r.user.trim().toLowerCase(), codigo: r.codigo.trim(), senha: r.senha }) });
      const j = await resp.json();
      if (!resp.ok || !j.ok) { this.setRec('erro', j.mensagem || 'Não consegui redefinir.'); return; }
      this.setState(st => ({ recuperar: null, authForm: { ...st.authForm, user: r.user.trim().toLowerCase(), pass: '', erro: '' } }));
      this.flash('Senha redefinida! Agora é só entrar.');
    } catch (e) { this.setRec('erro', 'Servidor não respondeu.'); }
  }

  /* ---------- gestão de usuários ---------- */
  abrirUsuario(u) {
    this.setState({ userModal: u ? { id: u.id, nome: u.nome, user: u.user, cargo: u.cargo || '', pass: '', perms: { ...u.perms }, erro: '' } : { id: null, nome: '', user: '', cargo: '', pass: '', perms: { ...PERMS_PADRAO }, erro: '' } });
  }
  setUM(k, v) { this.setState(st => ({ userModal: { ...st.userModal, [k]: v, erro: '' } })); }
  setUMPerm(k, v) { this.setState(st => ({ userModal: { ...st.userModal, perms: { ...st.userModal.perms, [k]: v }, erro: '' } })); }
  async salvarUsuario() {
    const m = this.state.userModal;
    const user = m.user.trim().toLowerCase();
    if (!m.nome.trim() || !user) { this.setUM('erro', 'Preencha nome e usuário.'); return; }
    if (this.state.users.some(u => u.user === user && u.id !== m.id)) { this.setUM('erro', 'Já existe um usuário com esse login.'); return; }
    if (m.id == null && m.pass.length < 4) { this.setUM('erro', 'Defina uma senha com pelo menos 4 caracteres.'); return; }
    if (m.pass && m.pass.length < 4) { this.setUM('erro', 'A senha precisa ter pelo menos 4 caracteres.'); return; }
    const hash = m.pass ? await hashSenha(m.pass) : null;
    this.setState(st => {
      if (m.id == null) {
        const id = Math.max(0, ...st.users.map(u => u.id)) + 1;
        return { userModal: null, users: [...st.users, { id, nome: m.nome.trim(), user, cargo: m.cargo.trim(), hash, perms: m.perms }] };
      }
      return { userModal: null, users: st.users.map(u => u.id === m.id ? { ...u, nome: m.nome.trim(), user, cargo: m.cargo.trim(), hash: hash || u.hash, perms: m.perms } : u) };
    });
    this.flash(m.id == null ? 'Usuário criado' : 'Usuário atualizado');
  }
  removerUsuario(id) {
    const me = this.currentUser();
    if (me && me.id === id) { this.flash('Você não pode excluir o próprio usuário'); return; }
    const admins = this.state.users.filter(u => u.perms.admin);
    const alvo = this.state.users.find(u => u.id === id);
    if (alvo && alvo.perms.admin && admins.length <= 1) { this.flash('É preciso manter pelo menos um administrador'); return; }
    this.setState(st => ({ users: st.users.filter(u => u.id !== id) }));
    this.flash('Usuário removido');
  }

  /* ---------- filtro por data ---------- */
  setPeriod(k, v) { this.setState(st => ({ period: { ...st.period, [k]: v } })); }
  inPeriod(dstr) {
    const { from, to } = this.state.period;
    if (!from && !to) return true;
    const d = parseBR(dstr);
    if (!d) return true;
    if (from && d < new Date(from + 'T00:00:00')) return false;
    if (to && d > new Date(to + 'T23:59:59')) return false;
    return true;
  }
  temFiltro() { return !!(this.state.period.from || this.state.period.to); }
  periodLabel() {
    const { from, to } = this.state.period;
    if (!from && !to) return MES_NOME + '/' + ANO;
    return (from ? isoParaBR(from) : 'início') + ' a ' + (to ? isoParaBR(to) : 'hoje');
  }

  /* ---------- exportação ---------- */
  setSetting(k, v) { this.setState(st => ({ settings: { ...st.settings, [k]: v } })); }
  exportar(tipo) {
    const st = this.state;
    const cv = (c) => c.value != null ? c.value : (this.client(c.clientId).monthly || 0);
    const catName = (id) => (st.categories.find(c => c.id === id) || {}).name || '—';
    const hoje = todayStr().replace(/\//g, '-');
    let headers, rows, nome;
    if (tipo === 'clientes') {
      nome = 'clientes'; headers = ['Nome', 'Tipo', 'Documento', 'Serviço', 'Mensalidade (R$)', 'Dia venc.', 'Entrada', 'Status', 'Contrato'];
      rows = st.clients.filter(c => this.inPeriod(c.entry)).map(c => [c.name, c.type, c.doc, c.service, numBR(c.monthly), c.payDay, c.entry, c.status, c.contract]);
    } else if (tipo === 'cobrancas') {
      nome = 'cobrancas'; headers = ['Cliente', 'Descrição', 'Valor (R$)', 'Pago (R$)', 'Falta (R$)', 'Status', 'Pago em', 'NFS-e'];
      rows = st.charges.filter(c => this.chargeStatus(c) !== 'pago' || this.inPeriod(c.paidDate)).map(c => { const cl = this.client(c.clientId); return [cl.name || '—', c.desc || cl.service || '—', numBR(cv(c)), numBR(this.chargePaid(c)), numBR(this.chargeRemaining(c)), this.chargeStatus(c), c.paidDate || '—', c.nfId || '—']; });
    } else if (tipo === 'despesas') {
      nome = 'despesas'; headers = ['Descrição', 'Categoria', 'Fornecedor', 'Data', 'Valor (R$)', 'Status', 'Recorrência'];
      rows = st.expenses.filter(e => this.inPeriod(e.date)).map(e => [e.desc, catName(e.catId), e.supplier, e.date, numBR(e.value), e.status, e.recur]);
    } else if (tipo === 'recebimentos') {
      nome = 'recebimentos'; headers = ['Descrição', 'Categoria', 'Data', 'Valor (R$)', 'Recebido (R$)', 'Falta (R$)', 'Recorrência'];
      rows = st.receipts.filter(r => this.inPeriod(r.date)).map(r => [r.desc, catName(r.catId), r.date, numBR(r.value), numBR(this.receiptPaid(r)), numBR(this.receiptRemaining(r)), r.recur]);
    } else if (tipo === 'extrato') {
      nome = 'extrato'; headers = ['Data', 'Descrição', 'Categoria', 'Tipo', 'Valor (R$)', 'Saldo (R$)'];
      rows = this.ledger().filter(l => this.inPeriod(l.date)).map(l => [l.date, l.desc, l.cat, l.type, (l.type === 'entrada' ? '' : '-') + numBR(l.value), numBR(l.balance)]);
    } else if (tipo === 'notas') {
      nome = 'notas-fiscais'; headers = ['Número', 'Tomador', 'Emissão', 'Valor (R$)'];
      rows = st.nfs.filter(n => this.inPeriod(n.date)).map(n => [n.number, this.client(n.clientId).name || '—', n.date, numBR(n.value)]);
    } else if (tipo === 'backup') {
      const out = {};
      PERSIST.forEach(k => { out[k] = st[k]; });
      baixarArquivo('backup-controle-clientes-' + hoje + '.json', JSON.stringify(out, null, 2), 'application/json');
      this.flash('Backup completo exportado');
      return;
    } else { return; }
    baixarArquivo(nome + '-' + hoje + '.csv', csvGerar(headers, rows), 'text/csv;charset=utf-8');
    this.flash('Arquivo ' + nome + '.csv exportado (' + rows.length + ' linhas)');
  }

  componentDidUpdate() {
    // navegador vira só um cache local — o banco oficial fica no servidor (dados/dados.json)
    const out = {};
    PERSIST.forEach(k => { out[k] = this.state[k]; });
    try { localStorage.setItem(STORE_KEY, JSON.stringify(out)); } catch (e) {}
    this.salvarNoServidor();
  }

  /* ---------- helpers ---------- */
  fmt(n) { return 'R$ ' + Number(n).toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
  initials(name) { const p = String(name).replace(/^(Dra?\.|Dr\.)\s*/, '').trim().split(/\s+/); return ((p[0] || '')[0] || '') + ((p[1] || '')[0] || ''); }
  client(id) { return this.state.clients.find(c => c.id === id) || {}; }
  parseV(v) { return parseFloat(String(v).replace(/\./g, '').replace(',', '.')) || 0; }

  /* ---------- navegação / formulário ---------- */
  go(s) { this.setState({ screen: s }); }
  blankForm() {
    return { type: 'PJ', name: '', doc: '', phone: '', email: '', cep: '', endereco: '', numero: '', bairro: '', cidade: 'Caxias do Sul', uf: 'RS', service: 'Gestão de Redes Sociais', monthly: '', payment: 'PIX', payDay: '5', warranty: 'Sem fidelidade', template: 'gestao', signerEmail: '', whats: true, sendNow: true, extraSigners: [], cobrancas: [{ id: 1, desc: 'Mensalidade', value: '', recur: 'Mensal recorrente', due: '' }] };
  }
  startNew() { if (!this.can('editar')) return; this.setState({ wizard: true, step: 1, sendState: 'idle', form: this.blankForm() }); }
  closeWizard() { this.setState({ wizard: false }); }
  setF(k, v) { this.setState(s => ({ form: { ...s.form, [k]: v } })); }

  addCobranca() { this.setState(st => { const id = Math.max(0, ...st.form.cobrancas.map(c => c.id)) + 1; return { form: { ...st.form, cobrancas: [...st.form.cobrancas, { id, desc: '', value: '', recur: 'Parcela única', due: '' }] } }; }); }
  removeCobranca(id) { this.setState(st => ({ form: { ...st.form, cobrancas: st.form.cobrancas.length > 1 ? st.form.cobrancas.filter(c => c.id !== id) : st.form.cobrancas } })); }
  updateCobranca(id, k, v) { this.setState(st => ({ form: { ...st.form, cobrancas: st.form.cobrancas.map(c => c.id === id ? { ...c, [k]: v } : c) } })); }
  setSendNow(v) { this.setF('sendNow', v); }
  addSigner() { this.setState(st => { const id = Math.max(0, ...st.form.extraSigners.map(s => s.id || 0)) + 1; return { form: { ...st.form, extraSigners: [...st.form.extraSigners, { id, name: '', email: '' }] } }; }); }
  removeSigner(id) { this.setState(st => ({ form: { ...st.form, extraSigners: st.form.extraSigners.filter(s => s.id !== id) } })); }
  updateSigner(id, k, v) { this.setState(st => ({ form: { ...st.form, extraSigners: st.form.extraSigners.map(s => s.id === id ? { ...s, [k]: v } : s) } })); }
  sendLater(id) { if (!this.can('editar')) return; const today = todayStr(); this.setState(st => ({ clients: st.clients.map(c => c.id === id ? { ...c, contract: 'aguardando', sentAt: today } : c) })); this.flash('Contrato enviado pelo Autentique'); }

  /* ---------- financeiro ---------- */
  setFinTab(t) { this.setState({ finTab: t }); }
  openCob(edit) {
    if (!this.can('editar')) return;
    if (edit) {
      const recur = edit.recur || 'unica';
      this.setState({ cob: { stage: 'form', id: edit.id, clientId: String(edit.clientId), desc: edit.desc || '', value: String(edit.value != null ? edit.value : (this.client(edit.clientId).monthly || '')).replace('.', ','), due: edit.dueDate ? brParaIso(edit.dueDate) : isoDaqui(3), payment: edit.payment || 'PIX', recur, installments: String(edit.installments || '12'), status: edit.status } });
    } else {
      this.setState({ cob: { stage: 'form', clientId: '', desc: 'Mensalidade', value: '', due: isoDaqui(3), payment: 'PIX', recur: 'mensal', installments: '12' } });
    }
  }
  closeCob() { this.setState({ cob: null }); }
  setCob(k, v) { this.setState(st => ({ cob: { ...st.cob, [k]: v } })); }
  createCob() {
    const c = this.state.cob; if (!c.clientId || this.parseV(c.value) <= 0 || !c.due) return;
    if (c.id) { // edição de cobrança existente — salva direto
      this.setState(st => ({ cob: null, charges: st.charges.map(ch => ch.id === c.id ? { ...ch, clientId: parseInt(c.clientId), value: this.parseV(c.value), desc: c.desc, due: parseInt(c.due.split('-')[2]), dueDate: isoParaBR(c.due), payment: c.payment, recur: c.recur } : ch) }));
      this.flash('Cobrança atualizada'); return;
    }
    this.setState(st => ({ cob: { ...st.cob, stage: 'loading' } }));
    setTimeout(() => {
      const parcelas = c.recur === 'parcelada' ? (parseInt(c.installments) || 1) : null;
      const cdesc = c.recur === 'parcelada' ? (c.desc + ' (parcela 1/' + parcelas + ')') : c.desc;
      this.setState(st => ({ cob: { ...st.cob, stage: 'done' }, charges: [...st.charges, { id: Date.now(), clientId: parseInt(c.clientId), status: 'pendente', nfId: null, value: this.parseV(c.value), desc: cdesc, due: parseInt(c.due.split('-')[2]), dueDate: isoParaBR(c.due), payment: c.payment, recur: c.recur, installments: parcelas }] }));
      this.flash(c.recur === 'unica' ? 'Cobrança única criada no Asaas' : c.recur === 'parcelada' ? ('Cobrança em ' + parcelas + 'x criada no Asaas') : 'Assinatura mensal criada no Asaas');
    }, 1500);
  }
  deleteCharge(id) { if (!this.can('editar')) return; this.setState(st => ({ charges: st.charges.filter(c => c.id !== id) })); this.flash('Cobrança excluída'); }
  openDespesa(edit) { if (!this.can('editar')) return; this.setState({ despesa: edit ? { id: edit.id, desc: edit.desc, catId: String(edit.catId), value: String(edit.value).replace('.', ','), date: edit.date, supplier: edit.supplier === '—' ? '' : edit.supplier, status: edit.status, recur: edit.recur } : { desc: '', catId: '4', value: '', date: todayStr(), supplier: '', status: 'pago', recur: 'unica' } }); }
  closeDespesa() { this.setState({ despesa: null }); }
  setDespesa(k, v) { this.setState(st => ({ despesa: { ...st.despesa, [k]: v } })); }
  saveDespesa() {
    const d = this.state.despesa;
    if (this.parseV(d.value) <= 0) return;
    if (d.id) {
      this.setState(st => ({ despesa: null, expenses: st.expenses.map(e => e.id === d.id ? { ...e, desc: d.desc || 'Despesa', catId: parseInt(d.catId), value: this.parseV(d.value), date: d.date, status: d.status, supplier: d.supplier || '—', recur: d.recur } : e) }));
      this.flash('Despesa atualizada'); return;
    }
    this.setState(st => ({ despesa: null, expenses: [{ id: Date.now(), desc: d.desc || 'Despesa', catId: parseInt(d.catId), value: this.parseV(d.value), date: d.date, status: d.status, supplier: d.supplier || '—', recur: d.recur }, ...st.expenses] }));
    this.flash(d.recur === 'mensal' ? 'Despesa recorrente registrada' : (d.status === 'pago' ? 'Despesa registrada — saída lançada no fluxo' : 'Despesa registrada'));
  }
  deleteExpense(id) { if (!this.can('editar')) return; this.setState(st => ({ expenses: st.expenses.filter(e => e.id !== id) })); this.flash('Despesa excluída'); }
  openPagarDespesa(e) { if (!this.can('editar')) return; this.setState({ pagarDesp: { expenseId: e.id, mode: 'total', valor: numBR(this.expenseRemaining(e)) } }); }
  closePagarDesp() { this.setState({ pagarDesp: null }); }
  setPagarDesp(k, v) { this.setState(st => ({ pagarDesp: { ...st.pagarDesp, [k]: v } })); }
  registrarPagamentoDespesa() {
    const pg = this.state.pagarDesp; if (!pg) return;
    const e = this.state.expenses.find(x => x.id === pg.expenseId); if (!e) return;
    const total = e.value;
    const jaPago = this.expensePaid(e);
    let novoPago;
    if (pg.mode === 'total') novoPago = total;
    else { const add = this.parseV(pg.valor); if (add <= 0) return; novoPago = Math.min(jaPago + add, total); }
    const quitado = novoPago >= total;
    this.setState(st => ({ pagarDesp: null, expenses: st.expenses.map(x => x.id === pg.expenseId ? { ...x, paid: novoPago, status: quitado ? 'pago' : 'pendente', date: todayStr() } : x) }));
    this.flash(quitado ? 'Despesa paga — saída lançada no fluxo' : ('Pagamento parcial de ' + this.fmt(novoPago - jaPago) + ' — falta ' + this.fmt(total - novoPago)));
  }
  markExpensePaid(id) { const e = this.state.expenses.find(x => x.id === id); if (e) this.openPagarDespesa(e); }
  openReceb(edit) {
    if (!this.can('editar')) return;
    if (edit) {
      const pago = edit.paid != null ? edit.paid : edit.value;
      const parcial = pago < edit.value;
      this.setState({ receb: { id: edit.id, desc: edit.desc, catId: String(edit.catId), value: String(edit.value).replace('.', ','), recebido: String(pago).replace('.', ','), parcial, date: edit.date, recur: edit.recur } });
    } else {
      this.setState({ receb: { desc: '', catId: '2', value: '', recebido: '', parcial: false, date: todayStr(), recur: 'unica' } });
    }
  }
  closeReceb() { this.setState({ receb: null }); }
  setReceb(k, v) { this.setState(st => ({ receb: { ...st.receb, [k]: v } })); }
  saveReceb() {
    const r = this.state.receb;
    const total = this.parseV(r.value);
    if (total <= 0) return;
    const pago = r.parcial ? Math.min(this.parseV(r.recebido), total) : total;
    const status = pago >= total ? 'pago' : (pago > 0 ? 'parcial' : 'pendente');
    if (r.id) {
      this.setState(st => ({ receb: null, receipts: st.receipts.map(x => x.id === r.id ? { ...x, desc: r.desc || 'Recebimento', catId: parseInt(r.catId), value: total, paid: pago, status, date: r.date, recur: r.recur } : x) }));
      this.flash('Recebimento atualizado'); return;
    }
    this.setState(st => ({ receb: null, receipts: [{ id: Date.now(), desc: r.desc || 'Recebimento', catId: parseInt(r.catId), value: total, paid: pago, status, date: r.date, recur: r.recur }, ...st.receipts] }));
    this.flash(r.parcial && pago < total ? ('Recebimento parcial de ' + this.fmt(pago) + ' — falta ' + this.fmt(total - pago)) : 'Recebimento registrado — entrada lançada no fluxo');
  }
  completarReceb(id) { if (!this.can('editar')) return; this.setState(st => ({ receipts: st.receipts.map(r => r.id === id ? { ...r, paid: r.value, status: 'pago' } : r) })); this.flash('Restante recebido — recebimento quitado'); }
  deleteReceipt(id) { if (!this.can('editar')) return; this.setState(st => ({ receipts: st.receipts.filter(r => r.id !== id) })); this.flash('Recebimento excluído'); }
  deleteNf(number) { if (!this.can('editar')) return; this.setState(st => ({ nfs: st.nfs.filter(n => n.number !== number), charges: st.charges.map(c => c.nfId === number ? { ...c, nfId: null } : c) })); this.flash('Nota fiscal excluída'); }
  /* ---------- cliente: editar / excluir ---------- */
  openCliente(edit) {
    if (!this.can('editar')) return;
    const docDig = String(edit.doc || '').replace(/\D/g, '');
    this.setState({ clienteModal: { id: edit.id, type: edit.type || 'PJ', name: edit.name || '', doc: docDig, phone: edit.phone || '', email: edit.email || '', cep: edit.cep || '', endereco: edit.endereco || '', numero: edit.numero || '', bairro: edit.bairro || '', cidade: edit.cidade || 'Caxias do Sul', uf: edit.uf || 'RS', service: edit.service || '', monthly: String(edit.monthly != null ? edit.monthly : '').replace('.', ','), payDay: String(edit.payDay || '5'), status: edit.status || 'ativo', erro: '' } });
  }
  setClienteModal(k, v) { this.setState(st => ({ clienteModal: { ...st.clienteModal, [k]: v, erro: '' } })); }
  saveCliente() {
    const m = this.state.clienteModal;
    const isPJ = m.type === 'PJ';
    if (!m.name.trim()) { this.setClienteModal('erro', 'Informe o nome/razão social.'); return; }
    if (m.doc && (isPJ ? !validaCNPJ(m.doc) : !validaCPF(m.doc))) { this.setClienteModal('erro', (isPJ ? 'CNPJ' : 'CPF') + ' inválido.'); return; }
    if (m.email && !validaEmail(m.email)) { this.setClienteModal('erro', 'E-mail inválido.'); return; }
    this.setState(st => ({ clienteModal: null, clients: st.clients.map(c => c.id === m.id ? { ...c, type: m.type, name: m.name.trim(), doc: (isPJ ? 'CNPJ ' : 'CPF ') + (m.doc || '—'), phone: m.phone, email: m.email, cep: m.cep, endereco: m.endereco, numero: m.numero, bairro: m.bairro, cidade: m.cidade, uf: m.uf, service: m.service, monthly: this.parseV(m.monthly), payDay: parseInt(m.payDay) || 5, status: m.status } : c) }));
    this.flash('Cliente atualizado');
  }
  deleteCliente(id) {
    if (!this.can('editar')) return;
    this.setState(st => ({ clients: st.clients.filter(c => c.id !== id), charges: st.charges.filter(c => c.clientId !== id) }));
    this.flash('Cliente e suas cobranças foram excluídos');
  }
  setCat(k, v) { this.setState(st => ({ catForm: { ...st.catForm, [k]: v } })); }
  addCategory() { if (!this.can('editar')) return; const c = this.state.catForm; if (!c.name.trim()) return; if (c.id) { this.setState(st => ({ catForm: { name: '', type: c.type }, categories: st.categories.map(x => x.id === c.id ? { ...x, name: c.name.trim(), type: c.type } : x) })); this.flash('Categoria atualizada'); return; } this.setState(st => ({ catForm: { name: '', type: c.type }, categories: [...st.categories, { id: Date.now(), name: c.name.trim(), type: c.type }] })); this.flash('Categoria criada'); }
  editCategory(cat) { if (!this.can('editar')) return; this.setState({ catForm: { id: cat.id, name: cat.name, type: cat.type } }); }
  removeCategory(id) { if (!this.can('editar')) return; this.setState(st => ({ categories: st.categories.filter(c => c.id !== id), catForm: st.catForm.id === id ? { name: '', type: st.catForm.type } : st.catForm })); this.flash('Categoria excluída'); }
  openSaldo() { if (!this.can('editar')) return; this.setState({ saldoModal: { value: this.state.openingBalance.toLocaleString('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } }); }
  closeSaldo() { this.setState({ saldoModal: null }); }
  setSaldo(v) { this.setState(st => ({ saldoModal: { ...st.saldoModal, value: v } })); }
  saveSaldo() { const v = parseFloat(String(this.state.saldoModal.value).replace(/\./g, '').replace(',', '.')); if (isNaN(v)) return; this.setState({ openingBalance: v, saldoModal: null }); this.flash('Saldo inicial de caixa ajustado'); }

  /* ---------- wizard ---------- */
  nextStep() {
    const s = this.state.step;
    const f = this.state.form;
    if (s === 1) {
      // exigências do Asaas (name + cpfCnpj) e da NFS-e (documento do tomador)
      const digitos = f.doc.replace(/\D/g, '');
      if (!f.name.trim()) { this.setState({ wizErro: 'Informe ' + (f.type === 'PJ' ? 'a razão social' : 'o nome completo') + ' — obrigatório no Asaas e no contrato.' }); return; }
      if (!digitos) { this.setState({ wizErro: 'Informe o ' + (f.type === 'PJ' ? 'CNPJ' : 'CPF') + ' — o Asaas e a nota fiscal exigem o documento do cliente.' }); return; }
      if (f.type === 'PJ' ? !validaCNPJ(f.doc) : !validaCPF(f.doc)) { this.setState({ wizErro: (f.type === 'PJ' ? 'CNPJ' : 'CPF') + ' inválido — confira os números digitados.' }); return; }
      if (f.email && !validaEmail(f.email)) { this.setState({ wizErro: 'E-mail inválido — confira o endereço digitado.' }); return; }
      this.setState({ step: 2, wizErro: null }); return;
    }
    if (s === 2) {
      const temCobranca = this.parseV(f.monthly) > 0 || f.cobrancas.some(c => this.parseV(c.value) > 0);
      if (!temCobranca) { this.setState({ wizErro: 'Informe o valor mensal ou pelo menos uma cobrança com valor — é o que será criado no Asaas.' }); return; }
      this.setState({ step: 3, wizErro: null }); return;
    }
    if (s === 3) {
      // Autentique envia por e-mail: sugere o e-mail do cadastro como signatário
      this.setState(st => ({ step: 4, wizErro: null, form: { ...st.form, signerEmail: st.form.signerEmail || (validaEmail(st.form.email) ? st.form.email : '') } }));
      return;
    }
    if (s === 4) {
      if (f.sendNow && !validaEmail(f.signerEmail)) { this.setState({ wizErro: 'Informe um e-mail válido para a assinatura — o Autentique envia o contrato por e-mail.' }); return; }
      this.setState({ wizErro: null });
      this.submit();
    }
  }
  prevStep() { if (this.state.step > 1) this.setState(st => ({ step: st.step - 1, wizErro: null })); }
  submit() {
    if (this.state.form.sendNow) { this.setState({ sendState: 'sending' }); setTimeout(() => this.finalize(), 1900); }
    else { this.finalize(); }
  }
  finalize() {
    const f = this.state.form;
    const newId = Math.max(0, ...this.state.clients.map(c => c.id)) + 1;
    const monthlyNum = this.parseV(f.monthly);
    const today = todayStr();
    const newClient = { id: newId, name: f.name || 'Novo Cliente', type: f.type, doc: (f.type === 'PJ' ? 'CNPJ ' : 'CPF ') + (f.doc || '—'), phone: f.phone, email: f.email, cep: f.cep, endereco: f.endereco, numero: f.numero, bairro: f.bairro, cidade: f.cidade, uf: f.uf, service: f.service, monthly: monthlyNum, payDay: parseInt(f.payDay), entry: today, status: 'ativo', contract: f.sendNow ? 'aguardando' : 'rascunho', sentAt: f.sendNow ? today : '—' };
    const cobs = f.cobrancas.filter(c => this.parseV(c.value) > 0);
    const list = cobs.length ? cobs : [{ desc: f.service, value: String(f.monthly), due: '' }];
    const newCharges = list.map((c, i) => ({ id: 1000 + newId * 10 + i, clientId: newId, status: 'pendente', nfId: null, value: this.parseV(c.value), desc: c.desc || f.service, due: parseInt(f.payDay), dueDate: c.due ? isoParaBR(c.due) : null }));
    this.setState(st => ({ sendState: 'done', clients: [newClient, ...st.clients], charges: [...st.charges, ...newCharges], flow: st.flow.map((m, i) => i === st.flow.length - 1 ? { ...m, inC: m.inC + 1, inV: m.inV + monthlyNum } : m) }));
    this.flash(newCharges.length + ' cobrança(s) criada(s) no Asaas');
  }
  goConClose() { this.setState({ wizard: false, screen: 'contratos' }); }

  /* ---------- valores pagos / recebidos (parcial) ---------- */
  cval(c) { return c.value != null ? c.value : (this.client(c.clientId).monthly || 0); }
  chargePaid(c) { return c.paid != null ? c.paid : (c.status === 'pago' ? this.cval(c) : 0); }
  chargeRemaining(c) { return Math.max(0, this.cval(c) - this.chargePaid(c)); }
  chargeStatus(c) { const p = this.chargePaid(c), t = this.cval(c); if (t > 0 && p >= t) return 'pago'; if (p > 0) return 'parcial'; return c.status === 'atrasado' ? 'atrasado' : 'pendente'; }
  receiptPaid(r) { return r.paid != null ? r.paid : r.value; }
  receiptRemaining(r) { return Math.max(0, r.value - this.receiptPaid(r)); }
  expensePaid(e) { return e.paid != null ? e.paid : (e.status === 'pago' ? e.value : 0); }
  expenseRemaining(e) { return Math.max(0, e.value - this.expensePaid(e)); }
  expenseStatus(e) { const p = this.expensePaid(e), t = e.value; if (t > 0 && p >= t) return 'pago'; if (p > 0) return 'parcial'; return 'pendente'; }

  /* ---------- cobranças / NF ---------- */
  openPagamento(charge) {
    if (!this.can('editar')) return;
    const falta = this.chargeRemaining(charge);
    this.setState({ pagamento: { chargeId: charge.id, mode: 'total', valor: numBR(falta).replace(',00', ',00') } });
  }
  closePagamento() { this.setState({ pagamento: null }); }
  setPagamento(k, v) { this.setState(st => ({ pagamento: { ...st.pagamento, [k]: v } })); }
  registrarPagamento() {
    const pg = this.state.pagamento; if (!pg) return;
    const ch = this.state.charges.find(c => c.id === pg.chargeId); if (!ch) return;
    const total = this.cval(ch);
    const jaPago = this.chargePaid(ch);
    let novoPago;
    if (pg.mode === 'total') novoPago = total;
    else { const add = this.parseV(pg.valor); if (add <= 0) return; novoPago = Math.min(jaPago + add, total); }
    const quitado = novoPago >= total;
    this.setState(st => ({ pagamento: null, charges: st.charges.map(c => c.id === pg.chargeId ? { ...c, paid: novoPago, paidDate: todayStr(), status: quitado ? 'pago' : (c.status === 'atrasado' ? 'atrasado' : 'pendente') } : c) }));
    this.flash(quitado ? 'Pagamento total registrado — entrada lançada no fluxo' : ('Pagamento parcial de ' + this.fmt(novoPago - jaPago) + ' — falta ' + this.fmt(total - novoPago)));
  }
  pay(chargeId) { const c = this.state.charges.find(x => x.id === chargeId); if (c) this.openPagamento(c); }
  openNf(chargeId) { if (!this.can('editar')) return; this.setState({ nf: { chargeId, stage: 'confirm' } }); }
  closeNf() { this.setState({ nf: null }); }
  confirmNf() {
    this.setState(st => ({ nf: { ...st.nf, stage: 'loading' } }));
    setTimeout(() => {
      const num = String(this.state.nfSeq + 1).padStart(6, '0');
      this.setState(st => {
        const ch = st.charges.find(c => c.id === st.nf.chargeId);
        const cl = this.client(ch.clientId);
        return {
          nfSeq: st.nfSeq + 1,
          nf: { ...st.nf, stage: 'done', number: num },
          charges: st.charges.map(c => c.id === st.nf.chargeId ? { ...c, nfId: num } : c),
          nfs: [{ number: num, clientId: ch.clientId, date: todayStr(), value: (ch.value != null ? ch.value : cl.monthly) }, ...st.nfs],
        };
      });
    }, 1800);
  }

  flash(msg) { this.setState({ toast: msg }); clearTimeout(this._t); this._t = setTimeout(() => this.setState({ toast: null }), 2600); }
  confirmarExclusao(msg, onYes) { if (!this.can('editar')) return; this.setState({ confirmModal: { msg, onYes } }); }

  navStyle(key) {
    const active = this.state.screen === key;
    return { display: 'flex', alignItems: 'center', gap: '12px', padding: '11px 12px', borderRadius: '10px', border: 'none', cursor: 'pointer', fontSize: '13.5px', fontWeight: active ? '700' : '500', fontFamily: 'inherit', textAlign: 'left', width: '100%', transition: 'all .14s', background: active ? 'rgba(224,60,49,.12)' : 'transparent', color: active ? '#EB6258' : '#B7C0CB', boxShadow: active ? 'inset 2px 0 0 #E03C31' : 'none' };
  }

  /* =====================================================================
     RENDER
     ===================================================================== */

  render() {
    const st = this.state;
    const cv = (c) => c.value != null ? c.value : this.client(c.clientId).monthly;
    this._cv = cv;

    if (st.bdStatus === 'verificando' && st.users.length === 0 && !this._modo) {
      return (
        <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;gap:16px;background:#0E1318')}>
          <div style={sx('width:44px;height:44px;border:3px solid rgba(255,255,255,.12);border-top-color:#E03C31;border-radius:999px;animation:kesSpin .8s linear infinite')}></div>
          <div style={sx('font-size:13px;color:#7C8794')}>Carregando o banco de dados…</div>
        </div>
      );
    }

    const me = this.currentUser();
    if (!me) return this.renderAuth();

    const telas = ['dashboard', 'financeiro', 'clientes', 'contratos', 'notas'];
    this._screen = (telas.includes(st.screen) && !this.can(st.screen)) ? this.firstAllowed(me) : st.screen;

    const pages = {
      dashboard: ['Painel', 'Visão Geral'], financeiro: ['Financeiro', 'Painel Financeiro'],
      clientes: ['Clientes', 'Entrada e Saída de Clientes'], contratos: ['Documentos', 'Contratos · Autentique'], notas: ['Fiscal', 'Notas Fiscais · NFS-e'],
      usuarios: ['Acesso', 'Usuários e Permissões'], integracoes: ['Conexões', 'Integrações']
    };
    const pg = pages[this._screen] || pages.dashboard;

    return (
      <div style={sx('display:flex;height:100vh;width:100%;overflow:hidden;background:#0E1318')}>
        {this.renderSidebar()}

        <main style={sx('flex:1;min-width:0;display:flex;flex-direction:column;overflow:hidden')}>
          <header style={sx('height:64px;flex-shrink:0;border-bottom:1px solid rgba(255,255,255,.08);background:#131B23;display:flex;align-items:center;justify-content:space-between;padding:0 28px;gap:16px')}>
            <div style={sx('min-width:0;display:flex;align-items:center;gap:12px')}>
              <div style={sx('min-width:0')}>
                <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31')}>{pg[0]}</div>
                <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{pg[1]}</div>
              </div>
              {st.apiStatus && st.apiStatus.ambiente === 'teste' && (
                <span title="Este é o banco de testes (controle-clientes.teste.sqlite) — nada aqui afeta a produção" style={sx('flex-shrink:0;font-size:10px;font-weight:800;letter-spacing:.08em;color:#F2A93B;background:rgba(242,169,59,.14);border:1px solid rgba(242,169,59,.4);padding:4px 10px;border-radius:999px;white-space:nowrap')}>AMBIENTE DE TESTE</span>
              )}
            </div>
            <div style={sx('display:flex;align-items:center;gap:12px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end')}>
              {this.can('clientes') && (
                <div style={sx('display:flex;align-items:center;gap:8px;background:#0E1318;border:1px solid rgba(255,255,255,.09);border-radius:999px;padding:8px 14px;color:#7C8794;font-size:13px')}>
                  <IcSearch />
                  <input
                    value={st.search}
                    onChange={e => { const v = e.target.value; this.setState({ search: v, screen: v ? 'clientes' : st.screen }); }}
                    placeholder="Buscar cliente…"
                    style={sx('background:transparent;border:none;outline:none;color:#fff;font-size:13px;width:130px')} />
                </div>
              )}
              <div style={sx('display:flex;align-items:center;gap:6px;background:#0E1318;border:1px solid rgba(255,255,255,.09);border-radius:999px;padding:5px 10px')}>
                <span style={sx('font-size:11px;color:#7C8794;font-weight:600')}>De</span>
                <input type="date" value={st.period.from} onChange={e => this.setPeriod('from', e.target.value)} style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.09);border-radius:8px;padding:5px 7px;color:#fff;font-size:12px;outline:none;color-scheme:dark;font-family:inherit')} />
                <span style={sx('font-size:11px;color:#7C8794;font-weight:600')}>Até</span>
                <input type="date" value={st.period.to} onChange={e => this.setPeriod('to', e.target.value)} style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.09);border-radius:8px;padding:5px 7px;color:#fff;font-size:12px;outline:none;color-scheme:dark;font-family:inherit')} />
                {this.temFiltro() && (
                  <button onClick={() => this.setState({ period: { from: '', to: '' } })} title="Limpar filtro de datas" className="hv-icon" style={sx('background:transparent;border:none;color:#7C8794;cursor:pointer;display:flex;padding:2px')}><IcX s={14} /></button>
                )}
              </div>
              {this.can('exportar') && (
                <button onClick={() => this.setState({ exportOpen: true })} className="hv-ghost" style={sx('display:flex;align-items:center;gap:7px;background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:8px 15px;font-weight:600;font-size:12.5px;cursor:pointer')}>
                  <IcDown />
                  Exportar
                </button>
              )}
            </div>
          </header>

          {st.bdStatus === 'offline' && (
            <div style={sx('flex-shrink:0;background:rgba(224,60,49,.12);border-bottom:1px solid rgba(224,60,49,.35);padding:8px 28px;font-size:12px;color:#EB6258;font-weight:600')}>
              ⚠ Sem conexão com o banco de dados local — os dados estão sendo salvos só neste navegador. Abra o sistema pelo atalho "Abrir Controle de Clientes.bat" para gravar no banco.
            </div>
          )}
          <div style={sx('flex:1;overflow-y:auto;padding:28px')}>
            {this._screen === 'dashboard' && this.renderDash()}
            {this._screen === 'financeiro' && this.renderFin()}
            {this._screen === 'clientes' && this.renderCli()}
            {this._screen === 'contratos' && this.renderCon()}
            {this._screen === 'notas' && this.renderNf()}
            {this._screen === 'usuarios' && this.can('admin') && this.renderUsers()}
            {this._screen === 'integracoes' && this.can('admin') && this.renderIntegracoes()}
          </div>
        </main>

        {st.wizard && this.renderWizard()}
        {!!st.nf && this.renderNfModal()}
        {!!st.cob && this.renderCobModal()}
        {!!st.despesa && this.renderDespesaModal()}
        {!!st.receb && this.renderRecebModal()}
        {!!st.saldoModal && this.renderSaldoModal()}
        {st.exportOpen && this.renderExportModal()}
        {!!st.userModal && this.renderUserModal()}
        {!!st.clienteModal && this.renderClienteModal()}
        {!!st.pagamento && this.renderPagamentoModal()}
        {!!st.pagarDesp && this.renderPagarDespModal()}
        {!!st.confirmModal && (
          <div className="ovl" style={sx(OVERLAY + ';z-index:80')} onClick={e => { if (e.target === e.currentTarget) this.setState({ confirmModal: null }); }}>
            <div style={sx('width:100%;max-width:400px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:26px;animation:kesPop .25s ease both')}>
              <div style={sx('width:48px;height:48px;border-radius:999px;background:rgba(224,60,49,.14);display:flex;align-items:center;justify-content:center;margin-bottom:14px')}><IcTrash s={22} /></div>
              <div style={sx('font-size:16px;font-weight:800;letter-spacing:-.02em;margin-bottom:6px')}>Confirmar exclusão</div>
              <div style={sx('font-size:13px;color:#B7C0CB;line-height:1.5')}>{st.confirmModal.msg}</div>
              <div style={sx('display:flex;gap:10px;margin-top:20px')}>
                <button onClick={() => this.setState({ confirmModal: null })} style={sx(BTN_CANCEL)}>Cancelar</button>
                <button onClick={() => { const fn = st.confirmModal.onYes; this.setState({ confirmModal: null }, () => fn && fn()); }} className="hv-red" style={sx('flex:1.4;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:pointer')}>Excluir</button>
              </div>
            </div>
          </div>
        )}

        {!!st.toast && (
          <div style={sx('position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:70;background:#1C2530;border:1px solid rgba(255,255,255,.12);border-left:3px solid #2BB673;border-radius:12px;padding:13px 20px;box-shadow:0 18px 48px rgba(8,12,16,.45);display:flex;align-items:center;gap:11px;animation:kesFade .25s ease both')}>
            <IcCheck s={18} c="#2BB673" />
            <span style={sx('font-size:13px;font-weight:500')}>{st.toast}</span>
          </div>
        )}
      </div>
    );
  }

  /* ---------- barra lateral ---------- */
  renderSidebar() {
    return (
      <aside data-sidebar="1" style={sx('width:248px;flex-shrink:0;background:#131B23;border-right:1px solid rgba(255,255,255,.08);display:flex;flex-direction:column;padding:20px 14px;gap:6px')}>
        <div data-brandwrap="1" style={sx('display:flex;align-items:center;gap:11px;padding:6px 8px 22px')}>
          <img src="assets/logo-mark-red.png" alt="KeS" style={sx('width:34px;height:auto;display:block')} />
          <div data-brandtext="1" style={sx('line-height:1.1')}>
            <div style={sx('font-weight:800;font-size:15px;letter-spacing:-.01em')}>KeS Assessoria</div>
            <div style={sx('font-size:10.5px;color:#7C8794;letter-spacing:.12em;text-transform:uppercase;font-weight:600;margin-top:2px')}>Controle de Clientes</div>
          </div>
        </div>

        {this.can('dashboard') && <button onClick={() => this.go('dashboard')} style={this.navStyle('dashboard')}><IcDash /><span data-nav-label="1">Visão Geral</span></button>}
        {this.can('financeiro') && <button onClick={() => this.go('financeiro')} style={this.navStyle('financeiro')}><IcFin /><span data-nav-label="1">Financeiro</span></button>}
        {this.can('clientes') && <button onClick={() => this.go('clientes')} style={this.navStyle('clientes')}><IcCli /><span data-nav-label="1">Clientes</span></button>}
        {this.can('contratos') && <button onClick={() => this.go('contratos')} style={this.navStyle('contratos')}><IcCon /><span data-nav-label="1">Contratos</span></button>}
        {this.can('notas') && <button onClick={() => this.go('notas')} style={this.navStyle('notas')}><IcNf /><span data-nav-label="1">Notas Fiscais</span></button>}
        {this.can('admin') && (
          <div style={sx('margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.07);display:flex;flex-direction:column;gap:6px')}>
            <div data-nav-label="1" style={sx('font-size:10px;color:#7C8794;letter-spacing:.12em;text-transform:uppercase;font-weight:700;padding:0 12px 2px')}>Administração</div>
            <button onClick={() => this.go('usuarios')} style={this.navStyle('usuarios')}><IcUsers /><span data-nav-label="1">Usuários</span></button>
            <button onClick={() => this.go('integracoes')} style={this.navStyle('integracoes')}><IcPlug /><span data-nav-label="1">Integrações</span></button>
          </div>
        )}

        <div style={sx('margin-top:auto;display:flex;flex-direction:column;gap:10px')}>
          {this.can('editar') && this.can('clientes') && (
            <button onClick={() => this.startNew()} className="hv-red" style={sx('display:flex;align-items:center;justify-content:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:999px;padding:12px 14px;font-weight:700;font-size:13.5px;cursor:pointer;box-shadow:0 10px 30px rgba(224,60,49,.35);transition:background .14s')}>
              <IcPlus s={17} />
              <span data-nav-label="1">Novo Cliente</span>
            </button>
          )}
          <div style={sx('display:flex;align-items:center;gap:10px;padding:8px;border-top:1px solid rgba(255,255,255,.07);margin-top:2px')}>
            <div style={sx('width:32px;height:32px;border-radius:999px;background:#28323E;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;color:#EB6258;flex-shrink:0')}>{this.initials((this.currentUser() || {}).nome || 'K S')}</div>
            <div data-nav-label="1" style={sx('line-height:1.2;overflow:hidden;flex:1')}>
              <div style={sx('font-size:12.5px;font-weight:600;white-space:nowrap;text-overflow:ellipsis;overflow:hidden')}>{(this.currentUser() || {}).nome}</div>
              <div style={sx('font-size:10.5px;color:#7C8794')}>{(this.currentUser() || {}).cargo || (this.can('admin') ? 'Administradora' : 'Usuário')}</div>
            </div>
            <button onClick={() => this.sair()} title="Sair do sistema" className="hv-icon" style={sx('background:transparent;border:none;color:#7C8794;cursor:pointer;display:flex;flex-shrink:0;padding:4px')}><IcLogout /></button>
          </div>
        </div>
      </aside>
    );
  }

  /* ---------- Visão Geral ---------- */
  renderDash() {
    const st = this.state;
    const cv = this._cv;
    const active = st.clients.filter(c => c.status !== 'inativo');
    const mrr = active.reduce((s, c) => s + c.monthly, 0);

    const dot = (c) => sx('width:9px;height:9px;border-radius:9px;background:' + c);
    const dashKpis = [
      { label: 'Clientes ativos', value: String(active.length), delta: '+2 este mês', dot: dot('#2BB673'), deltaStyle: 'font-size:11.5px;color:#2BB673;font-weight:600;margin-top:6px' },
      { label: 'Receita recorrente (MRR)', value: this.fmt(mrr), delta: '+8,4% vs. mês anterior', dot: dot('#E03C31'), deltaStyle: 'font-size:11.5px;color:#2BB673;font-weight:600;margin-top:6px' },
      { label: 'NFS-e emitidas (' + (this.temFiltro() ? 'período' : MES_AB) + ')', value: String(this.temFiltro() ? st.nfs.filter(n => this.inPeriod(n.date)).length : st.nfs.filter(n => n.date.includes('/' + MM + '/')).length), delta: this.temFiltro() ? this.periodLabel() : 'no mês corrente', dot: dot('#3E8EDE'), deltaStyle: 'font-size:11.5px;color:#7C8794;font-weight:500;margin-top:6px' },
      { label: 'Contratos pendentes', value: String(st.clients.filter(c => c.contract === 'aguardando').length), delta: 'aguardando assinatura', dot: dot('#F2A93B'), deltaStyle: 'font-size:11.5px;color:#F2A93B;font-weight:600;margin-top:6px' },
    ];

    const chartDemo = [18.2, 19.5, 21.0, 22.4, 23.1];
    const chartData = LAST6.map((m, i) => i < 5 ? [m, chartDemo[i]] : [m, mrr / 1000]);
    const maxv = Math.max(...chartData.map(d => d[1]));
    const chartBars = chartData.map((d, i) => {
      const last = i === chartData.length - 1;
      return { month: d[0], top: 'R$' + d[1].toFixed(1).replace('.', ','), barStyle: `width:100%;max-width:46px;border-radius:7px 7px 0 0;height:${(d[1] / maxv) * 100}%;background:${last ? '#E03C31' : '#28323E'};box-shadow:${last ? '0 10px 30px rgba(224,60,49,.35)' : 'none'};transition:height .4s` };
    });

    const actIcon = (c) => 'width:30px;height:30px;border-radius:8px;flex-shrink:0;display:flex;align-items:center;justify-content:center;background:' + c + '22';
    const actDot = (c) => 'width:8px;height:8px;border-radius:8px;background:' + c;
    const activity = [
      { iconWrap: actIcon('#2BB673'), dot: actDot('#2BB673'), text: 'NFS-e 000142 emitida — Studio Bloom Arquitetura', time: 'Hoje, 09:14' },
      { iconWrap: actIcon('#E03C31'), dot: actDot('#E03C31'), text: 'Contrato enviado pelo Autentique — Construtora Horizonte', time: 'Ontem, 16:40' },
      { iconWrap: actIcon('#3E8EDE'), dot: actDot('#3E8EDE'), text: 'Pagamento recebido — Mercado Vista Verde', time: 'Ontem, 11:02' },
      { iconWrap: actIcon('#F2A93B'), dot: actDot('#F2A93B'), text: 'Mensalidade em atraso — Academia Pulse', time: '2 dias atrás' },
      { iconWrap: actIcon('#2BB673'), dot: actDot('#2BB673'), text: 'Novo cliente cadastrado — João Pedro Salgados', time: '14/04/2026' },
    ];
    const upcoming = st.charges.filter(c => this.chargeStatus(c) !== 'pago').slice(0, 3).map(c => {
      const cl = this.client(c.clientId);
      return { name: cl.name, due: c.dueDate ? 'Vence ' + c.dueDate : 'Vence dia ' + (c.due != null ? c.due : cl.payDay) + '/' + MM, value: this.fmt(this.chargeRemaining(c)) };
    });

    const fsum = (k) => st.flow.reduce((s, m) => s + m[k], 0);
    const finCount = fsum('inC'), finVal = fsum('inV'), foutCount = fsum('outC'), foutVal = fsum('outV');
    const fnetC = finCount - foutCount, fnetV = finVal - foutVal;
    const flowCardDef = [
      ['Entradas de clientes', String(finCount), '+' + this.fmt(finVal) + ' / MRR', '#2BB673'],
      ['Saídas de clientes', String(foutCount), '−' + this.fmt(foutVal) + ' / MRR', '#E03C31'],
      ['Saldo líquido', (fnetC >= 0 ? '+' : '') + fnetC, (fnetV >= 0 ? '+' : '−') + this.fmt(Math.abs(fnetV)) + ' / MRR', fnetV >= 0 ? '#2BB673' : '#E03C31'],
    ];
    const maxFlow = Math.max(1, ...st.flow.map(m => Math.max(m.inV, m.outV)));

    return (
      <div>
        <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(212px,1fr));gap:16px')}>
          {dashKpis.map((k, i) => (
            <div key={i} style={sx(CARD + ';padding:20px')}>
              <div style={sx('display:flex;align-items:center;justify-content:space-between')}>
                <div style={sx('font-size:11.5px;color:#7C8794;font-weight:600;letter-spacing:.02em')}>{k.label}</div>
                <div style={k.dot}></div>
              </div>
              <div style={sx('font-size:28px;font-weight:800;letter-spacing:-.02em;margin-top:12px')}>{k.value}</div>
              <div style={sx(k.deltaStyle)}>{k.delta}</div>
            </div>
          ))}
        </div>

        <div style={sx(CARD + ';padding:22px;margin-top:16px')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:18px')}>
            <div style={sx('font-size:15px;font-weight:700')}>Entradas e saídas de clientes</div>
            <div style={sx('font-size:12px;color:#7C8794')}>Movimentação · {ANO}</div>
          </div>
          <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px')}>
            {flowCardDef.map(([label, qty, val, accent], i) => (
              <div key={i} style={sx(`background:#0E1318;border:1px solid rgba(255,255,255,.07);border-radius:14px;padding:18px;border-top:3px solid ${accent}`)}>
                <div style={sx('font-size:11.5px;color:#7C8794;font-weight:600')}>{label}</div>
                <div style={sx('display:flex;align-items:baseline;gap:8px;margin-top:8px')}>
                  <div style={sx('font-size:30px;font-weight:800;letter-spacing:-.02em')}>{qty}</div>
                  <div style={sx('font-size:11px;color:#7C8794')}>clientes</div>
                </div>
                <div style={sx(`font-size:12.5px;font-weight:700;color:${accent};margin-top:6px`)}>{val}</div>
              </div>
            ))}
          </div>
          <div style={sx('display:flex;align-items:flex-end;justify-content:space-between;gap:14px;height:120px;margin-top:22px;padding-top:8px;border-top:1px solid rgba(255,255,255,.06)')}>
            {st.flow.map((m, i) => (
              <div key={i} style={sx('flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;height:100%;justify-content:flex-end')}>
                <div style={sx('display:flex;align-items:flex-end;gap:4px;height:100%')}>
                  <div style={sx(`width:11px;border-radius:5px 5px 0 0;background:#2BB673;height:${Math.max(3, (m.inV / maxFlow) * 100)}%`)}></div>
                  <div style={sx(`width:11px;border-radius:5px 5px 0 0;background:#E03C31;height:${Math.max(3, (m.outV / maxFlow) * 100)}%`)}></div>
                </div>
                <div style={sx('font-size:11px;color:#7C8794;font-weight:600')}>{m.month}</div>
              </div>
            ))}
          </div>
          <div style={sx('display:flex;gap:18px;justify-content:center;margin-top:14px')}>
            <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:7px')}><span style={sx('width:10px;height:10px;border-radius:3px;background:#2BB673;display:inline-block')}></span>Entradas (MRR)</span>
            <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:7px')}><span style={sx('width:10px;height:10px;border-radius:3px;background:#E03C31;display:inline-block')}></span>Saídas (MRR)</span>
          </div>
        </div>

        <div className="g2" style={sx('display:grid;grid-template-columns:1.55fr 1fr;gap:16px;margin-top:16px')}>
          <div style={sx(CARD + ';padding:22px')}>
            <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:20px')}>
              <div style={sx('font-size:15px;font-weight:700')}>Receita recorrente — últimos 6 meses</div>
              <div style={sx('font-size:12px;color:#7C8794')}>em R$ mil</div>
            </div>
            <div style={sx('display:flex;align-items:flex-end;justify-content:space-between;gap:14px;height:180px;padding-top:10px')}>
              {chartBars.map((b, i) => (
                <div key={i} style={sx('flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;height:100%;justify-content:flex-end')}>
                  <div style={sx('font-size:11px;font-weight:700;color:#B7C0CB')}>{b.top}</div>
                  <div style={sx(b.barStyle)}></div>
                  <div style={sx('font-size:11px;color:#7C8794;font-weight:600')}>{b.month}</div>
                </div>
              ))}
            </div>
          </div>
          <div style={sx(CARD + ';padding:22px')}>
            <div style={sx('font-size:15px;font-weight:700;margin-bottom:16px')}>Atividade recente</div>
            <div style={sx('display:flex;flex-direction:column;gap:2px')}>
              {activity.map((a, i) => (
                <div key={i} style={sx('display:flex;gap:12px;padding:10px 0;border-bottom:1px solid rgba(255,255,255,.05)')}>
                  <div style={sx(a.iconWrap)}><div style={sx(a.dot)}></div></div>
                  <div style={sx('min-width:0')}>
                    <div style={sx('font-size:13px;font-weight:500;line-height:1.35')}>{a.text}</div>
                    <div style={sx('font-size:11px;color:#7C8794;margin-top:2px')}>{a.time}</div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>

        <div style={sx(CARD + ';padding:22px;margin-top:16px')}>
          <div style={sx('font-size:15px;font-weight:700;margin-bottom:6px')}>Próximos vencimentos</div>
          <div style={sx('font-size:12.5px;color:#7C8794;margin-bottom:14px')}>Cobranças que vencem nos próximos dias</div>
          <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px')}>
            {upcoming.map((u, i) => (
              <div key={i} style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.07);border-radius:12px;padding:14px 16px;display:flex;align-items:center;justify-content:space-between;gap:10px')}>
                <div style={sx('min-width:0')}>
                  <div style={sx('font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{u.name}</div>
                  <div style={sx('font-size:11.5px;color:#7C8794;margin-top:2px')}>{u.due}</div>
                </div>
                <div style={sx('font-size:14px;font-weight:800;color:#fff;white-space:nowrap')}>{u.value}</div>
              </div>
            ))}
            {upcoming.length === 0 && (
              <div style={sx('background:#0E1318;border:1px dashed rgba(255,255,255,.12);border-radius:12px;padding:16px;color:#7C8794;font-size:13px;text-align:center')}>Nenhuma cobrança pendente 🎉</div>
            )}
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Financeiro ---------- */
  renderFin() {
    const st = this.state;
    const tab = st.finTab;
    const finTabStyle = (k) => `padding:9px 16px;border-radius:999px;border:none;cursor:pointer;font-size:12.5px;font-weight:600;font-family:inherit;background:${tab === k ? '#E03C31' : 'transparent'};color:${tab === k ? '#fff' : '#B7C0CB'};white-space:nowrap`;
    const finTabs = [['fluxo', 'Fluxo de Caixa'], ['receb', 'Recebimentos'], ['desp', 'Despesas'], ['cat', 'Categorias'], ['extrato', 'Extrato']];

    return (
      <div>
        <div style={sx('display:flex;gap:6px;background:#131B23;padding:5px;border-radius:999px;border:1px solid rgba(255,255,255,.08);margin-bottom:18px;overflow-x:auto')}>
          {finTabs.map(([k, l]) => (
            <button key={k} onClick={() => this.setFinTab(k)} style={sx(finTabStyle(k))}>{l}</button>
          ))}
        </div>
        {tab === 'fluxo' && this.renderFluxo()}
        {tab === 'receb' && this.renderReceb()}
        {tab === 'desp' && this.renderDesp()}
        {tab === 'cat' && this.renderCat()}
        {tab === 'extrato' && this.renderExtrato()}
      </div>
    );
  }

  /* livro-caixa (entradas/saídas pagas, ordenado por data, com saldo acumulado) */
  ledger() {
    const st = this.state;
    const cv = this._cv;
    const catName = (id) => (st.categories.find(c => c.id === id) || {}).name || '—';
    const parseD = (d) => { const p = String(d).split('/'); return new Date(+p[2], (+p[1] || 1) - 1, +p[0] || 1); };
    let ledger = [];
    st.charges.forEach(c => { const p = this.chargePaid(c); if (p > 0) ledger.push({ date: c.paidDate || todayStr(), desc: this.client(c.clientId).name + (this.chargeRemaining(c) > 0 ? ' (parcial)' : ''), cat: 'Mensalidade de clientes', type: 'entrada', value: p, src: 'Asaas' }); });
    st.receipts.forEach(r => { const p = this.receiptPaid(r); if (p > 0) ledger.push({ date: r.date, desc: r.desc + (this.receiptRemaining(r) > 0 ? ' (parcial)' : ''), cat: catName(r.catId), type: 'entrada', value: p, src: 'Manual' }); });
    st.expenses.forEach(e => { const p = this.expensePaid(e); if (p > 0) ledger.push({ date: e.date, desc: e.desc + (this.expenseRemaining(e) > 0 ? ' (parcial)' : ''), cat: catName(e.catId), type: 'saida', value: p, src: e.supplier }); });
    ledger.sort((a, b) => parseD(a.date) - parseD(b.date));
    let run = st.openingBalance;
    return ledger.map(l => { run += l.type === 'entrada' ? l.value : -l.value; return { ...l, balance: run }; });
  }

  renderFluxo() {
    const st = this.state;
    const cv = this._cv;
    const ledgerAll = this.ledger();
    const ledger = ledgerAll.filter(l => this.inPeriod(l.date));
    const cashInAll = ledgerAll.filter(l => l.type === 'entrada').reduce((s, l) => s + l.value, 0);
    const cashOutAll = ledgerAll.filter(l => l.type === 'saida').reduce((s, l) => s + l.value, 0);
    const cashIn = ledger.filter(l => l.type === 'entrada').reduce((s, l) => s + l.value, 0);
    const cashOut = ledger.filter(l => l.type === 'saida').reduce((s, l) => s + l.value, 0);
    const saldoAtual = st.openingBalance + cashInAll - cashOutAll;
    const resultado = cashIn - cashOut;
    const aReceberFin = st.charges.reduce((s, c) => s + this.chargeRemaining(c), 0) + st.receipts.reduce((s, r) => s + this.receiptRemaining(r), 0);
    const aPagarFin = st.expenses.reduce((s, e) => s + this.expenseRemaining(e), 0);
    const inCount = ledger.filter(l => l.type === 'entrada').length, outCount = ledger.filter(l => l.type === 'saida').length;
    const rotulo = this.temFiltro() ? 'período' : MES_LOWER;

    const fluxoKpiRows = [
      { label: 'Saldo atual em caixa', value: this.fmt(saldoAtual), sub: 'Saldo inicial ' + this.fmt(st.openingBalance), border: '#E03C31', color: '#fff' },
      { label: 'Entradas (' + rotulo + ')', value: this.fmt(cashIn), sub: inCount + ' lançamentos', border: '#2BB673', color: '#2BB673' },
      { label: 'Saídas (' + rotulo + ')', value: this.fmt(cashOut), sub: outCount + ' lançamentos', border: '#F2A93B', color: '#EB6258' },
      { label: this.temFiltro() ? 'Resultado do período' : 'Resultado do mês', value: (resultado >= 0 ? '+' : '−') + this.fmt(Math.abs(resultado)), sub: 'Entradas − saídas', border: resultado >= 0 ? '#2BB673' : '#E03C31', color: resultado >= 0 ? '#2BB673' : '#E03C31' },
    ];
    const secondaryFin = [
      { label: 'A receber — cobranças em aberto', value: this.fmt(aReceberFin), color: '#F2A93B' },
      { label: 'A pagar — despesas pendentes', value: this.fmt(aPagarFin), color: '#EB6258' },
    ];
    const cashDemo = [[16200, 11800], [17500, 12400], [19000, 13100], [20100, 14200], [21300, 15050]];
    const cashHist = LAST6.map((m, i) => i < 5 ? [m, cashDemo[i][0], cashDemo[i][1]] : [m, cashIn, cashOut]);
    const maxCash = Math.max(1, ...cashHist.map(d => Math.max(d[1], d[2])));

    return (
      <div>
        <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px;flex-wrap:wrap')}>
          <div style={sx('font-size:15px;font-weight:700')}>Fluxo de caixa — {this.periodLabel()}</div>
          {this.can('editar') && (
            <button onClick={() => this.openSaldo()} className="hv-ghost" style={sx('display:flex;align-items:center;gap:7px;background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:9px 16px;font-weight:600;font-size:12.5px;cursor:pointer')}>
              <IcMoney />
              Ajustar saldo inicial
            </button>
          )}
        </div>
        <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:16px')}>
          {fluxoKpiRows.map((k, i) => (
            <div key={i} style={sx(CARD + `;padding:20px;border-top:3px solid ${k.border}`)}>
              <div style={sx('font-size:11.5px;color:#7C8794;font-weight:600')}>{k.label}</div>
              <div style={sx(`font-size:25px;font-weight:800;letter-spacing:-.02em;margin-top:10px;color:${k.color}`)}>{k.value}</div>
              <div style={sx('font-size:11.5px;color:#7C8794;margin-top:4px')}>{k.sub}</div>
            </div>
          ))}
        </div>
        <div className="g2" style={sx('display:grid;grid-template-columns:1.6fr 1fr;gap:16px;margin-top:16px')}>
          <div style={sx(CARD + ';padding:22px')}>
            <div style={sx('font-size:15px;font-weight:700;margin-bottom:18px')}>Entradas × Saídas — 6 meses</div>
            <div style={sx('display:flex;align-items:flex-end;justify-content:space-between;gap:14px;height:170px')}>
              {cashHist.map((d, i) => (
                <div key={i} style={sx('flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;height:100%;justify-content:flex-end')}>
                  <div style={sx('display:flex;align-items:flex-end;gap:5px;height:100%')}>
                    <div style={sx(`width:12px;border-radius:5px 5px 0 0;background:#2BB673;height:${Math.max(3, (d[1] / maxCash) * 100)}%`)}></div>
                    <div style={sx(`width:12px;border-radius:5px 5px 0 0;background:#E03C31;height:${Math.max(3, (d[2] / maxCash) * 100)}%`)}></div>
                  </div>
                  <div style={sx('font-size:11px;color:#7C8794;font-weight:600')}>{d[0]}</div>
                </div>
              ))}
            </div>
            <div style={sx('display:flex;gap:18px;justify-content:center;margin-top:14px')}>
              <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:7px')}><span style={sx('width:10px;height:10px;border-radius:3px;background:#2BB673;display:inline-block')}></span>Entradas</span>
              <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:7px')}><span style={sx('width:10px;height:10px;border-radius:3px;background:#E03C31;display:inline-block')}></span>Saídas</span>
            </div>
          </div>
          <div style={sx('display:flex;flex-direction:column;gap:14px')}>
            {secondaryFin.map((s, i) => (
              <div key={i} style={sx(CARD + ';padding:20px;flex:1;display:flex;flex-direction:column;justify-content:center')}>
                <div style={sx('font-size:12px;color:#7C8794;font-weight:600')}>{s.label}</div>
                <div style={sx(`font-size:22px;font-weight:800;margin-top:8px;color:${s.color}`)}>{s.value}</div>
              </div>
            ))}
          </div>
        </div>
      </div>
    );
  }

  renderReceb() {
    const st = this.state;
    const cv = this._cv;
    const catName = (id) => (st.categories.find(c => c.id === id) || {}).name || '—';
    const active = st.clients.filter(c => c.status !== 'inativo');
    const mrr = active.reduce((s, c) => s + c.monthly, 0);
    const visiveis = st.charges.filter(c => this.chargeStatus(c) !== 'pago' || this.inPeriod(c.paidDate));
    // recebido = soma do que efetivamente entrou (inclui a parte já paga das parciais)
    const recebido = visiveis.reduce((s, c) => s + (this.inPeriod(c.paidDate) || !c.paidDate ? this.chargePaid(c) : 0), 0);
    const areceber = visiveis.filter(c => this.chargeStatus(c) !== 'pago').reduce((s, c) => s + this.chargeRemaining(c), 0);
    const inad = visiveis.filter(c => c.status === 'atrasado').reduce((s, c) => s + this.chargeRemaining(c), 0);
    const canEdit = this.can('editar');
    const nPagos = visiveis.filter(c => this.chargePaid(c) > 0).length;
    const nParciais = visiveis.filter(c => this.chargeStatus(c) === 'parcial').length;
    const finKpis = [
      { label: 'Receita Mensal Recorrente', value: this.fmt(mrr), sub: active.length + ' clientes ativos', border: '#E03C31' },
      { label: this.temFiltro() ? 'Recebido no período' : 'Recebido em ' + MES_NOME, value: this.fmt(recebido), sub: nPagos + ' pagamento(s)' + (nParciais ? ' · ' + nParciais + ' parcial(is)' : ''), border: '#2BB673' },
      { label: 'A receber', value: this.fmt(areceber), sub: visiveis.filter(c => this.chargeStatus(c) !== 'pago').length + ' cobranças abertas', border: '#F2A93B' },
      { label: 'Inadimplência', value: this.fmt(inad), sub: visiveis.filter(c => c.status === 'atrasado').length + ' em atraso', border: '#E03C31' },
    ];

    const badge = { pago: ['Pago', '#2BB673'], parcial: ['Parcial', '#3E8EDE'], pendente: ['Pendente', '#F2A93B'], atrasado: ['Atrasado', '#E03C31'] };
    const order = { atrasado: 0, parcial: 1, pendente: 2, pago: 3 };
    const chargeRows = [...visiveis].sort((a, b) => (order[this.chargeStatus(a)] - order[this.chargeStatus(b)])).map(c => {
      const cl = this.client(c.clientId);
      const stt = this.chargeStatus(c);
      const [bl, bc] = badge[stt];
      const cval = cv(c);
      const cdue = c.due != null ? c.due : cl.payDay;
      return { c, cl, bl, bc, stt, cval, pago: this.chargePaid(c), falta: this.chargeRemaining(c), cdue };
    });
    const recRows = st.receipts.filter(r => this.inPeriod(r.date)).map(r => ({ obj: r, desc: r.desc, cat: catName(r.catId), date: r.date, value: r.value, pago: this.receiptPaid(r), falta: this.receiptRemaining(r), valueFmt: this.fmt(r.value), isRecur: r.recur === 'mensal' }));
    const avatar = (color) => sx('width:34px;height:34px;border-radius:9px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:#28323E;color:' + color);

    return (
      <div>
        <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px')}>
          {finKpis.map((k, i) => (
            <div key={i} style={sx(CARD + `;padding:20px;border-top:3px solid ${k.border}`)}>
              <div style={sx('font-size:11.5px;color:#7C8794;font-weight:600')}>{k.label}</div>
              <div style={sx('font-size:27px;font-weight:800;letter-spacing:-.02em;margin-top:10px')}>{k.value}</div>
              <div style={sx('font-size:11.5px;color:#7C8794;margin-top:4px')}>{k.sub}</div>
            </div>
          ))}
        </div>

        <div className="tbl" style={sx(CARD + ';margin-top:16px;overflow:hidden')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;padding:20px 22px 16px')}>
            <div>
              <div style={sx('font-size:16px;font-weight:700')}>Mensalidades — {this.periodLabel()}</div>
              <div style={sx('font-size:12.5px;color:#7C8794;margin-top:3px')}>Cobranças via Asaas · registre o pagamento e emita a NFS-e</div>
            </div>
            <div style={sx('display:flex;flex-direction:column;align-items:flex-end;gap:10px')}>
              {canEdit && (
                <button onClick={() => this.openCob()} className="hv-red" style={sx('display:flex;align-items:center;gap:7px;background:#E03C31;color:#fff;border:none;border-radius:9px;padding:9px 14px;font-size:12.5px;font-weight:700;cursor:pointer;box-shadow:0 8px 22px rgba(224,60,49,.28)')}>
                  <IcPlus s={15} />
                  Nova cobrança
                </button>
              )}
              <div style={sx('display:flex;gap:8px')}>
                <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:6px')}><span style={sx('width:8px;height:8px;border-radius:9px;background:#2BB673;display:inline-block')}></span>Pago</span>
                <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:6px;margin-left:8px')}><span style={sx('width:8px;height:8px;border-radius:9px;background:#F2A93B;display:inline-block')}></span>Pendente</span>
                <span style={sx('font-size:11.5px;color:#B7C0CB;display:flex;align-items:center;gap:6px;margin-left:8px')}><span style={sx('width:8px;height:8px;border-radius:9px;background:#E03C31;display:inline-block')}></span>Atrasado</span>
              </div>
            </div>
          </div>
          <div style={sx('display:grid;grid-template-columns:2fr 1.4fr 1fr 1fr 1.6fr;gap:12px;padding:11px 22px;border-top:1px solid rgba(255,255,255,.07);border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Cliente</div><div>Serviço</div><div>Valor</div><div>Status</div><div style={sx('text-align:right')}>Ações</div>
          </div>
          {chargeRows.map(({ c, cl, bl, bc, stt, cval, pago, falta, cdue }) => (
            <div key={c.id} style={sx('display:grid;grid-template-columns:2fr 1.4fr 1fr 1fr 1.6fr;gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
              <div style={sx('display:flex;align-items:center;gap:11px;min-width:0')}>
                <div style={avatar(c.status === 'atrasado' ? '#EB6258' : '#B7C0CB')}>{this.initials(cl.name)}</div>
                <div style={sx('min-width:0')}>
                  <div style={sx('font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{cl.name}</div>
                  <div style={sx('font-size:11px;color:#7C8794')}>{c.dueDate ? 'Venc. ' + c.dueDate : 'Venc. dia ' + cdue}</div>
                </div>
              </div>
              <div style={sx('font-size:12.5px;color:#B7C0CB;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{c.desc || cl.service}</div>
              <div>
                <div style={sx('font-size:13.5px;font-weight:700')}>{this.fmt(cval)}</div>
                {stt === 'parcial' && <div style={sx('font-size:10.5px;color:#7FB4EC;font-weight:600;margin-top:1px')}>Pago {this.fmt(pago)} · falta {this.fmt(falta)}</div>}
              </div>
              <div><span style={sx(`display:inline-block;font-size:11px;font-weight:700;padding:4px 11px;border-radius:999px;background:${bc}22;color:${bc}`)}>{bl}</span></div>
              <div style={sx('display:flex;gap:8px;justify-content:flex-end;align-items:center')}>
                {canEdit && stt !== 'pago' && (
                  <button onClick={() => this.openPagamento(c)} className="hv-ghost" style={sx('background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:7px 12px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap')}>{stt === 'parcial' ? 'Receber restante' : 'Registrar pagamento'}</button>
                )}
                {canEdit && stt === 'pago' && !c.nfId && (
                  <button onClick={() => this.openNf(c.id)} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:8px;padding:7px 12px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap')}>Gerar NFS-e</button>
                )}
                {!!c.nfId && (
                  <span style={sx('display:inline-flex;align-items:center;gap:6px;color:#2BB673;font-size:12px;font-weight:600;white-space:nowrap')}><IcCheck s={14} />NFS-e {c.nfId}</span>
                )}
                {canEdit && (
                  <button onClick={() => this.openCob(c)} title="Editar cobrança" className="hv-icon" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:7px;border-radius:8px')}><IcEdit s={14} /></button>
                )}
                {canEdit && (
                  <button onClick={() => this.confirmarExclusao('Excluir esta cobrança de ' + cl.name + '?', () => this.deleteCharge(c.id))} title="Excluir cobrança" className="hv-danger" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:7px;border-radius:8px')}><IcTrash s={14} /></button>
                )}
              </div>
            </div>
          ))}
        </div>

        <div style={sx('margin-top:16px;display:flex;align-items:center;justify-content:space-between')}>
          <div style={sx('font-size:14px;font-weight:700')}>Recebimentos avulsos</div>
          {canEdit && (
            <button onClick={() => this.openReceb()} className="hv-ghost" style={sx('display:flex;align-items:center;gap:7px;background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:9px 16px;font-weight:600;font-size:12.5px;cursor:pointer')}>
              <IcPlus s={14} />
              Registrar recebimento
            </button>
          )}
        </div>
        <div className="tbl" style={sx(CARD + ';overflow:hidden;margin-top:12px')}>
          <div style={sx('display:grid;grid-template-columns:2.4fr 1.4fr 1fr 1fr ' + (canEdit ? '0.9fr' : '') + ';gap:12px;padding:11px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Descrição</div><div>Categoria</div><div>Data</div><div style={sx('text-align:right')}>Valor</div>{canEdit && <div style={sx('text-align:right')}>Ações</div>}
          </div>
          {recRows.map((r, i) => (
            <div key={i} style={sx('display:grid;grid-template-columns:2.4fr 1.4fr 1fr 1fr ' + (canEdit ? '0.9fr' : '') + ';gap:12px;padding:13px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
              <div style={sx('min-width:0')}>
                <div style={sx('font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{r.desc}</div>
                {r.isRecur && <span style={sx('font-size:10px;font-weight:700;color:#2BB673;margin-top:2px;display:inline-block')}>↻ Recorrente mensal</span>}
              </div>
              <div style={sx('font-size:12px;color:#B7C0CB')}>{r.cat}</div>
              <div style={sx('font-size:12px;color:#B7C0CB')}>{r.date}</div>
              <div style={sx('text-align:right')}>
                <div style={sx('font-size:13px;font-weight:700;color:#2BB673')}>+ {this.fmt(r.pago)}</div>
                {r.falta > 0 && <div style={sx('font-size:10.5px;color:#F2A93B;font-weight:600;margin-top:1px')}>de {r.valueFmt} · falta {this.fmt(r.falta)}</div>}
              </div>
              {canEdit && (
                <div style={sx('display:flex;gap:6px;justify-content:flex-end;align-items:center')}>
                  {r.falta > 0 && <button onClick={() => this.completarReceb(r.obj.id)} title="Receber o restante" className="hv-ghost" style={sx('background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:7px;padding:6px 9px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap')}>Receber resto</button>}
                  <button onClick={() => this.openReceb(r.obj)} title="Editar" className="hv-icon" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcEdit s={13} /></button>
                  <button onClick={() => this.confirmarExclusao('Excluir o recebimento "' + r.desc + '"?', () => this.deleteReceipt(r.obj.id))} title="Excluir" className="hv-danger" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcTrash s={13} /></button>
                </div>
              )}
            </div>
          ))}
        </div>
      </div>
    );
  }

  renderDesp() {
    const st = this.state;
    const canEdit = this.can('editar');
    const catName = (id) => (st.categories.find(c => c.id === id) || {}).name || '—';
    const expBadge = { pago: ['Pago', '#2BB673'], parcial: ['Parcial', '#3E8EDE'], pendente: ['Pendente', '#F2A93B'] };
    const despesas = st.expenses.filter(e => this.inPeriod(e.date));
    const totalDespMesFmt = this.fmt(despesas.reduce((s, e) => s + e.value, 0));

    return (
      <div>
        <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px;flex-wrap:wrap')}>
          <div style={sx('font-size:13.5px;color:#B7C0CB')}>{this.temFiltro() ? 'Total lançado no período' : 'Total lançado no mês'}: <strong style={sx('color:#fff')}>{totalDespMesFmt}</strong></div>
          {canEdit && (
            <button onClick={() => this.openDespesa()} className="hv-red" style={sx('display:flex;align-items:center;gap:7px;background:#E03C31;color:#fff;border:none;border-radius:999px;padding:10px 18px;font-weight:700;font-size:13px;cursor:pointer;box-shadow:0 8px 22px rgba(224,60,49,.28)')}>
              <IcPlus s={15} />
              Nova despesa
            </button>
          )}
        </div>
        <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
          <div style={sx('display:grid;grid-template-columns:2fr 1.4fr 1.2fr 1fr 1fr 1.2fr;gap:12px;padding:12px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Descrição</div><div>Categoria</div><div>Fornecedor</div><div>Data</div><div>Valor</div><div style={sx('text-align:right')}>Status / Ações</div>
          </div>
          {despesas.map(e => {
            const stt = this.expenseStatus(e);
            const [bl, bc] = expBadge[stt];
            const falta = this.expenseRemaining(e);
            return (
              <div key={e.id} style={sx('display:grid;grid-template-columns:2fr 1.4fr 1.2fr 1fr 1fr 1.6fr;gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
                <div style={sx('min-width:0')}>
                  <div style={sx('font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{e.desc}</div>
                  {e.recur === 'mensal' && <span style={sx('font-size:10px;font-weight:700;color:#EB6258;margin-top:2px;display:inline-block')}>↻ Recorrente mensal</span>}
                </div>
                <div style={sx('font-size:12px;color:#B7C0CB;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{catName(e.catId)}</div>
                <div style={sx('font-size:12px;color:#7C8794;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{e.supplier}</div>
                <div style={sx('font-size:12px;color:#B7C0CB')}>{e.date}</div>
                <div>
                  <div style={sx('font-size:13px;font-weight:700;color:#EB6258')}>{this.fmt(e.value)}</div>
                  {stt === 'parcial' && <div style={sx('font-size:10.5px;color:#7FB4EC;font-weight:600;margin-top:1px')}>Pago {this.fmt(this.expensePaid(e))} · falta {this.fmt(falta)}</div>}
                </div>
                <div style={sx('display:flex;justify-content:flex-end;align-items:center;gap:6px')}>
                  {canEdit && stt !== 'pago' && (
                    <button onClick={() => this.openPagarDespesa(e)} className="hv-ghost" style={sx('background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:6px 10px;font-size:11px;font-weight:600;cursor:pointer;white-space:nowrap')}>{stt === 'parcial' ? 'Pagar resto' : 'Marcar pago'}</button>
                  )}
                  <span style={sx(`display:inline-block;font-size:11px;font-weight:700;padding:4px 10px;border-radius:999px;background:${bc}22;color:${bc}`)}>{bl}</span>
                  {canEdit && (
                    <button onClick={() => this.openDespesa(e)} title="Editar" className="hv-icon" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcEdit s={13} /></button>
                  )}
                  {canEdit && (
                    <button onClick={() => this.confirmarExclusao('Excluir a despesa "' + e.desc + '"?', () => this.deleteExpense(e.id))} title="Excluir" className="hv-danger" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcTrash s={13} /></button>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  renderCat() {
    const st = this.state;
    const canEdit = this.can('editar');
    const catTabStyle = (on) => `flex:1;padding:9px;border-radius:8px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.1)' : '#0E1318'};color:${on ? '#fff' : '#B7C0CB'};font-weight:600;font-size:12.5px;cursor:pointer;font-family:inherit`;
    const catEntrada = st.categories.filter(c => c.type === 'entrada');
    const catSaida = st.categories.filter(c => c.type === 'saida');
    const catList = (list) => (
      <div style={sx('display:flex;flex-direction:column;gap:8px')}>
        {list.map(c => (
          <div key={c.id} style={sx('display:flex;align-items:center;justify-content:space-between;background:#0E1318;border:1px solid rgba(255,255,255,.07);border-radius:10px;padding:11px 14px')}>
            <span style={sx('font-size:13px;font-weight:500')}>{c.name}</span>
            {canEdit && (
              <div style={sx('display:flex;gap:6px')}>
                <button onClick={() => this.editCategory(c)} title="Renomear" className="hv-icon" style={sx('background:transparent;border:none;color:#7C8794;cursor:pointer;display:flex')}><IcEdit s={14} /></button>
                <button onClick={() => this.confirmarExclusao('Excluir a categoria "' + c.name + '"?', () => this.removeCategory(c.id))} title="Excluir" className="hv-icon" style={sx('background:transparent;border:none;color:#7C8794;cursor:pointer;display:flex')}><IcTrashSimple s={15} /></button>
              </div>
            )}
          </div>
        ))}
      </div>
    );

    return (
      <div>
        {canEdit && (
        <div style={sx(CARD + ';padding:20px;margin-bottom:16px')}>
          <div style={sx('font-size:13px;font-weight:700;margin-bottom:12px')}>{st.catForm.id ? 'Editar categoria' : 'Nova categoria'}</div>
          <div style={sx('display:flex;gap:10px;flex-wrap:wrap;align-items:center')}>
            <input value={st.catForm.name} onChange={e => this.setCat('name', e.target.value)} placeholder="Nome da categoria" className="kes-in" style={sx('flex:1;min-width:180px;' + IN13)} />
            <div style={sx('display:flex;gap:8px;width:220px')}>
              <button onClick={() => this.setCat('type', 'entrada')} style={sx(catTabStyle(st.catForm.type === 'entrada'))}>Entrada</button>
              <button onClick={() => this.setCat('type', 'saida')} style={sx(catTabStyle(st.catForm.type === 'saida'))}>Saída</button>
            </div>
            <button onClick={() => this.addCategory()} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px 20px;font-weight:700;font-size:13px;cursor:pointer')}>{st.catForm.id ? 'Salvar' : 'Adicionar'}</button>
            {st.catForm.id && <button onClick={() => this.setState({ catForm: { name: '', type: st.catForm.type } })} style={sx(BTN_CANCEL + ';flex:0;padding:11px 16px')}>Cancelar</button>}
          </div>
        </div>
        )}
        <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:16px')}>
          <div style={sx(CARD + ';padding:20px')}>
            <div style={sx('font-size:13px;font-weight:700;color:#2BB673;margin-bottom:12px;display:flex;align-items:center;gap:8px')}><span style={sx('width:9px;height:9px;border-radius:9px;background:#2BB673;display:inline-block')}></span>Categorias de entrada</div>
            {catList(catEntrada)}
          </div>
          <div style={sx(CARD + ';padding:20px')}>
            <div style={sx('font-size:13px;font-weight:700;color:#E03C31;margin-bottom:12px;display:flex;align-items:center;gap:8px')}><span style={sx('width:9px;height:9px;border-radius:9px;background:#E03C31;display:inline-block')}></span>Categorias de saída</div>
            {catList(catSaida)}
          </div>
        </div>
      </div>
    );
  }

  renderExtrato() {
    const ledger = this.ledger().filter(l => this.inPeriod(l.date));
    const rows = [...ledger].reverse();
    return (
      <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
        <div style={sx('display:flex;align-items:center;justify-content:space-between;padding:18px 22px 14px')}>
          <div style={sx('font-size:15px;font-weight:700')}>{this.temFiltro() ? 'Extrato — ' + this.periodLabel() : 'Extrato — todas as movimentações'}</div>
          <div style={sx('font-size:12px;color:#7C8794')}>Entradas e saídas com saldo acumulado</div>
        </div>
        <div style={sx('display:grid;grid-template-columns:1fr 2.2fr 1.5fr 1fr 1.2fr 1.3fr;gap:12px;padding:11px 22px;border-top:1px solid rgba(255,255,255,.07);border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
          <div>Data</div><div>Descrição</div><div>Categoria</div><div>Tipo</div><div style={sx('text-align:right')}>Valor</div><div style={sx('text-align:right')}>Saldo</div>
        </div>
        {rows.map((r, i) => (
          <div key={i} style={sx('display:grid;grid-template-columns:1fr 2.2fr 1.5fr 1fr 1.2fr 1.3fr;gap:12px;padding:13px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
            <div style={sx('font-size:12px;color:#B7C0CB')}>{r.date}</div>
            <div style={sx('font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{r.desc}</div>
            <div style={sx('font-size:12px;color:#7C8794;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{r.cat}</div>
            <div><span style={sx(`display:inline-block;font-size:10px;font-weight:700;padding:3px 9px;border-radius:999px;background:${r.type === 'entrada' ? 'rgba(43,182,115,.16)' : 'rgba(224,60,49,.16)'};color:${r.type === 'entrada' ? '#2BB673' : '#EB6258'}`)}>{r.type === 'entrada' ? 'Entrada' : 'Saída'}</span></div>
            <div style={sx('text-align:right')}><span style={sx(`font-size:13px;font-weight:700;color:${r.type === 'entrada' ? '#2BB673' : '#EB6258'}`)}>{(r.type === 'entrada' ? '+ ' : '− ') + this.fmt(r.value)}</span></div>
            <div style={sx('text-align:right;font-size:13px;font-weight:700;color:#fff')}>{this.fmt(r.balance)}</div>
          </div>
        ))}
      </div>
    );
  }

  /* ---------- Clientes ---------- */
  renderCli() {
    const st = this.state;
    const filt = st.clientFilter;
    const q = st.search.trim().toLowerCase();
    const canEdit = this.can('editar');
    const filtered = st.clients
      .filter(c => filt === 'todos' ? true : filt === 'ativos' ? c.status === 'ativo' : filt === 'pendentes' ? (c.status === 'pendente' || c.contract === 'aguardando') : c.status === 'inativo')
      .filter(c => !q || c.name.toLowerCase().includes(q) || c.doc.toLowerCase().includes(q))
      .filter(c => this.inPeriod(c.entry));
    const tabStyle = (k) => `padding:8px 16px;border-radius:999px;border:none;cursor:pointer;font-size:12.5px;font-weight:600;font-family:inherit;background:${filt === k ? '#E03C31' : 'transparent'};color:${filt === k ? '#fff' : '#B7C0CB'};transition:all .14s`;
    const filterTabs = [['todos', 'Todos'], ['ativos', 'Ativos'], ['pendentes', 'Pendentes'], ['inativos', 'Inativos / Saída']];
    const sBadge = { ativo: ['Ativo', '#2BB673'], pendente: ['Inadimplente', '#E03C31'], inativo: ['Saída', '#7C8794'] };
    const avatar = sx('width:34px;height:34px;border-radius:9px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;background:#28323E;color:#B7C0CB');

    return (
      <div>
        <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;gap:14px;flex-wrap:wrap')}>
          <div className="scrollx" style={sx('display:flex;gap:8px;background:#131B23;padding:5px;border-radius:999px;border:1px solid rgba(255,255,255,.08)')}>
            {filterTabs.map(([k, l]) => (
              <button key={k} onClick={() => this.setState({ clientFilter: k })} style={sx(tabStyle(k))}>{l}</button>
            ))}
          </div>
          {canEdit && (
            <button onClick={() => this.startNew()} className="hv-red" style={sx('display:flex;align-items:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:999px;padding:10px 18px;font-weight:700;font-size:13px;cursor:pointer;box-shadow:0 10px 30px rgba(224,60,49,.3)')}>
              <IcPlus s={16} />
              Novo Cliente
            </button>
          )}
        </div>

        <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
          <div style={sx('display:grid;grid-template-columns:2.2fr 0.8fr 1.4fr 1fr 1.1fr 1fr ' + (canEdit ? '0.9fr' : '') + ';gap:12px;padding:12px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Cliente</div><div>Tipo</div><div>Serviço</div><div>Mensal</div><div>Entrada</div><div>Status</div>{canEdit && <div style={sx('text-align:right')}>Ações</div>}
          </div>
          {filtered.map(c => {
            const [sl, sc] = sBadge[c.status];
            return (
              <div key={c.id} className="hv-row" style={sx('display:grid;grid-template-columns:2.2fr 0.8fr 1.4fr 1fr 1.1fr 1fr ' + (canEdit ? '0.9fr' : '') + ';gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
                <div style={sx('display:flex;align-items:center;gap:12px;min-width:0')}>
                  <div style={avatar}>{this.initials(c.name)}</div>
                  <div style={sx('min-width:0')}>
                    <div style={sx('font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{c.name}</div>
                    <div style={sx('font-size:11px;color:#7C8794;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{c.doc}</div>
                  </div>
                </div>
                <div><span style={sx(`display:inline-block;font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:6px;background:${c.type === 'PJ' ? 'rgba(62,142,222,.16)' : 'rgba(235,98,88,.16)'};color:${c.type === 'PJ' ? '#7FB4EC' : '#EB6258'}`)}>{c.type}</span></div>
                <div style={sx('font-size:12.5px;color:#B7C0CB;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{c.service}</div>
                <div style={sx('font-size:13px;font-weight:700')}>{this.fmt(c.monthly)}</div>
                <div style={sx('font-size:12.5px;color:#B7C0CB')}>{c.entry}</div>
                <div><span style={sx(`display:inline-block;font-size:11px;font-weight:700;padding:4px 11px;border-radius:999px;background:${sc}22;color:${sc}`)}>{sl}</span></div>
                {canEdit && (
                  <div style={sx('display:flex;gap:6px;justify-content:flex-end')}>
                    <button onClick={() => this.openCliente(c)} title="Editar cliente" className="hv-icon" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcEdit s={14} /></button>
                    <button onClick={() => this.confirmarExclusao('Excluir ' + c.name + ' e todas as suas cobranças? Esta ação não pode ser desfeita.', () => this.deleteCliente(c.id))} title="Excluir cliente" className="hv-danger" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcTrash s={14} /></button>
                  </div>
                )}
              </div>
            );
          })}
          {filtered.length === 0 && (
            <div style={sx('padding:26px;text-align:center;color:#7C8794;font-size:13px')}>Nenhum cliente encontrado.</div>
          )}
        </div>
      </div>
    );
  }

  /* ---------- Contratos ---------- */
  renderCon() {
    const st = this.state;
    const cBadge = { assinado: ['Assinado', '#2BB673'], aguardando: ['Aguardando assinatura', '#F2A93B'], encerrado: ['Encerrado', '#7C8794'], rascunho: ['Rascunho · não enviado', '#7C8794'] };

    return (
      <div>
        <div style={sx('display:flex;align-items:center;gap:12px;background:#131B23;border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:14px 18px;margin-bottom:18px')}>
          <div style={sx('width:36px;height:36px;border-radius:9px;background:rgba(224,60,49,.14);display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
            <IcPen />
          </div>
          <div>
            <div style={sx('font-size:13.5px;font-weight:600')}>Integração com Autentique conectada</div>
            <div style={sx('font-size:12px;color:#7C8794;margin-top:1px')}>Contratos são preenchidos com os dados do cliente e enviados para assinatura digital automaticamente.</div>
          </div>
        </div>
        <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
          <div style={sx('display:grid;grid-template-columns:2.2fr 1.6fr 1.3fr 1.3fr 1fr;gap:12px;padding:12px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Documento / Cliente</div><div>Modelo</div><div>Enviado em</div><div>Status Autentique</div><div></div>
          </div>
          {st.clients.filter(c => this.inPeriod(c.sentAt)).map(c => {
            const [sl, sc] = cBadge[c.contract] || cBadge.rascunho;
            const canSend = c.contract === 'rascunho' && this.can('editar');
            return (
              <div key={c.id} style={sx('display:grid;grid-template-columns:2.2fr 1.6fr 1.3fr 1.3fr 1fr;gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
                <div style={sx('display:flex;align-items:center;gap:12px;min-width:0')}>
                  <div style={sx('width:34px;height:42px;border-radius:5px;background:#0E1318;border:1px solid rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
                    <IcDoc s={15} c="#7C8794" />
                  </div>
                  <div style={sx('min-width:0')}>
                    <div style={sx('font-size:13px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>Contrato — {c.name.split(' ').slice(0, 2).join(' ')}</div>
                    <div style={sx('font-size:11px;color:#7C8794')}>{c.name}</div>
                  </div>
                </div>
                <div style={sx('font-size:12.5px;color:#B7C0CB;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{c.service}</div>
                <div style={sx('font-size:12.5px;color:#B7C0CB')}>{c.sentAt}</div>
                <div><span style={sx(`display:inline-block;font-size:11px;font-weight:700;padding:4px 11px;border-radius:999px;background:${sc}22;color:${sc}`)}>{sl}</span></div>
                <div style={sx('text-align:right')}>
                  {canSend ? (
                    <button onClick={() => this.sendLater(c.id)} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:8px;padding:7px 13px;font-size:11.5px;font-weight:700;cursor:pointer;white-space:nowrap')}>Enviar agora</button>
                  ) : (
                    <a style={sx('font-size:12px;color:#EB6258;font-weight:600;cursor:pointer;text-decoration:none')}>Ver →</a>
                  )}
                </div>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  /* ---------- Notas Fiscais ---------- */
  renderNf() {
    const st = this.state;
    const cv = this._cv;
    const pendingNf = st.charges.filter(c => c.status === 'pago' && !c.nfId).map(c => {
      const cl = this.client(c.clientId);
      return { id: c.id, name: cl.name, service: (c.desc || cl.service), valueFmt: this.fmt(cv(c)) };
    });

    return (
      <div>
        <div style={sx('display:flex;align-items:center;gap:12px;background:#131B23;border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:14px 18px;margin-bottom:18px')}>
          <div style={sx('width:36px;height:36px;border-radius:9px;background:rgba(43,182,115,.14);display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
            <IcShield />
          </div>
          <div>
            <div style={sx('font-size:13.5px;font-weight:600')}>Emissão de NFS-e — Prefeitura Municipal</div>
            <div style={sx('font-size:12px;color:#7C8794;margin-top:1px')}>Integração gov.br ativa. Notas de serviço são transmitidas direto ao sistema da prefeitura.</div>
          </div>
        </div>

        <div style={sx('font-size:13px;font-weight:700;color:#B7C0CB;margin:4px 2px 12px;letter-spacing:.02em')}>Aguardando emissão</div>
        <div style={sx('display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;margin-bottom:26px')}>
          {pendingNf.map(p => (
            <div key={p.id} style={sx('background:#131B23;border:1px solid rgba(242,169,59,.3);border-radius:14px;padding:18px')}>
              <div style={sx('display:flex;align-items:center;justify-content:space-between')}>
                <div style={sx('font-size:14px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{p.name}</div>
                <span style={sx('font-size:10.5px;font-weight:700;color:#F2A93B;background:rgba(242,169,59,.14);padding:4px 9px;border-radius:999px;white-space:nowrap')}>Pago · sem NF</span>
              </div>
              <div style={sx('font-size:12px;color:#7C8794;margin-top:5px')}>{p.service}</div>
              <div style={sx('display:flex;align-items:flex-end;justify-content:space-between;margin-top:16px')}>
                <div style={sx('font-size:20px;font-weight:800')}>{p.valueFmt}</div>
                {this.can('editar') && <button onClick={() => this.openNf(p.id)} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:9px;padding:9px 15px;font-size:12.5px;font-weight:700;cursor:pointer')}>Gerar NFS-e</button>}
              </div>
            </div>
          ))}
          {pendingNf.length === 0 && (
            <div style={sx('background:#131B23;border:1px dashed rgba(255,255,255,.12);border-radius:14px;padding:26px;display:flex;align-items:center;justify-content:center;color:#7C8794;font-size:13px')}>Nenhuma nota pendente 🎉</div>
          )}
        </div>

        <div style={sx('font-size:13px;font-weight:700;color:#B7C0CB;margin:4px 2px 12px;letter-spacing:.02em')}>Notas emitidas</div>
        <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
          <div style={sx('display:grid;grid-template-columns:1.1fr 2fr 1.2fr 1fr 1.3fr;gap:12px;padding:12px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Nº NFS-e</div><div>Tomador</div><div>Emissão</div><div>Valor</div><div style={sx('text-align:right')}>PDF / Ações</div>
          </div>
          {st.nfs.filter(n => this.inPeriod(n.date)).map((n, i) => (
            <div key={i} style={sx('display:grid;grid-template-columns:1.1fr 2fr 1.2fr 1fr 1.3fr;gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
              <div style={sx('font-family:ui-monospace,monospace;font-size:12.5px;font-weight:600;color:#EB6258')}>{n.number}</div>
              <div style={sx('font-size:13px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{this.client(n.clientId).name}</div>
              <div style={sx('font-size:12.5px;color:#B7C0CB')}>{n.date}</div>
              <div style={sx('font-size:13px;font-weight:700')}>{this.fmt(n.value)}</div>
              <div style={sx('text-align:right;display:flex;gap:8px;justify-content:flex-end;align-items:center')}>
                <a className="hv-link" style={sx('display:inline-flex;align-items:center;gap:5px;font-size:12px;color:#B7C0CB;font-weight:600;cursor:pointer;text-decoration:none')} onClick={() => this.flash('O PDF fica disponível quando a integração real com a prefeitura estiver ativa')}>
                  <IcDown />Baixar
                </a>
                {this.can('editar') && (
                  <button onClick={() => this.confirmarExclusao('Excluir a nota ' + n.number + ' do controle? (Cancelar uma NFS-e oficial deve ser feito também no portal da prefeitura.)', () => this.deleteNf(n.number))} title="Excluir do controle" className="hv-danger" style={sx('background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;padding:6px;border-radius:7px')}><IcTrash s={13} /></button>
                )}
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  /* ---------- Wizard Novo Cliente ---------- */
  renderWizard() {
    const st = this.state;
    const f = st.form;
    const ss = st.sendState;
    const isPJ = f.type === 'PJ';
    const dayOptions = Array.from({ length: 31 }, (_, i) => String(i + 1));

    const stepDefs = [[1, 'Cadastro'], [2, 'Acordo'], [3, 'Modelo'], [4, 'Finalizar']];
    const tStyle = (on) => `flex:1;padding:12px;border-radius:10px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.1)' : '#0E1318'};color:${on ? '#fff' : '#B7C0CB'};font-weight:600;font-size:13px;cursor:pointer;font-family:inherit`;
    const choiceStyle = (on) => `flex:1;text-align:left;padding:14px;border-radius:12px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.08)' : '#0E1318'};cursor:pointer;font-family:inherit;color:${on ? '#fff' : '#B7C0CB'}`;

    const tmpls = [['gestao', 'Prestação de Serviços', 'Gestão de redes sociais e conteúdo'], ['trafego', 'Tráfego Pago', 'Gestão de anúncios e mídia paga'], ['completa', 'Estratégia Completa', 'Pacote full de marketing e vendas']];
    const titleMap = { gestao: 'Contrato de Prestação de Serviços de Marketing', trafego: 'Contrato de Gestão de Tráfego Pago', completa: 'Contrato de Estratégia Completa de Marketing' };
    const contractTitle = titleMap[f.template] || titleMap.gestao;

    const dispName = f.name || (isPJ ? '[Razão Social]' : '[Nome do Cliente]');
    const dispDoc = f.doc || (isPJ ? '[CNPJ]' : '[CPF]');
    const monthlyNum = this.parseV(f.monthly);
    const dispMonthly = f.monthly ? this.fmt(monthlyNum) : '[valor mensal]';
    const pv = { name: dispName, doc: dispDoc, docLabel: isPJ ? 'CNPJ' : 'CPF', service: f.service, monthly: dispMonthly, payment: f.payment, payDay: f.payDay, warranty: f.warranty, initials: this.initials(f.name || 'NC') };

    const cobTotal = f.cobrancas.reduce((s, c) => s + this.parseV(c.value), 0);
    const successTitle = f.sendNow ? 'Contrato enviado pelo Autentique!' : 'Cliente cadastrado!';
    const successMsg = f.sendNow
      ? ('O documento foi enviado para ' + (f.name || 'o cliente') + ' e está aguardando assinatura. O cliente e as cobranças no Asaas já foram criados.')
      : ((f.name || 'O cliente') + ' foi cadastrado e as cobranças criadas no Asaas. O contrato está salvo como rascunho — você pode enviá-lo pelo Autentique quando quiser, na aba Contratos.');

    const MARK = 'background:#F6BCB6;padding:1px 5px;border-radius:4px;font-weight:700';
    const showFooter = !(st.step === 4 && ss !== 'idle');

    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:50')}>
        <div style={sx('width:100%;max-width:880px;max-height:92vh;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:20px;box-shadow:0 18px 48px rgba(8,12,16,.45);display:flex;flex-direction:column;overflow:hidden;animation:kesPop .25s ease both')}>
          {/* cabeçalho */}
          <div style={sx('padding:22px 28px;border-bottom:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-between')}>
            <div style={sx('display:flex;align-items:center;gap:14px')}>
              <img src="assets/logo-mark-red.png" alt="" style={sx('width:28px;height:auto')} />
              <div style={sx('font-size:17px;font-weight:800;letter-spacing:-.02em')}>Novo Cliente</div>
            </div>
            <button onClick={() => this.closeWizard()} className="hv-close" style={sx('width:34px;height:34px;border-radius:9px;background:#0E1318;border:1px solid rgba(255,255,255,.1);color:#B7C0CB;cursor:pointer;display:flex;align-items:center;justify-content:center')}>
              <IcX s={17} />
            </button>
          </div>
          {/* passos */}
          <div style={sx('display:flex;gap:0;padding:16px 28px 0;border-bottom:1px solid rgba(255,255,255,.08)')}>
            {stepDefs.map(([n, l]) => {
              const done = st.step > n, cur = st.step === n;
              return (
                <div key={n} style={sx('flex:1;display:flex;flex-direction:column;gap:9px;padding-bottom:14px')}>
                  <div style={sx('display:flex;align-items:center;gap:9px')}>
                    <div style={sx(`width:24px;height:24px;border-radius:999px;display:flex;align-items:center;justify-content:center;font-size:11.5px;font-weight:700;flex-shrink:0;${done ? 'background:#2BB673;color:#fff' : cur ? 'background:#E03C31;color:#fff' : 'background:#28323E;color:#7C8794'}`)}>{done ? '✓' : String(n)}</div>
                    <div style={sx(`font-size:12.5px;font-weight:${cur ? '700' : '600'};color:${cur ? '#fff' : done ? '#B7C0CB' : '#7C8794'}`)}>{l}</div>
                  </div>
                  <div style={sx(`height:3px;border-radius:3px;background:${cur || done ? '#E03C31' : 'rgba(255,255,255,.08)'}`)}></div>
                </div>
              );
            })}
          </div>

          <div style={sx('flex:1;overflow-y:auto;padding:26px 28px')}>
            {/* PASSO 1: CADASTRO */}
            {st.step === 1 && (
              <div style={sx('animation:kesFade .25s ease both')}>
                <div style={sx('font-size:13px;color:#7C8794;margin-bottom:18px')}>Dados de identificação do cliente.</div>
                <div style={sx('display:flex;gap:10px;margin-bottom:18px')}>
                  <button onClick={() => this.setF('type', 'PJ')} style={sx(tStyle(isPJ))}>Pessoa Jurídica</button>
                  <button onClick={() => this.setF('type', 'PF')} style={sx(tStyle(!isPJ))}>Pessoa Física</button>
                </div>
                <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:14px')}>
                  <label style={sx(COL + ';grid-column:span 2')}>
                    <span style={sx(LBL)}>{isPJ ? 'Razão social' : 'Nome completo'} <span style={sx('color:#EB6258')}>*</span></span>
                    <input value={f.name} onChange={e => this.setF('name', e.target.value)} placeholder={isPJ ? 'Ex.: Studio Bloom Arquitetura Ltda' : 'Ex.: Maria Silva'} className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>{isPJ ? 'CNPJ' : 'CPF'} <span style={sx('color:#EB6258')}>*</span>{f.doc.replace(/\D/g, '') && (isPJ ? validaCNPJ(f.doc) : validaCPF(f.doc)) ? <span style={sx('color:#2BB673;font-weight:700')}> ✓ válido</span> : null}</span>
                    <input value={f.doc} onChange={e => this.setF('doc', isPJ ? mCNPJ(e.target.value) : mCPF(e.target.value))} placeholder={isPJ ? '00.000.000/0000-00' : '000.000.000-00'} className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Telefone / WhatsApp</span>
                    <input value={f.phone} onChange={e => this.setF('phone', mFone(e.target.value))} placeholder="(54) 9 9999-9999" className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL + ';grid-column:span 2')}>
                    <span style={sx(LBL)}>E-mail <span style={sx('color:#7C8794;font-weight:500')}>(usado na cobrança do Asaas e na assinatura pelo Autentique)</span></span>
                    <input value={f.email} onChange={e => this.setF('email', e.target.value)} placeholder="contato@empresa.com.br" className="kes-in" style={sx(IN)} />
                  </label>
                </div>
                <div style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase;margin:20px 0 4px')}>Endereço <span style={sx('color:#7C8794;font-weight:500;text-transform:none;letter-spacing:0')}> — usado no boleto (Asaas) e na nota fiscal (NFS-e)</span></div>
                <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:10px')}>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>CEP</span>
                    <input value={f.cep} onChange={e => this.setF('cep', mCEP(e.target.value))} placeholder="95000-000" className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Bairro</span>
                    <input value={f.bairro} onChange={e => this.setF('bairro', e.target.value)} placeholder="Centro" className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL + ';grid-column:span 2')}>
                    <span style={sx(LBL)}>Endereço (rua/avenida)</span>
                    <div style={sx('display:flex;gap:10px')}>
                      <input value={f.endereco} onChange={e => this.setF('endereco', e.target.value)} placeholder="Rua Exemplo" className="kes-in" style={sx(IN + ';flex:1')} />
                      <input value={f.numero} onChange={e => this.setF('numero', e.target.value)} placeholder="Nº" className="kes-in" style={sx(IN + ';width:90px')} />
                    </div>
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Cidade</span>
                    <input value={f.cidade} onChange={e => this.setF('cidade', e.target.value)} placeholder="Caxias do Sul" className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>UF</span>
                    <input value={f.uf} onChange={e => this.setF('uf', e.target.value.toUpperCase().slice(0, 2))} placeholder="RS" className="kes-in" style={sx(IN)} />
                  </label>
                </div>
              </div>
            )}

            {/* PASSO 2: ACORDO */}
            {st.step === 2 && (
              <div style={sx('animation:kesFade .25s ease both')}>
                <div style={sx('font-size:13px;color:#7C8794;margin-bottom:18px')}>Condições negociadas — estes dados serão inseridos automaticamente no contrato.</div>
                <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:14px')}>
                  <label style={sx(COL + ';grid-column:span 2')}>
                    <span style={sx(LBL)}>Serviço contratado</span>
                    <select value={f.service} onChange={e => this.setF('service', e.target.value)} className="kes-in" style={sx(IN)}>
                      <option>Gestão de Redes Sociais</option>
                      <option>Tráfego Pago</option>
                      <option>Gestão + Tráfego Pago</option>
                      <option>Consultoria de Marketing</option>
                      <option>Prospecção BDR/SDR</option>
                      <option>Estratégia Completa</option>
                    </select>
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Valor mensal (R$)</span>
                    <input value={f.monthly} onChange={e => this.setF('monthly', e.target.value)} placeholder="2.800,00" className="kes-in" style={sx(IN)} />
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Forma de pagamento</span>
                    <select value={f.payment} onChange={e => this.setF('payment', e.target.value)} className="kes-in" style={sx(IN)}>
                      <option>PIX</option>
                      <option>Boleto bancário</option>
                      <option>Cartão de crédito</option>
                      <option>Cliente escolhe no link (Asaas)</option>
                    </select>
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Dia de vencimento</span>
                    <select value={f.payDay} onChange={e => this.setF('payDay', e.target.value)} className="kes-in" style={sx(IN)}>
                      {dayOptions.map(d => <option key={d} value={d}>{d}</option>)}
                    </select>
                  </label>
                  <label style={sx(COL)}>
                    <span style={sx(LBL)}>Garantia / Fidelidade</span>
                    <select value={f.warranty} onChange={e => this.setF('warranty', e.target.value)} className="kes-in" style={sx(IN)}>
                      <option>Sem fidelidade</option>
                      <option>Fidelidade de 6 meses</option>
                      <option>Fidelidade de 12 meses</option>
                      <option>Garantia de resultado 90 dias</option>
                    </select>
                  </label>
                </div>
                <div style={sx('margin-top:22px;display:flex;align-items:center;justify-content:space-between;margin-bottom:10px')}>
                  <div style={sx('display:flex;align-items:center;gap:9px')}>
                    <span style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase')}>Cobranças no Asaas</span>
                    <span style={sx('display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;color:#2BB673;background:rgba(43,182,115,.14);padding:3px 8px;border-radius:999px')}><span style={sx('width:6px;height:6px;border-radius:9px;background:#2BB673;display:inline-block')}></span>Conectado</span>
                  </div>
                  <button onClick={() => this.addCobranca()} className="hv-ghost" style={sx('display:flex;align-items:center;gap:6px;background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:7px 12px;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit')}>
                    <IcPlus s={14} />
                    Adicionar cobrança
                  </button>
                </div>
                <div style={sx('font-size:11.5px;color:#7C8794;margin-bottom:12px')}>Crie uma ou mais cobranças — útil quando o cliente paga valores diferentes ao longo do contrato (ex.: setup, mensalidade, produção de vídeo).</div>
                <div style={sx('display:flex;flex-direction:column;gap:10px')}>
                  {f.cobrancas.map(cb => (
                    <div key={cb.id} className="gwrap" style={sx('display:grid;grid-template-columns:1.4fr 0.9fr 1.2fr 1.1fr auto;gap:10px;align-items:end;background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:12px')}>
                      <label style={sx('display:flex;flex-direction:column;gap:5px')}>
                        <span style={sx('font-size:10.5px;font-weight:600;color:#7C8794')}>Descrição</span>
                        <input value={cb.desc} onChange={e => this.updateCobranca(cb.id, 'desc', e.target.value)} placeholder="Ex.: Mensalidade" className="kes-in" style={sx('background:#131B23;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:9px 11px;color:#fff;font-size:12.5px;outline:none')} />
                      </label>
                      <label style={sx('display:flex;flex-direction:column;gap:5px')}>
                        <span style={sx('font-size:10.5px;font-weight:600;color:#7C8794')}>Valor (R$)</span>
                        <input value={cb.value} onChange={e => this.updateCobranca(cb.id, 'value', e.target.value)} placeholder="0,00" className="kes-in" style={sx('background:#131B23;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:9px 11px;color:#fff;font-size:12.5px;outline:none')} />
                      </label>
                      <label style={sx('display:flex;flex-direction:column;gap:5px')}>
                        <span style={sx('font-size:10.5px;font-weight:600;color:#7C8794')}>Recorrência</span>
                        <select value={cb.recur} onChange={e => this.updateCobranca(cb.id, 'recur', e.target.value)} className="kes-in" style={sx('background:#131B23;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:9px 11px;color:#fff;font-size:12.5px;outline:none')}>
                          <option>Mensal recorrente</option><option>Parcela única</option><option>Parcelada 3x</option><option>Parcelada 6x</option><option>Parcelada 12x</option>
                        </select>
                      </label>
                      <label style={sx('display:flex;flex-direction:column;gap:5px')}>
                        <span style={sx('font-size:10.5px;font-weight:600;color:#7C8794')}>1º vencimento</span>
                        <input type="date" value={cb.due} onChange={e => this.updateCobranca(cb.id, 'due', e.target.value)} className="kes-in" style={sx('background:#131B23;border:1px solid rgba(255,255,255,.12);border-radius:8px;padding:8px 10px;color:#fff;font-size:12.5px;outline:none;color-scheme:dark;font-family:inherit')} />
                      </label>
                      {f.cobrancas.length > 1 && (
                        <button onClick={() => this.removeCobranca(cb.id)} className="hv-danger" style={sx('width:38px;height:38px;border-radius:8px;background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;align-items:center;justify-content:center')}>
                          <IcTrash s={15} />
                        </button>
                      )}
                    </div>
                  ))}
                </div>
                <div style={sx('margin-top:14px;background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:16px;display:flex;align-items:center;justify-content:space-between')}>
                  <div style={sx('font-size:12.5px;color:#7C8794')}>Total das cobranças</div>
                  <div style={sx('font-size:15px;font-weight:800')}>{this.fmt(cobTotal)}</div>
                </div>
              </div>
            )}

            {/* PASSO 3: MODELO */}
            {st.step === 3 && (
              <div style={sx('animation:kesFade .25s ease both')}>
                <div style={sx('font-size:13px;color:#7C8794;margin-bottom:18px')}>Escolha o modelo de contrato. Os dados do cliente e do acordo serão mesclados automaticamente.</div>
                <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:20px')}>
                  {tmpls.map(([k, n, d]) => {
                    const on = f.template === k;
                    return (
                      <button key={k} onClick={() => this.setF('template', k)} style={sx(`display:flex;flex-direction:column;align-items:flex-start;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.1)'};background:${on ? 'rgba(224,60,49,.08)' : '#0E1318'};border-radius:12px;padding:16px;cursor:pointer;color:${on ? '#EB6258' : '#B7C0CB'};font-family:inherit;text-align:left`)}>
                        <div style={sx('display:flex;align-items:center;justify-content:space-between;width:100%')}>
                          <IcDoc s={20} />
                          <div style={sx(`width:18px;height:18px;border-radius:999px;border:2px solid ${on ? '#E03C31' : 'rgba(255,255,255,.2)'};background:${on ? '#E03C31' : 'transparent'}`)}></div>
                        </div>
                        <div style={sx('font-size:13px;font-weight:700;text-align:left;margin-top:10px;line-height:1.3')}>{n}</div>
                        <div style={sx('font-size:11px;color:#7C8794;text-align:left;margin-top:3px')}>{d}</div>
                      </button>
                    );
                  })}
                  <div className="hv-ghost" style={sx('border:1.5px dashed rgba(255,255,255,.18);border-radius:12px;padding:16px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:#7C8794;cursor:pointer')} onClick={() => this.flash('O envio de modelos .docx fica disponível na integração real')}>
                    <IcUpload />
                    <div style={sx('font-size:12px;font-weight:600')}>Enviar novo modelo (.docx)</div>
                  </div>
                </div>

                <div style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase;margin-bottom:10px')}>Pré-visualização — dados mesclados</div>
                <div style={sx('background:#F7F8F9;color:#131B23;border-radius:12px;padding:26px 30px;font-size:12.5px;line-height:1.7;max-height:230px;overflow-y:auto')}>
                  <div style={sx('text-align:center;font-weight:800;font-size:14px;letter-spacing:.02em;margin-bottom:14px;text-transform:uppercase')}>{contractTitle}</div>
                  <p style={sx('margin-bottom:10px')}>Pelo presente instrumento particular, de um lado <strong>{this.state.empresa.razao || 'KeS Assessoria de Marketing'}</strong>{this.state.empresa.cnpj ? <span>, CNPJ nº <strong>{this.state.empresa.cnpj}</strong></span> : null}, doravante <strong>CONTRATADA</strong>, e de outro lado <mark style={sx(MARK)}>{pv.name}</mark>, inscrito(a) sob o {pv.docLabel} nº <mark style={sx(MARK)}>{pv.doc}</mark>, doravante <strong>CONTRATANTE</strong>, têm entre si justo e acordado o seguinte:</p>
                  <p style={sx('margin-bottom:8px')}><strong>Cláusula 1ª — Objeto.</strong> A CONTRATADA prestará serviços de <mark style={sx(MARK)}>{pv.service}</mark>.</p>
                  <p style={sx('margin-bottom:8px')}><strong>Cláusula 2ª — Remuneração.</strong> O valor mensal será de <mark style={sx(MARK)}>{pv.monthly}</mark>, pago via <mark style={sx(MARK)}>{pv.payment}</mark>, com vencimento todo dia <mark style={sx(MARK)}>{pv.payDay}</mark>.</p>
                  <p style={sx('margin-bottom:8px')}><strong>Cláusula 3ª — Vigência e garantia.</strong> O presente contrato vigora sob a condição de <mark style={sx(MARK)}>{pv.warranty}</mark>.</p>
                  <p style={sx('color:#4B5662')}>Cláusula 4ª — Foro. Fica eleito o foro da comarca de Caxias do Sul/RS para dirimir quaisquer questões oriundas deste contrato.</p>
                </div>
              </div>
            )}

            {/* PASSO 4: AUTENTIQUE */}
            {st.step === 4 && (
              <div style={sx('animation:kesFade .25s ease both')}>
                {ss === 'idle' && (
                  <div>
                    <div style={sx('display:flex;gap:10px;margin-bottom:18px')}>
                      <button onClick={() => this.setSendNow(true)} style={sx(choiceStyle(f.sendNow))}>
                        <div style={sx('font-size:13px;font-weight:700')}>Enviar contrato agora</div>
                        <div style={sx('font-size:11px;color:#7C8794;margin-top:3px;font-weight:500')}>Gera o PDF e envia pelo Autentique para assinatura</div>
                      </button>
                      <button onClick={() => this.setSendNow(false)} style={sx(choiceStyle(!f.sendNow))}>
                        <div style={sx('font-size:13px;font-weight:700')}>Cadastrar sem enviar</div>
                        <div style={sx('font-size:11px;color:#7C8794;margin-top:3px;font-weight:500')}>Salva o contrato como rascunho — envie depois</div>
                      </button>
                    </div>
                    {f.sendNow && (
                      <div>
                        <div style={sx('font-size:13px;color:#7C8794;margin-bottom:14px')}>Confira o envelope que será enviado para assinatura digital pelo Autentique.</div>
                        <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.1);border-radius:14px;overflow:hidden')}>
                          <div style={sx('display:flex;align-items:center;gap:13px;padding:18px 20px;border-bottom:1px solid rgba(255,255,255,.08)')}>
                            <div style={sx('width:42px;height:52px;border-radius:6px;background:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
                              <IcDoc s={20} c="#131B23" />
                            </div>
                            <div style={sx('min-width:0')}>
                              <div style={sx('font-size:14px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{contractTitle}</div>
                              <div style={sx('font-size:12px;color:#7C8794;margin-top:2px')}>PDF gerado · {pv.name}</div>
                            </div>
                          </div>
                          <div style={sx('padding:18px 20px')}>
                            <div style={sx('font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700;margin-bottom:12px')}>Signatários</div>
                            <div style={sx('display:flex;align-items:center;gap:11px;margin-bottom:14px')}>
                              <div style={sx('width:34px;height:34px;border-radius:999px;background:#28323E;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#EB6258')}>{pv.initials}</div>
                              <div style={sx('flex:1;min-width:0')}>
                                <div style={sx('font-size:13px;font-weight:600')}>{pv.name}</div>
                                <input value={f.signerEmail} onChange={e => this.setF('signerEmail', e.target.value)} placeholder="e-mail do cliente para assinatura" className="kes-underline" style={sx('background:transparent;border:none;border-bottom:1px solid rgba(255,255,255,.15);padding:4px 0;color:#B7C0CB;font-size:12px;outline:none;width:100%;margin-top:2px')} />
                              </div>
                              <span style={sx('font-size:10.5px;color:#F2A93B;font-weight:600;white-space:nowrap')}>Assina como CONTRATANTE</span>
                            </div>
                            {f.extraSigners.map(s => (
                              <div key={s.id} style={sx('display:flex;align-items:center;gap:11px;margin-bottom:14px')}>
                                <div style={sx('width:34px;height:34px;border-radius:999px;background:#28323E;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#EB6258;flex-shrink:0')}>{s.name ? this.initials(s.name) : '+'}</div>
                                <div style={sx('flex:1;min-width:0;display:flex;flex-direction:column;gap:3px')}>
                                  <input value={s.name} onChange={e => this.updateSigner(s.id, 'name', e.target.value)} placeholder="Nome do signatário" className="kes-underline" style={sx('background:transparent;border:none;border-bottom:1px solid rgba(255,255,255,.15);padding:3px 0;color:#fff;font-size:13px;font-weight:600;outline:none;width:100%')} />
                                  <input value={s.email} onChange={e => this.updateSigner(s.id, 'email', e.target.value)} placeholder="e-mail para assinatura" className="kes-underline" style={sx('background:transparent;border:none;border-bottom:1px solid rgba(255,255,255,.15);padding:3px 0;color:#B7C0CB;font-size:12px;outline:none;width:100%')} />
                                </div>
                                <span style={sx('font-size:10.5px;color:#F2A93B;font-weight:600;white-space:nowrap')}>Assina como CONTRATANTE</span>
                                <button onClick={() => this.removeSigner(s.id)} className="hv-danger" style={sx('width:28px;height:28px;border-radius:7px;background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
                                  <IcX s={13} />
                                </button>
                              </div>
                            ))}
                            <div style={sx('display:flex;align-items:center;gap:11px')}>
                              <div style={sx('width:34px;height:34px;border-radius:999px;background:#28323E;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#EB6258')}>KS</div>
                              <div style={sx('flex:1;min-width:0')}>
                                <div style={sx('font-size:13px;font-weight:600')}>Ketherly Schmidt — {this.state.empresa.razao || 'KeS Assessoria'}</div>
                                <div style={sx('font-size:12px;color:#7C8794;margin-top:2px')}>{this.state.empresa.email || 'kesassessoria@kesassessoria.com.br'}</div>
                              </div>
                              <span style={sx('font-size:10.5px;color:#2BB673;font-weight:600;white-space:nowrap')}>Assina como CONTRATADA</span>
                            </div>
                            <button onClick={() => this.addSigner()} className="hv-ghost" style={sx('display:flex;align-items:center;gap:7px;background:transparent;color:#B7C0CB;border:1px dashed rgba(255,255,255,.2);border-radius:9px;padding:9px 14px;font-size:12.5px;font-weight:600;cursor:pointer;font-family:inherit;margin-top:14px;width:100%;justify-content:center')}>
                              <IcPlus s={14} />
                              Adicionar signatário
                            </button>
                          </div>
                        </div>
                        <label style={sx('display:flex;align-items:center;gap:9px;margin-top:16px;font-size:12.5px;color:#B7C0CB;cursor:pointer')}>
                          <input type="checkbox" checked={f.whats} onChange={e => this.setF('whats', e.target.checked)} style={sx('width:16px;height:16px;accent-color:#E03C31')} />
                          Notificar o cliente também por WhatsApp
                        </label>
                      </div>
                    )}
                    {!f.sendNow && (
                      <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.1);border-radius:14px;padding:20px;display:flex;gap:14px;align-items:flex-start')}>
                        <div style={sx('width:38px;height:38px;border-radius:9px;background:rgba(124,135,148,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0')}>
                          <IcDoc s={20} c="#B7C0CB" />
                        </div>
                        <div>
                          <div style={sx('font-size:14px;font-weight:700')}>Contrato salvo como rascunho</div>
                          <div style={sx('font-size:12.5px;color:#7C8794;margin-top:4px;line-height:1.5')}>O cliente será cadastrado e as cobranças criadas no Asaas. O contrato fica pronto e você poderá enviá-lo pelo Autentique a qualquer momento na aba <strong style={sx('color:#B7C0CB')}>Contratos</strong>.</div>
                        </div>
                      </div>
                    )}
                  </div>
                )}

                {ss === 'sending' && (
                  <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:50px 20px;gap:18px')}>
                    <div style={sx('width:46px;height:46px;border:3px solid rgba(255,255,255,.12);border-top-color:#E03C31;border-radius:999px;animation:kesSpin .8s linear infinite')}></div>
                    <div style={sx('font-size:14px;font-weight:600')}>Enviando para o Autentique…</div>
                    <div style={sx('font-size:12px;color:#7C8794')}>Gerando PDF e disparando solicitação de assinatura</div>
                  </div>
                )}

                {ss === 'done' && (
                  <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:36px 20px;gap:14px;animation:kesPop .3s ease both')}>
                    <div style={sx('width:60px;height:60px;border-radius:999px;background:rgba(43,182,115,.15);display:flex;align-items:center;justify-content:center')}>
                      <IcCheck s={30} c="#2BB673" />
                    </div>
                    <div style={sx('font-size:18px;font-weight:800')}>{successTitle}</div>
                    <div style={sx('font-size:13px;color:#B7C0CB;text-align:center;max-width:440px;line-height:1.5')}>{successMsg}</div>
                    <div style={sx('display:flex;gap:10px;margin-top:8px')}>
                      <button onClick={() => this.goConClose()} style={sx('background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:11px 18px;font-size:13px;font-weight:600;cursor:pointer')}>Ver contratos</button>
                      <button onClick={() => this.closeWizard()} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px 18px;font-size:13px;font-weight:700;cursor:pointer')}>Concluir</button>
                    </div>
                  </div>
                )}
              </div>
            )}
          </div>

          {/* rodapé */}
          {showFooter && (
            <div style={sx('padding:14px 28px 16px;border-top:1px solid rgba(255,255,255,.08)')}>
              {!!st.wizErro && (
                <div style={sx('font-size:12.5px;color:#EB6258;font-weight:600;margin-bottom:10px;display:flex;align-items:center;gap:8px')}>
                  <span style={sx('width:7px;height:7px;border-radius:9px;background:#E03C31;display:inline-block;flex-shrink:0')}></span>{st.wizErro}
                </div>
              )}
              <div style={sx('display:flex;align-items:center;justify-content:space-between')}>
                <button onClick={() => this.prevStep()} style={sx(`background:transparent;color:${st.step === 1 ? '#3a4450' : '#B7C0CB'};border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:11px 18px;font-size:13.5px;font-weight:600;cursor:${st.step === 1 ? 'default' : 'pointer'};font-family:inherit;pointer-events:${st.step === 1 ? 'none' : 'auto'}`)}>← Voltar</button>
                <button onClick={() => this.nextStep()} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px 22px;font-size:13.5px;font-weight:700;cursor:pointer;box-shadow:0 8px 24px rgba(224,60,49,.3)')}>
                  {st.step === 3 ? 'Avançar para finalizar' : st.step === 4 ? (f.sendNow ? 'Enviar pelo Autentique' : 'Concluir cadastro') : 'Continuar'}
                </button>
              </div>
            </div>
          )}
        </div>
      </div>
    );
  }

  /* ---------- Modal NFS-e ---------- */
  renderNfModal() {
    const st = this.state;
    const cv = this._cv;
    const nf = st.nf;
    const ch = st.charges.find(c => c.id === nf.chargeId);
    const cl = ch ? this.client(ch.clientId) : {};
    const v = ch ? cv(ch) : 0;
    const emp = st.empresa;
    const aliq = parseFloat(String(emp.aliquota).replace(',', '.')) || 0;
    const docDigitos = String(cl.doc || '').replace(/\D/g, '');
    const pendencias = [];
    if (!emp.cnpj || !emp.codTrib || !aliq) pendencias.push('Complete os dados fiscais da empresa (CNPJ, código de tributação e alíquota) em Administração → Integrações.');
    if (!docDigitos) pendencias.push('Este cliente está sem CPF/CNPJ — a NFS-e exige o documento do tomador.');
    if (!cl.endereco && !cl.cep) pendencias.push('Este cliente está sem endereço — recomendado para a NFS-e.');

    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:60')}>
        <div style={sx('width:100%;max-width:440px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          {nf.stage === 'confirm' && (
            <div>
              <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Emissão de NFS-e</div>
              <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>Confirmar emissão</div>
              <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:11px')}>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Tomador</span><span style={sx('font-weight:600;text-align:right')}>{cl.name}</span></div>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Documento</span><span style={sx('font-weight:600;text-align:right;color:' + (docDigitos ? '#fff' : '#EB6258'))}>{cl.doc || 'não informado'}</span></div>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Serviço</span><span style={sx('font-weight:600;text-align:right')}>{ch && (ch.desc || cl.service)}</span></div>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Cód. tributação nacional</span><span style={sx('font-weight:600;text-align:right;color:' + (emp.codTrib ? '#B7C0CB' : '#EB6258'))}>{emp.codTrib || 'não configurado'}</span></div>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Valor do serviço</span><span style={sx('font-weight:700')}>{this.fmt(v)}</span></div>
                <div style={sx('display:flex;justify-content:space-between;font-size:13px;border-top:1px solid rgba(255,255,255,.07);padding-top:10px')}><span style={sx('color:#7C8794')}>ISS{aliq ? ' (' + String(emp.aliquota) + '%)' : ''}</span><span style={sx('font-weight:600;color:#B7C0CB')}>{aliq ? this.fmt(v * aliq / 100) : '—'}</span></div>
              </div>
              {pendencias.length > 0 && (
                <div style={sx('background:rgba(242,169,59,.1);border:1px solid rgba(242,169,59,.35);border-radius:10px;padding:11px 13px;margin-top:12px;display:flex;flex-direction:column;gap:6px')}>
                  {pendencias.map((pd, i) => <div key={i} style={sx('font-size:11.5px;color:#F2A93B;line-height:1.5')}>⚠ {pd}</div>)}
                </div>
              )}
              <div style={sx('font-size:11.5px;color:#7C8794;margin-top:14px;line-height:1.5')}>A nota será transmitida ao sistema da Prefeitura via integração gov.br. Esta ação não pode ser desfeita.</div>
              <div style={sx('display:flex;gap:10px;margin-top:20px')}>
                <button onClick={() => this.closeNf()} style={sx(BTN_CANCEL)}>Cancelar</button>
                <button onClick={() => this.confirmNf()} className="hv-red" style={sx('flex:1.4;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:pointer')}>Emitir nota fiscal</button>
              </div>
            </div>
          )}
          {nf.stage === 'loading' && (
            <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:16px')}>
              <div style={sx('width:44px;height:44px;border:3px solid rgba(255,255,255,.12);border-top-color:#E03C31;border-radius:999px;animation:kesSpin .8s linear infinite')}></div>
              <div style={sx('font-size:14px;font-weight:600')}>Transmitindo à Prefeitura…</div>
              <div style={sx('font-size:12px;color:#7C8794;text-align:center')}>Aguardando autorização do município via gov.br</div>
            </div>
          )}
          {nf.stage === 'done' && (
            <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:18px 10px;gap:13px;animation:kesPop .3s ease both')}>
              <div style={sx('width:56px;height:56px;border-radius:999px;background:rgba(43,182,115,.15);display:flex;align-items:center;justify-content:center')}>
                <IcCheck s={28} c="#2BB673" />
              </div>
              <div style={sx('font-size:17px;font-weight:800')}>NFS-e autorizada!</div>
              <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:12px 16px;text-align:center')}>
                <div style={sx('font-size:11px;color:#7C8794')}>Número da nota</div>
                <div style={sx('font-family:ui-monospace,monospace;font-size:18px;font-weight:700;color:#EB6258;margin-top:2px')}>{nf.number || ''}</div>
              </div>
              <button onClick={() => this.closeNf()} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px 26px;font-size:13px;font-weight:700;cursor:pointer;margin-top:4px')}>Concluir</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  /* ---------- Modal Nova Cobrança (Asaas) ---------- */
  renderCobModal() {
    const st = this.state;
    const cob = st.cob;
    const parseCob = this.parseV(cob.value);
    const cobCanCreate = !!(cob.clientId && parseCob > 0);
    const cobActiveClients = st.clients.filter(c => c.status !== 'inativo');
    const dayOptions = Array.from({ length: 31 }, (_, i) => String(i + 1));

    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:64')}>
        <div style={sx('width:100%;max-width:470px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          {cob.stage === 'form' && (
            <div>
              <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:6px')}>
                <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31')}>Asaas</div>
                <span style={sx('display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;color:#2BB673;background:rgba(43,182,115,.14);padding:3px 8px;border-radius:999px')}><span style={sx('width:6px;height:6px;border-radius:9px;background:#2BB673;display:inline-block')}></span>Conectado</span>
              </div>
              <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>{cob.id ? 'Editar cobrança' : 'Nova cobrança'}</div>
              <div style={sx('display:flex;flex-direction:column;gap:13px')}>
                <label style={sx(COL6)}>
                  <span style={sx(LBL)}>Cliente</span>
                  <select value={cob.clientId} onChange={e => this.setCob('clientId', e.target.value)} className="kes-in" style={sx(IN13)}>
                    <option value="">Selecione um cliente…</option>
                    {cobActiveClients.map(o => <option key={o.id} value={String(o.id)}>{o.name}</option>)}
                  </select>
                </label>
                <label style={sx(COL6)}>
                  <span style={sx(LBL)}>Descrição</span>
                  <input value={cob.desc} onChange={e => this.setCob('desc', e.target.value)} placeholder="Ex.: Mensalidade junho" className="kes-in" style={sx(IN13)} />
                </label>
                <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
                  <label style={sx(COL6)}>
                    <span style={sx(LBL)}>Tipo de cobrança</span>
                    <select value={cob.recur} onChange={e => this.setCob('recur', e.target.value)} className="kes-in" style={sx(IN13)}>
                      <option value="unica">Valor único</option>
                      <option value="parcelada">Parcelada (nº definido)</option>
                      <option value="mensal">Mensal — assinatura (sem fim)</option>
                    </select>
                  </label>
                  <label style={sx(COL6)}>
                    <span style={sx(LBL)}>Forma de pagamento</span>
                    <select value={cob.payment} onChange={e => this.setCob('payment', e.target.value)} className="kes-in" style={sx(IN13)}><option>PIX</option><option>Boleto</option><option>Cartão</option><option>Cliente escolhe</option></select>
                  </label>
                </div>
                <div className="g3" style={sx('display:grid;grid-template-columns:' + (cob.recur === 'parcelada' ? '1fr 1fr 0.8fr' : '1fr 1fr') + ';gap:10px')}>
                  <label style={sx(COL6)}>
                    <span style={sx(LBL)}>Valor (R$)</span>
                    <input value={cob.value} onChange={e => this.setCob('value', e.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
                  </label>
                  <label style={sx(COL6)}>
                    <span style={sx(LBL)}>Vencimento</span>
                    <input type="date" value={cob.due} onChange={e => this.setCob('due', e.target.value)} className="kes-in" style={sx(IN13 + ';color-scheme:dark')} />
                  </label>
                  {cob.recur === 'parcelada' && (
                    <label style={sx(COL6)}>
                      <span style={sx(LBL)}>Parcelas</span>
                      <input value={cob.installments} onChange={e => this.setCob('installments', e.target.value)} placeholder="12" className="kes-in" style={sx(IN13 + ';text-align:center')} />
                    </label>
                  )}
                </div>
              </div>
              <div style={sx('display:flex;gap:10px;margin-top:20px')}>
                <button onClick={() => this.closeCob()} style={sx(BTN_CANCEL)}>Cancelar</button>
                <button onClick={() => this.createCob()} style={sx(`flex:1.4;background:${cobCanCreate ? '#E03C31' : '#3a2a28'};color:${cobCanCreate ? '#fff' : '#7C8794'};border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:${cobCanCreate ? 'pointer' : 'not-allowed'}`)}>{cob.id ? 'Salvar alterações' : 'Criar e enviar no Asaas'}</button>
              </div>
            </div>
          )}
          {cob.stage === 'loading' && (
            <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:16px')}>
              <div style={sx('width:44px;height:44px;border:3px solid rgba(255,255,255,.12);border-top-color:#E03C31;border-radius:999px;animation:kesSpin .8s linear infinite')}></div>
              <div style={sx('font-size:14px;font-weight:600')}>Criando cobrança no Asaas…</div>
              <div style={sx('font-size:12px;color:#7C8794;text-align:center')}>Gerando link de pagamento e enviando ao cliente</div>
            </div>
          )}
          {cob.stage === 'done' && (
            <div style={sx('display:flex;flex-direction:column;align-items:center;justify-content:center;padding:18px 10px;gap:13px;animation:kesPop .3s ease both')}>
              <div style={sx('width:56px;height:56px;border-radius:999px;background:rgba(43,182,115,.15);display:flex;align-items:center;justify-content:center')}><IcCheck s={28} c="#2BB673" /></div>
              <div style={sx('font-size:17px;font-weight:800')}>Cobrança criada!</div>
              <div style={sx('font-size:13px;color:#B7C0CB;text-align:center;max-width:330px;line-height:1.5')}>{this.fmt(parseCob)} enviada via Asaas. O cliente recebe o link de pagamento por e-mail e WhatsApp.</div>
              <button onClick={() => this.closeCob()} className="hv-red" style={sx('background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px 26px;font-size:13px;font-weight:700;cursor:pointer;margin-top:4px')}>Concluir</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  /* ---------- Modal Nova Despesa ---------- */
  renderDespesaModal() {
    const st = this.state;
    const d = st.despesa;
    const dspParse = this.parseV(d.value);
    const catTabStyle = (on) => `flex:1;padding:9px;border-radius:8px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.1)' : '#0E1318'};color:${on ? '#fff' : '#B7C0CB'};font-weight:600;font-size:12.5px;cursor:pointer;font-family:inherit`;

    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:64')}>
        <div style={sx('width:100%;max-width:470px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Saída</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>{d.id ? 'Editar despesa' : 'Nova despesa'}</div>
          <div style={sx('display:flex;flex-direction:column;gap:13px')}>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Descrição</span>
              <input value={d.desc} onChange={e => this.setDespesa('desc', e.target.value)} placeholder={'Ex.: Meta Ads — ' + MES_LOWER} className="kes-in" style={sx(IN13)} />
            </label>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Categoria</span>
                <select value={d.catId} onChange={e => this.setDespesa('catId', e.target.value)} className="kes-in" style={sx(IN13)}>
                  {st.categories.filter(c => c.type === 'saida').map(o => <option key={o.id} value={String(o.id)}>{o.name}</option>)}
                </select>
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Valor (R$)</span>
                <input value={d.value} onChange={e => this.setDespesa('value', e.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
              </label>
            </div>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Fornecedor</span>
                <input value={d.supplier} onChange={e => this.setDespesa('supplier', e.target.value)} placeholder="Opcional" className="kes-in" style={sx(IN13)} />
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Data</span>
                <input value={d.date} onChange={e => this.setDespesa('date', e.target.value)} placeholder="dd/mm/aaaa" className="kes-in" style={sx(IN13)} />
              </label>
            </div>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Recorrência</span>
              <select value={d.recur} onChange={e => this.setDespesa('recur', e.target.value)} className="kes-in" style={sx(IN13)}>
                <option value="unica">Única (lançamento único)</option>
                <option value="mensal">Mensal (recorrente)</option>
              </select>
            </label>
            <div style={sx(COL6)}>
              <span style={sx(LBL)}>Situação</span>
              <div style={sx('display:flex;gap:8px')}>
                <button onClick={() => this.setDespesa('status', 'pago')} style={sx(catTabStyle(d.status === 'pago'))}>Pago</button>
                <button onClick={() => this.setDespesa('status', 'pendente')} style={sx(catTabStyle(d.status === 'pendente'))}>A pagar</button>
              </div>
            </div>
          </div>
          <div style={sx('display:flex;gap:10px;margin-top:20px')}>
            <button onClick={() => this.closeDespesa()} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.saveDespesa()} style={sx(`flex:1.4;background:${dspParse > 0 ? '#E03C31' : '#3a2a28'};color:${dspParse > 0 ? '#fff' : '#7C8794'};border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:${dspParse > 0 ? 'pointer' : 'not-allowed'}`)}>{d.id ? 'Salvar alterações' : 'Salvar despesa'}</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal Registrar Recebimento ---------- */
  renderRecebModal() {
    const st = this.state;
    const r = st.receb;
    const rcbParse = this.parseV(r.value);

    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:64')}>
        <div style={sx('width:100%;max-width:460px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#2BB673;margin-bottom:6px')}>Entrada</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>{r.id ? 'Editar recebimento' : 'Registrar recebimento'}</div>
          <div style={sx('display:flex;flex-direction:column;gap:13px')}>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Descrição</span>
              <input value={r.desc} onChange={e => this.setReceb('desc', e.target.value)} placeholder="Ex.: Projeto avulso — site" className="kes-in" style={sx(IN13)} />
            </label>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Categoria</span>
                <select value={r.catId} onChange={e => this.setReceb('catId', e.target.value)} className="kes-in" style={sx(IN13)}>
                  {st.categories.filter(c => c.type === 'entrada').map(o => <option key={o.id} value={String(o.id)}>{o.name}</option>)}
                </select>
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Valor (R$)</span>
                <input value={r.value} onChange={e => this.setReceb('value', e.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
              </label>
            </div>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Data</span>
                <input value={r.date} onChange={e => this.setReceb('date', e.target.value)} placeholder="dd/mm/aaaa" className="kes-in" style={sx(IN13)} />
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Recorrência</span>
                <select value={r.recur} onChange={e => this.setReceb('recur', e.target.value)} className="kes-in" style={sx(IN13)}>
                  <option value="unica">Único</option>
                  <option value="mensal">Mensal (recorrente)</option>
                </select>
              </label>
            </div>
            <label style={sx('display:flex;align-items:center;gap:9px;font-size:12.5px;color:#B7C0CB;cursor:pointer;padding:4px 0')}>
              <input type="checkbox" checked={!!r.parcial} onChange={e => this.setReceb('parcial', e.target.checked)} style={sx('width:16px;height:16px;accent-color:#E03C31')} />
              Recebido parcialmente (o restante fica pendente)
            </label>
            {r.parcial && (
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Valor recebido agora (R$)</span>
                <input value={r.recebido} onChange={e => this.setReceb('recebido', e.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
                <span style={sx('font-size:11.5px;color:#7C8794;margin-top:2px')}>{rcbParse > 0 && this.parseV(r.recebido) > 0 ? 'Ficará pendente: ' + this.fmt(Math.max(0, rcbParse - this.parseV(r.recebido))) : 'Informe quanto entrou; o resto fica a receber'}</span>
              </label>
            )}
          </div>
          <div style={sx('display:flex;gap:10px;margin-top:20px')}>
            <button onClick={() => this.closeReceb()} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.saveReceb()} style={sx(`flex:1.4;background:${rcbParse > 0 ? '#E03C31' : '#3a2a28'};color:${rcbParse > 0 ? '#fff' : '#7C8794'};border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:${rcbParse > 0 ? 'pointer' : 'not-allowed'}`)}>{r.id ? 'Salvar alterações' : (r.parcial ? 'Registrar parcial' : 'Registrar entrada')}</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal Saldo Inicial ---------- */
  renderSaldoModal() {
    const st = this.state;
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:64')}>
        <div style={sx('width:100%;max-width:420px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Caixa</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:8px')}>Ajustar saldo inicial</div>
          <div style={sx('font-size:12.5px;color:#7C8794;line-height:1.5;margin-bottom:18px')}>Informe quanto há em caixa hoje. Esse valor é a base do fluxo — as entradas e saídas são somadas a partir dele.</div>
          <label style={sx(COL)}>
            <span style={sx(LBL)}>Saldo em caixa (R$)</span>
            <input value={st.saldoModal.value} onChange={e => this.setSaldo(e.target.value)} placeholder="0,00" className="kes-in" style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:12px 14px;color:#fff;font-size:16px;font-weight:700;outline:none')} />
          </label>
          <div style={sx('display:flex;gap:10px;margin-top:20px')}>
            <button onClick={() => this.closeSaldo()} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.saveSaldo()} className="hv-red" style={sx('flex:1.4;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:pointer')}>Salvar saldo</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Tela "esqueci minha senha" ---------- */
  renderRecuperar() {
    const r = this.state.recuperar;
    return (
      <div style={sx('display:flex;align-items:center;justify-content:center;height:100vh;width:100%;background:#0E1318;padding:24px')}>
        <div style={sx('width:100%;max-width:420px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:20px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:34px;animation:kesPop .25s ease both')}>
          <div style={sx('display:flex;flex-direction:column;align-items:center;gap:10px;margin-bottom:20px')}>
            <img src="assets/logo-mark-red.png" alt="KeS" style={sx('width:40px;height:auto')} />
            <div style={sx('font-weight:800;font-size:17px;letter-spacing:-.01em')}>Redefinir senha</div>
          </div>
          <div style={sx('background:rgba(62,142,222,.1);border:1px solid rgba(62,142,222,.3);border-radius:10px;padding:12px 14px;font-size:12px;color:#B7C0CB;line-height:1.55;margin-bottom:18px')}>
            Para provar que é você, informe o <strong style={sx('color:#7FB4EC')}>código de recuperação</strong>. Ele fica no arquivo <strong style={sx('color:#7FB4EC')}>config.php</strong>, no servidor: hPanel → Gerenciador de Arquivos → pasta do site.
          </div>
          <div style={sx('display:flex;flex-direction:column;gap:13px')}>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Usuário</span>
              <input value={r.user} onChange={e => this.setRec('user', e.target.value)} placeholder="ex.: ketherly" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Código de recuperação</span>
              <input value={r.codigo} onChange={e => this.setRec('codigo', e.target.value)} placeholder="KES-XXXX-XXXX" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Nova senha (mínimo 6 caracteres)</span>
              <input type="password" value={r.senha} onChange={e => this.setRec('senha', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Confirmar nova senha</span>
              <input type="password" value={r.senha2} onChange={e => this.setRec('senha2', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} onKeyDown={e => { if (e.key === 'Enter') this.redefinirSenha(); }} />
            </label>
            {!!r.erro && <div style={sx('font-size:12px;color:#EB6258;font-weight:600')}>{r.erro}</div>}
            <button onClick={() => this.redefinirSenha()} className="hv-red" style={sx('display:flex;align-items:center;justify-content:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:12px;font-size:13.5px;font-weight:700;cursor:pointer;box-shadow:0 8px 24px rgba(224,60,49,.3);margin-top:4px')}>
              <IcLock />
              Redefinir senha
            </button>
            <button onClick={() => this.fecharRecuperar()} style={sx('background:transparent;border:none;color:#7C8794;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;padding:6px')}>← Voltar para o login</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Tela de login / primeiro acesso ---------- */
  renderAuth() {
    const f = this.state.authForm;
    // no local: não há usuário salvo · na versão publicada: quem diz é o servidor
    const primeiroAcesso = this._modo === 'php' ? this.state.phpPrimeiroAcesso : this.state.users.length === 0;
    const enviar = () => primeiroAcesso ? this.criarAdmin() : this.entrar();
    if (this.state.recuperar) return this.renderRecuperar();
    return (
      <div style={sx('display:flex;align-items:center;justify-content:center;height:100vh;width:100%;background:#0E1318;padding:24px')}>
        <div style={sx('width:100%;max-width:400px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:20px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:34px;animation:kesPop .25s ease both')}>
          <div style={sx('display:flex;flex-direction:column;align-items:center;gap:10px;margin-bottom:24px')}>
            <img src="assets/logo-mark-red.png" alt="KeS" style={sx('width:46px;height:auto')} />
            <div style={sx('text-align:center')}>
              <div style={sx('font-weight:800;font-size:18px;letter-spacing:-.01em')}>KeS Assessoria</div>
              <div style={sx('font-size:10.5px;color:#7C8794;letter-spacing:.12em;text-transform:uppercase;font-weight:600;margin-top:3px')}>Controle de Clientes</div>
            </div>
          </div>
          {primeiroAcesso && (
            <div style={sx('background:rgba(224,60,49,.08);border:1px solid rgba(224,60,49,.3);border-radius:10px;padding:12px 14px;font-size:12px;color:#B7C0CB;line-height:1.5;margin-bottom:18px')}>
              <strong style={sx('color:#EB6258')}>Primeiro acesso.</strong> Crie a conta da administradora — ela terá acesso total e poderá cadastrar outros usuários com permissões limitadas.
            </div>
          )}
          <div style={sx('display:flex;flex-direction:column;gap:13px')}>
            {primeiroAcesso && (
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Seu nome</span>
                <input value={f.nome} onChange={e => this.setAuth('nome', e.target.value)} placeholder="Ex.: Ketherly Schmidt" className="kes-in" style={sx(IN13)} />
              </label>
            )}
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Usuário</span>
              <input value={f.user} onChange={e => this.setAuth('user', e.target.value)} placeholder="ex.: ketherly" className="kes-in" style={sx(IN13)} onKeyDown={e => { if (e.key === 'Enter' && !primeiroAcesso) enviar(); }} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Senha</span>
              <input type="password" value={f.pass} onChange={e => this.setAuth('pass', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} onKeyDown={e => { if (e.key === 'Enter' && !primeiroAcesso) enviar(); }} />
            </label>
            {primeiroAcesso && (
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Confirmar senha</span>
                <input type="password" value={f.pass2} onChange={e => this.setAuth('pass2', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} onKeyDown={e => { if (e.key === 'Enter') enviar(); }} />
              </label>
            )}
            {!!f.erro && <div style={sx('font-size:12px;color:#EB6258;font-weight:600')}>{f.erro}</div>}
            <button onClick={enviar} className="hv-red" style={sx('display:flex;align-items:center;justify-content:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:12px;font-size:13.5px;font-weight:700;cursor:pointer;box-shadow:0 8px 24px rgba(224,60,49,.3);margin-top:4px')}>
              <IcLock />
              {primeiroAcesso ? 'Criar conta e entrar' : 'Entrar no sistema'}
            </button>
            {!primeiroAcesso && this._modo === 'php' && (
              <button onClick={() => this.abrirRecuperar()} className="hv-link" style={sx('background:transparent;border:none;color:#7C8794;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit;padding:6px;margin-top:2px')}>Esqueci minha senha</button>
            )}
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Usuários e Permissões ---------- */
  renderUsers() {
    const st = this.state;
    const me = this.currentUser();
    const resumoPerms = (u) => {
      if (u.perms.admin) return 'Acesso total (administradora)';
      const telas = PERM_TELAS.filter(([k]) => u.perms[k]).map(([, l]) => l).join(', ') || 'Nenhuma tela';
      const extras = [u.perms.editar ? 'edita' : 'somente leitura', u.perms.exportar ? 'exporta' : null].filter(Boolean).join(' · ');
      return telas + ' — ' + extras;
    };
    return (
      <div>
        <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;gap:14px;flex-wrap:wrap')}>
          <div style={sx('font-size:13px;color:#7C8794')}>Cada pessoa tem seu próprio login. As permissões controlam quais telas aparecem e o que pode ser feito.</div>
          <button onClick={() => this.abrirUsuario(null)} className="hv-red" style={sx('display:flex;align-items:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:999px;padding:10px 18px;font-weight:700;font-size:13px;cursor:pointer;box-shadow:0 10px 30px rgba(224,60,49,.3)')}>
            <IcPlus s={16} />
            Novo usuário
          </button>
        </div>
        <div className="tbl" style={sx(CARD + ';overflow:hidden')}>
          <div style={sx('display:grid;grid-template-columns:1.6fr 1fr 2.2fr 1.2fr;gap:12px;padding:12px 22px;border-bottom:1px solid rgba(255,255,255,.07);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:#7C8794;font-weight:700')}>
            <div>Nome</div><div>Usuário</div><div>Permissões</div><div style={sx('text-align:right')}>Ações</div>
          </div>
          {st.users.map(u => (
            <div key={u.id} style={sx('display:grid;grid-template-columns:1.6fr 1fr 2.2fr 1.2fr;gap:12px;padding:14px 22px;border-bottom:1px solid rgba(255,255,255,.05);align-items:center')}>
              <div style={sx('display:flex;align-items:center;gap:11px;min-width:0')}>
                <div style={sx('width:34px;height:34px;border-radius:999px;background:#28323E;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;color:#EB6258;flex-shrink:0')}>{this.initials(u.nome)}</div>
                <div style={sx('min-width:0')}>
                  <div style={sx('font-size:13.5px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis')}>{u.nome}{me && me.id === u.id ? ' (você)' : ''}</div>
                  <div style={sx('font-size:11px;color:#7C8794')}>{u.cargo || (u.perms.admin ? 'Administradora' : 'Usuário')}</div>
                </div>
              </div>
              <div style={sx('font-size:12.5px;color:#B7C0CB;font-family:ui-monospace,monospace')}>{u.user}</div>
              <div style={sx('font-size:12px;color:#B7C0CB;line-height:1.4')}>{resumoPerms(u)}</div>
              <div style={sx('display:flex;gap:8px;justify-content:flex-end')}>
                <button onClick={() => this.abrirUsuario(u)} className="hv-ghost" style={sx('background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:8px;padding:7px 12px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap')}>Editar</button>
                {!(me && me.id === u.id) && (
                  <button onClick={() => this.removerUsuario(u.id)} className="hv-danger" style={sx('width:32px;height:32px;border-radius:8px;background:transparent;border:1px solid rgba(255,255,255,.12);color:#7C8794;cursor:pointer;display:flex;align-items:center;justify-content:center')}><IcTrash s={14} /></button>
                )}
              </div>
            </div>
          ))}
        </div>
        <div style={sx('font-size:11.5px;color:#7C8794;margin-top:12px;line-height:1.5')}>Dica: para trocar a sua senha, clique em <strong style={sx('color:#B7C0CB')}>Editar</strong> no seu usuário e digite a nova senha.</div>
      </div>
    );
  }

  /* ---------- Modal de usuário ---------- */
  renderUserModal() {
    const m = this.state.userModal;
    const chk = (k, label) => (
      <label key={k} style={sx('display:flex;align-items:center;gap:9px;font-size:12.5px;color:#B7C0CB;cursor:pointer;padding:7px 10px;background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:9px')}>
        <input type="checkbox" checked={!!m.perms[k]} onChange={e => this.setUMPerm(k, e.target.checked)} style={sx('width:15px;height:15px;accent-color:#E03C31')} />
        {label}
      </label>
    );
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:66')}>
        <div style={sx('width:100%;max-width:520px;max-height:92vh;overflow-y:auto;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Acesso</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>{m.id == null ? 'Novo usuário' : 'Editar usuário'}</div>
          <div style={sx('display:flex;flex-direction:column;gap:13px')}>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Nome</span>
                <input value={m.nome} onChange={e => this.setUM('nome', e.target.value)} placeholder="Ex.: Maria Silva" className="kes-in" style={sx(IN13)} />
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Cargo (opcional)</span>
                <input value={m.cargo} onChange={e => this.setUM('cargo', e.target.value)} placeholder="Ex.: Financeiro" className="kes-in" style={sx(IN13)} />
              </label>
            </div>
            <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:10px')}>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>Usuário (login)</span>
                <input value={m.user} onChange={e => this.setUM('user', e.target.value)} placeholder="ex.: maria" className="kes-in" style={sx(IN13)} />
              </label>
              <label style={sx(COL6)}>
                <span style={sx(LBL)}>{m.id == null ? 'Senha' : 'Nova senha (deixe vazio p/ manter)'}</span>
                <input type="password" value={m.pass} onChange={e => this.setUM('pass', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} />
              </label>
            </div>
            <div>
              <div style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase;margin-bottom:8px')}>Telas que pode ver</div>
              <div style={sx('display:grid;grid-template-columns:1fr 1fr;gap:8px')}>
                {PERM_TELAS.map(([k, l]) => chk(k, l))}
              </div>
            </div>
            <div>
              <div style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase;margin-bottom:8px')}>O que pode fazer</div>
              <div style={sx('display:flex;flex-direction:column;gap:8px')}>
                {PERM_ACOES.map(([k, l]) => chk(k, l))}
              </div>
            </div>
            {!!m.erro && <div style={sx('font-size:12px;color:#EB6258;font-weight:600')}>{m.erro}</div>}
          </div>
          <div style={sx('display:flex;gap:10px;margin-top:20px')}>
            <button onClick={() => this.setState({ userModal: null })} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.salvarUsuario()} className="hv-red" style={sx('flex:1.4;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:pointer')}>{m.id == null ? 'Criar usuário' : 'Salvar alterações'}</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal de pagamento (total / parcial) ---------- */
  renderPagamentoModal() {
    const pg = this.state.pagamento;
    const ch = this.state.charges.find(c => c.id === pg.chargeId) || {};
    const cl = this.client(ch.clientId);
    const total = this.cval(ch);
    const jaPago = this.chargePaid(ch);
    const falta = this.chargeRemaining(ch);
    const parcial = pg.mode === 'parcial';
    const add = parcial ? this.parseV(pg.valor) : falta;
    const okValor = !parcial || (add > 0 && add <= falta);
    const restante = Math.max(0, falta - add);
    const modoBtn = (on) => `flex:1;text-align:left;padding:12px 14px;border-radius:11px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.08)' : '#0E1318'};cursor:pointer;font-family:inherit;color:${on ? '#fff' : '#B7C0CB'}`;
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:66')} onClick={e => { if (e.target === e.currentTarget) this.closePagamento(); }}>
        <div style={sx('width:100%;max-width:440px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#2BB673;margin-bottom:6px')}>Entrada</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:4px')}>Registrar pagamento</div>
          <div style={sx('font-size:12.5px;color:#7C8794;margin-bottom:16px')}>{cl.name} · {ch.desc || cl.service}</div>
          <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:8px;margin-bottom:16px')}>
            <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Valor da cobrança</span><span style={sx('font-weight:700')}>{this.fmt(total)}</span></div>
            {jaPago > 0 && <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Já recebido</span><span style={sx('font-weight:600;color:#2BB673')}>{this.fmt(jaPago)}</span></div>}
            <div style={sx('display:flex;justify-content:space-between;font-size:13px;border-top:1px solid rgba(255,255,255,.07);padding-top:8px')}><span style={sx('color:#7C8794')}>Falta receber</span><span style={sx('font-weight:700;color:#F2A93B')}>{this.fmt(falta)}</span></div>
          </div>
          <div style={sx('display:flex;gap:10px;margin-bottom:14px')}>
            <button onClick={() => this.setPagamento('mode', 'total')} style={sx(modoBtn(!parcial))}>
              <div style={sx('font-size:13px;font-weight:700')}>Pago total</div>
              <div style={sx('font-size:11px;color:#7C8794;margin-top:2px')}>Quita os {this.fmt(falta)}</div>
            </button>
            <button onClick={() => this.setPagamento('mode', 'parcial')} style={sx(modoBtn(parcial))}>
              <div style={sx('font-size:13px;font-weight:700')}>Pago parcial</div>
              <div style={sx('font-size:11px;color:#7C8794;margin-top:2px')}>Recebe parte · resto pendente</div>
            </button>
          </div>
          {parcial && (
            <label style={sx(COL6 + ';margin-bottom:6px')}>
              <span style={sx(LBL)}>Valor recebido agora (R$)</span>
              <input value={pg.valor} onChange={e => this.setPagamento('valor', e.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
              <span style={sx('font-size:11.5px;color:' + (okValor ? '#7C8794' : '#EB6258') + ';margin-top:2px')}>{okValor ? (add > 0 ? 'Ficará pendente: ' + this.fmt(restante) : 'Informe o valor recebido') : 'O valor não pode passar de ' + this.fmt(falta)}</span>
            </label>
          )}
          <div style={sx('display:flex;gap:10px;margin-top:16px')}>
            <button onClick={() => this.closePagamento()} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.registrarPagamento()} style={sx(`flex:1.4;background:${okValor && add > 0 ? '#2BB673' : '#2a3a33'};color:${okValor && add > 0 ? '#fff' : '#7C8794'};border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:${okValor && add > 0 ? 'pointer' : 'not-allowed'}`)}>{parcial ? 'Registrar parcial' : 'Registrar pagamento'}</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal de pagamento de despesa (total / parcial) ---------- */
  renderPagarDespModal() {
    const pg = this.state.pagarDesp;
    const e = this.state.expenses.find(x => x.id === pg.expenseId) || {};
    const total = e.value || 0;
    const jaPago = this.expensePaid(e);
    const falta = this.expenseRemaining(e);
    const parcial = pg.mode === 'parcial';
    const add = parcial ? this.parseV(pg.valor) : falta;
    const okValor = !parcial || (add > 0 && add <= falta);
    const restante = Math.max(0, falta - add);
    const modoBtn = (on) => `flex:1;text-align:left;padding:12px 14px;border-radius:11px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.08)' : '#0E1318'};cursor:pointer;font-family:inherit;color:${on ? '#fff' : '#B7C0CB'}`;
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:66')} onClick={ev => { if (ev.target === ev.currentTarget) this.closePagarDesp(); }}>
        <div style={sx('width:100%;max-width:440px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Saída</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:4px')}>Pagar despesa</div>
          <div style={sx('font-size:12.5px;color:#7C8794;margin-bottom:16px')}>{e.desc}</div>
          <div style={sx('background:#0E1318;border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:8px;margin-bottom:16px')}>
            <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Valor da despesa</span><span style={sx('font-weight:700')}>{this.fmt(total)}</span></div>
            {jaPago > 0 && <div style={sx('display:flex;justify-content:space-between;font-size:13px')}><span style={sx('color:#7C8794')}>Já pago</span><span style={sx('font-weight:600;color:#EB6258')}>{this.fmt(jaPago)}</span></div>}
            <div style={sx('display:flex;justify-content:space-between;font-size:13px;border-top:1px solid rgba(255,255,255,.07);padding-top:8px')}><span style={sx('color:#7C8794')}>Falta pagar</span><span style={sx('font-weight:700;color:#F2A93B')}>{this.fmt(falta)}</span></div>
          </div>
          <div style={sx('display:flex;gap:10px;margin-bottom:14px')}>
            <button onClick={() => this.setPagarDesp('mode', 'total')} style={sx(modoBtn(!parcial))}>
              <div style={sx('font-size:13px;font-weight:700')}>Pago total</div>
              <div style={sx('font-size:11px;color:#7C8794;margin-top:2px')}>Quita os {this.fmt(falta)}</div>
            </button>
            <button onClick={() => this.setPagarDesp('mode', 'parcial')} style={sx(modoBtn(parcial))}>
              <div style={sx('font-size:13px;font-weight:700')}>Pago parcial</div>
              <div style={sx('font-size:11px;color:#7C8794;margin-top:2px')}>Paga parte · resto pendente</div>
            </button>
          </div>
          {parcial && (
            <label style={sx(COL6 + ';margin-bottom:6px')}>
              <span style={sx(LBL)}>Valor pago agora (R$)</span>
              <input value={pg.valor} onChange={ev => this.setPagarDesp('valor', ev.target.value)} placeholder="0,00" className="kes-in" style={sx(IN13)} />
              <span style={sx('font-size:11.5px;color:' + (okValor ? '#7C8794' : '#EB6258') + ';margin-top:2px')}>{okValor ? (add > 0 ? 'Ficará pendente: ' + this.fmt(restante) : 'Informe o valor pago') : 'O valor não pode passar de ' + this.fmt(falta)}</span>
            </label>
          )}
          <div style={sx('display:flex;gap:10px;margin-top:16px')}>
            <button onClick={() => this.closePagarDesp()} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.registrarPagamentoDespesa()} className="hv-red" style={sx(`flex:1.4;background:${okValor && add > 0 ? '#E03C31' : '#3a2a28'};color:${okValor && add > 0 ? '#fff' : '#7C8794'};border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:${okValor && add > 0 ? 'pointer' : 'not-allowed'}`)}>{parcial ? 'Registrar parcial' : 'Marcar pago'}</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal de edição de cliente ---------- */
  renderClienteModal() {
    const m = this.state.clienteModal;
    const isPJ = m.type === 'PJ';
    const docOk = m.doc.replace(/\D/g, '') && (isPJ ? validaCNPJ(m.doc) : validaCPF(m.doc));
    const tStyle = (on) => `flex:1;padding:10px;border-radius:9px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.1)' : '#0E1318'};color:${on ? '#fff' : '#B7C0CB'};font-weight:600;font-size:12.5px;cursor:pointer;font-family:inherit`;
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:66')}>
        <div style={sx('width:100%;max-width:560px;max-height:92vh;overflow-y:auto;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31;margin-bottom:6px')}>Cliente</div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:18px')}>Editar cliente</div>
          <div style={sx('display:flex;gap:10px;margin-bottom:14px')}>
            <button onClick={() => this.setClienteModal('type', 'PJ')} style={sx(tStyle(isPJ))}>Pessoa Jurídica</button>
            <button onClick={() => this.setClienteModal('type', 'PF')} style={sx(tStyle(!isPJ))}>Pessoa Física</button>
          </div>
          <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:12px')}>
            <label style={sx(COL6 + ';grid-column:span 2')}>
              <span style={sx(LBL)}>{isPJ ? 'Razão social' : 'Nome completo'}</span>
              <input value={m.name} onChange={e => this.setClienteModal('name', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>{isPJ ? 'CNPJ' : 'CPF'}{docOk ? <span style={sx('color:#2BB673;font-weight:700')}> ✓</span> : null}</span>
              <input value={m.doc} onChange={e => this.setClienteModal('doc', isPJ ? mCNPJ(e.target.value) : mCPF(e.target.value))} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Telefone / WhatsApp</span>
              <input value={m.phone} onChange={e => this.setClienteModal('phone', mFone(e.target.value))} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6 + ';grid-column:span 2')}>
              <span style={sx(LBL)}>E-mail</span>
              <input value={m.email} onChange={e => this.setClienteModal('email', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Serviço</span>
              <input value={m.service} onChange={e => this.setClienteModal('service', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Valor mensal (R$)</span>
              <input value={m.monthly} onChange={e => this.setClienteModal('monthly', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Dia de vencimento</span>
              <select value={m.payDay} onChange={e => this.setClienteModal('payDay', e.target.value)} className="kes-in" style={sx(IN13)}>{Array.from({ length: 31 }, (_, i) => String(i + 1)).map(d => <option key={d} value={d}>{d}</option>)}</select>
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Status</span>
              <select value={m.status} onChange={e => this.setClienteModal('status', e.target.value)} className="kes-in" style={sx(IN13)}>
                <option value="ativo">Ativo</option>
                <option value="pendente">Inadimplente</option>
                <option value="inativo">Inativo / Saída</option>
              </select>
            </label>
          </div>
          <div style={sx('font-size:12px;font-weight:700;color:#B7C0CB;letter-spacing:.04em;text-transform:uppercase;margin:18px 0 4px')}>Endereço</div>
          <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:10px')}>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>CEP</span>
              <input value={m.cep} onChange={e => this.setClienteModal('cep', mCEP(e.target.value))} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Bairro</span>
              <input value={m.bairro} onChange={e => this.setClienteModal('bairro', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6 + ';grid-column:span 2')}>
              <span style={sx(LBL)}>Endereço e número</span>
              <div style={sx('display:flex;gap:10px')}>
                <input value={m.endereco} onChange={e => this.setClienteModal('endereco', e.target.value)} className="kes-in" style={sx(IN13 + ';flex:1')} />
                <input value={m.numero} onChange={e => this.setClienteModal('numero', e.target.value)} placeholder="Nº" className="kes-in" style={sx(IN13 + ';width:90px')} />
              </div>
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Cidade</span>
              <input value={m.cidade} onChange={e => this.setClienteModal('cidade', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>UF</span>
              <input value={m.uf} onChange={e => this.setClienteModal('uf', e.target.value.toUpperCase().slice(0, 2))} className="kes-in" style={sx(IN13)} />
            </label>
          </div>
          {!!m.erro && <div style={sx('font-size:12px;color:#EB6258;font-weight:600;margin-top:12px')}>{m.erro}</div>}
          <div style={sx('display:flex;gap:10px;margin-top:20px')}>
            <button onClick={() => this.setState({ clienteModal: null })} style={sx(BTN_CANCEL)}>Cancelar</button>
            <button onClick={() => this.saveCliente()} className="hv-red" style={sx('flex:1.4;background:#E03C31;color:#fff;border:none;border-radius:10px;padding:11px;font-size:13px;font-weight:700;cursor:pointer')}>Salvar alterações</button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Modal de exportação ---------- */
  renderExportModal() {
    const opcoes = [
      ['clientes', 'Clientes', 'Lista completa com serviço, mensalidade e status'],
      ['cobrancas', 'Cobranças / Mensalidades', 'Status de pagamento e NFS-e vinculada'],
      ['despesas', 'Despesas', 'Com categoria, fornecedor e situação'],
      ['recebimentos', 'Recebimentos avulsos', 'Entradas registradas manualmente'],
      ['extrato', 'Extrato (fluxo de caixa)', 'Todas as movimentações com saldo acumulado'],
      ['notas', 'Notas fiscais', 'NFS-e emitidas'],
    ];
    return (
      <div className="ovl" style={sx(OVERLAY + ';z-index:66')} onClick={e => { if (e.target === e.currentTarget) this.setState({ exportOpen: false }); }}>
        <div style={sx('width:100%;max-width:480px;background:#131B23;border:1px solid rgba(255,255,255,.1);border-radius:18px;box-shadow:0 18px 48px rgba(8,12,16,.45);padding:28px;animation:kesPop .25s ease both')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;margin-bottom:6px')}>
            <div style={sx('font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:#E03C31')}>Exportação</div>
            <button onClick={() => this.setState({ exportOpen: false })} className="hv-close" style={sx('width:30px;height:30px;border-radius:8px;background:#0E1318;border:1px solid rgba(255,255,255,.1);color:#B7C0CB;cursor:pointer;display:flex;align-items:center;justify-content:center')}><IcX s={14} /></button>
          </div>
          <div style={sx('font-size:19px;font-weight:800;letter-spacing:-.02em;margin-bottom:6px')}>Exportar dados</div>
          <div style={sx('font-size:12px;color:#7C8794;margin-bottom:16px')}>Arquivos .csv abrem direto no Excel. {this.temFiltro() ? 'O filtro de datas atual (' + this.periodLabel() + ') será aplicado.' : 'Sem filtro de datas — exporta tudo.'}</div>
          <div style={sx('display:flex;flex-direction:column;gap:8px')}>
            {opcoes.map(([k, titulo, desc]) => (
              <button key={k} onClick={() => this.exportar(k)} className="hv-ghost" style={sx('display:flex;align-items:center;justify-content:space-between;gap:10px;background:#0E1318;color:#fff;border:1px solid rgba(255,255,255,.09);border-radius:11px;padding:12px 15px;cursor:pointer;font-family:inherit;text-align:left')}>
                <span>
                  <span style={sx('display:block;font-size:13px;font-weight:600')}>{titulo}</span>
                  <span style={sx('display:block;font-size:11px;color:#7C8794;margin-top:2px')}>{desc}</span>
                </span>
                <IcDown />
              </button>
            ))}
            <button onClick={() => this.exportar('backup')} className="hv-red" style={sx('display:flex;align-items:center;justify-content:center;gap:8px;background:#E03C31;color:#fff;border:none;border-radius:11px;padding:12px;font-size:13px;font-weight:700;cursor:pointer;margin-top:6px')}>
              <IcDown />
              Backup completo (.json) — todos os dados
            </button>
          </div>
        </div>
      </div>
    );
  }

  /* ---------- Integrações ---------- */
  renderIntegracoes() {
    const api = this.state.apiStatus;
    const fi = this.state.intForm;
    const tt = this.state.intTest;
    const statusChip = (ok, txtOk, txtNo) => (
      <span style={sx(`display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:700;color:${ok ? '#2BB673' : '#F2A93B'};background:${ok ? 'rgba(43,182,115,.14)' : 'rgba(242,169,59,.14)'};padding:3px 9px;border-radius:999px;white-space:nowrap`)}>
        <span style={sx(`width:6px;height:6px;border-radius:9px;background:${ok ? '#2BB673' : '#F2A93B'};display:inline-block`)}></span>{ok ? txtOk : txtNo}
      </span>
    );
    const cardT = 'font-size:15px;font-weight:800;letter-spacing:-.01em';
    const p = 'font-size:12.5px;color:#B7C0CB;line-height:1.6;margin-top:8px';
    const li = 'font-size:12.5px;color:#B7C0CB;line-height:1.7;margin-left:16px';
    const btnS = 'background:#E03C31;color:#fff;border:none;border-radius:9px;padding:10px 16px;font-size:12.5px;font-weight:700;cursor:pointer;white-space:nowrap';
    const btnT = 'background:#28323E;color:#fff;border:1px solid rgba(255,255,255,.1);border-radius:9px;padding:10px 16px;font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap';
    const ambBtn = (on) => `padding:8px 14px;border-radius:8px;border:1.5px solid ${on ? '#E03C31' : 'rgba(255,255,255,.12)'};background:${on ? 'rgba(224,60,49,.1)' : '#0E1318'};color:${on ? '#fff' : '#B7C0CB'};font-weight:600;font-size:12px;cursor:pointer;font-family:inherit`;
    const resultado = (qual) => tt[qual] && (
      <div style={sx(`font-size:12px;font-weight:600;margin-top:10px;color:${tt[qual].ok === null ? '#B7C0CB' : tt[qual].ok ? '#2BB673' : '#EB6258'}`)}>{tt[qual].mensagem}</div>
    );
    return (
      <div style={sx('display:flex;flex-direction:column;gap:16px;max-width:900px')}>
        {/* status do servidor de integrações */}
        <div style={sx(CARD + `;padding:16px 20px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;border-left:3px solid ${api ? '#2BB673' : api === null ? '#E03C31' : '#F2A93B'}`)}>
          <div style={sx('flex:1;min-width:200px')}>
            <div style={sx('font-size:13.5px;font-weight:700')}>Servidor de integrações {api ? '— online ✓' : api === null ? '— offline' : '— verificando…'}</div>
            <div style={sx('font-size:12px;color:#7C8794;margin-top:3px;line-height:1.5')}>
              {api ? <span>As chaves ficam guardadas com segurança neste computador, nunca no navegador. <strong style={sx('color:#B7C0CB')}>Banco de dados (SQLite):</strong> {this.state.bdStatus === 'ok' ? <span>ativo — <strong style={sx('color:#B7C0CB')}>{api.banco}</strong> (ambiente de <strong style={sx('color:' + (api.ambiente === 'teste' ? '#F2A93B' : '#2BB673'))}>{api.ambiente === 'teste' ? 'TESTE' : 'produção'}</strong>), gravado a cada alteração com backup diário. Projeto próprio — sem vínculo com o KeS Metas nem com o Gerenciador.</span> : 'sem conexão — dados só no navegador.'}</span>
                : api === null ? 'Abra o sistema pelo atalho "Abrir Controle de Clientes.bat" (ele inicia o servidor com as integrações e o banco de dados). Sem ele, os dados ficam só neste navegador.'
                : 'Aguarde…'}
            </div>
          </div>
          <button onClick={() => this.carregarStatus()} className="hv-ghost" style={sx(btnT)}>Verificar de novo</button>
        </div>
        <div style={sx('font-size:13px;color:#7C8794;line-height:1.6')}>
          As ações do sistema seguem em <strong style={sx('color:#F2A93B')}>modo simulado</strong> até cada conexão ser ativada. Salve as credenciais abaixo e use <strong style={sx('color:#B7C0CB')}>Testar conexão</strong> — com tudo verde, eu ativo o envio real.
        </div>

        {/* Dados da empresa (prestador) */}
        <div style={sx(CARD + ';padding:22px')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap')}>
            <div style={sx(cardT)}>Dados da empresa (prestador)</div>
            {statusChip(!!(this.state.empresa.cnpj && this.state.empresa.codTrib && this.state.empresa.aliquota), 'Completo', 'Incompleto')}
          </div>
          <div style={sx(p)}>São os dados que <strong style={sx('color:#fff')}>a NFS-e Nacional exige de quem emite</strong> a nota (e que também preenchem o contrato). Ficam salvos automaticamente. Dica: copie o código de tributação e a alíquota de uma nota que você já emitiu no Emissor Nacional.</div>
          <div className="g2" style={sx('display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:14px')}>
            <label style={sx(COL6 + ';grid-column:span 2')}>
              <span style={sx(LBL)}>Razão social</span>
              <input value={this.state.empresa.razao} onChange={e => this.setEmp('razao', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>CNPJ {this.state.empresa.cnpj && validaCNPJ(this.state.empresa.cnpj) ? <span style={sx('color:#2BB673;font-weight:700')}>✓</span> : null}</span>
              <input value={this.state.empresa.cnpj} onChange={e => this.setEmp('cnpj', mCNPJ(e.target.value))} placeholder="00.000.000/0001-00" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Inscrição municipal</span>
              <input value={this.state.empresa.im} onChange={e => this.setEmp('im', e.target.value)} placeholder="número na prefeitura" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Regime tributário</span>
              <select value={this.state.empresa.regime} onChange={e => this.setEmp('regime', e.target.value)} className="kes-in" style={sx(IN13)}>
                <option>MEI</option>
                <option>Simples Nacional</option>
                <option>Outro</option>
              </select>
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>E-mail da empresa</span>
              <input value={this.state.empresa.email} onChange={e => this.setEmp('email', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Código de tributação nacional do serviço</span>
              <input value={this.state.empresa.codTrib} onChange={e => this.setEmp('codTrib', e.target.value)} placeholder="ex.: 17.06.01 — copie de uma nota emitida" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Alíquota de ISS (%)</span>
              <input value={this.state.empresa.aliquota} onChange={e => this.setEmp('aliquota', e.target.value.replace(/[^\d,\.]/g, ''))} placeholder="ex.: 2,00" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>CEP</span>
              <input value={this.state.empresa.cep} onChange={e => this.setEmp('cep', mCEP(e.target.value))} placeholder="95000-000" className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Bairro</span>
              <input value={this.state.empresa.bairro} onChange={e => this.setEmp('bairro', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6 + ';grid-column:span 2')}>
              <span style={sx(LBL)}>Endereço e número</span>
              <div style={sx('display:flex;gap:10px')}>
                <input value={this.state.empresa.endereco} onChange={e => this.setEmp('endereco', e.target.value)} placeholder="Rua Exemplo" className="kes-in" style={sx(IN13 + ';flex:1')} />
                <input value={this.state.empresa.numero} onChange={e => this.setEmp('numero', e.target.value)} placeholder="Nº" className="kes-in" style={sx(IN13 + ';width:90px')} />
              </div>
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>Cidade</span>
              <input value={this.state.empresa.cidade} onChange={e => this.setEmp('cidade', e.target.value)} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6)}>
              <span style={sx(LBL)}>UF · Código IBGE do município</span>
              <div style={sx('display:flex;gap:10px')}>
                <input value={this.state.empresa.uf} onChange={e => this.setEmp('uf', e.target.value.toUpperCase().slice(0, 2))} placeholder="RS" className="kes-in" style={sx(IN13 + ';width:70px')} />
                <input value={this.state.empresa.codIbge} onChange={e => this.setEmp('codIbge', e.target.value.replace(/\D/g, '').slice(0, 7))} placeholder="4305108 (Caxias do Sul)" className="kes-in" style={sx(IN13 + ';flex:1')} />
              </div>
            </label>
          </div>
        </div>

        {/* Autentique */}
        <div style={sx(CARD + ';padding:22px')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap')}>
            <div style={sx(cardT)}>Autentique — assinatura digital de contratos</div>
            {statusChip(!!(api && api.autentique.configurado), 'Token salvo no servidor', 'Simulado')}
          </div>
          <div style={sx(p)}>
            A API oficial (GraphQL) funciona com um <strong>token de API</strong> gerado no painel do Autentique. O plano <strong>gratuito inclui 20 documentos/mês</strong> com API liberada; o plano pago (≈ US$ 19/mês) libera documentos ilimitados.
          </div>
          <div style={sx(p)}><strong style={sx('color:#fff')}>O que você precisa fazer:</strong></div>
          <ol style={sx('margin-top:4px')}>
            <li style={sx(li)}>Criar conta em <strong>autentique.com.br</strong> (a gratuita já serve).</li>
            <li style={sx(li)}>No painel: <strong>Perfil → API keys</strong> (painel.autentique.com.br/perfil/api) → gerar o token e colar abaixo.</li>
            <li style={sx(li)}>Colar o token abaixo, salvar e clicar em <strong>Testar conexão</strong>.</li>
          </ol>
          <div style={sx('display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;align-items:flex-end')}>
            <label style={sx(COL6 + ';flex:1;min-width:240px;max-width:520px')}>
              <span style={sx(LBL)}>Token da API do Autentique {api && api.autentique.configurado ? ('· salvo: ' + api.autentique.tokenMascarado) : ''}</span>
              <input type="password" value={fi.autentiqueToken} onChange={e => this.setInt('autentiqueToken', e.target.value)} placeholder={api && api.autentique.configurado ? 'já configurado — cole aqui só para trocar' : 'cole o token aqui'} className="kes-in" style={sx(IN13)} />
            </label>
            <button onClick={() => this.salvarIntegracao({ autentiqueToken: fi.autentiqueToken })} disabled={!fi.autentiqueToken} className="hv-red" style={sx(btnS + (fi.autentiqueToken ? '' : ';opacity:.45;cursor:not-allowed'))}>Salvar</button>
            <button onClick={() => this.testarIntegracao('autentique')} className="hv-ghost" style={sx(btnT)}>Testar conexão</button>
          </div>
          {resultado('autentique')}
        </div>

        {/* NFS-e */}
        <div style={sx(CARD + ';padding:22px')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap')}>
            <div style={sx(cardT)}>NFS-e — emissão de nota fiscal de serviço</div>
            {statusChip(!!(api && api.nfse.configurado), 'Certificado configurado', 'Simulado')}
          </div>
          <div style={sx(p)}>
            A empresa já emite pelo <strong style={sx('color:#fff')}>Emissor Nacional</strong> (nfse.gov.br) com certificado digital — então o caminho é a <strong style={sx('color:#fff')}>API oficial do Sistema Nacional NFS-e</strong> (SEFIN Nacional), <strong>sem custo por nota e sem intermediário pago</strong>. O sistema gera a DPS (declaração da nota), assina com o seu certificado e transmite ao gov.br; a NFS-e volta autorizada com número e chave de acesso.
          </div>
          <div style={sx(p)}><strong style={sx('color:#fff')}>O que é preciso:</strong></div>
          <ol style={sx('margin-top:4px')}>
            <li style={sx(li)}><strong style={sx('color:#fff')}>Certificado e-CNPJ tipo A1</strong> (arquivo .pfx/.p12 + senha) — é ele que assina a nota e autentica a conexão. Se o seu certificado for <strong>A3</strong> (cartão/token físico), me avise: aí avaliamos gerar um A1 (≈ R$ 150–250/ano) ou outra saída, porque o A3 não funciona bem para automação.</li>
            <li style={sx(li)}>Dados fiscais do serviço (código de tributação nacional do serviço prestado, alíquota de ISS e regime — os mesmos que você já usa ao emitir manualmente; dá para copiar de uma nota anterior).</li>
            <li style={sx(li)}>Informar abaixo o <strong>caminho do arquivo .pfx</strong> no computador e a senha, salvar e clicar em <strong>Testar conexão</strong> — o teste confere o certificado e a conexão segura com a SEFIN Nacional, começando pelo <strong>ambiente de homologação</strong> (notas de teste, sem valor fiscal).</li>
          </ol>
          <div style={sx('display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;align-items:flex-end')}>
            <label style={sx(COL6 + ';flex:2;min-width:260px')}>
              <span style={sx(LBL)}>Caminho do certificado A1 (.pfx)</span>
              <input value={fi.nfsePfx} onChange={e => this.setInt('nfsePfx', e.target.value)} placeholder={'Ex.: C:\\Certificados\\empresa.pfx'} className="kes-in" style={sx(IN13)} />
            </label>
            <label style={sx(COL6 + ';flex:1;min-width:160px')}>
              <span style={sx(LBL)}>Senha do certificado {api && api.nfse.temSenha ? '· salva' : ''}</span>
              <input type="password" value={fi.nfseSenha} onChange={e => this.setInt('nfseSenha', e.target.value)} placeholder="••••••••" className="kes-in" style={sx(IN13)} />
            </label>
          </div>
          <div style={sx('display:flex;gap:10px;margin-top:12px;flex-wrap:wrap;align-items:center')}>
            <div style={sx('display:flex;gap:8px')}>
              <button onClick={() => this.setInt('nfseAmbiente', 'homologacao')} style={sx(ambBtn(fi.nfseAmbiente === 'homologacao'))}>Homologação (testes)</button>
              <button onClick={() => this.setInt('nfseAmbiente', 'producao')} style={sx(ambBtn(fi.nfseAmbiente === 'producao'))}>Produção</button>
            </div>
            <button onClick={() => this.salvarIntegracao({ nfsePfx: fi.nfsePfx, nfseSenha: fi.nfseSenha, nfseAmbiente: fi.nfseAmbiente })} disabled={!fi.nfsePfx} className="hv-red" style={sx(btnS + (fi.nfsePfx ? '' : ';opacity:.45;cursor:not-allowed'))}>Salvar</button>
            <button onClick={() => this.testarIntegracao('nfse')} className="hv-ghost" style={sx(btnT)}>Testar conexão</button>
          </div>
          {resultado('nfse')}
          <div style={sx(p)}>Com o teste verde, a próxima etapa é a emissão de verdade: geramos uma nota de teste em homologação e, funcionando, ligamos a produção. Até lá o botão "Gerar NFS-e" segue simulado.</div>
        </div>

        {/* Asaas */}
        <div style={sx(CARD + ';padding:22px')}>
          <div style={sx('display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap')}>
            <div style={sx(cardT)}>Asaas — cobranças e links de pagamento</div>
            {statusChip(!!(api && api.asaas.configurado), 'Chave salva no servidor (' + (api ? api.asaas.ambiente : '') + ')', 'Simulado')}
          </div>
          <div style={sx(p)}>
            <strong style={sx('color:#fff')}>Sem mensalidade</strong> — paga-se por cobrança recebida: PIX e boleto <strong>R$ 1,99</strong> (R$ 0,99 nos 3 primeiros meses); cartão à vista R$ 0,49 + 2,99%. PIX cai na hora; cartão em até 2 dias úteis. Criada a cobrança, <strong>o próprio Asaas envia o link/boleto ao cliente</strong> por e-mail (pacote R$ 0,99 por cobrança paga) e, se quiser, WhatsApp (R$ 0,55/mensagem).
          </div>
          <div style={sx(p)}><strong style={sx('color:#fff')}>O que você precisa fazer:</strong></div>
          <ol style={sx('margin-top:4px')}>
            <li style={sx(li)}>Criar a conta em <strong>asaas.com</strong> (grátis; MEI usa o CCMEI; aprovação em até 2 dias úteis).</li>
            <li style={sx(li)}>No painel: <strong>menu do usuário → Integrações → Chave de API</strong> → gerar a chave (ela aparece uma única vez — copie na hora) e colar abaixo.</li>
            <li style={sx(li)}>Opcional, recomendado: criar também uma conta de testes em <strong>sandbox.asaas.com</strong> (aprovação automática, sem dinheiro real) — usamos ela primeiro para testar tudo com segurança.</li>
            <li style={sx(li)}>Colar a chave abaixo (escolhendo o ambiente certo), salvar e clicar em <strong>Testar conexão</strong>. A chave dá acesso total à conta, por isso fica guardada <strong>no servidor local, nunca no navegador</strong>.</li>
          </ol>
          <div style={sx('display:flex;gap:10px;margin-top:14px;flex-wrap:wrap;align-items:flex-end')}>
            <label style={sx(COL6 + ';flex:1;min-width:240px;max-width:520px')}>
              <span style={sx(LBL)}>Chave de API {api && api.asaas.configurado ? ('· salva: ' + api.asaas.chaveMascarada) : '(sandbox: $aact_hmlg_… · produção: $aact_prod_…)'}</span>
              <input type="password" value={fi.asaasChave} onChange={e => this.setInt('asaasChave', e.target.value)} placeholder={api && api.asaas.configurado ? 'já configurada — cole aqui só para trocar' : 'cole a chave aqui'} className="kes-in" style={sx(IN13)} />
            </label>
            <div style={sx('display:flex;gap:8px')}>
              <button onClick={() => this.setInt('asaasAmbiente', 'sandbox')} style={sx(ambBtn(fi.asaasAmbiente === 'sandbox'))}>Sandbox (testes)</button>
              <button onClick={() => this.setInt('asaasAmbiente', 'producao')} style={sx(ambBtn(fi.asaasAmbiente === 'producao'))}>Produção</button>
            </div>
            <button onClick={() => this.salvarIntegracao({ asaasChave: fi.asaasChave, asaasAmbiente: fi.asaasAmbiente })} disabled={!fi.asaasChave} className="hv-red" style={sx(btnS + (fi.asaasChave ? '' : ';opacity:.45;cursor:not-allowed'))}>Salvar</button>
            <button onClick={() => this.testarIntegracao('asaas')} className="hv-ghost" style={sx(btnT)}>Testar conexão</button>
          </div>
          {resultado('asaas')}
        </div>
      </div>
    );
  }
}

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