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();