const { useState, useEffect } = React; // --- Funcões Auxilares e Máscaras --- function validarCPF(cpf) { cpf = cpf.replace(/[^\d]+/g, ''); if (cpf.length !== 11 || !!cpf.match(/(\d)\1{10}/)) return false; let soma = 0, resto; for (let i = 1; i <= 9; i++) soma += parseInt(cpf.substring(i - 1, i)) * (11 - i); resto = (soma * 10) % 11; if (resto === 10 || resto === 11) resto = 0; if (resto !== parseInt(cpf.substring(9, 10))) return false; soma = 0; for (let i = 1; i <= 10; i++) soma += parseInt(cpf.substring(i - 1, i)) * (12 - i); resto = (soma * 10) % 11; if (resto === 10 || resto === 11) resto = 0; if (resto !== parseInt(cpf.substring(10, 11))) return false; return true; } function formatCPF(value) { if (!value) return ''; return value.replace(/\D/g, '') .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d{1,2})/, '$1-$2') .replace(/(-\d{2})\d+?$/, '$1'); } function formatCNPJ(value) { if (!value) return ''; return value.replace(/\D/g, '') .replace(/(\d{2})(\d)/, '$1.$2') .replace(/(\d{3})(\d)/, '$1.$2') .replace(/(\d{3})(\d)/, '$1/$2') .replace(/(\d{4})(\d{1,2})/, '$1-$2') .replace(/(-\d{2})\d+?$/, '$1'); } function formatCardNumber(value) { return value.replace(/\D/g, '').replace(/(\d{4})/g, '$1 ').trim().substring(0, 19); } function formatExpiry(value) { const v = value.replace(/\D/g, ''); if (v.length >= 2) return v.substring(0, 2) + '/' + v.substring(2, 4); return v; } function validarSenha(senha) { const minLength = 8; const hasUpperCase = /[A-Z]/.test(senha); const hasNumber = /[0-9]/.test(senha); const hasSpecial = /[!@#$%^&*(),.?":{}|<>]/.test(senha); if (senha.length < minLength) return "A senha deve ter pelo menos 8 caracteres."; if (!hasUpperCase) return "A senha deve conter pelo menos uma letra maiúscula."; if (!hasNumber) return "A senha deve conter pelo menos um número."; if (!hasSpecial) return "A senha deve conter pelo menos um caractere especial."; return null; } // RemovidonotasDB mock e integrado com MariaDB via backend. const API_URL = 'https://api.iaagro.app/api'; // Interceptor global de requisições para anexar o JWT Token const originalFetch = window.fetch; window.fetch = async function () { let [resource, config] = arguments; if (typeof resource === 'string' && resource.startsWith(API_URL)) { const userStr = localStorage.getItem('iaagro_user'); if (userStr) { try { const user = JSON.parse(userStr); if (user && user.token) { if (!config) config = {}; if (!config.headers) config.headers = {}; // URLs de autenticação públicas não exigem token const isAuthRoute = resource.includes('/login') || resource.includes('/register') || resource.includes('/forgot') || resource.includes('/reset'); if (!isAuthRoute) { config.headers['Authorization'] = 'Bearer ' + user.token; } } } catch (e) { console.error("Erro ao ler token", e); } } } return originalFetch(resource, config); }; function App() { const [view, setView] = useState(() => { const params = new URLSearchParams(window.location.search); if (window.location.hostname === 'portal.iaagro.app') { return params.get('view') || 'login'; } return params.get('view') || 'landing'; }); const handleSetView = (newView) => { const isPortalView = ['login', 'register', 'termos', 'portal', 'checkout-success', 'checkout-cancel'].includes(newView); // Se está no domínio principal e quer acessar o portal if (window.location.hostname === 'iaagro.app' && isPortalView) { window.location.href = `https://portal.iaagro.app/?view=${newView}`; return; } // Se está no portal e quer voltar ao site if (window.location.hostname === 'portal.iaagro.app' && newView === 'landing') { window.location.href = 'https://iaagro.app/'; return; } setView(newView); }; const [user, setUser] = useState(null); const [theme, setTheme] = useState('light'); const [notification, setNotification] = useState({ show: false, msg: '', type: 'info' }); const notify = (msg, type = 'info') => { setNotification({ show: true, msg, type }); setTimeout(() => setNotification({ show: false, msg: '', type: 'info' }), 4000); }; // --- Sistema de Temas --- useEffect(() => { const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const applyTheme = (isDark) => { const newTheme = isDark ? 'dark' : 'light'; setTheme(newTheme); document.documentElement.setAttribute('data-theme', newTheme); }; applyTheme(mediaQuery.matches); const handleChange = (e) => applyTheme(e.matches); mediaQuery.addEventListener('change', handleChange); // Fade-out global loader after React mounts const loader = document.getElementById('global-loader'); if (loader) { document.body.style.overflow = 'hidden'; setTimeout(() => { loader.style.opacity = '0'; loader.style.visibility = 'hidden'; document.body.style.overflow = ''; setTimeout(() => loader.remove(), 500); }, 300); // Dar um tempo extra para imagens e vídeos começarem a carregar } // Persistência de Login: Recuperar do localStorage const savedUser = localStorage.getItem('iaagro_user'); if (savedUser) { try { const parsedUser = JSON.parse(savedUser); // 🔥 VALIDAÇÃO REAL NO BACKEND fetch(`${API_URL}/user/${parsedUser.id}`) .then(res => res.json()) .then(data => { if (!data || data.error) { localStorage.removeItem('iaagro_user'); setUser(null); handleSetView(window.location.hostname === 'portal.iaagro.app' ? 'login' : 'landing'); } else { setUser(data); handleSetView('portal'); } }) .catch(() => { handleSetView(window.location.hostname === 'portal.iaagro.app' ? 'login' : 'landing'); }); } catch (e) { localStorage.removeItem('iaagro_user'); } } return () => mediaQuery.removeEventListener('change', handleChange); }, []); // --- Detectar retorno do Stripe Checkout via query params --- useEffect(() => { const params = new URLSearchParams(window.location.search); if (params.get("checkout") === "success") { handleSetView('checkout-success'); window.history.replaceState({}, document.title, window.location.pathname); } else if (params.get("checkout") === "cancel") { handleSetView('checkout-cancel'); window.history.replaceState({}, document.title, window.location.pathname); } }, []); // --- Observador para Animações de Scroll (Reveal) --- useEffect(() => { const observerOptions = { threshold: 0.15, rootMargin: '0px 0px -50px 0px' }; const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('active'); // Opcional: parar de observar após animar uma vez // observer.unobserve(entry.target); } }); }, observerOptions); const revealElements = document.querySelectorAll('.reveal'); revealElements.forEach(el => observer.observe(el)); // Re-observar se a view mudar (importante p/ SPA) return () => { revealElements.forEach(el => observer.unobserve(el)); }; }, [view]); // Dispara novamente ao trocar de tela (landing -> portal etc) const switchTheme = () => { const newTheme = theme === 'dark' ? 'light' : 'dark'; setTheme(newTheme); document.documentElement.setAttribute('data-theme', newTheme); }; const handleLogin = (userData) => { localStorage.setItem('iaagro_user', JSON.stringify(userData)); setUser(userData); handleSetView('portal'); }; const handleLogout = () => { localStorage.removeItem('iaagro_user'); setUser(null); handleSetView('landing'); }; return ( {/* Botão Global de Tema - Efeito Glass Fixo (Apenas em outras telas, no landing vai p/ navbar) */} {view !== 'landing' && ( )} {view === 'landing' && } {view === 'login' && } {view === 'register' && } {view === 'termos' && } {view === 'portal' && } {view === 'checkout-success' && } {view === 'checkout-cancel' && } {/* Botão de WhatsApp Flutuante */} {/* Sistema de Notificação Customizado */}
{notification.msg}
); } // === LANDING PAGE === function Landing({ onNavigate, theme, onSwitchTheme }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; const [menuOpen, setMenuOpen] = React.useState(false); const [activeDots, setActiveDots] = React.useState(1); // Foco no meio (index 1) const [billingCycle, setBillingCycle] = React.useState('annual'); // 'monthly' | 'annual' const plansRef = React.useRef(null); const [isVideoMuted, setIsVideoMuted] = React.useState(true); const [isVideoPlaying, setIsVideoPlaying] = React.useState(true); const videoRef = React.useRef(null); const toggleVideoMute = () => { if (videoRef.current) { videoRef.current.muted = !isVideoMuted; setIsVideoMuted(!isVideoMuted); } }; const toggleVideoPlay = () => { if (videoRef.current) { if (isVideoPlaying) { videoRef.current.pause(); } else { videoRef.current.play(); } setIsVideoPlaying(!isVideoPlaying); } }; React.useEffect(() => { // Focar no plano Família (meio) no mobile if (window.innerWidth <= 768 && plansRef.current) { setTimeout(() => { if (plansRef.current && plansRef.current.children[1]) { const middleElement = plansRef.current.children[1]; const container = plansRef.current; container.scrollLeft = middleElement.offsetLeft - container.offsetLeft - (container.clientWidth / 2) + (middleElement.clientWidth / 2); } }, 100); } }, [billingCycle]); const handleScroll = (e) => { if (window.innerWidth > 768) return; const scrollLeft = e.target.scrollLeft; const scrollWidth = e.target.scrollWidth - e.target.clientWidth; const percentage = scrollLeft / scrollWidth; let index = 1; if (percentage < 0.25) index = 0; else if (percentage > 0.75) index = 2; if (index !== activeDots) setActiveDots(index); }; return (

