Partners Elite de Prospección

Commission-Based Appointment Setter for Digital Agency Services

Comercial por Comisión para Servicios de Agencia Digital

Agenda reuniones B2B cualificadas para servicios premium de desarrollo web, asistencia virtual y branding.

Vetting Process 0% completed
1. Contact Information
2. Outbound Experience & Channels
3. Verification & Metrics
const sections = document.querySelectorAll("details"); sections.forEach((section) => { section.querySelector("summary").addEventListener("click", (e) => { if (section.hasAttribute("open")) return; sections.forEach((otherSection) => { if (otherSection !== section) otherSection.removeAttribute("open"); }); }); }); const dropzones = ['cv', 'portfolio']; dropzones.forEach(id => { const dropzoneEl = document.getElementById(`dropzone-${id}`); const inputEl = document.getElementById(id); ['dragenter', 'dragover'].forEach(eventName => { dropzoneEl.addEventListener(eventName, (e) => { e.preventDefault(); dropzoneEl.classList.add('dragover'); }, false); }); ['dragleave', 'drop'].forEach(eventName => { dropzoneEl.addEventListener(eventName, (e) => { e.preventDefault(); dropzoneEl.classList.remove('dragover'); }, false); }); dropzoneEl.addEventListener('drop', (e) => { const dt = e.dataTransfer; inputEl.files = dt.files; inputEl.dispatchEvent(new Event('change')); }, false); }); function handleFileSelect(input, previewId) { const preview = document.getElementById(previewId); preview.innerHTML = ''; if(input.files.length > 0) { Array.from(input.files).forEach(file => { const item = document.createElement('div'); item.className = 'file-item'; item.innerHTML = `🎯 ${file.name} (${(file.size/1024/1024).toFixed(2)} MB)`; preview.appendChild(item); }); input.closest('.form-group').classList.remove('error'); input.closest('.form-group').classList.add('valid'); } else { input.closest('.form-group').classList.remove('valid'); } calculateOverallProgress(); } function validateInput(element) { const group = element.closest('.form-group'); if (element.value.trim() !== "") { group.classList.remove('error'); group.classList.add('valid'); } else { group.classList.remove('valid'); group.classList.add('error'); } calculateOverallProgress(); } function validateEmailField(element) { const group = element.closest('.form-group'); const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (emailRegex.test(element.value)) { group.classList.remove('error'); group.classList.add('valid'); } else { group.classList.remove('valid'); group.classList.add('error'); } calculateOverallProgress(); } function calculateOverallProgress() { const fields = [ document.getElementById('fullname'), document.getElementById('email'), document.getElementById('phone'), document.getElementById('channel'), document.getElementById('stack'), document.getElementById('experience'), document.getElementById('cv'), document.getElementById('portfolio') ]; let completed = 0; fields.forEach(field => { if(field.type === 'file') { if(field.files && field.files.length > 0) completed++; } else if(field.type === 'email') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if(emailRegex.test(field.value)) completed++; } else { if(field.value.trim() !== "") completed++; } }); const percent = Math.round((completed / fields.length) * 100); document.getElementById('progressBar').style.width = percent + '%'; document.getElementById('progressText').textContent = percent + '% completed'; } function handleFormSubmit(event) { event.preventDefault(); const fields = [ { el: document.getElementById('fullname'), group: 'group-fullname', sec: 'sec1', type: 'text' }, { el: document.getElementById('email'), group: 'group-email', sec: 'sec1', type: 'email' }, { el: document.getElementById('phone'), group: 'group-phone', sec: 'sec1', type: 'text' }, { el: document.getElementById('channel'), group: 'group-channel', sec: 'sec2', type: 'text' }, { el: document.getElementById('stack'), group: 'group-stack', sec: 'sec2', type: 'text' }, { el: document.getElementById('experience'), group: 'group-experience', sec: 'sec2', type: 'text' }, { el: document.getElementById('cv'), group: 'group-cv', sec: 'sec3', type: 'file' }, { el: document.getElementById('portfolio'), group: 'group-portfolio', sec: 'sec3', type: 'file' } ]; let firstErrorField = null; fields.forEach(field => { const groupEl = document.getElementById(field.group); let isValid = false; if (field.type === 'file') { isValid = (field.el.files && field.el.files.length > 0); } else if (field.type === 'email') { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; isValid = emailRegex.test(field.el.value); } else { isValid = field.el.value.trim() !== ""; } if (!isValid) { groupEl.classList.add('error'); groupEl.classList.remove('valid'); if (!firstErrorField) firstErrorField = field; } else { groupEl.classList.remove('error'); groupEl.classList.add('valid'); } }); if (firstErrorField) { const sectionToOpen = document.getElementById(firstErrorField.sec); sectionToOpen.setAttribute('open', 'true'); sections.forEach(sec => { if(sec.id !== firstErrorField.sec) sec.removeAttribute('open'); }); document.getElementById(firstErrorField.group).scrollIntoView({ behavior: 'smooth', block: 'center' }); return; } const submitBtn = document.getElementById('submitBtn'); const errorBanner = document.getElementById('submitError'); errorBanner.classList.remove('show'); // Conversión de datos en el FormData para envío estructurado const formData = new FormData(); formData.append('candidate_name', document.getElementById('fullname').value); formData.append('candidate_email', document.getElementById('email').value); formData.append('candidate_phone', document.getElementById('phone').value); formData.append('outbound_channel', document.getElementById('channel').value); formData.append('tech_stack', document.getElementById('stack').value); formData.append('monthly_volume', document.getElementById('experience').value); // Función auxiliar para convertir archivos a Base64 para que Google Apps Script los reciba sin problemas const readFileAsBase64 = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve({ base64: reader.result.split(',')[1], filename: file.name, mimeType: file.type }); reader.onerror = error => reject(error); reader.readAsDataURL(file); }); }; // Estado de carga: deshabilitamos el botón y avisamos al usuario const originalBtnHTML = submitBtn.innerHTML; submitBtn.disabled = true; submitBtn.textContent = 'Enviando… / Submitting…'; // Ejecución asíncrona del procesado y subida. // El modal de éxito SOLO se muestra si el servidor confirma la recepción. (async () => { try { const cvFile = document.getElementById('cv').files[0]; const cvData = await readFileAsBase64(cvFile); formData.append('cv_base64', cvData.base64); formData.append('cv_filename', cvData.filename); formData.append('cv_mimetype', cvData.mimeType); const portfolioFiles = document.getElementById('portfolio').files; const portfolioPromises = Array.from(portfolioFiles).map(file => readFileAsBase64(file)); const portfolioResults = await Promise.all(portfolioPromises); formData.append('portfolio_files', JSON.stringify(portfolioResults)); // ENVÍO DIRECTO A GOOGLE APPS SCRIPT (sin Worker intermediario) await fetch('https://script.google.com/macros/s/TU_DEPLOYMENT_ID_AQUI/exec', { method: 'POST', mode: 'no-cors', body: formData }); // Con no-cors la respuesta es opaca — mostramos éxito directamente. document.getElementById('thanksModal').classList.add('active'); } catch (err) { console.error("Error en el procesado de envío:", err); errorBanner.classList.add('show'); errorBanner.scrollIntoView({ behavior: 'smooth', block: 'center' }); } finally { submitBtn.disabled = false; submitBtn.innerHTML = originalBtnHTML; } })(); } function closeModal() { document.getElementById('thanksModal').classList.remove('active'); document.getElementById('neroxForm').reset(); document.querySelectorAll('.form-group').forEach(group => group.classList.remove('valid', 'error')); document.getElementById('preview-cv').innerHTML = ''; document.getElementById('preview-port').innerHTML = ''; document.getElementById('sec1').setAttribute('open', 'true'); document.getElementById('sec2').removeAttribute('open'); document.getElementById('sec3').removeAttribute('open'); calculateOverallProgress(); }