// ============================================================ // Calc logic v2 — modelo simplificado SH Squads // v1 preservada em logic.jsx // ============================================================ // ---------- Range → average value mappers ---------- const CLIENTES_MAP = { "<500": 300, "500-2k": 1200, "2k-10k": 6000, "10k-50k": 30000, "50k+": 100000, }; const FATURAMENTO_MAP = { "<100k": 75000, "100k-500k": 300000, "500k-2M": 1250000, "2M-10M": 6000000, "10M+": 20000000, }; // Display labels const CLIENTES_LABEL = { "<500": "Menos de 500", "500-2k": "500 a 2.000", "2k-10k": "2.000 a 10.000", "10k-50k": "10.000 a 50.000", "50k+": "Mais de 50.000", }; const FATURAMENTO_LABEL = { "<100k": "Até R$ 100k", "100k-500k": "R$ 100k a R$ 500k", "500k-2M": "R$ 500k a R$ 2M", "2M-10M": "R$ 2M a R$ 10M", "10M+": "Mais de R$ 10M", }; const SEGMENT_LABEL = { marketplace: "Marketplace / Plataforma", saas: "SaaS / Software", varejo: "Varejo / E-commerce", educacao: "Educação / Health / Serviços", fintech: "Fintech / Banking", outro: "Outro", }; // ---------- Product meta ---------- const PRODUCTS = [ { id: "maquininha", name: "Maquininha de Cartão / Gateway", desc: "Receita por % do volume processado · sub-adquirência parceira", model: "Sub-adquirência · spread MDR", icon: "credit-card" }, { id: "conta", name: "Conta digital", desc: "Ganhe por tarifas e cesta de serviços ou reduza o custo que você já tem hoje", model: "Float + tarifa", icon: "wallet" }, { id: "cartao", name: "Cartão com sua marca", desc: "Interchange + anuidade em cartão emitido pela sua fintech", model: "Interchange + anuidade", icon: "card-shield" }, { id: "credito", name: "Crédito pessoal ou PJ", desc: "Spread sobre originações via securitizadora parceira", model: "Securitizadora · spread sobre originação", icon: "trending-up" }, { id: "antecipacao", name: "Antecipação de recebíveis", desc: "Taxa sobre antecipação dos pagamentos", model: "Taxa de antecipação", icon: "fast-forward" }, { id: "cambio", name: "Câmbio / Internacional", desc: "Spread em remessas e operações cambiais", model: "Spread cambial", icon: "globe" }, ]; // ---------- v2 Model constants ---------- // Taxa de adoção baseada em experiência dos sócios const ADOPTION_RATE = 0.70127; // Base anual por cliente (independente de produto) const BASE_PER_CLIENT = 700; // Peso anual por produto — escolhidos para nunca gerar soma terminando em .x0 // Combinação válida: últimos dígitos {3,6,3,3,3,3} const PRODUCT_WEIGHTS = { maquininha: 162.23, conta: 114.56, antecipacao: 210.13, cambio: 132.93, cartao: 143.63, credito: 189.03, }; // ---------- Receita calc v2 ---------- function computeReceita(form) { const clientes = CLIENTES_MAP[form.clientes] || 0; const faturamentoMensal = FATURAMENTO_MAP[form.faturamento] || 0; const faturamentoAnual = faturamentoMensal * 12; const ticketMedio = clientes > 0 ? faturamentoMensal / clientes : 0; // Clientes que adotam os serviços financeiros const adoptingClients = clientes * ADOPTION_RATE; // Soma dos pesos dos produtos selecionados let weightSum = 0; for (const pid of form.produtos) { weightSum += PRODUCT_WEIGHTS[pid] || 0; } // Total = clientes adotantes × (base + soma dos pesos selecionados) const basePerClient = BASE_PER_CLIENT + weightSum; const total = adoptingClients * basePerClient; // Receita por produto: peso + fração da base (para somar = total) // Base R$700 dividida igualmente entre os produtos selecionados const n = form.produtos.length || 1; const baseShare = BASE_PER_CLIENT / n; const byProduct = {}; for (const pid of form.produtos) { const w = PRODUCT_WEIGHTS[pid] || 0; byProduct[pid] = adoptingClients * (baseShare + w); } return { total, byProduct, clientes, faturamentoMensal, faturamentoAnual, ticketMedio, adoptingClients, basePerClient, }; } // ---------- Formatters (pt-BR) ---------- function fmtBRL(v, opts = {}) { const { short = false, fractionDigits } = opts; if (v == null || isNaN(v)) return "R$ 0"; if (short) { if (Math.abs(v) >= 1e9) return "R$ " + (v / 1e9).toLocaleString("pt-BR", { maximumFractionDigits: 2 }) + " bi"; if (Math.abs(v) >= 1e6) return "R$ " + (v / 1e6).toLocaleString("pt-BR", { maximumFractionDigits: 2 }) + " M"; if (Math.abs(v) >= 1e3) return "R$ " + (v / 1e3).toLocaleString("pt-BR", { maximumFractionDigits: 0 }) + "k"; return "R$ " + Math.round(v).toLocaleString("pt-BR"); } const fd = fractionDigits != null ? fractionDigits : 2; return "R$ " + v.toLocaleString("pt-BR", { minimumFractionDigits: fd, maximumFractionDigits: fd }); } function fmtNum(v) { if (v == null || isNaN(v)) return "0"; return Math.round(v).toLocaleString("pt-BR"); } // ---------- Email validation ---------- const PERSONAL_EMAIL_DOMAINS = new Set([ "gmail.com","hotmail.com","outlook.com","yahoo.com","yahoo.com.br", "icloud.com","live.com","msn.com","bol.com.br","uol.com.br", "terra.com.br","ig.com.br","globo.com","r7.com", ]); function validateEmail(value) { const v = (value || "").trim().toLowerCase(); if (!v) return { ok: false, kind: "empty" }; const m = /^[a-z0-9._%+\-]+@([a-z0-9.\-]+\.[a-z]{2,})$/i.exec(v); if (!m) return { ok: false, kind: "format" }; const domain = m[1].toLowerCase(); if (PERSONAL_EMAIL_DOMAINS.has(domain)) return { ok: false, kind: "personal" }; return { ok: true }; } // ---------- WhatsApp mask ---------- // Formato: (XX) XXXXX-XXXX — sem prefixo +55 no valor do input function maskPhone(v) { const digits = (v || "").replace(/\D/g, "").slice(0, 11); if (digits.length === 0) return ""; if (digits.length <= 2) return `(${digits}`; if (digits.length <= 7) return `(${digits.slice(0,2)}) ${digits.slice(2)}`; return `(${digits.slice(0,2)}) ${digits.slice(2,7)}-${digits.slice(7,11)}`; } function isPhoneValid(v) { const d = (v || "").replace(/\D/g, ""); return d.length >= 10 && d.length <= 11; } // ---------- Icons ---------- function Icon({ name, size = 20, stroke = 1.7, ...rest }) { const props = { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", ...rest, }; switch (name) { case "arrow-right": return ; case "check": return ; case "x": return ; case "calculator": return ; case "trending-up": return ; case "credit-card": return ; case "wallet": return ; case "card-shield": return ; case "fast-forward": return ; case "globe": return ; case "spark": return ; case "linkedin": return ; case "whatsapp": return ; case "link": return ; case "download": return ; case "calendar": return ; case "chevron-left": return ; case "alert": return ; default: return null; } } // expose to globals (sobrescreve a v1 se ambas carregadas) Object.assign(window, { CLIENTES_MAP, FATURAMENTO_MAP, CLIENTES_LABEL, FATURAMENTO_LABEL, SEGMENT_LABEL, PRODUCTS, PRODUCT_WEIGHTS, ADOPTION_RATE, BASE_PER_CLIENT, computeReceita, fmtBRL, fmtNum, validateEmail, maskPhone, isPhoneValid, Icon, });