Emita suas Notas Fiscais pelo
WhatsApp em Segundos

Sem burocracia, sem computadores difíceis. Mande um áudio ou texto e a nossa inteligência faz todo o trabalho chato por você.

{/* RECURSOS */}

Como a IAAGRO trabalha?

Um fluxo completo e automatizado, do campo até a Receita Federal.

1. Você usa o WhatsApp?

É só isso que você precisa. Nada de instalar programas ou aprender sistemas novos.

2. Envie um áudio, texto ou foto

Fale ou escreva o que vendeu, a quantidade e o comprador. Nossa inteligência entende tudo.

3. Sua nota pronta na hora

A nota é gerada automaticamente e enviada em PDF direto no seu WhatsApp, pronta para usar.

4. Tudo salvo e seguro

Suas notas ficam guardadas em segurança enquanto você for nosso cliente. Seu contador pode baixar tudo depois.

Escolha seu Plano

Flexibilidade para todos os tamanhos de negócios e produtores rurais.

Produtor

R${billingCycle === 'monthly' ? '74' : '64'},90
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 30 Emissões mensais
  • 1 CNPJ/CPF
  • Via WhatsApp (texto)
  • 3 números autorizados
  • Suporte em 24h úteis
Mais Escolhido

Família

R${billingCycle === 'monthly' ? '104' : '94'},90
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 100 Emissões mensais
  • 3 CNPJs/CPFs
  • Via WhatsApp (texto, áudio, img)
  • 3 Acessos ao portal / 5 WhatsApps
  • Suporte em 12h úteis

