// js/components/pages/Clientes.jsx
// Gestão de clientes (CRUD + filtros + histórico)
// Extraído de index.html em Fase 6 (2026-04-29): L4535-L4839
// Deps runtime: fmt, fmtDate, saleFinalTotal, nid, genIdUUID, dualWrite, toast, showConfirm, Modal, SmartSelect, StatusBadge, Icon, znxGuard
(function() {
  'use strict';
  const {useState, useEffect, useRef, useMemo} = React;

  // [REFACTOR Wave 3 + Wave 9 + Wave 17 + v224.55 2026-05-28] vars+check MOVED to component body (preventivo)
  // regra_validacao_helpers_runtime_quando_ordem_scripts_uncertain

let _inFlightDeleteClient=false;

function Clientes({clients,setClients,sales,quotes,products,receivables,setReceivables,payables,setPayables,user,allUsers}){
  // [v224.55 FIX-PREV-2 2026-05-28] vars+check em render time
  const Tabs = window.ZNX?.widgets?.Tabs;
  const CreditPanel = window.ZNX?.widgets?.clientes?.CreditPanel;
  const ClientForm = window.ZNX?.widgets?.clientes?.ClientForm;
  const clientesCalcs = window.ZNX?.clientes?.calcs;
  const ClientModalW = window.ZNX?.widgets?.clientes?.ClientModal;
  const CreditModalW = window.ZNX?.widgets?.clientes?.CreditModal;
  const CreditRefundModalW = window.ZNX?.widgets?.clientes?.CreditRefundModal;
  const BlockClientModalW = window.ZNX?.widgets?.clientes?.BlockClientModal; // [v224.123 NUCLEAR] fail-soft, fora do guard hard
  const InsightsTabViewW = window.ZNX?.widgets?.clientes?.InsightsTabView;
  const ListaTabViewW = window.ZNX?.widgets?.clientes?.ListaTabView;
  // regra_estender_bloco_refs_fail_loud — 4 originais + 4 novos em 1 mensagem agregada
  if (!Tabs || !CreditPanel || !ClientForm || !clientesCalcs ||
      !ClientModalW || !CreditModalW || !InsightsTabViewW || !ListaTabViewW) {
    const _msg = '[Clientes v223.43] widgets/bundles faltando: '+JSON.stringify({
      Tabs:!!Tabs, CreditPanel:!!CreditPanel, ClientForm:!!ClientForm, clientesCalcs:!!clientesCalcs,
      ClientModalW:!!ClientModalW, CreditModalW:!!CreditModalW,
      InsightsTabViewW:!!InsightsTabViewW, ListaTabViewW:!!ListaTabViewW
    });
    console.error(_msg);
    window.Sentry?.captureMessage?.(_msg, 'error');
    // continua execução — widgets faltando mostram fallback próprio
  }
  // [v139 fix Jamal 20260509] Lookup auth_user_id → nome via allUsers (passado por App.jsx)
  // Usado pra mostrar "Editado por X em DD/MM/YYYY HH:MM" na ficha do cliente.
  const userNameByAuthId = React.useMemo(()=>{
    const m = new Map();
    (allUsers||[]).forEach(u=>{ if(u?.auth_user_id) m.set(u.auth_user_id, u.name||u.username||'?'); });
    return m;
  },[allUsers]);
  const fmtEditadoPor = (uuid, ts)=>{
    if(!uuid && !ts) return null;
    const nome = uuid ? (userNameByAuthId.get(uuid) || 'Usuário desconhecido') : 'sistema';
    const data = ts ? new Date(ts).toLocaleString('pt-BR',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : '';
    return data ? `${nome} em ${data}` : nome;
  };
  const EMPTY={name:'',phone:'',whatsapp:'',email:'',document:'',type:'PF',
    city:'',state:'',neighborhood:'',cep:'',address:'',segment:'',channel:'',notes:'',
    creditLimit:0,paymentTerm:'',canalPref:'',birthDate:''};
  // [H1a v205 20260512] Persistência busca em localStorage
  const[search,setSearch]=useState(()=>{try{return localStorage.getItem('znx_search_clientes')||'';}catch(_){return '';}});
  React.useEffect(()=>{try{localStorage.setItem('znx_search_clientes',search);}catch(_){}},[search]);
  const[filterState,setFilterState]=useState('');
  const[filterIncompletos,setFilterIncompletos]=useState(false);
  const[filterSegment,setFilterSegment]=useState('');
  const[modal,setModal]=useState(null);
  // [AUD2-CRIT3 2026-05-09] regra_state_id_vs_objeto — armazena ID, lookup fresco
  // ANTES: setView(c) passava objeto → modal stale se outro user editar em paralelo
  const[viewId,setViewId]=useState(null);
  const view=useMemo(()=>viewId?clients.find(c=>c.id===viewId):null,[clients,viewId]);
  const setView=(c)=>setViewId(c&&typeof c==='object'?c.id:c);
  const[form,setForm]=useState(EMPTY);
  // [FEATURE crédito 20260504] modal pra admin/financeiro adicionar/remover crédito
  const[creditModal,setCreditModal]=useState(null);
  const[refundModal,setRefundModal]=useState(null);
  const[blockModal,setBlockModal]=useState(null); // [v224.123 NUCLEAR] bloqueio cliente (admin)
  const[creditHistory,setCreditHistory]=useState([]);
  // [FEATURE 360º 20260505 sistema 4 onda 2] sistema de abas Lista / Insights / 360º
  const[activeTab,setActiveTab]=useState('lista');
  // [v127 CRIT-7 2026-05-09] regra_state_id_vs_objeto — armazena ID, lookup fresco no render.
  // ANTES: selected360 = objeto inteiro → após edit cliente, modal mostrava dado stale.
  // AGORA: selected360Id + selected360 derivado via useMemo a partir de clients (sempre fresh).
  const[selected360Id,setSelected360Id]=useState(null);
  const selected360=useMemo(()=>selected360Id?clients.find(c=>c.id===selected360Id):null,[clients,selected360Id]);
  const setSelected360=(client)=>setSelected360Id(client?.id||null);
  // [v127 CRIT-3 2026-05-09] Guards do creditModal — evita duplicação de crédito por double-click
  const _inFlightCreditRef=useRef(false);
  const[creditSaving,setCreditSaving]=useState(false);
  // [FEATURE Onda 4a 20260505] Bulk reativação — set de IDs marcados na Lista
  const[selectedIds,setSelectedIds]=useState(new Set());
  const[campaignOpen,setCampaignOpen]=useState(false);
  const MAX_BULK=25; // limite anti-ban WhatsApp por dia
  // [FEATURE 4a-fix 20260505] Disciplina diária — outreaches de HOJE
  const[todayOutreachMap,setTodayOutreachMap]=useState(new Map()); // client_id -> {sent_at, vendedora_name, template_id, message_sent}
  const[todaySentByMe,setTodaySentByMe]=useState(0); // contagem hoje pela vendedora atual
  const[outreachReloadTick,setOutreachReloadTick]=useState(0); // pra forçar reload após disparo
  // [FEATURE 4b 20260505] Approval flow — quando vendedora clica "Disparar" em pedido aprovado
  const[presetApproval,setPresetApproval]=useState(null); // {requestId, templateId}
  useEffect(()=>{
    let cancelled = false;
    const today=new Date();today.setHours(0,0,0,0);
    const todayISO=today.toISOString();
    sb.from('client_outreach')
      .select('client_id,sent_at,vendedora_name,template_id,message_sent')
      .gte('sent_at',todayISO)
      .order('sent_at',{ascending:false})
      .then(({data,error})=>{
        if(cancelled) return;
        if(error){znxLogWarn('[v224.72 outreach reload]', error);return;}
        const map=new Map();
        let myCount=0;
        const myName=user?.name||'';
        for(const o of (data||[])){
          if(!map.has(o.client_id))map.set(o.client_id,o);
          if(o.vendedora_name===myName)myCount++;
        }
        setTodayOutreachMap(map);
        setTodaySentByMe(myCount);
      })
      .catch(e=>{ if(!cancelled) znxLogWarn('[v224.72 outreach exception]', e); });
    return ()=>{ cancelled = true; };
  },[user?.name,outreachReloadTick]);
  function reloadOutreach(){setOutreachReloadTick(t=>t+1);}

  // [Onda 4b] Listener pra "Disparar agora" depois de aprovação — vem do ApprovalBell
  useEffect(()=>{
    function handler(e){
      const payload = e.detail || window.__znxApprovedRequest;
      if(!payload || !payload.clientIds || payload.clientIds.length===0)return;
      // Pré-popular seleção e estado
      setSelectedIds(new Set(payload.clientIds.map(String).map(id=>{
        // tenta achar como uuid e como número
        const c=clients.find(x=>String(x.id)===String(id));
        return c?c.id:id;
      })));
      setPresetApproval({requestId:payload.requestId,templateId:payload.templateId});
      setActiveTab('lista');
      // Abre modal direto
      setTimeout(()=>setCampaignOpen(true),200);
      window.__znxApprovedRequest = null;
    }
    window.addEventListener('znx:openApprovedCampaign',handler);
    return()=>window.removeEventListener('znx:openApprovedCampaign',handler);
  },[clients]);
  const savingRef=useRef(false);
  // [ONDA1-A 2026-05-11] regra_loading_state_obrigatorio — state pra disabled no botão.
  // Ref sozinho não trigga re-render → botão fica enabled durante save (bug Nadjia/Luanna).
  const[isSavingClient,setIsSavingClient]=useState(false);
  useEffect(()=>{savingRef.current=false;setIsSavingClient(false);},[modal]);
  // Carregar histórico de crédito quando abrir view de cliente
  useEffect(()=>{
    if(!view?.id){setCreditHistory([]);return;}
    let cancelled = false;
    sb.from('client_credit_history').select('*').eq('client_id',view.id).order('created_at',{ascending:false}).limit(50)
      .then(({data})=>{ if(!cancelled) setCreditHistory(Array.isArray(data)?data:[]); })
      .catch(()=>{ if(!cancelled) setCreditHistory([]); });
    return ()=>{ cancelled = true; };
  },[view?.id]);
  const canEditCredit=user?.role==='admin'||user?.role==='financeiro';
  const isAdmin=user?.role==='admin';

  // Vendedora vê clientes que cadastrou (seller_id=app_users.id após ONDA-A #7), teve venda ou orçamento.
  // [BUG-FIX 2026-05-12] Era user.auth_user_id — quebrou tudo porque ONDA-A #7 (2026-05-11) repointou
  // FK clients.seller_id de auth.users(id) pra app_users(id). Trigger preenche seller_id com app_users.id
  // automaticamente, mas filtro frontend comparava com auth_user_id → SEMPRE FALSE → cliente sumia da lista
  // após sync → vendedora duplicava (BUG-TARIK 12 dups). Fix: comparar com user.id (que vem de app_users.id
  // via resolveUserFromAuth em auth.js).
  // JSONB sync expõe como sellerId (camelCase) e seller_id (via to_jsonb).
  const visibleClients=useMemo(()=>{
    if(!user||user.role==='admin'||user.role==='estoquista'||user.role==='financeiro')return clients;
    const myAppUserId=(window.getSellerAppUserId?window.getSellerAppUserId(user):String(user.id||'')); // app_users.id — pós ONDA-A #7 (helper v201)
    const myClientIds=new Set([
      ...sales.filter(s=>s.sellerName===user.name).map(s=>String(s.clientId||s.client_id||'')).filter(Boolean),
      ...quotes.filter(q=>q.sellerName===user.name).map(q=>String(q.clientId||q.client_id||'')).filter(Boolean),
    ]);
    // [v224.26 BUG-OFFDUTY-VISIBILITY fix 20260525] Cobertura Modelo C client-side:
    // vendedora vê clients de colega vendedora cuja is_on_duty=false (off-duty/férias).
    // Espelha znx_can_see_seller RLS backend. Defensive: filtra só role='vendedor'.
    const offDutyIds=new Set(((typeof window!=='undefined'&&window.__ZNX_APP_USERS__)||[])
      .filter(u=>u&&u.role==='vendedor'&&u.is_on_duty===false).map(u=>String(u.id)));
    return clients.filter(c=>{
      const sid=String(c.sellerId||c.seller_id||'');
      return myClientIds.has(String(c.id))
        ||(myAppUserId && sid===myAppUserId)
        ||(sid && offDutyIds.has(sid));
    });
  },[clients,sales,quotes,user]);

  // [FEAT 20260504] Helper unico — cliente incompleto = sem CPF E sem WhatsApp.
  // Esses 2 sao OBRIGATORIOS no save() — fantasma significa cadastro nao terminado.
  // [Wave 9 KIMI 2026-05-17] 5 cálculos extraídos pra lib/clientes/calcs.js (factory puras)
  const filtered=useMemo(()=>visibleClients.filter(c=>{
    const q=search.toLowerCase();
    const matchSearch=!q||c.name.toLowerCase().includes(q)||c.document?.includes(q)||c.phone?.includes(q)||c.whatsapp?.includes(q)||c.email?.toLowerCase().includes(q)||c.city?.toLowerCase().includes(q);
    const matchState=!filterState||c.state===filterState;
    const matchSeg=!filterSegment||c.segment===filterSegment;
    const matchIncompleto=!filterIncompletos||clientesCalcs.isClienteIncompleto(c);
    return matchSearch&&matchState&&matchSeg&&matchIncompleto;
  }),[visibleClients,search,filterState,filterSegment,filterIncompletos]);

  // [v126 PRIVACY] visibleClients gate preservado dentro da factory
  const growthData=useMemo(()=>clientesCalcs.computeGrowthData(visibleClients),[visibleClients]);

  const maxGrowth=Math.max(...growthData.map(m=>m.count),1);
  const totalComData=visibleClients.filter(c=>c.createdAt).length;
  const semData=visibleClients.length-totalComData;
  const thisMonth=growthData[growthData.length-1];
  const lastMonth=growthData[growthData.length-2];

  // [FEATURE 360º + Health Score + KPIs Insights] — 3 factories puras
  const clientLTV=useMemo(()=>clientesCalcs.computeClientLTV(visibleClients,sales),[visibleClients,sales]);
  const clientHealthMap=useMemo(()=>clientesCalcs.computeClientHealthMap(visibleClients,sales),[visibleClients,sales]);
  const healthBuckets=useMemo(()=>clientesCalcs.computeHealthBuckets(clientHealthMap),[clientHealthMap]);

  // [FEATURE 360º 20260505 sistema 4 onda 2] abre 360º de um cliente — usado em row e em Insights
  function open360(client){
    setSelected360(client);
    setActiveTab('360');
  }
  // Callbacks pro ClienteTimeline.jsx
  function onEdit360(client){setForm(client);setModal('edit');}
  function onAddCredit360(client){
    setCreditModal({clientId:client.id,clientName:client.name,type:'add',value:'',reason:''});
  }
  // [WIRE Novo Orçamento] dispatch event global → App.jsx navega + Orcamentos.jsx prefilla
  function onOpenQuote360(client){
    window.__znxPendingQuoteClient=client;
    window.dispatchEvent(new CustomEvent('znx:openQuoteForClient',{detail:{client}}));
  }

  // [Onda 4a] Helpers de seleção múltipla pra Bulk Reativação
  function toggleSelect(id){
    setSelectedIds(prev=>{
      const next=new Set(prev);
      if(next.has(id))next.delete(id);
      else{
        if(next.size>=MAX_BULK){toast(`⚠ Máximo ${MAX_BULK} clientes por campanha (anti-ban WhatsApp)`);return prev;}
        next.add(id);
      }
      return next;
    });
  }
  function selectAllVisible(list){
    const next=new Set();
    for(const c of list){
      if(next.size>=MAX_BULK)break;
      next.add(c.id);
    }
    setSelectedIds(next);
    if(list.length>MAX_BULK)toast(`Selecionei só os primeiros ${MAX_BULK} (limite anti-ban)`);
  }
  function clearSelection(){setSelectedIds(new Set());}
  // Clientes selecionados objetos completos (na ordem visível)
  const selectedClientsList=useMemo(()=>{
    return filtered.filter(c=>selectedIds.has(c.id));
  },[filtered,selectedIds]);

  async function save(){
    if(savingRef.current)return;
    savingRef.current=true;
    setIsSavingClient(true);
    // [FEAT 20260504] Validação reforçada — bloqueia "uma vez pra sempre" novos
    // clientes fantasma. Retornado o foco no campo faltando + mensagem clara.
    if(!form.name?.trim()){toast('⚠ Nome completo é obrigatório.');savingRef.current=false;setIsSavingClient(false);return;}
    if(!form.document?.trim()){toast('⚠ CPF ou CNPJ é obrigatório (sem isso, não consegue emitir nota nem rastrear cliente).');savingRef.current=false;setIsSavingClient(false);return;}
    // Mín 11 chars (CPF) e remove pontuação pra contar
    const docNumeric=String(form.document).replace(/\D/g,'');
    if(docNumeric.length<11){toast('⚠ CPF/CNPJ inválido — precisa ter no mínimo 11 dígitos.');savingRef.current=false;setIsSavingClient(false);return;}
    if(!form.whatsapp?.trim()){toast('⚠ WhatsApp é obrigatório (canal único de contato com o cliente).');savingRef.current=false;setIsSavingClient(false);return;}
    const waNumeric=String(form.whatsapp).replace(/\D/g,'');
    if(waNumeric.length<10){toast('⚠ WhatsApp inválido — precisa de DDD + número (mín 10 dígitos).');savingRef.current=false;setIsSavingClient(false);return;}
    if(!form.cep?.trim()){toast('⚠ CEP é obrigatório (necessário pra entrega).');savingRef.current=false;setIsSavingClient(false);return;}
    if(!form.city?.trim()){toast('⚠ Cidade é obrigatória.');savingRef.current=false;setIsSavingClient(false);return;}
    try {
      const isNew=modal==='new';
      const id=isNew?genIdUUID():form.id;
      // [BUG-FIX 20260504] Antes enviava só {name} pro banco — perdia document/phone/cidade/etc.
      // Causa raiz dos 60 clientes "fantasma" só com nome. Agora envia payload completo
      // mapeado pra schema relacional (snake_case onde aplicavel; aqui formulario ja usa
      // mesmas chaves do banco: name/phone/whatsapp/email/document/type/address/city/state/
      // neighborhood/cep/segment/channel + birth_date/notes adicionados).
      const dbPayload={
        name:form.name?.trim()||null,
        document:form.document?.trim()||null,
        phone:form.phone?.trim()||null,
        whatsapp:form.whatsapp?.trim()||null,
        email:form.email?.trim()||null,
        type:form.type||null,
        segment:form.segment||null,
        channel:form.channel||null,
        cep:form.cep?.trim()||null,
        city:form.city?.trim()||null,
        state:form.state||null,
        neighborhood:form.neighborhood?.trim()||null,
        address:form.address?.trim()||null,
        birth_date:form.birthDate||null,
        notes:form.notes?.trim()||null,
      };

      // [v218.28 ONDA-P R3-F3 P1 2026-05-13 regra_pwa_offline_queue + regra_falha_silenciosa_proibida]
      // Detect offline ANTES de dualWrite → enfileira no IDB + toast. Senão venda em campo 4G ruim
      // perdia cliente recém-criado quando Mona criava cliente offline + venda offline.
      // Idempotency_key protege drain de duplicação se 2 abas tentarem replay.
      if(typeof navigator!=='undefined' && navigator.onLine===false && isNew && window.znxOfflineQueue?.isAvailable()){
        try{
          const idemKeyCli = (window.genIdempotencyKey ? window.genIdempotencyKey() : (crypto?.randomUUID?crypto.randomUUID():String(Date.now())));
          const rowWithIdem = { ...dbPayload, id, idempotency_key: idemKeyCli, seller_id: user?.id || null };
          await window.znxOfflineQueue.enqueue('__upsert_clients', { row: rowWithIdem, p_idempotency_key: idemKeyCli });
          // Atualiza state local otimistamente (vendedora vê cliente na lista até sync)
          setClients(prev=>[...prev,{...form,id,createdAt:new Date().toISOString().slice(0,10),updatedAt:new Date().toISOString(),createdBy:user?.name||'',sellerId:user?.id||'',seller_id:user?.id||'',_queued:true}]);
          toast('💾 Cliente salvo offline — vai sincronizar quando voltar internet','warning');
          if(typeof Sentry!=='undefined') Sentry.addBreadcrumb({category:'offline-queue', message:'client queued offline', level:'info', data:{idemKey:idemKeyCli, clientId:id}});
          setModal(null);
          setIsSavingClient(false);
          savingRef.current=false;
          return;
        }catch(eEnq){
          znxLogWarn('[Clientes] enqueue offline falhou, tentando online', eEnq);
          // Falha enqueue → cai pra dualWrite normal (vai falhar com network_error visível)
        }
      }

      const ok=await dualWrite('clients',id,dbPayload,isNew,()=>{
        // [BUG-FIX 2026-05-12] sellerId/seller_id usam user.id (app_users.id) — pós ONDA-A #7.
        // Antes era user.auth_user_id, mas trigger auto_fill_seller_id grava app_users.id no banco.
        // Inconsistência causou cliente recém-criado sumir do filtro após realtime sync (BUG-TARIK 12 dups).
        if(isNew)setClients(prev=>[...prev,{...form,id,createdAt:new Date().toISOString().slice(0,10),updatedAt:new Date().toISOString(),createdBy:user?.name||'',sellerId:user?.id||'',seller_id:user?.id||''}]);
        else setClients(prev=>prev.map(c=>c.id===id?{...c,...form}:c));
      });
      if(!ok){savingRef.current=false;setIsSavingClient(false);return;}
      setModal(null);
      // [ONDA1-A 2026-05-11] limpa loading state ao fechar modal com sucesso
      setIsSavingClient(false);
    } catch(e) {
      savingRef.current=false;
      setIsSavingClient(false);
      const msg=e.message||'Erro inesperado. Tente novamente.';
      toast('❌ '+msg);
      if(typeof Sentry!=='undefined')Sentry.captureException(e,{extra:{context:'save_clientes',errorMessage:e.message}});
    }
  }
  // [A-04] Cascade delete: bloqueia se tem vendas/orçamentos ativos; remove receivables/payables órfãos
  async function del(id){
    // [SEC-001] Server-side role check — delete client
    if(!await znxGuard(['admin']))return;
    const activeSales=sales.filter(s=>nid(s.clientId,id)&&s.status!=='Cancelada');
    const activeQuotes=quotes.filter(q=>nid(q.clientId,id)&&q.status!=='Cancelado'&&q.status!=='Convertido');
    if(activeSales.length>0||activeQuotes.length>0){
      toast('⚠️ Não é possível excluir este cliente pois possui '+activeSales.length+' venda(s) e '+activeQuotes.length+' orçamento(s) ativo(s).');
      return;
    }
    const orphanRec=(receivables||[]).filter(r=>nid(r.clientId,id));
    const orphanPay=(payables||[]).filter(p=>nid(p.clientId,id));
    const extras=orphanRec.length||orphanPay.length?' Também serão removidos: '+orphanRec.length+' conta(s) a receber e '+orphanPay.length+' conta(s) a pagar vinculadas.':'';
    if(!await showConfirm({title:'Excluir Cliente',message:'Excluir cliente?'+extras,confirmText:'Excluir',confirmColor:'#DC2626'}))return;
    if(_inFlightDeleteClient){toast('⏳ Processando...');return;}
    _inFlightDeleteClient=true;
    try{
      // 1. Soft delete no relacional (deleted_at — preserva histórico de vendas/orçamentos)
      const{error:cErr}=await sb.from('clients').update({deleted_at:new Date().toISOString()}).eq('id',id);
      if(cErr){toast('❌ Erro ao excluir cliente: '+cErr.message);if(typeof Sentry!=='undefined')Sentry.captureException(new Error(cErr.message),{extra:{context:'deleteClient',clientId:id}});return;}
      // 2. Cascade: SOFT delete receivables órfãos (best-effort)
      // [AUDM 20260511 P0-3] regra_soft_delete_financeiro — receivables/payables NUNCA hard delete
      // (compliance fiscal). Antes: .delete() violava RG2.
      const nowISO=new Date().toISOString();
      if(orphanRec.length)await sb.from('receivables').update({deleted_at:nowISO,status:'Cancelado'}).eq('client_id',id).eq('status','Pendente')
        .then(({error:re})=>{
          if(re){
            console.warn('[ZNX] deleteClient: receivables soft delete failed',re.message);
            if(typeof Sentry!=='undefined')Sentry.captureException(new Error('receivables soft delete failed: '+re.message),{extra:{context:'deleteClient_cascade_receivables',clientId:id}});
          }
        })
        .catch(e=>{console.warn('[ZNX] deleteClient: receivables exception',e);if(typeof Sentry!=='undefined')Sentry.captureException(e,{extra:{context:'deleteClient_cascade_receivables',clientId:id}});});
      // 3. Cascade: SOFT delete payables órfãos (best-effort)
      if(orphanPay.length)await sb.from('payables').update({deleted_at:nowISO,status:'Cancelado'}).eq('client_id',id).eq('status','Pendente')
        .then(({error:pe})=>{
          if(pe){
            console.warn('[ZNX] deleteClient: payables soft delete failed',pe.message);
            if(typeof Sentry!=='undefined')Sentry.captureException(new Error('payables soft delete failed: '+pe.message),{extra:{context:'deleteClient_cascade_payables',clientId:id}});
          }
        })
        .catch(e=>{console.warn('[ZNX] deleteClient: payables exception',e);if(typeof Sentry!=='undefined')Sentry.captureException(e,{extra:{context:'deleteClient_cascade_payables',clientId:id}});});
      // 4. State updates (APÓS sucesso no banco)
      if(orphanRec.length&&setReceivables)setReceivables(prev=>prev.filter(r=>!nid(r.clientId,id)));
      if(orphanPay.length&&setPayables)setPayables(prev=>prev.filter(p=>!nid(p.clientId,id)));
      setClients(prev=>prev.filter(c=>c.id!==id));
      toast('✅ Cliente excluído.');
    }catch(e){
      console.error('[ZNX] deleteClient error:',e);
      if(typeof Sentry!=='undefined')Sentry.captureException(e,{extra:{context:'deleteClient',clientId:id}});
      toast('❌ Erro inesperado ao excluir cliente.');
    }finally{
      _inFlightDeleteClient=false;
    }
  }

  // [Wave 17 v223.43] function ClientModal({client}) extracted to widgets/clientes/ClientModal.jsx

  return(
    <div>
      {/* [FEATURE 360º 20260505 sistema 4 onda 2] tab bar */}
      <Tabs
        tabs={[
          {id:'lista',label:'📋 Lista',count:visibleClients.length},
          {id:'insights',label:'📊 Insights',count:clientLTV.length},
          {id:'360',label:'🎯 Cliente 360º',count:selected360?1:0,disabled:!selected360}
        ]}
        active={activeTab}
        onChange={setActiveTab}
      />

      {/* ═══════════════════════ ABA LISTA [Wave 17 v223.43 extracted to widgets/clientes/ListaTabView.jsx] ═══════════════════════ */}
      {activeTab==='lista' && ListaTabViewW && (
        <ListaTabViewW
          filtered={filtered}
          visibleClients={visibleClients}
          receivables={receivables}
          search={search} setSearch={setSearch}
          filterState={filterState} setFilterState={setFilterState}
          filterSegment={filterSegment} setFilterSegment={setFilterSegment}
          filterIncompletos={filterIncompletos} setFilterIncompletos={setFilterIncompletos}
          selectedIds={selectedIds}
          MAX_BULK={MAX_BULK}
          todaySentByMe={todaySentByMe}
          todayOutreachMap={todayOutreachMap}
          clientHealthMap={clientHealthMap}
          user={user}
          clientesCalcs={clientesCalcs}
          EMPTY={EMPTY}
          selectedClientsList={selectedClientsList}
          onOpen360={open360}
          onSetView={setView}
          onSetForm={setForm}
          onSetModal={setModal}
          onDel={del}
          onToggleSelect={toggleSelect}
          onSelectAllVisible={selectAllVisible}
          onClearSelection={clearSelection}
          onOpenCampaign={()=>setCampaignOpen(true)}
        />
      )}
      {/* ABA LISTA inline original wrap removed [v223.43] */}

      {/* ═══════════════════════ ABA INSIGHTS [Wave 17 v223.43 extracted to widgets/clientes/InsightsTabView.jsx] ═══════════════════════ */}
      {activeTab==='insights' && InsightsTabViewW && (
        <InsightsTabViewW
          visibleClients={visibleClients}
          clientLTV={clientLTV}
          clientHealthMap={clientHealthMap}
          healthBuckets={healthBuckets}
          growthData={growthData}
          maxGrowth={maxGrowth}
          thisMonth={thisMonth}
          lastMonth={lastMonth}
          semData={semData}
          user={user}
          onOpen360={open360}
        />
      )}
      {/* ABA INSIGHTS inline original wrap removed [v223.43] */}

      {/* ═══════════════════════ ABA 360º ═══════════════════════ */}
      {activeTab==='360'&&(
        selected360
          ?<ClienteTimeline
              client={selected360}
              clients={clients}
              sales={sales}
              quotes={quotes}
              products={products}
              receivables={receivables}
              user={user}
              health={clientHealthMap.get(selected360.id)}
              todayOutreach={todayOutreachMap.get(selected360.id)}
              onBack={()=>{setSelected360(null);setActiveTab('lista');}}
              onAddCredit={onAddCredit360}
              onEdit={onEdit360}
              onOpenQuote={onOpenQuote360}
            />
          :<div style={{padding:40,textAlign:'center',color:'#6B7280'}}>
            <div style={{fontSize:48,marginBottom:12}}>🎯</div>
            <div style={{fontSize:16,fontWeight:600,marginBottom:8}}>Nenhum cliente selecionado</div>
            <div style={{fontSize:13}}>Volte pra <button className="btn-outline btn-sm" onClick={()=>setActiveTab('lista')}>Lista</button> e clique no 🎯 ao lado de um cliente.</div>
          </div>
      )}

      {/* [Wave 9 KIMI 2026-05-17] Form body extraído pra widgets/clientes/ClientForm.jsx (Modal wrapper preservado no pai) */}
      {modal&&(
        <Modal title={modal==='new'?'Novo Cliente':'Editar Cliente'} onClose={()=>setModal(null)} large>
          <ClientForm
            form={form}
            setForm={setForm}
            isNew={modal==='new'}
            isSavingClient={isSavingClient}
            onSave={save}
            onClose={()=>setModal(null)}
          />
        </Modal>
      )}
      {/* [Wave 17 v223.43] ClientModal extracted to widgets/clientes/ClientModal.jsx */}
      {view && ClientModalW && (
        <ClientModalW
          client={view}
          sales={sales}
          quotes={quotes}
          receivables={receivables}
          products={products}
          fmtEditadoPor={fmtEditadoPor}
          creditPanelProps={{
            canEditCredit:canEditCredit,
            creditHistory:creditHistory,
            onAddCredit:()=>setCreditModal({clientId:view.id,clientName:view.name,type:'add',value:'',reason:''}),
            onRemoveCredit:()=>setCreditModal({clientId:view.id,clientName:view.name,type:'remove',value:'',reason:''}),
            isAdmin:isAdmin,
            onRefundCredit:()=>setRefundModal({clientId:view.id,clientName:view.name,maxValue:Number(view.creditBalance||view.credit_balance||0)})
          }}
          isAdmin={isAdmin}
          onToggleBlock={()=>{const _c=view;setView(null);setBlockModal({client:_c});}}
          onClose={()=>setView(null)}
        />
      )}
      {/* [FEATURE crédito] Modal adicionar/remover crédito (admin/financeiro) */}
      {/* [Onda 4a] Modal de campanha bulk */}
      {campaignOpen&&(
        <CampaignModal
          selectedClients={selectedClientsList}
          sales={sales}
          quotes={quotes}
          products={products}
          user={user}
          health={clientHealthMap}
          todayOutreachMap={todayOutreachMap}
          todaySentByMe={todaySentByMe}
          maxBulk={MAX_BULK}
          presetApproval={presetApproval}
          onClose={()=>{setCampaignOpen(false);clearSelection();setPresetApproval(null);reloadOutreach();}}
        />
      )}
      {/* [v224.123 NUCLEAR] BlockClientModal — admin bloqueia/desbloqueia cliente */}
      {blockModal && BlockClientModalW && (
        <BlockClientModalW
          client={blockModal.client}
          setClients={setClients}
          onClose={()=>setBlockModal(null)}
        />
      )}
      {/* [Wave 17 v223.43] CreditModal extracted to widgets/clientes/CreditModal.jsx (state interno + handler RPC) */}
      {creditModal && CreditModalW && (
        <CreditModalW
          creditModal={creditModal}
          setCreditModal={setCreditModal}
          setClients={setClients}
          setCreditHistory={setCreditHistory}
        />
      )}
      {refundModal && CreditRefundModalW && (
        <CreditRefundModalW
          refundModal={refundModal}
          setRefundModal={setRefundModal}
          setClients={setClients}
          setCreditHistory={setCreditHistory}
        />
      )}
    </div>
  );
}

  window.ZNX = window.ZNX || {};
  window.ZNX.components = window.ZNX.components || {};
  window.ZNX.components.Clientes = Clientes;
  window.Clientes = Clientes;

  window.ZNX.refactor_phase_6_loaded = window.ZNX.refactor_phase_6_loaded || {};
  window.ZNX.refactor_phase_6_loaded.Clientes = true;
  window.ZNX.refactor_phase_6_loaded.Clientes_v223_43_wave17 = true;

})();
