1<!DOCTYPE html><!-- This site was created in Webflow. https://webflow.com --><!-- Last Published: Sat Oct 03 2026 13:28:24 GMT+0000 (Coordinated Universal Time) --><html data-wf-domain="redentrenadores-268bb8.webflow.io" data-wf-page="6908a45a566828a761f9d274" data-wf-site="68c5a729c71fa3f37858fbbe" lang="es"><head><meta charset="utf-8"/><link href="https://cdn.prod.website-files.com" rel="preconnect" crossorigin="anonymous"/><title>Pregunta al experto | RedEntrenadores</title><meta content="Resuelve tus dudas sobre entrenamiento fÃsico, adelgazamiento y ganancia muscular. ¡Respuestas gratuitas de entrenadores personales en 48 horas!" name="description"/><meta content="Pregunta al experto | RedEntrenadores" property="og:title"/><meta content="Resuelve tus dudas sobre entrenamiento fÃsico, adelgazamiento y ganancia muscular. ¡Respuestas gratuitas de entrenadores personales en 48 horas!" property="og:description"/><meta content="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68cf1d169359063e6094caad_logo-256x256.png" property="og:image"/><meta content="Pregunta al experto | RedEntrenadores" name="twitter:title"/><meta content="Resuelve tus dudas sobre entrenamiento fÃsico, adelgazamiento y ganancia muscular. ¡Respuestas gratuitas de entrenadores personales en 48 horas!" name="twitter:description"/><meta property="og:type" content="website"/><meta content="summary_large_image" name="twitter:card"/><meta content="width=device-width, initial-scale=1" name="viewport"/><meta content="Ibl2YfMxHx18YwgwBo_Cf7Y0noaXDXPp8qxAux6bR7I" name="google-site-verification"/><meta content="Webflow" name="generator"/><link href="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/css/redentrenadores-268bb8.webflow.shared.5fdd5e548.min.css" rel="stylesheet" type="text/css" integrity="sha384-X91eVImZbdbFw8t2Y6QKDl7HUD/gaoQ44rfY64Qq+p7zJMJt635EzDj9JzMjxLwR" crossorigin="anonymous"/><link href="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/css/redentrenadores-268bb8.webflow.6908a45a566828a761f9d274.359fa989a.opt.min.css" rel="stylesheet" type="text/css" integrity="sha384-NZ+piafdpavFkpz/DtcJy0Su1V+7rTG+Meoe1lNWSoav6Yf2PX9YHlVW6ymKS9mE" crossorigin="anonymous"/><link href="https://fonts.googleapis.com" rel="preconnect"/><link href="https://fonts.gstatic.com" rel="preconnect" crossorigin="anonymous"/>
1<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.26/webfont.js" type="text/javascript"></script>
1<script type="text/javascript">WebFont.load({ google: { families: ["Open Sans:300,300italic,400,400italic,600,600italic,700,700italic,800,800italic","Barlow:300,400,500,600,700","DM Sans:300,400,500,600,700","Plus Jakarta Sans:300,400,500,600,700"] }});</script>
1<script type="text/javascript">!function(o,c){var n=c.documentElement,t=" w-mod-";n.className+=t+"js",("ontouchstart"in o||o.DocumentTouch&&c instanceof DocumentTouch)&&(n.className+=t+"touch")}(window,document);</script>
1<link href="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68cf1cb49e59be031d2f13dc_favicon-32x32.png" rel="shortcut icon" type="image/x-icon"/><link href="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68cf1d169359063e6094caad_logo-256x256.png" rel="apple-touch-icon"/>
1<script type="application/ld+json"> 2{ 3 "@context": "https://schema.org", 4 "@type": "FAQPage", 5 "mainEntity": [ 6 { 7 "@type": "Question", 8 "acceptedAnswer": { 9 "@type": "Answer", 10 "text": "Es completamente gratis. Puedes hacer tantas preguntas como quieras sin ningún coste ni compromiso de contratación." 11 }, 12 "name": "¿Cuánto cuesta preguntar a un entrenador personal?" 13 }, 14 { 15 "@type": "Question", 16 "acceptedAnswer": { 17 "@type": "Answer", 18 "text": "El tiempo medio de respuesta es de menos de 24 horas, aunque puede variar según la categorÃa y disponibilidad." 19 }, 20 "name": "¿En cuánto tiempo responden los entrenadores?" 21 }, 22 { 23 "@type": "Question", 24 "acceptedAnswer": { 25 "@type": "Answer", 26 "text": "Entrenadores personales certificados verificados por RedEntrenadores, activos en la plataforma." 27 }, 28 "name": "¿Quién responde las preguntas?" 29 }, 30 { 31 "@type": "Question", 32 "acceptedAnswer": { 33 "@type": "Answer", 34 "text": "SÃ, no es necesario compartir datos personales para publicar tu pregunta." 35 }, 36 "name": "¿Puedo hacer una pregunta de forma anónima?" 37 }, 38 { 39 "@type": "Question", 40 "acceptedAnswer": { 41 "@type": "Answer", 42 "text": "SÃ, desde la respuesta puedes ver el perfil del entrenador y contactar con él si quieres profundizar." 43 }, 44 "name": "¿Puedo contactar directamente con el entrenador que me respondió?" 45 } 46 ] 47} 48</script>
48<script>(function(w,i,g){w[g]=w[g]||[];if(typeof w[g].push=='function')w[g].push.apply(w[g],Array.isArray(i)?i:[i]);})(window,['G-GSEP18CGXX'],'google_tags_first_party');</script>
48<script async="" src="/avljl2rk9q5pNjhjNWE3MjljNzFmYTNmMzc4NThmYmJl/peOuxr4X3pGz_RiXea8PdbkeTQo"></script>
48<script type="text/javascript">window.dataLayer = window.dataLayer || [];function gtag(){dataLayer.push(arguments);}gtag('set', 'developer_id.dZGVlNj', true);gtag('set', 'developer_id.dYWYxNW', true);gtag('js', new Date());gtag('config', 'G-GSEP18CGXX');</script>
48<script type="text/javascript">!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.agent='plwebflow';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init', '1689976748571004');fbq('track', 'PageView');</script>
48<!-- Google Tag Manager -->
49<script>
vendor: 327 bytes, lines 49-53
49(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 50new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 51j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 52'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 53})(window,document,'script','dataLayer','
53GTM-KBD83QKS
vendor: 3 bytes, line 53
53');
53</script>
53 54<!-- End Google Tag Manager --> 55 56<!-- Start cookieyes banner -->
57<script id="cookieyes" type="text/javascript" src="https://cdn-cookieyes.com/client_data/3b90490e3918e885f5cdab3d/script.js"></script>
57 58<!-- End cookieyes banner --> 59 60 61<!-- Hotjar / Contentsquare: async porque es analitica pura, nada en el repo 62 depende de que este cargado (grep sin resultados fuera de backups de este 63 mismo bloque). Eran 623 KB bloqueando el primer pintado: el mayor peso de 64 los 4760ms de "solicitudes que bloquean el renderizado" de PageSpeed. -->
65<script src="https://t.contentsquare.net/uxa/e67e16087d6b8.js" async></script>
65 66 67 68<!-- Memberstack: solo fuera de /lp/*. Fuente y porque: 69 widgets/custom-code-memberstack-por-ruta.html -->
70<script> 71(function () { 72 // Las landings de paid no tienen nada que dependa de Memberstack. Cualquier 73 // otra ruta lo carga igual que siempre, bloqueando, porque los widgets del 74 // panel dan por hecho que $memberstackDom ya existe cuando ellos corren. 75 if (/^\/lp(\/|$)/.test(location.pathname)) return; 76 77 document.write( 78 '
78<script data-memberstack-app="app_cmgl62z3b00kw0tt21gk97j2x"' + 79 ' src="https://static.memberstack.com/scripts/v1/memberstack.js"' + 80 ' type="text/javascript"><\/script>' 81 ); 82})(); 83</script>
83 84 85<!-- Para que los dropdown sean blancos --> 86<style> 87.w-dropdown-list { 88 background-color: white !important; 89} 90 91.w-dropdown.w--open .w-dropdown-list { 92 background-color: white !important; 93} 94 95.search-dropdown { 96 background-color: white !important; 97} 98</style> 99 100 101<!-- Limita el texto descriptivo de cada entrenador -->
102<script> 103document.addEventListener("DOMContentLoaded", function() { 104 const elements = document.querySelectorAll(".limit-text"); 105 106 elements.forEach(el => { 107 const text = el.textContent.trim(); 108 109 if (text.length > 80) { 110 el.textContent = text.substring(0, 80) + "..."; 111 } 112 }); 113}); 114</script>
114 115 116 117<!-- Canonical autorreferenciado (auditoria SEO 2026-08) -->
118<script> 119(function () { 120 if (document.querySelector('link[rel="canonical"]')) return; 121 var p = location.pathname; 122 while (p.length > 1 && p.charAt(p.length - 1) === '/') p = p.slice(0, -1); 123 var l = document.createElement('link'); 124 l.rel = 'canonical'; 125 l.href = 'https://www.redentrenadores.com' + (p || '/'); 126 document.head.appendChild(l); 127})(); 128</script>
128 129 130 131<!-- Impide que se descargue el video de fondo de la seccion de FAQ. 132 Es decorativo, esta al final de la pagina y su webm pesa 4,6 MB en las 61 133 paginas que lo llevan (home, listado y las 59 de ciudad). Ocultarlo por CSS 134 no basta: Webflow crea el <video> con autoplay y el navegador se lo baja 135 igual aunque el contenedor este en display:none (comprobado: paused=false, 136 buffered=15s). Sin data-video-urls el script de Webflow no crea el <video>, 137 asi que no hay nada que pedir. 138 Para revertirlo, basta con borrar este bloque y quitar el display:none de 139 la clase bg-video_faqs. 140 ACTUALIZACION 2026-08-11: en /entrenadores-personales/* el elemento se 141 borro directamente (ver commit), asi que este bloque ya no hace nada ahi. 142 Sigue haciendo falta en / y /entrenador-personal hasta que se borre el 143 elemento tambien en esas dos plantillas (vive dentro de un componente que 144 la API no puede recorrer). -->
145<script> 146document.addEventListener('DOMContentLoaded', function () { 147 document.querySelectorAll('[data-video-urls]').forEach(function (el) { 148 el.removeAttribute('data-video-urls'); 149 }); 150}); 151</script>
151 152 153 154<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 155 AMPLITUDE - analitica + session replay (2026-08-14) 156 El proyecto no tiene bundler (no existe package.json en el repo), asi que 157 en vez de `npm install @amplitude/unified` se carga EL MISMO paquete como 158 modulo ESM desde jsDelivr por import() dinamico. La llamada de init es la 159 de la documentacion: initAll, no init. 160 161 CONSENTIMIENTO: no arranca hasta que cookieyes tiene la categoria 162 "analytics" en yes. Esto vale tanto para la analitica como para Session 163 Replay, que graba el 100% de las sesiones (sampleRate 1) y sin 164 consentimiento no puede grabar nada. Verificado sobre el propio banner.js 165 que sirve el sitio: la cookie es "cookieyes-consent" con pares 166 clave:valor separados por coma, las categorias son necessary / functional 167 / analytics / performance / advertisement, y al aceptar se despacha 168 "cookieyes_consent_update" sobre document. Bajo RGPD analytics arranca en 169 "no", asi que por defecto aqui no se carga nada. 170 171 El stub de window.amplitude existe desde el primer momento y encola los 172 track() previos a la carga del modulo; al inicializar se vacia la cola. Si 173 el usuario nunca da su consentimiento, la cola se descarta sin enviarse. 174 Los embeds de widgets/ llaman a amplitude.track(...) contando con esto. 175 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ -->
176<script> 177(function () { 178 window.amplitude = window.amplitude || { _q: [], track: function () { this._q.push(['track', arguments]); } }; 179 180 function hayConsentimiento() { 181 var m = document.cookie.match(/(?:^|;\s*)cookieyes-consent=([^;]*)/); 182 if (!m) return false; 183 return decodeURIComponent(m[1]).split(',').indexOf('analytics:yes') !== -1; 184 } 185 186 var arrancado = false; 187 function arrancar() { 188 if (arrancado || !hayConsentimiento()) return; 189 arrancado = true; 190 import('https://cdn.jsdelivr.net/npm/@amplitude/unified@1/+esm').then(function (amplitude) { 191 var pendientes = (window.amplitude && window.amplitude._q) || []; 192 // Amplitude ingestion key â public by design; move to an env var when you set up environments. 193 amplitude.initAll('21ce582269eabdf7c4208d1960a7731e', {"analytics":{"autocapture":true},"sessionReplay":{"sampleRate":1}}); 194 window.amplitude = amplitude; 195 pendientes.forEach(function (c) { amplitude[c[0]].apply(null, c[1]); }); 196 }).catch(function () { arrancado = false; }); 197 } 198 199 arrancar(); 200 document.addEventListener('cookieyes_consent_update', arrancar); 201})(); 202</script>
202 203 204<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 205 SIMULAR PANEL DE ENTRENADOR (2026-08-21) 206 Deja que el equipo recorra /panel-entrenador viendo los datos de un 207 entrenador concreto, sin entrar con su cuenta y sin poder tocar nada. Lo 208 enciende la pestana "Simular entrenador" de /admin, que abre el panel con 209 ?simular=<token>. Solo lectura: el shim corta cualquier metodo que no sea 210 GET, y las Edge Functions lo rechazan tambien en el servidor. 211 212 Va aqui y no como registered script porque esos tienen un tope de 2.000 213 caracteres y este no cabe (son 10 KB). Se pega MINIFICADO: la fuente 214 legible, con el porque de cada parte, es 215 widgets/custom-code-simular-entrenador.js del repo, y lo de abajo sale de 216 widgets/custom-code-simular-entrenador.min.js. Para cambiarlo: editar la 217 fuente, regenerar con terser y volver a pegar. No editar esto a mano. 218 219 Se corta solo en cuanto la ruta no empieza por /panel-entrenador, asi que 220 en el resto del sitio no hace nada mas que existir. 221 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ -->
222<script> 223/*! Simular panel de entrenador â RedEntrenadores 224 * Version minificada. La fuente legible, con el porque de cada parte, esta en 225 * widgets/custom-code-simular-entrenador.js del repo. Para cambiarlo: editar 226 * ese fichero, regenerar con `npx terser` y volver a pegar aqui. No editar 227 * este bloque a mano. */ 228!function(){var e='https://yrzanatzshdpvypazgeb.supabase.co/functions/v1/',t='rt-simulacion',r=['comprobar-titulo'],n=/^rt-perfil-/;if(0===location.pathname.indexOf('/panel-entrenador')){var o=new URL(location.href),a=o.searchParams.get('simular');if(a){!function(e){try{sessionStorage.setItem(t,JSON.stringify(e))}catch(e){}}({token:a,nombre:o.searchParams.get('simular_nombre')||'este entrenador'}),o.searchParams.delete('simular'),o.searchParams.delete('simular_nombre');var i=o.pathname+(o.searchParams.toString()?'?'+o.searchParams:'')+o.hash;history.replaceState(null,'',i)}var s=function(){try{return JSON.parse(sessionStorage.getItem(t)||'null')}catch(e){return null}}();if(s&&s.token)if(g(s.token)<Date.now())h();else{window.rtSimulacion={entrenador:s.nombre,expira:new Date(g(s.token))};var l='rt-sim:'+s.token.split('.')[1]+':',c=Storage.prototype.getItem,u=Storage.prototype.setItem,d=Storage.prototype.removeItem;Storage.prototype.getItem=function(e){return c.call(this,v(this)?b(e):e)},Storage.prototype.setItem=function(e,t){return u.call(this,v(this)?b(e):e,t)},Storage.prototype.removeItem=function(e){return d.call(this,v(this)?b(e):e)};var m=window.fetch;if(window.fetch=function(t,n){var o='string'==typeof t?t:t&&t.url||'';if(0!==o.indexOf(e))return m.apply(this,arguments);var a=(n&&n.method||t&&t.method||'GET').toUpperCase(),i=o.slice(54).split(/[/?#]/)[0];if('GET'!==a&&'OPTIONS'!==a&&r.indexOf(i)<0)return function(){var e=document.getElementById('rt-sim-aviso');if(!e)return;e.textContent='No se ha guardado nada: el panel simulado es solo de lectura.',e.hidden=!1,clearTimeout(f),f=setTimeout(function(){e.hidden=!0},5e3)}(),Promise.resolve(new Response('simulacion_solo_lectura',{status:403}));if('string'==typeof t){var l=new Headers(n&&n.headers||{});l.set('Authorization','Bearer '+s.token);var c={};for(var u in n||{})c[u]=n[u];return c.headers=l,m.call(this,t,c)}var d=new Request(t,n);return d.headers.set('Authorization','Bearer '+s.token),m.call(this,d)},window.$memberstackDom)y(window.$memberstackDom);else try{var p;Object.defineProperty(window,'$memberstackDom',{configurable:!0,get:function(){return p},set:function(e){p=y(e)}})}catch(e){}var f=null;'loading'===document.readyState?document.addEventListener('DOMContentLoaded',x):x()}}function h(){try{sessionStorage.removeItem(t)}catch(e){}}function g(e){var t=String(e||'').split('.');return 4===t.length?Number(t[2]):0}function b(e){return n.test(String(e))?l+e:e}function v(e){try{return e===window.localStorage}catch(e){return!1}}function y(e){if(!e||'function'!=typeof e.getMemberCookie||e.__rtSimParcheado)return e;var t=e.getMemberCookie.bind(e);return e.getMemberCookie=function(){return Promise.resolve().then(t).then(function(e){return e||s.token}).catch(function(){return s.token})},e.__rtSimParcheado=!0,e}function x(){if(!document.getElementById('rt-sim-banner')){var e=document.createElement('style');e.textContent=['#rt-sim-banner{position:fixed;left:0;right:0;bottom:0;z-index:10000;','background:#FEF3C7;border-top:1px solid #92400E;color:#92400E;','font-family:\'Plus Jakarta Sans\',-apple-system,system-ui,sans-serif;font-size:13px;','display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:10px 16px;box-sizing:border-box}','#rt-sim-banner .rt-sim-texto{flex:1;
228min-width:200px;margin:0}','#rt-sim-banner strong{color:#1B1E22}','#rt-sim-banner .rt-sim-salir{padding:9px 18px;border:1px solid #089F3B;border-radius:8px;','background:#fff;color:#089F3B;text-transform:uppercase;font-weight:700;letter-spacing:0.03em;','font-size:11px;font-family:inherit;cursor:pointer;flex-shrink:0}','#rt-sim-banner .rt-sim-salir:hover{background:#EAF3DE}','#rt-sim-aviso{flex-basis:100%;background:#FCEBEB;color:#791F1F;border-radius:8px;','padding:10px 12px;font-size:13px;margin:0}','#rt-sim-aviso[hidden]{display:none}','body{padding-bottom:64px}'].join(''),document.head.appendChild(e);var t,r=document.createElement('div');r.id='rt-sim-banner',r.innerHTML='<p class="rt-sim-texto">Estás viendo el panel de <strong>'+(t=s.nombre,String(null==t?'':t).replace(/[&<>"']/g,function(e){return{'&':'&','<':'<','>':'>','"':'"','\'':'''}[e]})+'</strong>. Solo lectura: nada de lo que hagas aquà se guarda.</p><button type="button" class="rt-sim-salir">Salir de la simulación</button><p id="rt-sim-aviso" hidden></p>'),document.body.appendChild(r),r.querySelector('.rt-sim-salir').addEventListener('click',function(){h(),function(){try{for(var e=[],t=0;t<localStorage.length;t++){var r=localStorage.key(t);r&&0===r.indexOf('rt-sim:')&&e.push(r)}e.forEach(function(e){d.call(localStorage,e)})}catch(e){}}(),location.href='/admin'})}}}(); 229 230</script>
230<!-- Canonical estatico (2026-08-19) 231 Los 8 chips de categoria de esta pagina son enlaces reales a 232 /pregunta-al-experto?categoria=..., que devuelven el mismo title, la misma 233 meta description y el mismo contenido base. Ubersuggest los marcaba como 234 metadescripciones duplicadas. 235 236 El script del <head> del sitio ya inyecta este mismo canonical por JS 237 (construido desde location.pathname, tirando la query), pero solo lo ve 238 quien ejecuta JS. Aqui va en el HTML servido, para el resto. 239 240 GOTCHA: ese script tiene un "si ya hay canonical, no hagas nada", pero NO 241 se aparta. El custom code del sitio va antes que el de la pagina, asi que 242 cuando el script corre este <link> todavia no esta parseado y acaba metiendo 243 un segundo canonical. Por eso el script de abajo: se queda con este y borra 244 cualquier otro. Solo afecta a esta pagina; el HTML servido ya trae uno solo. 245 Si algun dia se ponen canonicals estaticos en mas paginas, sale mas a cuenta 246 retrasar el script del sitio a DOMContentLoaded que repetir esto. --> 247<link rel="canonical" id="canonical-fijo" href="https://www.redentrenadores.com/pregunta-al-experto">
248<script> 249(function () { 250 function limpiar() { 251 var todos = document.querySelectorAll('link[rel="canonical"]'); 252 for (var i = 0; i < todos.length; i++) { 253 if (todos[i].id !== 'canonical-fijo' && todos[i].parentNode) { 254 todos[i].parentNode.removeChild(todos[i]); 255 } 256 } 257 } 258 limpiar(); 259 if (document.readyState === 'loading') { 260 document.addEventListener('DOMContentLoaded', limpiar); 261 } 262})(); 263</script>
vendor: 1 bytes, line 263
263
264<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6a7b1ad624d451ff7ab95b57%2Fdeep_link_panel_tras_login-0.0.1.js" type="text/javascript"></script>
264<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6a8188e2745a991cf146025e%2Fredirect_urls_antiguas_panel-1.0.0.js" type="text/javascript"></script>
264<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6a90ae0a69c0c92f2517a3b6%2Fawintracking2-1.0.0.js" type="text/javascript"></script>
264<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6aa83d1b2d69974135a99d99%2Fguardian_sesion_panel-1.0.0.js" type="text/javascript"></script>
264<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6aaed9e15b8290737a4ca482%2Fguardia_onboarding_entrenador-1.0.0.js" type="text/javascript"></script>
264<script type="text/javascript">window.__WEBFLOW_CURRENCY_SETTINGS = {"currencyCode":"USD","symbol":"$","decimal":".","fractionDigits":2,"group":",","template":"{{wf {\"path\":\"symbol\",\"type\":\"PlainText\"} }} {{wf {\"path\":\"amount\",\"type\":\"CommercePrice\"} }} {{wf {\"path\":\"currencyCode\",\"type\":\"PlainText\"} }}","hideDecimalForWholeNumbers":false};</script>
264</head><body><div data-animation="default" class="rl_navbar5_component padding-top-medium w-nav" data-easing2="ease" data-easing="ease" data-collapse="medium" role="banner" data-duration="400" data-wf--header-v6--variant="base" ms-code-scroll-offset="40"><div class="rl_navbar5_container"><a href="/" class="rl_navbar5_logo-link w-nav-brand"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px.avif" loading="lazy" width="63" sizes="63px" alt="Logotipo de RedEntrenadores" srcset="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px-p-500.avif 500w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px-p-800.avif 800w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px.avif 2246w" class="rl_navbar5_logo"/></a><nav role="navigation" data-ms-content="!members" class="rl_navbar5_menu w-nav-menu"><div class="nav-pills-grupo"><a href="/pregunta-al-experto" aria-current="page" class="nav-pill-item w--current">Pregunta al experto</a><div class="nav-pill-sep"></div><a href="/donde-entrenar" class="nav-pill-item">Dónde entrenar</a></div><div class="rl_navbar5_menu-left"><div data-hover="true" data-delay="300" class="rl_navbar5_menu-dropdown w-dropdown"><div class="rl_navbar5_dropdown-toggle w-dropdown-toggle"><div class="rl-dropdown-icon w-embed"><svg width=" 100%" height=" 100%" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 265<path fill-rule="evenodd" clip-rule="evenodd" d="M2.55806 6.29544C2.46043 6.19781 2.46043 6.03952 2.55806 5.94189L3.44195 5.058C3.53958 4.96037 3.69787 4.96037 3.7955 5.058L8.00001 9.26251L12.2045 5.058C12.3021 4.96037 12.4604 4.96037 12.5581 5.058L13.4419 5.94189C13.5396 6.03952 13.5396 6.19781 13.4419 6.29544L8.17678 11.5606C8.07915 11.6582 7.92086 11.6582 7.82323 11.5606L2.55806 6.29544Z" fill="currentColor"/> 266</svg></div><div class="rl_navbar5_link-text">Recursos</div></div><nav class="rl_navbar5_dropdown-list-copy w-dropdown-list"><div class="rl_navbar5_container"><div class="rl_navbar5_dropdown-content"><div class="rl_navbar5_dropdown-content-left"><div class="rl_navbar5_dropdown-link-list"><h4 class="rl_navbar5_dropdown-column-text">Herramientas gratis</h4><a href="/calculadoras/precio-entrenador-personal" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/6a637ee83ad287f97c093036_euro%20coin.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Cuánto cuesta un entrenador</div><p class="rl_navbar5_text-small">La horquilla real por sesión y al mes, con 126 tarifas publicadas.</p></div></a><a href="/calculadoras/donde-te-conviene-entrenar" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/6a6333b155392ae72dc3544a_icono%20lugar.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">¿Dónde te conviene entrenar?</div><p class="rl_navbar5_text-small">Gimnasio, a domicilio, al aire libre u online: seis preguntas y la pega de cada uno.</p></div></a><a href="/calculadoras/plan-primer-mes" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176197f33ec0043320f2a_dumbbell-01.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Tu primer mes de entrenamiento</div><p class="rl_navbar5_text-small">Cuántas sesiones y con qué foco cada semana, según los dÃas que tengas y dónde entrenes.</p></div></a><a href="/calculadoras/calculadora-calorias" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176422d38a3a4f0d2241b_weight-scale.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Calculadora de calorÃas</div><p class="rl_navbar5_text-small">Calcula las calorÃas diarias que necesitas según tu edad, peso y nivel de actividad fÃsica.</p></div></a><a href="/calculadoras/calculadora-proteina" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a17619966cea24bd9bfe87_ta
266pe-measure.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Calculadora de proteÃna</div><p class="rl_navbar5_text-small">Cuánta proteÃna necesitas al dÃa según tu peso y tu objetivo, con las calorÃas al lado.</p></div></a><a href="/calculadoras/calculadora-imc" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/6ac016796b7d40191f7cf036_indice-imc.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Calculadora de IMC</div><p class="rl_navbar5_text-small">Tu Ãndice de masa corporal y el rango de peso que corresponde a tu altura.</p></div></a><a href="/calculadoras/test-nivel-fitness" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176695fe1a56f4bbbe03b_workout-run.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Test de nivel de fitness</div><p class="rl_navbar5_text-small">Evalúa tu condición fÃsica actual y descubre qué tipo de entrenador necesitas.</p></div></a><a href="/calculadoras/pruebas-fisicas-oposiciones" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69c411fa43d84eac3751591f_medal.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Pruebas fÃsicas de oposición</div><p class="rl_navbar5_text-small">Compara tus marcas con las que te piden y descubre cuál es tu punto débil.</p></div></a></div><div class="rl_navbar5_dropdown-link-list"><h4 class="rl_navbar5_dropdown-column-text">Entrenadores por ciudad</h4><a href="https://www.redentrenadores.com/entrenadores-personales/madrid" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Madrid</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/barcelona" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Barcelona</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/zaragoza" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Zaragoza</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/valencia" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Valencia</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/sevilla" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Sevilla</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/malaga" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Málaga</div></div></a><a href="https://www.redentrenadores.com/entrenadores-personales/murcia" target="_blank" class="rl_navbar5_dropdown-link w-inline-block"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div id="w-node-_64a5fb61-5186-864a-70be-7b9103a4a59d-03a4a53d" class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Murcia</div></div></a><a href="/donde-entrenar" class="rl_navbar5_dropdown-link"><div class="rl_navbar5_icon-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/69a176ab30392e5a11168bd3_icon%20simple.svg" loading="lazy" alt="" class="rl_navbar5_icon"/></div><div class="rl_navbar5_item-right"><div class="rl_navbar5_item-title">Dónde entrenar</div></div></a><a id="w-node-_64a5fb61-5186-864a-70be-7b9103a4a5a0-03a4a53d" href="/entrenador-personal" class="btn-green indide-menu w-inline-block"><div>Ver más ciudades</div></a></div></div><div class="rl_navbar5_dropdown-content-right"><div class="rl_navbar5_dropdown-content-wrapper"><h4 class="rl_navbar5_dropdown-column-text white">ArtÃculos destacados</h4><div id="w-node-_64a5fb61-5186-864a-70be-7b9103a4a5a7-03a4a53d" class="rl_navbar5_blog-list-wrapper"><div class="rl_navbar5_blog-list"><a href="https://www.redentrenadores.com/blog/precio-entrenador-personal-online" target="_blank" class="rl_navbar5_blog-item w-inline-block"><div class="rl_navbar5_blog-image-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840.webp" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-500.webp 500w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-800.webp 800w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-1080.webp 1080w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-1600.webp 1600w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-2000.webp 2000w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-2600.webp 2600w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840-p-3200.webp 3200w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d4c5d1b40a16e775245_pexels-cottonbro-4064840.webp 4152w" alt="Persona realizando un entrenamiento personal" class="rl_navbar5_blog-image"/></div><div class="rl_navbar5_large-item-content"><div class="rl_navbar5_item-title white">¿Cuánto cuesta realmente un entrenador personal online?</div><div class="rl_navbar5_item-link">Leer artÃculo</div></div></a><a href="https://www.redentrenadores.com/blog/tipos-entrenamiento-personal" target="_blank" class="rl_navbar5_blog-item w-inline-block"><div class="rl_navbar5_blog-image-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d6acffc97162b06e822_In-home-personal-training.webp" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d6acffc97162b06e822_In-home-personal-training-p-500.webp 500w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d6acffc97162b06e822_In-home-personal-training.webp 600w" alt="Entrenador personal entrenando a un cliente a domicilio" class="rl_navbar5_blog-image"/></div><div class="rl_navbar5_large-item-content"><div class="rl_navbar5_item-title white">Tipos de entrenamiento personal: descubre cuál se adapta mejor a ti</div><div class="rl_navbar5_item-link">Leer artÃculo</div></div></a><a href="https://www.redentrenadores.com/blog/entrenador-personal-nutricionista-fisioterapeuta" target="_blank" class="rl_navbar5_blog-item w-inline-block"><div id="w-node-_64a5fb61-5186-864a-70be-7b9103a4a5c2-03a4a53d" class="rl_navbar5_blog-image-wrapper"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341.webp" loading="lazy" sizes="100vw" srcset="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-500.webp 500w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-800.webp 800w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-1080.webp 1080w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-1600.webp 1600w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-2000.webp 2000w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-2600.webp 2600w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341-p-3200.webp 3200w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/693c5d7b2b95815407c9ec1e_pexels-olly-3813341.webp 7680w" alt="Persona realizando un entrenamiento personal" class="rl_navbar5_blog-image"/></div><div class="rl_navbar5_large-item-content"><div class="rl_navbar5_item-title white">Entrenador personal, nutricionista y fisioterapeuta: el equipo perfecto para transformar tu cuerpo</div><div class="rl_navbar5_item-link">Leer artÃculo</div></div></a></div></div><div class="rl-button-group"><a href="/blog" class="rl-button-link w-inline-block"><div class="rl-button-link-text">Ver todos los artÃculos</div><div class="rl-button-link-icon w-embed">
266<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> 267<path d="M6 3L11 8L6 13" stroke="CurrentColor" stroke-width="1.5"/> 268</svg></div></a></div></div><div class="rl_navbar5_dropdown-background-layer"></div></div></div></div></nav></div></div><div class="rl_navbar5_menu-right"><div data-hover="true" data-delay="0" data-ms-content="!members" class="drop-menu_entrenador w-dropdown"><div class="rl_navbar5_dropdown-toggle menu w-dropdown-toggle"><div class="rl-dropdown-icon w-icon-dropdown-toggle"></div><div>¿Eres entrenador?</div></div><nav class="dropdown-list-2 w-dropdown-list"><a href="/panel-entrenador" class="w-dropdown-link">Zona para entrenadores</a><a href="/registrate" class="w-dropdown-link">Activar perfil gratuito</a></nav></div><a href="https://www.redentrenadores.com/encuentra-entrenador" class="btn-green menu w-button">Buscar entrenador</a></div></nav><div class="w-layout-hflex"><a data-ms-content="members" href="/panel-entrenador" class="btn-green white_version w-button">Mi panel</a><div class="rl_navbar5_menu-button w-nav-button"><div data-ms-content="!members" class="rl_menu-icon"><div class="rl_menu-icon_line-top"></div><div class="rl_menu-icon_line-middle"><div class="rl_menu-icon_line-middle-inner"></div></div><div class="rl_menu-icon_line-bottom"></div></div></div></div></div><div class="w-embed w-script">
268<script> 269/* Webflow solo permite 991/767/479 como punto de colapso y su JS decide por su 270 cuenta (ignora el CSS). Con data-collapse=all lo gestiona en todos los anchos 271 y el <style> de abajo devuelve la fila a partir de 1180px. Este embed es hijo 272 del navbar y se ejecuta al parsearse, antes de que cargue webflow.js al final 273 del body, asi que el atributo ya esta puesto cuando Webflow inicializa. 274 El listener de resize hace falta porque, al creer que siempre esta colapsado, 275 Webflow ya no cierra el menu al cruzar un breakpoint: sin esto, quien abre el 276 menu a 1100px y luego amplia la ventana se queda con el menu enganchado 277 (Webflow mueve el <nav> dentro de .w-nav-overlay al abrir). Se cierra con su 278 propio boton para que Webflow restaure el DOM y limpie sus estilos inline. */ 279(function(){ 280 var s=document.currentScript,n=s&&s.parentElement&&s.parentElement.closest(".w-nav"); 281 if(!n)return; 282 n.setAttribute("data-collapse","all"); 283 var t; 284 window.addEventListener("resize",function(){ 285 clearTimeout(t); 286 t=setTimeout(function(){ 287 if(window.innerWidth<1180)return; 288 var b=n.querySelector(".w-nav-button.w--open"); 289 if(b)b.click(); 290 },100); 291 }); 292})(); 293</script>
293 294<style> 295/* El menu colapsa a hamburguesa hasta 1179px: por debajo de ~1165 no caben los 296 dos enlaces destacados y el flexbox aplasta el logo de 250 a 99px (medido). 297 Este bloque replica, para 992-1179px, las reglas que Webflow ya aplica bajo 298 991px al desplegar el menu. Revertir: borrar este embed entero y poner 299 "Collapse menu at" = medium en el Navbar. 300 Fuente en el repo: widgets/custom-code-header-colapso.css */ 301@media screen and (min-width:992px) and (max-width:1179px){.nav-pills-grupo{flex-direction:column;width:100%;background-color:transparent;border-radius:0;padding:0;row-gap:0}.nav-pill-item{width:100%;justify-content:center;font-size:1.125rem;padding:.75rem 0}.nav-pill-sep{display:none}.rl_menu-icon_line-middle{background-color:#000;justify-content:center;align-items:center;width:24px;height:2px;margin-top:6px;margin-bottom:6px;padding-bottom:0;padding-right:0;display:flex}.rl_navbar5_dropdown-toggle{flex-flow:row;justify-content:center;align-items:center;min-width:250px;padding:.75rem 0;font-size:1.125rem;display:flex}.rl_navbar5_dropdown-toggle.menu{width:100%;min-width:100%}.rl_navbar5_dropdown-toggle.menu.profile{justify-content:flex-end;align-items:center}.rl_navbar5_blog-item{width:100%}.rl_menu-icon_line-top{background-color:#000;width:24px;height:2px;padding-bottom:0;padding-right:0}.rl_menu-icon{flex-direction:column;justify-content:center;align-items:center;width:48px;height:48px;margin-right:-.5rem;padding-bottom:0;padding-right:0;display:flex}.rl_navbar5_dropdown-background-layer{border-radius:4px;min-width:100%}.rl_navbar5_dropdown-content-left{align-content:start;align-items:start;padding-left:0;padding-right:0}.rl_navbar5_large-item-content{width:100%}.rl_navbar5_dropdown-content{flex-direction:column}.rl_navbar5_menu-button{flex-flow:row}.rl_navbar5_menu-button.w--open{background-color:#0000}.rl_navbar5_blog-image{object-fit:cover}.rl_navbar5_blog-list{width:100%}.rl_menu-icon_line-middle-inner{width:4px;height:0;padding-bottom:0;padding-right:0}.rl-dropdown-icon{grid-column-gap:0px;grid-row-gap:0px;justify-content:center;align-items:center;inset:auto 0% auto auto}.rl_menu-icon_line-bottom{background-color:#000;width:24px;height:2px;padding-bottom:0;padding-right:0}.rl_navbar5_menu-right{flex-direction:column;grid-template-rows:auto auto;grid-template-columns:1fr;align-items:stretch;margin-top:1rem}.rl_navbar5_menu{-webkit-text-fill-color:inherit;background-color:#fff;background-clip:border-box;border-bottom:1px solid #000;align-items:flex-start;height:100vh;margin-left:0;padding:1rem 5% 15rem;position:absolute;overflow:auto}.rl_navbar5_dropdown-content-right{align-items:flex-start;max-width:none}.rl_navbar5_menu-left{flex-flow:column;justify-content:center;align-items:center}.rl_navbar5_menu-dropdown{flex-flow:column;width:100%;display:flex;position:relative}.rl_navbar5_dropdown-content-wrapper{width:100%}.drop-menu_entrenador{width:100%}.drop-menu_entrenador.profile{margin-left:0}.rl_navbar5_dropdown-list-copy{position:relative;overflow:hidden}.rl_navbar5_dropdown-list-copy.w--open{border-style:none;align-items:flex-start;min-width:auto;margin-bottom:1.5rem;padding:0 0 0;position:relative;top:0;left:0;right:0}} 302@media screen and (min-width:1180px){.w-nav[data-collapse=all] .w-nav-menu{display:flex}.w-nav[data-collapse=all] .w-nav-button{display:none}} 303</style></div></div><div class="w-layout-vflex flex-block-16"><section class="re-hero"><div class="re-hero__wrap"><div class="re-hero__content"><div class="re-hero__eyebrow"><span class="re-hero__dot"></span><span class="re-hero__eyebrow-text">Entrenadores respondiendo ahora</span></div><h1 class="re-hero__title">Pregunta a un entrenador personal certificado</h1><p class="re-hero__subtitle">Escribe tu duda y varios entrenadores certificados te responden, casi siempre el mismo dÃa. Sin registro.</p><a href="#formulario" class="re-hero__cta">Escribe tu pregunta gratis â</a></div><div class="w-embed w-script"><!-- RedEntrenadores â /pregunta-al-experto · hero en dos columnas + formulario en dos pasos 304 Rediseño 2026-08-19 (prototipo de Figma Make "Optimizar formulario cuestionario"). 305 306 DÃNDE VIVE: es el único HtmlEmbed de la landing (page 6908a45a566828a761f9d274) y está 307 dentro de `.re-hero__wrap`, justo después de `.re-hero__content` (antes estaba en su 308 propia sección `.re-form-section`, que ahora queda oculta en el Designer). 309 310 QUà PINTA: la tarjeta del formulario (columna derecha) y la fila de cifras (columna 311 izquierda, bajo el subtÃtulo). El eyebrow, el H1 y el subtÃtulo siguen siendo elementos 312 nativos de Webflow: aquà solo se les cambia el estilo. El CSS convierte `.re-hero__wrap` 313 en un grid de dos columnas y el `.w-embed` que envuelve este embed se hace `display: 314 contents` para que la tarjeta y las cifras sean items del grid. 315 316 DATOS: inserta en `preguntas` por la REST de Supabase con la clave publicable. No pide 317 categorÃa: llega como `sin-clasificar` y la asigna `avisar-pregunta-pendiente` al 318 recibir la pregunta (ver supabase/functions/avisar-pregunta-pendiente). Si el texto 319 pasa de 25 palabras se parte en `pregunta` (tÃtulo) + `detalle`, como hacÃa el 320 formulario antiguo con sus dos campos. 321 322 Al editar: cambiar este fichero Y el `code` del embed en Webflow (no se sincronizan). --> 323 324<div id="formulario" class="pae-card" data-paso="1"> 325 326 <!-- Paso 1: la pregunta --> 327 <form class="pae-paso pae-paso--1" id="pae-form-1" novalidate> 328 <label class="pae-card__titulo" for="pae-pregunta">¿Cuál es tu duda?</label> 329 <textarea id="pae-pregunta" class="pae-field pae-field--area" rows="4" 330 placeholder="EscrÃbela con tus palabras, aunque sea corta" autocomplete="off"></textarea> 331 <p class="pae-error" id="pae-error-1" role="alert"></p> 332 <button type="submit" class="pae-btn" id="pae-continuar">Continuar â</button> 333 </form> 334 335 <!-- Paso 2: quién pregunta --> 336 <form class="pae-paso pae-paso--2" id="pae-form-2" novalidate hidden> 337 <div class="pae-resumen"> 338 <p class="pae-resumen__texto" id="pae-resumen-texto"></p> 339 <button type="button" class="pae-link" id="pae-editar">Editar</button> 340 </div> 341 342 <label class="pae-label" for="pae-nombre">Tu nombre</label> 343 <input id="pae-nombre" class="pae-field" type="text" placeholder="ej. MarÃa" 344 autocomplete="given-name" maxlength="80" /> 345 <p class="pae-ayuda">Aparecerá junto a tu pregunta cuando se publique</p> 346 347 <label class="pae-label" for="pae-email">Tu email</label> 348 <input id="pae-email" class="pae-field" type="email" placeholder="[email protected]" 349 autocomplete="email" inputmode="email" maxlength="160" /> 350 <p class="pae-ayuda">Te avisamos aquà en cuanto haya respuesta. No se publica.</p> 351 352 <!-- Trampa para bots: nunca visible ni rellenable por una persona --> 353 <div class="pae-hp" aria-hidden="true"> 354 <label for="pae-web">Web</label> 355 <input id="pae-web" type="text" tabindex="-1" autocomplete="off" /> 356 </div> 357 358 <div class="pae-pasos"> 359 <p class="pae-pasos__titulo">Qué pasa ahora</p> 360 <ol class="pae-pasos__lista"> 361 <li><span>1.</span>Varios entrenadores certificados leen tu pregunta.</li> 362 <li><span>2.</span>Te avisamos por email en cuanto tengas respuestas.</li> 363 <li><span>
3633.</span>Publicamos tu pregunta y las respuestas solo con tu nombre, para que ayuden a otros.</li> 364 </ol> 365 </div> 366 367 <p class="pae-error" id="pae-error-2" role="alert"></p> 368 <button type="submit" class="pae-btn" id="pae-enviar">Enviar pregunta</button> 369 <p class="pae-legal">Al enviar aceptas nuestra <a href="https://www.redentrenadores.com/legal/politica-de-privacidad" target="_blank" rel="noopener">polÃtica de privacidad</a>.</p> 370 </form> 371 372 <!-- Enviada --> 373 <div class="pae-paso pae-paso--ok" id="pae-ok" hidden> 374 <div class="pae-ok__icono" aria-hidden="true"> 375 <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg> 376 </div> 377 <p class="pae-ok__titulo">¡Pregunta enviada!</p> 378 <p class="pae-ok__texto">La revisamos y, en cuanto esté publicada, te avisamos por email. Las respuestas suelen llegar el mismo dÃa.</p> 379 <button type="button" class="pae-link pae-link--lg" id="pae-otra">Hacer otra pregunta</button> 380 </div> 381</div> 382 383<!-- Cifras bajo el subtÃtulo (columna izquierda). Los valores del HTML son reales a 384 fecha de 2026-08-19; el JS los recalcula al cargar y solo pisa los que consigue. 385 El número va en su propio span para animarlo como un recuento. --> 386<div class="pae-stats" role="group" aria-label="Cifras del servicio"> 387 <div class="pae-stat"><span class="pae-stat__num"><span class="pae-stat__n" data-cifra="respuestas" data-valor="15">15</span><span class="pae-stat__unidad" data-unidad="respuestas"> respuestas</span></span><span class="pae-stat__txt">de media por pregunta</span></div> 388 <div class="pae-stat"><span class="pae-stat__num"><span class="pae-stat__n" data-cifra="horas" data-valor="3">3</span><span class="pae-stat__unidad" data-unidad="horas"> horas</span></span><span class="pae-stat__txt">hasta la primera respuesta</span></div> 389 <div class="pae-stat"><span class="pae-stat__num"><span class="pae-stat__n" data-cifra="entrenadores" data-valor="98">98</span></span><span class="pae-stat__txt">entrenadores certificados</span></div> 390</div> 391 392<style> 393 /* ---------- Hero: de bloque centrado sobre foto a dos columnas sobre blanco ---------- */ 394 section.re-hero { background: #fff; } 395 .re-hero .re-hero__wrap { 396 display: grid; 397 grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr); 398 /* La segunda fila absorbe lo que la tarjeta crezca (paso 2 es el doble de alta que 399 el 1): asà el texto de la izquierda se queda quieto y la tarjeta se centra sola. */ 400 grid-template-rows: auto 1fr; 401 align-items: start; 402 gap: 32px 64px; 403 max-width: 1152px; 404 margin: 0 auto; 405 padding: 56px 24px; 406 } 407 /* Webflow envuelve el embed en un div .w-embed: transparente para el grid, y sin los 408 pseudo-elementos de clearfix que Webflow le pone (serÃan items fantasma del grid). */ 409 .re-hero .re-hero__wrap > .w-embed { display: contents; } 410 .re-hero .re-hero__wrap > .w-embed::before, 411 .re-hero .re-hero__wrap > .w-embed::after { content: none; display: none; } 412 413 .re-hero .re-hero__content { grid-column: 1; grid-row: 1; align-self: start; max-width: none; text-align: left; } 414 .re-hero .pae-stats { grid-column: 1; grid-row: 2; align-self: start; } 415 .re-hero .pae-card { grid-column: 2; grid-row: 1 / span 2; align-self: center; } 416 417 .re-hero .re-hero__eyebrow { 418 display: inline-flex; align-items: center; gap: 8px; 419 background: #EAF3DE; border-radius: 999px; padding: 6px 12px; margin: 0 0 20px; 420 } 421 .re-hero .re-hero__dot { width: 8px; height: 8px; border-radius: 50%; background: #089F3B; animation: pae-pulso 2s cubic-bezier(.4, 0, .6, 1) infinite; } 422 .re-hero .re-hero__eyebrow-text { font-size: 12px; line-height: 16px; font-weight: 700; color: #089F3B; text-transform: none; letter-spacing: 0; } 423 @keyframes pae-pulso { 50% { opacity: .35; } } 424 425 .re-hero .re-hero__title { color: #1B1E22; text-align: left; font-size: 44px; line-height: 1.1; margin: 0 0 16px; } 426 .re-hero .re-hero__subtitle { color: #4B5563; text-align: left; font-size: 18px; line-height: 1.6; max-width: 520px; margin: 0; } 427 .re-hero .re-hero__cta { display: none; } /* el formulario ya está al lado: sobra la flecha */ 428 429 /* ---------- Cifras ---------- */ 430 .pae-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; } 431 .pae-stat { text-align: center; } 432 .pae-stat__num { display: block; font-size: 24px; line-height: 1.2; font-weight: 700; color: #089F3B; } 433 .pae-stat__n { font-variant-numeric: tabular-nums; } /* el recuento no baila de ancho */ 434 .pae-stat__txt { display: block; font-size: 13px; line-height: 18px; color: #6b7280; margin-top: 2px; } 435 436 /* ---------- Tarjeta ---------- */ 437 .pae-card { 438 background: #fff; border: 1px solid #d9dde2; border-radius: 12px; 439 box-shadow: 0 4px 14px rgba(0, 0, 0, .09); padding: 28px; box-sizing: border-box; 440 width: 100%; min-width: 0; scroll-margin-top: 90px; 441 } 442 .pae-card * { box-sizing: border-box; } 443 .pae-card__titulo { display: block; font-size: 16px; line-height: 22px; font-weight: 700; color: #1B1E22; margin: 0 0 14px; } 444 .pae-label { display: block; font-size: 14px; line-height: 20px; font-weight: 600; color: #1B1E22; margin: 0 0 6px; } 445 .pae-field { 446 width: 100%; border: 1px solid #d9dde2; border-radius: 8px; background: #fff; 447 padding: 11px 14px; font: inherit; font-size: 15px; line-height: 22px; color: #1B1E22; margin: 0; 448 } 449 .pae-field::placeholder { color: #6b7280; opacity: 1; } 450 .pae-field:focus { outline: none; border-color: #089F3B; box-shadow: 0 0 0 3px rgba(8, 159, 59, .15); } 451 .pae-field.is-invalid { border-color: #791F1F; } 452 .pae-field--area { resize: vertical; min-height: 112px; display: block; } 453 .pae-ayuda { font-size: 12px; line-height: 16px; color: #6b7280; margin: 6px 0 16px; } 454 .pae-error { font-size: 13px; line-height: 18px; color: #791F1F; margin: 0; min-height: 0; } 455 .pae-error:not(:empty) { margin: 8px 0 0; } 456 .pae-paso--1 .pae-error:not(:empty) { margin-bottom: 4px; } 457 458 /* Botón público (§51: 14px) con las mayúsculas que ya usan los CTA de esta página. */ 459 .pae-btn { 460 display: block; width: 100%; margin-top: 14px; padding: 13px 18px; border: none; border-radius: 8px; 461 background: #089F3B; color: #fff; cursor: pointer; font: inherit; font-size: 14px; font-weight: 700; 462 letter-spacing: .03em; text-transform: uppercase; line-height: 18px; transition: background .15s; 463 } 464 .pae-btn:hover { background: #067A2D; } 465 .pae-btn:disabled { opacity: .5;
465 cursor: not-allowed; } 466 /* Enlaces-botón con área de toque de 44px sin mover el texto (padding compensado con margen). */ 467 .pae-link { background: none; border: none; padding: 12px 8px; margin: -12px -8px; cursor: pointer; font: inherit; font-size: 13px; line-height: 20px; font-weight: 600; color: #089F3B; } 468 .pae-link:hover { color: #067A2D; text-decoration: underline; } 469 .pae-link:disabled { opacity: .5; cursor: not-allowed; text-decoration: none; } 470 .pae-link--lg { font-size: 14px; margin-top: -8px; } 471 472 .pae-resumen { display: flex; align-items: flex-start; gap: 12px; background: #F9FAFB; border-radius: 8px; padding: 12px 14px; margin: 0 0 18px; } 473 .pae-resumen__texto { flex: 1; min-width: 0; font-size: 14px; line-height: 20px; color: #1B1E22; margin: 0; max-height: 120px; overflow: auto; overflow-wrap: anywhere; } 474 .pae-resumen .pae-link { flex-shrink: 0; margin: -12px -8px -12px 0; } 475 476 .pae-hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; } 477 478 .pae-pasos { background: #EAF3DE; border-radius: 12px; padding: 14px 16px; margin: 2px 0 0; } 479 .pae-pasos__titulo { font-size: 12px; line-height: 16px; font-weight: 700; color: #089F3B; margin: 0 0 8px; } 480 .pae-pasos__lista { list-style: none; margin: 0; padding: 0; } 481 .pae-pasos__lista li { display: flex; gap: 8px; font-size: 14px; line-height: 20px; color: #1B1E22; margin: 0 0 6px; padding: 0; } 482 .pae-pasos__lista li:last-child { margin-bottom: 0; } 483 .pae-pasos__lista li span { font-weight: 700; color: #089F3B; flex-shrink: 0; } 484 485 .pae-legal { font-size: 12px; line-height: 16px; color: #6b7280; text-align: center; margin: 10px 0 0; } 486 .pae-legal a { color: #4B5563; text-decoration: underline; } 487 488 .pae-paso--ok { text-align: center; padding: 16px 0 8px; } 489 .pae-ok__icono { width: 56px; height: 56px; border-radius: 50%; background: #EAF3DE; color: #089F3B; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; } 490 .pae-ok__titulo { font-size: 20px; line-height: 26px; font-weight: 700; color: #1B1E22; margin: 0 0 8px; } 491 .pae-ok__texto { font-size: 14px; line-height: 21px; color: #4B5563; margin: 0 auto 12px; max-width: 360px; } 492 493 /* ---------- Responsive ---------- */ 494 @media (max-width: 991px) { 495 .re-hero .re-hero__wrap { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto; gap: 28px; padding: 40px 20px; } 496 .re-hero .re-hero__content { grid-row: 1; align-self: auto; } 497 .re-hero .pae-stats { grid-row: 2; align-self: auto; } 498 .re-hero .pae-card { grid-column: 1; grid-row: 3; align-self: auto; } 499 .re-hero .re-hero__title { font-size: 36px; } 500 .re-hero .re-hero__subtitle { max-width: none; } 501 } 502 @media (max-width: 767px) { 503 .pae-field { font-size: 16px; } /* por debajo de 16px iOS hace zoom al enfocar */ 504 .pae-card { padding: 22px 18px; } 505 } 506 @media (max-width: 600px) { 507 /* Por debajo de ~550px "15 respuestas" no cabe en un tercio de columna: cada cifra 508 pasa a ser una fila con número y texto seguidos. */ 509 .pae-stats { grid-template-columns: minmax(0, 1fr); gap: 6px; } 510 .pae-stat { display: flex; align-items: baseline; justify-content: center; gap: 8px; flex-wrap: wrap; } 511 .pae-stat__num { font-size: 20px; } 512 .pae-stat__txt { font-size: 13px; line-height: 18px; margin-top: 0; } 513 } 514 @media (max-width: 479px) { 515 .re-hero .re-hero__wrap { padding: 28px 16px 36px; gap: 24px; } 516 .re-hero .re-hero__title { font-size: 30px; } 517 .re-hero .re-hero__subtitle { font-size: 16px; } 518 } 519 @media (prefers-reduced-motion: reduce) { 520 .re-hero .re-hero__dot { animation: none; } 521 } 522</style> 523
524<script> 525(function () { 526 var SUPABASE_URL = 'https://yrzanatzshdpvypazgeb.supabase.co'; 527 var SUPABASE_KEY = 'sb_publishable_O4cIYIJcDs3_GcbjNHdd9g_e2cQKOUD'; 528 var CABECERAS = { apikey: SUPABASE_KEY, Authorization: 'Bearer ' + SUPABASE_KEY }; 529 var MAX_PALABRAS_TITULO = 25; // el mismo tope que tenÃa el campo "Tu pregunta" antiguo 530 var MAX_SLUG = 90; 531 var RE_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 532 // Marcas combinantes (tildes sueltas tras normalizar a NFD): el rango se construye con 533 // fromCharCode a propósito, sin secuencias de escape unicode en el fuente, porque el 534 // canal con el que se escribe el embed en Webflow no las transporta bien. 535 var MARCAS_COMBINANTES = new RegExp('[' + String.fromCharCode(0x300) + '-' + String.fromCharCode(0x36f) + ']', 'g'); 536 537 var card = document.getElementById('formulario'); 538 if (!card) return; 539 var $ = function (id) { return document.getElementById(id); }; 540 var form1 = $('pae-form-1'), form2 = $('pae-form-2'), ok = $('pae-ok'); 541 var preguntaEl = $('pae-pregunta'), nombreEl = $('pae-nombre'), emailEl = $('pae-email'), hpEl = $('pae-web'); 542 var error1 = $('pae-error-1'), error2 = $('pae-error-2'); 543 var btnContinuar = $('pae-continuar'), btnEnviar = $('pae-enviar'); 544 545 /* ---- medición (Amplitude; el head del sitio deja un stub hasta el consentimiento) ---- */ 546 var vistos = {}; 547 function medir(evento, props) { 548 var datos = props || {}; 549 datos.pagina = location.pathname; 550 try { if (window.amplitude && window.amplitude.track) window.amplitude.track(evento, datos); } catch (e) {} 551 } 552 function medirPaso(paso) { 553 if (vistos[paso]) return; 554 vistos[paso] = true; 555 medir('pae_paso_visto', { paso: paso }); 556 } 557 558 /* ---- helpers ---- */ 559 function contarPalabras(t) { t = t.trim(); return t ? t.split(/\s+/).length : 0; } 560 561 function slugify(texto) {
562 var s = texto.normalize('NFD').replace(MARCAS_COMBINANTES, '') 563 .toLowerCase().trim() 564 .replace(/[^a-z0-9\s-]/g, '').replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); 565 if (s.length > MAX_SLUG) { 566 var corte = s.lastIndexOf('-', MAX_SLUG); 567 s = s.slice(0, corte > 20 ? corte : MAX_SLUG); 568 } 569 // Sin letras latinas (cirÃlico, solo emojis, solo signos) el slug quedarÃa vacÃo y la 570 // página no se podrÃa publicar: se le da uno provisional que se puede retocar al moderar. 571 return s || 'pregunta-' + Date.now().toString(36); 572 } 573 574 // El tÃtulo de la página es `pregunta` tal cual, asà que no puede ser un párrafo entero. 575 // Si el texto pasa del tope: se corta en el último "?" que quede dentro de las primeras 576 // 25 palabras (con al menos 3 delante) o, si no lo hay, en la palabra 25. El resto va a 577 // `detalle`, que la ficha pinta debajo del tÃtulo. 578 function partirPregunta(texto) { 579 var limpio = texto.replace(/\s+/g, ' ').trim(); 580 var palabras = limpio.split(' '); 581 if (palabras.length <= MAX_PALABRAS_TITULO) return { pregunta: limpio, detalle: null }; 582 583 var cabeza = palabras.slice(0, MAX_PALABRAS_TITULO).join(' '); 584 var corte = -1, i = -1; 585 while ((i = cabeza.indexOf('?', i + 1)) !== -1) { 586 if (contarPalabras(cabeza.slice(0, i)) >= 3) corte = i + 1; 587 } 588 var titulo = corte > 0 ? cabeza.slice(0, corte) : cabeza; 589 var resto = limpio.slice(titulo.length).trim(); 590 return { pregunta: titulo.trim(), detalle: resto || null }; 591 } 592 593 function mostrar(paso) { 594 form1.hidden = paso !== 1; 595 form2.hidden = paso !== 2; 596 ok.hidden = paso !== 'ok'; 597 card.setAttribute('data-paso', String(paso)); 598 medirPaso(paso); 599 } 600 601 function marcar(el, mal) { el.classList.toggle('is-invalid', !!mal); } 602 603 /* ---- paso 1 â 2 ---- */ 604 form1.addEventListener('submit', function (ev) { 605 ev.preventDefault(); 606 var texto = preguntaEl.value.trim(); 607 if (contarPalabras(texto) < 3) { 608 error1.textContent = texto ? 'Escribe tu pregunta un poco más completa.' : 'Escribe tu pregunta antes de continuar.'; 609 marcar(preguntaEl, true); 610 preguntaEl.focus(); 611 medir('pae_datos_invalidos', { campo: 'pregunta' }); 612 return; 613 } 614 error1.textContent = ''; 615 marcar(preguntaEl, false); 616 $('pae-resumen-texto').textContent = texto; 617 mostrar(2); 618 medir('pae_continuar', { palabras: contarPalabras(texto) }); 619 nombreEl.focus(); 620 }); 621 preguntaEl.addEventListener('input', function () { if (error1.textContent) { error1.textContent = ''; marcar(preguntaEl, false); } }); 622 623 /* ---- volver a editar ---- */ 624 $('pae-editar').addEventListener('click', function () { 625 if (enviando) return; // con el envÃo en vuelo, volver atrás duplicarÃa la pregunta 626 mostrar(1); 627 medir('pae_editar'); 628 preguntaEl.focus(); 629 }); 630 631 /* ---- paso 2: enviar ---- */ 632 var enviando = false; 633 form2.addEventListener('submit', function (ev) { 634 ev.preventDefault(); 635 if (enviando) return; 636 var nombre = nombreEl.value.trim(); 637 var email = emailEl.value.trim(); 638 marcar(nombreEl, false); marcar(emailEl, false); 639 640 if (!nombre) { 641 error2.textContent = 'Escribe tu nombre.'; 642 marcar(nombreEl, true); nombreEl.focus(); 643 medir('pae_datos_invalidos', { campo: 'nombre' }); 644 return; 645 } 646 if (!RE_EMAIL.test(email)) { 647 error2.textContent = 'Introduce un email válido.'; 648 marcar(emailEl, true); emailEl.focus(); 649 medir('pae_datos_invalidos', { campo: 'email' }); 650 return; 651 } 652 error2.textContent = ''; 653 654 var partes = partirPregunta(preguntaEl.value); 655 656 // Bot: se le enseña el éxito y no se guarda nada. 657 if (hpEl && hpEl.value) { mostrar('ok'); return; } 658 659 enviando = true; 660 btnEnviar.disabled = true; 661 $('pae-editar').disabled = true; 662 var textoBtn = btnEnviar.textContent; 663 btnEnviar.textContent = 'Enviandoâ¦'; 664 665 fetch(SUPABASE_URL + '/rest/v1/preguntas', { 666 method: 'POST', 667 headers: Object.assign({ 'Content-Type': 'application/json', Prefer: 'return=minimal' }, CABECERAS), 668 body: JSON.stringify({ 669 nombre_usuario: nombre, 670 pregunta: partes.pregunta, 671 detalle: partes.detalle, 672 // La asigna el servidor al recibir la pregunta (avisar-pregunta-pendiente). 673 categoria: 'sin-clasificar', 674 categoria_publica: null, 675 slug: slugify(partes.pregunta), 676 email_usuario: email 677 }) 678 }).then(function (r) { 679 if (!r.ok) throw new Error('HTTP ' + r.status); 680 medir('pae_pregunta_enviada', { palabras: contarPalabras(preguntaEl.value), con_detalle: !!partes.detalle }); 681 mostrar('ok'); 682 }).catch(function (e) { 683 console.error('No se pudo guardar la pregunta:', e);
684 error2.textContent = 'No hemos podido enviar tu pregunta. Inténtalo de nuevo en unos segundos.'; 685 medir('pae_envio_fallido'); 686 }).then(function () { 687 enviando = false; 688 btnEnviar.disabled = false; 689 $('pae-editar').disabled = false; 690 btnEnviar.textContent = textoBtn; 691 }); 692 }); 693 nombreEl.addEventListener('input', function () { if (error2.textContent) { error2.textContent = ''; marcar(nombreEl, false); } }); 694 emailEl.addEventListener('input', function () { if (error2.textContent) { error2.textContent = ''; marcar(emailEl, false); } }); 695 696 /* ---- otra pregunta ---- */ 697 $('pae-otra').addEventListener('click', function () { 698 preguntaEl.value = ''; nombreEl.value = ''; emailEl.value = ''; 699 error1.textContent = ''; error2.textContent = ''; 700 vistos = {}; 701 mostrar(1); 702 preguntaEl.focus(); 703 }); 704 705 medirPaso(1); 706 707 /* ---- cifras: recuento animado + valores reales (los del HTML son el respaldo) ---- */ 708 var REDUCIR_MOVIMIENTO = !!(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches); 709 var enCurso = {}; 710 711 // Lleva el número de una cifra desde lo que muestra ahora hasta `objetivo`, como un 712 // contador rápido (1,2 s, frenando al final). Con "reducir movimiento" va directo. 713 function animarCifra(clave, objetivo) { 714 var el = document.querySelector('.pae-stat__n[data-cifra="' + clave + '"]'); 715 if (!el || !(objetivo >= 0)) return; 716 var desde = parseInt(el.getAttribute('data-actual') || '0', 10) || 0; 717 el.setAttribute('data-valor', String(objetivo)); 718 if (REDUCIR_MOVIMIENTO || !window.requestAnimationFrame) { 719 el.textContent = String(objetivo); el.setAttribute('data-actual', String(objetivo)); 720 return; 721 } 722 if (enCurso[clave]) cancelAnimationFrame(enCurso[clave]); 723 var inicio = null, DURACION = 1200; 724 function paso(t) { 725 if (inicio === null) inicio = t; 726 var p = Math.min((t - inicio) / DURACION, 1); 727 var suave = 1 - Math.pow(1 - p, 3); 728 var v = Math.round(desde + (objetivo - desde) * suave); 729 el.textContent = String(v); 730 el.setAttribute('data-actual', String(v)); 731 if (p < 1) enCurso[clave] = requestAnimationFrame(paso); 732 } 733 enCurso[clave] = requestAnimationFrame(paso); 734 } 735 function pintarUnidad(clave, texto) { 736 var u = document.querySelector('.pae-stat__unidad[data-unidad="' + clave + '"]'); 737 if (u) u.textContent = texto; 738 } 739 function plural(n, uno, varios) { return n === 1 ? uno : varios; } 740 741 // Arranca el recuento (de 0 al valor del HTML) cuando las cifras entran en pantalla. 742 function arrancarRecuento() { 743 var nums = document.querySelectorAll('.pae-stat__n'); 744 for (var i = 0; i < nums.length; i++) { 745 var objetivo = parseInt(nums[i].getAttribute('data-valor'), 10); 746 nums[i].setAttribute('data-actual', '0'); 747 animarCifra(nums[i].getAttribute('data-cifra'), objetivo); 748 } 749 } 750 var stats = document.querySelector('.pae-stats'); 751 if (stats && 'IntersectionObserver' in window && !REDUCIR_MOVIMIENTO) { 752 var obs = new IntersectionObserver(function (entradas) { 753 if (entradas.some(function (e) { return e.isIntersecting; })) { obs.disconnect(); arrancarRecuento(); } 754 }, { threshold: 0.3 }); 755 obs.observe(stats); 756 } else if (stats) { 757 arrancarRecuento(); 758 } 759 760 function mediana(nums) { 761 if (!nums.length) return null; 762 var s = nums.slice().sort(function (a, b) { return a - b; }); 763 var m = s.length >> 1; 764 return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; 765 } 766 767 // Preguntas aprobadas con la fecha de cada respuesta: de ahà salen la media de 768 // respuestas y las horas hasta la primera. La RLS solo devuelve preguntas con respuesta. 769 fetch(SUPABASE_URL + '/rest/v1/preguntas?select=id,created_at,respuestas(created_at)&aprobada=eq.true&limit=1000', { headers: CABECERAS }) 770 .then(function (r) { return r.ok ? r.json() : []; }) 771 .then(function (filas) { 772 var conRespuestas = (filas || []).filter(function (p) { return p.respuestas && p.respuestas.length; }); 773 if (!conRespuestas.length) return; 774 var total = conRespuestas.reduce(function (n, p) { return n + p.respuestas.length; }, 0); 775 var media = Math.round(total / conRespuestas.length); 776 if (media >= 1) { animarCifra('respuestas', media); pintarUnidad('respuestas', plural(media, ' respuesta', ' respuestas')); } 777 778 var horas = conRespuestas.map(function (p) { 779 var primera = Math.min.apply(null, p.respuestas.map(function (r) { return new Date(r.created_at).getTime(); })); 780 return (primera - new Date(p.created_at).getTime()) / 36e5; 781 }).filter(function (h) { return isFinite(h) && h >= 0; }); 782 var med = mediana(horas); 783 if (med !== null) { 784 var h = Math.max(1, Math.round(med)); 785 animarCifra('horas', h); 786 pintarUnidad('horas', med < 1 ? ' hora o menos' : plural(h, ' hora', ' horas')); 787 } 788 }) 789 .catch(function () {}); 790 791 fetch(SUPABASE_URL + '/rest/v1/entrenadores_publico?select=id&limit=1', { 792 method: 'HEAD', 793 headers: Object.assign({ Prefer: 'count=exact' }, CABECERAS) 794 }) 795 .then(function (r) { 796 var rango = r.headers.get('content-range') || ''; 797 var n = parseInt(rango.split('/')[1], 10); 798 if (n > 0) animarCifra('entrenadores', n); 799 }) 800 .catch(function () {}); 801})(); 802</script>
802 803</div><div class="w-embed w-script"><!-- RedEntrenadores â /pregunta-al-experto · pop-up de confirmación tras enviar la pregunta 804 805 DÃNDE VIVE: segundo HtmlEmbed de la landing, hermano del formulario 806 (`widget-pregunta-al-experto.html`) dentro de `.re-hero__wrap`. Va DESPUÃS de él: 807 su JS busca en el DOM la tarjeta que pinta el otro. 808 809 QUà HACE: cuando la pregunta se ha guardado de verdad, el formulario deja su 810 tarjeta en el estado "enviada" y este embed abre encima un pop-up que lo confirma 811 y ofrece el recomendador. Es el único momento en que se tiene toda la atención de 812 quien pregunta, y quien acaba de escribir una duda de entrenamiento es justo a 813 quien le sirve que le propongan entrenador. 814 815 CÃMO SE ENTERA: mirando cuándo `#pae-ok` deja de estar oculto. Ese id y su `hidden` 816 son el contrato con el otro embed; van juntos aunque estén en dos ficheros. Se hace 817 asÃ, y no con una llamada directa, para no tener que reescribir el embed del 818 formulario â27 KB que hoy funcionanâ solo para colgarle un pop-up. 819 820 EL RECOMENDADOR: `window.reRecomendador.abrir()`, el gancho manual del bloque 3 de 821 `widget-popup-recomendador.html`, que vive en el Footer V1 y carga también aquÃ. En 822 esta ruta ese pop-up no sale por su cuenta âestá en RUTAS_EXCLUIDASâ, pero desde el 823 25/09/2026 sà se monta y deja el gancho disponible (lo hace el parche 824 `widget-popup-recomendador-gancho-manual.html`, también en el Footer V1). Si no 825 estuviera, el botón lleva al buscador en vez de quedarse muerto. 826 827 Al editar: cambiar este fichero Y el `code` del embed en Webflow (no se sincronizan). --> 828 829<div class="pae-modal" id="pae-modal" hidden> 830 <div class="pae-modal__caja" role="dialog" aria-modal="true" aria-labelledby="pae-modal-titulo"> 831 <button type="button" class="pae-modal__cerrar" id="pae-modal-x" aria-label="Cerrar"> 832 <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg> 833 </button> 834 835 <div class="pae-modal__icono" aria-hidden="true"> 836 <svg viewBox="0 0 24 24" width="30" height="30" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg> 837 </div> 838 <h2 class="pae-modal__titulo" id="pae-modal-titulo">Ya hemos enviado tu pregunta</h2> 839 <p class="pae-modal__texto">Varios entrenadores certificados la están leyendo. Te avisamos por email en cuanto tengas respuestas: suelen llegar el mismo dÃa.</p> 840 841 <div class="pae-modal__oferta"> 842 <p class="pae-modal__oferta-titulo">Mientras llegan las respuestas</p> 843 <p class="pae-modal__oferta-texto">Contesta tres preguntas rápidas y te ponemos en contacto con los entrenadores que mejor encajan contigo. Gratis y sin compromiso.</p> 844 <button type="button" class="pae-btn pae-modal__cta" id="pae-modal-recomendador">Encontrar mi entrenador</button> 845 </div> 846 847 <button type="button" class="pae-link pae-modal__no" id="pae-modal-no">Ahora no</button> 848 </div> 849</div> 850 851<style> 852 /* `.pae-btn` y `.pae-link` los define el embed del formulario: los botones de aquà 853 son los mismos y se heredan tal cual. Lo propio de este bloque es el `pae-modal__*`. */ 854 855 /* Por debajo del recomendador (10000): al pulsar el CTA este se cierra y el otro se 856 abre encima, y durante esa décima de segundo el orden tiene que ser este. */ 857 .pae-modal { 858 position: fixed; inset: 0; z-index: 9990; 859 display: flex; align-items: center; justify-content: center; 860 padding: 20px; 861 background: rgba(27, 30, 34, .55); 862 /* `dvh` mide el viewport que se ve de verdad en móvil; el `vh` es el respaldo. */ 863 height: 100vh; height: 100dvh; 864 overflow-y: auto; 865 animation: pae-modal-fundido .18s ease-out; 866 } 867 .pae-modal[hidden] { display: none; } 868 .pae-modal__caja { 869 position: relative; 870 width: 100%; max-width: 440px; 871 box-sizing: border-box; 872 background: #fff; border-radius: 12px; 873 box-shadow: 0 18px 44px rgba(0, 0, 0, .22); 874 padding: 32px 28px 24px; 875 text-align: center; 876 animation: pae-modal-sube .22s cubic-bezier(.22, 1, .36, 1); 877 } 878 .pae-modal__caja * { box-sizing: border-box; } 879 .pae-modal__cerrar { 880 position: absolute; top: 10px; right: 10px; 881 width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; 882 background: none; border: none; border-radius: 8px; cursor: pointer; color: #6b7280; 883 } 884 .pae-modal__cerrar:hover { background: #F9FAFB; color: #1B1E22; } 885 .pae-modal__icono { 886 width: 60px; height: 60px; border-radius: 50%; 887 background: #EAF3DE; color: #089F3B; 888 display: flex; align-items: center; justify-content: center; 889 margin: 0 auto 16px; 890 } 891 .pae-modal__titulo { font-size: 22px; line-height: 28px; font-weight: 700; color: #1B1E22; margin: 0 0 8px; } 892 .pae-modal__texto { font-size: 14px; line-height: 21px; color: #4B5563; margin: 0 auto 20px; max-width: 340px; } 893 .pae-modal__oferta { background: #EAF3DE; border-radius: 12px; padding: 18px 18px 20px; text-align: left; } 894 .pae-modal__oferta-titulo { font-size: 12px; line-height: 16px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: #089F3B; margin: 0 0 6px; } 895 .pae-modal__oferta-texto { font-size: 14px; line-height: 20px; color: #1B1E22; margin: 0; } 896 .pae-modal__cta { margin-top: 14px; } 897 .pae-modal__no { display: block; margin: 14px auto 0; color: #6b7280; } 898 .pae-modal__no:hover { color: #4B5563; } 899 @keyframes pae-modal-fundido { from { opacity: 0; } } 900 @keyframes pae-modal-sube { from { opacity: 0; transform: translateY(14px); } } 901 902 @media (max-width: 479px) { 903 .pae-modal { padding: 12px; padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); align-items: flex-end; } 904 .pae-modal__caja { padding: 26px 18px 20px; } 905 .pae-modal__titulo { font-size: 20px;
905 line-height: 26px; } 906 } 907 @media (prefers-reduced-motion: reduce) { 908 .pae-modal, .pae-modal__caja { animation: none; } 909 } 910</style> 911
912<script> 913(function () { 914 var modal = document.getElementById('pae-modal'); 915 var ok = document.getElementById('pae-ok'); 916 if (!modal || !ok) return; // sin el embed del formulario delante, aquà no hay nada que hacer 917 918 var $ = function (id) { return document.getElementById(id); }; 919 var focoPrevio = null; 920 921 /* Medición (Amplitude; el head del sitio deja un stub hasta el consentimiento). Los 922 nombres siguen la familia `pae_*` del formulario para que el embudo se lea seguido. */ 923 function medir(evento, props) { 924 var datos = props || {}; 925 datos.pagina = location.pathname; 926 try { if (window.amplitude && window.amplitude.track) window.amplitude.track(evento, datos); } catch (e) {} 927 } 928 929 function abrir() { 930 if (!modal.hidden) return; 931 focoPrevio = document.activeElement; 932 modal.hidden = false; 933 document.body.style.overflow = 'hidden'; 934 var cta = $('pae-modal-recomendador'); 935 if (cta) cta.focus(); 936 medir('pae_popup_enviada_visto'); 937 } 938 939 // `motivo` "recomendador" es el único que no devuelve el foco: justo después se abre 940 // el otro pop-up y es él quien decide dónde tiene que estar. 941 function cerrar(motivo) { 942 if (modal.hidden) return; 943 modal.hidden = true; 944 document.body.style.overflow = ''; 945 medir('pae_popup_cerrado', { motivo: motivo || 'cerrar' }); 946 if (motivo === 'recomendador') return; 947 // El foco vuelve a la tarjeta, que es donde ha quedado la confirmación. 948 var destino = $('pae-otra') || focoPrevio; 949 if (destino && destino.focus) destino.focus(); 950 } 951 952 // El recomendador vuelve a pedir nombre y email en su último paso. Están escritos 953 // ahà al lado, en el formulario que se acaba de enviar: se los damos hechos. 954 function prellenarRecomendador() { 955 try { 956 var estado = window.__reRecNucleo && window.__reRecNucleo.estado; 957 if (!estado) return; 958 var nombre = $('pae-nombre'), email = $('pae-email'); 959 if (!estado.nombre && nombre) estado.nombre = nombre.value.trim(); 960 if (!estado.email && email) estado.email = email.value.trim(); 961 } catch (e) {} 962 } 963 964 modal.addEventListener('click', function (ev) { if (ev.target === modal) cerrar('fondo'); }); 965 $('pae-modal-x').addEventListener('click', function () { cerrar('aspa'); }); 966 $('pae-modal-no').addEventListener('click', function () { cerrar('ahora-no'); }); 967 document.addEventListener('keydown', function (ev) { 968 if (ev.key === 'Escape' && !modal.hidden) cerrar('escape'); 969 }); 970 971 $('pae-modal-recomendador').addEventListener('click', function () { 972 var rec = window.reRecomendador; 973 var disponible = !!(rec && rec.abrir); 974 medir('pae_recomendador_pulsado', { disponible: disponible }); 975 cerrar('recomendador'); 976 if (disponible) { 977 prellenarRecomendador(); 978 rec.abrir(); 979 return; 980 } 981 window.location.href = '/encuentra-entrenador'; 982 }); 983 984 /* El disparo: `#pae-ok` es el bloque "¡Pregunta enviada!" del embed del formulario y 985 solo pierde su `hidden` cuando la fila ya está guardada en `preguntas`. Vigilarlo es 986 lo que permite dejar aquel embed sin tocar. Si algún dÃa el formulario emite un 987 evento propio, este observer se cambia por él y el resto se queda igual. */ 988 new MutationObserver(function () { 989 if (!ok.hidden) abrir(); 990 }).observe(ok, { attributes: true, attributeFilter: ['hidden'] }); 991})(); 992</script>
992 993</div></div></section><section class="re-cats"><h2 class="re-cats__title">Preguntas por categorÃa</h2><ul role="list" class="re-cats__list"><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=perdida-peso" class="re-cats__link"><div class="re-cats__icon-1"></div>Pérdida de peso</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=entrenamiento-funcional" class="re-cats__link"><div class="re-cats__icon-2"></div>Entrenamiento funcional</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=recuperacion-lesiones" class="re-cats__link"><div class="re-cats__icon-3"></div>Recuperación de lesiones</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=recuperacion-posparto" class="re-cats__link"><div class="re-cats__icon-4"></div>Recuperación posparto</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=prevencion-lesiones" class="re-cats__link"><div class="re-cats__icon-5"></div>Prevención de lesiones</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=aumento-masa-muscular" class="re-cats__link"><div class="re-cats__icon-6"></div>Aumento de masa muscular</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=movilidad-flexibilidad" class="re-cats__link"><div class="re-cats__icon-7"></div>Movilidad y flexibilidad</a></li><li class="re-cats__item"><a href="/pregunta-al-experto?categoria=preparacion-oposiciones" class="re-cats__link"><div class="re-cats__icon-8"></div>Preparación de oposiciones</a></li></ul></section><div class="w-layout-vflex general-seccion max-width"><div id="otras-preguntas" class="re-oq-section"><div class="otras_preguntas-header"><h2 class="re-oq-title">¿Tienes prisa? Encuentra tu respuesta al instante</h2></div><div class="w-dyn-list"><div role="list" class="re-oq-grid w-dyn-items"><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-reducir-la-barriga-entrenando-6-dias-por-semana" class="re-oq-card w-inline-block"><div class="re-oq-hidden">perdida-peso</div><div class="re-oq-tag">Pérdida de peso</div><div class="re-oq-question">Cómo reducir la barriga entrenando 6 dÃas por semana</div><div class="re-oq-detail w-richtext">Mucha panza y hago gimnasia 6 veces por semana 45 minutos por dÃa</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Ovidio</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-volver-a-entrenar-a-los-7-meses-del-parto-con-tripa-floja" class="re-oq-card w-inline-block"><div class="re-oq-hidden">recuperacion-posparto</div><div class="re-oq-tag">Recuperación posparto</div><div class="re-oq-question">Cómo volver a entrenar a los 7 meses del parto</div><div class="re-oq-detail w-richtext">hola, tuve a mi hijo hace 7 meses y quiero volver a entrenar pero me noto la tripa muy floja. que ejercicios puedo hacer para empezar? merece la pena un entrenador especializado en postparto o con videos de youtube me vale? gracias</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Jana</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/entrenar-pesas-todos-los-dias-sin-descanso-es-malo" class="re-oq-card w-inline-block"><div class="re-oq-hidden">entrenamiento-funcional</div><div class="re-oq-tag">Entrenamiento funcional</div><div class="re-oq-question">Entrenar pesas todos los dÃas sin descanso: ¿es malo?</div><div class="re-oq-detail w-richtext">Buenas, una duda. Hace unos meses que entreno y ahora estoy yendo al gym todos los dÃas a hacer pesas, sin descansar ningun dÃa. Me encuentro bien y voy progresando, pero he leido en varios sitios que hay que descansar minimo 1 o 2 dias a la semana. Hacer pesas todos los dÃas es realmente malo o depende de como lo organizes?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Dolo</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-combinar-entrenamiento-de-fuerza-e-hipertrofia-en-la-misma-rutina" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">Cómo combinar entrenamiento de fuerza e hipertrofia en la misma rutina</div><div class="re-oq-detail w-richtext">Para alguien que trabaja la hipertrofia pero que también quiere trabajar fuerza , ¿Quue le recomendarÃas ? ¿Ajustar pesos de máquinas pesados y enfocarse en rangos de repeticiones de 5 a 6 , o directamente hacer un planning de dÃas especÃficos y ejercicios especÃficos diferentes a los dÃas de hipertrofia? Gracias de antemano!</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Karim</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-ganar-peso-y-masa-muscular-combinando-gimnasio-y-baloncesto" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">
993Cómo ganar peso y masa muscular combinando gimnasio y baloncesto</div><div class="re-oq-detail w-richtext">Soy una persona que peso muy muy poco y mido 1,80 y me gustaria subir de peso y masa muscular .hago baloncesto 3 dias a la semana y me gustaria ir al gimasion 5 haciendo en un mismo dia gym y baloncesto pero no estoy descansando nada ¿que hago?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Iker</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/cuantas-calorias-comer-al-dia-para-ganar-musculo-sin-perder-definicion" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">Cuántas calorÃas comer al dÃa para ganar músculo sin perder definición</div><div class="re-oq-detail w-richtext">Hola peso 58-59 kg y mido 1.54 y quiero aumenta masa muscular sà que tengo algo poco y tengo aún un poco de grasa llevo menos de año en el gym y quiero aumentar brazosarcas espalda y sobretodo tren inferior entonces quiero saber cuántas kcal debo comer al dÃa para aumentar glúteos y tal pero seguir definida</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Bea</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/estancamiento-muscular-en-pecho-y-hombros-tras-8-meses" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">Estancamiento muscular en pecho y hombros tras 8 meses</div><div class="re-oq-detail w-richtext">Llevo 8 meses entrenando 4 dias a la semana y he dejado de ganar musculo, sobre todo en pecho y hombros. Deveria cambiar de rutina o es que estoy comiendo poco?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">JP</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/cuando-volver-a-correr-tras-un-esguince-de-tobillo" class="re-oq-card w-inline-block"><div class="re-oq-hidden">recuperacion-lesiones</div><div class="re-oq-tag">Recuperación de lesiones</div><div class="re-oq-question">Cuándo volver a correr tras un esguince de tobillo</div><div class="re-oq-detail w-richtext">Me doblé el tobillo jugando al padel hace tres semanas. Ya casi no me duele al andar, pero lo noto inestable cuando bajo escaleras, puedo volver a correr o me arriesgo a recaer?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Iago</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/merece-la-pena-un-entrenador-online-si-solo-tengo-esterilla-y-gomas" class="re-oq-card w-inline-block"><div class="re-oq-hidden">entrenamiento-funcional</div><div class="re-oq-tag">Entrenamiento funcional</div><div class="re-oq-question">¿Merece la pena un entrenador online entrenando en casa con gomas?</div><div class="re-oq-detail w-richtext">Trabajo desde casa y tengo huecos sueltos entre reuniones asi que ir al gimnasio a una hora fija no me cuadra nada. He estado mirando lo del entrenador online pero en casa solo tengo una esterilla y unas gomas, no tengo espacio para mas cosas la verdad. Lo que me echa para atras es pagar por algo que igual me lo puedo bajar gratis de internet, y ademas por videollamada nadie te corrige la tecnica digo yo. Que diferencia hay de verdad entre pagar un entrenador online y seguir una rutina de internet? y se puede avanzar solo con gomas o al final necesitas pesas si o si? Gracias de anteman</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Rafa</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/me-da-tiempo-a-arreglar-la-trepa-de-cuerda-para-bombero-en-mayo" class="re-oq-card w-inline-block"><div class="re-oq-hidden">preparacion-oposiciones</div><div class="re-oq-tag">Preparación de oposiciones</div><div class="re-oq-question">Cómo entrenar la trepa de cuerda para la oposición de bombero</div><div class="re-oq-detail w-richtext">Hola, voy a por bombero y llevo un año preparandome por mi cuenta. El problema que tengo es que en la prueba de press banca me quedo corto de repeticiones y en la de trepa de cuerda directamente no subo, o subo con las piernas pero eso creo que ahi no vale. En natacion voy justo tambien. Yo entreno en un gimnasio normal de barrio, hago pecho lunes, espalda miercoles y pierna viernes, lo tipico, pero me da la sensacion de que estoy entrenando como para ponerme fuerte y no como para pasar la prueba, no se si me explico. Lo que quiero saber es si un entrenador personal normal me sirve o tengo que buscar a alguien que sepa especificamente de opos de bombero, porque los que he mirado son de fitness y no se si sabran de esto. Y si de aqui a la convocatoria de mayo me da tiempo a arreglar lo de la cuerda. Gracias</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Filo</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/que-mejoro-en-mi-entreno-con-mancuernas-para-volver-a-bajar-de-peso" class="re-oq-card w-inline-block"><div class="re-oq-hidden">perdida-peso</div><div class="re-oq-tag">Pérdida de peso</div><div class="re-oq-question">Cómo mejorar el entrenamiento con mancuernas en casa para perder peso</div><div class="re-oq-detail w-richtext">43años, 1,82 m, 84/85 kg. Llevo 9 meses entrenando en casa 2/3 dÃas por semana con mancuernas (3 series, superior/inferior alternos, series de 12 con 14 kg, y una de 16 para peso muerto y extensiones). Sin abdominales ni banco andaba 15km semanales y baje a 82 kg pero entre el calor y una mala racha llevo un mes parado y he rebotado. Tomaba 3 g de creatina, como 1.200 kcal diarias y no duelo dormir mucho. que mejorarias en entreno y en bajada de peso?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Juanma</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/mi-rutina-de-5-dias-sirve-para-ganar-volumen-en-piernas" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">¿Sirve una rutina de 5 dÃas para ganar volumen en piernas?</div><div class="re-oq-detail w-richtext">Hola o/ Tengo 1 semana y media en el gym, primera vez en mi vida. Peso 66 kilos y mido 1,63. Mi meta es sacar volumen en piernas y bajar algo de grasa en general. Llevo dieta sana hace 2 meses (sin galletas, papitas ni refresco) y solo con eso baje 3 kilos. Ahora entreno 5 dias a la semana con entrenador, rutina 4x12: pierna-brazo-espalda, piern
993a-gluteo, pecho-brazo, pierna. Hago mi calentamiento por mi cuenta con videos segun el area del dia, y el entrenador me pone 2 series mas. Al final hago 20 min de cardio y estiramientos yo sola. Mi duda es: ¿esta rutina y el calentamiento que hago estan bien para lo que quiero (piernas + bajar grasa), o le falta algo?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Mary</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-tomar-creatina-monohidrato-a-diario-tambien-en-dias-sin-entrenar" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">Cómo tomar creatina monohidrato a diario, también en dÃas sin entrenar</div><div class="re-oq-detail w-richtext">Creatina monohidrato, cómo se lleva en el dÃa a dÃa? Sé lo básico (3-5g, sin carga) pero tengo dudas prácticas: se toma también los dÃas sin entrenar? da molestias digestivas al principio? vale la pena el sello Creapure o cualquier marca sirve? se nota mucho la retención de agua?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">JR</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/mi-brazo-no-engorda" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Ganar masa muscular</div><div class="re-oq-question">Cómo ganar volumen en los brazos</div><div class="re-oq-detail w-richtext">No es un brazo flácido, tiene silueta de un buen brazo, pero no es nada ancho/no tiene volumen (por decirlo de alguna forma) y no se que hacer para que cambie 994 995He cambiado agarres, hecho un dÃa especifico para el brazo, priorizarlos al inicio de la rutina y aun asà no cambia 996 997Normalmente para triceps hago 4 series de extensión, 2 de press francés y 2 de unilateral tras nuca (o como se llame), para bÃceps 4 predicador y 2 martillos, para hombro 4 laterales y 2 press militar 998 999Qué hago o cambio? Mi torso (sin ser algo espectacular) se ve mucho mas grande que mis brazos</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Fear</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/puedo-perder-peso-pero-manteniendo-mi-figura-rellenita-como-esque-soy-gordita-y-quiero-seguir-asi-pero-bajar-algo-de-peso-120kg-peso-actualmente" class="re-oq-card w-inline-block"><div class="re-oq-hidden">perdida-peso</div><div class="re-oq-tag">Pérdida de peso</div><div class="re-oq-question">Cómo perder peso sin dejar de tener una figura rellenita</div><div class="re-oq-detail w-richtext">soy gordita y quiero seguir asÃ, pero bajar algo de peso... 120kg peso actualmente... Tengo inseguridades por culpa de la gente... El tema es que quiero seguir siendo rellenita, una bolita tierna de grasa, pero quiero perder algo de kilos, al menos llegar a 60-70kg pero sin cambiar mi fÃsico...</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Shadow</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/ejercicios-para-mejorar-la-condicion-fisica" class="re-oq-card w-inline-block"><div class="re-oq-hidden">perdida-peso</div><div class="re-oq-tag">Pérdida de peso</div><div class="re-oq-question">Ejercicios en casa para mejorar la condición fÃsica con sobrepeso</div><div class="re-oq-detail w-richtext">actualmente tengo sobre peso, mido 1.80 y ando Serca de los 130 kilos, quiero comenzar hacer ejercicio e ir al gim pero hace poco tuve un choque y no e tenido la solvencia económica por estos pagos, en ese Inter que ejercicios recomendarÃan para agarrar condición y posteriormente ir al gimnasio.</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Gio</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/dejar-el-gym" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Ganar masa muscular</div><div class="re-oq-question">Dejar el gimnasio por falta de motivación tras dos años entrenando</div><div class="re-oq-detail w-richtext">Todos los principios de mes me hago la misma pregunta, deberÃa dejarlo? Ya no siento ganas de ir, pero al mismo tiempo siento q soy esclavo de mi fÃsico, no estoy como para el olympia, pero estoy mejor q nunca, y todos me halagan por eso, cosa q me molestarÃa perder, me desmotiva el hecho de q hace frio, no puedo darme el lujo de comer bien las proteÃnas necesarias, y me consume energÃa que puedo usar en otras cosas, deberÃa descansar un mes? DeberÃa cambiarme de gym? Entreno hace mas de 2 años, y me gustarÃa recibir el consejo de alguien q haya estado en esta situación</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Juan</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/como-afecta-el-ciclo-menstrual-al-entreno-de-fuerza" class="re-oq-card w-inline-block"><div class="re-oq-hidden">entrenamiento-funcional</div><div class="re-oq-tag">Entrenamiento funcional</div><div class="re-oq-question">Cómo afecta el ciclo menstrual al entrenamiento de fuerza</div><div class="re-oq-detail w-richtext">¿Como afecta el ciclo menstrual al entreno de fuerza?</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Anna</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/si-hago-2-dias-a-la-semana-pierna-y-gluteo-los-dos-dias-debo-de-hacer-la-misma-rutina" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">¿Hay que hacer la misma rutina de pierna dos dÃas por semana?</div><div class="re-oq-detail w-richtext">Me he estado preguntando eso, ya que veo que hay gente que tiene una rutina para cada dia pero quiero saber si se progresa con una sola rutina los dos dias. Yo hago ejercicio en casa con mancuernas y no tengo a quien preguntarle.</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Gisela</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div><div role="listitem" class="w-dyn-item"><a href="/pregunta-al-experto/necesito-ayuda-con-las-rutinas" class="re-oq-card w-inline-block"><div class="re-oq-hidden">aumento-masa-muscular</div><div class="re-oq-tag">Aumento de masa muscular</div><div class="re-oq-question">Cómo armar tu propia rutina de entrenamiento en el gimnasio</div><div class="re-oq-detail w-richtext">He estado entrenando casi por 1 año ya pero estuve siempre en smartfit, ahora como no me queda ninguno cerca pasare a uno más clasico por asi decirlo, quisiera que me ayudaran a como buscar informacion sobre como hacer mis rutinas, tengo la rutina en pdf que me mandaba mi coach anterior pero no quiero depender completamente de una rutina asi, si me pudieran guiar sobre como ir buscando o armando las rutinas lo agradeceria mucho.</div><div data-oq-toggle="true" class="re-oq-toggle">Ver más</div><div class="re-oq-footer"><div class="re-oq-autor">Medhi</div><div data-oq-ver="true" class="re-oq-ver">Leer respuesta del entrenador â</div></div></a></div></div></div></div></div><section class="re-cta"><h2 class="re-cta__title">¿No encuentras la respuesta que buscas?</h2><p class="re-cta__subtitle">
999Haz tu pregunta gratis y un entrenador personal certificado te responderá en menos de 24h</p><a href="#formulario" class="re-cta__btn">Haz tu pregunta gratis</a></section><section class="re-faq"><h2 class="re-faq__title">Preguntas frecuentes sobre el servicio</h2><div class="re-faq__list"><details class="re-faq__item re-faq__item"><summary class="re-faq__q re-faq__q">¿Cuánto cuesta preguntar a un entrenador personal?</summary><p class="re-faq__a">Es completamente gratis. Puedes hacer tantas preguntas como quieras sin ningún coste ni compromiso de contratación.</p></details><details class="re-faq__item re-faq__item"><summary class="re-faq__q re-faq__q">¿En cuánto tiempo responden los entrenadores?</summary><p class="re-faq__a">El tiempo medio de respuesta es de menos de 24 horas, aunque puede variar según la categorÃa y disponibilidad.</p></details><details class="re-faq__item re-faq__item"><summary class="re-faq__q re-faq__q">¿Quién responde las preguntas?</summary><p class="re-faq__a">Entrenadores personales certificados verificados por RedEntrenadores, activos en la plataforma.</p></details><details class="re-faq__item re-faq__item"><summary class="re-faq__q re-faq__q">¿Puedo hacer una pregunta de forma anónima?</summary><p class="re-faq__a">SÃ, no es necesario compartir datos personales para publicar tu pregunta.</p></details><details class="re-faq__item re-faq__item"><summary class="re-faq__q re-faq__q">¿Puedo contactar directamente con el entrenador que me respondió?</summary><p class="re-faq__a">SÃ, desde la respuesta puedes ver el perfil del entrenador y contactar con él si quieres profundizar.</p></details></div></section></div><footer class="footer"><div class="footer-middle footer-v1"><div class="footer-grid"><a id="w-node-_5855e537-ce68-c8ac-151a-51c4bee40e4e-1e51f0f5" href="#" class="w-inline-block"><img src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px.avif" alt="Logotipo de RedEntrenadores" sizes="100vw" srcset="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px-p-500.avif 500w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px-p-800.avif 800w, https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/68c5a7f45b0c2bd091e87d5c_RedEntrenadores_Cropped_400px.avif 2246w" class="footer-logo"/></a><div class="w-layout-vflex"><div>Encuentra entrenador personal para alcanzar tus objetivos en cualquier ciudad de España</div><div class="space_36"></div><div><strong>SÃguenos</strong></div><div class="social-grid"><a href="https://twitter.com/redentrenadores" target="_blank" class="social-link-icon w-inline-block"><div class="social-icon-font">î ©</div></a><a href="https://www.instagram.com/redentrenadores" target="_blank" class="social-link-icon w-inline-block"><div class="social-icon-font">î </div></a><a href="https://facebook.com/redentrenadores" target="_blank" class="social-link-icon w-inline-block"><div class="social-icon-font">î </div></a></div><div><strong>EscrÃbenos si necesitas ayuda.</strong></div><a href="mailto:[email protected]" class="footer-link">[email protected]</a></div><div class="w-layout-vflex"><h4 class="h4-footer">Páginas</h4><a href="/sobre-nosotros" class="footer-link">Nosotros</a><a href="/blog" class="footer-link">Blog</a><a href="/donde-entrenar" class="footer-link">Dónde entrenar</a><a href="/calculadoras/precio-entrenador-personal" class="footer-link">Cuánto cuesta un entrenador</a><a href="/calculadoras/donde-te-conviene-entrenar" class="footer-link">¿Dónde te conviene entrenar?</a><a href="/calculadoras/plan-primer-mes" class="footer-link">Tu primer mes de entrenamiento</a><a href="/calculadoras/calculadora-calorias" class="footer-link">Calculadora de calorÃas</a><a href="/calculadoras/calculadora-proteina" class="footer-link">Calculadora de proteÃna</a><a href="/calculadoras/calculadora-imc" class="footer-link">Calculadora de IMC</a><a href="/calculadoras/test-nivel-fitness" class="footer-link">Test de nivel de fitness</a><a href="/calculadoras/pruebas-fisicas-oposiciones" class="footer-link">Pruebas fÃsicas de oposición</a><a href="/herramientas" class="footer-link">Todas las herramientas</a><a href="https://www.redentrenadores.com/entrenamiento/online" class="footer-link">Entrenador personal online</a><a href="https://www.redentrenadores.com/entrenamiento/a-domicilio" class="footer-link">Entrenador personal a domicilio</a><a href="https://www.redentrenadores.com/entrenamiento/en-un-gimnasio" class="footer-link">Entrenador personal en gimnasio</a><a href="https://www.redentrenadores.com/entrenamiento/en-exteriores" class="footer-link">Entrenador personal al aire libre</a><a href="https://www.redentrenadores.com/entrenamiento/presencial" class="footer-link">Entrenador personal presencial</a><a href="/usuarios/mi-cuenta" class="footer-link">Ãrea de usuario</a><a href="/legal/politica-de-privacidad" class="footer-link">PolÃtica de privacidad</a><a href="/legal/terminos-y-condiciones" class="footer-link">Términos y condiciones</a><a href="/legal/aviso-legal" class="footer-link">Aviso legal</a></div><div class="w-layout-vflex"><h4 class="h4-footer">Entrenadores por ciudad</h4><a href="https://www.redentrenadores.com/entrenadores-personales/barcelona" class="footer-link">Entrenadores personales Barcelona</a><a href="https://www.redentrenadores.com/entrenadores-personales/madrid" class="footer-link">Entrenadores personales Madrid</a><a href="https://www.redentrenadores.com/entrenadores-personales/valencia" class="footer-link">Entrenadores personales Valencia</a><a href="https://www.redentrenadores.com/entrenadores-personales/malaga" class="footer-link">Entrenadores personales Málaga</a><a href="https://www.redentrenadores.com/entrenadores-personales/zaragoza" class="footer-link">Entrenadores personales Zaragoza</a><a href="https://www.redentrenadores.com/entrenadores-personales/sevilla" class="footer-link">Entrenadores personales Sevilla</a><a href="https://www.redentrenadores.com/entrenadores-personales/granada" class="footer-link">Entrenadores personales Granada</a><a href="https://www.redentrenadores.com/entrenadores-personales/alicante" class="footer-link">Entrenadores personales Alicante</a><a href="https://www.redentrenadores.com/entrenadores-personales/getafe" class="footer-link">Entrenadores personales Getafe</a><a href="https://www.redentrenadores.com/entrenadores-personales/alcala-de-henares" class="footer-link">Entrenadores personales Alcalá de Henares</a><a href="https://www.redentrenadores.com/entrenadores-personales/pozuelo-de-alarcon" class="footer-link">Entrenadores personales Pozuelo de Alarcón</a><a href="https://www.redentrenadores.com/entrenadores-personales/terrassa" class="footer-link">Entrenadores personales Terrassa</a><a href="https://www.redentrenadores.com/entrenadores-personales/badalona" class="footer-link">Entrenadores personales Badalona</a></div><div class="w-layout-vflex"><h4 class="h4-footer">Entrenadores por objetivo</h4>
999<a href="https://www.redentrenadores.com/objetivo/movilidad-flexibilidad" class="footer-link">Movilidad y flexibilidad</a><a href="https://www.redentrenadores.com/objetivo/prevencion-lesiones" class="footer-link">Prevención de lesiones</a><a href="https://www.redentrenadores.com/objetivo/preparacion-oposiciones" class="footer-link">Preparación de oposiciones</a><a href="https://www.redentrenadores.com/objetivo/mejora-forma-fisica" class="footer-link">Mejora de forma fÃsica</a><a href="https://www.redentrenadores.com/objetivo/aumento-masa-muscular" class="footer-link">Aumento de masa muscular</a><a href="https://www.redentrenadores.com/objetivo/entrenamiento-funcional" class="footer-link">Entrenamiento funcional</a><a href="https://www.redentrenadores.com/objetivo/recuperacion-lesiones" class="footer-link">Recuperación de lesiones</a><a href="https://www.redentrenadores.com/objetivo/preparacion-deportistas" class="footer-link">Preparación para deportistas</a><a href="https://www.redentrenadores.com/objetivo/correccion-postural" class="footer-link">Corrección postural</a><a href="https://www.redentrenadores.com/objetivo/perdida-peso" class="footer-link">Pérdida de peso</a><a href="https://www.redentrenadores.com/objetivo/recuperacion-posparto" class="footer-link">Recuperación posparto</a></div></div></div><div class="footer-bottom-copy"><div id="w-node-_2744af48-cdd0-c61e-6264-4fdb1e51f15a-1e51f0f5" class="text-block-14">© 2026 RedEntrenadores. Todos los derechos reservados.</div></div><div class="footer-bottom"><a href="/legal/politica-de-privacidad" class="footer-link xs">PolÃtica de Privacidad</a><a href="/legal/aviso-legal" class="footer-link xs">Aviso Legal</a><a href="/legal/terminos-y-condiciones" class="footer-link xs">Términos y condiciones</a></div><div class="w-embed"><style> 1000 /* Namespace propio `re-rec-*` en TODO. El sitio ya tiene `.re-modal`, 1001 `.re-cta` y `.re-field` en las landings y en el listado, y este bloque 1002 carga en las mismas páginas: reutilizar esos nombres es garantizarse un 1003 choque de estilos el dÃa que alguien toque cualquiera de los dos. */ 1004 #re-rec-overlay { 1005 position: fixed; 1006 inset: 0; 1007 /* En móvil `inset: 0` mide el viewport GRANDE â el de la barra del 1008 navegador escondida â, asà que el borde de abajo del overlay cae por 1009 debajo de lo que se ve. Como en móvil el modal se ancla justo ahà 1010 (bottom sheet), el último campo y el botón de enviar quedaban tapados 1011 por la barra del navegador, sin forma de llegar a ellos: el modal ya 1012 está en su tope de scroll interno y lo que sobra queda fuera de 1013 pantalla. `dvh` mide el viewport visible de verdad; el `vh` de arriba 1014 es el respaldo para navegadores sin `dvh`, que se quedan como estaban. 1015 El teclado no lo arregla ninguna unidad CSS â de eso se encarga 1016 `ajustarAlViewport()` en el bloque 3. */ 1017 height: 100vh; 1018 height: 100dvh; 1019 z-index: 10000; 1020 display: none; 1021 align-items: center; 1022 justify-content: center; 1023 padding: 20px; 1024 background: rgba(27, 30, 34, 0.55); 1025 font-family: 'Plus Jakarta Sans', sans-serif; 1026 box-sizing: border-box; 1027 } 1028 #re-rec-overlay.re-rec-abierto { display: flex; } 1029 1030 #re-rec-overlay .re-rec-modal { 1031 position: relative; 1032 width: 100%; 1033 max-width: 520px; 1034 max-height: 90vh; 1035 overflow-y: auto; 1036 /* Que el scroll del modal no arrastre la página de detrás al llegar al 1037 final: en iOS el encadenado hace que el sheet parezca que "se va". */ 1038 overscroll-behavior: contain; 1039 background: #fff; 1040 border-radius: 12px; 1041 box-shadow: 0 4px 14px rgba(0, 0, 0, 0.09); 1042 padding: 28px; 1043 box-sizing: border-box; 1044 /* El paso de confirmación es bastante más ancho que las preguntas (tres 1045 fichas en fila); se ensancha con una clase en vez de dejar el modal 1046 ancho todo el rato, que harÃa que las 4 preguntas naden en el hueco. */ 1047 } 1048 #re-rec-overlay .re-rec-modal.re-rec-ancho { max-width: 640px; } 1049 1050 #re-rec-overlay .re-rec-cerrar { 1051 position: absolute; 1052 top: 10px; 1053 right: 10px; 1054 width: 44px; 1055 height: 44px; 1056 border: 0; 1057 background: none; 1058 color: #6b7280; 1059 font-size: 20px; 1060 line-height: 1; 1061 cursor: pointer; 1062 } 1063 #re-rec-overlay .re-rec-cerrar:hover { color: #1B1E22; } 1064 1065 #re-rec-overlay .re-rec-dots { display: flex; gap: 6px; margin-bottom: 18px; } 1066 #re-rec-overlay .re-rec-dots span { 1067 width: 26px; 1068 height: 4px; 1069 border-radius: 999px; 1070 background: #d9dde2; 1071 } 1072 #re-rec-overlay .re-rec-dots span.re-rec-hecho { background: #089F3B; } 1073 1074 #re-rec-overlay .re-rec-eyebrow { 1075 font-size: 12px; 1076 font-weight: 700; 1077 letter-spacing: 0.48px; 1078 text-transform: uppercase; 1079 color: #089F3B; 1080 margin: 0 0 6px; 1081 } 1082 #re-rec-overlay h3 { 1083 font-family: 'Barlow', sans-serif; 1084 font-size: 24px; 1085 line-height: 1.25; 1086 font-weight: 700; 1087 color: #1B1E22; 1088 margin: 0 0 6px; 1089 padding-right: 34px; 1090 } 1091 #re-rec-overlay .re-rec-sub { 1092 font-size: 16px; 1093 line-height: 1.6; 1094 color: #4B5563; 1095 margin: 0 0 18px; 1096 } 1097 1098 #re-rec-overlay .re-rec-pregunta { 1099 font-size: 14px; 1100 font-weight: 600; 1101 color: #1B1E22; 1102 margin: 0 0 8px; 1103 } 1104 1105 /* Opciones: botón grande de fila completa. Objetivo táctil holgado (48px) 1106 por el punto 13 del Apéndice A del design system â el código nuevo no 1107 deberÃa nacer ya con deuda de accesibilidad. */ 1108 #re-rec-overlay .re-rec-opciones { display: grid; gap: 8px; } 1109 #re-rec-overlay .re-rec-op { 1110 display: flex; 1111 align-items: center; 1112 gap: 12px; 1113 width: 100%; 1114 min-height: 48px; 1115 padding: 10px 14px; 1116 text-align: left; 1117 background: #fff; 1118 border: 1px solid #d9dde2; 1119 border-radius: 8px; 1120 font-family: inherit; 1121 font-size: 16px; 1122 font-weight: 500; 1123 color: #1B1E22; 1124 cursor: pointer; 1125 box-sizing: border-box; 1126 } 1127 #re-rec-overlay .re-rec-op:hover { border-color: #089F3B; background: #EAF3DE; } 1128 #re-rec-overlay .re-rec-op.re-rec-op-on { border-color: #089F3B; background: #EAF3DE; color: #067A2D; font-weight: 600; } 1129 #re-rec-overlay .re-rec-op-txt { flex: 1; min-width: 0; } 1130 #re-rec-overlay .re-rec-op-nota { display: block; font-size: 13px; font-weight: 400; color: #6b7280; margin-top: 2px; } 1131 1132 /* El icono va en su propia pastilla verde clara y no suelto: suelto se 1133 pierde contra el blanco de la fila, y es lo que hace que la lista se lea 1134 de un vistazo en vez de leerse renglón a renglón. Al pasar por encima el 1135 fondo de la fila se vuelve `#EAF3DE`, el mismo de la pastilla, asà que 1136 ahà la pastilla invierte a verde macizo para no desaparecer. */ 1137 #re-rec-overlay .re-rec-op-ico { 1138 display: flex; 1139 align-items: center; 1140 justify-content: center; 1141 width: 38px; 1142 height: 38px; 1143 flex-shrink: 0; 1144 border-radius: 10px; 1145 background: #EAF3DE; 1146 color: #089F3B; 1147 } 1148 #re-rec-overlay .re-rec-op-ico svg { width: 21px; height: 21px; display: block; } 1149 #re-rec-overlay .re-rec-op:hover .re-rec-op-ico, 1150 #re-rec-overlay .re-rec-op.re-rec-op-on .re-rec-op-ico { background: #089F3B; color: #fff; } 1151 1152 #re-rec-overlay .re-rec-op-flecha { 1153 width: 17px; 1154 height: 17px; 1155 flex-shrink: 0; 1156 color: #C9CED4; 1157 } 1158 #re-rec-overlay .re-rec-op:hover .re-rec-op-flecha, 1159 #re-rec-overlay .re-rec-op.re-rec-op-on .re-rec-op-flecha { color: #089F3B; } 1160 1161 /* "Ver más opciones" no es una opción más: va sin caja para que no compita 1162 con las dos que sà son elegibles. */ 1163 #re-rec-overlay .re-rec-vermas { 1164 justify-self: start; 1165 min-height: 44px; 1166 padding: 4px 0; 1167 background: none; 1168 border: 0; 1169 cursor: pointer; 1170 font-family: inherit; 1171 font-size: 14px; 1172 font-weight: 600; 1173 color: #089F3B; 1174 text-decoration: underline; 1175 } 1176 #re-rec-overlay .re-rec-vermas:hover { color: #067A2D; } 1177 1178 #re-rec-overlay .re-rec-campo { margin-bottom: 14px; } 1179 /* La ciudad aparece debajo de las opciones de modalidad, no en su propia 1180 pantalla: necesita aire por arriba para no leerse como una quinta opción. */ 1181 #re-rec-overlay .re-rec-campo-ciudad { margin-top: 18px; margin-bottom: 0; } 1182 #re-rec-overlay .re-rec-campo label { 1183 display: block; 1184 font-size: 14px; 1185 font-weight: 600; 1186 color: #1B1E22; 1187 margin-bottom: 6px; 1188 } 1189 #re-rec-overlay .re-rec-campo input { 1190 width: 100%; 1191 min-height: 48px; 1192 padding: 12px 14px; 1193 border: 1px solid #d9dde2; 1194 border-radius: 8px; 1195 font-family: inherit; 1196 font-size: 16px; 1197 color: #1B1E22; 1198 background: #fff; 1199 box-sizing: border-box; 1200 } 1201 /* 16px en los inputs y no 14: por debajo de 16 Safari en iPhone hace zoom 1202 al enfocar el campo y descoloca el modal entero. */ 1203 #re-rec-overlay .re-rec-campo input:focus { 1204 outline: none; 1205 border-color: #089F3B; 1206 box-shadow: 0 0 0 3px rgba(8, 159, 59, 0.15); 1207 } 1208 #re-rec-overlay .re-rec-opcional { font-weight: 400; color: #6b7280; } 1209 1210 #re-rec-overlay .re-rec-btn { 1211 display: inline-block; 1212 border: 0; 1213 border-radius: 8px; 1214 background: #089F3B; 1215 color: #fff; 1216 font-family: inherit; 1217 font-size: 16px; 1218 font-weight: 600; 1219 padding: 13px 22px; 1220 min-height: 48px; 1221 cursor: pointer; 1222 } 1223 #re-rec-overlay .re-rec-btn:hover { background: #067A2D; } 1224 #re-rec-overlay .re-rec-btn:disabled { opacity: 0.5;
1224 cursor: not-allowed; } 1225 1226 #re-rec-overlay .re-rec-nav { display: flex; align-items: center; gap: 14px; margin-top: 12px; } 1227 #re-rec-overlay .re-rec-atras { 1228 background: none; 1229 border: 0; 1230 padding: 12px 0; 1231 cursor: pointer; 1232 font-family: inherit; 1233 font-size: 14px; 1234 font-weight: 600; 1235 color: #4B5563; 1236 text-decoration: underline; 1237 } 1238 #re-rec-overlay .re-rec-atras:hover { color: #067A2D; } 1239 1240 #re-rec-overlay .re-rec-error { 1241 display: none; 1242 margin: 12px 0 0; 1243 font-size: 14px; 1244 color: #791F1F; 1245 background: #FCEBEB; 1246 border-radius: 8px; 1247 padding: 10px 12px; 1248 } 1249 #re-rec-overlay .re-rec-legal { 1250 margin: 12px 0 0; 1251 font-size: 12px; 1252 line-height: 1.45; 1253 color: #6b7280; 1254 } 1255 #re-rec-overlay .re-rec-legal a { color: #6b7280; text-decoration: underline; } 1256 1257 /* El pie: lo que antes era el subtÃtulo de arriba. Va separado por un filete 1258 y en cuerpo pequeño â es contexto para quien duda, no la instrucción de la 1259 pantalla, que ahora es el tÃtulo más las propias opciones. */ 1260 #re-rec-overlay .re-rec-pie { 1261 display: flex; 1262 align-items: flex-start; 1263 gap: 9px; 1264 margin: 18px 0 0; 1265 padding-top: 14px; 1266 border-top: 1px solid #E5E6E8; 1267 font-size: 13px; 1268 line-height: 1.5; 1269 color: #6b7280; 1270 } 1271 #re-rec-overlay .re-rec-pie-ico { color: #089F3B; flex-shrink: 0; line-height: 0; } 1272 #re-rec-overlay .re-rec-pie-ico svg { width: 17px; height: 17px; display: block; } 1273 1274 /* ---------------------------------------------------------- confirmación */ 1275 1276 #re-rec-overlay .re-rec-fichas { 1277 display: grid; 1278 grid-template-columns: repeat(3, minmax(0, 1fr)); 1279 gap: 12px; 1280 margin: 18px 0 0; 1281 } 1282 /* Sin `:hover` ni cursor de mano: la ficha no es clicable (ver fichaHTML), y 1283 un borde que reacciona al pasar por encima promete un clic que no existe. */ 1284 #re-rec-overlay .re-rec-ficha { 1285 display: block; 1286 text-align: center; 1287 border: 1px solid #d9dde2; 1288 border-radius: 12px; 1289 padding: 14px 10px; 1290 color: inherit; 1291 } 1292 /* El envoltorio existe solo para anclar el badge: la foto es un <img> y no 1293 puede tener hijos. Se lleva el margen que antes tenÃa la foto. */ 1294 #re-rec-overlay .re-rec-foto-wrap { 1295 position: relative; 1296 display: block; 1297 width: 96px; 1298 margin: 0 auto 10px; 1299 } 1300 /* La foto crece dentro de la misma tarjeta: es lo único de la ficha que se 1301 reconoce de un vistazo, y a 64px competÃa con el texto en vez de mandar.
1302 Cuadrada y no circular: a igual ancho, el cÃrculo recorta los laterales y 1303 se come parte de la cara. Las esquinas van a 8px, el mismo radio que los 1304 botones â no a 12px, que es el de la tarjeta que la contiene. */ 1305 #re-rec-overlay .re-rec-foto { 1306 width: 100%; 1307 height: 96px; 1308 border-radius: 8px; 1309 object-fit: cover; 1310 display: block; 1311 background: #F3F3F3; 1312 } 1313 1314 /* Badge "Solo online" encima de la foto, arriba a la izquierda, como en el 1315 resto de tarjetas del sitio. Solo el icono: a 96px ây a 64px en móvilâ el 1316 literal no cabe, y ya sale escrito como primer dato de la ficha, justo 1317 debajo. El texto lo llevan title y aria-label. Blanco con borde y sombra, 1318 no verde: informa de la modalidad, no premia nada (§50). */ 1319 #re-rec-overlay .re-rec-online { 1320 position: absolute; 1321 top: 6px; 1322 left: 6px; 1323 z-index: 2; 1324 display: inline-flex; 1325 align-items: center; 1326 justify-content: center; 1327 width: 22px; 1328 height: 22px; 1329 box-sizing: border-box; 1330 background: #fff; 1331 border: 1px solid #d9dde2; 1332 border-radius: 999px; 1333 box-shadow: 0 2px 6px rgba(0,0,0,.18); 1334 color: #4B5563; 1335 } 1336 #re-rec-overlay .re-rec-online svg { width: 13px; height: 13px; display: block; } 1337 #re-rec-overlay .re-rec-ficha-nombre { 1338 font-family: 'Barlow', sans-serif; 1339 font-size: 15px; 1340 font-weight: 700; 1341 color: #1B1E22; 1342 display: block; 1343 line-height: 1.25; 1344 } 1345 /* PÃldora pública: radio 999px, nunca los 12px de la pÃldora de panel. */ 1346 #re-rec-overlay .re-rec-pill { 1347 display: inline-block; 1348 margin-top: 6px; 1349 padding: 4px 10px; 1350 border-radius: 999px; 1351 background: #F3F4F6; 1352 color: #4B5563; 1353 font-size: 11px; 1354 font-weight: 600; 1355 line-height: 1.3; 1356 } 1357 #re-rec-overlay .re-rec-pill-match { background: #EAF3DE; color: #089F3B; } 1358 #re-rec-overlay .re-rec-ficha-meta { 1359 font-size: 12px; 1360 line-height: 1.4; 1361 color: #4B5563; 1362 display: block; 1363 margin-top: 5px; 1364 } 1365 #re-rec-overlay .re-rec-estrella { vertical-align: -1px; } 1366 1367 #re-rec-overlay .re-rec-p2 { margin-top: 22px; padding-top: 18px; border-top: 1px solid #d9dde2; } 1368 #re-rec-overlay .re-rec-p2 h4 { 1369 font-family: 'Barlow', sans-serif; 1370 font-size: 18px; 1371 font-weight: 700; 1372 color: #1B1E22; 1373 margin: 0 0 4px; 1374 } 1375 /* minmax(0, 1fr) y no 1fr: el mÃnimo "auto" de un <select> es el ancho de su 1376 opción más larga, asà que con 1fr las columnas no encogen y desbordan. */ 1377 #re-rec-overlay .re-rec-p2-grid { 1378 display: grid; 1379 grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); 1380 gap: 0 14px; 1381 } 1382 #re-rec-overlay .re-rec-p2-grid > div { min-width: 0; } 1383 #re-rec-overlay .re-rec-p2-full { grid-column: 1 / -1; } 1384 #re-rec-overlay .re-rec-campo select, 1385 #re-rec-overlay .re-rec-campo textarea { 1386 width: 100%; 1387 min-height: 48px; 1388 padding: 12px 14px; 1389 border: 1px solid #d9dde2; 1390 border-radius: 8px; 1391 font-family: inherit; 1392 font-size: 16px; 1393 color: #1B1E22; 1394 background: #fff; 1395 box-sizing: border-box; 1396 } 1397 #re-rec-overlay .re-rec-campo textarea { min-height: 70px; resize: vertical; } 1398 #re-rec-overlay .re-rec-acciones { display: flex; align-items: center; gap: 18px; margin-top: 8px; } 1399 #re-rec-overlay .re-rec-saltar { 1400 background: none; 1401 border: 0; 1402 padding: 12px 0; 1403 cursor: pointer; 1404 font-family: inherit; 1405 font-size: 14px; 1406 font-weight: 600; 1407 color: #4B5563; 1408 text-decoration: underline; 1409 } 1410 1411 @media (max-width: 560px) { 1412 #re-rec-overlay { 1413 padding: 12px; 1414 /* La barra de gestos del iPhone se come los últimos ~34px: sin esto el 1415 pie del modal queda por debajo de ella. */ 1416 padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px)); 1417 align-items: flex-end; 1418 } 1419 /* `100%` es el alto del overlay ya descontado su padding, o sea lo que de 1420 verdad se ve. Antes era `92vh`, que se mide contra el viewport grande y 1421 dejaba el pie del modal fuera de pantalla. */ 1422 #re-rec-overlay .re-rec-modal { padding: 22px 18px; max-height: 100%; } 1423 1424 /* Con el teclado abierto el modal se queda en ~350px de alto y el 1425 contenido no cabe de ninguna manera: al escribir la ciudad, el botón 1426 quedaba por debajo del borde del modal. Se veÃa cortado y no habÃa nada 1427 que indicara que el modal tiene scroll propio, asà que el paso parecÃa 1428 roto. Pegado abajo, el botón está siempre a la vista y lo que scrolla es 1429 lo de arriba. Solo en móvil: en escritorio no hay teclado que robe media 1430 pantalla y el modal casi nunca desborda. 1431 1432 El fondo blanco es obligatorio âel contenido pasa por detrásâ y el 1433 padding de abajo replica el del modal, porque `bottom: 0` ancla contra 1434 el borde del scrollport y no contra su padding; el margen negativo lo 1435 descuenta para que en reposo, sin pegar, nada se mueva de sitio. */ 1436 #re-rec-overlay .re-rec-nav { 1437 position: sticky; 1438 bottom: 0; 1439 background: #fff; 1440 padding-bottom: 22px; 1441 margin-bottom: -22px; 1442 /* El padding de arriba se descuenta del margen para que en reposo la 1443 separación siga siendo la misma. La sombra es lo que hace que, cuando 1444 hay contenido pasando por detrás, la barra se lea como barra y no como 1445 un campo cortado a medias; a esta opacidad no se nota cuando la barra 1446 está en su sitio y no tapa nada. */ 1447 padding-top: 10px; 1448 margin-top: 2px; 1449 box-shadow: 0 -6px 10px -8px rgba(27, 30, 34, 0.16); 1450 } 1451 #re-rec-overlay h3 { font-size: 21px; } 1452 #re-rec-overlay .re-rec-fichas { grid-template-columns: minmax(0, 1fr); } 1453 #re-rec-overlay .re-rec-ficha { display: flex; align-items: center; gap: 14px; text-align: left; } 1454 #re-rec-overlay .re-rec-foto-wrap { width: 64px; margin: 0; flex-shrink: 0; } 1455 #re-rec-overlay .re-rec-foto { height: 64px; } 1456 #re-rec-overlay .re-rec-online { top: 4px; left: 4px;
1456 width: 20px; height: 20px; } 1457 #re-rec-overlay .re-rec-ficha-datos { min-width: 0; } 1458 #re-rec-overlay .re-rec-p2-grid { grid-template-columns: minmax(0, 1fr); } 1459 } 1460</style></div><div class="w-embed w-script">
1460<script> 1461(function () { 1462 "use strict"; 1463 1464 window.RE_RECOMENDADOR_VERSION = "1.0.0"; 1465 1466 var SUPABASE_URL = "https://yrzanatzshdpvypazgeb.supabase.co"; 1467 var SUPABASE_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlyemFuYXR6c2hkcHZ5cGF6Z2ViIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODQ0NjczNzcsImV4cCI6MjEwMDA0MzM3N30.WNRs7Bc8lA1JUw-KbOHYDBlEjTIPjwxx3cvlBmdpgHU"; 1468 var PRIVACIDAD_URL = "https://www.redentrenadores.com/legal/politica-de-privacidad"; 1469 1470 // Rutas donde el pop-up no debe salir nunca. El panel y el área de usuario 1471 // son zonas privadas de alguien que ya está dentro; /lp/ son las landings de 1472 // paid, que ya llevan este mismo flujo dentro de su propio widget y verÃan 1473 // dos modales peleándose; /crear-cuenta y /crea-tu-perfil son el alta y el 1474 // onboarding del entrenador, a quien ofrecerle buscar entrenador no le sirve 1475 // de nada y encima le tapa el formulario a medio rellenar; en 1476 // /pregunta-al-experto es donde contesta preguntas. /donde-entrenar queda 1477 // fuera por decisión de Esteve (20/08/2026). 1478 var RUTAS_EXCLUIDAS = [ 1479 "/panel-entrenador", "/usuarios/", "/mi-cuenta", "/admin", "/lp/", 1480 "/crear-cuenta", "/crea-tu-perfil", "/pregunta-al-experto", "/donde-entrenar" 1481 ]; 1482 1483 var SEGUNDOS_ESPERA = 25; 1484 var SCROLL_DISPARO = 0.5; 1485 1486 var CLAVE_APAGADO = "re_recomendador_off"; // localStorage: no volver a salir 1487 var CLAVE_SESION = "re_recomendador_visto"; // sessionStorage: ya salió hoy 1488 1489 // Los `value` son las etiquetas exactas de `entrenadores.tipo_atencion`, no 1490 // slugs, y tienen que seguir siéndolo: es lo que ya hay guardado en 1491 // `solicitudes_contacto.modalidad` y lo que el panel del entrenador pinta 1492 // sin traducir. El label es lo único redactable para el usuario. 1493 // 1494 // El orden importa: se enseñan solo las dos primeras y el resto se destapa 1495 // con "Ver más", igual que hace el wizard de /encuentra-entrenador. Online y
1496 // gimnasio son las dos que cubren a casi todo el mundo, y una pantalla con 1497 // cuatro opciones grandes pesa más de lo que aporta como primera impresión. 1498 var MODALIDADES = [ 1499 { value: "Online", label: "Online, por videollamada", nota: "Desde donde quieras" }, 1500 { value: "En un gimnasio", label: "En un gimnasio", nota: "En su centro o en el tuyo" }, 1501 { value: "A domicilio", label: "En mi casa", nota: "El entrenador se desplaza hasta ti" }, 1502 { value: "En exteriores", label: "Al aire libre", nota: "Parques y espacios abiertos" } 1503 ]; 1504 // Cuántas opciones se enseñan de entrada en cada paso; el resto va tras 1505 // "Ver más opciones". Las listas están ordenadas para que las visibles sean 1506 // las que cubren a más gente: en objetivos, las tres primeras son las que 1507 // más entrenadores trabajan (53, 47 y 45 de 95). 1508 var VISIBLES = { donde: 2, objetivo: 3 }; 1509 1510 // Son las especialidades tal cual están en `entrenadores.especialidades`, 1511 // porque la RPC filtra con `especialidades @> ARRAY[p_objetivo]`: cualquier 1512 // slug inventado aquà no casarÃa con nadie y devolverÃa lista vacÃa. Se 1513 // ofrecen las 6 con cobertura real (11 y 5 entrenadores las menos vistas de 1514 // esta lista); las de cola larga caen en "Aún no lo sé", que no filtra. 1515 var OBJETIVOS = [ 1516 { value: "Pérdida de peso", label: "Perder peso" }, 1517 { value: "Ganar masa muscular", label: "Ganar masa muscular" }, 1518 { value: "Mejora de forma fÃsica", label: "Estar en forma" }, 1519 { value: "Entrenamiento funcional", label: "Entrenamiento funcional" }, 1520 { value: "Recuperación de lesiones", label: "Recuperarme de una lesión" }, 1521 { value: "Preparación para deportistas", label: "Preparar una prueba o competición" }, 1522 { value: "", label: "Aún no lo tengo claro", nota: "Te ayudarán a definirlo" } 1523 ]; 1524 1525 // `cuando_empieza` no tiene hoy ni una fila rellena en toda la tabla (el 1526 // campo existe en el formulario de la ficha pero no llega), asà que estos 1527 // valores no chocan con nada previo. Son texto legible a propósito: el 1528 // entrenador los lee tal cual en su panel, sin traducción. 1529 var CUANDOS = [ 1530 { value: "Lo antes posible", label: "Lo antes posible" }, 1531 { value: "En 2-4 semanas", label: "En 2-4 semanas" }, 1532 { value: "En 1-2 meses", label: "En 1 o 2 meses" }, 1533 { value: "Solo me estoy informando", label: "Solo me estoy informando" } 1534 ]; 1535 1536 var NIVELES = [ 1537 { value: "sedentario", label: "Sedentario, casi no me muevo" }, 1538 { value: "poco-activo", label: "Poco activo, 1-2 dÃas" }, 1539 { value: "activo", label: "Activo, 3-4 dÃas" }, 1540 { value: "muy-activo", label: "Muy activo, 5 dÃas o más" } 1541 ]; 1542 1543 var estado = { 1544 indice: 0, // posición dentro de pasos() 1545 fase: "preguntas", // "preguntas" | "confirmacion" | "cierre" 1546 modalidad: "", 1547 abiertas: {}, // pasos cuyo "Ver más" ya se ha pulsado 1548 ciudad: "", 1549 ciudadDePagina: false, // en las 60 páginas de ciudad tampoco se pregunta 1550 objetivo: "", 1551 objetivoDePagina: false, // en /objetivo/* no se pregunta, ya lo sabemos 1552 objetivoRespondido: false, 1553 cuando: "", 1554 nombre: "", 1555 email: "", 1556 telefono: "", 1557 enviando: false, 1558 error: "", 1559 top3: [], 1560 top3Clave: null, // respuestas con las que se busco ese reparto 1561 claimToken: null, 1562 1563 // Solo para la medición: qué pantallas se han contado ya en esta apertura, 1564 // cuándo se abrió y si esta apertura cuenta. Las previsualizaciones y el 1565 // ?recomendador=1 no cuentan: si no, cada vez que se revisa el copy se 1566 // ensucia el embudo con una sesión que no es de nadie. 1567 vistas: {}, 1568 abiertoEn: 0, 1569 midiendo: true 1570 }; 1571 1572 // La secuencia de pantallas no es fija: quien entrena online no tiene que 1573 // decir su ciudad, y en una página de ciudad o de objetivo esa respuesta ya 1574 // la da la propia página. Se calcula en cada render en vez de encadenar 1575 // condiciones dentro de cada paso â un contador fijo de "paso N de 4" con 1576 // ramas de distinta longitud acaba pintando 5 pantallas bajo 4 puntos. 1577 //
1578 // Nunca se pregunta algo que ya sabemos: quien entra por 1579 // /entrenadores-personales/valencia ya ha dicho dónde busca, y volver a 1580 // preguntárselo se lee como que no le hemos escuchado. 1581 function pasos() { 1582 var lista = ["donde"]; 1583 if (!estado.objetivoDePagina) lista.push("objetivo"); 1584 lista.push("cuando", "datos"); 1585 return lista; 1586 } 1587 1588 function pasoActual() { 1589 return pasos()[estado.indice]; 1590 } 1591 1592 var ciudades = []; 1593 1594 // ------------------------------------------------------------------ utils 1595 1596 function esc(str) { 1597 var d = document.createElement("div"); 1598 d.textContent = str == null ? "" : String(str); 1599 return d.innerHTML; 1600 } 1601 1602 function leer(clave, almacen) { 1603 try { return almacen.getItem(clave); } catch (e) { return null; } 1604 } 1605 1606 function escribir(clave, valor, almacen) { 1607 try { almacen.setItem(clave, valor); } catch (e) {} 1608 } 1609 1610 // La ciudad de las 60 páginas de ciudad sale de #re-ciudad-nombre, el 1611 // elemento oculto bindeado al campo Nombre de la colección Ciudades que ya 1612 // usa el glue del listado. Se usa el nombre y no el slug porque la RPC casa 1613 // por `ciudad_nombre ILIKE` y hay ciudades cuyo slug no coincide con su 1614 // nombre ("la-sagra-toledo" contra "La sagra Toledo"). 1615 function ciudadDeLaPagina() { 1616 var el = document.getElementById("re-ciudad-nombre"); 1617 if (el && el.textContent.trim()) return el.textContent.trim(); 1618 var param = new URLSearchParams(location.search).get("ciudad"); 1619 return param ? param.trim() : ""; 1620 } 1621 1622 // Las marcas que deja NFD al separar las tildes (U+0300-U+036F). Va con 1623 // fromCharCode y no con un escape unicode porque este bloque se sube al 1624 // embed dentro del JSON de una llamada MCP y esa secuencia no sobrevive: 1625 // queda el carácter combinante suelto, invisible en el editor. El rango es 1626 // exactamente el mismo. La propiedad Diacritic no vale como atajo: deja 1627 // fuera 19 puntos de este bloque. 1628 var MARCAS_COMBINANTES = new RegExp( 1629 "[" + String.fromCharCode(0x300) + "-" + String.fromCharCode(0x36f) + "]", "g"); 1630 1631 // En /objetivo/* el objetivo ya lo dice la propia página: preguntarlo otra 1632 // vez es hacerle repetir al usuario lo que acaba de elegir al entrar. 1633 function aSlug(texto) { 1634 return texto.toLowerCase()
1635 .normalize("NFD").replace(MARCAS_COMBINANTES, "") 1636 .replace(/[^a-z0-9]+/g, "-") 1637 .replace(/^-|-$/g, ""); 1638 } 1639 1640 function objetivoDeLaPagina() { 1641 var m = location.pathname.match(/^\/objetivo\/([^/]+)/); 1642 if (!m) return null; 1643 var encontrado = OBJETIVOS.filter(function (o) { return o.value; }) 1644 .find(function (o) { return aSlug(o.value) === m[1]; }); 1645 return encontrado ? encontrado.value : null; 1646 } 1647 1648 // ------------------------------------------------------------------ datos 1649 1650 function rest(ruta, opciones) { 1651 var o = opciones || {}; 1652 return fetch(SUPABASE_URL + ruta, { 1653 method: o.method || "GET", 1654 headers: { 1655 apikey: SUPABASE_ANON_KEY, 1656 Authorization: "Bearer " + SUPABASE_ANON_KEY, 1657 "Content-Type": "application/json" 1658 }, 1659 body: o.body ? JSON.stringify(o.body) : undefined 1660 }).then(function (res) { 1661 if (!res.ok) throw new Error(ruta + " -> " + res.status); 1662 return res.json(); 1663 }); 1664 } 1665 1666 function cargarCiudades() { 1667 if (ciudades.length) return Promise.resolve(ciudades); 1668 return rest("/rest/v1/entrenadores_publico?select=ciudad_nombre&tipo_entrenador=eq.personal&ciudad_nombre=not.is.null") 1669 .then(function (filas) { 1670 ciudades = Array.from(new Set(filas.map(function (f) { return f.ciudad_nombre; }))) 1671 .sort(function (a, b) { return a.localeCompare(b, "es"); }); 1672 var dl = document.getElementById("re-rec-ciudades"); 1673 if (dl) dl.innerHTML = ciudades.map(function (c) { return '<option value="' + esc(c) + '">'; }).join(""); 1674 return ciudades; 1675 }) 1676 .catch(function (err) { 1677 // Sin la lista el campo sigue siendo un input de texto libre y la RPC 1678 // casa por ILIKE, asà que se puede enviar igual: solo se pierde el 1679 // autocompletado. No es motivo para bloquear el formulario. 1680 console.warn("[re-recomendador] no se pudieron cargar las ciudades", err); 1681 return []; 1682 }); 1683 } 1684 1685 // A quién se le manda. Se pide a la MISMA RPC que pinta el listado público, 1686 // asà que el orden es el mismo que ve el usuario si navega él solo â no un 1687 // ranking paralelo que dijera otra cosa. 1688 // 1689 // NO se fuerza a que salgan tres. Si con lo que ha pedido solo encaja uno, se 1690 // le manda a ese uno y ya: rellenar el hueco con gente que no encaja es un 1691 // mal reparto para el usuario y un contacto quemado para el entrenador. 1692 // Valladolid es el caso real: un único entrenador en toda la ciudad, y la 1693 // versión vieja acababa soltando la ciudad para llegar a tres, asà que 1694 // devolvÃa tres de cualquier sitio menos el de Valladolid. 1695 // 1696 // Los filtros solo se sueltan cuando no encaja NADIE, de menos a más 1697 // importante: primero el objetivo (casi cualquier entrenador sirve para un 1698 // objetivo que no tenga marcado), luego la modalidad, y la ciudad la última 1699 // porque quien no cubre la zona no puede atender a este cliente y punto. 1700 function buscarTop3() { 1701 var intentos = [ 1702 { objetivo: estado.objetivo, modalidad: estado.modalidad, ciudad: estado.ciudad }, 1703 { objetivo: "", modalidad: estado.modalidad, ciudad: estado.ciudad }, 1704 { objetivo: "", modalidad: "", ciudad: estado.ciudad }, 1705 { objetivo: "", modalidad: estado.modalidad, ciudad: "" } 1706 ]; 1707 1708 function intentar(i) { 1709 if (i >= intentos.length) return Promise.resolve([]); 1710 var f = intentos[i]; 1711 return rest("/rest/v1/rpc/buscar_entrenadores_listado", { 1712 method: "POST", 1713 body: { 1714 p_objetivo: f.objetivo || null, 1715 p_modalidad: f.modalidad || null, 1716 p_ciudad: f.ciudad || null, 1717 p_perfil: null, 1718 p_precio_min: null, 1719 p_precio_max: null, 1720 p_orden: "relevancia", 1721 p_limite: 3, 1722 p_offset: 0 1723 } 1724 }).then(function (filas) { 1725 // Basta con uno: el siguiente intento no serÃa "mejor", serÃa menos 1726 // fiel a lo que ha pedido. 1727 if (filas && filas.length) return filas; 1728 return intentar(i + 1); 1729 }); 1730 } 1731 1732 return intentar(0); 1733 } 1734 1735 // ------------------------------------------------------------------ vistas 1736 1737 function dotsHTML() { 1738 var total = pasos().length; 1739 var html = ""; 1740 for (var i = 0; i < total; i++) { 1741 html += '<span class="' + (i <= estado.indice ? "re-rec-hecho" : "") + '"></span>'; 1742 } 1743 return '<div class="re-rec-dots">' + html + "</div>"; 1744 } 1745 1746 function cabeceraHTML(eyebrow, titulo, sub) { 1747 return '<button type="button" class="re-rec-cerrar" data-accion="cerrar" aria-label="Cerrar">✕</button>' + 1748 dotsHTML() + 1749 (eyebrow ? '<p class="re-rec-eyebrow">' + esc(eyebrow) + "</p>" : "") + 1750 "<h3>" + esc(titulo) + "</h3>" + 1751 (sub ? '<p class="re-rec-sub">' + esc(sub) + "</p>" : ""); 1752 } 1753 1754 // Las opciones se identifican por su valor y no por su posición: con la 1755 // lista de modalidades plegada, un Ãndice apuntarÃa a la posición dentro de 1756 // lo que se está pintando y no dentro de la lista real. 1757 // Pinta las opciones de un paso ya recortadas a las visibles, y decide sola 1758 // si hace falta el "Ver más": asà ningún paso puede quedarse con el enlace 1759 // puesto pero sin nada que destapar. 1760 function opcionesDelPaso(paso, lista, seleccionado) { 1761 var tope = VISIBLES[paso]; 1762 var plegado = tope && !estado.abiertas[paso] && lista.length > tope; 1763 return opcionesHTML(plegado ? lista.slice(0, tope) : lista, seleccionado, plegado); 1764 } 1765 1766 function opcionesHTML(lista, seleccionado, verMas) { 1767 return '<div class="re-rec-opciones">' + lista.map(function (o) { 1768 var on = o.value === seleccionado; 1769 return '<button type="button" class="re-rec-op' + (on ? " re-rec-op-on" : "") + '"' + 1770 ' data-accion="opcion" data-valor="' + esc(o.value) + '">' + 1771 "<span>" + esc(o.label) + 1772 (o.nota ? '<span class="re-rec-op-nota">' + esc(o.nota) + "</span>" : "") + 1773 "</span></button>"; 1774 }).join("") + 1775 (verMas ? '<button type="button" class="re-rec-vermas" data-accion="ver-mas">Ver más opciones</button>' : "") + 1776 "</div>"; 1777 } 1778 1779 function navHTML(conAtras, textoBoton) { 1780 return '<div class="re-rec-nav">' + 1781 (conAtras ? '<button type="button" class="re-rec-atras" data-accion="atras">Atrás</button>' : "") + 1782 (textoBoton ? '<button type="button" class="re-rec-btn" data-accion="siguiente">' + esc(textoBoton) + "</button>" : "") + 1783 "</div>"; 1784 } 1785 1786 function errorHTML() { 1787 return '<div class="re-rec-error"' + (estado.error ? ' style="display:block"' : "") + ">" + esc(estado.error) + "</div>"; 1788 } 1789 1790 // ¿Hace falta preguntar la ciudad? Solo si ya ha dicho que quiere entrenar 1791 // presencialmente y la página no nos la ha dado hecha. Entrenar por
1792 // videollamada no depende de dónde viva, y filtrar por su ciudad ahà solo 1793 // servirÃa para dejar fuera a entrenadores que le valdrÃan igual. 1794 function pideCiudad() { 1795 return !!estado.modalidad && estado.modalidad !== "Online" && !estado.ciudadDePagina; 1796 } 1797 1798 // Modalidad y ciudad comparten pantalla: son la misma pregunta ("¿dónde?") 1799 // partida en dos, y separarlas metÃa un paso entero para un solo campo. El 1800 // campo de ciudad no está desde el principio â aparece al elegir una 1801 // modalidad presencial, porque hasta entonces no se sabe si hace falta. 1802 function pasoDondeHTML() { 1803 // El número va escrito y se cuenta solo. Dos motivos: el recorrido no 1804 // siempre tiene los mismos pasos (el objetivo se salta en /objetivo/*), y 1805 // la última pantalla no es una pregunta sino el formulario de contacto â 1806 // por eso se descuenta uno y no se anuncian cuatro preguntas. 1807 var PALABRAS = { 1: "Una", 2: "Dos", 3: "Tres" }; 1808 return cabeceraHTML( 1809 "Te ayudamos a decidir", 1810 "¿TodavÃa no lo tienes claro? Te recomendamos un entrenador.", 1811 (PALABRAS[pasos().length - 1] || "Unas") + " preguntas rápidas" + 1812 " y te ponemos en contacto con los entrenadores" + 1813 (estado.ciudad ? " de " + estado.ciudad : "") + 1814 " que mejor encajan contigo. Gratis y sin compromiso." 1815 ) + 1816 // Al dejar de ser el tÃtulo, la primera pregunta necesita su propia 1817 // etiqueta: sin ella las cuatro opciones quedan colgando del subtÃtulo 1818 // y no se entiende qué se está eligiendo. 1819 '<p class="re-rec-pregunta">¿Dónde te gustarÃa entrenar?</p>' + 1820 opcionesDelPaso("donde", MODALIDADES, estado.modalidad) + 1821 (pideCiudad() 1822 ? '<div class="re-rec-campo re-rec-campo-ciudad">' + 1823 '<label for="re-rec-ciudad">¿En qué ciudad?</label>' + 1824 '<input type="text" id="re-rec-ciudad" list="re-rec-ciudades" autocomplete="address-level2" placeholder="Empieza a escribirâ¦" value="' + esc(estado.ciudad) + '">' + 1825 '<datalist id="re-rec-ciudades"></datalist>' + 1826 "</div>" 1827 : "") + 1828 errorHTML() + 1829 (pideCiudad() ? navHTML(false, "Siguiente") : ""); 1830 } 1831 1832 function pasoObjetivoHTML() { 1833 return cabeceraHTML("", "¿Cuál es tu objetivo?", "Con esto sabemos qué entrenadores están especializados en lo que buscas.") + 1834 // El centinela es `null` y no "": "" es una respuesta valida ("aun no lo 1835 // tengo claro"), y sin distinguirla esa opcion saldria ya marcada antes 1836 // de que el usuario hubiera contestado nada. 1837 opcionesDelPaso("objetivo", OBJETIVOS, estado.objetivoRespondido ? estado.objetivo : null) + 1838 errorHTML() + 1839 navHTML(true, ""); 1840 } 1841 1842 function pasoCuandoHTML() { 1843 return cabeceraHTML("", "¿Cuándo te gustarÃa empezar?", "Asà te proponen un plan realista desde el primer mensaje.") + 1844 opcionesHTML(CUANDOS, estado.cuando) + 1845 errorHTML() + 1846 navHTML(true, ""); 1847 } 1848 1849 // "Ana, Marcos y Juanma" â coma entre todos menos el último, que va con "y". 1850 function enumerar(nombres) { 1851 if (nombres.length === 1) return nombres[0]; 1852 return nombres.slice(0, -1).join(", ") + " y " + nombres[nombres.length - 1]; 1853 } 1854 1855 // El subtÃtulo de la última pantalla nombra a los tres entrenadores en 1856 // cuanto se sabe quiénes son. Mientras la búsqueda va en camino (o si 1857 // falla) se cae a la versión sin nombres: pedir el email es el momento de 1858 // menos paciencia del recorrido y no puede quedarse esperando a nada. 1859 function subtituloDatosHTML() { 1860 if (!estado.top3.length) { 1861 return "Enviaremos tu solicitud a los entrenadores que mejor encajan contigo. Te escribirán ellos directamente."; 1862 } 1863 var nombres = estado.top3.map(function (e) { return "<strong>" + esc(e.nombre) + "</strong>"; }); 1864 return enumerar(nombres) + 1865 (estado.top3.length === 1 ? " es el que mejor encaja" : " son los que mejor encajan") + 1866 " contigo. Te " + (estado.top3.length === 1 ? "escribirá" : "escribirán") + " directamente."; 1867 } 1868 1869 // El aviso legal tiene que decir con cuánta gente se comparte el dato, y 1870 // ya no siempre son tres: si en la ciudad solo encaja uno, es uno. Si la 1871 // búsqueda aún no ha vuelto no se inventa un número. 1872 function conQuienHTML() { 1873 var cuantos = estado.top3.length; 1874 if (cuantos === 1) return "este entrenador"; 1875 if (cuantos) return "estos " + cuantos + " entrenadores"; 1876 return "los entrenadores que mejor encajen contigo"; 1877 } 1878 1879 function pasoDatosHTML() { 1880 return cabeceraHTML("", "Ãltimo paso: ¿cómo te contactamos?", null) + 1881 '<p class="re-rec-sub">' + subtituloDatosHTML() + "</p>" + 1882 '<div class="re-rec-campo">' + 1883 '<label for="re-rec-nombre">Nombre</label>' + 1884 '<input type="text" id="re-rec-nombre" autocomplete="given-name" value="' + esc(estado.nombre) + '">' + 1885 "</div>" + 1886 // El email vuelve al paso 1 y es obligatorio: sin él no hay chat, porque 1887 // no hay a quién avisar de un mensaje nuevo ni con qué entrar al área 1888 // (la cuenta es passwordless por email). Ver specs/spec-chat-plataforma.md. 1889 '<div class="re-rec-campo">' + 1890 '<label for="re-rec-email">Email</label>' + 1891 '<input type="email" id="re-rec-email" autocomplete="email" inputmode="email" placeholder="[email protected]" value="' + esc(estado.email) + '">' + 1892 "</div>" + 1893 // El teléfono se queda, pero pasa a opcional. No se quita: es el dato con 1894 // el que el entrenador llama, y en los 90 dÃas previos las solicitudes 1895 // que solo lo traÃan se gestionaron casi todas, frente a dos tercios de 1896 // las que solo traÃan email. 1897 '<div class="re-rec-campo">' + 1898 '<label for="re-rec-telefono">Teléfono <span class="re-rec-opcional">(opcional)</span></label>' + 1899 '<input type="tel" id="re-rec-telefono" autocomplete="tel" inputmode="tel" placeholder="600 11 22 33" value="' + esc(estado.telefono) + '">' + 1900 "</div>" + 1901 errorHTML() + 1902 '<div class="re-rec-nav">' + 1903 '<button type="button" class="re-rec-atras" data-accion="atras">Atrás</button>' + 1904 '<button type="button" class="re-rec-btn" data-accion="enviar"' + (estado.enviando ? " disabled" : "") + ">" +
1905 (estado.enviando ? "Enviandoâ¦" : "Enviar mi solicitud") + 1906 "</button>" + 1907 "</div>" + 1908 '<p class="re-rec-legal">Al enviar aceptas nuestra <a href="' + PRIVACIDAD_URL + '" target="_blank" rel="noopener">polÃtica de privacidad</a>. Compartiremos tu nombre y tus datos de contacto con ' + conQuienHTML() + ' para que puedan responderte.</p>'; 1909 } 1910 1911 // Estrella siempre en el verde de marca, con independencia de la nota: es la 1912 // convención del listado público (§48 del design system), no un color de 1913 // estado que deba variar con el resultado. 1914 var ESTRELLA_SVG = '<svg class="re-rec-estrella" width="13" height="13" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0l2.47 5.01 5.53.8-4 3.9.94 5.5L8 12.6l-4.94 2.6.94-5.5-4-3.9 5.53-.8L8 0z" fill="#089F3B"/></svg>'; 1915 1916 // La pÃldora solo lleva el check cuando ese entrenador trabaja de verdad el 1917 // objetivo que ha pedido el usuario. Cuando el reparto ha tenido que soltar 1918 // el filtro de objetivo para completar los tres (o cuando ha contestado "aún 1919 // no lo tengo claro"), se enseña su especialidad principal sin check: decir 1920 // "coincide con lo que buscas" de alguien que no lo trabaja es mentir en la 1921 // pantalla donde más confianza hace falta. 1922 function especialidadHTML(e) { 1923 var suyas = e.especialidades || []; 1924 if (!suyas.length) return ""; 1925 1926 var coincide = estado.objetivo && suyas.indexOf(estado.objetivo) !== -1; 1927 var texto = coincide ? etiquetaObjetivo(estado.objetivo) : suyas[0]; 1928 return '<span class="re-rec-pill' + (coincide ? " re-rec-pill-match" : "") + '">' + 1929 (coincide ? "â " : "") + esc(texto) + 1930 "</span>"; 1931 } 1932 1933 // "Pérdida de peso" es como está guardado; "Perder peso" es como se lo hemos 1934 // preguntado. Se le devuelve su propia palabra. 1935 function etiquetaObjetivo(valor) { 1936 var op = OBJETIVOS.find(function (o) { return o.value === valor; }); 1937 return op ? op.label : valor; 1938 } 1939 1940 function fichaHTML(e) { 1941 // Sin `loading="lazy"`: son tres fotos que se pintan ya visibles y en el 1942 // momento en que se muestra el modal. Diferirlas solo consigue que la 1943 // confirmación aparezca un instante con tres huecos grises. 1944 var foto = e.foto_url 1945 ? '<img class="re-rec-foto" src="' + esc(e.foto_url) + '" alt="' + esc(e.nombre) + '">' 1946 : '<div class="re-rec-foto"></div>'; 1947 1948 // Nota y experiencia, y nada más: el precio se probó como relleno y se 1949 // descartó (decisión de Esteve del 17/08/2026). Esta pantalla va de si el 1950 // entrenador encaja, y meter una cifra en euros justo cuando el usuario 1951 // acaba de dar sus datos convierte una confirmación en un escaparate. 1952 // 1953 // La consecuencia asumida es que muchas fichas se quedan solo con nombre y 1954 // especialidad: de los 93 entrenadores personales, 22 tienen años de 1955 // experiencia y 15 tienen opiniones. Eso no se arregla aquà sino haciendo 1956 // que rellenen esos campos. 1957 // 1958 // Nunca se pinta un cero: un "0 opiniones" o un "0 años" en la pantalla 1959 // que tiene que dar confianza juega en contra del entrenador. 1960 var datos = []; 1961 if (e.total_reviews > 0) { 1962 datos.push(ESTRELLA_SVG + " " + e.rating_promedio + " (" + e.total_reviews + 1963 (e.total_reviews === 1 ? " opinión)" : " opiniones)")); 1964 } 1965 if (e.anos_experiencia > 0) { 1966 // "de experiencia" completo y no solo "2 años": suelto, se lee como la 1967 // edad del entrenador. 1968 datos.push(e.anos_experiencia + (e.anos_experiencia === 1 ? " año" : " años") + " de experiencia"); 1969 } 1970 1971 // La ficha NO es un enlace, a propósito. Debajo de estas tres tarjetas 1972 // está el paso de completar el perfil, que es lo único que queda por 1973 // hacer y lo que da valor a la solicitud recién enviada; un enlace al 1974 // perfil aquà arriba se lleva al usuario a otra pestaña justo antes de 1975 // llegar a él. Las fotos y los datos ya cumplen su función, que es 1976 // demostrar que la solicitud ha ido a gente real. 1977 return '<div class="re-rec-ficha">' + 1978 foto + 1979 '<span class="re-rec-ficha-datos">' + 1980 '<span class="re-rec-ficha-nombre">' + esc(e.nombre) + "</span>" + 1981 especialidadHTML(e) + 1982 // Cada dato en su lÃnea y no separados por "·": en una tarjeta de 1983 // 180px, "5 (12 opiniones) · 8 años de experiencia" se parte por donde 1984 // le toca y deja huérfano medio dato en la lÃnea siguiente. 1985 datos.map(function (d) { return '<span class="re-rec-ficha-meta">' + d + "</span>"; }).join("") + 1986 "</span>" + 1987 "</div>"; 1988 } 1989 1990 function opcionesSelectHTML(lista, placeholder) { 1991 return '<option value="">' + esc(placeholder) + "</option>" + lista.map(function (o) { 1992 return '<option value="' + esc(o.value) + '">
1992' + esc(o.label) + "</option>"; 1993 }).join(""); 1994 } 1995 1996 // Paso 2 del journey de usuario. Va SIEMPRE después de la solicitud, nunca 1997 // antes: es opcional y no puede convertirse en un peaje para escribir a un 1998 // entrenador. `claim_token` es lo que devuelve solicitar-contacto-entrenador 1999 // y lo único que prueba que quien rellena esto es quien acaba de enviar. 2000 function confirmacionHTML() { 2001 var cuantos = estado.top3.length; 2002 var p2 = estado.claimToken 2003 ? '<div class="re-rec-p2">' + 2004 "<h4>Cuéntanos algo más</h4>" + 2005 '<p class="re-rec-sub" style="font-size:14px;margin-bottom:14px">Opcional y son 20 segundos, pero con esto te preparan una propuesta a tu medida.</p>' + 2006 // El nivel de actividad va a fila completa porque su etiqueta no 2007 // cabe a media columna; edad y peso comparten fila, que son dos 2008 // campos de tres caracteres. 2009 '<div class="re-rec-p2-grid">' + 2010 // El email ya no se pide aquÃ: lo trae el paso 1, obligatorio. 2011 // Pedirlo dos veces es peor que pedirlo una. 2012 '<div class="re-rec-campo re-rec-p2-full"><label for="re-rec-actividad">¿Cuánto ejercicio haces ahora?</label><select id="re-rec-actividad">' + opcionesSelectHTML(NIVELES, "Selecciona tu nivel") + "</select></div>" + 2013 '<div class="re-rec-campo"><label for="re-rec-edad">Edad</label><input type="number" id="re-rec-edad" min="10" max="120" inputmode="numeric" placeholder="Años"></div>' + 2014 '<div class="re-rec-campo"><label for="re-rec-peso">Peso</label><input type="number" id="re-rec-peso" min="20" max="400" step="0.1" inputmode="decimal" placeholder="Kg"></div>' + 2015 // Altura y disponibilidad los pide la tarjeta de "Solicitud inicial" 2016 // del panel del entrenador, que enseña ocho datos del cliente. 2017 // Están aquà y no en el paso 1 por lo mismo que el resto: el paso 1 2018 // pide lo mÃnimo para poder responder, esto es lo que ayuda a 2019 // preparar la propuesta y se pregunta cuando ya no cuesta conversión. 2020 '<div class="re-rec-campo"><label for="re-rec-altura">Altura</label><input type="number" id="re-rec-altura" min="100" max="250" inputmode="numeric" placeholder="Cm"></div>' + 2021 '<div class="re-rec-campo"><label for="re-rec-disponibilidad">¿Cuándo puedes entrenar?</label><input type="text" id="re-rec-disponibilidad" placeholder="Tardes entre semana"></div>' + 2022 '<div class="re-rec-campo re-rec-p2-full"><label for="re-rec-notas">¿Algo más que debamos saber?</label><textarea id="re-rec-notas" placeholder="Lesiones, horarios que te van bien, experiencia previaâ¦"></textarea></div>' + 2023 "</div>" + 2024 '<div class="re-rec-error" id="re-rec-p2-error"></div>' + 2025 '<div class="re-rec-acciones">' + 2026 '<button type="button" class="re-rec-btn" data-accion="guardar-perfil">Enviar mis datos</button>' + 2027 '<button type="button" class="re-rec-saltar" data-accion="cerrar">Ahora no</button>' + 2028 "</div>" + 2029 "</div>" 2030 : ""; 2031 2032 return '<button type="button" class="re-rec-cerrar" data-accion="cerrar" aria-label="Cerrar">✕</button>' + 2033 '<p class="re-rec-eyebrow">Solicitud enviada</p>' + 2034 "<h3>" + (cuantos === 1 ? "Este es tu entrenador" : "Estos son tus " + cuantos + " entrenadores") + "</h3>" + 2035 '<p class="re-rec-sub">Ya tienen tu solicitud y te escribirán directamente.</p>' + 2036 '<div class="re-rec-fichas">' + estado.top3.map(fichaHTML).join("") + "</div>" + 2037 p2; 2038 } 2039 2040 // `ofreceCuenta` sigue siendo un parámetro y no un `true` fijo aunque el 2041 // email vuelva a ser obligatorio en el paso 1: este cierre también lo pinta 2042 // el camino en el que el paso 2 falla, y ahà no se da nada por hecho. 2043 function cierreHTML(guardado, ofreceCuenta) { 2044 var corto = estado.nombre ? estado.nombre.trim().split(" ")[0] : ""; 2045 return '<button type="button" class="re-rec-cerrar" data-accion="cerrar" aria-label="Cerrar">✕</button>' + 2046 "<h3>" + esc(guardado ? "¡Listo!" : (corto ? "¡Gracias, " + corto + "!" : "¡Gracias!")) + "</h3>" + 2047 '<p class="re-rec-sub">' + 2048 (guardado 2049 ? "Tus datos ya van con la solicitud. Se pondrán en contacto contigo muy pronto." 2050 : "Tu solicitud está enviada. Se pondrán en contacto contigo muy pronto.") + 2051 "</p>" + 2052 (estado.claimToken && ofreceCuenta 2053 ? '<div class="re-rec-p2">' + 2054 "<h4>¿Quieres seguir tus solicitudes?</h4>" + 2055 '<p class="re-rec-sub" style="font-size:14px;margin-bottom:14px">Crea tu cuenta y consulta cuándo las ve cada entrenador. Sin contraseña y sin salir de aquÃ.</p>' + 2056 '<div class="re-rec-error" id="re-rec-cuenta-error"></div>' + 2057 '<button type="button" class="re-rec-btn" data-accion="crear-cuenta">Crear mi cuenta</button>' + 2058 '<p class="re-rec-legal">Al crear la cuenta aceptas nuestra <a href="' + PRIVACIDAD_URL + '" target="_blank" rel="noopener">polÃtica de privacidad</a>.</p>' + 2059 "</div>" 2060 : ""); 2061 } 2062 2063 // ------------------------------------------------------------------ puente 2064 2065 // El widget no cabe en un solo Embed de Webflow (tope de 50.000 caracteres), 2066 // asi que va partido en dos bloques. Esto es TODO lo que cruza de este 2067 // bloque al siguiente; es interno y no es la API publica, que sigue siendo 2068 // window.reRecomendador y la monta el bloque de comportamiento. 2069 // 2070 // `estado` cruza como referencia, no como copia: los dos bloques escriben 2071 // sobre el mismo objeto y esa es justo la idea. 2072 window.__reRecNucleo = { 2073 RUTAS_EXCLUIDAS: RUTAS_EXCLUIDAS, 2074 SEGUNDOS_ESPERA: SEGUNDOS_ESPERA, 2075 SCROLL_DISPARO: SCROLL_DISPARO, 2076 CLAVE_APAGADO: CLAVE_APAGADO, 2077 CLAVE_SESION: CLAVE_SESION,
2078 MODALIDADES: MODALIDADES, 2079 OBJETIVOS: OBJETIVOS, 2080 CUANDOS: CUANDOS, 2081 estado: estado, 2082 pasos: pasos, 2083 pasoActual: pasoActual, 2084 pideCiudad: pideCiudad, 2085 leer: leer, 2086 escribir: escribir, 2087 ciudadDeLaPagina: ciudadDeLaPagina, 2088 objetivoDeLaPagina: objetivoDeLaPagina, 2089 rest: rest, 2090 cargarCiudades: cargarCiudades, 2091 buscarTop3: buscarTop3, 2092 pasoDondeHTML: pasoDondeHTML, 2093 pasoObjetivoHTML: pasoObjetivoHTML, 2094 pasoCuandoHTML: pasoCuandoHTML, 2095 pasoDatosHTML: pasoDatosHTML, 2096 subtituloDatosHTML: subtituloDatosHTML, 2097 confirmacionHTML: confirmacionHTML, 2098 cierreHTML: cierreHTML 2099 }; 2100})(); 2101</script>
2101 2102</div><div class="w-embed w-script"><!-- RedEntrenadores - Parche del pop-up recomendador: a quien se reparte --> 2103<!-- Va en el Footer V1, ENTRE el bloque 2 (JS nucleo) y el bloque 3 (JS 2104 comportamiento), y tiene que seguir ahi: el bloque 3 se lleva `buscarTop3` 2105 a una variable local nada mas cargar, asi que quien quiera cambiarla tiene 2106 que hacerlo antes. 2107 2108 Que cambia. El nucleo reparte con `buscar_entrenadores_listado`, que es la 2109 RPC del listado publico y ordena por un score de cinco criterios (plan 35, 2110 puja 25, recomendaciones 20, calidad 12, valoracion 8). Eso vale para 2111 ordenar un listado, pero repartir no es ordenar: con 180 entrenadores el 2112 score le daba casi todos los contactos a los mismos seis. 2113 2114 Desde el 12/09/2026 el reparto son dos criterios y ni uno mas 2115 (`buscar_entrenadores_reparto`, migracion 20260913030000): 2116 2117 1. primero los de plan de pago, que pagan por eso; 2118 2. despues todos los demas, empezando por quien menos leads ha recibido. 2119 2120 Y sigue haciendo lo de antes: `p_solo_contactables` deja fuera del reparto 2121 a los perfiles de escaparate (`entrenadores.recibe_contactos = false`), que 2122 tienen que seguir saliendo en el listado y no pueden salir aqui, porque 2123 aqui no se enseña, se reparte el contacto. El servidor ya los descartaba 2124 (`solicitar-contacto-entrenador` no les manda nada), pero el pop-up 2125 enseñaba su tarjeta diciendo "ya tienen tu solicitud", que de ellos no era 2126 verdad. 2127 2128 Por que un parche y no una linea en el bloque 2. Escribir ese embed por 2129 MCP obliga a reproducir sus 34 KB enteros a mano, sobre el flujo que trae 2130 los leads. Esto son 30 lineas y se puede tirar entero el dia que se toque 2131 el nucleo: entonces, cambia alli la RPC y borra este embed. 2132 2133 La cascada de intentos es copia literal del nucleo, con el corte en cuanto 2134 hay UNO (23/08/2026, cambiado en los dos sitios a la vez). Si algun dia 2135 cambia la del nucleo, el numero de version de abajo apaga el parche en vez 2136 de imponer la version vieja en silencio. 2137 2138 ID del embed: e4e348ca-b183-eafe-444a-cd8dbed588ae (componente Footer V1). -->
2139<script> 2140(function () { 2141 "use strict"; 2142 2143 var N = window.__reRecNucleo; 2144 if (!N || !N.buscarTop3) return; 2145 2146 // Copiado del bloque 2 con esta version. Si el nucleo cambia de version, el 2147 // parche se aparta: mejor que el pop-up reparta con el score viejo (el 2148 // servidor sigue filtrando) a que se quede congelado con una busqueda vieja. 2149 if (window.RE_RECOMENDADOR_VERSION !== "1.0.0") { 2150 console.warn("[recomendador] parche de reparto desactivado: el nucleo es la version", window.RE_RECOMENDADOR_VERSION, "y el parche se escribio para la 1.0.0. Cambia la RPC a buscar_entrenadores_reparto en buscarTop3 y borra este embed."); 2151 return; 2152 } 2153 2154 var estado = N.estado; 2155 var rest = N.rest; 2156 2157 N.buscarTop3 = function buscarTop3() { 2158 var intentos = [ 2159 { objetivo: estado.objetivo, modalidad: estado.modalidad, ciudad: estado.ciudad }, 2160 { objetivo: "", modalidad: estado.modalidad, ciudad: estado.ciudad }, 2161 { objetivo: "", modalidad: "", ciudad: estado.ciudad }, 2162 { objetivo: "", modalidad: estado.modalidad, ciudad: "" } 2163 ]; 2164 2165 function intentar(i) { 2166 if (i >= intentos.length) return Promise.resolve([]); 2167 var f = intentos[i]; 2168 return rest("/rest/v1/rpc/buscar_entrenadores_reparto", { 2169 method: "POST", 2170 body: { 2171 p_objetivo: f.objetivo || null, 2172 p_modalidad: f.modalidad || null, 2173 p_ciudad: f.ciudad || null, 2174 p_limite: 3, 2175 // Va dentro de la busqueda y no filtrando el resultado porque el 2176 // LIMIT ya se ha aplicado: quitar a uno aqui fuera dejaria dos, y un 2177 // lead repartido de menos es un entrenador que no recibe nada. 2178 p_solo_contactables: true 2179 } 2180 }).then(function (filas) { 2181 // Basta con uno: el siguiente intento no seria "mejor", seria menos 2182 // fiel a lo que ha pedido. 2183 if (filas && filas.length) return filas; 2184 return intentar(i + 1); 2185 }); 2186 } 2187 2188 return intentar(0); 2189 }; 2190 2191 window.RE_RECOMENDADOR_ESCAPARATE = "2026-09-12-1"; 2192})(); 2193</script>
2193</div><div class="w-embed w-script"><!-- RedEntrenadores - Parche del pop-up recomendador: no preguntar lo que ya nos han dicho --> 2194<!-- Va en el Footer V1, ENTRE el parche 2b (escaparate) y el bloque 3 (JS 2195 comportamiento), y tiene que seguir ahi: el bloque 3 se lleva `pasos`, 2196 `pasoActual` y las cuatro funciones de pantalla a variables locales nada 2197 mas cargar, asi que quien quiera cambiarlas tiene que hacerlo antes. 2198 2199 Que arregla. El buscador de la home (widget-home-hero-buscar.html) ya le ha 2200 sacado a la persona el objetivo, la modalidad, la ciudad y cuando quiere 2201 empezar. Sin esto, el pop-up se abria igualmente por la primera pantalla 2202 ("¿donde quieres entrenar?") a preguntarle lo que acababa de escribir. 2203 2204 Como se le dice lo que ya sabemos. Quien abra el pop-up con respuestas 2205 puestas marca ademas `estado.sabidos`: 2206 2207 estado.sabidos = { donde: true, objetivo: true, cuando: true } 2208 2209 Solo se salta lo que este marcado Y ademas tenga valor de verdad. "Donde" 2210 es la unica con truco: esa pantalla pregunta modalidad y, si es presencial, 2211 tambien ciudad, asi que saltarla exige tener las dos. 2212 2213 El paso "datos" no se salta nunca: es donde se piden nombre y email, que es 2214 lo unico que no se puede adivinar de una frase. Con las cuatro respuestas 2215 puestas, el pop-up abre directamente ahi. 2216 2217 Sin `estado.sabidos` la lista es exactamente la del nucleo (["donde", 2218 "objetivo", "cuando", "datos"], con "objetivo" fuera en /objetivo/*). Quien 2219 llega por el pop-up automatico de los 25 segundos no nota ningun cambio y 2220 el embudo que ya se esta midiendo sigue igual. 2221 2222 Por que hay que parchear CUATRO cosas y no solo `pasos`: 2223 2224 - `pasoActual` la calcula el nucleo con su `pasos` interna, no con la 2225 exportada. Sin parchearla, el bloque 3 pintaria la pantalla de la lista 2226 vieja: la lista diria "datos" y la pantalla seguiria siendo "donde". 2227 - Los puntos de progreso y el "Tres preguntas rapidas" tambien salen de 2228 esa `pasos` interna, y estan dentro de `cabeceraHTML`, que no se 2229 exporta. Se corrigen sobre el HTML ya generado, que es feo pero cabe 2230 aqui; la alternativa era reescribir los 30 KB del nucleo a mano. 2231 - El boton "Atras" de la pantalla de datos esta escrito a pelo (no pasa 2232 por `navHTML`), asi que cuando datos es la unica pantalla se queda un 2233 boton que no lleva a ningun sitio. Se quita cuando no hay nada detras. 2234 2235 Desde el 23/08/2026 corrige ademas las tres frases del nucleo que daban por 2236 hecho que el reparto siempre son tres entrenadores; ya no lo es. 2237 2238 Todo esto se cae solo el dia que el nucleo incorpore `sabidos`: sube su 2239 RE_RECOMENDADOR_VERSION y este embed se apaga entero. Mismo criterio y 2240 mismo guardia de version que el parche 2b. -->
2241<script> 2242(function () { 2243 "use strict"; 2244 2245 var N = window.__reRecNucleo; 2246 if (!N || !N.pasos || !N.estado) return; 2247 2248 // Igual que en el parche 2b: si el nucleo cambia de version, este parche se 2249 // aparta y vuelve el recorrido completo. Preguntar de mas es recuperable; 2250 // saltarse una pantalla que ha cambiado de contenido, no. 2251 if (window.RE_RECOMENDADOR_VERSION !== "1.0.0") { 2252 console.warn("[recomendador] parche de pasos desactivado: el nucleo es la version", window.RE_RECOMENDADOR_VERSION, "y el parche se escribio para la 1.0.0. Metele `sabidos` a pasos() y borra este embed."); 2253 return; 2254 } 2255 2256 var estado = N.estado; 2257 2258 // La pantalla "donde" son dos preguntas en una. Saltarla solo vale si ninguna 2259 // de las dos queda en el aire: online no necesita ciudad, presencial si. 2260 function sabemosDonde() { 2261 if (!estado.modalidad) return false; 2262 if (estado.modalidad === "Online") return true; 2263 return !!estado.ciudad || !!estado.ciudadDePagina; 2264 } 2265 2266 N.pasos = function pasos() { 2267 var sabidos = estado.sabidos || {}; 2268 var lista = []; 2269 if (!sabidos.donde || !sabemosDonde()) lista.push("donde"); 2270 if (!estado.objetivoDePagina && !(sabidos.objetivo && estado.objetivo)) lista.push("objetivo"); 2271 if (!(sabidos.cuando && estado.cuando)) lista.push("cuando"); 2272 lista.push("datos"); 2273 return lista; 2274 }; 2275 2276 N.pasoActual = function pasoActual() { 2277 return N.pasos()[estado.indice]; 2278 }; 2279 2280 var PALABRAS = { 1: "Una", 2: "Dos", 3: "Tres" }; 2281 2282 function dotsHTML() { 2283 var total = N.pasos().length; 2284 var html = ""; 2285 for (var i = 0; i < total; i++) { 2286 html += '<span class="' + (i <= estado.indice ? "re-rec-hecho" : "") + '"></span>'; 2287 } 2288 return '<div class="re-rec-dots">' + html + "</div>"; 2289 } 2290 2291 // Cuantos entrenadores van a recibir la solicitud de verdad. En la primera 2292 // pintada de la pantalla de datos todavia no se sabe (la busqueda va en 2293 // camino), y ahi no se inventa un numero. 2294 function conQuien() { 2295 var n = (estado.top3 || []).length; 2296 if (n === 1) return "este entrenador"; 2297 if (n) return "estos " + n + " entrenadores"; 2298 return "los entrenadores que mejor encajen contigo"; 2299 } 2300 2301 // Cirugia sobre el HTML que devuelve el nucleo. Los reemplazos son literales 2302 // suyos: si alguno deja de aparecer, no rompe nada (el replace no encuentra 2303 // y devuelve el mismo HTML), solo deja de corregir esa parte. 2304 function corregir(html) { 2305 html = html.replace(/<div class="re-rec-dots">[\s\S]*?<\/div>/, dotsHTML()); 2306 2307 var preguntas = N.pasos().length - 1; // la ultima pantalla no es pregunta 2308 html = html.replace( 2309 /(?:Una|Dos|Tres|Unas) preguntas rápidas/, 2310 preguntas === 1 ? "Una pregunta rápida" : (PALABRAS[preguntas] || "Unas") + " preguntas rápidas" 2311 ); 2312 2313 // El reparto dejo de forzar tres (parche 2b, 23/08/2026): si en la ciudad 2314 // solo encaja uno, se le manda a uno. Estas tres frases del nucleo daban 2315 // por hecho que siempre eran tres, asi que la primera pantalla prometia 2316 // algo que no se iba a cumplir y el aviso legal decia con cuanta gente se 2317 // comparte el dato sin que fuera verdad. 2318 html = html.replace("con los 3 entrenadores", "con los entrenadores"); 2319 html = html.replace("Enviaremos tu solicitud a los 3 entrenadores", "Enviaremos tu solicitud a los entrenadores"); 2320 html = html.replace("con estos 3 entrenadores", "con " + conQuien()); 2321 2322 if (estado.indice === 0) { 2323 html = html.replace('<button type="button" class="re-rec-atras" data-accion="atras">Atrás</button>', ""); 2324 } 2325 return html; 2326 } 2327 2328 ["pasoDondeHTML", "pasoObjetivoHTML", "pasoCuandoHTML", "pasoDatosHTML"].forEach(function (nombre) { 2329 var original = N[nombre]; 2330 if (typeof original !== "function") return; 2331 N[nombre] = function () { return corregir(original.apply(null, arguments)); }; 2332 }); 2333})(); 2334</script>
2334</div><div class="w-embed w-script"><!-- ============================================================ 2335 RedEntrenadores â Parche del pop-up recomendador: gancho manual en una ruta excluida 2336 2337 QUà ARREGLA: en /pregunta-al-experto el recomendador está en `RUTAS_EXCLUIDAS`, y 2338 el bloque 3 entiende "excluida" como "ni siquiera arranques": sale de `init()` antes 2339 de montar nada, asà que allà no existe `window.reRecomendador` y no hay forma de 2340 ofrecerlo a mano. Lo queremos ofrecer justo después de enviar la pregunta, que es 2341 cuando quien pregunta tiene el problema fresco y toda la atención puesta. 2342 2343 QUà HACE: en esa ruta, y solo en esa, 2344 2345 1. saca `/pregunta-al-experto` de `RUTAS_EXCLUIDAS` para que el bloque 3 monte el 2346 pop-up y deje el gancho `window.reRecomendador.abrir()` disponible; 2347 2. le hace creer que ya salió en esta sesión, envolviendo `leer` para la clave de 2348 sesión. `puedeSalir()` la consulta antes de cada disparo por tiempo o scroll, 2349 asà que el pop-up no sale por su cuenta; `abrir()` no la mira, asà que el 2350 gancho manual sigue funcionando. 2351 2352 Quien lo abre es el pop-up de confirmación de esa landing: 2353 `widget-pregunta-al-experto-2-popup.html`. 2354 2355 POR QUà UN PARCHE Y NO TOCAR EL BLOQUE 3: aquel embed son 28 KB que habrÃa que 2356 reproducir enteros para cambiarle dos lÃneas. Es el mismo motivo por el que ya 2357 existen `-escaparate` y `-saltar-sabidos`. El dÃa que se reescriba el bloque 3, 2358 el cambio limpio es que `excluida()` apague solo los disparos automáticos y deje 2359 el resto del `init()` en pie; entonces este embed se borra. 2360 2361 DÃNDE VA: cuarto Embed del componente Footer V1, ENTRE el núcleo (bloque 2, del que 2362 lee `window.__reRecNucleo`) y el comportamiento (bloque 3, al que tiene que 2363 adelantarse). Junto a los otros dos parches, delante del bloque 3. 2364 ============================================================ --> 2365
2366<script> 2367(function () { 2368 var N = window.__reRecNucleo; 2369 if (!N) return; // sin el núcleo no hay nada que parchear; el bloque 3 ya avisa por consola 2370 2371 // Guardia de versión, como los otros parches: si el núcleo cambia de versión, este 2372 // bloque se calla en vez de operar sobre algo que ya no es lo que espera. 2373 if (window.RE_RECOMENDADOR_VERSION !== "1.0.0") { 2374 console.warn("[re-recomendador] parche del gancho manual: version distinta, no se aplica"); 2375 return; 2376 } 2377 2378 var RUTA = "/pregunta-al-experto"; 2379 if (location.pathname.indexOf(RUTA) !== 0) return; 2380 2381 N.RUTAS_EXCLUIDAS = N.RUTAS_EXCLUIDAS.filter(function (r) { return r !== RUTA; }); 2382 2383 // El bloque 3 copia `N.leer` a una variable local en cuanto arranca, asà que esto 2384 // tiene que estar puesto antes que él. Solo miente sobre la clave de sesión: todo lo 2385 // demás (el apagado permanente, entre otros) sigue leyéndose de verdad. 2386 var leerOriginal = N.leer; 2387 N.leer = function (clave, almacen) { 2388 if (clave === N.CLAVE_SESION) return "1"; 2389 return leerOriginal(clave, almacen); 2390 }; 2391 2392 window.RE_RECOMENDADOR_GANCHO_MANUAL = true; 2393})(); 2394</script>
2394 2395</div><div class="w-embed w-script">
2395<script> 2396(function () { 2397 "use strict"; 2398 2399 // Segunda mitad del pop-up: cuando se abre, que pasa al pulsar y que se 2400 // mide. Depende del bloque anterior; si por lo que sea no ha cargado, aqui 2401 // no se hace nada en vez de reventar con un TypeError por cada pagina. 2402 var N = window.__reRecNucleo; 2403 if (!N) { 2404 console.error("[re-recomendador] falta el bloque 1, el pop-up no arranca"); 2405 return; 2406 } 2407 2408 // Se sacan a variables locales para que el resto del bloque se lea igual 2409 // que cuando todo esto era un unico fichero. 2410 var RUTAS_EXCLUIDAS = N.RUTAS_EXCLUIDAS; 2411 var SEGUNDOS_ESPERA = N.SEGUNDOS_ESPERA; 2412 var SCROLL_DISPARO = N.SCROLL_DISPARO; 2413 var CLAVE_APAGADO = N.CLAVE_APAGADO; 2414 var CLAVE_SESION = N.CLAVE_SESION; 2415 var MODALIDADES = N.MODALIDADES; 2416 var OBJETIVOS = N.OBJETIVOS; 2417 var CUANDOS = N.CUANDOS; 2418 var estado = N.estado; 2419 var pasos = N.pasos; 2420 var pasoActual = N.pasoActual; 2421 var pideCiudad = N.pideCiudad; 2422 var leer = N.leer; 2423 var escribir = N.escribir; 2424 var ciudadDeLaPagina = N.ciudadDeLaPagina; 2425 var objetivoDeLaPagina = N.objetivoDeLaPagina; 2426 var rest = N.rest; 2427 var cargarCiudades = N.cargarCiudades; 2428 var buscarTop3 = N.buscarTop3; 2429 var pasoDondeHTML = N.pasoDondeHTML; 2430 var pasoObjetivoHTML = N.pasoObjetivoHTML; 2431 var pasoCuandoHTML = N.pasoCuandoHTML; 2432 var pasoDatosHTML = N.pasoDatosHTML; 2433 var subtituloDatosHTML = N.subtituloDatosHTML; 2434 var confirmacionHTML = N.confirmacionHTML; 2435 var cierreHTML = N.cierreHTML; 2436 2437 var overlay = null; 2438 var caja = null; 2439 2440 // ------------------------------------------------ alto real de la pantalla 2441 2442 // Lo que el CSS no puede arreglar solo: ni `100dvh` ni un `position: fixed` 2443 // se enteran de que ha subido el teclado. El modal se sigue dibujando contra 2444 // el viewport de siempre y el teclado le tapa la parte de abajo, que en el 2445 // paso de datos es justo donde están el último campo y el botón de enviar. 2446 // La única medida que sà encoge con el teclado es `visualViewport`, asà que 2447 // mientras el pop-up está abierto el overlay se pega a ella. Hace falta 2448 // colocarlo además de medirlo (`top`/`left`) porque iOS no encoge el 2449 // viewport visible: lo desplaza. 2450 var vv = window.visualViewport || null; 2451 var ultimoEncaje = ""; 2452 2453 function ajustarAlViewport() { 2454 if (!overlay || !vv) return; 2455 var encaje = [vv.height, vv.width, vv.offsetTop, vv.offsetLeft].join("|"); 2456 if (encaje === ultimoEncaje) return; 2457 ultimoEncaje = encaje; 2458 // Con `top` + `height` y `left` + `width` puestos, el `inset: 0` del CSS 2459 // deja de mandar en el borde de abajo y en el de la derecha. 2460 overlay.style.top = vv.offsetTop + "px"; 2461 overlay.style.left = vv.offsetLeft + "px"; 2462 overlay.style.height = vv.height + "px"; 2463 overlay.style.width = vv.width + "px"; 2464 // Si estaba escribiendo, que su campo quede en el centro de lo que queda 2465 // visible. `center` y no `nearest`: `nearest` lo deja pegado al borde de 2466 // abajo, que es justo donde está la barra de botones pegada, y el campo 2467 // acaba detrás de ella. 2468 var foco = document.activeElement; 2469 if (foco && caja && foco !== caja && caja.contains(foco)) { 2470 try { foco.scrollIntoView({ block: "center" }); } catch (e) {} 2471 } 2472 } 2473 2474 function soltarViewport() { 2475 if (!overlay) return; 2476 ultimoEncaje = ""; 2477 overlay.style.top = ""; 2478 overlay.style.left = ""; 2479 overlay.style.height = ""; 2480 overlay.style.width = ""; 2481 } 2482 2483 // ----------------------------------------------------------------- render 2484 2485 var VISTAS = { 2486 donde: pasoDondeHTML, 2487 objetivo: pasoObjetivoHTML, 2488 cuando: pasoCuandoHTML, 2489 datos: pasoDatosHTML 2490 }; 2491 2492 var LISTAS = { 2493 donde: MODALIDADES, 2494 objetivo: OBJETIVOS, 2495 cuando: CUANDOS 2496 }; 2497 2498 function pintar() { 2499 var html; 2500 if (estado.fase === "confirmacion") html = confirmacionHTML(); 2501 else html = VISTAS[pasoActual()](); 2502 2503 caja.innerHTML = html; 2504 caja.classList.toggle("re-rec-ancho", estado.fase !== "preguntas"); 2505 caja.scrollTop = 0; 2506 2507 if (document.getElementById("re-rec-ciudad")) cargarCiudades(); 2508 if (estado.fase === "preguntas" && pasoActual() === "datos") precargarTop3(); 2509 // El foco automático solo en escritorio: en móvil abre el teclado y tapa 2510 // media pantalla justo cuando el usuario aún está leyendo la pregunta. 2511 var primerInput = caja.querySelector("input"); 2512 if (primerInput && window.innerWidth > 560) primerInput.focus(); 2513 2514 medirPantalla(); 2515 } 2516
2517 function claveBusqueda() { 2518 return [estado.ciudad, estado.objetivo, estado.modalidad].join("|"); 2519 } 2520 2521 // Se busca a los tres al llegar a la pantalla de datos, no al enviarla, para 2522 // poder nombrarlos antes de pedir el email. El resultado se guarda con la 2523 // clave de las respuestas que lo produjeron: si vuelve atrás y cambia una, 2524 // la caché deja de valer sola y `enviar` no manda la solicitud a los tres 2525 // entrenadores de una búsqueda que ya no es la suya. 2526 function precargarTop3() { 2527 var clave = claveBusqueda(); 2528 if (estado.top3Clave === clave) return; 2529 estado.top3Clave = clave; 2530 estado.top3 = []; 2531 2532 buscarTop3().then(function (top3) { 2533 if (estado.top3Clave !== clave) return; // llegó tarde: ya cambió algo 2534 estado.top3 = top3; 2535 // Se reescribe solo el subtÃtulo en vez de repintar la pantalla: un 2536 // `pintar()` aquà borrarÃa lo que el usuario ya estuviera escribiendo 2537 // en el nombre o el email. 2538 var sub = caja.querySelector(".re-rec-sub"); 2539 if (sub && estado.fase === "preguntas" && pasoActual() === "datos") { 2540 sub.innerHTML = subtituloDatosHTML(); 2541 } 2542 }).catch(function (err) { 2543 console.warn("[re-recomendador] no se pudo precargar el reparto", err); 2544 estado.top3Clave = null; // que `enviar` lo reintente 2545 }); 2546 } 2547 2548 function avanzar() { 2549 estado.error = ""; 2550 estado.indice += 1; 2551 pintar(); 2552 } 2553 2554 function elegirOpcion(valor) { 2555 var paso = pasoActual(); 2556 var lista = LISTAS[paso]; 2557 if (!lista) return; 2558 var op = lista.find(function (o) { return o.value === valor; }); 2559 if (!op) return; 2560 guardarCampos(); 2561 2562 if (paso === "donde") { 2563 estado.modalidad = op.value; 2564 medir("recomendador_respuesta", { paso: "modalidad", valor: op.value }); 2565 // Elegir modalidad avanza solo, salvo cuando eso destapa el campo de 2566 // ciudad: ahà hay que quedarse en la pantalla para que lo rellene. 2567 if (pideCiudad()) { estado.error = ""; return pintar(); } 2568 } else if (paso === "objetivo") { 2569 estado.objetivo = op.value; 2570 estado.objetivoRespondido = true; 2571 // El objetivo vacÃo es la opción "Aún no lo tengo claro", no un fallo: 2572 // se le pone nombre para poder contarla como una respuesta más. 2573 medir("recomendador_respuesta", { paso: "objetivo", valor: op.value || "Aun no lo tengo claro" }); 2574 } else if (paso === "cuando") { 2575 estado.cuando = op.value; 2576 medir("recomendador_respuesta", { paso: "cuando", valor: op.value }); 2577 } 2578 2579 avanzar(); 2580 } 2581 2582 function validarYAvanzar() { 2583 var input = document.getElementById("re-rec-ciudad"); 2584 if (!input) return; 2585 var ciudad = input.value.trim(); 2586 if (!ciudad) return mostrarError("Dinos en qué ciudad buscas."); 2587 estado.ciudad = ciudad; 2588 // La ciudad va tal cual la escribe: saber que piden Albacete y que ahà no 2589 // hay nadie vale más que el propio embudo. 2590 medir("recomendador_respuesta", { paso: "ciudad", valor: ciudad }); 2591 avanzar(); 2592 } 2593 2594 function mostrarError(texto) { 2595 estado.error = texto; 2596 var el = caja.querySelector(".re-rec-error"); 2597 if (el) { el.textContent = texto; el.style.display = "block"; } 2598 } 2599 2600 // Cualquier repintado tira el contenido de los campos, asà que antes de 2601 // movernos se guarda lo que haya escrito. Sin esto, quien vuelve atrás a 2602 // cambiar el objetivo se encuentra el nombre y el email en blanco al 2603 // regresar, y eso es un abandono servido en bandeja. 2604 function guardarCampos() { 2605 var campos = { nombre: "re-rec-nombre", email: "re-rec-email", telefono: "re-rec-telefono", ciudad: "re-rec-ciudad" }; 2606 Object.keys(campos).forEach(function (clave) { 2607 var el = document.getElementById(campos[clave]); 2608 if (el) estado[clave] = el.value.trim(); 2609 }); 2610 } 2611 2612 function retroceder() { 2613 guardarCampos(); 2614 medir("recomendador_atras", { desde: pasoActual() }); 2615 estado.error = ""; 2616 if (estado.indice > 0) estado.indice -= 1; 2617 pintar(); 2618 } 2619 2620 // ----------------------------------------------------------------- envÃo 2621 2622 // re-validar: copia inline de `_shared/validacion-contacto.ts`. Los embeds de 2623 // Webflow son sueltos y no pueden importar nada, asà que cuando se toque 2624 // aquel fichero hay que tocar también esto. Deja el número en E.164 o 2625 // devuelve null: 9 dÃgitos se asumen españoles, 00 pasa a +, y lo que no 2626 // encaje se rechaza en vez de guardarse en crudo. 2627 function reNormalizarTelefono(valor) { 2628 if (typeof valor !== "string") return null; 2629 var limpio = valor.replace(/[\s\-.()]/g, ""); 2630 if (!limpio) return null; 2631 var candidato; 2632 if (limpio.charAt(0) === "+") candidato = limpio; 2633 else if (limpio.slice(0, 2) === "00") candidato = "+" + limpio.slice(2); 2634 else if (/^\d{9}$/.test(limpio)) candidato = "+34" + limpio; 2635 else return null; 2636 return /^\+\d{6,15}$/.test(candidato) ? candidato : null; 2637 } 2638 2639 function enviar() { 2640 var nombre = (document.getElementById("re-rec-nombre").value || "").trim(); 2641 var email = (document.getElementById("re-rec-email").value || "").trim(); 2642 var telefono = (document.getElementById("re-rec-telefono").value || "").trim(); 2643
2644 estado.nombre = nombre; 2645 estado.email = email; 2646 estado.telefono = telefono; 2647 2648 if (!nombre) { 2649 medir("recomendador_datos_invalidos", { campo: "nombre" }); 2650 return mostrarError("Introduce tu nombre."); 2651 } 2652 // El email es el campo obligatorio desde que existe el chat: es lo que 2653 // permite avisar de un mensaje y lo que abre la cuenta. 2654 if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { 2655 medir("recomendador_datos_invalidos", { campo: "email" }); 2656 return mostrarError(email 2657 ? "Revisa el email, parece que falta algo." 2658 : "Introduce tu email para que puedan escribirte."); 2659 } 2660 // El teléfono es opcional, pero si lo escribe tiene que servir para llamar. 2661 // Se manda ya normalizado para que el backend guarde la misma forma. 2662 var telefonoE164 = telefono ? reNormalizarTelefono(telefono) : null; 2663 if (telefono && !telefonoE164) { 2664 medir("recomendador_datos_invalidos", { campo: "telefono" }); 2665 return mostrarError("Revisa el teléfono: 9 dÃgitos, o con prefijo internacional (+34â¦)."); 2666 } 2667 2668 estado.enviando = true; 2669 estado.error = ""; 2670 pintar(); 2671 2672 // Si la precarga de esta misma búsqueda ya trajo el reparto, se manda a 2673 // esos: son exactamente los que se le acaban de nombrar en pantalla. 2674 var yaEstan = estado.top3.length && estado.top3Clave === claveBusqueda(); 2675 (yaEstan ? Promise.resolve(estado.top3) : buscarTop3()).then(function (top3) { 2676 if (!top3.length) throw new Error("sin_entrenadores"); 2677 estado.top3 = top3; 2678 2679 return rest("/functions/v1/solicitar-contacto-entrenador", { 2680 method: "POST", 2681 body: { 2682 entrenador_slugs: top3.map(function (e) { return e.slug; }), 2683 nombre: nombre, 2684 email: email, 2685 telefono: telefonoE164, 2686 zona: estado.ciudad || null, 2687 objetivo: estado.objetivo || null, 2688 modalidad: estado.modalidad || null, 2689 cuando_empieza: estado.cuando || null, 2690 tipo_solicitud: "multiple", 2691 origen: "popup-recomendador" 2692 } 2693 }); 2694 }).then(function (datos) { 2695 escribir(CLAVE_APAGADO, "enviado", localStorage); 2696 estado.claimToken = (datos && datos.claim_token) || null; 2697 estado.enviando = false; 2698 estado.fase = "confirmacion"; 2699 pintar(); 2700 medir("recomendador_solicitud_enviada", { 2701 entrenadores: estado.top3.length, 2702 ciudad: estado.ciudad || null, 2703 objetivo: estado.objetivo || "Aun no lo tengo claro", 2704 modalidad: estado.modalidad || null, 2705 cuando: estado.cuando || null, 2706 // El email es obligatorio, asà que no se mide: serÃa siempre true. El 2707 // teléfono sÃ, que ahora es el opcional y su tasa es justo lo que dice 2708 // si el cambio ha costado el dato con el que el entrenador llama. 2709 con_telefono: !!telefonoE164, 2710 segundos: estado.abiertoEn ? Math.round((Date.now() - estado.abiertoEn) / 1000) : null 2711 }); 2712 }).catch(function (err) { 2713 console.error("[re-recomendador] envÃo", err); 2714 // "sin_entrenadores" no es una averÃa: es demanda en una ciudad sin 2715 // cobertura. Separarlo del fallo de red es la diferencia entre arreglar 2716 // el widget y salir a captar entrenadores en esa ciudad. 2717 medir("recomendador_envio_fallido", { 2718 motivo: err.message === "sin_entrenadores" ? "sin_entrenadores" : "error", 2719 ciudad: estado.ciudad || null 2720 }); 2721 estado.enviando = false; 2722 estado.error = err.message === "sin_entrenadores" 2723 ? "No hemos encontrado entrenadores para esa búsqueda. Prueba con otra ciudad." 2724 : "No hemos podido enviar tu solicitud. Inténtalo de nuevo en un momento."; 2725 pintar(); 2726 }); 2727 } 2728 2729 function guardarPerfil() { 2730 var btn = caja.querySelector('[data-accion="guardar-perfil"]'); 2731 var errEl = document.getElementById("re-rec-p2-error"); 2732 var numero = function (id) { 2733 var v = (document.getElementById(id).value || "").trim(); 2734 return v === "" ? null : Number(v); 2735 }; 2736 2737 btn.disabled = true; 2738 btn.textContent = "Guardandoâ¦"; 2739 if (errEl) errEl.style.display = "none"; 2740 2741 rest("/functions/v1/completar-perfil-usuario", { 2742 method: "POST", 2743 body: { 2744 claim_token: estado.claimToken, 2745 // El email ya viajó con la solicitud en el paso 1. Mandarlo otra vez 2746 // no aportarÃa nada: el RPC respeta el que la ficha ya tiene. 2747 email: null, 2748 objetivo: estado.objetivo || null, 2749 modalidad: estado.modalidad || null, 2750 zona: estado.ciudad || null, 2751 edad: numero("re-rec-edad"), 2752 peso_kg: numero("re-rec-peso"), 2753 altura_cm: numero("re-rec-altura"), 2754 disponibilidad: (document.getElementById("re-rec-disponibilidad").value || "").trim() || null, 2755 nivel_actividad: document.getElementById("re-rec-actividad").value || null, 2756 comentarios: (document.getElementById("re-rec-notas").value || "").trim() || null 2757 } 2758 }).then(function (datos) { 2759 // La cuenta se ofrece porque la ficha tiene email: lo dio en el paso 1 y 2760 // la solicitud se creó con él. Ya no se mira `email_guardado`, que desde 2761 // este cambio viene siempre a false âsignifica "no he escrito nada nuevo 2762 // porque la ficha ya tenÃa email", que es exactamente lo que pasa ahoraâ
2763 // y usarlo escondÃa la oferta de cuenta a todo el mundo. 2764 var ofreceCuenta = !!estado.email; 2765 medir("recomendador_perfil_guardado", {}); 2766 caja.innerHTML = cierreHTML(true, ofreceCuenta); 2767 }).catch(function (err) { 2768 console.error("[re-recomendador] paso 2", err); 2769 if (errEl) { 2770 errEl.textContent = "No hemos podido guardarlo. Tu solicitud sà se envió, no te preocupes."; 2771 errEl.style.display = "block"; 2772 } 2773 btn.disabled = false; 2774 btn.textContent = "Enviar mis datos"; 2775 }); 2776 } 2777 2778 function crearCuenta() { 2779 var btn = caja.querySelector('[data-accion="crear-cuenta"]'); 2780 var errEl = document.getElementById("re-rec-cuenta-error"); 2781 btn.disabled = true; 2782 btn.textContent = "Creandoâ¦"; 2783 if (errEl) errEl.style.display = "none"; 2784 2785 rest("/functions/v1/crear-cuenta-usuario", { 2786 method: "POST", 2787 body: { claim_token: estado.claimToken } 2788 }).then(function (datos) { 2789 medir("recomendador_cuenta_creada", { requiere_email: !!datos.requiere_email }); 2790 // Cuenta nueva: se entra en el acto. Si ya existÃa, la función manda el 2791 // enlace al buzón, que ahà es lo único que prueba que el email es suyo. 2792 if (datos.requiere_email) { 2793 btn.parentElement.innerHTML = 2794 "<h4>Ya tienes cuenta</h4>" + 2795 '<p class="re-rec-sub" style="font-size:14px;margin:4px 0 0">Te hemos mandado un enlace a tu email para entrar y ver esta solicitud junto a las anteriores.</p>'; 2796 } else { 2797 btn.textContent = "Entrandoâ¦"; 2798 window.location.href = datos.url; 2799 } 2800 }).catch(function (err) { 2801 console.error("[re-recomendador] crear cuenta", err); 2802 if (errEl) { 2803 errEl.textContent = "No hemos podido crear la cuenta. Tu solicitud sà se envió; inténtalo de nuevo en un momento."; 2804 errEl.style.display = "block"; 2805 } 2806 btn.disabled = false; 2807 btn.textContent = "Crear mi cuenta"; 2808 }); 2809 } 2810 2811 // Amplitude no envÃa nada hasta que cookieyes acepte analytics; hasta 2812 // entonces los eventos se acumulan en su cola. Nunca se cuelga una conversión de 2813 // Ads de este pop-up: el activador de GTM de las landings de paid va por el 2814 // selector `.re-modal-success`, y por eso aquà no se usa esa clase. 2815 // 2816 // No hace falta encolar nada aquÃ: el bloque de Amplitude del head del sitio 2817 // deja un stub `{ _q: [], track }` desde el primer momento y vacÃa esa cola 2818 // cuando el módulo arranca, asà que un `track` disparado antes del 2819 // consentimiento no se pierde. Si el consentimiento no llega nunca, la cola 2820 // se descarta sin enviarse, que es justo lo que debe pasar. 2821 function medir(evento, props) { 2822 if (!estado.midiendo) return; 2823 var datos = props || {}; 2824 // Segmentar por página es lo que distingue "el pop-up funciona" de "el 2825 // pop-up funciona en las páginas de ciudad y en la home no". 2826 datos.pagina = location.pathname; 2827 try { 2828 if (window.amplitude && window.amplitude.track) window.amplitude.track(evento, datos); 2829 } catch (e) {} 2830 } 2831 2832 // Una misma pantalla se repinta varias veces âal destapar "Ver más", al 2833 // abrirse el campo de ciudad, al enseñar un errorâ y ninguno de esos 2834 // repintados es una pantalla nueva. Contando solo la primera, el embudo mide 2835 // personas y no renders. 2836 function medirPantalla() { 2837 var lista = pasos(); 2838 var clave = estado.fase === "confirmacion" ? "confirmacion" : pasoActual(); 2839 if (estado.vistas[clave]) return; 2840 estado.vistas[clave] = true; 2841 medir("recomendador_paso_visto", { 2842 paso: clave, 2843 numero: estado.fase === "confirmacion" ? lista.length + 1 : estado.indice + 1, 2844 total: lista.length 2845 }); 2846 } 2847 2848 // ------------------------------------------------------------ abrir/cerrar 2849 2850 function abrir(motivo) { 2851 if (!overlay || overlay.classList.contains("re-rec-abierto")) return; 2852 escribir(CLAVE_SESION, "1", sessionStorage); 2853 estado.indice = 0; 2854 estado.fase = "preguntas"; 2855 estado.vistas = {}; 2856 estado.abiertoEn = Date.now(); 2857 estado.midiendo = motivo !== "preview" && motivo !== "debug"; 2858 // Va antes de `pintar()` para que la apertura quede por delante del primer 2859 // paso en el orden de eventos, y no detrás. 2860 medir("recomendador_popup_abierto", { 2861 motivo: motivo || "manual", 2862 ciudad_de_pagina: estado.ciudadDePagina, 2863 objetivo_de_pagina: estado.objetivoDePagina, 2864 pasos: pasos().length 2865 }); 2866 pintar(); 2867 overlay.classList.add("re-rec-abierto"); 2868 document.body.style.overflow = "hidden"; 2869 ajustarAlViewport(); 2870 if (vv) { 2871 vv.addEventListener("resize", ajustarAlViewport); 2872 vv.addEventListener("scroll", ajustarAlViewport); 2873 } 2874 } 2875 2876 function cerrar() { 2877 if (!overlay) return; 2878 var estabaAbierto = overlay.classList.contains("re-rec-abierto"); 2879 overlay.classList.remove("re-rec-abierto"); 2880 document.body.style.overflow = ""; 2881 if (vv) { 2882 vv.removeEventListener("resize", ajustarAlViewport); 2883 vv.removeEventListener("scroll", ajustarAlViewport); 2884 } 2885 soltarViewport(); 2886 // El evento más útil de todos: dice en qué pantalla exacta se cae la gente. 2887 if (estabaAbierto) { 2888 medir("recomendador_cerrado", { 2889 paso: estado.fase === "preguntas" ? pasoActual() : estado.fase,
2890 habia_enviado: estado.fase !== "preguntas", 2891 segundos: estado.abiertoEn ? Math.round((Date.now() - estado.abiertoEn) / 1000) : null 2892 }); 2893 } 2894 // Solo se apaga para siempre si ya habÃa enviado; cerrar en la primera 2895 // pregunta no deberÃa castigarle a no volver a verlo nunca en el sitio. 2896 if (estado.fase !== "preguntas") escribir(CLAVE_APAGADO, "enviado", localStorage); 2897 } 2898 2899 // Con otro modal abierto no se dispara: en la ficha del entrenador convive 2900 // con el pop-up de contacto ("Nuevo contacto v2"), y dos modales apilados 2901 // es la forma más rápida de perder el lead que ya estaba escribiendo. 2902 function hayOtroModalAbierto() { 2903 // El banner de cookies entra en la lista: a un visitante nuevo le sale 2904 // CookieYes nada más entrar, y taparlo con este pop-up es a la vez una 2905 // mala experiencia y un problema de consentimiento. 2906 var candidatos = [ 2907 ".pop-up-contacto", 2908 "#re-overlay.re-open-modal-overlay", 2909 ".re-modal-overlay", 2910 "[data-re-modal-abierto]", 2911 ".cky-consent-container", 2912 ".cky-modal" 2913 ]; 2914 return candidatos.some(function (sel) { 2915 var el = document.querySelector(sel); 2916 if (!el) return false; 2917 // No basta con mirar `display`: los pop-ups de Webflow los abre y cierra 2918 // IX2, que muchas veces deja el elemento en el flujo y solo le anima la 2919 // opacidad. Dando por abierto todo lo que no sea `display:none`, este 2920 // pop-up no volverÃa a salir nunca en las 95 fichas de entrenador â y 2921 // sin un error en consola que lo delate. Se mide lo que de verdad se ve. 2922 var estilo = window.getComputedStyle(el); 2923 if (estilo.display === "none" || estilo.visibility === "hidden") return false; 2924 if (Number(estilo.opacity) === 0) return false; 2925 var caja = el.getBoundingClientRect(); 2926 return caja.width > 0 && caja.height > 0; 2927 }); 2928 } 2929 2930 function puedeSalir() { 2931 if (leer(CLAVE_APAGADO, localStorage)) return false; 2932 if (leer(CLAVE_SESION, sessionStorage)) return false; 2933 if (hayOtroModalAbierto()) return false; 2934 // Si está escribiendo en cualquier formulario de la página, no se le 2935 // interrumpe: quien ya está rellenando algo no necesita que le recomienden nada. 2936 var activo = document.activeElement; 2937 if (activo && /^(INPUT|TEXTAREA|SELECT)$/.test(activo.tagName)) return false; 2938 return true; 2939 } 2940 2941 // ------------------------------------------------------------------ init 2942 2943 function excluida() { 2944 var ruta = location.pathname; 2945 return RUTAS_EXCLUIDAS.some(function (r) { return ruta.indexOf(r) === 0; }); 2946 } 2947 2948 function init() { 2949 if (excluida()) return; 2950 2951 overlay = document.createElement("div"); 2952 overlay.id = "re-rec-overlay"; 2953 overlay.innerHTML = '<div class="re-rec-modal" role="dialog" aria-modal="true" aria-label="Te ayudamos a decidir"></div>'; 2954 document.body.appendChild(overlay); 2955 caja = overlay.querySelector(".re-rec-modal"); 2956 2957 estado.ciudad = ciudadDeLaPagina(); 2958 estado.ciudadDePagina = !!estado.ciudad; 2959 var obj = objetivoDeLaPagina(); 2960 if (obj) { estado.objetivo = obj; estado.objetivoDePagina = true; } 2961 2962 overlay.addEventListener("click", function (ev) { 2963 if (ev.target === overlay) return cerrar(); 2964 var btn = ev.target.closest("[data-accion]"); 2965 if (!btn) return; 2966 var accion = btn.getAttribute("data-accion"); 2967 if (accion === "cerrar") cerrar(); 2968 else if (accion === "opcion") elegirOpcion(btn.getAttribute("data-valor")); 2969 else if (accion === "ver-mas") { 2970 // Si mucha gente lo pulsa, las opciones visibles que fija `VISIBLES` no 2971 // son las que cubren a la gente que llega de verdad. 2972 medir("recomendador_ver_mas", { paso: pasoActual() }); 2973 estado.abiertas[pasoActual()] = true; 2974 pintar(); 2975 } 2976 else if (accion === "siguiente") validarYAvanzar(); 2977 else if (accion === "atras") retroceder(); 2978 else if (accion === "enviar") enviar(); 2979 else if (accion === "guardar-perfil") guardarPerfil(); 2980 else if (accion === "crear-cuenta") crearCuenta(); 2981 }); 2982 2983 document.addEventListener("keydown", function (ev) { 2984 if (ev.key === "Escape" && overlay.classList.contains("re-rec-abierto")) cerrar
2984(); 2985 }); 2986 2987 // Disparo por tiempo o por scroll, lo que llegue antes. 2988 var disparado = false; 2989 var entrada = Date.now(); 2990 2991 function disparar(motivo) { 2992 if (disparado) return false; 2993 if (!puedeSalir()) return false; 2994 disparado = true; 2995 abrir(motivo); 2996 return true; 2997 } 2998 2999 // 30/08/2026: sale nada más entrar, no a los 25 segundos. Lo único que se 3000 // espera es a que CookieYes pinte y despache su banner â`puedeSalir` ya lo 3001 // cuenta como modal abierto, pero tarda unas décimas en aparecer y sin esa 3002 // espera mÃnima el pop-up se le adelantaba y se quedaba debajoâ, asà que la 3003 // espera baja de 25 segundos a segundo y medio. 3004 // 3005 // El disparo se reintenta en vez de perderse: en el primer intento puede 3006 // haber un motivo temporal para no salir (el banner de cookies sin 3007 // responder, otro modal abierto, el usuario escribiendo en un campo) y con 3008 // un `setTimeout` suelto esa única oportunidad se gastaba ahÃ. El sondeo 3009 // baja a 400 ms âantes 5 sâ para que en cuanto se aceptan las cookies el 3010 // pop-up salga acto seguido y no hasta cinco segundos después. 3011 // 3012 // `SEGUNDOS_ESPERA` del bloque 2 se queda sin usar a propósito: reescribir 3013 // aquel embed de 34 KB solo para bajar una constante no compensa. 3014 var ESPERA_MS = 1500; 3015 var reintento = setInterval(function () { 3016 var transcurrido = Date.now() - entrada; 3017 if (disparado || transcurrido > 180000) return clearInterval(reintento); 3018 if (transcurrido >= ESPERA_MS) disparar("tiempo"); 3019 }, 400); 3020 3021 window.addEventListener("scroll", function () { 3022 var alto = document.documentElement.scrollHeight - window.innerHeight; 3023 if (alto <= 0) return; 3024 if (window.scrollY / alto >= SCROLL_DISPARO) disparar("scroll"); 3025 }, { passive: true }); 3026 3027 // Gancho manual: para un botón "¿No lo tienes claro?" en cualquier sitio 3028 // de la página, y para poder probarlo sin esperar al disparo automático. 3029 window.reRecomendador = { 3030 abrir: function () { abrir("manual"); }, 3031 cerrar: cerrar, 3032 reset: function () { 3033 try { localStorage.removeItem(CLAVE_APAGADO); sessionStorage.removeItem(CLAVE_SESION); } catch (e) {} 3034 }, 3035 // Pinta la confirmación con el reparto real de una búsqueda, pero SIN 3036 // enviar nada: sirve para revisar el copy y las fichas sin avisar a tres 3037 // entrenadores de verdad cada vez que se quiere mirar esa pantalla. 3038 // `tokenFalso` sirve para ver el bloque de perfil y el de crear cuenta, 3039 // que solo se pintan cuando hay `claim_token`. Guardar desde ahà falla 3040 // (el token no existe), que es justo lo que debe pasar. 3041 previsualizarConfirmacion: function (ciudad, objetivo, modalidad, tokenFalso) { 3042 estado.ciudad = ciudad || estado.ciudad; 3043 estado.objetivo = objetivo || ""; 3044 estado.modalidad = modalidad || "En un gimnasio"; 3045 return buscarTop3().then(function (top3) { 3046 // abrir() reinicia el estado al primer paso, asà que va antes de 3047 // dejar la fase en "confirmacion" y no después. 3048 abrir("preview"); 3049 estado.top3 = top3; 3050 estado.claimToken = tokenFalso ? "token-de-prueba" : null; 3051 estado.fase = "confirmacion"; 3052 pintar(); 3053 return top3.map(function (e) { return e.slug; }); 3054 }); 3055 } 3056 }; 3057 3058 if (new URLSearchParams(location.search).get("recomendador") === "1") abrir("debug"); 3059 } 3060 3061 // Si en consola hay VERSION pero no LISTO, es que falta el segundo embed. 3062 window.RE_RECOMENDADOR_LISTO = true; 3063 3064 if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init); 3065 else init(); 3066})(); 3067</script>
3067 3068</div><div class="w-embed w-script"><!-- RedEntrenadores - Origen de campana (embed en el componente Footer V1) --> 3069<!-- Va en el Footer V1 porque tiene que correr en las 115 paginas: el custom 3070 code de sitio haria lo mismo, pero ese campo son 20 KB y reescribirlo 3071 entero por API es justo el tamano que se corta a medias. 3072 3073 El problema que resuelve: cada formulario leia `document.referrer` en el 3074 momento de enviarse, y para entonces ya era interno. Quien llegaba por un 3075 enlace de campana, aterrizaba en una landing y contactaba desde la ficha 3076 de un entrenador nacia en `usuarios` sin origen ninguno. Y las utm de la 3077 URL de llegada se perdian en el primer salto. 3078 3079 Hace tres cosas: 3080 1. Guarda la primera llegada de la sesion y la sirve en `window.rtOrigen()`. 3081 2. Envuelve `fetch` para que toda solicitud a `solicitar-contacto-entrenador` 3082 lleve el origen. Asi el formulario de la ficha (22 KB) y el de la landing 3083 de paid quedan cubiertos sin tocarles una linea. 3084 3. Arrastra las utm a los enlaces que apuntan a /encuentra-entrenador, que 3085 es el unico sitio del que el wizard las lee. 3086 3087 Por que (3) en vez de envolver tambien la RPC del wizard: supabase-js 3088 resuelve `fetch` al construir el cliente, y ese cliente nace al parsearse 3089 su embed, antes que este. La envoltura no le llegaria. 3090 3091 Fuente versionada: widgets/widget-origen-campana.html -->
3092<script> 3093(function () { 3094 var CLAVE = 're-origen'; 3095 var ENDPOINT_CONTACTO = '/functions/v1/solicitar-contacto-entrenador'; 3096 var DESTINO_WIZARD = '/encuentra-entrenador'; 3097 3098 // Copia en memoria: en navegacion privada o con el almacenamiento capado, 3099 // sessionStorage lanza al escribir. Sin esto el origen se perderia en el 3100 // primer salto interno justo en los navegadores mas restrictivos. 3101 var enMemoria = null; 3102 3103 function leer() { 3104 try { 3105 var crudo = sessionStorage.getItem(CLAVE); 3106 if (crudo) return JSON.parse(crudo); 3107 } catch (e) { /* almacenamiento capado: tiramos de la copia en memoria */ } 3108 return enMemoria; 3109 } 3110 3111 function guardar(o) { 3112 enMemoria = o; 3113 try { sessionStorage.setItem(CLAVE, JSON.stringify(o)); } catch (e) {} 3114 } 3115 3116 function referenteExterno() { 3117 try { 3118 if (!document.referrer) return null; 3119 var r = new URL(document.referrer); 3120 if (r.hostname === location.hostname) return null; 3121 // Sin query: el referente de un buscador lleva la consulta del usuario. 3122 return r.origin + r.pathname; 3123 } catch (e) { return null; } 3124 } 3125 3126 var params = new URLSearchParams(location.search); 3127 var llegada = { 3128 pagina: location.pathname, 3129 referrer: referenteExterno(), 3130 utm_source: params.get('utm_source'), 3131 utm_medium: params.get('utm_medium'), 3132 utm_campaign: params.get('utm_campaign') 3133 }; 3134 3135 // Manda el primer toque de la sesion, con una excepcion: una llegada nueva 3136 // CON utm_source la pisa. Asi quien vuelve desde un enlace de campana se 3137 // atribuye a esa campana y no al referente con el que abrio el navegador 3138 // hace media hora. 3139 if (!leer() || llegada.utm_source) guardar(llegada); 3140 3141 // Devuelve siempre las cinco claves: quien lo consume puede volcarlo en el 3142 // payload sin comprobar nada. 3143 window.rtOrigen = function () { 3144 var o = leer() || {}; 3145 return { 3146 pagina: o.pagina || location.pathname, 3147 referrer: o.referrer || null, 3148 utm_source: o.utm_source || null, 3149 utm_medium: o.utm_medium || null, 3150 utm_campaign: o.utm_campaign || null 3151 }; 3152 }; 3153 3154 // 2. El origen viaja con cada solicitud de contacto 3155 // 3156 // Envolver `fetch` en vez de editar los formularios: son embeds de 22 y 40 3157 // KB y reescribirlos por API no cabe. La envoltura es defensiva de arriba 3158 // abajo: cualquier fallo cae al fetch original con los argumentos tal cual, 3159 // porque aqui pasa el envio del formulario de contacto y romperlo significa 3160 // perder leads. 3161 var fetchOriginal = window.fetch; 3162 3163 if (typeof fetchOriginal === 'function') { 3164 window.fetch = function (entrada, opciones) { 3165 try { 3166 var url = typeof entrada === 'string' ? entrada : (entrada && entrada.url) || ''; 3167 if (url.indexOf(ENDPOINT_CONTACTO) !== -1 && opciones && typeof opciones.body === 'string') { 3168 var cuerpo = JSON.parse(opciones.body); 3169 if (cuerpo && typeof cuerpo === 'object' && !Array.isArray(cuerpo)) { 3170 var o = window.rtOrigen(); 3171 // Solo se rellena lo que falta: un formulario que ya mande su 3172 // origen manda el. 3173 if (!cuerpo.pagina_origen) cuerpo.pagina_origen = location.pathname; 3174 if (!cuerpo.referrer_externo && o.referrer) cuerpo.referrer_externo = o.referrer; 3175 if (!cuerpo.utm_source && o.utm_source) cuerpo.utm_source = o.utm_source; 3176 if (!cuerpo.utm_medium && o.utm_medium) cuerpo.utm_medium = o.utm_medium; 3177 if (!cuerpo.utm_campaign && o.utm_campaign) cuerpo.utm_campaign = o.utm_campaign; 3178 3179 var copia = {}; 3180 for (var k in opciones) { if (Object.prototype.hasOwnProperty.call(opciones, k)) copia[k] = opciones[k]; } 3181 copia.body = JSON.stringify(cuerpo); 3182 return fetchOriginal.call(this, entrada, copia); 3183 } 3184 } 3185 } catch (e) { /* cuerpo ilegible o lo que sea: va el original */ } 3186 return fetchOriginal.apply(this, arguments); 3187 }; 3188 } 3189 3190 // 3. Las utm llegan al wizard 3191 // 3192 // El wizard de /encuentra-entrenador lee las utm de SU url. Quien llega 3193 // desde una landing interna las habria perdido por el camino, asi que se 3194 // las pegamos a los enlaces que apuntan alli. Solo a esos: llenar de utm 3195 // todos los enlaces del sitio ensuciaria las URLs indexables y contaria 3196 // cada pagina interna como una visita de campana. 3197 function propagarAlWizard() { 3198 var o = window.rtOrigen(); 3199 if (!o.utm_source && !o.utm_medium && !o.utm_campaign) return; 3200 3201 var enlaces = document.querySelectorAll('a[href*="' + DESTINO_WIZARD + '"]'); 3202 Array.prototype.forEach.call(enlaces, function (a) {
3203 try { 3204 if (a.getAttribute('data-re-origen')) return; 3205 var u = new URL(a.href, location.origin); 3206 if (u.hostname !== location.hostname) return; 3207 if (u.pathname.indexOf(DESTINO_WIZARD) !== 0) return; 3208 if (o.utm_source && !u.searchParams.get('utm_source')) u.searchParams.set('utm_source', o.utm_source); 3209 if (o.utm_medium && !u.searchParams.get('utm_medium')) u.searchParams.set('utm_medium', o.utm_medium); 3210 if (o.utm_campaign && !u.searchParams.get('utm_campaign')) u.searchParams.set('utm_campaign', o.utm_campaign); 3211 a.setAttribute('href', u.pathname + u.search + u.hash); 3212 a.setAttribute('data-re-origen', '1'); 3213 } catch (e) {} 3214 }); 3215 } 3216 3217 if (document.readyState === 'loading') { 3218 document.addEventListener('DOMContentLoaded', propagarAlWizard); 3219 } else { 3220 propagarAlWizard(); 3221 } 3222 // Los listados y el pop-up recomendador pintan enlaces despues de cargar. 3223 setTimeout(propagarAlWizard, 1500); 3224})(); 3225</script>
3225</div></footer>
3225<script src="https://d3e54v103j8qbb.cloudfront.net/js/jquery-3.5.1.min.dc5e7f18c8.js?site=68c5a729c71fa3f37858fbbe" type="text/javascript" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
3225<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe/js/webflow.975777c1.283252e848e5c0e3.js" type="text/javascript" integrity="sha384-Zo/MMcWLLsMN+SlQPBtgBQDPWtbRCmp8ijx9WnkKyzy4q4eYV7FVR0+rAXA6bZmH" crossorigin="anonymous"></script>
3225<!-- Google Tag Manager (noscript) -->
vendor: 68 bytes, lines 3225-3226
3225 3226<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=
3226GTM-KBD83QKS
vendor: 83 bytes, lines 3226-3227
3226" 3227height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
3228<!-- End Google Tag Manager (noscript) --> 3229 3230<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3231 PREGUNTA AL EXPERTO â filtro por categoria + contador 3232 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ --> 3233<style> 3234 .re-cats__link.is-active { background:#089F3B !important; border-color:#089F3B !important; color:#fff !important; } 3235 .re-cats__link.is-active [class*="re-cats__icon-"] { filter: brightness(0) invert(1); } 3236 .re-cats__check { position:absolute; top:6px; right:8px; width:18px; height:18px; border-radius:50%; background:#fff; color:#089F3B; font-size:12px; font-weight:800; line-height:1; display:flex; align-items:center; justify-content:center; } 3237 .re-cats__reset { display:block; width:fit-content; margin:16px auto 0; padding:10px 20px; border:1.5px solid #1B1E22; border-radius:999px; font-size:13px; font-weight:700; color:#1B1E22; text-decoration:none; cursor:pointer; } 3238 .re-cats__reset:hover { background:#1B1E22; color:#fff; } 3239 .re-oq-title.is-filtered { font-size:20px !important; text-transform:none !important; line-height:1.25 !important; text-align:center; } 3240 .otras_preguntas-header:has(.re-oq-title.is-filtered) { display:block !important; } 3241 #re-oq-clear { display:block !important; width:100% !important; text-align:center !important; margin-top:8px !important; float:none !important; } 3242 /* Boton de continuidad al final del bloque */ 3243 .re-oq-explorar { display:block; width:fit-content; margin:32px auto 0; padding:14px 28px; border-radius:8px; background:#089F3B; color:#fff !important; font-size:13px; font-weight:700; text-transform:uppercase; letter-spacing:.02em; text-decoration:none; text-align:center; } 3244 .re-oq-explorar:hover { background:#067A2D; } 3245</style>
3246<script> 3247(function () { 3248 if (window.location.pathname.indexOf('/pregunta-al-experto') !== 0) return; 3249 3250 var CAT = new URLSearchParams(window.location.search).get('categoria'); 3251 var contadoresPintados = false; 3252 var yaColocado = false; 3253 3254 function textoVer(n) { 3255 if (!n) return 'Leer respuesta del entrenador â'; 3256 if (n === 1) return 'Leer respuesta del entrenador â'; 3257 return 'Leer las ' + n + ' respuestas â'; 3258 } 3259 3260 function quitarFiltro(e) { 3261 if (e) { e.preventDefault(); e.stopPropagation(); } 3262 CAT = null; 3263 try { history.replaceState(null, '', window.location.pathname); } catch (err) {} 3264 3265 Array.prototype.forEach.call(document.querySelectorAll('.re-cats__link'), function (chip) { 3266 chip.classList.remove('is-active'); 3267 var c = chip.querySelector('.re-cats__check'); 3268 if (c) c.parentNode.removeChild(c); 3269 var orig = chip.getAttribute('data-re-href'); 3270 if (orig) chip.setAttribute('href', orig); 3271 }); 3272 3273 Array.prototype.forEach.call(document.querySelectorAll('.re-oq-card'), function (card) { 3274 var wrap = card.closest ? (card.closest('.w-dyn-item') || card) : card; 3275 wrap.style.setProperty('display', '', 'important'); 3276 }); 3277 3278 var header = document.querySelector('.re-oq-title'); 3279 if (header) { 3280 header.removeAttribute('data-re-done'); 3281 header.classList.remove('is-filtered'); 3282 header.textContent = '¿Tienes prisa? Encuentra tu respuesta al instante'; 3283 } 3284 var clear = document.getElementById('re-oq-clear'); 3285 if (clear) clear.parentNode.removeChild(clear); 3286 var reset = document.getElementById('re-cats-reset'); 3287 if (reset) reset.parentNode.removeChild(reset); 3288 var expl = document.getElementById('re-oq-explorar'); 3289 if (expl) expl.style.display = 'none'; 3290 } 3291 3292 function aplicar() { 3293 var chips = document.querySelectorAll('.re-cats__link'); 3294 var cards = document.querySelectorAll('.re-oq-card'); 3295 3296 Array.prototype.forEach.call(chips, function (chip) { 3297 if (!chip.getAttribute('data-re-href')) { 3298 chip.setAttribute('data-re-href', chip.getAttribute('href') || ''); 3299 } 3300 }); 3301 3302 if (CAT) { 3303 Array.prototype.forEach.call(chips, function (chip) { 3304 var orig = chip.getAttribute('data-re-href') || ''; 3305 if (orig.indexOf('categoria=' + CAT) !== -1) { 3306 chip.classList.add('is-active'); 3307 chip.setAttribute('title', 'Quitar filtro'); 3308 if (!chip.getAttribute('data-re-toggle')) { 3309 chip.setAttribute('data-re-toggle', '1'); 3310 chip.addEventListener('click', function (e) { 3311 if (chip.classList.contains('is-active')) quitarFiltro(e); 3312 }); 3313 } 3314 if (!chip.querySelector('.re-cats__check')) { 3315 var c = document.createElement('span'); 3316 c.className = 're-cats__check'; 3317 c.textContent = 'â'; 3318 chip.appendChild(c); 3319 } 3320 } 3321 }); 3322 3323 var listaCats = document.querySelector('.re-cats__list'); 3324 if (listaCats && !document.getElementById('re-cats-reset')) { 3325 var r = document.createElement('a'); 3326 r.id = 're-cats-reset'; 3327 r.className = 're-cats__reset'; 3328 r.href = '#'; 3329 r.textContent = 'Ver todas las categorias'; 3330 r.addEventListener('click', quitarFiltro); 3331 listaCats.insertAdjacentElement('afterend', r); 3332 } 3333 3334 if (!yaColocado) { 3335 yaColocado = true; 3336 var seccionCats = document.querySelector('.re-cats'); 3337 if (seccionCats && window.scrollY < 40) { 3338 var top = seccionCats.getBoundingClientRect().top + window.scrollY - 70; 3339 window.scrollTo(0, top > 0 ? top : 0); 3340 } 3341 } 3342 } 3343 3344 if (!cards.length) return false; 3345 3346 // Boton de continuidad al final del bloque de preguntas 3347 var seccionOQ = document.querySelector('.re-oq-section'); 3348 var listaOQ = document.querySelector('.re-oq-section .w-dyn-list'); 3349 if (seccionOQ && listaOQ && !document.getElementById('re-oq-explorar')) { 3350 var ex = document.createElement('a'); 3351 ex.id = 're-oq-explorar'; 3352 ex.className = 're-oq-explorar'; 3353 ex.href = '/pregunta-al-experto'; 3354 ex.textContent = 'Explorar toda la biblioteca de preguntas'; 3355 ex.addEventListener('click', function (e) { 3356 if (CAT) { quitarFiltro(e); } 3357 }); 3358 listaOQ.insertAdjacentElement('afterend', ex); 3359 } 3360 var explBtn = document.getElementById('re-oq-explorar'); 3361 if (explBtn) explBtn.style.display = CAT ? 'block' : 'none'; 3362 3363 if (CAT) { 3364 var filtro = CAT.trim().toLowerCase(); 3365 var visibles = 0; 3366 Array.prototype.forEach.call(cards, function (card) { 3367 var h = card.querySelector('.re-oq-hidden'); 3368 var cat = h ? (h.textContent || '').trim().toLowerCase() : ''; 3369 var ok = (cat === filtro); 3370 var wrap = card.closest ? (card.closest('.w-dyn-item') || card) : card; 3371 wrap.style.setProperty('display', ok ? '' : 'none', 'important'); 3372 if (ok) visibles++; 3373 }); 3374 3375 var header = document.querySelector('.re-oq-title'); 3376 if (header && !header.getAttribute('data-re-done')) { 3377 header.setAttribute('data-re-done', '1'); 3378 header.classList.add('is-filtered'); 3379 var nombre = filtro.replace(/-/g, ' ');
3380 nombre = nombre.charAt(0).toUpperCase() + nombre.slice(1); 3381 header.textContent = visibles > 0 3382 ? nombre + ' (' + visibles + ')' 3383 : 'Todavia no hay preguntas sobre ' + nombre.toLowerCase(); 3384 3385 if (!document.getElementById('re-oq-clear')) { 3386 var a = document.createElement('a'); 3387 a.id = 're-oq-clear'; 3388 a.href = '#'; 3389 a.textContent = 'Ver todas las preguntas â'; 3390 a.style.cssText = 'display:block;width:100%;text-align:center;margin-top:8px;font-size:13px;font-weight:700;color:#089F3B;text-decoration:none;'; 3391 a.addEventListener('click', quitarFiltro); 3392 var contenedor = header.parentElement || header; 3393 contenedor.insertAdjacentElement('afterend', a); 3394 } 3395 } 3396 } 3397 3398 Array.prototype.forEach.call(cards, function (card) { 3399 var detail = card.querySelector('.re-oq-detail'); 3400 var toggle = card.querySelector('[data-oq-toggle]'); 3401 if (!detail || !toggle || toggle.getAttribute('data-re-bound')) return; 3402 toggle.setAttribute('data-re-bound', '1'); 3403 toggle.style.display = (detail.scrollHeight > detail.clientHeight + 2) ? 'block' : 'none'; 3404 toggle.addEventListener('click', function (e) { 3405 e.preventDefault(); 3406 e.stopPropagation(); 3407 var exp = detail.classList.toggle('re-oq-detail--expanded'); 3408 toggle.textContent = exp ? 'Ver menos' : 'Ver mas'; 3409 }); 3410 }); 3411 3412 if (!contadoresPintados) { 3413 contadoresPintados = true; 3414 cargarSupabase(function (db) { 3415 // Nada de respuestas(count): PostgREST lo traduce a una referencia de 3416 // fila entera y la clave publicable tiene los permisos de `respuestas` 3417 // por columna (migracion 20260819150000), asi que devuelve 403. Se 3418 // piden los id de una columna concedida y se cuentan aqui. 3419 db.from('preguntas').select('slug, respuestas(id)').then(function (res) { 3420 if (res.error) { console.warn('[PAE] contador de respuestas:', res.error.message); return; } 3421 var mapa = {}; 3422 (res.data || []).forEach(function (r) { 3423 mapa[r.slug] = (r.respuestas || []).length; 3424 }); 3425 Array.prototype.forEach.call(document.querySelectorAll('.re-oq-card'), function (card) { 3426 var slug = (card.getAttribute('href') || '').split('/').filter(Boolean).pop(); 3427 var v = card.querySelector('[data-oq-ver]'); 3428 if (!v) return; 3429 v.textContent = textoVer(mapa[slug]); 3430 }); 3431 }); 3432 }); 3433 } 3434 3435 return true; 3436 } 3437 3438 function cargarSupabase(cb) { 3439 function go() { 3440 try { 3441 cb(window.supabase.createClient( 3442 'https://yrzanatzshdpvypazgeb.supabase.co', 3443 'sb_publishable_O4cIYIJcDs3_GcbjNHdd9g_e2cQKOUD' 3444 )); 3445 } catch (e) {} 3446 } 3447 if (window.supabase && window.supabase.createClient) { go(); return; } 3448 var s = document.createElement('script'); 3449 s.src = 'https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2'; 3450 s.onload = go; 3451 document.head.appendChild(s); 3452 } 3453 3454 function arrancar() { 3455 aplicar(); 3456 var intentos = 0; 3457 var t = setInterval(function () { 3458 intentos++; 3459 aplicar(); 3460 if (intentos > 20) clearInterval(t); 3461 }, 300); 3462 3463 var lista = document.querySelector('.w-dyn-list'); 3464 if (lista && window.MutationObserver) { 3465 new MutationObserver(function () { aplicar(); }).observe(lista, { childList: true, subtree: true }); 3466 } 3467 } 3468 3469 if (document.readyState === 'loading') { 3470 document.addEventListener('DOMContentLoaded', arrancar); 3471 } else { 3472 arrancar(); 3473 } 3474})(); 3475</script>
3475 3476 3477<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3478 BOTÃN FLOTANTE DE WHATSAPP 3479 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ --> 3480 3481<a 3482 href="https://wa.me/34633590498?text=Hola%2C%20me%20gustar%C3%ADa%20m%C3%A1s%20informaci%C3%B3n" 3483 target="_blank" 3484 rel="noopener noreferrer" 3485 class="wa-float-btn" 3486 aria-label="Contactar por WhatsApp" 3487> 3488 <svg viewBox="0 0 24 24" width="30" height="30" fill="#FFFFFF" xmlns="http://www.w3.org/2000/svg"> 3489 <path d="M12 2a10 10 0 0 0-8.61 15.06L2 22l5.06-1.33A10 10 0 1 0 12 2zm0 18.13a8.1 8.1 0 0 1-4.14-1.14l-.3-.18-3.08.81.82-3-.2-.31A8.13 8.13 0 1 1 12 20.13z"/> 3490 <path d="M16.57 13.72c-.25-.13-1.47-.72-1.7-.81s-.4-.13-.57.13-.65.81-.8.97-.3.19-.55.06a6.76 6.76 0 0 1-2-1.23 7.47 7.47 0 0 1-1.38-1.72c-.14-.25 0-.38.11-.5s.25-.3.37-.44a1.7 1.7 0 0 0 .25-.42.46.46 0 0 0 0-.44c-.06-.13-.57-1.37-.78-1.87s-.42-.43-.57-.44h-.49a.94.94 0 0 0-.68.32 2.87 2.87 0 0 0-.9 2.13 5 5 0 0 0 1.05 2.64 11.4 11.4 0 0 0 4.36 3.85c.61.26 1.08.42 1.45.54a3.48 3.48 0 0 0 1.6.1 2.62 2.62 0 0 0 1.72-1.21 2.13 2.13 0 0 0 .15-1.21c-.06-.11-.23-.17-.48-.3z"/> 3491 </svg> 3492</a> 3493 3494<style> 3495 .wa-float-btn { 3496 position: fixed; 3497 bottom: 24px; 3498 right: 24px; 3499 width: 60px; 3500 height: 60px; 3501 background-color: #25D366; 3502 border-radius: 50%; 3503 display: flex; 3504 align-items: center; 3505 justify-content: center; 3506 box-shadow: 0 4px 14px rgba(0,0,0,0.25); 3507 z-index: 9999; 3508 text-decoration: none; 3509 transition: transform 0.2s ease, box-shadow 0.2s ease; 3510 } 3511 .wa-float-btn:hover { 3512 transform: scale(1.08); 3513 box-shadow: 0 6px 18px rgba(0,0,0,0.3); 3514 } 3515 @media (max-width: 480px) { 3516 .wa-float-btn { 3517 width: 52px; 3518 height: 52px; 3519 bottom: 16px; 3520 right: 16px; 3521 } 3522 } 3523</style> 3524 3525<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3526 OCULTAR SECCIONES CON LISTAS CMS VACÃAS (ciudad + objetivo) 3527 Solo listas de primer nivel: una lista anidada vacÃa dentro 3528 de una tarjeta (p. ej. las especialidades de un entrenador) 3529 no debe esconder la sección entera. 3530 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ -->
3531<script> 3532document.querySelectorAll('.w-dyn-empty').forEach(function (emptyEl) { 3533 var lista = emptyEl.closest('.w-dyn-list'); 3534 if (!lista) return; 3535 3536 // Lista anidada dentro de otra lista CMS (una tarjeta): no decide por la sección. 3537 if (lista.parentElement && lista.parentElement.closest('.w-dyn-list')) return; 3538 3539 // Webflow deja el estado vacio en el HTML aunque la lista tenga elementos: 3540 // lo tapa con w-dyn-hide. Sin esta comprobacion se escondia un bloque lleno. 3541 if (lista.querySelector('.w-dyn-item')) return; 3542 3543 // En la ficha del entrenador cada bloque es un .entrenador_sobre-m dentro de 3544 // una unica <section> que ademas lleva el formulario de contacto, asi que la 3545 // regla de abajo no llegaba y se quedaba a la vista el "No items found." de 3546 // Modalidad (Ismael Diaz, 20/08/2026). Aqui manda el bloque, no la seccion. 3547 var bloque = emptyEl.closest('.entrenador_sobre-m'); 3548 if (bloque) { 3549 bloque.style.display = 'none'; 3550 return; 3551 } 3552 3553 var section = emptyEl.closest('section'); 3554 if (section && !section.querySelector('form')) { 3555 section.style.display = 'none'; 3556 } 3557}); 3558</script>
3558 3559 3560<!-- ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 3561 EMAIL â aviso de errata en el dominio (sugiere, no bloquea) 3562 Vale para TODOS los formularios del sitio, presentes y futuros: 3563 engancha por delegacion a cualquier <input> de email, no hay 3564 que tocar los widgets uno a uno. 3565 La regla vive en supabase/functions/_shared/validacion-contacto.ts 3566 (`sugerenciaDominioEmail`); esto es su copia de navegador. 3567 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ --> 3568<style> 3569 .re-mailerrata { margin-top:6px; padding:8px 10px; border-radius:8px; background:#FEF3C7; color:#92400E; font-size:12.5px; line-height:1.35; } 3570 .re-mailerrata__fix { padding:0; border:0; background:none; font:inherit; font-weight:700; color:#92400E; text-decoration:underline; cursor:pointer; } 3571</style>
3572<script> 3573(function () { 3574 /* Dominios que damos por buenos. Un dominio de esta lista no se marca nunca, 3575 y un dominio a 1-2 letras de uno de ellos es el que se sugiere. */ 3576 var DOMINIOS = [ 3577 'gmail.com','googlemail.com', 3578 'hotmail.com','hotmail.es','hotmail.fr','hotmail.it','hotmail.co.uk','hotmail.com.ar','hotmail.com.mx', 3579 'outlook.com','outlook.es','outlook.fr','outlook.it', 3580 'live.com','live.com.mx','live.co.uk','msn.com', 3581 'yahoo.com','yahoo.es','yahoo.it','yahoo.fr','yahoo.co.uk','yahoo.com.ar','yahoo.com.mx', 3582 'icloud.com','me.com','mac.com', 3583 'aol.com','protonmail.com','proton.me','gmx.es','gmx.com','gmx.net','zoho.com','mail.com', 3584 'terra.es','telefonica.net','movistar.es','orange.es','wanadoo.es','ya.com','arrakis.es','euskalnet.net' 3585 ]; 3586 3587 function distancia(a, b) { 3588 var m = a.length, n = b.length, i, j, fila = [], ant, tmp; 3589 for (j = 0; j <= n; j++) fila[j] = j; 3590 for (i = 1; i <= m; i++) { 3591 ant = fila[0]; fila[0] = i; 3592 for (j = 1; j <= n; j++) { 3593 tmp = fila[j]; 3594 fila[j] = Math.min(fila[j] + 1, fila[j - 1] + 1, ant + (a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1)); 3595 ant = tmp; 3596 } 3597 } 3598 return fila[n]; 3599 } 3600 3601 /* Devuelve el email corregido o null. Solo mira el dominio, y solo cuando el 3602 email ya tiene forma valida: el "falta algo" lo dice el widget, no esto. 3603 El tope de 2 letras se reserva a dominios largos; en uno corto (ya.com, 3604 me.com) dos cambios ya son otro dominio distinto, no una errata. */ 3605 function sugerencia(valor) { 3606 var limpio = String(valor == null ? '' : valor).trim().toLowerCase(); 3607 if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(limpio)) return null; 3608 var corte = limpio.lastIndexOf('@'); 3609 var local = limpio.slice(0, corte), dominio = limpio.slice(corte + 1); 3610 if (DOMINIOS.indexOf(dominio) !== -1) return null; 3611 var tope = dominio.length >= 8 ? 2 : 1; 3612 var mejor = null, mejorD = 3, i, d; 3613 for (i = 0; i < DOMINIOS.length; i++) { 3614 d = distancia(dominio, DOMINIOS[i]); 3615 if (d <= tope && d < mejorD) { mejorD = d; mejor = DOMINIOS[i]; } 3616 } 3617 return mejor ? local + '@' + mejor : null; 3618 } 3619 3620 function esCampoEmail(el) { 3621 if (!el || el.tagName !== 'INPUT') return false; 3622 var t = (el.getAttribute('type') || '').toLowerCase(); 3623 if (t === 'email') return true; 3624 if (t && t !== 'text') return false; 3625 if ((el.getAttribute('inputmode') || '').toLowerCase() === 'email') return true; 3626 return /e-?mail|correo/i.test((el.name || '') + ' ' + (el.id || '') + ' ' + (el.getAttribute('autocomplete') || '')); 3627 } 3628 3629 function quitar(input) { 3630 var sig = input.nextElementSibling; 3631 if (sig && sig.classList && sig.classList.contains('re-mailerrata')) sig.parentNode.removeChild(sig); 3632 } 3633 3634 function medir(evento, dominio) { 3635 try { if (window.amplitude && window.amplitude.track) window.amplitude.track(evento, { dominio: dominio }); } catch (e) {} 3636 } 3637 3638 function revisar(input) { 3639 quitar(input); 3640 if (!input.isConnected) return; 3641 var propuesta = sugerencia(input.value); 3642 if (!propuesta) return; 3643 3644 var caja = document.createElement('div'); 3645 caja.className = 're-mailerrata'; 3646 caja.setAttribute('role', 'status'); 3647 caja.appendChild(document.createTextNode('¿Quisiste decir ')); 3648 3649 var btn = document.createElement('button'); 3650 btn.type = 'button'; 3651 btn.className = 're-mailerrata__fix'; 3652 btn.textContent = propuesta; 3653 btn.addEventListener('click', function () { 3654 input.value = propuesta;
3655 try { 3656 input.dispatchEvent(new Event('input', { bubbles: true })); 3657 input.dispatchEvent(new Event('change', { bubbles: true })); 3658 } catch (e) {} 3659 quitar(input); 3660 input.focus(); 3661 medir('email_errata_corregida', propuesta.split('@')[1]); 3662 }); 3663 3664 caja.appendChild(btn); 3665 caja.appendChild(document.createTextNode('?')); 3666 input.insertAdjacentElement('afterend', caja); 3667 medir('email_errata_sugerida', input.value.split('@').pop().toLowerCase()); 3668 } 3669 3670 /* Al escribir se propone con retardo (asi el aviso ya esta en pantalla antes 3671 de que llegue al boton de enviar) y al salir del campo, de inmediato. */ 3672 var reloj = null; 3673 document.addEventListener('input', function (e) { 3674 if (!esCampoEmail(e.target)) return; 3675 quitar(e.target); 3676 clearTimeout(reloj); 3677 var input = e.target; 3678 reloj = setTimeout(function () { revisar(input); }, 500); 3679 }); 3680 document.addEventListener('focusout', function (e) { 3681 if (!esCampoEmail(e.target)) return; 3682 clearTimeout(reloj); 3683 revisar(e.target); 3684 }); 3685})(); 3686</script>
3686 3687 3688<!-- Master Tag add just before the closing </body> tag --> 3689
3689<script src="https://www.dwin1.com/130189.js" type="text/javascript" defer="defer"></script>
vendor: 1 bytes, line 3689
3689
3690<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6a815b2ecfc98b6d18a9afab%2Fvisitas_ficha_publica-1.0.0.js" type="text/javascript"></script>
3690<script src="https://cdn.prod.website-files.com/68c5a729c71fa3f37858fbbe%2F689e5ba67671442434f3ca35%2F6a9562a243b6dd3757592da5%2Flimpiarcamposcontacto-1.0.0.js" type="text/javascript"></script>
3690</body></html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.