Cooperativa

R${billingCycle === 'monthly' ? '174' : '154'},90
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 500 Emissões mensais
  • CNPJ/CPF Ilimitado
  • 5 Acessos ao portal / 10 WhatsApps
  • Relatórios completos por produtor
  • Suporte em 4h úteis
{/* Paginação do Carrossel (Mobile Only) */}

Addons (Adicionais)

Potencialize seu plano com recursos extras sob demanda

Relatório Mensal Email/Wpp
R$ 14,90
Buscador de NF-e de Compras (Produtor)
R$ 14,90
Relatório Compras/Vendas (IR)
R$ 14,90
Autorização de Número Adicional (Emissão)
R$ 4,90
{/* DEPOIMENTOS */}

O que dizem os produtores

Opiniões reais de quem já eliminou processos ultrapassados no agronegócio.

CA

Carlos Almeida

Fazenda Boa Esperança

“Antigamente eu perdia horas no computador puxando XML, era horrível estar no pasto e ter que voltar. Hoje eu mando um texto rápido pelo WhatsApp com os dados da carga, e a nota desponta no e-mail do cliente na mesma hora. Fantástico.”

MF

Marta Fidelis

Cooperativa Agrosul

“Conseguimos alocar a IAAGRO para operar notas de mais de 12 caminhões por dia. Basta digitar o pedido e a IA faz a conversão tributária exata, zeramos multas por esquecimento de regras estaduais complexas.”

RR

Rafael Rios

Engenheiro Agrônomo Independente

“Poder gerar a NF-e pelo WhatsApp é um divisor de águas. O sistema reconhece a descrição dos produtos e fecha a nota em segundos, sem precisar abrir sistemas complexos. Sensacional, recomendo!”

{/* FAQ E DUVIDAS */}

Dúvidas Frequentes

Nossa equipe está pronta para lhe integrar à nova era fiscal.

Preciso de um certificado digital para que funcione?

Sim. É requisito legal da Secretaria da Fazenda que toda empresa possua certificado A1 (.pfx) para gerar a assinatura digital nas transações financeiras. Após o upload o robô assume esta etapa por você.

É seguro enviar os dados das vendas pelo WhatsApp?

Absolutamente. Nosso tráfego emprega criptografia E2EE (ponta-a-ponta) e descartamos históricos temporais de conversão em áudio em servidores imediatamente após a geração do Danfe.

O bot do WhatsApp funciona aos finais de semana?

Sim! A automação operada em infraestrutura da IAAGRO está disponível 24 horas por dia, 7 dias por semana, sem intervenção humana, incluindo feriados nacionais.

E se a nota gerar um erro por informações enviadas erradas (Cancelamento)?

Você pode cancelar a NF-e solicitando por mensagem no chat. O próprio bot faz os testes com o Portal Sefaz, caso alguma variável (como valor e CPF incompatíveis) aconteça, o sistema vai pedir confirmação antes da emissão definitiva (draft).

{/* FOOTER OFICIAL NUUV*/}
); } // === TERMOS === function Termos({ onNavigate, theme }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; return (
IAAGRO

Termos de Uso e Condições

Ao utilizar os serviços da IAAGRO, você concorda com os seguintes termos e condições:

1. Responsabilidade pelos Dados: A IAAGRO atua exclusivamente como uma ferramenta tecnológica facilitadora para a emissão de Notas Fiscais Eletrônicas (NF-e). NÃO NOS RESPONSABILIZAMOS pelos dados fiscais, valores, alíquotas, NCMs ou quaisquer outras informações tributárias informadas ou sugeridas pelo sistema.

2. Sugestões da Inteligência Artificial: As sugestões de códigos e tributações geradas por nossa Inteligência Artificial são baseadas em padrões de mercado, mas não substituem a validação de um contador qualificado. O cliente deve SEMPRE se atentar e conferir os dados antes de confirmar a emissão, pois apenas emitimos e damos sugestões.

3. Obrigação do Usuário: É de inteira responsabilidade do produtor rural ou de sua contabilidade garantir que todas as transações fiscais estejam de acordo com a legislação vigente em seu estado.

