/* ===== Growing Now — Typeform-style contact flow (hosted by Zuki) ===== */ (function () { const { useState, useEffect, useRef } = React; const Icon = window.Icon; window.gcalLink = function (lang = 'es') { return window.GN.bookingUrl('zuki', lang, 'lead_form'); }; window.saveLead = async function (lead) { const response = await fetch('/api/submit-lead', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(lead) }); if (!response.ok) throw new Error('Lead submission failed'); const data = await response.json(); if (!data.ok) throw new Error('Lead submission was not confirmed'); return data; }; const TIER = { hot: { es: 'Cliente ideal', en: 'Ideal client', c: '#2E8C3A' }, warm: { es: 'Buen potencial', en: 'Good potential', c: '#F7941E' }, cold: { es: 'Por explorar', en: 'To explore', c: '#2E8BD6' }, }; function Gauge({ value, color, label }) { const r = 54, c = 2 * Math.PI * r, off = c * (1 - value / 100); return (
{value}
{label}
); } function ContactModal({ lang, onClose, onChat }) { const L = window.I18N.STR[lang]; const Q = window.GN.questions; const zuki = window.GN.byId.zuki; const [idx, setIdx] = useState(-1); // -1 intro, n = done const [ans, setAns] = useState({}); const [err, setErr] = useState(''); const [result, setResult] = useState(null); const [submitting, setSubmitting] = useState(false); const inputRef = useRef(null); useEffect(() => { document.body.classList.add('no-scroll'); return () => document.body.classList.remove('no-scroll'); }, []); useEffect(() => { setErr(''); if (inputRef.current) setTimeout(() => inputRef.current.focus(), 120); }, [idx]); const total = Q.length; const q = idx >= 0 && idx < total ? Q[idx] : null; function next() { if (q) { if (q.required && !ans[q.id]) { setErr(L.form_required); return; } if (q.type === 'email' && ans.email && !/^\S+@\S+\.\S+$/.test(ans.email)) { setErr(L.form_required); return; } } if (idx === total - 1) return submit(); setIdx(idx + 1); } function back() { if (idx > -1) setIdx(idx - 1); } async function submit() { if (submitting) return; const sc = window.GN.score(ans); const lead = { id: 'L' + Date.now().toString(36), ts: Date.now(), agent_id: 'zuki', source: 'lead_form', answers: ans, score: sc, status: 'new', lang }; setSubmitting(true); setErr(''); try { await window.saveLead(lead); setResult({ lead, sc }); setIdx(total); } catch (error) { setErr(L.form_submit_error); } finally { setSubmitting(false); } } const setVal = (v) => setAns(a => ({ ...a, [q.id]: v })); const toggle = (v) => setAns(a => { const cur = a[q.id] || []; return { ...a, [q.id]: cur.includes(v) ? cur.filter(x => x !== v) : [...cur, v] }; }); const chooseSingle = (v) => { setAns(a => ({ ...a, [q.id]: v })); setErr(''); setTimeout(() => setIdx(current => current + 1), 220); }; const progress = idx < 0 ? 0 : idx >= total ? 100 : Math.round((idx / total) * 100); return (
{/* progress */}
Zuki
Zuki
{idx === -1 && (
Zuki

{L.form_intro_title}

{L.form_intro_sub}

{lang === 'es' ? 'Al enviar, aceptas que usemos tus datos para responder a tu solicitud. Consulta nuestra ' : 'By submitting, you agree that we may use your information to respond to your request. See our '} {lang === 'es' ? 'Política de privacidad' : 'Privacy Policy'} {lang === 'es' ? ' y el ' : ' and '} {lang === 'es' ? 'aviso de IA' : 'AI notice'}.

)} {q && (
{idx + 1} / {total}

{q.q[lang]}

{q.sub &&

{q.sub[lang]}

}
{(q.type === 'text' || q.type === 'email') && ( setVal(e.target.value)} onKeyDown={e => e.key === 'Enter' && next()} style={fieldStyle} /> )} {q.type === 'textarea' && (