{"id":18271,"date":"2026-07-02T08:58:24","date_gmt":"2026-07-02T08:58:24","guid":{"rendered":"https:\/\/www.turitop.com\/"},"modified":"2026-07-03T13:53:45","modified_gmt":"2026-07-03T13:53:45","slug":"enoastroturismo-quiz","status":"publish","type":"page","link":"https:\/\/www.turitop.com\/en\/enoastroturismo-quiz\/","title":{"rendered":"EnoAstroturismo Quiz"},"content":{"rendered":"\n<!doctype html>\n<html lang=\"es\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no, viewport-fit=cover\">\n<title>Enoastroturismo de Canarias \u00b7 Diagn\u00f3stico Digital<\/title>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Oswald:wght@500;600;700&#038;family=Playfair+Display:wght@500;600&#038;family=Inter:wght@400;500;600;700&#038;display=swap\" rel=\"stylesheet\">\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/Chart.js\/4.4.1\/chart.umd.min.js\"><\/script>\n<style>\n  :root{\n    --navy-950:#0d1226;\n    --navy-900:#121a38;\n    --navy-800:#1b2951;\n    --navy-700:#26356b;\n    --gold:#c9a24c;\n    --gold-light:#e9d19c;\n    --cream:#f6f0e2;\n    --wine:#7a2436;\n    --star-dim:#3a4568;\n    --ok:#8fae6f;\n    --bad:#b5636b;\n  }\n  *{box-sizing:border-box;}\n  html,body{margin:0;padding:0;}\n  body{\n    font-family:'Inter',sans-serif;\n    -webkit-font-smoothing:antialiased;\n    -moz-osx-font-smoothing:grayscale;\n    text-rendering:optimizeLegibility;\n    background:radial-gradient(ellipse at 50% -10%, var(--navy-700) 0%, var(--navy-900) 45%, var(--navy-950) 100%);\n    color:var(--cream);\n    min-height:100vh;\n    display:flex;\n    justify-content:center;\n    padding:0;\n    overflow-x:hidden;\n  }\n  #app{\n    width:100%;\n    max-width:480px;\n    min-height:100vh;\n    position:relative;\n    display:flex;\n    flex-direction:column;\n    padding:0 0 calc(40px + env(safe-area-inset-bottom)) 0;\n  }\n  .starfield{\n    position:fixed;\n    inset:0;\n    z-index:0;\n    pointer-events:none;\n    opacity:.55;\n  }\n  .screen{\n    position:relative;\n    z-index:1;\n    padding:28px 22px 10px 22px;\n    animation:fadeIn .5s ease;\n    flex:1;\n    display:flex;\n    flex-direction:column;\n  }\n  @keyframes fadeIn{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:translateY(0);}}\n\n  .eyebrow{\n    font-family:'Inter',sans-serif;\n    font-size:clamp(12.1px, 3.6vw, 13.8px);\n    letter-spacing:.16em;\n    text-transform:uppercase;\n    color:var(--gold-light);\n    font-weight:600;\n    margin-bottom:6px;\n    overflow-wrap:break-word;\n  }\n  h1.display{\n    font-family:'Oswald',sans-serif;\n    font-weight:700;\n    text-transform:uppercase;\n    letter-spacing:.02em;\n    font-size:clamp(27.6px, 9vw, 35.6px);\n    line-height:1.12;\n    text-wrap:balance;\n    margin:0 0 6px 0;\n    color:var(--cream);\n  }\n  .subtitle{\n    font-family:'Playfair Display',serif;\n    font-style:normal;\n    font-weight:500;\n    color:var(--gold-light);\n    font-size:clamp(17.2px, 5.2vw, 19.5px);\n    margin:0 0 20px 0;\n    line-height:1.45;\n    text-wrap:pretty;\n  }\n  .poster-wrap{\n    position:relative;\n    width:100%;\n    border-radius:14px;\n    overflow:hidden;\n    border:1px solid rgba(201,162,76,.35);\n    box-shadow:0 20px 50px -20px rgba(0,0,0,.6);\n    margin-bottom:22px;\n  }\n  .poster-wrap img{width:100%;display:block;}\n  .scroll-hint{\n    position:absolute;\n    left:50%;\n    bottom:16px;\n    transform:translateX(-50%);\n    display:flex;\n    flex-direction:column;\n    align-items:center;\n    animation:hintFloat 1.8s ease-in-out infinite;\n    filter:drop-shadow(0 2px 6px rgba(0,0,0,.6));\n    z-index:2;\n  }\n  @keyframes hintFloat{\n    0%,100%{transform:translateX(-50%) translateY(0);}\n    50%{transform:translateX(-50%) translateY(8px);}\n  }\n\n  .card{\n    background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));\n    border:1px solid rgba(201,162,76,.22);\n    border-radius:16px;\n    padding:20px;\n    margin-bottom:16px;\n  }\n\n  .btn{\n    display:inline-flex;\n    align-items:center;\n    justify-content:center;\n    gap:8px;\n    width:100%;\n    padding:15px 20px;\n    border-radius:12px;\n    border:none;\n    font-family:'Inter',sans-serif;\n    font-weight:700;\n    font-size:clamp(16.1px, 4.8vw, 17.8px);\n    letter-spacing:.01em;\n    cursor:pointer;\n    transition:transform .12s ease, filter .12s ease;\n  }\n  .btn:active{transform:scale(.98);}\n  .btn-primary{\n    background:linear-gradient(135deg, var(--gold), #a97f2f);\n    color:var(--navy-950);\n  }\n  .btn-primary:hover{filter:brightness(1.08);}\n  .btn-ghost{\n    background:transparent;\n    color:var(--gold-light);\n    border:1px solid rgba(201,162,76,.4);\n  }\n  .btn-ghost:hover{background:rgba(201,162,76,.08);}\n  .btn:disabled{opacity:.4;cursor:not-allowed;}\n\n  label{\n    display:block;\n    font-size:clamp(12.6px, 3.8vw, 13.8px);\n    font-weight:600;\n    letter-spacing:.06em;\n    text-transform:uppercase;\n    color:var(--gold-light);\n    margin:14px 0 6px 0;\n  }\n  label:first-of-type{margin-top:0;}\n  input[type=text],input[type=email],input[type=tel],input[type=password]{\n    width:100%;\n    padding:13px 14px;\n    border-radius:10px;\n    border:1px solid rgba(255,255,255,.18);\n    background:rgba(255,255,255,.06);\n    color:var(--cream);\n    font-family:'Inter',sans-serif;\n    font-size:clamp(18.4px, 5.1vw, 19.5px);\n    outline:none;\n  }\n  input::placeholder{color:rgba(246,240,226,.35);}\n  input:focus{border-color:var(--gold);background:rgba(255,255,255,.09);}\n  input.invalid{border-color:var(--bad)!important;background:rgba(181,99,107,.08)!important;}\n  .hint{font-size:clamp(13.2px, 3.9vw, 14.4px);color:rgba(246,240,226,.65);margin-top:4px;}\n  .req{color:var(--wine);}\n\n  \/* Quiz *\/\n  .progress-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;}\n  .progress-label{font-size:clamp(12.6px, 3.8vw, 13.8px);color:rgba(246,240,226,.75);letter-spacing:.05em;text-transform:uppercase;}\n  .block-tag{\n    display:inline-block;\n    font-size:clamp(11.5px, 3.4vw, 12.6px);\n    font-weight:700;\n    letter-spacing:.08em;\n    text-transform:uppercase;\n    padding:5px 10px;\n    border-radius:999px;\n    margin-bottom:12px;\n  }\n  .block-A{background:rgba(122,36,54,.25);color:#e4a1ac;border:1px solid rgba(122,36,54,.5);}\n  .block-B{background:rgba(201,162,76,.18);color:var(--gold-light);border:1px solid rgba(201,162,76,.4);}\n  .block-C{background:rgba(143,174,111,.18);color:#c9dcb7;border:1px solid rgba(143,174,111,.4);}\n\n  .constellation-wrap{margin:6px 0 20px 0;}\n\n  .question-text{\n    font-family:'Inter',sans-serif;\n    font-style:normal;\n    font-weight:600;\n    font-size:clamp(20.1px, 6.3vw, 24.1px);\n    line-height:1.55;\n    letter-spacing:.005em;\n    margin:0 0 26px 0;\n    text-wrap:pretty;\n    color:var(--cream);\n  }\n  .answer-row{display:flex;gap:12px;}\n  .answer-btn{\n    flex:1;\n    padding:18px 10px;\n    border-radius:14px;\n    font-family:'Oswald',sans-serif;\n    font-weight:600;\n    font-size:clamp(18.4px, 5.5vw, 20.7px);\n    letter-spacing:.06em;\n    text-transform:uppercase;\n    border:1px solid rgba(255,255,255,.18);\n    background:rgba(255,255,255,.05);\n    color:var(--cream);\n    cursor:pointer;\n    transition:all .15s ease;\n  }\n  .answer-btn.yes:active{border-color:var(--ok);background:rgba(143,174,111,.15);}\n  .answer-btn.no:active{border-color:var(--bad);background:rgba(181,99,107,.15);}\n  @media (hover:hover) and (pointer:fine){\n    .answer-btn.yes:hover{border-color:var(--ok);background:rgba(143,174,111,.15);}\n    .answer-btn.no:hover{border-color:var(--bad);background:rgba(181,99,107,.15);}\n  }\n  button, .answer-btn, .btn{-webkit-tap-highlight-color:transparent;}\n  .answer-btn:focus, .btn:focus{outline:none;}\n  .answer-btn:disabled{background:rgba(255,255,255,.05)!important;border-color:rgba(255,255,255,.18)!important;}\n\n  .feedback{\n    display:flex;\n    align-items:center;\n    gap:10px;\n    padding:14px 16px;\n    border-radius:12px;\n    font-size:clamp(15.5px, 4.6vw, 17.2px);\n    line-height:1.5;\n    font-weight:600;\n    margin-top:18px;\n    animation:fadeIn .3s ease;\n  }\n  .feedback.ok{background:rgba(143,174,111,.15);color:var(--ok);border:1px solid rgba(143,174,111,.4);}\n  .feedback.bad{background:rgba(181,99,107,.15);color:var(--bad);border:1px solid rgba(181,99,107,.4);}\n\n  \/* Result *\/\n  .score-hero{text-align:center;margin:10px 0 18px 0;}\n  .score-num{\n    font-family:'Oswald',sans-serif;\n    font-weight:700;\n    font-size:clamp(57.5px, 18.4vw, 73.6px);\n    color:var(--gold);\n    line-height:1;\n  }\n  .score-den{font-size:clamp(18.4px, 6vw, 23px);color:rgba(246,240,226,.55);}\n  .band-name{\n    font-family:'Playfair Display',serif;\n    font-style:normal;\n    font-size:clamp(21.8px, 6.7vw, 25.3px);\n    color:var(--gold-light);\n    margin-top:8px;\n  }\n  .band-desc{font-size:clamp(16.1px, 4.8vw, 17.8px);line-height:1.65;color:rgba(246,240,226,.88);margin-top:10px;text-wrap:pretty;}\n\n  .gift-wrap{\n    position:relative;\n    display:block;\n    width:100%;\n    margin-top:6px;\n    padding:14px 10px;\n  }\n  .btn-gift{\n    background:linear-gradient(135deg, var(--gold), #a97f2f);\n    color:var(--navy-950);\n    box-shadow:0 0 0 rgba(201,162,76,.5);\n    animation:giftGlow 2.2s ease-in-out infinite;\n  }\n  @keyframes giftGlow{\n    0%,100%{box-shadow:0 0 0px rgba(201,162,76,.0);}\n    50%{box-shadow:0 0 22px rgba(201,162,76,.55);}\n  }\n  .gift-wrap .spark{\n    position:absolute;\n    color:var(--gold-light);\n    font-size:15px;\n    pointer-events:none;\n    animation:twinkle 1.6s ease-in-out infinite;\n  }\n  @keyframes twinkle{\n    0%,100%{opacity:.15;transform:scale(.7);}\n    50%{opacity:1;transform:scale(1.2);}\n  }\n  .gift-note{\n    font-size:clamp(14.9px, 4.5vw, 16.1px);\n    text-align:center;\n    color:rgba(246,240,226,.85);\n    margin:4px 0 4px 0;\n    line-height:1.6;\n  }\n  .constellation-loader{\n    display:flex;\n    flex-direction:column;\n    align-items:center;\n    padding:18px 0 6px 0;\n  }\n  .constellation-loader svg circle{animation:twinkle 1.2s ease-in-out infinite;}\n  .constellation-loader svg line{\n    stroke-dasharray:60;\n    stroke-dashoffset:60;\n    animation:drawLine 2.4s ease-in-out infinite;\n  }\n  @keyframes drawLine{\n    0%{stroke-dashoffset:60;}\n    50%{stroke-dashoffset:0;}\n    100%{stroke-dashoffset:-60;}\n  }\n  .loader-text{\n    font-size:clamp(14.9px, 4.5vw, 16.1px);\n    color:var(--gold-light);\n    margin-top:12px;\n    letter-spacing:.03em;\n  }\n\n  .footer-brand{\n    text-align:center;\n    font-size:clamp(11.5px, 3.4vw, 12.6px);\n    letter-spacing:.1em;\n    text-transform:uppercase;\n    color:rgba(246,240,226,.55);\n    padding:18px 22px 4px 22px;\n  }\n  .admin-link{\n    text-align:center;\n    font-size:11px;\n    color:rgba(246,240,226,.3);\n    padding:2px 0 14px 0;\n    cursor:pointer;\n    text-decoration:underline;\n  }\n\n  \/* Pantalla de presentaci\u00f3n de bloques *\/\n  .intro-block{\n    display:flex;\n    gap:14px;\n    align-items:flex-start;\n    background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));\n    border:1px solid rgba(201,162,76,.22);\n    border-radius:16px;\n    padding:18px;\n    margin-bottom:14px;\n  }\n  .intro-block .letter{\n    font-family:'Oswald',sans-serif;\n    font-weight:700;\n    font-size:clamp(21px, 6vw, 25px);\n    width:46px;\n    height:46px;\n    flex-shrink:0;\n    display:flex;\n    align-items:center;\n    justify-content:center;\n    border-radius:12px;\n  }\n  .intro-block.iA .letter{background:rgba(122,36,54,.3);color:#e4a1ac;border:1px solid rgba(122,36,54,.6);}\n  .intro-block.iB .letter{background:rgba(201,162,76,.2);color:var(--gold-light);border:1px solid rgba(201,162,76,.5);}\n  .intro-block.iC .letter{background:rgba(143,174,111,.2);color:#c9dcb7;border:1px solid rgba(143,174,111,.5);}\n  .intro-block h3{\n    font-family:'Oswald',sans-serif;\n    font-weight:600;\n    text-transform:uppercase;\n    letter-spacing:.03em;\n    font-size:clamp(15px, 4.4vw, 17px);\n    margin:2px 0 4px 0;\n    color:var(--cream);\n  }\n  .intro-block .q-count{\n    font-size:clamp(10.5px, 3.1vw, 11.5px);\n    font-weight:700;\n    letter-spacing:.08em;\n    text-transform:uppercase;\n    color:rgba(246,240,226,.55);\n  }\n  .intro-block p{\n    font-size:clamp(13px, 3.9vw, 14.5px);\n    line-height:1.55;\n    color:rgba(246,240,226,.8);\n    margin:6px 0 0 0;\n  }\n  @media (min-width:768px){\n    .intro-block{padding:22px;gap:18px;}\n    .intro-block .letter{width:54px;height:54px;font-size:28px;}\n    .intro-block h3{font-size:19px;}\n    .intro-block p{font-size:16px;}\n    .intro-block .q-count{font-size:12.5px;}\n  }\n\n  \/* RGPD *\/\n  .rgpd-check{\n    display:flex;\n    align-items:flex-start;\n    gap:10px;\n    margin-top:16px;\n    cursor:pointer;\n  }\n  .rgpd-check input[type=checkbox]{\n    width:20px;\n    height:20px;\n    flex-shrink:0;\n    margin:1px 0 0 0;\n    accent-color:var(--gold);\n    cursor:pointer;\n  }\n  .rgpd-check span{\n    font-size:clamp(12.5px, 3.7vw, 13.5px);\n    line-height:1.5;\n    color:rgba(246,240,226,.85);\n    text-transform:none;\n    letter-spacing:0;\n    font-weight:500;\n  }\n  .rgpd-check.invalid span{color:var(--bad);}\n  .legal-text{\n    font-size:clamp(10.5px, 3.1vw, 11.5px);\n    line-height:1.55;\n    color:rgba(246,240,226,.55);\n    margin:10px 0 0 0;\n  }\n  .legal-text a{\n    color:var(--gold-light);\n    text-decoration:underline;\n  }\n  @media (min-width:768px){\n    .rgpd-check span{font-size:14.5px;}\n    .legal-text{font-size:12.5px;}\n  }\n\n  .config-warning{\n    font-size:clamp(13.8px, 4.3vw, 14.9px);\n    background:rgba(181,99,107,.15);\n    border:1px solid rgba(181,99,107,.5);\n    color:var(--bad);\n    border-radius:12px;\n    padding:12px 14px;\n    font-size:13px;\n    font-weight:600;\n    margin-bottom:16px;\n  }\n\n  \/* Admin *\/\n  .admin-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;margin-bottom:18px;}\n  .admin-header button{flex-shrink:0;}\n  .stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:18px;}\n  .stat-box{\n    background:rgba(255,255,255,.05);\n    border:1px solid rgba(201,162,76,.25);\n    border-radius:12px;\n    padding:14px;\n  }\n  .stat-num{font-family:'Oswald',sans-serif;font-weight:700;font-size:clamp(24.1px, 7.5vw, 29.9px);color:var(--gold);}\n  .stat-label{font-size:12px;color:rgba(246,240,226,.75);text-transform:uppercase;letter-spacing:.05em;margin-top:2px;}\n  canvas{max-width:100%;}\n  .chart-box{position:relative;width:100%;height:200px;}\n  .chart-box.tall{height:260px;}\n  .chart-title{font-size:clamp(13.8px, 4.1vw, 14.9px);font-weight:700;color:var(--gold-light);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px;}\n  .empty-note{font-size:clamp(14.9px, 4.5vw, 16.1px);color:rgba(246,240,226,.65);text-align:center;padding:30px 10px;}\n  .error-msg{color:var(--bad);font-size:14px;margin-top:10px;}\n  .table-mini{width:100%;font-size:13px;border-collapse:collapse;margin-top:8px;}\n  .table-mini th,.table-mini td{text-align:left;padding:6px 4px;border-bottom:1px solid rgba(255,255,255,.08);}\n  .table-mini th{color:rgba(246,240,226,.55);font-weight:600;text-transform:uppercase;font-size:10px;letter-spacing:.04em;}\n\n  @media (max-width:360px){\n    .screen{padding:22px 16px 10px 16px;}\n    .answer-btn{padding:16px 8px;}\n  }\n\n  \/* ---------- ESCRITORIO \/ PRESENTACI\u00d3N ---------- *\/\n  @media (min-width:768px){\n    body{\n      align-items:center;\n      padding:48px 24px;\n    }\n    #app{\n      max-width:640px;\n      min-height:auto;\n      border:1px solid rgba(201,162,76,.28);\n      border-radius:26px;\n      background:linear-gradient(180deg, rgba(255,255,255,.035), rgba(0,0,0,.18));\n      box-shadow:0 40px 90px -30px rgba(0,0,0,.75), 0 0 70px -25px rgba(201,162,76,.18);\n      padding:10px 0 44px 0;\n      backdrop-filter:blur(2px);\n    }\n    .screen{padding:44px 52px 18px 52px;}\n    h1.display{font-size:42px;}\n    .subtitle{font-size:20px;}\n    .eyebrow{font-size:14px;}\n    .question-text{font-size:27px;line-height:1.5;}\n    .answer-btn{font-size:21px;padding:24px 10px;}\n    .btn{font-size:17px;padding:17px 22px;}\n    .block-tag{font-size:12.5px;}\n    .progress-label{font-size:13.5px;}\n    .feedback{font-size:16.5px;}\n    .score-num{font-size:88px;}\n    .score-den{font-size:26px;}\n    .band-name{font-size:27px;}\n    .band-desc{font-size:17.5px;}\n    .gift-note{font-size:15.5px;}\n    label{font-size:13.5px;}\n    input[type=text],input[type=email],input[type=tel],input[type=password]{font-size:17.5px;padding:15px 16px;}\n    .hint{font-size:14px;}\n    .stat-num{font-size:34px;}\n    .stat-label{font-size:13px;}\n    .chart-title{font-size:15px;}\n    .table-mini{font-size:14px;}\n    .empty-note{font-size:15.5px;}\n    .footer-brand{font-size:12px;}\n    .admin-link{font-size:12.5px;}\n  }\n<\/style>\n<\/head>\n<body>\n<svg class=\"starfield\" id=\"starfield\"><\/svg>\n<div id=\"app\"><\/div>\n\n<script>\n\/* ---------------------------------------------------------\n   CONFIGURACI\u00d3N DEL BACKEND (Google Apps Script)\n   1. Instala quiz-enoastro-apps-script.gs en un Google Sheet\n      (Extensiones \u2192 Apps Script) y despli\u00e9galo como\n      Aplicaci\u00f3n web (Ejecutar como: Yo \u00b7 Acceso: Cualquier usuario).\n   2. Pega aqu\u00ed la URL de la aplicaci\u00f3n web (termina en \/exec).\n--------------------------------------------------------- *\/\nconst APP_VERSION = 'v5-rgpd';\nconsole.log('Quiz Enoastroturismo \u00b7 ' + 'versi\u00f3n ' + APP_VERSION);\n\nconst API_URL = 'https:\/\/script.google.com\/macros\/s\/AKfycbxw8081N9Z9ZxA1a-Ui_MrhjR1RHyh4NhFZjmBKt86SpRpnp5H7BJGwfM5I3o5iEP8l\/exec';\n\n\/* ---------------------------------------------------------\n   DATOS DE LAS PREGUNTAS\n--------------------------------------------------------- *\/\nconst QUESTIONS = [\n  {block:'A', blockLabel:'Que te puedan reservar', text:'\u00bfTienes un bot\u00f3n de reserva online con confirmaci\u00f3n inmediata y pago en tu web, o el cliente depende de enviar un email\/WhatsApp y esperar a que respondas?'},\n  {block:'A', blockLabel:'Que te puedan reservar', text:'\u00bfTus horarios de visita, aforos de catas y stock de botellas especiales est\u00e1n automatizados en un sistema para evitar el overbooking?'},\n  {block:'A', blockLabel:'Que te puedan reservar', text:'\u00bfCobras las experiencias por adelantado (o pides fianza) para evitar que un grupo de 10 personas no se presente (no-show) y te deje la mesa puesta?'},\n  {block:'B', blockLabel:'Que decidan elegirte', text:'\u00bfLa ficha de tu bodega en la web tiene fotos profesionales del vi\u00f1edo, detalla claramente qu\u00e9 vinos se catan, incluye precios y muestra rese\u00f1as reales de clientes?'},\n  {block:'B', blockLabel:'Que decidan elegirte', text:'\u00bfEs f\u00e1cil reservar una cata desde el tel\u00e9fono m\u00f3vil en menos de 3 pasos y sin obligar al usuario a crearse una cuenta con contrase\u00f1a?'},\n  {block:'B', blockLabel:'Que decidan elegirte', text:'\u00bfTu proceso de compra y reserva est\u00e1 adaptado a idiomas clave para el enoturista premium (m\u00ednimo ingl\u00e9s\/alem\u00e1n)?'},\n  {block:'C', blockLabel:'Que repitan, compren y recomienden', text:'\u00bfEnv\u00edas correos autom\u00e1ticos post-visita agradeciendo la asistencia e incluyendo un enlace directo para que compren tus vinos online o dejen una rese\u00f1a?'},\n  {block:'C', blockLabel:'Que repitan, compren y recomienden', text:'\u00bfTienes una pol\u00edtica clara y visible sobre qu\u00e9 ocurre si un cliente cancela a \u00faltima hora o si el clima impide hacer el paseo por el vi\u00f1edo?'},\n  {block:'C', blockLabel:'Que repitan, compren y recomienden', text:'\u00bfMiras las estad\u00edsticas de tu web y reservas cada mes para saber qu\u00e9 d\u00edas se llenan m\u00e1s y de qu\u00e9 pa\u00edses vienen tus mejores clientes?'},\n  {block:'C', blockLabel:'Que repitan, compren y recomienden', text:'\u00bfOfreces alguna experiencia diferencial que nadie pueda copiar (un Relato \u00danico), como combinar tu vino con la observaci\u00f3n de estrellas (Enoastroturismo), o vendes la misma visita guiada que la bodega de al lado?'}\n];\n\n\/* ---------------------------------------------------------\n   CARTEL DE LAS JORNADAS\n   Sube el cartel como \"poster.jpg\" en la misma carpeta que\n   este HTML (o cambia la ruta\/URL aqu\u00ed). Si la imagen no\n   carga, el bloque se oculta autom\u00e1ticamente.\n--------------------------------------------------------- *\/\nconst POSTER_URL = 'poster.jpg';\n\n\/* ---------------------------------------------------------\n   VALIDACI\u00d3N DE TEL\u00c9FONO Y EMAIL\n--------------------------------------------------------- *\/\nconst DISPOSABLE_EMAIL_DOMAINS = [\n  'mailinator.com','tempmail.com','temp-mail.org','guerrillamail.com','10minutemail.com',\n  'yopmail.com','trashmail.com','throwawaymail.com','fakeinbox.com','sharklasers.com',\n  'getnada.com','dispostable.com','maildrop.cc','moakt.com','mintemail.com','discard.email'\n];\nconst FAKE_EMAIL_LOCALS = ['test','prueba','asdf','asd','xxx','xxxx','noexiste','fake','ejemplo','nose','aaaa','1234','qwerty','sinemail','none','ninguno'];\n\nfunction isValidEmail(raw){\n  const e = String(raw||'').trim().toLowerCase();\n  const re = \/^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,}$\/;\n  if(!re.test(e)) return false;\n  const [local, domain] = e.split('@');\n  if(DISPOSABLE_EMAIL_DOMAINS.includes(domain)) return false;\n  if(FAKE_EMAIL_LOCALS.includes(local)) return false;\n  if(\/^(.)\\1+$\/.test(local)) return false; \/\/ aaaa, xxxx, 1111\n  return true;\n}\n\nfunction normalizePhoneDigits(raw){\n  return String(raw||'').replace(\/[\\s\\-\\.\\(\\)]\/g,'');\n}\n\nfunction isValidPhone(raw){\n  const p = normalizePhoneDigits(raw);\n  if(!\/^\\+?[0-9]{9,15}$\/.test(p)) return false;\n  const digits = p.replace('+','');\n  if(\/^(\\d)\\1+$\/.test(digits)) return false; \/\/ 000000000, 111111111...\n  const ascending = '01234567891234567891234'.slice(0, digits.length);\n  const descending = '98765432109876543210987'.slice(0, digits.length);\n  if(digits === ascending || digits === descending) return false;\n  \/\/ M\u00f3vil\/fijo espa\u00f1ol sin prefijo: 9 d\u00edgitos, debe empezar por 6,7,8,9\n  if(digits.length===9 && !\/^[6789]\/.test(digits)) return false;\n  return true;\n}\n\n\nlet state = {\n  screen: 'welcome', \/\/ welcome | register | blocks | quiz | result | adminLogin | adminDashboard\n  form: {nombre:'', empresa:'', telefono:'', email:''},\n  qIndex: 0,\n  answers: [], \/\/ {value:bool}\n  lastFeedback: null,\n  saved: false,\n  adminEmailInput: '',\n  adminError: '',\n  adminCreds: null,\n  dashboardData: null,\n  dashboardLoading: false,\n  dashboardError: ''\n};\n\nconst app = document.getElementById('app');\n\n\/* ---------------------------------------------------------\n   STARFIELD DECORATIVO\n--------------------------------------------------------- *\/\nfunction initStarfield(){\n  const svg = document.getElementById('starfield');\n  const w = window.innerWidth, h = window.innerHeight;\n  svg.setAttribute('viewBox', `0 0 ${w} ${h}`);\n  let stars = '';\n  for(let i=0;i<70;i++){\n    const x = Math.random()*w, y = Math.random()*h*0.6;\n    const r = Math.random()*1.4+0.4;\n    const op = Math.random()*0.6+0.2;\n    stars += `<circle cx=\"${x}\" cy=\"${y}\" r=\"${r}\" fill=\"#e9d19c\" opacity=\"${op}\"\/>`;\n  }\n  svg.innerHTML = stars;\n}\ninitStarfield();\nlet resizeTimer;\nwindow.addEventListener('resize', ()=>{\n  clearTimeout(resizeTimer);\n  resizeTimer = setTimeout(initStarfield, 200);\n});\n\n\/* ---------------------------------------------------------\n   BACKEND HELPERS (Google Apps Script)\n--------------------------------------------------------- *\/\nfunction apiConfigured(){\n  return API_URL && API_URL.indexOf('https:\/\/') === 0;\n}\n\nasync function postToApi(entry){\n  \/\/ Content-Type text\/plain evita el preflight CORS que Apps Script no soporta\n  const res = await fetch(API_URL, {\n    method:'POST',\n    headers:{'Content-Type':'text\/plain;charset=utf-8'},\n    body: JSON.stringify({action:'save', data:entry})\n  });\n  const json = await res.json();\n  if(!(json && json.ok)) throw new Error((json && json.error) || 'Respuesta no v\u00e1lida del servidor');\n  return true;\n}\n\nasync function saveParticipant(entry){\n  if(!apiConfigured()){\n    console.error('API_URL sin configurar: la respuesta se guarda en cola local.');\n    queuePending(entry);\n    return false;\n  }\n  try{\n    await postToApi(entry);\n    return true;\n  }catch(e){\n    console.error('Error guardando participante, se guarda en cola local', e);\n    queuePending(entry);\n    return false;\n  }\n}\n\n\/* Cola local por si falla la conexi\u00f3n durante el taller:\n   los env\u00edos pendientes se reintentan al recargar la p\u00e1gina. *\/\nfunction queuePending(entry){\n  try{\n    const q = JSON.parse(localStorage.getItem('quiz_enoastro_pending')||'[]');\n    q.push(entry);\n    localStorage.setItem('quiz_enoastro_pending', JSON.stringify(q));\n  }catch(e){}\n}\n\nasync function flushPending(){\n  if(!apiConfigured()) return;\n  let q = [];\n  try{ q = JSON.parse(localStorage.getItem('quiz_enoastro_pending')||'[]'); }catch(e){ return; }\n  if(!q.length) return;\n  const remaining = [];\n  for(const entry of q){\n    try{ await postToApi(entry); }\n    catch(e){ remaining.push(entry); }\n  }\n  try{ localStorage.setItem('quiz_enoastro_pending', JSON.stringify(remaining)); }catch(e){}\n}\nflushPending();\n\nasync function fetchParticipants(email, pass){\n  const url = API_URL\n    + '?action=list&email=' + encodeURIComponent(email)\n    + '&pass=' + encodeURIComponent(pass)\n    + '&t=' + Date.now(); \/\/ evita cach\u00e9\n  const res = await fetch(url);\n  return res.json();\n}\n\n\/* ---------------------------------------------------------\n   RENDER PRINCIPAL\n--------------------------------------------------------- *\/\nfunction render(){\n  if(state.screen==='welcome') return renderWelcome();\n  if(state.screen==='register') return renderRegister();\n  if(state.screen==='blocks') return renderBlocks();\n  if(state.screen==='quiz') return renderQuiz();\n  if(state.screen==='result') return renderResult();\n  if(state.screen==='adminLogin') return renderAdminLogin();\n  if(state.screen==='adminDashboard') return renderAdminDashboard();\n}\n\n\/* ---------- WELCOME ---------- *\/\nfunction renderWelcome(){\n  const configWarning = apiConfigured() ? '' :\n    `<div class=\"config-warning\">\u26a0\ufe0f API_URL sin configurar: las respuestas no se guardar\u00e1n. Edita la constante API_URL al inicio del archivo con la URL de tu Google Apps Script.<\/div>`;\n  app.innerHTML = `\n    <div class=\"screen\">\n      ${configWarning}\n      <div class=\"eyebrow\">I Jornadas \u00b7 TuriTop<\/div>\n      <h1 class=\"display\">Enoastroturismo de Canarias<\/h1>\n      <p class=\"subtitle\">Cielo, vino y territorio: descubre en 2 minutos cu\u00e1n preparada est\u00e1 tu bodega para vender online.<\/p>\n      <div class=\"poster-wrap\" id=\"posterWrap\">\n        <img decoding=\"async\" src=\"${POSTER_URL}\" alt=\"Cartel I Jornadas Enoastroturismo de Canarias\"\n             onerror=\"document.getElementById('posterWrap').style.display='none';\"\/>\n        <div class=\"scroll-hint\">\n          <svg width=\"64\" height=\"82\" viewBox=\"0 0 70 90\" fill=\"none\">\n            <defs>\n              <linearGradient id=\"trailGrad\" x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n                <stop offset=\"0\" stop-color=\"#e9d19c\" stop-opacity=\"0\"\/>\n                <stop offset=\"1\" stop-color=\"#e9d19c\" stop-opacity=\"0.9\"\/>\n              <\/linearGradient>\n            <\/defs>\n            <line x1=\"6\" y1=\"4\" x2=\"30\" y2=\"34\" stroke=\"url(#trailGrad)\" stroke-width=\"2.5\" stroke-linecap=\"round\"\/>\n            <circle cx=\"32\" cy=\"37\" r=\"3.2\" fill=\"#e9d19c\"\/>\n            <circle cx=\"32\" cy=\"37\" r=\"7\" fill=\"#e9d19c\" opacity=\"0.25\"\/>\n            <path d=\"M35 46 L35 72 M35 72 L25 62 M35 72 L45 62\" stroke=\"#c9a24c\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n          <\/svg>\n        <\/div>\n      <\/div>\n      <div class=\"card\">\n        <p style=\"margin:0 0 4px 0;font-size:clamp(15.5px, 4.7vw, 17.2px);line-height:1.65;color:rgba(246,240,226,.9);\">\n          Responde <strong>10 preguntas r\u00e1pidas de S\u00ed\/No<\/strong> sobre c\u00f3mo gestiona tu bodega las reservas, la venta y la fidelizaci\u00f3n. Cada acierto suma una estrella a tu constelaci\u00f3n.\n        <\/p>\n      <\/div>\n      <button class=\"btn btn-primary\" id=\"startBtn\">Comenzar cuestionario<\/button>\n      <div class=\"footer-brand\">TuriTop \u00b7 Software de reservas<\/div>\n      <div class=\"admin-link\" id=\"adminLink\">Panel organizador<\/div>\n    <\/div>\n  `;\n  document.getElementById('startBtn').onclick = ()=>{ state.screen='register'; render(); };\n  document.getElementById('adminLink').onclick = ()=>{ state.screen='adminLogin'; render(); };\n}\n\n\/* ---------- REGISTER ---------- *\/\nfunction renderRegister(){\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"eyebrow\">Paso 1 de 2<\/div>\n      <h1 class=\"display\" style=\"font-size:clamp(23px, 7.1vw, 27.6px);\">Antes de empezar<\/h1>\n      <p class=\"subtitle\" style=\"font-size:clamp(15.5px, 4.6vw, 17.2px);\">Solo lo usaremos para identificar tu resultado. No se muestran datos personales en ning\u00fan panel p\u00fablico.<\/p>\n      <div class=\"card\">\n        <label>Nombre y apellidos <span class=\"req\">*<\/span><\/label>\n        <input type=\"text\" id=\"fNombre\" placeholder=\"Ej. Mar\u00eda Rodr\u00edguez P\u00e9rez\" value=\"${escapeAttr(state.form.nombre)}\"\/>\n\n        <label>Nombre de la empresa <span class=\"req\">*<\/span><\/label>\n        <input type=\"text\" id=\"fEmpresa\" placeholder=\"Ej. Bodega La Constelaci\u00f3n\" value=\"${escapeAttr(state.form.empresa)}\"\/>\n\n        <label>Tel\u00e9fono (opcional)<\/label>\n        <input type=\"tel\" id=\"fTelefono\" placeholder=\"+34 6XX XXX XXX\" value=\"${escapeAttr(state.form.telefono)}\"\/>\n\n        <label>Email (opcional)<\/label>\n        <input type=\"email\" id=\"fEmail\" placeholder=\"tunombre@tuempresa.com\" value=\"${escapeAttr(state.form.email)}\"\/>\n\n        <label class=\"rgpd-check\" id=\"rgpdRow\">\n          <input type=\"checkbox\" id=\"fRgpd\"\/>\n          <span>He le\u00eddo y acepto el tratamiento de mis datos seg\u00fan se describe a continuaci\u00f3n. <span class=\"req\">*<\/span><\/span>\n        <\/label>\n        <p class=\"legal-text\">TuriTop tratar\u00e1 tus datos de contacto corporativo para gestionar tu solicitud de reserva y asignar una prueba gratuita, con base en la relaci\u00f3n precontractual, y a trav\u00e9s de distintos medios de contacto incluidas aplicaciones de mensajer\u00eda como WhatsApp. Tambi\u00e9n te mantendremos informado de nuestros servicios y ofertas, con base en nuestro inter\u00e9s leg\u00edtimo, al cual podr\u00e1s oponerte en cualquier momento. Visita nuestra <a href=\"https:\/\/www.turitop.com\/politicas-de-privacidad\/\" target=\"_blank\" rel=\"noopener\">pol\u00edtica de privacidad<\/a> para conocer c\u00f3mo ejercer tus derechos y obtener informaci\u00f3n detallada sobre el tratamiento.<\/p>\n\n        <div class=\"hint\" id=\"formError\" style=\"color:var(--bad);display:none;\"><\/div>\n      <\/div>\n      <button class=\"btn btn-primary\" id=\"nextBtn\">Continuar<\/button>\n    <\/div>\n  `;\n  document.getElementById('nextBtn').onclick = ()=>{\n    const nombre = document.getElementById('fNombre').value.trim();\n    const empresa = document.getElementById('fEmpresa').value.trim();\n    const telefono = document.getElementById('fTelefono').value.trim();\n    const email = document.getElementById('fEmail').value.trim();\n    const rgpd = document.getElementById('fRgpd').checked;\n    const err = document.getElementById('formError');\n    const telInput = document.getElementById('fTelefono');\n    const emailInput = document.getElementById('fEmail');\n    const rgpdRow = document.getElementById('rgpdRow');\n    telInput.classList.remove('invalid');\n    emailInput.classList.remove('invalid');\n    rgpdRow.classList.remove('invalid');\n\n    const problems = [];\n    if(!nombre || !empresa){\n      problems.push('Nombre y empresa son obligatorios.');\n    }\n    if(!rgpd){\n      problems.push('Debes aceptar el tratamiento de datos para continuar.');\n      rgpdRow.classList.add('invalid');\n    }\n    if(telefono && !isValidPhone(telefono)){\n      problems.push('Revisa el tel\u00e9fono: no parece un n\u00famero real (m\u00edn. 9 d\u00edgitos, sin repeticiones ni secuencias).');\n      telInput.classList.add('invalid');\n    }\n    if(email && !isValidEmail(email)){\n      problems.push('Revisa el email: el formato no es v\u00e1lido o parece una direcci\u00f3n de prueba.');\n      emailInput.classList.add('invalid');\n    }\n    if(problems.length){\n      err.style.display='block';\n      err.innerHTML = problems.join('<br>');\n      return;\n    }\n    state.form = {nombre, empresa, telefono, email};\n    state.screen = 'blocks';\n    render();\n  };\n}\n\n\/* ---------- PRESENTACI\u00d3N DE BLOQUES ---------- *\/\nfunction renderBlocks(){\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"eyebrow\">Paso 2 de 2<\/div>\n      <h1 class=\"display\" style=\"font-size:clamp(23px, 7.1vw, 27.6px);\">El recorrido<\/h1>\n      <p class=\"subtitle\">10 preguntas de S\u00ed\/No en 3 bloques: las tres cosas que tu bodega necesita dominar para vender online.<\/p>\n\n      <div class=\"intro-block iA\">\n        <div class=\"letter\">A<\/div>\n        <div>\n          <div class=\"q-count\">Bloque A \u00b7 3 preguntas<\/div>\n          <h3>Que te puedan reservar<\/h3>\n          <p>Reserva online con confirmaci\u00f3n inmediata, disponibilidad automatizada y cobro anticipado. Sin esto, el resto no importa.<\/p>\n        <\/div>\n      <\/div>\n\n      <div class=\"intro-block iB\">\n        <div class=\"letter\">B<\/div>\n        <div>\n          <div class=\"q-count\">Bloque B \u00b7 3 preguntas<\/div>\n          <h3>Que decidan elegirte<\/h3>\n          <p>Tu escaparate digital: fotos, precios y rese\u00f1as, reserva f\u00e1cil desde el m\u00f3vil e idiomas para el enoturista internacional.<\/p>\n        <\/div>\n      <\/div>\n\n      <div class=\"intro-block iC\">\n        <div class=\"letter\">C<\/div>\n        <div>\n          <div class=\"q-count\">Bloque C \u00b7 4 preguntas<\/div>\n          <h3>Que repitan, compren y recomienden<\/h3>\n          <p>Fidelizaci\u00f3n post-visita, pol\u00edticas claras, datos para decidir y una experiencia que nadie pueda copiar.<\/p>\n        <\/div>\n      <\/div>\n\n      <button class=\"btn btn-primary\" id=\"startQuizBtn\" style=\"margin-top:6px;\">Empezar preguntas<\/button>\n    <\/div>\n  `;\n  document.getElementById('startQuizBtn').onclick = ()=>{\n    state.screen = 'quiz';\n    state.qIndex = 0;\n    state.answers = [];\n    state.lastFeedback = null;\n    state.saved = false;\n    render();\n  };\n}\n\n\/* ---------- QUIZ ---------- *\/\nfunction constellationSVG(highlightIndex, answers){\n  \/\/ 10 fixed star positions loosely resembling a constellation arc, drawn progressively\n  const positions = [\n    [20,70],[55,40],[95,55],[130,25],[170,45],\n    [205,70],[235,40],[270,60],[300,35],[335,55]\n  ];\n  let lines = '';\n  for(let i=1;i<positions.length;i++){\n    if(i<=highlightIndex || (i===highlightIndex+1 &#038;&#038; answers[i-1]!==undefined)){\n      const [x1,y1]=positions[i-1], [x2,y2]=positions[i];\n      const done = answers[i-1]!==undefined;\n      lines += `<line x1=\"${x1}\" y1=\"${y1}\" x2=\"${x2}\" y2=\"${y2}\" stroke=\"${done?'#c9a24c':'#3a4568'}\" stroke-width=\"1.4\" opacity=\"${done?0.85:0.3}\"\/>`;\n    }\n  }\n  let stars = '';\n  positions.forEach((p,i)=>{\n    let fill = '#3a4568', r=4, glow='';\n    if(answers[i]===true){ fill='#e9d19c'; r=6; glow=`<circle cx=\"${p[0]}\" cy=\"${p[1]}\" r=\"10\" fill=\"#e9d19c\" opacity=\"0.25\"\/>`; }\n    else if(answers[i]===false){ fill='#6b7794'; r=5; }\n    else if(i===highlightIndex){ fill='#c9a24c'; r=6; glow=`<circle cx=\"${p[0]}\" cy=\"${p[1]}\" r=\"12\" fill=\"#c9a24c\" opacity=\"0.3\"><animate attributeName=\"r\" values=\"10;14;10\" dur=\"1.6s\" repeatCount=\"indefinite\"\/><\/circle>`; }\n    stars += glow + `<circle cx=\"${p[0]}\" cy=\"${p[1]}\" r=\"${r}\" fill=\"${fill}\"\/>`;\n  });\n  return `<svg viewBox=\"0 0 355 100\" width=\"100%\" height=\"70\">${lines}${stars}<\/svg>`;\n}\n\nfunction renderQuiz(){\n  const i = state.qIndex;\n  const q = QUESTIONS[i];\n  const answersMap = state.answers.map(a=>a.value);\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"progress-row\">\n        <span class=\"progress-label\">Pregunta ${i+1} de ${QUESTIONS.length}<\/span>\n        <span class=\"progress-label\">${state.answers.filter(a=>a.value).length} \u2b50<\/span>\n      <\/div>\n      <div class=\"constellation-wrap\">${constellationSVG(i, answersMap)}<\/div>\n      <span class=\"block-tag block-${q.block}\">Bloque ${q.block} \u00b7 ${q.blockLabel}<\/span>\n      <p class=\"question-text\">${q.text}<\/p>\n      <div class=\"answer-row\" id=\"answerRow\">\n        <button class=\"answer-btn yes\" id=\"btnYes\">S\u00ed<\/button>\n        <button class=\"answer-btn no\" id=\"btnNo\">No<\/button>\n      <\/div>\n      <div id=\"feedbackSlot\"><\/div>\n    <\/div>\n  `;\n  document.getElementById('btnYes').onclick = ()=>answerQuestion(true);\n  document.getElementById('btnNo').onclick = ()=>answerQuestion(false);\n}\n\nfunction answerQuestion(value){\n  document.getElementById('btnYes').disabled = true;\n  document.getElementById('btnNo').disabled = true;\n  document.getElementById('btnYes').blur();\n  document.getElementById('btnNo').blur();\n  state.answers[state.qIndex] = {value};\n  const slot = document.getElementById('feedbackSlot');\n  if(value){\n    slot.innerHTML = `<div class=\"feedback ok\">\u2b50 \u00a1Bien! Suma una estrella a tu constelaci\u00f3n.<\/div>`;\n  }else{\n    slot.innerHTML = `<div class=\"feedback bad\">Sin estrella esta vez. Es una oportunidad de mejora.<\/div>`;\n  }\n  setTimeout(()=>{\n    if(state.qIndex < QUESTIONS.length-1){\n      state.qIndex++;\n      renderQuiz();\n    }else{\n      finishQuiz();\n    }\n  }, 900);\n}\n\nasync function finishQuiz(){\n  state.screen = 'result';\n  render();\n  if(!state.saved){\n    state.saved = true;\n    const score = state.answers.filter(a=>a.value).length;\n    const entry = {\n      nombre: state.form.nombre,\n      empresa: state.form.empresa,\n      telefono: state.form.telefono,\n      email: state.form.email,\n      respuestas: state.answers.map(a=>a.value),\n      score,\n      rgpd: 'S\u00ed',\n      ts: Date.now()\n    };\n    await saveParticipant(entry);\n  }\n}\n\n\/* ---------- RESULT ---------- *\/\nfunction scoreBand(score){\n  if(score<=3) return {name:'Modo Manual', desc:'Tu bodega depende todav\u00eda de procesos manuales. Digitalizar la reserva y el cobro es el paso con mayor impacto inmediato.'};\n  if(score<=6) return {name:'En \u00d3rbita', desc:'Ya tienes piezas clave en marcha. Cerrando los huecos automatizas overbooking, idiomas y fidelizaci\u00f3n, tu despegue ser\u00e1 mucho m\u00e1s r\u00e1pido.'};\n  if(score<=9) return {name:'Casi Estelar', desc:'Muy buen nivel de digitalizaci\u00f3n. Afinando los \u00faltimos detalles puedes convertirte en referencia del sector.'};\n  return {name:'Constelaci\u00f3n Completa', desc:'Enhorabuena, tu bodega tiene una gesti\u00f3n digital ejemplar y una propuesta diferencial clara.'};\n}\n\nfunction renderResult(){\n  const score = state.answers.filter(a=>a.value).length;\n  const band = scoreBand(score);\n  const answersMap = state.answers.map(a=>a.value);\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"eyebrow\" style=\"text-align:center;\">${escapeHtml(state.form.nombre)} \u00b7 ${escapeHtml(state.form.empresa)}<\/div>\n      <div class=\"score-hero\">\n        <div class=\"score-num\">${score}<span class=\"score-den\">\/10<\/span><\/div>\n        <div class=\"band-name\">${band.name}<\/div>\n      <\/div>\n      <div class=\"constellation-wrap\">${constellationSVG(10, answersMap)}<\/div>\n      <div class=\"card\">\n        <p class=\"band-desc\">${band.desc}<\/p>\n      <\/div>\n      <p style=\"font-size:clamp(14.9px, 4.5vw, 16.1px);text-align:center;line-height:1.6;color:rgba(246,240,226,.8);\">Gracias por participar en el taller de TuriTop. \u00a1Nos vemos bajo las estrellas! \ud83c\udf77\u2728<\/p>\n      <p class=\"gift-note\">Como agradecimiento por tu participaci\u00f3n, tenemos un peque\u00f1o regalo que te ayudar\u00e1 en el d\u00eda a d\u00eda de tu negocio.<\/p>\n      <div id=\"giftArea\" class=\"gift-wrap\">\n        <span class=\"spark\" style=\"top:-2px; left:8%; animation-delay:0s;\">\u2726<\/span>\n        <span class=\"spark\" style=\"top:-6px; right:10%; animation-delay:.3s;\">\u2727<\/span>\n        <span class=\"spark\" style=\"bottom:-4px; left:14%; animation-delay:.6s;\">\u2727<\/span>\n        <span class=\"spark\" style=\"bottom:-2px; right:6%; animation-delay:.9s;\">\u2726<\/span>\n        <span class=\"spark\" style=\"top:40%; left:-4px; animation-delay:.45s;\">\u2726<\/span>\n        <span class=\"spark\" style=\"top:35%; right:-4px; animation-delay:.75s;\">\u2727<\/span>\n        <button class=\"btn btn-gift\" id=\"giftBtn\">\u2728 Acepto mi regalo \u2728<\/button>\n      <\/div>\n    <\/div>\n  `;\n  document.getElementById('giftBtn').onclick = ()=>{\n    document.getElementById('giftArea').innerHTML = constellationLoaderHTML();\n    setTimeout(()=>{\n      window.location.href = 'https:\/\/www.turitop.com\/jornadas-de-enoastroturismo-de-canarias\/';\n    }, 6000);\n  };\n}\n\nfunction constellationLoaderHTML(){\n  return `\n    <div class=\"constellation-loader\">\n      <svg width=\"140\" height=\"90\" viewBox=\"0 0 140 90\">\n        <line x1=\"15\" y1=\"60\" x2=\"45\" y2=\"20\" stroke=\"#c9a24c\" stroke-width=\"1.6\" opacity=\"0.7\"\/>\n        <line x1=\"45\" y1=\"20\" x2=\"80\" y2=\"45\" stroke=\"#c9a24c\" stroke-width=\"1.6\" opacity=\"0.7\" style=\"animation-delay:.3s;\"\/>\n        <line x1=\"80\" y1=\"45\" x2=\"110\" y2=\"15\" stroke=\"#c9a24c\" stroke-width=\"1.6\" opacity=\"0.7\" style=\"animation-delay:.6s;\"\/>\n        <line x1=\"80\" y1=\"45\" x2=\"125\" y2=\"65\" stroke=\"#c9a24c\" stroke-width=\"1.6\" opacity=\"0.7\" style=\"animation-delay:.9s;\"\/>\n        <circle cx=\"15\" cy=\"60\" r=\"4\" fill=\"#e9d19c\" style=\"animation-delay:0s;\"\/>\n        <circle cx=\"45\" cy=\"20\" r=\"4\" fill=\"#e9d19c\" style=\"animation-delay:.2s;\"\/>\n        <circle cx=\"80\" cy=\"45\" r=\"5\" fill=\"#e9d19c\" style=\"animation-delay:.4s;\"\/>\n        <circle cx=\"110\" cy=\"15\" r=\"4\" fill=\"#e9d19c\" style=\"animation-delay:.6s;\"\/>\n        <circle cx=\"125\" cy=\"65\" r=\"4\" fill=\"#e9d19c\" style=\"animation-delay:.8s;\"\/>\n      <\/svg>\n      <div class=\"loader-text\">Preparando tu regalo bajo las estrellas\u2026<\/div>\n    <\/div>\n  `;\n}\n\n\/* ---------- ADMIN LOGIN ---------- *\/\nfunction renderAdminLogin(){\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"eyebrow\">Acceso restringido<\/div>\n      <h1 class=\"display\" style=\"font-size:clamp(23px, 7.1vw, 27.6px);\">Panel del organizador<\/h1>\n      <p class=\"subtitle\" style=\"font-size:clamp(14.9px, 4.5vw, 16.1px);\">Introduce el email autorizado para ver el dashboard agregado y anonimizado.<\/p>\n      <div class=\"card\">\n        <label>Email<\/label>\n        <input type=\"email\" id=\"adminEmail\" placeholder=\"tunombre@turitop.com\" value=\"${escapeAttr(state.adminEmailInput)}\"\/>\n        <label>Contrase\u00f1a<\/label>\n        <input type=\"password\" id=\"adminPassword\" placeholder=\"Contrase\u00f1a\" value=\"\"\/>\n        ${state.adminError ? `<div class=\"error-msg\">${escapeHtml(state.adminError)}<\/div>` : ''}\n      <\/div>\n      <button class=\"btn btn-primary\" id=\"adminEnterBtn\">Entrar<\/button>\n      <button class=\"btn btn-ghost\" id=\"backBtn\" style=\"margin-top:10px;\">Volver<\/button>\n    <\/div>\n  `;\n  document.getElementById('adminEnterBtn').onclick = async ()=>{\n    const val = document.getElementById('adminEmail').value.trim().toLowerCase();\n    const pass = document.getElementById('adminPassword').value;\n    state.adminEmailInput = val;\n    if(!apiConfigured()){\n      state.adminError = 'API_URL sin configurar en el archivo.';\n      render();\n      return;\n    }\n    const btn = document.getElementById('adminEnterBtn');\n    btn.disabled = true;\n    btn.textContent = 'Comprobando\u2026';\n    try{\n      \/\/ La validaci\u00f3n de email y contrase\u00f1a se hace en el servidor (Apps Script)\n      const json = await fetchParticipants(val, pass);\n      if(json && json.ok){\n        state.adminError = '';\n        state.adminCreds = {email: val, pass};\n        state.dashboardData = json.participants || [];\n        state.dashboardError = '';\n        state.screen = 'adminDashboard';\n        render();\n      }else{\n        state.adminError = (json && json.error) || 'Acceso denegado.';\n        render();\n      }\n    }catch(e){\n      state.adminError = 'No se pudo conectar con el servidor. Revisa la conexi\u00f3n o la URL del script.';\n      render();\n    }\n  };\n  document.getElementById('backBtn').onclick = ()=>{ state.screen='welcome'; render(); };\n}\n\n\/* ---------- ADMIN DASHBOARD ---------- *\/\nlet scoreChartInstance = null;\nlet questionChartInstance = null;\n\nasync function loadDashboard(){\n  if(!state.adminCreds){\n    state.screen = 'adminLogin';\n    render();\n    return;\n  }\n  state.dashboardLoading = true;\n  render();\n  try{\n    const json = await fetchParticipants(state.adminCreds.email, state.adminCreds.pass);\n    if(json && json.ok){\n      state.dashboardData = json.participants || [];\n      state.dashboardError = '';\n    }else{\n      state.dashboardError = (json && json.error) || 'Error al cargar los datos.';\n      state.dashboardData = state.dashboardData || [];\n    }\n  }catch(e){\n    state.dashboardError = 'No se pudo conectar con el servidor.';\n    state.dashboardData = state.dashboardData || [];\n  }\n  state.dashboardLoading = false;\n  render();\n}\n\nfunction renderAdminDashboard(){\n  const data = state.dashboardData || [];\n  app.innerHTML = `\n    <div class=\"screen\">\n      <div class=\"admin-header\">\n        <div>\n          <div class=\"eyebrow\">Panel organizador<\/div>\n          <h1 class=\"display\" style=\"font-size:clamp(21.8px, 6.7vw, 25.3px);\">Resultados del taller<\/h1>\n        <\/div>\n        <button class=\"btn btn-ghost\" id=\"refreshBtn\" style=\"width:auto;padding:9px 15px;font-size:13px;\">\u21bb Actualizar<\/button>\n      <\/div>\n      ${state.dashboardLoading ? '<div class=\"empty-note\">Cargando respuestas\u2026<\/div>' : renderDashboardBody(data)}\n      <button class=\"btn btn-ghost\" id=\"logoutBtn\" style=\"margin-top:18px;\">Salir del panel<\/button>\n    <\/div>\n  `;\n  document.getElementById('refreshBtn').onclick = loadDashboard;\n  document.getElementById('logoutBtn').onclick = ()=>{ state.adminCreds=null; state.screen='welcome'; render(); };\n\n  if(!state.dashboardLoading && data.length>0){\n    setTimeout(()=>drawCharts(data), 30);\n  }\n}\n\nfunction renderDashboardBody(data){\n  if(state.dashboardError){\n    return `<div class=\"empty-note error-msg\">${escapeHtml(state.dashboardError)}<\/div>`;\n  }\n  if(data.length===0){\n    return `<div class=\"empty-note\">Todav\u00eda no hay respuestas registradas. Actualiza cuando los asistentes empiecen a participar.<\/div>`;\n  }\n  const avg = (data.reduce((s,d)=>s+d.score,0)\/data.length).toFixed(1);\n  return `\n    <div class=\"stat-grid\">\n      <div class=\"stat-box\"><div class=\"stat-num\">${data.length}<\/div><div class=\"stat-label\">Participantes<\/div><\/div>\n      <div class=\"stat-box\"><div class=\"stat-num\">${avg}<\/div><div class=\"stat-label\">Puntuaci\u00f3n media<\/div><\/div>\n    <\/div>\n    <div class=\"card\">\n      <div class=\"chart-title\">Distribuci\u00f3n de puntuaciones<\/div>\n      <div class=\"chart-box\"><canvas id=\"scoreChart\"><\/canvas><\/div>\n    <\/div>\n    <div class=\"card\">\n      <div class=\"chart-title\">% de acierto por pregunta<\/div>\n      <div class=\"chart-box tall\"><canvas id=\"questionChart\"><\/canvas><\/div>\n    <\/div>\n    <div class=\"card\">\n      <div class=\"chart-title\">Bloques del checklist<\/div>\n      <table class=\"table-mini\">\n        <tr><th>Bloque<\/th><th>% acierto medio<\/th><\/tr>\n        ${renderBlockRows(data)}\n      <\/table>\n    <\/div>\n  `;\n}\n\nfunction renderBlockRows(data){\n  const blocks = {A:[0,1,2], B:[3,4,5], C:[6,7,8,9]};\n  const labels = {A:'A \u00b7 Que te puedan reservar', B:'B \u00b7 Que decidan elegirte', C:'C \u00b7 Que repitan y recomienden'};\n  let rows = '';\n  for(const b of ['A','B','C']){\n    const idxs = blocks[b];\n    let total=0, hits=0;\n    data.forEach(p=>{ idxs.forEach(i=>{ total++; if(p.respuestas[i]) hits++; }); });\n    const pct = total? Math.round(hits\/total*100):0;\n    rows += `<tr><td>${labels[b]}<\/td><td>${pct}%<\/td><\/tr>`;\n  }\n  return rows;\n}\n\nfunction renderFallbackTables(data){\n  const dist = new Array(11).fill(0);\n  data.forEach(p=>{ dist[p.score] = (dist[p.score]||0)+1; });\n  let rows1 = dist.map((n,i)=>`<tr><td>${i} pts<\/td><td>${n}<\/td><\/tr>`).join('');\n  const pct = QUESTIONS.map((q,i)=>{\n    const total = data.length;\n    const hits = data.filter(p=>p.respuestas[i]).length;\n    return total? Math.round(hits\/total*100):0;\n  });\n  let rows2 = pct.map((p,i)=>`<tr><td>Pregunta ${i+1}<\/td><td>${p}%<\/td><\/tr>`).join('');\n  return `\n    <table class=\"table-mini\"><tr><th>Puntuaci\u00f3n<\/th><th>N\u00ba participantes<\/th><\/tr>${rows1}<\/table>\n    <div class=\"chart-title\" style=\"margin-top:16px;\">% de acierto por pregunta<\/div>\n    <table class=\"table-mini\"><tr><th>Pregunta<\/th><th>% acierto<\/th><\/tr>${rows2}<\/table>\n  `;\n}\n\nfunction drawCharts(data){\n  if(typeof Chart === 'undefined'){\n    const c1 = document.getElementById('scoreChart');\n    const c2 = document.getElementById('questionChart');\n    const fallback = '<div class=\"empty-note\">No se pudo cargar la librer\u00eda de gr\u00e1ficos (sin conexi\u00f3n al CDN). Mostrando datos en tabla.<\/div>' + renderFallbackTables(data);\n    if(c1 && c1.closest('.card')) c1.closest('.card').innerHTML = '<div class=\"chart-title\">Distribuci\u00f3n de puntuaciones<\/div>' + fallback;\n    if(c2 && c2.closest('.card')) c2.closest('.card').style.display = 'none';\n    return;\n  }\n  \/\/ Distribuci\u00f3n de puntuaciones 0..10\n  const dist = new Array(11).fill(0);\n  data.forEach(p=>{ dist[p.score] = (dist[p.score]||0)+1; });\n\n  const ctx1 = document.getElementById('scoreChart');\n  if(ctx1){\n    if(scoreChartInstance) scoreChartInstance.destroy();\n    scoreChartInstance = new Chart(ctx1, {\n      type:'bar',\n      data:{\n        labels: dist.map((_,i)=>i),\n        datasets:[{ data: dist, backgroundColor:'#c9a24c', borderRadius:4 }]\n      },\n      options:{\n        responsive:true,\n        maintainAspectRatio:false,\n        plugins:{legend:{display:false}},\n        scales:{\n          x:{ title:{display:true,text:'Puntuaci\u00f3n',color:'#f6f0e2'}, ticks:{color:'#f6f0e2'}, grid:{color:'rgba(255,255,255,.08)'} },\n          y:{ ticks:{color:'#f6f0e2', precision:0}, grid:{color:'rgba(255,255,255,.08)'} }\n        }\n      }\n    });\n  }\n\n  \/\/ % acierto por pregunta\n  const pct = QUESTIONS.map((q,i)=>{\n    const total = data.length;\n    const hits = data.filter(p=>p.respuestas[i]).length;\n    return total? Math.round(hits\/total*100):0;\n  });\n  const ctx2 = document.getElementById('questionChart');\n  if(ctx2){\n    if(questionChartInstance) questionChartInstance.destroy();\n    questionChartInstance = new Chart(ctx2, {\n      type:'bar',\n      data:{\n        labels: pct.map((_,i)=>'P'+(i+1)),\n        datasets:[{ data: pct, backgroundColor: pct.map(v=> v<40?'#b5636b': v<70?'#c9a24c':'#8fae6f'), borderRadius:4 }]\n      },\n      options:{\n        indexAxis:'y',\n        responsive:true,\n        maintainAspectRatio:false,\n        plugins:{legend:{display:false}},\n        scales:{\n          x:{ min:0, max:100, ticks:{color:'#f6f0e2', callback:v=>v+'%'}, grid:{color:'rgba(255,255,255,.08)'} },\n          y:{ ticks:{color:'#f6f0e2'}, grid:{display:false} }\n        }\n      }\n    });\n  }\n}\n\n\/* ---------------------------------------------------------\n   UTILIDADES\n--------------------------------------------------------- *\/\nfunction escapeHtml(str){\n  return String(str||'').replace(\/[&<>\"']\/g, s=>({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'}[s]));\n}\nfunction escapeAttr(str){ return escapeHtml(str); }\n\nrender();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Canary Islands Wine Tourism \u00b7 Digital Assessment<\/p>","protected":false},"author":16,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_kad_blocks_custom_css":"","_kad_blocks_head_custom_js":"","_kad_blocks_body_custom_js":"","_kad_blocks_footer_custom_js":"","_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":true,"_kad_post_footer":true,"_kad_post_classname":"","_glsr_average":0,"_glsr_ranking":0,"_glsr_reviews":0,"footnotes":""},"class_list":["post-18271","page","type-page","status-publish","hentry"],"acf":[],"taxonomy_info":[],"featured_image_src_large":false,"author_info":{"display_name":"Alberto","author_link":"https:\/\/www.turitop.com\/en\/author\/alberto\/"},"comment_info":0,"_links":{"self":[{"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/pages\/18271","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/users\/16"}],"replies":[{"embeddable":true,"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/comments?post=18271"}],"version-history":[{"count":4,"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/pages\/18271\/revisions"}],"predecessor-version":[{"id":18282,"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/pages\/18271\/revisions\/18282"}],"wp:attachment":[{"href":"https:\/\/www.turitop.com\/en\/wp-json\/wp\/v2\/media?parent=18271"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}