4. Armazenamento: Manteremos seus arquivos XML e PDF armazenados de forma segura em nossos servidores pelo período em que você mantiver uma assinatura ativa conosco.

); } // === LOGIN === function Login({ onNavigate, onLogin, theme, notify }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; const [phone, setPhone] = useState(''); const [password, setPassword] = useState(''); const [showPass, setShowPass] = useState(false); // Forgot password state const [forgotStep, setForgotStep] = useState(0); // 0=login, 1=enter phone, 2=enter code+newPass const [forgotPhone, setForgotPhone] = useState(''); const [resetCode, setResetCode] = useState(''); const [newPassword, setNewPassword] = useState(''); const [confirmNewPassword, setConfirmNewPassword] = useState(''); const [forgotLoading, setForgotLoading] = useState(false); const [showNewPass, setShowNewPass] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!phone || !password) return notify("Preencha todos os campos.", "error"); try { const response = await fetch(`${API_URL}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone: phone.replace(/\D/g, ''), password }) }); const data = await response.json(); if (response.ok) { onLogin(data); notify("Bem-vindo ao IAAGRO!", "success"); } else { notify(data.error || "Erro ao fazer login.", "error"); } } catch (err) { notify("Erro ao conectar com o servidor.", "error"); } }; const handleForgotSendCode = async (e) => { e.preventDefault(); if (!forgotPhone) return notify("Informe seu telefone.", "error"); setForgotLoading(true); try { const res = await fetch(`${API_URL}/forgot-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone: forgotPhone.replace(/\D/g, '') }) }); const data = await res.json(); if (res.status === 429) { notify(data.error || "Muitas tentativas. Aguarde.", "error"); } else if (res.ok) { notify("Código enviado via WhatsApp! Verifique seu celular.", "success"); setForgotStep(2); } else { notify(data.error || "Erro ao enviar código.", "error"); } } catch { notify("Erro ao conectar com o servidor.", "error"); } setForgotLoading(false); }; const handleResetPassword = async (e) => { e.preventDefault(); if (!resetCode || !newPassword) return notify("Preencha todos os campos.", "error"); const erroSenha = validarSenha(newPassword); if (erroSenha) return notify(erroSenha, "error"); if (newPassword !== confirmNewPassword) return notify("As senhas não coincidem.", "error"); setForgotLoading(true); try { const res = await fetch(`${API_URL}/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ phone: forgotPhone.replace(/\D/g, ''), code: resetCode, newPassword }) }); const data = await res.json(); if (res.ok) { notify("Senha redefinida com sucesso! Faça login.", "success"); setForgotStep(0); setResetCode(''); setNewPassword(''); setConfirmNewPassword(''); } else { notify(data.error || "Erro ao redefinir senha.", "error"); } } catch { notify("Erro ao conectar com o servidor.", "error"); } setForgotLoading(false); }; // --- Forgot password step 1: enter phone --- if (forgotStep === 1) { return (
IAAGRO

Recuperar Senha

Informe seu telefone cadastrado para receber o código via WhatsApp

setForgotPhone(e.target.value)} />
); } // --- Forgot password step 2: enter code + new password --- if (forgotStep === 2) { return (
IAAGRO

Nova Senha

Digite o código de 6 dígitos enviado via WhatsApp e sua nova senha

setResetCode(e.target.value)} maxLength={6} style={{ letterSpacing: '0.5em', textAlign: 'center', fontSize: '1.3rem', fontWeight: 'bold' }} />
setNewPassword(e.target.value)} />
setConfirmNewPassword(e.target.value)} />
); } // --- Normal login --- return (
IAAGRO

Acessar Portal

Emita suas notas via IA no WhatsApp

setPhone(e.target.value)} />
setPassword(e.target.value)} />
Ainda não é cliente?
); } // === REGISTER === function Register({ onNavigate, onLogin, theme, notify }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; const [formData, setFormData] = useState({ name: '', cpf: '', email: '', cnpj: '', phone: '', password: '', confirmPassword: '' }); const [showPass, setShowPass] = useState(false); const [termsAccepted, setTermsAccepted] = useState(false); const handleChange = (e) => { setFormData({ ...formData, [e.target.id]: e.target.value }); }; const handleSubmit = async (e) => { e.preventDefault(); if (!formData.name || !formData.email || !formData.cnpj || !formData.phone || !formData.password || !formData.cpf) { return notify("Preencha todos os campos.", "error"); } if (!termsAccepted) { return notify("Você deve aceitar os Termos de Uso e Condições.", "error"); } if (!validarCPF(formData.cpf)) { return notify("CPF Inválido. Digite um CPF correto.", "error"); } const erroSenha = validarSenha(formData.password); if (erroSenha) return notify(erroSenha, "error"); if (formData.password !== formData.confirmPassword) { return notify("As senhas não coincidem.", "error"); } try { const response = await fetch(`${API_URL}/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, cpf: formData.cpf.replace(/\D/g, ''), phone: formData.phone.replace(/\D/g, '') }) }); const data = await response.json(); if (response.ok) { onLogin(data); notify("Cadastro realizado com sucesso!", "success"); } else { notify(data.error || "Erro ao registrar.", "error"); } } catch (err) { notify("Erro ao conectar com o servidor.", "error"); } }; return (
IAAGRO

Começar Grátis

Prepare sua conta e seu certificado A1

setFormData({ ...formData, cpf: e.target.value.replace(/\D/g, '') })} />
setFormData({ ...formData, cnpj: e.target.value.replace(/\D/g, '') })} />

Mínimo 8 caracteres, 1 maiúscula, números e símbolos.

Já possui conta?
); } // === PORTAL DASHBOARD === function Portal({ user, onLogout, theme, notify }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; const [tab, setTab] = useState('perfil'); const [activeCnpj, setActiveCnpj] = useState(() => { if (user.available_cnpjs && user.available_cnpjs.length > 0) { return user.available_cnpjs[0]; } return { type: 'main', cnpj: user.cnpj, name: user.name, id: user.id }; }); return (
{/* Sidebar */}
{!user.subscription_valid && (
Atenção: Sua assinatura está vencida.
)}

Empresa Ativa (CNPJ)

