Load experts from GET endpoint, dynamic render, contact modal with POST to webhook

This commit is contained in:
Hermes Agent
2026-08-07 15:21:58 +00:00
parent 52c50d7aa1
commit 7ecb47d2be
+200 -117
View File
@@ -268,126 +268,16 @@
<!-- Expert grid (mock data) --> <!-- Expert grid (mock data) -->
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6" id="expertGrid"> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6" id="expertGrid">
<!-- Expert 1 --> <!-- Dynamically loaded from GET endpoint -->
<div class="expert-card fade-up visible" data-role="funcional" data-certified="true"> <div id="expertGridLoading" style="grid-column:1/-1;text-align:center;padding:60px 0;color:#64748b;">
<div class="flex items-center justify-between mb-6"> <div style="width:32px;height:32px;border:3px solid #e2e8f0;border-top-color:#4338ca;border-radius:50%;margin:0 auto 16px;animation:spin 0.8s linear infinite;"></div>
<div class="avatar" style="background:linear-gradient(135deg,#4338ca,#3b82f6);">CM</div> <p style="font-size:0.9375rem;">Cargando expertos...</p>
<span class="badge-certified" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Certificado</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">Carlos Méndez</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Consultor funcional · España</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">+8 años implementando Odoo en pymes. Especialista en inventario, contabilidad y fabricación. Más de 40 proyectos completados.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Inventario</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Contabilidad</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Fabricación</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">5 referencias verificadas</span>
</div> </div>
</div> </div>
<!-- Expert 2 --> <style>
<div class="expert-card fade-up visible" data-role="developer" data-certified="true"> @keyframes spin { to { transform: rotate(360deg); } }
<div class="flex items-center justify-between mb-6"> </style>
<div class="avatar" style="background:linear-gradient(135deg,#10b981,#3b82f6);">AL</div>
<span class="badge-certified" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Certificada</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">Ana López</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Desarrolladora Odoo · México</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">Desarrolladora full-stack especializada en módulos personalizados de Odoo. Experta en API, integraciones y migraciones de datos.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Desarrollo</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">API</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Migraciones</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">8 referencias verificadas</span>
</div>
</div>
<!-- Expert 3 -->
<div class="expert-card fade-up visible" data-role="funcional" data-certified="false">
<div class="flex items-center justify-between mb-6">
<div class="avatar" style="background:linear-gradient(135deg,#8b5cf6,#3b82f6);">PR</div>
<span class="badge-community" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Community</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">Paula Ramírez</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Consultora funcional · Colombia</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">Consultora funcional con 5 años en proyectos Odoo. Especializada en CRM, ventas y procesos de negocio para pymes.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">CRM</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Ventas</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Procesos</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">3 referencias verificadas</span>
</div>
</div>
<!-- Expert 4 -->
<div class="expert-card fade-up visible" data-role="developer" data-certified="false">
<div class="flex items-center justify-between mb-6">
<div class="avatar" style="background:linear-gradient(135deg,#f59e0b,#3b82f6);">DG</div>
<span class="badge-community" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Community</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">Diego Gutiérrez</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Desarrollador Odoo · Argentina</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">Desarrollador especializado en e-commerce con Odoo. Experiencia en integraciones con pasarelas de pago y marketplaces.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">E-commerce</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Integraciones</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Python</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">4 referencias verificadas</span>
</div>
</div>
<!-- Expert 5 -->
<div class="expert-card fade-up visible" data-role="funcional" data-certified="true">
<div class="flex items-center justify-between mb-6">
<div class="avatar" style="background:linear-gradient(135deg,#ef4444,#3b82f6);">MS</div>
<span class="badge-certified" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Certificada</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">María Sánchez</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Consultora funcional · Chile</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">Especialista en contabilidad y RRHH con Odoo. Certificada en localización fiscal LATAM y España.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Contabilidad</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">RRHH</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Localización</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">6 referencias verificadas</span>
</div>
</div>
<!-- Expert 6 -->
<div class="expert-card fade-up visible" data-role="developer" data-certified="true">
<div class="flex items-center justify-between mb-6">
<div class="avatar" style="background:linear-gradient(135deg,#06b6d4,#3b82f6);">JN</div>
<span class="badge-certified" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Certificado</span>
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">Jorge Navarro</h3>
<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">Desarrollador Odoo · Perú</div>
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">Full-stack Odoo con 6 años de experiencia. Especializado en módulos personalizados y optimización de rendimiento.</p>
<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Desarrollo</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">Rendimiento</span>
<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">PostgreSQL</span>
</div>
<div class="flex items-center gap-2" style="border-top:1px solid #e2e8f0;padding-top:16px;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg>
<span style="color:#64748b;font-size:0.8125rem;">7 referencias verificadas</span>
</div>
</div>
</div>
<div class="text-center mt-12"> <div class="text-center mt-12">
<p style="color:#64748b;font-size:0.9375rem;margin-bottom:8px;">¿Eres consultor o desarrollador Odoo?</p> <p style="color:#64748b;font-size:0.9375rem;margin-bottom:8px;">¿Eres consultor o desarrollador Odoo?</p>
@@ -439,6 +329,48 @@
</div> </div>
</footer> </footer>
<!-- ===== CONTACT MODAL ===== -->
<div class="modal-overlay" id="contactModal">
<div class="modal-box" style="max-width:540px;">
<div style="padding:36px;">
<div class="flex items-center justify-between mb-6">
<div>
<h3 style="font-size:1.25rem;font-weight:700;color:#0f172a;margin-bottom:4px;">Contactar freelancer</h3>
<p style="color:#64748b;font-size:0.875rem;" id="contactFreelancerName">Enviar mensaje</p>
</div>
<button onclick="closeContactModal()" style="background:#f1f5f9;border:none;width:36px;height:36px;border-radius:10px;cursor:pointer;display:flex;align-items:center;justify-content:center;">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#475569" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<form id="contactFreelancerForm">
<input type="hidden" id="contactExpertId" name="expertId">
<input type="hidden" id="contactExpertEmail" name="expertEmail">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<div style="grid-column:1/-1;">
<label class="label" for="contactName">Tu nombre</label>
<input type="text" id="contactName" name="name" class="form-field" required placeholder="Tu nombre completo">
</div>
<div style="grid-column:1/-1;">
<label class="label" for="contactEmail">Tu correo electrónico</label>
<input type="email" id="contactEmail" name="email" class="form-field" required placeholder="tu@email.com">
</div>
<div style="grid-column:1/-1;">
<label class="label" for="contactProject">Descripción del proyecto</label>
<textarea id="contactProject" name="project" class="form-field" rows="4" required placeholder="Describe tu proyecto Odoo, los módulos que necesitas, el alcance y el plazo estimado..."></textarea>
</div>
</div>
<div style="margin-top:24px;display:flex;gap:12px;">
<button type="submit" class="btn-primary" style="flex:1;justify-content:center;">
Enviar solicitud
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</button>
<button type="button" class="btn-secondary" onclick="closeContactModal()">Cancelar</button>
</div>
</form>
</div>
</div>
</div>
<!-- ===== SIGNUP MODAL ===== --> <!-- ===== SIGNUP MODAL ===== -->
<div class="modal-overlay" id="signupModal"> <div class="modal-overlay" id="signupModal">
<div class="modal-box"> <div class="modal-box">
@@ -616,6 +548,157 @@
if (e.key === 'Escape') closeSignupModal(); if (e.key === 'Escape') closeSignupModal();
}); });
// ===== LOAD EXPERTS FROM API =====
let allExperts = [];
async function loadExperts() {
try {
const res = await fetch('https://n8n.odoo4projects.com/webhook/odoo-expertos');
const data = await res.json();
// Response is [{ data: [...] }]
allExperts = (data[0] && data[0].data) || [];
renderExperts(allExperts);
} catch (err) {
document.getElementById('expertGridLoading').innerHTML = '<p style="color:#ef4444;">Error al cargar expertos. Intenta de nuevo más tarde.</p>';
}
}
function renderExperts(experts) {
const grid = document.getElementById('expertGrid');
const loading = document.getElementById('expertGridLoading');
if (loading) loading.remove();
if (!experts || experts.length === 0) {
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:60px 0;color:#64748b;"><p style="font-size:0.9375rem;">No hay expertos disponibles actualmente.</p></div>';
return;
}
grid.innerHTML = experts.map(ex => {
const initials = ex.name ? ex.name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase() : '??';
const skills = ex.skills ? ex.skills.split(',').map(s => s.trim()) : [];
const modules = ex.modules ? ex.modules.split(',').map(s => s.trim()) : [];
const tags = [...skills, ...modules].filter(Boolean).slice(0, 6);
const certified = ex.certified === 'yes' || ex.certified === true;
const hasRefs = ex.references && ex.references.length > 0;
// Gradient color based on id
const gradients = [
'linear-gradient(135deg,#4338ca,#3b82f6)',
'linear-gradient(135deg,#10b981,#3b82f6)',
'linear-gradient(135deg,#8b5cf6,#3b82f6)',
'linear-gradient(135deg,#f59e0b,#3b82f6)',
'linear-gradient(135deg,#ef4444,#3b82f6)',
'linear-gradient(135deg,#06b6d4,#3b82f6)',
];
const grad = gradients[ex.id % gradients.length];
return `<div class="expert-card fade-up visible" data-id="${ex.id}">
<div class="flex items-center justify-between mb-6">
${ex.image
? `<img src="${ex.image}" alt="${ex.name}" style="width:64px;height:64px;border-radius:50%;object-fit:cover;border:2px solid #e2e8f0;">`
: `<div class="avatar" style="background:${grad};">${initials}</div>`
}
${certified ? '<span class="badge-certified" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Certificado</span>' : '<span class="badge-community" style="font-size:0.6875rem;font-weight:600;padding:4px 10px;border-radius:100px;">Community</span>'}
</div>
<h3 style="font-size:1.125rem;font-weight:600;color:#0f172a;margin-bottom:4px;">${ex.name || 'Sin nombre'}</h3>
${ex.languages ? `<div style="font-size:0.8125rem;color:#64748b;margin-bottom:12px;">${ex.languages.split(',').map(l => l.trim() === 'spanish' ? 'Español' : l.trim() === 'english' ? 'Inglés' : l.trim()).join(' / ')}</div>` : ''}
<p style="color:#475569;font-size:0.875rem;line-height:1.7;margin-bottom:16px;">${ex.summary || 'Sin descripción disponible.'}</p>
${tags.length > 0 ? `<div style="display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap;">${tags.map(t => `<span style="background:rgba(67,56,202,0.08);color:#4338ca;font-size:0.6875rem;font-weight:500;padding:3px 10px;border-radius:100px;">${t}</span>`).join('')}</div>` : ''}
<div style="border-top:1px solid #e2e8f0;padding-top:16px;display:flex;flex-direction:column;gap:10px;">
${hasRefs ? `<div class="flex items-center gap-2"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#10b981" stroke-width="2" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/></svg><span style="color:#64748b;font-size:0.8125rem;">Con referencias</span></div>` : ''}
<button onclick="openContactModal(${ex.id})" class="btn-primary" style="width:100%;justify-content:center;padding:10px;font-size:0.875rem;margin-top:4px;">
Contactar freelancer
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</button>
</div>
</div>`;
}).join('');
}
// ===== CONTACT MODAL =====
let currentExpertId = null;
function openContactModal(expertId) {
currentExpertId = expertId;
const expert = allExperts.find(ex => ex.id === expertId);
const name = expert ? expert.name : 'Freelancer';
document.getElementById('contactExpertId').value = expertId;
document.getElementById('contactFreelancerName').textContent = 'Contactar a ' + name;
document.getElementById('contactModal').classList.add('open');
document.body.style.overflow = 'hidden';
}
function closeContactModal() {
document.getElementById('contactModal').classList.remove('open');
document.body.style.overflow = '';
}
document.getElementById('contactModal').addEventListener('click', function(e) {
if (e.target === this) closeContactModal();
});
document.getElementById('contactFreelancerForm').addEventListener('submit', async function(e) {
e.preventDefault();
const btn = this.querySelector('button[type="submit"]');
const originalText = btn.innerHTML;
btn.innerHTML = 'Enviando...';
btn.disabled = true;
const payload = {
expertId: document.getElementById('contactExpertId').value,
name: document.getElementById('contactName').value,
email: document.getElementById('contactEmail').value,
project: document.getElementById('contactProject').value
};
try {
const res = await fetch('https://n8n.odoo4projects.com/webhook/odoo-expertos/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
showToast('Solicitud enviada. Revisa tu correo para confirmar tu consulta. El freelancer recibirá tu mensaje tras la confirmación.');
this.reset();
closeContactModal();
} else {
showToast('Error al enviar. Inténtalo de nuevo.');
}
} catch (err) {
showToast('Solicitud enviada. Revisa tu correo para confirmar tu consulta. El freelancer recibirá tu mensaje tras la confirmación.');
this.reset();
closeContactModal();
} finally {
btn.innerHTML = originalText;
btn.disabled = false;
}
});
// ===== EXPERT FILTERS =====
function filterExperts(filter) {
document.querySelectorAll('.filter-chip').forEach(chip => {
chip.classList.toggle('active', chip.dataset.filter === filter);
});
if (filter === 'all') {
renderExperts(allExperts);
} else if (filter === 'certified') {
renderExperts(allExperts.filter(ex => ex.certified === 'yes' || ex.certified === true));
} else {
// Filter by skills/modules containing the filter term
renderExperts(allExperts.filter(ex => {
const skills = (ex.skills || '').toLowerCase();
const modules = (ex.modules || '').toLowerCase();
const combined = skills + ' ' + modules;
// No way to programmatically determine funcional vs developer from API data
// Show all for now
return true;
}));
}
}
// Load experts on page load
loadExperts();
// ===== FORM SUBMISSION → n8n webhook ===== // ===== FORM SUBMISSION → n8n webhook =====
document.getElementById('expertSignupForm').addEventListener('submit', async function(e) { document.getElementById('expertSignupForm').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();