Ficha privada de invitados y condiciones de participación

- /invitados.html (noindex) con enlace personal ?k=<código>; las respuestas
  llegan por correo desde contacto@ en lugar de Google Forms.
- server.py: /api/invitados/check y /api/invitados (códigos en INVITE_TOKENS).
- terminos.html: sección 5 con las condiciones de participación.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSyR5Zw5eGj6oPTRVjvP1b
This commit is contained in:
Alex
2026-10-20 23:50:53 +02:00
co-authored by Claude Sonnet 5.5
parent f0682d7de8
commit da8cc01fad
10 changed files with 589 additions and 7 deletions
+52
View File
@@ -0,0 +1,52 @@
(function () {
var $ = function (id) { return document.getElementById(id); };
var k = (new URLSearchParams(location.search).get('k') || '').trim();
var f = $('g'), st = $('st'), hola = $('hola'), malo = $('malo'), intro = $('intro');
var t0 = Date.now(), busy = false, KEY = 'aqnsn-ficha-' + k.slice(0, 10);
function say(m, ok) { st.textContent = m; st.className = 'status ' + (ok ? 'ok' : 'err'); }
function store(op, v) { try { return op === 'get' ? localStorage.getItem(KEY) : op === 'set' ? localStorage.setItem(KEY, v) : localStorage.removeItem(KEY); } catch (e) { return null; } }
if (!k) { malo.hidden = false; return; }
fetch('/api/invitados/check?k=' + encodeURIComponent(k), { cache: 'no-store' })
.then(function (r) { return r.json(); })
.then(function (j) {
if (!j.ok) { malo.hidden = false; return; }
hola.textContent = 'Hola, ' + j.nombre + '. ¡Gracias por querer venir al programa!';
hola.hidden = false; intro.hidden = false; f.hidden = false;
try { var d = JSON.parse(store('get') || '{}'); Object.keys(d).forEach(function (n) { if (f.elements[n] && f.elements[n].type !== 'checkbox') f.elements[n].value = d[n]; }); } catch (e) {}
})
.catch(function () { malo.hidden = false; });
var timer;
f.addEventListener('input', function () {
clearTimeout(timer);
timer = setTimeout(function () {
var d = {};
Array.prototype.forEach.call(f.elements, function (el) { if (el.name && el.type !== 'checkbox' && el.name !== 'web' && el.value) d[el.name] = el.value; });
store('set', JSON.stringify(d));
}, 600);
});
f.addEventListener('submit', function (e) {
e.preventDefault();
if (busy) return;
var resp = {}, falta = null;
Array.prototype.forEach.call(f.elements, function (el) {
if (/^q\d+$/.test(el.name)) { resp[el.name] = el.value.trim(); if (el.required && !resp[el.name] && !falta) falta = el; }
});
if (falta) { falta.focus(); say('Faltan respuestas obligatorias (las marcadas con *).', false); return; }
if (!f.consent.checked || !f.privacy.checked) { say('Marca las dos casillas finales para poder enviar la ficha.', false); return; }
var email = (f.email.value || '').trim();
if (email && !/^[^@\s]+@[^@\s]+\.[^@\s]{2,}$/.test(email)) { say('El correo no parece válido.', false); return; }
busy = true; say('Enviando…', true);
fetch('/api/invitados', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ k: k, email: email, resp: resp, consent: true, web: f.web.value || '', t: Date.now() - t0 }) })
.then(function (r) { return r.json().catch(function () { return {}; }).then(function (j) { return { ok: r.ok && j.ok, j: j }; }); })
.then(function (x) {
if (x.ok) { store('del'); f.reset(); Array.prototype.forEach.call(f.querySelectorAll('fieldset,label,button,.note'), function (n) { n.hidden = true; }); say(f.getAttribute('data-ok'), true); }
else say((x.j && x.j.error) || 'No se pudo enviar. Escríbenos a andaquenosabesnada@gmail.com.', false);
})
.catch(function () { say('No se pudo enviar. Escríbenos a andaquenosabesnada@gmail.com.', false); })
.then(function () { busy = false; });
});
})();