{activeCnpj.hasCertificate && (
Certificado Ativo {activeCnpj.certificateExpirationDate ? `(Vence em: ${new Date(activeCnpj.certificateExpirationDate).toLocaleDateString('pt-BR')})` : ''}
)}
{tab === 'notas' && } {tab === 'perfil' && } {tab === 'plano' && } {tab === 'autorizados' && } {tab === 'nova-empresa' && window.location.reload()} />}
); } function NovaEmpresaView({ user, notify, onAdded }) { const [cnpj, setCnpj] = useState(''); const [name, setName] = useState(''); const [loading, setLoading] = useState(false); const handleSubmit = async (e) => { e.preventDefault(); if (!cnpj || !name) return notify("Preencha CNPJ e Nome", "error"); setLoading(true); try { const userStr = localStorage.getItem('iaagro_user'); const token = userStr ? JSON.parse(userStr).token : null; const res = await fetch(`${API_URL}/user/empresas`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ cnpj: cnpj.replace(/\D/g, ''), name }) }); const data = await res.json(); if (res.ok) { notify("Empresa adicionada com sucesso!", "success"); setTimeout(() => onAdded(), 1000); } else { notify(data.error || "Erro ao adicionar empresa", "error"); } } catch(err) { notify("Erro de conexão", "error"); } setLoading(false); }; return (

Adicionar Novo CNPJ

Insira os dados da nova empresa. O limite de CNPJs depende do seu plano atual.

setName(e.target.value)} placeholder="Ex: Fazenda Bela Vista" required />
setCnpj(formatCNPJ(e.target.value))} placeholder="00.000.000/0000-00" required maxLength={18} />
); } function NotasView({ user, notify }) { const userId = user.id; const [notas, setNotas] = useState([]); const [search, setSearch] = useState(''); const [dataInicio, setDataInicio] = useState(''); const [dataFim, setDataFim] = useState(''); const [selected, setSelected] = useState([]); useEffect(() => { const fetchNotas = async () => { try { const response = await fetch(`${API_URL}/notas?userId=${userId}`); const data = await response.json(); if (response.ok && Array.isArray(data)) { setNotas(data); } else if (data.error) { console.error(data.error); } } catch (err) { console.error("Erro ao carregar notas."); } }; if (userId) fetchNotas(); }, [userId]); // Filtragem const filteredNotas = notas.filter(n => { let match = true; if (search && !n.numero.includes(search) && !n.chave.includes(search)) match = false; if (dataInicio && n.date < dataInicio) match = false; if (dataFim && n.date > dataFim) match = false; return match; }); const toggleSelectAll = (e) => { if (e.target.checked) setSelected(filteredNotas.map(n => n.id)); else setSelected([]); }; const toggleSelect = (id) => { if (selected.includes(id)) setSelected(selected.filter(s => s !== id)); else setSelected([...selected, id]); }; const handleDownload = async (nota, type) => { const key = type === 'xml' ? nota.xml_key : nota.pdf_key; if (!key) return notify("Arquivo não disponível.", "error"); try { notify("Iniciando download...", "info"); const url = `${API_URL}/download?key=${encodeURIComponent(key)}`; const token = localStorage.getItem('iaagro_token'); const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}` } }); if (!response.ok) throw new Error("Falha no download"); const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.style.display = 'none'; a.href = downloadUrl; a.download = `nota_${nota.numero}_${type}.${type}`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(downloadUrl); document.body.removeChild(a); notify("Download concluído!", "success"); } catch (err) { console.error(err); notify("Erro ao baixar o arquivo. Tente novamente.", "error"); } }; const handleBulkDownload = (type) => { if (selected.length === 0) return notify("Selecione ao menos uma nota.", "error"); notify(`Preparando arquivo ZIP com ${selected.length} notas (${type.toUpperCase()})`, "success"); } const handleAcao = async (rota, body, msgSucesso) => { try { const userStr = localStorage.getItem('iaagro_user'); const token = userStr ? JSON.parse(userStr).token : null; notify("Processando requisição no ACBr...", "info"); const res = await fetch(`${API_URL}/notas/${rota}`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(body) }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Erro na API do ACBr"); notify(msgSucesso, "success"); } catch (err) { console.error(err); notify(err.message, "error"); } }; const handleCancelar = (n) => { if(!n.chave) return notify("A nota não possui chave para ser cancelada.", "error"); const just = prompt("Informe a justificativa para cancelar (mínimo 15 caracteres):"); if (!just || just.length < 15) return notify("Justificativa inválida", "error"); handleAcao('cancelar', { chave: n.chave, justificativa: just }, "Cancelamento processado no ACBr!"); }; const handleCce = (n) => { if(!n.chave) return notify("A nota não possui chave para carta de correção.", "error"); const corr = prompt("Informe o texto da Carta de Correção (mínimo 15 caracteres):"); if (!corr || corr.length < 15) return notify("Texto de correção inválido", "error"); handleAcao('cce', { chave: n.chave, correcao: corr }, "Carta de Correção processada no ACBr!"); }; const handleInutilizar = (n) => { const just = prompt("Justificativa para inutilizar numeração (mínimo 15 caracteres):"); if (!just || just.length < 15) return notify("Justificativa inválida", "error"); const num = prompt("Informe o número exato para inutilizar (ex: 100):"); if (!num) return; const ano = n.date ? new Date(n.date).getFullYear().toString().slice(-2) : new Date().getFullYear().toString().slice(-2); handleAcao('inutilizar', { ano, modelo: "55", serie: n.serie || "1", numeroInicial: num, numeroFinal: num, justificativa: just }, "Inutilização processada no ACBr!"); }; const renderStatus = (nota) => { const temEvento = nota.status === 'Cancelada' || nota.status === 'Inutilizada' || (nota.eventos && nota.eventos.length > 0); if (temEvento) { return (
Aviso na Nota {nota.eventos && nota.eventos.length > 0 ? nota.eventos.join(', ') : nota.status}
); } return ; } return (

Notas Emitidas e Processadas

Histórico completo de transmissões via Secretaria da Fazenda.

Cota: {notas.length} / {user.notes_limit > 1000 ? 'Ilimitado' : (user.notes_limit || 0)} notas
setSearch(e.target.value)} />
setDataInicio(e.target.value)} />
setDataFim(e.target.value)} />
{filteredNotas.length > 0 ? ( {filteredNotas.map(n => ( ))}
0} style={{ width: '16px', height: '16px', cursor: 'pointer' }} /> Número Série Data Autorização Valor Bruto Status Sefaz Ações Fiscais Baixar Lote Único
toggleSelect(n.id)} style={{ width: '16px', height: '16px', cursor: 'pointer' }} /> {n.numero} {n.serie} {n.date ? n.date.split('T')[0].split('-').reverse().join('/') : '--/--/----'} {n.value} {renderStatus(n)}
) : (

Nenhuma nota encontrada nos filtros.

Seus apontamentos com IA ainda não geraram uma Sefaz oficial ou os filtros estão vazios.

)}
) } function PerfilView({ user, notify, activeCnpj }) { const targetCnpj = activeCnpj ? activeCnpj.cnpj : user.cnpj; const [cnpjData, setCnpjData] = useState(null); const [fetchingCnpj, setFetchingCnpj] = useState(false); const [codMunicipio, setCodMunicipio] = useState(null); const [fetchingCodMunicipio, setFetchingCodMunicipio] = useState(false); const [serieNfe, setSerieNfe] = useState(user.serie_nfe || '7'); const [ultimaNota, setUltimaNota] = useState(user.ultima_nota || 1); const [fileError, setFileError] = useState(''); const [isDrag, setIsDrag] = useState(false); const [showModal, setShowModal] = useState(false); const [correctionField, setCorrectionField] = useState('Razão Social'); const [correctionData, setCorrectionData] = useState(''); const [dropdownOpen, setDropdownOpen] = useState(false); const [certFile, setCertFile] = useState(null); const [certPass, setCertPass] = useState(''); const [saving, setSaving] = useState(false); const modalOptions = ["Razão Social", "Endereço / Logradouro", "Inscrição Estadual", "Natureza Jurídica", "Código IBGE"]; useEffect(() => { if (targetCnpj) { setFetchingCodMunicipio(true); const cleanCnpj = targetCnpj.replace(/\D/g, ''); fetch(`https://brasilapi.com.br/api/cnpj/v1/${cleanCnpj}`) .then(res => res.json()) .then(data => { if (data.razao_social) setCodMunicipio(data); }) .catch(err => console.log('Erro ao buscar código do município')) .finally(() => setFetchingCodMunicipio(false)); fetch(`https://api.opencnpj.org/${cleanCnpj}`) .then(res => res.json()) .then(data => { if (data.razao_social) setCnpjData(data); }) .catch(err => console.log('Erro ao buscar CNPJ (Usando Mock Local)')) .finally(() => setFetchingCnpj(false)); } }, [targetCnpj]); const processFile = (file) => { setFileError(''); if (file) { if (!file.name.toLowerCase().endsWith('.pfx')) { setFileError('Segurança: Arquivo rejeitado. Por favor, envie o certificado A1 original criptografado em .PFX.'); } else { setFileError('Sucesso: ' + file.name + ' anexado pronto para validação de senha.'); setCertFile(file); console.log("Certificado PFX aceito na checagem front-end", file); } } }; const handleDrag = (e) => { e.preventDefault(); e.stopPropagation(); if (e.type === 'dragenter' || e.type === 'dragover') setIsDrag(true); else setIsDrag(false); }; const handleDrop = (e) => { e.preventDefault(); e.stopPropagation(); setIsDrag(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { processFile(e.dataTransfer.files[0]); } }; const handleSaveNfeConfig = async () => { try { const userStr = localStorage.getItem('iaagro_user'); const token = userStr ? JSON.parse(userStr).token : null; notify("Salvando configurações...", "info"); const res = await fetch(`${API_URL}/user/update-nfe-config`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ serie_nfe: serieNfe, ultima_nota: ultimaNota }) }); if (!res.ok) throw new Error("Erro ao salvar configuração."); notify("Configuração de NF-e salva com sucesso!", "success"); } catch (err) { notify(err.message, "error"); } }; return (

Meu Perfil Físcal

Mantenha seus dados e certificados atualizados para o motor de Inteligência Artificial processar sem falhas.

{fetchingCnpj &&
Sincronizando metadados...
} {cnpjData && !fetchingCnpj && (
Dados da Empresa Sincronizados Oficialmente!

Dados retonados e validados do Emitente {cnpjData.razao_social}

)} {showModal && (

Solicitar Correção de Dado

setDropdownOpen(!dropdownOpen)} style={{ cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center', userSelect: 'none' }}> {correctionField}
{dropdownOpen && (
{modalOptions.map(opt => (
e.target.style.background = opt === correctionField ? 'var(--primary-color)' : 'rgba(128,128,128,0.1)'} onMouseLeave={(e) => e.target.style.background = opt === correctionField ? 'var(--primary-color)' : 'transparent'} style={{ padding: '0.8rem 1rem', borderRadius: '8px', cursor: 'pointer', transition: 'background 0.2s', backgroundColor: correctionField === opt ? 'var(--primary-color)' : 'transparent', color: correctionField === opt ? '#000' : 'var(--text-primary)', fontWeight: correctionField === opt ? '600' : 'normal', marginBottom: '2px' }} onClick={() => { setCorrectionField(opt); setDropdownOpen(false); }}> {opt}
))}
)}
)}

Configuração de Emissão (NF-e)

Atenção: Apenas altere estes dados se você souber o que está fazendo. Padrão: Série 7 e Última Nota Emitida 1.
setSerieNfe(e.target.value)} />
setUltimaNota(e.target.value)} />

