const { useState, useEffect, useRef, useMemo } = React; /* ============================================================ CONFIG ============================================================ */ const WEBHOOK_URL = "https://wautom.agenflex.com/webhook/45929a1d-f07b-4aba-9885-c540e4778aad"; const STORAGE_KEY = "agenflex_cadastro_v1"; const UFS = [ ["AC","Acre"],["AL","Alagoas"],["AP","Amapá"],["AM","Amazonas"],["BA","Bahia"], ["CE","Ceará"],["DF","Distrito Federal"],["ES","Espírito Santo"],["GO","Goiás"], ["MA","Maranhão"],["MT","Mato Grosso"],["MS","Mato Grosso do Sul"],["MG","Minas Gerais"], ["PA","Pará"],["PB","Paraíba"],["PR","Paraná"],["PE","Pernambuco"],["PI","Piauí"], ["RJ","Rio de Janeiro"],["RN","Rio Grande do Norte"],["RS","Rio Grande do Sul"], ["RO","Rondônia"],["RR","Roraima"],["SC","Santa Catarina"],["SP","São Paulo"], ["SE","Sergipe"],["TO","Tocantins"] ]; /* ============================================================ MASKS & VALIDATORS ============================================================ */ const onlyDigits = (s) => (s || "").replace(/\D/g, ""); function maskCNPJ(v){ const d = onlyDigits(v).slice(0,14); let o = d; if(d.length>2) o = d.slice(0,2)+"."+d.slice(2); if(d.length>5) o = d.slice(0,2)+"."+d.slice(2,5)+"."+d.slice(5); if(d.length>8) o = d.slice(0,2)+"."+d.slice(2,5)+"."+d.slice(5,8)+"/"+d.slice(8); if(d.length>12) o = d.slice(0,2)+"."+d.slice(2,5)+"."+d.slice(5,8)+"/"+d.slice(8,12)+"-"+d.slice(12); return o; } function maskCEP(v){ const d = onlyDigits(v).slice(0,8); return d.length>5 ? d.slice(0,5)+"-"+d.slice(5) : d; } function maskPhone(v){ const d = onlyDigits(v).slice(0,11); if(d.length<=2) return d.length?("("+d):""; if(d.length<=6) return "("+d.slice(0,2)+") "+d.slice(2); if(d.length<=10) return "("+d.slice(0,2)+") "+d.slice(2,6)+"-"+d.slice(6); return "("+d.slice(0,2)+") "+d.slice(2,7)+"-"+d.slice(7); } const MASKS = { cnpj:maskCNPJ, cep:maskCEP, phone:maskPhone }; const isEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test((v||"").trim()); // CNPJ — validação dos dígitos verificadores (módulo 11) function isValidCNPJ(value){ const c = onlyDigits(value); if(c.length!==14) return false; if(/^(\d)\1{13}$/.test(c)) return false; // rejeita 00000000000000 etc. const dv = (base)=>{ const len = base.length; let pos = len-7, sum = 0; for(let i=len; i>=1; i--){ sum += parseInt(base.charAt(len-i),10) * pos--; if(pos<2) pos=9; } const r = sum % 11; return r < 2 ? 0 : 11 - r; }; if(dv(c.slice(0,12)) !== parseInt(c.charAt(12),10)) return false; if(dv(c.slice(0,13)) !== parseInt(c.charAt(13),10)) return false; return true; } // Consulta dados públicos do CNPJ (Receita) via BrasilAPI async function fetchCNPJ(digits){ const res = await fetch("https://brasilapi.com.br/api/cnpj/v1/"+digits, { headers:{ "Accept":"application/json" } }); if(!res.ok) return null; return res.json(); } // Consulta endereço pelo CEP via ViaCEP async function fetchCEP(digits){ const res = await fetch("https://viacep.com.br/ws/"+digits+"/json/"); if(!res.ok) return null; const j = await res.json(); return j && j.erro ? null : j; } // UUID v4 para identificar a submissão (idempotência no N8N) function uuid(){ if(typeof crypto!=="undefined" && crypto.randomUUID) return crypto.randomUUID(); return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,(ch)=>{ const r = Math.random()*16|0; const v = ch==="x" ? r : (r&0x3|0x8); return v.toString(16); }); } /* ============================================================ FIELD SCHEMA ============================================================ */ const SECTIONS = [ { id:"empresa", label:"Dados da Empresa", short:"Empresa", intro:"Informações cadastrais e fiscais da sua empresa.", fields:[ {name:"razao_social", label:"Razão Social", required:true, col:2}, {name:"nome_fantasia", label:"Nome Fantasia", required:false, col:2}, {name:"cnpj", label:"CNPJ", required:true, mask:"cnpj", placeholder:"00.000.000/0000-00", inputMode:"numeric"}, {name:"inscricao_estadual", label:"Inscrição Estadual", required:true, toggle:{name:"ie_isento", label:"Isento / não possuo"}}, {name:"inscricao_municipal", label:"Inscrição Municipal", required:false}, {name:"regime_tributario", label:"Regime Tributário", required:true, type:"radio", options:["Simples Nacional","Lucro Presumido","Lucro Real"], col:2}, ] }, { id:"contatos", label:"Contatos", short:"Contatos", intro:"Pessoas e e-mails para comunicação comercial e financeira.", groups:[ { title:"Comprador", fields:[ {name:"nome_comprador", label:"Nome do Comprador", required:true, col:2}, {name:"telefone_comprador", label:"Telefone / WhatsApp", required:true, mask:"phone", placeholder:"(00) 00000-0000", inputMode:"tel"}, {name:"email_comprador", label:"E-mail do Comprador", required:true, type:"email", placeholder:"nome@empresa.com.br"}, ]}, { title:"E-mails de cobrança", fields:[ {name:"email_nota_fiscal", label:"E-mail para Nota Fiscal", required:true, type:"email", placeholder:"fiscal@empresa.com.br"}, {name:"email_boletos", label:"E-mail para Boletos", required:true, type:"email", placeholder:"boletos@empresa.com.br"}, ]}, { title:"Financeiro", fields:[ {name:"responsavel_financeiro", label:"Responsável Financeiro", required:true, col:2}, {name:"telefone_financeiro", label:"Telefone do Financeiro", required:true, mask:"phone", placeholder:"(00) 00000-0000", inputMode:"tel"}, {name:"email_financeiro", label:"E-mail do Financeiro", required:true, type:"email", placeholder:"financeiro@empresa.com.br"}, ]}, ] }, { id:"faturamento", label:"Endereço de Faturamento", short:"Faturamento", intro:"Endereço vinculado ao CNPJ para emissão de documentos.", fields:[ {name:"fat_cep", label:"CEP", required:true, mask:"cep", placeholder:"00000-000", inputMode:"numeric", col:1}, {name:"fat_endereco", label:"Endereço", required:true, col:2}, {name:"fat_numero", label:"Número", required:true, col:1}, {name:"fat_complemento", label:"Complemento", required:false, col:1}, {name:"fat_bairro", label:"Bairro", required:true, col:1}, {name:"fat_cidade", label:"Cidade", required:true, col:1}, {name:"fat_estado", label:"Estado", required:true, type:"uf", col:1}, ] }, { id:"entrega", label:"Endereço de Entrega", short:"Entrega", intro:"Para onde os produtos serão enviados.", sameAs:true, fields:[ {name:"ent_cep", label:"CEP", required:true, mask:"cep", placeholder:"00000-000", inputMode:"numeric", col:1}, {name:"ent_endereco", label:"Endereço", required:true, col:2}, {name:"ent_numero", label:"Número", required:true, col:1}, {name:"ent_complemento", label:"Complemento", required:false, col:1}, {name:"ent_bairro", label:"Bairro", required:true, col:1}, {name:"ent_cidade", label:"Cidade", required:true, col:1}, {name:"ent_estado", label:"Estado", required:true, type:"uf", col:1}, ] }, { id:"logistica", label:"Agendamento e Transporte", short:"Logística", intro:"Opcional — preencha se houver agendamento de recebimento.", groups:[ { title:"Em caso de agendamento", note:"Campos opcionais", fields:[ {name:"contato_recebimento", label:"Contato no Recebimento", required:false, col:2}, {name:"telefone_recebimento", label:"Telefone do Recebimento", required:false, mask:"phone", placeholder:"(00) 00000-0000", inputMode:"tel"}, {name:"horario_recebimento", label:"Horário de Recebimento", required:false, placeholder:"Ex.: 08h às 17h"}, ]}, { title:"Transporte", fields:[ {name:"transportadora_cliente", label:"Transportadora do Cliente", required:false, col:2, placeholder:"Deixe em branco se for por nossa conta"}, ]}, ] }, ]; const FAT_TO_ENT = { fat_cep:"ent_cep", fat_endereco:"ent_endereco", fat_numero:"ent_numero", fat_complemento:"ent_complemento", fat_bairro:"ent_bairro", fat_cidade:"ent_cidade", fat_estado:"ent_estado" }; const allFieldsOf = (sec) => sec.fields ? sec.fields : sec.groups.flatMap(g=>g.fields); /* ============================================================ STYLES ============================================================ */ /* ============================================================ COMBOBOX (UF) ============================================================ */ function UFCombo({ value, onChange, invalid, id }){ const [open,setOpen] = useState(false); const [q,setQ] = useState(""); const [hl,setHl] = useState(0); const ref = useRef(null); const selected = UFS.find(u=>u[0]===value); const display = selected ? `${selected[1]} (${selected[0]})` : ""; useEffect(()=>{ const h=(e)=>{ if(ref.current && !ref.current.contains(e.target)) setOpen(false); }; document.addEventListener("mousedown",h); return ()=>document.removeEventListener("mousedown",h); },[]); const list = useMemo(()=>{ const t = q.trim().toLowerCase(); if(!t) return UFS; return UFS.filter(([c,n])=> n.toLowerCase().includes(t) || c.toLowerCase().includes(t) || n.normalize("NFD").replace(/[\u0300-\u036f]/g,"").toLowerCase().includes(t)); },[q]); const pick = (uf)=>{ onChange(uf[0]); setQ(""); setOpen(false); }; return (
{ setQ(e.target.value); setOpen(true); setHl(0); }} onFocus={()=>{ setOpen(true); setQ(""); }} onKeyDown={(e)=>{ if(e.key==="ArrowDown"){e.preventDefault();setOpen(true);setHl(h=>Math.min(h+1,list.length-1));} else if(e.key==="ArrowUp"){e.preventDefault();setHl(h=>Math.max(h-1,0));} else if(e.key==="Enter"){ if(open&&list[hl]){e.preventDefault();pick(list[hl]);} } else if(e.key==="Escape"){ setOpen(false); } }} /> {open && (
{list.length===0 &&
Nenhum estado encontrado
} {list.map((uf,i)=>(
setHl(i)} onMouseDown={(e)=>{e.preventDefault();pick(uf);}}> {uf[1]}{uf[0]}
))}
)}
); } /* ============================================================ FIELD ============================================================ */ function Field({ f, value, error, onChange, mirrored, toggleValue }){ const fid = "f_"+f.name; const cls = "field"+(f.col===2?" col2":"")+(error?" invalid":""); if(f.type==="radio"){ return (
{f.options.map(opt=>( ))}
{error &&
{error}
}
); } return (
{f.toggle ? (
) : ( )} {f.type==="uf" ? onChange(f.name,v)} /> : { const raw = e.target.value; onChange(f.name, f.mask ? MASKS[f.mask](raw) : raw); }} />} {error &&
{error}
}
); } /* ============================================================ APP ============================================================ */ function App(){ const saved = (()=>{ try{ return JSON.parse(localStorage.getItem(STORAGE_KEY))||{}; }catch(e){ return {}; } })(); const [data,setData] = useState(saved.data||{}); const [step,setStep] = useState(saved.step||0); const [maxStep,setMaxStep] = useState(saved.maxStep||0); const [sameAddr,setSameAddr] = useState(saved.sameAddr||false); const [errors,setErrors] = useState({}); const [status,setStatus] = useState("idle"); // idle | sending | success | error const [errMsg,setErrMsg] = useState(""); const [subId,setSubId] = useState(saved.subId || uuid()); const [cnpjInfo,setCnpjInfo] = useState(null); // {status:"loading|ok|warn|notfound|error", situacao} const [cepLoading,setCepLoading] = useState(""); // "" | "fat" | "ent" const topRef = useRef(null); const lastCnpj = useRef(""); const lastCep = useRef({fat:"",ent:""}); useEffect(()=>{ localStorage.setItem(STORAGE_KEY, JSON.stringify({data,step,maxStep,sameAddr,subId})); },[data,step,maxStep,sameAddr,subId]); const sec = SECTIONS[step]; const isEntrega = sec.id==="entrega"; const mirrored = isEntrega && sameAddr; const setField = (name,val)=>{ setData(d=>{ const nd = {...d,[name]:val}; // Inscrição Estadual isenta: trava o campo com "ISENTO" if(name==="ie_isento") nd.inscricao_estadual = val ? "ISENTO" : ""; // keep entrega in sync while mirrored if(sameAddr && FAT_TO_ENT[name]) nd[FAT_TO_ENT[name]] = val; return nd; }); setErrors(e=>{ const n = {...e}; if(n[name]) delete n[name]; if(name==="ie_isento" && val) delete n.inscricao_estadual; return n; }); }; // wrapper: grava o campo e dispara as consultas de CNPJ/CEP quando completos const onFieldChange = (name,val)=>{ setField(name,val); if(name==="cnpj"){ if(isValidCNPJ(val)) doCnpjLookup(val); else setCnpjInfo(null); } if((name==="fat_cep"||name==="ent_cep") && onlyDigits(val).length===8){ doCepLookup(name==="fat_cep"?"fat":"ent", val); } }; // Preenche os campos de endereço de um prefixo (fat/ent), espelhando quando aplicável const fillAddress = (prefix, a)=>{ setData(d=>{ const nd = {...d}; const apply = (px)=>{ if(a.cep!=null && a.cep!=="") nd[px+"_cep"] = maskCEP(a.cep); if(a.endereco) nd[px+"_endereco"] = a.endereco; if(a.bairro) nd[px+"_bairro"] = a.bairro; if(a.cidade) nd[px+"_cidade"] = a.cidade; if(a.estado) nd[px+"_estado"] = a.estado; if(a.complemento) nd[px+"_complemento"] = a.complemento; }; apply(prefix); if(prefix==="fat" && sameAddr) apply("ent"); return nd; }); setErrors(e=>{ const n = {...e}; const clear = (px)=>[px+"_endereco",px+"_bairro",px+"_cidade",px+"_estado",px+"_cep"].forEach(k=>delete n[k]); clear(prefix); if(prefix==="fat" && sameAddr) clear("ent"); return n; }); }; const doCepLookup = async (prefix, masked)=>{ const digits = onlyDigits(masked); if(digits.length!==8 || lastCep.current[prefix]===digits) return; lastCep.current[prefix] = digits; setCepLoading(prefix); try{ const a = await fetchCEP(digits); if(a) fillAddress(prefix,{ endereco:a.logradouro, bairro:a.bairro, cidade:a.localidade, estado:a.uf, complemento:a.complemento||"" }); }catch(_){ /* silencioso: cliente preenche manualmente */ } finally{ setCepLoading(""); } }; const doCnpjLookup = async (masked)=>{ const digits = onlyDigits(masked); if(!isValidCNPJ(digits) || lastCnpj.current===digits) return; lastCnpj.current = digits; setCnpjInfo({status:"loading"}); try{ const c = await fetchCNPJ(digits); if(!c){ setCnpjInfo({status:"notfound"}); return; } const situacao = (c.descricao_situacao_cadastral||"").toUpperCase(); const ativa = situacao==="ATIVA" || c.situacao_cadastral===2; setData(d=>{ const nd = {...d}; if(c.razao_social) nd.razao_social = c.razao_social; if(c.nome_fantasia) nd.nome_fantasia = c.nome_fantasia; if(c.opcao_pelo_simples===true) nd.regime_tributario = "Simples Nacional"; if(c.cep) nd.fat_cep = maskCEP(c.cep); if(c.logradouro) nd.fat_endereco = c.logradouro; if(c.numero) nd.fat_numero = c.numero; if(c.complemento) nd.fat_complemento = c.complemento; if(c.bairro) nd.fat_bairro = c.bairro; if(c.municipio) nd.fat_cidade = c.municipio; if(c.uf) nd.fat_estado = c.uf; if(sameAddr) Object.entries(FAT_TO_ENT).forEach(([f,t])=>{ nd[t]=nd[f]||""; }); return nd; }); setErrors(e=>{ const n = {...e}; ["razao_social","fat_cep","fat_endereco","fat_numero","fat_bairro","fat_cidade","fat_estado"].forEach(k=>delete n[k]); return n; }); setCnpjInfo({status: ativa?"ok":"warn", situacao}); }catch(_){ setCnpjInfo({status:"error"}); } }; const toggleSame = ()=>{ const next = !sameAddr; setSameAddr(next); if(next){ setData(d=>{ const nd={...d}; Object.entries(FAT_TO_ENT).forEach(([f,t])=>{ nd[t]=d[f]||""; }); return nd; }); setErrors(e=>{ const n={...e}; Object.values(FAT_TO_ENT).forEach(t=>delete n[t]); return n; }); } }; const validateSection = ()=>{ if(mirrored) return {}; const errs={}; allFieldsOf(sec).forEach(f=>{ // Inscrição Estadual: dispensada quando marcada como isenta if(f.name==="inscricao_estadual" && data.ie_isento) return; const v = (data[f.name]||"").toString().trim(); if(f.required && !v){ errs[f.name]="Campo obrigatório"; return; } if(v && f.type==="email" && !isEmail(v)) errs[f.name]="E-mail inválido"; if(v && f.mask==="cnpj"){ if(onlyDigits(v).length!==14) errs[f.name]="CNPJ incompleto"; else if(!isValidCNPJ(v)) errs[f.name]="CNPJ inválido"; } if(v && f.mask==="cep" && onlyDigits(v).length!==8) errs[f.name]="CEP incompleto"; if(v && f.mask==="phone" && onlyDigits(v).length<10) errs[f.name]="Telefone incompleto"; }); return errs; }; const goTop = ()=>{ if(topRef.current) topRef.current.scrollIntoView({block:"start"}); window.scrollTo({top:0,behavior:"smooth"}); }; const next = ()=>{ const errs = validateSection(); if(Object.keys(errs).length){ setErrors(errs); return; } if(step < SECTIONS.length-1){ const ns = step+1; setStep(ns); setMaxStep(m=>Math.max(m,ns)); setErrors({}); requestAnimationFrame(goTop); } else { submit(); } }; const back = ()=>{ if(step>0){ setStep(step-1); setErrors({}); requestAnimationFrame(goTop); } }; const jump = (i)=>{ if(i<=maxStep){ setStep(i); setErrors({}); requestAnimationFrame(goTop); } }; const buildPayload = ()=>({ formulario:"Cadastro Agenflex", submission_id:subId, enviado_em:new Date().toISOString(), empresa:{ razao_social:data.razao_social||"", nome_fantasia:data.nome_fantasia||"", cnpj:data.cnpj||"", inscricao_estadual:data.inscricao_estadual||"", inscricao_estadual_isento:!!data.ie_isento, inscricao_municipal:data.inscricao_municipal||"", regime_tributario:data.regime_tributario||"", situacao_cadastral:(cnpjInfo && cnpjInfo.situacao) ? cnpjInfo.situacao : "" }, contatos:{ nome_comprador:data.nome_comprador||"", telefone_comprador:data.telefone_comprador||"", email_comprador:data.email_comprador||"", email_nota_fiscal:data.email_nota_fiscal||"", email_boletos:data.email_boletos||"", responsavel_financeiro:data.responsavel_financeiro||"", telefone_financeiro:data.telefone_financeiro||"", email_financeiro:data.email_financeiro||"" }, endereco_faturamento:{ cep:data.fat_cep||"", endereco:data.fat_endereco||"", numero:data.fat_numero||"", complemento:data.fat_complemento||"", bairro:data.fat_bairro||"", cidade:data.fat_cidade||"", estado:data.fat_estado||"" }, endereco_entrega:{ mesmo_endereco_faturamento:!!sameAddr, cep:data.ent_cep||"", endereco:data.ent_endereco||"", numero:data.ent_numero||"", complemento:data.ent_complemento||"", bairro:data.ent_bairro||"", cidade:data.ent_cidade||"", estado:data.ent_estado||"" }, logistica:{ contato_recebimento:data.contato_recebimento||"", telefone_recebimento:data.telefone_recebimento||"", horario_recebimento:data.horario_recebimento||"", transportadora_cliente:data.transportadora_cliente||"" } }); const submit = async ()=>{ const payload = buildPayload(); console.log("Payload Agenflex →", payload); setStatus("sending"); setErrMsg(""); try{ const res = await fetch(WEBHOOK_URL,{ method:"POST", headers:{ "Content-Type":"application/json" }, body:JSON.stringify(payload) }); if(!res.ok) throw new Error("HTTP "+res.status); setStatus("success"); requestAnimationFrame(goTop); }catch(err){ console.warn("Falha no envio:",err); setErrMsg(err.message||"Erro de conexão"); setStatus("error"); requestAnimationFrame(goTop); } }; const reset = ()=>{ localStorage.removeItem(STORAGE_KEY); setData({}); setStep(0); setMaxStep(0); setSameAddr(false); setErrors({}); setStatus("idle"); setSubId(uuid()); setCnpjInfo(null); setCepLoading(""); lastCnpj.current = ""; lastCep.current = {fat:"",ent:""}; requestAnimationFrame(goTop); }; const pct = Math.round(((step + 1) / SECTIONS.length) * 100); /* ---- render a section's fields ---- */ const renderFields = (fields)=> (
{fields.map(f=>( ))}
); return (
{/* SIDEBAR */} {/* MAIN */}
{status==="success" ? (

Cadastro enviado!

Recebemos os dados da sua empresa. Nossa equipe dará sequência ao cadastro e entrará em contato em breve.

) : status==="error" ? (

Não foi possível enviar

Ocorreu um problema ao enviar o cadastro ({errMsg}). Verifique sua conexão e tente novamente — seus dados foram preservados.

) : (

Formulário de Cadastro

Cadastro Agenflex

Preencha as etapas abaixo para abrirmos seu cadastro de cliente. Campos com * são obrigatórios.

Etapa {step+1} de {SECTIONS.length}

{sec.label}

{sec.intro}

{isEntrega && (
Mesmo endereço de faturamentoUsar os dados informados na etapa anterior
)} {sec.id==="empresa" && cnpjInfo && ( cnpjInfo.status==="loading" ? (
Consultando CNPJ na Receita…
) : cnpjInfo.status==="ok" ? (
Empresa localizada e ATIVA. Preenchemos os dados — confira e ajuste se precisar.
) : cnpjInfo.status==="warn" ? (
Situação cadastral: {cnpjInfo.situacao||"—"}. Dados preenchidos, mas confira a situação antes de prosseguir.
) : cnpjInfo.status==="notfound" ? (
CNPJ não encontrado na base. Confira o número ou preencha manualmente.
) : (
Não foi possível consultar o CNPJ agora. Preencha os dados manualmente.
) )} {((sec.id==="faturamento" && cepLoading==="fat") || (sec.id==="entrega" && cepLoading==="ent")) && (
Buscando endereço pelo CEP…
)} {sec.fields && renderFields(sec.fields)} {sec.groups && sec.groups.map(g=>(

{g.title}{g.note && {g.note}}

{renderFields(g.fields)}
))}

Seus dados são enviados de forma segura à Agenflex.

)}
); } ReactDOM.createRoot(document.getElementById("root")).render();