Dados Tributários (Portal Automático)

Certificado Digital Sefaz

Para habilitar o disparo criptografado das notas, faça o upload de seu arquivo .PFX Tipo A1 válido.

{activeCnpj?.hasCertificate && (
Certificado Ativo na ACBr! Você já possui um certificado configurado {activeCnpj.certificateExpirationDate ? `(Vence em ${new Date(activeCnpj.certificateExpirationDate).toLocaleDateString('pt-BR')})` : ''}. Não é necessário enviar novamente, a menos que queira substituí-lo.
)}
processFile(e.target.files[0])} className="drop-input" />

Arraste seu certificado .pfx aqui ou clique para procurar

{fileError &&

{fileError}

}
setCertPass(e.target.value)} />
); } function AuthorizedNumbersView({ user, notify }) { const { id: userId, plan_key: planKey } = user; const [numbers, setNumbers] = useState([]); const [loading, setLoading] = useState(true); const [newName, setNewName] = useState(''); const [newPhone, setNewPhone] = useState(''); const limits = { produtor: 3, produtor_anual: 3, familia: 5, familia_anual: 5, cooperativa: 10, cooperativa_anual: 10, iniciante: 5 }; const maxNum = limits[planKey] || 0; const fetchNumbers = async () => { try { const res = await fetch(`${API_URL}/authorized-numbers/${userId}`); const data = await res.json(); if (res.ok && Array.isArray(data)) { setNumbers(data); } else { setNumbers([]); if (data.error) console.error(data.error); } } catch (err) { console.error(err); setNumbers([]); } finally { setLoading(false); } }; useEffect(() => { fetchNumbers(); }, []); const handleAdd = async (e) => { e.preventDefault(); if (!newName || !newPhone) return notify("Preencha nome e número.", "error"); try { const res = await fetch(`${API_URL}/authorized-numbers/add`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId, name: newName, phone: newPhone }) }); const data = await res.json(); if (res.ok && data.success) { setNewName(''); setNewPhone(''); fetchNumbers(); notify("Número autorizado com sucesso!", "success"); } else { notify(data.error || "Erro ao adicionar.", "error"); } } catch (err) { console.error(err); notify("Erro no servidor.", "error"); } }; const handleDelete = async (id) => { if (!confirm("Deseja remover esta autorização?")) return; try { const res = await fetch(`${API_URL}/authorized-numbers/${id}`, { method: 'DELETE' }); if (res.ok) { fetchNumbers(); notify("Autorização removida.", "info"); } } catch (err) { console.error(err); } }; return (

Números Autorizados

Gerencie quem pode emitir notas fiscais pelo WhatsApp em seu nome.

0 && numbers.length >= maxNum ? 'var(--danger)' : 'var(--primary-color)' }}>{numbers.length} / {maxNum || 0}
Números em uso

Adicionar Nova Autorização

setNewName(e.target.value)} placeholder="Ex: João da Fazenda" />
setNewPhone(e.target.value)} placeholder="1530422070" />

Lista de Autorizados

{loading ?

Carregando...

: ( {numbers.map(n => ( ))} {numbers.length === 0 && ( )}
Nome Número (WhatsApp) Data Autorização Ações
{n.name} {n.phone} {new Date(n.created_at).toLocaleDateString()}
Ninguém autorizado ainda.
)}
); } function PlanoView({ user, notify }) { const [loading, setLoading] = useState(false); const [currentUser, setCurrentUser] = useState(user); const [showPayForm, setShowPayForm] = useState(null); // 'iniciante', 'profissional', etc const [cardData, setCardData] = useState({ holderName: '', number: '', expiry: '', cvv: '' }); const [billingCycle, setBillingCycle] = React.useState('annual'); useEffect(() => { // Atualiza dados do usuário para pegar plano/vencimento novo fetch(`${API_URL}/user/${user.id}`) .then(res => res.json()) .then(data => { if (data.id) setCurrentUser(data); }); }, []); const handleSubscribe = async (planKey) => { setLoading(true); try { const response = await fetch(`${API_URL}/subscription/checkout`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: user.id, planKey }) }); const data = await response.json(); if (response.ok) { if (data.invoiceUrl) { const width = 600; const height = 800; const left = (window.screen.width / 2) - (width / 2); const top = (window.screen.height / 2) - (height / 2); const popup = window.open(data.invoiceUrl, 'AsaasCheckout', `width=${width},height=${height},top=${top},left=${left}`); if (!popup || popup.closed || typeof popup.closed === 'undefined') { notify("Checkout bloqueado pelo navegador. Por favor, libere popups e tente novamente.", "error"); // Adicional: Abrir fallback em aba normal window.open(data.invoiceUrl, '_blank'); } else { notify("Janela de pagamento aberta! Aguarde a conclusão.", "info"); } // Polling para verificar quando o plano muda const checkStatus = setInterval(async () => { const res = await fetch(`${API_URL}/user/${user.id}`); const updated = await res.json(); // Ele só deve dar confirmed se o pagamento aprovar e o plano mudar no banco if (updated.subscription_status === 'ACTIVE' && updated.plan_key === planKey) { setCurrentUser(updated); notify("Pagamento confirmado! Plano ativado com sucesso.", "success"); clearInterval(checkStatus); } }, 5000); // Limpar polling após 10 minutos setTimeout(() => clearInterval(checkStatus), 600000); } else { notify("Requisição concluída.", "success"); } } else { notify(data.error || "Erro ao processar assinatura.", "error"); } } catch (err) { notify("Erro de conexão.", "error"); } finally { setLoading(false); } }; const isCurrentPlan = (key) => currentUser.plan_key === key; const isScheduled = (key) => currentUser.next_plan_key === key; return (

Gestão de Assinatura

Status atual: {currentUser.subscription_status === 'ACTIVE' ? `Pago - Válido até ${new Date(currentUser.due_date).toLocaleDateString()}` : 'Aguardando Pagamento / Inativo'}

Cota de Emissão: {currentUser.notes_used || 0} / {currentUser.notes_limit > 1000 ? 'Ilimitado' : (currentUser.notes_limit || 0)} notas

{/* --- Toggle Mensal/Anual (Removido, plano fixo no anual) --- */}
{(isCurrentPlan('produtor') || isCurrentPlan('produtor_anual')) &&
Seu Plano
}

Produtor

R${billingCycle === 'monthly' ? '74,90' : '64,90'}/mês
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 30 Emissões mensais
  • 1 CNPJ/CPF
  • 3 Números Autorizados
{billingCycle === 'monthly' ? ( ) : ( )}
{(isCurrentPlan('familia') || isCurrentPlan('familia_anual')) ?
Seu Plano
:
Recomendado
}

Família

R${billingCycle === 'monthly' ? '104,90' : '94,90'}/mês
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 100 Emissões mensais
  • 3 CNPJ/CPF
  • 5 Números Autorizados
{billingCycle === 'monthly' ? ( ) : ( )}
{(isCurrentPlan('cooperativa') || isCurrentPlan('cooperativa_anual')) &&
Seu Plano
}

Cooperativa

R${billingCycle === 'monthly' ? '174,90' : '154,90'}/mês
{billingCycle === 'monthly' ? 'mensal' : 'mensal (faturado anualmente)'}
    {billingCycle === 'annual' &&
  • 1 Certificado A1 Grátis
  • }
  • 500 Emissões mensais
  • CNPJ/CPF Ilimitado
  • 10 Números Autorizados
{billingCycle === 'monthly' ? ( ) : ( )}
); } // === COMPONENTE DE FUNDO INTERATIVO === function InteractiveBackground() { const glowRef = React.useRef(null); const gridRef = React.useRef(null); useEffect(() => { let mouseX = window.innerWidth / 2; let mouseY = window.innerHeight / 2; const handleMouseMove = (e) => { mouseX = e.clientX; mouseY = e.clientY; }; window.addEventListener('mousemove', handleMouseMove); let animationFrameId; let currentX = mouseX; let currentY = mouseY; const render = () => { currentX += (mouseX - currentX) * 0.06; currentY += (mouseY - currentY) * 0.06; if (glowRef.current) { glowRef.current.style.transform = `translate(calc(${currentX}px - 50%), calc(${currentY}px - 50%))`; } if (gridRef.current) { // Mascara o GRID iluminando somente a área envolta do cursor dinamicamente const maskStyle = `radial-gradient(circle 350px at ${currentX}px ${currentY}px, black 0%, transparent 100%)`; gridRef.current.style.maskImage = maskStyle; gridRef.current.style.WebkitMaskImage = maskStyle; } animationFrameId = requestAnimationFrame(render); }; render(); return () => { window.removeEventListener('mousemove', handleMouseMove); cancelAnimationFrame(animationFrameId); }; }, []); return (
{/* O Grid Transparente que é revelado pelo mouse */}
{/* Orbe dinâmico de luz verde/neon */}
); } function CheckoutResult({ type, onNavigate, theme }) { const logoSrc = theme === 'dark' ? 'assets/logo-dark.svg' : 'assets/logo-light.svg'; return (
IAAGRO {type === 'success' ? ( <>

Pagamento Concluído!

Parabéns! Sua assinatura foi ativada com sucesso. Você já pode emitir suas Notas Fiscais no WhatsApp com o poder da IA.

) : ( <>

Pagamento Cancelado

O processo de pagamento não foi concluído. Fique tranquilo, nenhuma cobrança foi realizada.

)}
); } // Render do React const root = ReactDOM.createRoot(document.getElementById('root')); root.render();