{"id":3091,"date":"2026-07-04T20:29:53","date_gmt":"2026-07-04T20:29:53","guid":{"rendered":"https:\/\/grupoprodex.com\/?page_id=3091"},"modified":"2026-08-30T19:51:07","modified_gmt":"2026-08-30T19:51:07","slug":"calculadora-de-reinversion","status":"publish","type":"page","link":"https:\/\/grupoprodex.com\/en\/calculadora-de-reinversion\/","title":{"rendered":"Calculadora de reinversi\u00f3n"},"content":{"rendered":"<!-- ============================================================\r\n     PRODEX - Calculadora de Reinversion - bloque para WordPress\r\n     VERSION 2026-08-30 - 66837546076d\r\n     Debe coincidir con lo pegado en WPCode #3092 (ver README.md).\r\n\r\n     CSS acotado a .prodex-calc y JS acotado al contenedor.\r\n     100% ASCII (markup y CSS por refs; JS por escapes \\uXXXX).\r\n     Pegar ENTERO en WPCode #3092 - no es un parche, sustituye al anterior.\r\n     Generado por wordpress\/build-calc-embed.py - no editar a mano:\r\n     edita pages\/calculadora-reinversion.html y regenera.\r\n     Incluye el parche de nivel 2 (02-ago): ya no se pega aparte.\r\n\r\n     AVISOS (revisar antes de publicar):\r\n     1. El token del ERP (X-Lead-Token) viaja en el JS: es visible en el\r\n        codigo fuente. Conviene un proxy servidor o rotarlo.\r\n     2. Contenido fiscal PENDIENTE DE VALIDACION LETRADA (F. Bravo, 25-jul).\r\n     3. El fetch va a erp.grupoprodex.com: requiere CORS habilitado alli.\r\n     ============================================================ -->\r\n\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;500;600&family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,400&display=swap\" rel=\"stylesheet\">\r\n\r\n<style>\r\n\r\n        .prodex-calc{\r\n            --gold:#c3b49b; --gold-soft:#efeae0; --ink:#27323d; --ink2:#1b222a;\r\n            --gray:#56626e; --line:#e4dfd5; --cream:#f6f4f0; --white:#ffffff;\r\n            --ok:#3f7d5f; --warn:#8a7350; --taupe:#555047; --radius:4px;\r\n            --serif:'Playfair Display',Georgia,'Times New Roman',serif;\r\n            --sans:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;\r\n        }\r\n        .prodex-calc *{box-sizing:border-box;margin:0;padding:0}\r\n        .prodex-calc{font-family:var(--sans);font-weight:300;color:var(--ink);background:var(--cream);line-height:1.6;-webkit-font-smoothing:antialiased;overflow-x:hidden}\r\n        .prodex-calc .wrap{max-width:1080px;margin:0 auto;padding:0 18px;overflow-x:hidden}\r\n        \/* HEADER *\/\r\n        .prodex-calc .top{background:linear-gradient(165deg,var(--ink) 0%,var(--ink2) 75%);padding:44px 0 38px;text-align:center}\r\n        .prodex-calc .top img{height:60px;width:auto;max-width:80%}\r\n        .prodex-calc .top .tag{color:var(--gold);font-family:var(--sans);font-size:12px;font-weight:500;letter-spacing:.28em;margin-top:16px;text-transform:uppercase}\r\n        \/* HERO *\/\r\n        .prodex-calc .hero{text-align:center;padding:44px 0 14px}\r\n        .prodex-calc .hero h1{font-family:var(--serif);font-weight:500;font-size:clamp(1.8rem,4vw,2.3rem);line-height:1.18;letter-spacing:-.01em}\r\n        .prodex-calc .hero h1 .g{color:var(--taupe);font-style:italic}\r\n        .prodex-calc .hero p{font-family:var(--sans);color:var(--gray);font-size:16px;max-width:680px;margin:14px auto 0;line-height:1.7}\r\n        .prodex-calc .rule{height:2px;width:60px;background:var(--gold);margin:24px auto}\r\n        \/* MODULES *\/\r\n        .prodex-calc .mod{background:var(--white);border:1px solid rgba(39,50,61,.08);border-top:3px solid var(--gold);border-radius:var(--radius);padding:26px 26px 22px;margin:22px 0;box-shadow:0 10px 30px -20px rgba(27,34,42,.28)}\r\n        .prodex-calc .mod-h{display:flex;align-items:center;gap:14px;margin-bottom:6px}\r\n        .prodex-calc .mod-num{font-size:34px;font-weight:500;color:var(--gold);font-family:var(--serif);line-height:1}\r\n        .prodex-calc .mod-t{font-family:var(--serif);font-size:20px;font-weight:500;letter-spacing:0}\r\n        .prodex-calc .mod-sub{font-family:var(--sans);color:var(--gray);font-size:13.5px;margin:2px 0 18px 0;line-height:1.6}\r\n        \/* GRID FIELDS *\/\r\n        .prodex-calc .grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 22px}\r\n        @media(max-width:680px){.prodex-calc .grid{grid-template-columns:1fr}}\r\n        .prodex-calc .f{display:flex;flex-direction:column;gap:5px}\r\n        .prodex-calc .f.full{grid-column:1\/-1}\r\n        .prodex-calc label{font-family:var(--sans);font-size:13px;font-weight:500;color:var(--ink)}\r\n        label .hint{display:block;font-weight:300;color:var(--gray);font-size:11.5px;margin-top:2px}\r\n        .prodex-calc input[type=text],.prodex-calc input[type=number],.prodex-calc input[type=email],.prodex-calc input[type=tel]{\r\n            font-family:var(--sans);font-size:15px;font-weight:300;padding:10px 12px;border:1px solid var(--line);\r\n            border-radius:var(--radius);background:var(--white);color:var(--ink);width:100%;max-width:100%}\r\n        .prodex-calc input:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px var(--gold-soft)}\r\n        \/* desplegables del bloque: mismo aspecto que los campos de texto *\/\r\n        .prodex-calc .f select{font-family:var(--sans);font-size:15px;font-weight:300;padding:10px 12px;border:1px solid var(--line);\r\n            border-radius:var(--radius);background:var(--white);color:var(--ink);width:100%;max-width:100%}\r\n        .prodex-calc .f select:focus{outline:none;border-color:var(--gold);box-shadow:0 0 0 3px var(--gold-soft)}\r\n        .prodex-calc .chk{display:flex;align-items:flex-start;gap:9px;font-family:var(--sans);font-size:13.5px;color:var(--ink);cursor:pointer;position:relative}\r\n        .prodex-calc .chk input{margin-top:2px;width:16px;height:16px;accent-color:var(--taupe);flex:0 0 auto}\r\n        \/* Nada mas en este bloque baja de 44px de lado salvo el cuadrado de la\r\n           casilla, que mide 16. Lo que de verdad marca la casilla es la etiqueta\r\n           entera (262x108 medidos), pero quien apunta al cuadradito tiene 16px\r\n           de margen de error y Chrome de Android no agranda objetivos como hace\r\n           Safari. Este rectangulo invisible PERTENECE a la <label>, asi que\r\n           pulsarlo marca la casilla igual. Crece hacia arriba y hacia abajo,\r\n           nunca hacia la derecha, para no pisar el enlace de la politica de\r\n           privacidad -- un enlace tiene que seguir siendo un enlace. Los 11px de\r\n           arriba caben en el hueco de 14 que hay hasta el campo de correo\r\n           (medido a 360 y a 412), asi que no le roba ni un pixel. Solo en movil:\r\n           en escritorio no cambia nada. *\/\r\n        @media(max-width:680px){\r\n            .prodex-calc .chk::before{content:\"\";position:absolute;left:0;top:-11px;width:25px;height:42px}\r\n        }\r\n        \/* filas de propietarios *\/\r\n        .prodex-calc .owner-row{display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap}\r\n        .prodex-calc .owner-row .onum{font-family:var(--sans);font-size:12px;font-weight:500;color:var(--gray);min-width:78px;flex:0 0 auto}\r\n        .prodex-calc .owner-row input{font-family:var(--sans);font-size:14px;padding:8px 10px;border:1px solid var(--line);border-radius:var(--radius);min-width:0;width:70px}\r\n        .prodex-calc .owner-row .oedad{width:70px;flex:0 0 auto}\r\n        .prodex-calc .owner-row .opct{width:70px;flex:0 0 auto}\r\n        .prodex-calc .owner-row select.ores{font-family:var(--sans);font-size:13px;padding:8px 10px;border:1px solid var(--line);border-radius:var(--radius);background:var(--white);color:var(--ink);flex:0 0 auto}\r\n        .prodex-calc .owner-row .osuf{font-family:var(--sans);font-size:12px;color:var(--gray);flex:0 0 auto}\r\n        \/* alertas de oportunidad *\/\r\n        .prodex-calc .alert{display:flex;gap:12px;align-items:flex-start;background:var(--gold-soft);border-left:3px solid var(--gold);border-radius:var(--radius);padding:14px 16px;margin-bottom:10px}\r\n        .prodex-calc .alert .ic{font-size:16px;flex:0 0 auto;margin-top:1px;color:var(--taupe)}\r\n        .prodex-calc .alert .tx{font-family:var(--sans);font-size:13.5px;color:var(--ink);line-height:1.5}\r\n        .prodex-calc .alert .tx b{color:var(--ink);font-weight:500}\r\n        \/* CALC ROWS (resultados intermedios) *\/\r\n        .prodex-calc .calc{margin-top:18px;border-top:1px solid var(--line);padding-top:14px}\r\n        .prodex-calc .crow{display:flex;justify-content:space-between;align-items:baseline;padding:8px 0;font-family:var(--sans);font-size:14.5px;border-bottom:1px dashed var(--line)}\r\n        .prodex-calc .crow .lbl{color:var(--ink)}\r\n        .prodex-calc .crow .lbl small{display:block;color:var(--gray);font-size:11.5px}\r\n        .prodex-calc .crow .val{font-weight:500;text-align:right;white-space:nowrap}\r\n        .prodex-calc .crow .val small{display:block;font-weight:300;color:var(--gray);font-size:11px}\r\n        .prodex-calc .crow.minus .val{color:#9a3b2f}\r\n        .prodex-calc .crow.plus .val{color:var(--ink)}\r\n        \/* TOTAL BOX *\/\r\n        .prodex-calc .total{display:flex;justify-content:space-between;align-items:center;margin-top:16px;border-radius:var(--radius);overflow:hidden;border:1px solid var(--gold)}\r\n        .prodex-calc .total .tl{background:var(--ink);color:#fff;font-family:var(--sans);font-weight:500;letter-spacing:.08em;padding:16px 20px;font-size:14px;flex:1;text-transform:uppercase}\r\n        .prodex-calc .total .tv{background:var(--gold-soft);color:var(--ink);font-family:var(--serif);font-weight:500;font-size:24px;padding:14px 20px;white-space:nowrap}\r\n        .prodex-calc .total.cost .tl{background:var(--ink2)}\r\n        \/* GRAND RESULT *\/\r\n        .prodex-calc .grand{background:linear-gradient(165deg,var(--ink) 0%,var(--ink2) 80%);border-radius:var(--radius);padding:34px 30px;margin:26px 0;text-align:center;color:#fff}\r\n        .prodex-calc .grand .gl{font-family:var(--sans);letter-spacing:.24em;color:var(--gold);font-size:12px;text-transform:uppercase}\r\n        .prodex-calc .grand .gv{font-family:var(--serif);font-size:46px;font-weight:500;margin:10px 0 4px}\r\n        .prodex-calc .grand .gd{font-family:var(--sans);color:rgba(255,255,255,.65);font-size:13.5px}\r\n        .prodex-calc .grand .split{display:flex;justify-content:center;gap:40px;margin-top:20px;flex-wrap:wrap}\r\n        .prodex-calc .grand .split div{font-family:var(--sans)}\r\n        .prodex-calc .grand .split .n{font-family:var(--serif);font-size:22px;font-weight:500;color:#fff}\r\n        .prodex-calc .grand .split .l{font-size:11px;color:rgba(255,255,255,.55);letter-spacing:.1em;text-transform:uppercase;margin-top:2px}\r\n        \/* NOTE *\/\r\n        .prodex-calc .note{font-family:var(--sans);font-size:12px;color:var(--gray);font-style:italic;background:var(--gold-soft);border-radius:var(--radius);padding:12px 16px;margin:8px 0;line-height:1.6}\r\n        .prodex-calc .badge{display:inline-block;font-family:var(--sans);font-size:11px;color:var(--warn);font-style:normal;margin-left:6px}\r\n        \/* LEAD FORM *\/\r\n        .prodex-calc .lead{background:var(--white);border:1px solid var(--gold);border-radius:var(--radius);padding:30px;margin:26px 0}\r\n        .prodex-calc .lead h2{font-family:var(--serif);font-weight:500;font-size:22px;margin-bottom:6px}\r\n        .prodex-calc .lead p.s{font-family:var(--sans);color:var(--gray);font-size:14px;margin-bottom:18px;line-height:1.6}\r\n        .prodex-calc .lead .antes-enviar{font-family:var(--sans);font-size:15px;color:var(--ink);line-height:1.65;margin:16px 0 0}\r\n        .prodex-calc #oportunidades h2{font-family:var(--serif);font-weight:500}\r\n        .prodex-calc .btn{display:block;width:100%;background:var(--gold);color:var(--ink2);border:none;border-radius:var(--radius);\r\n            font-family:var(--sans);font-weight:600;font-size:15px;letter-spacing:.02em;padding:15px;cursor:pointer;margin-top:10px;transition:.2s}\r\n        .prodex-calc .btn:hover{background:var(--white);box-shadow:inset 0 0 0 1px var(--gold)}\r\n        .prodex-calc .btn:disabled{opacity:.45;cursor:not-allowed}\r\n        .prodex-calc .err{color:#9a3b2f;font-family:var(--sans);font-size:12.5px;margin-top:3px;display:none}\r\n        \/* Aviso de envio fallido: mas grande que los .err de campo porque no es\r\n           te falta un dato, es no hemos podido y hay que poder leerlo y\r\n           marcar el numero sin buscarlo. *\/\r\n        .prodex-calc .err-envio{display:none;margin-top:12px;padding:12px 14px;border:1px solid #9a3b2f;\r\n                   border-radius:8px;background:#fdf4f2;color:#9a3b2f;\r\n                   font-family:var(--sans);font-size:14px;line-height:1.55}\r\n        .prodex-calc .err-envio a{color:#9a3b2f;font-weight:bold;white-space:nowrap}\r\n        .prodex-calc .ok-msg{display:none;background:var(--gold-soft);border:1px solid var(--gold);border-radius:var(--radius);padding:18px;text-align:center;font-family:var(--sans);color:var(--ink);margin-top:14px;line-height:1.6}\r\n        .prodex-calc .foot{background:var(--ink2);color:rgba(255,255,255,.55);text-align:center;font-family:var(--sans);font-size:12px;padding:24px;margin-top:30px;line-height:1.8}\r\n        .prodex-calc .disc{font-family:var(--sans);font-size:11.5px;color:var(--gray);text-align:center;max-width:760px;margin:18px auto 0;font-style:italic;line-height:1.6}\r\n        \/* NIVELES  anunciar lo que cuesta cada nivel y dejar elegir *\/\r\n        .prodex-calc .niveles{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:6px 0 4px}\r\n        .prodex-calc .nivel-card{background:var(--white);border:1px solid var(--line);border-top:3px solid var(--line);border-radius:var(--radius);padding:18px 18px 16px;cursor:pointer;transition:.2s;box-shadow:0 10px 30px -24px rgba(27,34,42,.28)}\r\n        .prodex-calc .nivel-card:hover{border-color:var(--gold)}\r\n        .prodex-calc .nivel-card.activa{border-color:var(--gold);border-top-color:var(--gold);background:var(--gold-soft)}\r\n        .prodex-calc .nivel-card .nv-k{font-family:var(--sans);font-size:11px;font-weight:500;letter-spacing:.18em;text-transform:uppercase;color:var(--warn)}\r\n        .prodex-calc .nivel-card .nv-t{font-family:var(--serif);font-size:19px;font-weight:500;margin:4px 0 6px;color:var(--ink)}\r\n        .prodex-calc .nivel-card .nv-s{font-family:var(--sans);font-size:13px;color:var(--gray);line-height:1.5}\r\n        .prodex-calc .nivel-card .nv-r{font-family:var(--sans);font-size:12.5px;color:var(--ink);font-weight:500;margin-top:8px}\r\n        \/* nivel basico: los campos del nivel 2 quedan plegados (siguen en el DOM, el motor no cambia) *\/\r\n        .prodex-calc .nivel-basico .lvl2{display:none!important}\r\n        .prodex-calc .lvl1aviso{display:none}\r\n        .prodex-calc .nivel-basico .lvl1aviso{display:block}\r\n        \/* Atajo al Nivel 2 bajo cada total (Pedro, 04-ago): el momento en que\r\n           alguien ve su cifra es cuando quiere afinarla  sin volver a subir.\r\n           Secundario a proposito: el dorado macizo es del CTA del lead. *\/\r\n        \/* Sin display propio: lo gobierna .lvl1aviso (visible solo en Nivel 1) *\/\r\n        .prodex-calc .btn-afinar{margin:10px auto 0;background:transparent;color:var(--gold);\r\n            border:1px solid var(--gold);border-radius:var(--radius);font-family:var(--sans);\r\n            font-weight:600;font-size:13.5px;padding:9px 18px;cursor:pointer;transition:.2s}\r\n        .prodex-calc .btn-afinar:hover{background:var(--gold);color:var(--ink2)}\r\n        \/* GATING  desenfoque hasta dejar datos *\/\r\n        .prodex-calc .lockwrap{position:relative}\r\n        .prodex-calc .locked .lockblur{filter:blur(8px);user-select:none;pointer-events:none;opacity:.85}\r\n        .prodex-calc .lockover{display:none;position:absolute;inset:0;z-index:5;align-items:center;justify-content:center;flex-direction:column;\r\n            text-align:center;background:rgba(246,244,240,.55);border-radius:var(--radius);backdrop-filter:blur(1px)}\r\n        .prodex-calc .locked .lockover{display:flex}\r\n        .prodex-calc .lockover .lock-ic{font-size:26px;margin-bottom:8px}\r\n        .prodex-calc .lockover .lock-t{font-family:var(--serif);font-weight:500;font-size:19px;color:var(--ink);max-width:420px;padding:0 16px}\r\n        .prodex-calc .lockover .lock-s{font-family:var(--sans);font-size:13px;color:var(--gray);margin-top:6px;max-width:380px;padding:0 16px;line-height:1.5}\r\n        \/* El candado es una PUERTA, no un cartel (3-ago). Ver el comentario del\r\n           markup: sin este boton el momento de mas ganas no llevaba a ningun sitio. *\/\r\n        .prodex-calc .lockover .lock-cta{margin-top:14px;background:var(--gold)!important;color:var(--ink2)!important;border:none;\r\n            border-radius:var(--radius);font-family:var(--sans)!important;font-weight:500;font-size:14.5px;\r\n            padding:11px 24px;cursor:pointer;transition:.2s}\r\n        .prodex-calc .lockover .lock-cta:hover{filter:brightness(1.06)}\r\n        \/* Bloque WhatsApp (30-ago-2026): el boton reutiliza .btn; esto es solo\r\n           el estado deshabilitado y la linea RGPD. Selectores por CLASE y a\r\n           principio de linea para que scope_css() los acote. *\/\r\n        .prodex-calc .btn:disabled{opacity:.55;cursor:default}\r\n        .prodex-calc .wa-rgpd{font-family:var(--sans);font-size:12px;color:var(--gray);margin-top:12px;line-height:1.5;text-align:center}\r\n        .prodex-calc .wa-rgpd a{color:var(--gold)}\r\n        \/* PDF download button *\/\r\n        .prodex-calc .btn-pdf{display:none;margin:14px auto 0;background:var(--ink);color:#fff;border:none;border-radius:var(--radius);\r\n            font-family:var(--sans);font-weight:500;font-size:15px;padding:12px 28px;cursor:pointer;transition:.2s}\r\n        .prodex-calc .btn-pdf:hover{background:var(--ink2)}\r\n        \/* SIN ZOOM AL ENFOCAR EN MOVIL (09-ago). Safari iOS hace zoom automatico\r\n           sobre cualquier campo enfocable de menos de 16px, y quien enfoca no es\r\n           el usuario: era nuestro propio focus tras pulsar el candado. Se sube a\r\n           16px SOLO en movil; en escritorio los campos siguen a 15\/14\/13px.\r\n           Medido: la altura del campo no cambia (40px antes y despues) y a 320px\r\n           no aparece desbordamiento horizontal.\r\n           OJO AL GENERADOR: este bloque va MULTILINEA a proposito. scope_css()\r\n           de build-calc-embed.py acota por linea; escrito en una sola linea,\r\n           .prodex-calc .f \/ .owner-row saldrian sin acotar y se filtrarian al tema de\r\n           WordPress (.lead y .f existen en temas tipo Bootstrap). *\/\r\n        @media(max-width:680px){\r\n            .prodex-calc input[type=text],.prodex-calc input[type=number],.prodex-calc input[type=email],.prodex-calc input[type=tel]{font-size:16px}\r\n            .prodex-calc .f select{font-size:16px}\r\n            .prodex-calc .owner-row input{font-size:16px}\r\n            .prodex-calc .owner-row select.ores{font-size:16px}\r\n        }\r\n        \/* EL BOTON FLOTA SOBRE EL TECLADO (09-ago). En iOS el teclado se dibuja\r\n           ENCIMA: el viewport de MAQUETACION no encoge, asi que un sticky normal\r\n           se queda DETRAS del teclado -- por eso se descarto esta manana. Lo que\r\n           si encoge es el viewport VISUAL, y window.visualViewport dice cuanta\r\n           franja tapa el teclado. El JS escribe esa franja en --kb-gap y anade\r\n           la clase kb-float. Sin JS, sin visualViewport o en escritorio esa\r\n           clase no se pone NUNCA y estas reglas no existen: todo queda igual.\r\n           El desplazamiento va en TRANSFORM, no en bottom: durante el arrastre\r\n           elastico de iOS se recoloca en cada cuadro y transform va al\r\n           compositor sin provocar layout.\r\n           transition:none NO es opcional: .btn lleva transition:.2s para TODAS\r\n           las propiedades, o sea que animaria cada reajuste del teclado.\r\n           OJO AL GENERADOR: selector por CLASE y a principio de linea, para que\r\n           scope_css() lo acote a .prodex-calc. Un \"#send{\" a principio de linea\r\n           NO se acotaria (la lista de ids es explicita) y se filtraria al tema.\r\n           No hace falta regla de @print: el bloque de arriba ya oculta .btn. *\/\r\n        @media(max-width:680px){\r\n            .prodex-calc .btn.kb-float{position:fixed;left:0;right:0;bottom:0;width:auto;margin:0;border-radius:0;z-index:60;transform:translate3d(0,calc(var(--kb-gap,0px) * -1),0);transition:none;box-shadow:0 -12px 30px -18px rgba(27,34,42,.55)}\r\n        }\r\n        \/* Print styles *\/\r\n        @media print{\r\n            .prodex-calc .top img{height:44px}\r\n            .prodex-calc .lead,.prodex-calc .foot,.prodex-calc .btn,.prodex-calc .btn-pdf{display:none!important}\r\n            \/* OJO  aqui habia dos reglas que QUITABAN el desenfoque al imprimir\r\n               (3-ago). Sonaban a que el PDF salga limpio, pero solo aplicaban\r\n               a .locked: cuando alguien deja sus datos, showOk() quita esa clase\r\n               y el PDF ya sale nitido sin ayuda. Es decir, lo unico que hacian\r\n               era regalar el desglose entero a quien pulsara Ctrl+P sin dejar\r\n               nada. El candado tambien vale en papel. *\/\r\n            .prodex-calc .lock-cta{display:none!important}\r\n            .prodex-calc{background:#fff}\r\n            .prodex-calc .mod,.prodex-calc .grand{box-shadow:none;border:1px solid #ccc;page-break-inside:avoid}\r\n        }\r\n\r\n        \/*\r\n          PARCHE  Calculadora de reinversion  v2 (02-ago-2026)\r\n          =====================================================\r\n          Integrado en el fuente el 02-ago-2026. Antes iba pegado a mano detras del\r\n          bloque generado (WPCode 3092): una regeneracion lo habria borrado.\r\n          Copia de seguridad del fragmento antes del parche: WPCode #3113\r\n          \"calculadora de reinversion - Copy\", desactivado.\r\n\r\n          MEDIDO EN MOVIL REAL (375x812), que es de donde viene el trafico de Meta:\r\n          el primer campo estaba a 1.211 px  UNA PANTALLA Y MEDIA de scroll antes\r\n          de poder tocar nada. Y lo que ocupaba ese hueco eran las dos tarjetas de\r\n          nivel: una decision, antes de haber dado nada a cambio. De 365 visitas,\r\n          56 llegaron a ver una cifra y 2 pulsaron el nivel 2.\r\n\r\n          De ahi las dos ideas de esta version:\r\n\r\n          A. NO SE PREGUNTA AL ENTRAR. La pagina arranca en nivel 1 y las tarjetas\r\n             de la cabecera se pliegan (siguen en el DOM: el motor y los latidos del\r\n             embudo no se enteran). El primer campo sube a media pantalla.\r\n\r\n          B. EL NIVEL 2 SE OFRECE CUANDO YA HAY UNA CIFRA, y no como \"nivel\" sino\r\n             como una pega concreta a lo que el visitante acaba de ver: que ha dado\r\n             por supuesto la calculadora para llegar a ese numero. El caso fuerte es\r\n             la exencion por reinversion  la pagina se llama \"de reinversion\" y por\r\n             defecto asume que NO reinviertes, asi que le esta restando un IRPF que\r\n             probablemente no va a pagar. Eso es una razon; \"Nivel 2\" era una etiqueta.\r\n\r\n          Y lo de siempre:\r\n\r\n          1. Las tarjetas de nivel son botones de verdad (role, foco, teclado).\r\n          2. Cambiar de nivel fuerza recalc(): sin eso el latido \"resultado\/reales\"\r\n             no salia nunca y el ERP contaba 0 cifras en nivel 2 aunque las hubiera.\r\n          3. El select de residencia del propietario no tenia listener: ni\r\n             recalculaba ni contaba. Se anade por delegacion (las filas se crean\r\n             sobre la marcha).\r\n          4. El candado del PDF no aparece mientras el resultado sea 0,00  EUR.\r\n        *\/\r\n        \/* ---- A. la eleccion de nivel no abre la pagina ---- *\/\r\n        .prodex-calc .wrap.nivel-basico #niveles{ display:none }\r\n\r\n        \/* ---- las tarjetas de nivel, cuando se ven, son botones ---- *\/\r\n        .prodex-calc .nivel-card:focus-visible{\r\n            outline:2px solid var(--gold);\r\n            outline-offset:3px;\r\n        }\r\n        .prodex-calc .nivel-card .nv-cta{\r\n            font-family:var(--sans);\r\n            font-size:12.5px;\r\n            font-weight:600;\r\n            color:var(--ink);\r\n            margin-top:12px;\r\n            padding-top:10px;\r\n            border-top:1px solid var(--line);\r\n            display:flex;\r\n            align-items:center;\r\n            gap:7px;\r\n        }\r\n        .prodex-calc .nivel-card .nv-cta::before{\r\n            content:'';\r\n            width:13px;height:13px;\r\n            border:1.5px solid var(--gray);\r\n            border-radius:50%;\r\n            flex:0 0 auto;\r\n            transition:.2s;\r\n        }\r\n        .prodex-calc .nivel-card.activa .nv-cta::before{\r\n            border-color:var(--gold);\r\n            background:radial-gradient(circle,var(--gold) 0 45%,transparent 46%);\r\n        }\r\n        .prodex-calc .nivel-card.activa .nv-cta{ color:var(--warn) }\r\n\r\n        \/* ---- B. la pega, en oscuro, detras del balance ---- *\/\r\n        .prodex-calc #prodex-afinar{\r\n            display:none;\r\n            background:linear-gradient(165deg,var(--ink) 0%,var(--ink2) 80%);\r\n            border-radius:var(--radius);\r\n            padding:30px 28px;\r\n            margin:0 0 26px;\r\n            color:#fff;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-k{\r\n            font-family:var(--sans);\r\n            font-size:11px;\r\n            font-weight:500;\r\n            letter-spacing:.22em;\r\n            text-transform:uppercase;\r\n            color:var(--gold);\r\n            margin-bottom:10px;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-t{\r\n            font-family:var(--serif);\r\n            font-size:23px;\r\n            font-weight:500;\r\n            line-height:1.3;\r\n            margin-bottom:16px;\r\n            color:#fff;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-l{\r\n            list-style:none;\r\n            padding:0;\r\n            margin:0 0 20px;\r\n            font-family:var(--sans);\r\n            font-size:14px;\r\n            line-height:1.6;\r\n            color:rgba(255,255,255,.78);\r\n        }\r\n        .prodex-calc #prodex-afinar .af-l li{\r\n            padding-left:22px;\r\n            position:relative;\r\n            margin-bottom:6px;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-l li::before{\r\n            content:'\\0000b7';\r\n            position:absolute;\r\n            left:6px;\r\n            color:var(--gold);\r\n            font-size:20px;\r\n            line-height:1.1;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-b{\r\n            font-family:var(--sans);\r\n            font-size:15px;\r\n            font-weight:600;\r\n            color:var(--ink);\r\n            background:var(--gold);\r\n            border:0;\r\n            border-radius:999px;\r\n            padding:15px 30px;\r\n            cursor:pointer;\r\n            transition:.2s;\r\n            width:100%;\r\n            max-width:340px;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-b:hover{ filter:brightness(1.08) }\r\n        .prodex-calc #prodex-afinar .af-b:focus-visible{\r\n            outline:2px solid #fff;\r\n            outline-offset:3px;\r\n        }\r\n        .prodex-calc #prodex-afinar .af-n{\r\n            font-family:var(--sans);\r\n            font-size:12.5px;\r\n            color:rgba(255,255,255,.5);\r\n            margin-top:12px;\r\n            line-height:1.5;\r\n        }\r\n\r\n        \/* ---- volver a la estimacion rapida, discreto ---- *\/\r\n        .prodex-calc #prodex-volver{\r\n            display:none;\r\n            font-family:var(--sans);\r\n            font-size:13px;\r\n            color:var(--gray);\r\n            background:none;\r\n            border:0;\r\n            padding:6px 0;\r\n            margin:0 0 18px;\r\n            cursor:pointer;\r\n            text-decoration:underline;\r\n        }\r\n\r\n        \/* ---- sin cifra no hay candado ---- *\/\r\n        .prodex-calc .lockwrap.sin-cifra .lockover{ display:none !important }\r\n    \r\n\r\n\/* === Blindaje WordPress === *\/\r\n.prodex-calc h1, .prodex-calc h2, .prodex-calc .mod-t, .prodex-calc .lock-t, .prodex-calc .grand .gv, .prodex-calc .grand .split .n, .prodex-calc .total .tv {\r\n  font-family: var(--serif) !important; font-weight: 500 !important; text-transform: none !important; letter-spacing: -0.01em !important;\r\n}\r\n.prodex-calc .top .tag, .prodex-calc .grand .gl, .prodex-calc .total .tl { font-family: var(--sans) !important; text-transform: uppercase !important; }\r\n.prodex-calc label, .prodex-calc input, .prodex-calc button, .prodex-calc .mod-sub, .prodex-calc .crow, .prodex-calc .alert .tx, .prodex-calc .note, .prodex-calc .disc { font-family: var(--sans) !important; }\r\n.prodex-calc a { text-decoration: none !important; box-shadow: none !important; }\r\n.prodex-calc .btn { background: var(--gold) !important; color: var(--ink2) !important; }\r\n.prodex-calc .btn-pdf { background: var(--ink) !important; color: #fff !important; }\r\n.prodex-calc input { background: var(--white) !important; color: var(--ink) !important; }\r\n.prodex-calc .top, .prodex-calc .grand { color: #fff !important; }\r\n\r\n<\/style>\r\n\r\n<div class=\"prodex-calc\" data-calc-version=\"66837546076d\"><header class=\"top\"><img decoding=\"async\" data-src=\"https:\/\/grupoprodex.com\/wp-content\/uploads\/2026\/06\/PRODEX_logo_blanco_transparente.png\" alt=\"PRODEX\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" class=\"lazyload\" style=\"--smush-placeholder-width: 1200px; --smush-placeholder-aspect-ratio: 1200\/500;\"><div class=\"tag\">Gesti&#243;n y Representaci&#243;n Patrimonial<\/div><\/header><div class=\"wrap nivel-basico\" id=\"pxwrap\"><section class=\"hero\"><h1>&#191;Cu&#225;nto te queda <span class=\"g\">de verdad<\/span> al cambiar de vivienda?<\/h1><!-- LA PROMESA COMPLETA, EN LA PUERTA (30-ago-2026): las cifras ya no se pintan en pantalla &#8212; el informe llega por WhatsApp. El trato se dice ANTES de rellenar nada, y solo se promete lo que se cumple: la cifra y el desglose. Ni llamadas ni nada m&#225;s. --><p>Calcula en un minuto la liquidez real de tu venta y el coste total de tu compra, con la fiscalidad de Andaluc&#237;a aplicada. Rellena tus datos y recibe tu cifra exacta con el desglose en tu WhatsApp.<\/p><div class=\"rule\"><\/div><\/section><!-- ===================== NIVELES DE PROFUNDIDAD ===================== --><section class=\"niveles\" id=\"niveles\"><div class=\"nivel-card activa\" id=\"nivel1\"><div class=\"nv-k\">Nivel 1<\/div><div class=\"nv-t\">En un minuto<\/div><div class=\"nv-s\">Siete datos que ya sabes. No necesitas buscar ning&#250;n papel.<\/div><div class=\"nv-r\">Te enviamos la estimaci&#243;n a tu WhatsApp.<\/div><\/div><div class=\"nivel-card\" id=\"nivel2\"><div class=\"nv-k\">Nivel 2<\/div><div class=\"nv-t\">Con tus n&#250;meros reales<\/div><div class=\"nv-s\">Cinco o diez minutos. <strong>Ten a mano el recibo del IBI, la escritura de compra y el saldo de tu hipoteca.<\/strong><\/div><div class=\"nv-r\">Te enviamos la cifra exacta con el desglose completo a tu WhatsApp.<\/div><\/div><\/section><!-- ===================== M&#211;DULO 1 ===================== --><section class=\"mod\"><div class=\"mod-h\"><span class=\"mod-num\">01<\/span><span class=\"mod-t\">VIVIENDA PROPIA &#183; Desinversi&#243;n<\/span><\/div><div class=\"mod-sub\">Lo que vendes. Calculamos cu&#225;nto dinero l&#237;quido acabas teniendo en el bolsillo tras impuestos, hipoteca y honorarios.<\/div><div class=\"grid\"><div class=\"f\"><label>Poblaci&#243;n <span class=\"hint\">Municipio donde est&#225; la vivienda (define el tipo de plusval&#237;a)<\/span><\/label><input type=\"text\" id=\"v_pob\" list=\"munis\" placeholder=\"Ej.: M&#225;laga\" autocomplete=\"off\"><datalist id=\"munis\"><\/datalist><\/div><div class=\"f lvl2\"><label>N&#186; de propietarios <span class=\"hint\">Se generar&#225; una fila por propietario para su edad y % de titularidad<\/span><\/label><input type=\"number\" id=\"v_props\" min=\"1\" max=\"6\" step=\"1\" value=\"1\"><\/div><div class=\"f full lvl2\" id=\"v_ownersbox\"><label>Propietarios: edad y % de titularidad<\/label><div id=\"v_owners\"><\/div><div id=\"v_pctwarn\" style=\"display:none;font-family:Calibri,Arial,sans-serif;font-size:12px;color:#8a2a2a;margin-top:4px\"><\/div><div class=\"note\" id=\"v_usunote\" style=\"margin-top:8px;display:none\">&#191;Hay <strong>usufructo<\/strong> o nuda propiedad? El reparto entonces no sigue estos porcentajes. Esta calculadora asume plena propiedad: si existe usufructo, el c&#225;lculo real lo afinar&#225; PRODEX para tu caso.<\/div><\/div><div class=\"f\"><label>A&#241;o de adquisici&#243;n <span class=\"hint\">A&#241;o en que comprasteis la vivienda<\/span><\/label><input type=\"number\" id=\"v_anio\" min=\"1950\" max=\"2026\" step=\"1\" placeholder=\"Ej.: 2008\"><\/div><div class=\"f\"><label>Valor de adquisici&#243;n (&#8364;) <span class=\"hint\">Precio al que comprasteis<\/span><\/label><input type=\"number\" id=\"v_adq\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f\"><label>Valor estimado de venta (&#8364;) <span class=\"hint\">Precio al que esper&#225;is vender<\/span><\/label><input type=\"number\" id=\"v_venta\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f lvl2\"><label>Cancelaci&#243;n de hipoteca (&#8364;) <span class=\"hint\">Capital pendiente que deb&#233;is al banco<\/span><\/label><input type=\"number\" id=\"v_hip\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f lvl2\"><label>Otras deudas \/ cargas (&#8364;) <span class=\"hint\">Embargos, derramas, IBI atrasado&#8230;<\/span><\/label><input type=\"number\" id=\"v_deudas\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f lvl2\"><label>Gastos deducibles compra + venta (&#8364;) <span class=\"hint\">Suma de gastos de la compra original (ITP, notar&#237;a) y de la venta (notar&#237;a, certificados&#8230;). Reducen el IRPF.<\/span><\/label><input type=\"number\" id=\"v_gastosded\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f lvl2\"><label>Valor catastral del SUELO (&#8364;) <span class=\"hint\">Est&#225; en tu recibo del IBI. Sin &#233;l no calculamos la plusval&#237;a objetiva.<\/span><\/label><input type=\"number\" id=\"v_catastro\" min=\"0\" step=\"any\" placeholder=\"Opcional\"><\/div><div class=\"f lvl2\"><label>Valor catastral TOTAL (&#8364;) <span class=\"hint\">En el mismo recibo del IBI (suelo + construcci&#243;n). Nos deja comparar el segundo m&#233;todo de plusval&#237;a, que a veces sale m&#225;s barato.<\/span><\/label><input type=\"number\" id=\"v_catastro_total\" min=\"0\" step=\"any\" placeholder=\"Opcional\"><\/div><div class=\"f lvl2\"><label>Tu comisi&#243;n de venta (%) <span class=\"hint\">% de honorarios de agencia sobre el valor de venta<\/span><\/label><input type=\"number\" id=\"v_comi\" min=\"0\" step=\"any\" placeholder=\"Ej.: 5\"><\/div><div class=\"f lvl2\"><label>Honorarios abogado \/ gestor&#237;a (%) <span class=\"hint\">Suele rondar entre 0,5% y 1% del importe de la venta<\/span><\/label><input type=\"number\" id=\"v_abog\" min=\"0\" step=\"any\" placeholder=\"Ej.: 0,75\"><\/div><\/div><div class=\"grid\" style=\"margin-top:14px\"><label class=\"chk\"><input type=\"checkbox\" id=\"v_habitual\" checked> Es la vivienda habitual <span class=\"hint\" style=\"margin-left:0\">(necesario para la exenci&#243;n de 65+ a&#241;os, que se aplica autom&#225;ticamente por edad)<\/span><\/label><label class=\"chk lvl2\"><input type=\"checkbox\" id=\"v_notpagacomprador\" checked> Notar&#237;a y registro: los paga el comprador <span class=\"hint\" style=\"margin-left:0\">(marcado, no se restan de tu l&#237;quido; desm&#225;rcalo si los asumes t&#250;)<\/span><\/label><\/div><div class=\"grid lvl2\" style=\"margin-top:10px\"><label class=\"chk\"><input type=\"checkbox\" id=\"v_exreinv\"> Reinvierto en una nueva vivienda habitual <span class=\"hint\" style=\"margin-left:0\">(exenci&#243;n de IRPF proporcional a lo reinvertido)<\/span><\/label><div class=\"f\" id=\"v_reinvbox\" style=\"display:none\"><label>% de la ganancia que reinviertes <span class=\"hint\">100% = exenci&#243;n total. Si reinviertes menos, tributa la parte no reinvertida.<\/span><\/label><input type=\"number\" id=\"v_reinvpct\" min=\"0\" max=\"100\" step=\"any\" value=\"100\"><\/div><\/div><div class=\"lockwrap sin-cifra\" id=\"lock_vtotal\" style=\"margin-top:18px\"><div class=\"lockblur\"><div class=\"total\" style=\"margin-top:0\"><div class=\"tl\">L&#205;QUIDO TOTAL DE LA VENTA<\/div><div class=\"tv\" id=\"v_total\">&#8212; &#8364;<\/div><\/div><\/div><div class=\"lockover\"><div class=\"lock-ic\">&#128274;<\/div><div class=\"lock-t\">Aqu&#237; est&#225; tu l&#237;quido exacto<\/div><div class=\"lock-s\">Calculado con tus n&#250;meros reales, no con una estimaci&#243;n. Te lo enviamos hoy mismo a tu WhatsApp.<\/div><button type=\"button\" class=\"lock-cta\">Recibir mi cifra por WhatsApp<\/button><\/div><\/div><button type=\"button\" class=\"btn-afinar lvl1aviso\" data-afinar>Afinar con mis n&#250;meros reales &#8594;<\/button><div class=\"note\" id=\"v_plusaviso\" style=\"display:none;margin-top:10px\"><\/div><div class=\"note lvl1aviso\" style=\"margin-top:10px\">Estimaci&#243;n aproximada. (Informaci&#243;n general, no asesoramiento fiscal.)<\/div><div class=\"lockwrap locked\" id=\"lock_v\" style=\"margin-top:14px\"><div class=\"lockblur\"><div class=\"calc\" id=\"v_calc\"><\/div><\/div><div class=\"lockover\"><div class=\"lock-ic\">&#128274;<\/div><div class=\"lock-t\">El desglose completo va en tu informe.<\/div><div class=\"lock-s\">Cada n&#250;mero explicado, hoy mismo en tu WhatsApp.<\/div><button type=\"button\" class=\"lock-cta\">Recibir mi informe por WhatsApp<\/button><\/div><\/div><\/section><!-- ===================== M&#211;DULO 2 ===================== --><section class=\"mod\"><div class=\"mod-h\"><span class=\"mod-num\">02<\/span><span class=\"mod-t\">COMPRA DE VIVIENDA &#183; Inversi&#243;n<\/span><\/div><div class=\"mod-sub\">Lo que compras. Calculamos el coste total real de la operaci&#243;n, con todos los impuestos y gastos de Andaluc&#237;a incluidos.<\/div><div class=\"grid\"><div class=\"f\"><label>Poblaci&#243;n <span class=\"hint\">Municipio de la nueva vivienda<\/span><\/label><input type=\"text\" id=\"c_pob\" list=\"munis\" placeholder=\"Ej.: Marbella\" autocomplete=\"off\"><\/div><div class=\"f\"><label>Valor estimado de compra (&#8364;) <span class=\"hint\">Precio de la nueva vivienda<\/span><\/label><input type=\"number\" id=\"c_compra\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><div class=\"f lvl2\"><label>&#191;Ser&#225; tu vivienda habitual? <span class=\"hint\">Andaluc&#237;a baja el ITP en la compra de vivienda habitual por debajo de 150.000 &#8364;<\/span><\/label><select id=\"c_habitual\"><option value=\"si\">S&#237;, vivir&#233; en ella<\/option><option value=\"no\">No, es una segunda vivienda o inversi&#243;n<\/option><\/select><\/div><div class=\"f lvl2\"><label>&#191;Est&#225;s en alguno de estos casos? <span class=\"hint\">Bajan el ITP al 3,5% si la vivienda es habitual y no pasa de 150.000 &#8364;<\/span><\/label><select id=\"c_supuesto\"><option value=\"\">Ninguno<\/option><option value=\"joven\">Menor de 35 a&#241;os<\/option><option value=\"discapacidad\">Discapacidad igual o superior al 33%<\/option><option value=\"numerosa\">Familia numerosa<\/option><option value=\"monoparental\">Familia monoparental<\/option><option value=\"violencia\">V&#237;ctima de violencia de g&#233;nero o de terrorismo<\/option><\/select><\/div><div class=\"f lvl2\"><label>ITP (%) <span class=\"hint\">Lo calculamos con lo anterior. Puedes cambiarlo si tu asesor te ha dicho otro.<\/span><\/label><input type=\"number\" id=\"c_itp\" min=\"0\" step=\"any\" value=\"7\"><div class=\"note\" id=\"c_itpnota\" style=\"margin:6px 0 0\"><\/div><\/div><div class=\"f lvl2\"><label>Tu comisi&#243;n de compra (%) <span class=\"hint\">Honorarios de intermediaci&#243;n \/ personal shopper<\/span><\/label><input type=\"number\" id=\"c_comi\" min=\"0\" step=\"any\" placeholder=\"Ej.: 5\"><\/div><div class=\"f lvl2\"><label>Honorarios abogado \/ gestor&#237;a (%) <span class=\"hint\">Suele rondar entre 0,5% y 1% del importe de la compra<\/span><\/label><input type=\"number\" id=\"c_abog\" min=\"0\" step=\"any\" placeholder=\"Ej.: 0,75\"><\/div><div class=\"f lvl2\"><label>Otros gastos (&#8364;) <span class=\"hint\">Reforma, mudanza, liquidez adicional&#8230;<\/span><\/label><input type=\"number\" id=\"c_otros\" min=\"0\" step=\"any\" placeholder=\"0\"><\/div><\/div><div class=\"grid lvl2\" style=\"margin-top:14px\"><label class=\"chk\"><input type=\"checkbox\" id=\"c_notaria\" checked> Notar&#237;a, registro y gestor&#237;a <span class=\"hint\" style=\"margin-left:0\">(los paga el comprador &#183; estimado 1% del precio)<\/span><\/label><label class=\"chk\"><input type=\"checkbox\" id=\"c_hipoteca\" checked> Voy a constituir hipoteca <span class=\"hint\" style=\"margin-left:0\">(estima tasaci&#243;n y gesti&#243;n asociadas)<\/span><\/label><\/div><!-- Simulador de cuota de hipoteca --><div id=\"c_simbox\" class=\"lvl2\" style=\"margin-top:18px;border-top:1px solid var(--line);padding-top:16px\"><div style=\"font-family:Calibri,Arial,sans-serif;font-weight:bold;font-size:14px;color:var(--ink);margin-bottom:4px\">Simulador de cuota mensual (orientativo)<\/div><div style=\"font-family:Calibri,Arial,sans-serif;font-size:12.5px;color:var(--gray);margin-bottom:12px\">Solo si ya tienes claras tus condiciones de hipoteca <strong>fija<\/strong>. &#191;Es variable o mixta? Lo calculamos a medida dentro de PRODEX.<\/div><div class=\"grid\"><div class=\"f\"><label>Importe a financiar (&#8364;) <span class=\"hint\">Lo que te presta el banco<\/span><\/label><input type=\"number\" id=\"h_importe\" min=\"0\" step=\"any\" placeholder=\"Ej.: 200000\"><\/div><div class=\"f\"><label>Tipo de inter&#233;s fijo (%) <span class=\"hint\">TIN anual de tu hipoteca fija<\/span><\/label><input type=\"number\" id=\"h_tin\" min=\"0\" step=\"any\" placeholder=\"Ej.: 2,9\"><\/div><div class=\"f\"><label>Plazo (a&#241;os) <span class=\"hint\">Duraci&#243;n del pr&#233;stamo<\/span><\/label><input type=\"number\" id=\"h_anios\" min=\"1\" max=\"40\" step=\"1\" placeholder=\"Ej.: 25\"><\/div><div class=\"f\" style=\"justify-content:flex-end\"><div class=\"total\" style=\"margin:0\"><div class=\"tl\" style=\"font-size:13px;padding:12px 16px\">CUOTA MENSUAL APROX.<\/div><div class=\"tv\" id=\"h_cuota\" style=\"font-size:20px;padding:12px 16px\">&#8212; &#8364;<\/div><\/div><\/div><\/div><div class=\"note\" style=\"margin-top:10px\">C&#225;lculo orientativo de cuota a tipo fijo (sistema franc&#233;s). No incluye seguros ni comisiones vinculadas. <strong>Si tu hipoteca es variable o mixta, en PRODEX te calculamos el escenario real.<\/strong><\/div><\/div><div class=\"lockwrap sin-cifra\" id=\"lock_ctotal\" style=\"margin-top:18px\"><div class=\"lockblur\"><div class=\"total cost\" style=\"margin-top:0\"><div class=\"tl\">COSTE TOTAL DE LA COMPRA<\/div><div class=\"tv\" id=\"c_total\">&#8212; &#8364;<\/div><\/div><\/div><div class=\"lockover\"><div class=\"lock-ic\">&#128274;<\/div><div class=\"lock-t\">Aqu&#237; est&#225; el coste exacto de tu compra<\/div><div class=\"lock-s\">Calculado con tus n&#250;meros reales, no con una estimaci&#243;n. Te lo enviamos hoy mismo a tu WhatsApp.<\/div><button type=\"button\" class=\"lock-cta\">Recibir mi cifra por WhatsApp<\/button><\/div><\/div><button type=\"button\" class=\"btn-afinar lvl1aviso\" data-afinar>Afinar con mis n&#250;meros reales &#8594;<\/button><div class=\"note lvl1aviso\" style=\"margin-top:10px\">Estimaci&#243;n aproximada. (Informaci&#243;n general, no asesoramiento fiscal.)<\/div><div class=\"lockwrap locked\" id=\"lock_c\" style=\"margin-top:14px\"><div class=\"lockblur\"><div class=\"calc\" id=\"c_calc\"><\/div><\/div><div class=\"lockover\"><div class=\"lock-ic\">&#128274;<\/div><div class=\"lock-t\">El desglose completo va en tu informe.<\/div><div class=\"lock-s\">Cada n&#250;mero explicado, hoy mismo en tu WhatsApp.<\/div><button type=\"button\" class=\"lock-cta\">Recibir mi informe por WhatsApp<\/button><\/div><\/div><\/section><!-- ===================== RESULTADO GLOBAL ===================== --><section class=\"grand\"><div class=\"gl\">Balance de tu operaci&#243;n de reinversi&#243;n<\/div><div class=\"lockwrap sin-cifra\" id=\"lock_balance\"><div class=\"lockblur\"><div class=\"gv\" id=\"g_saldo\">&#8212; &#8364;<\/div><div class=\"gd\" id=\"g_desc\">L&#237;quido de la venta menos coste de la nueva compra<\/div><div class=\"split\"><div><div class=\"n\" id=\"g_liq\">&#8212; &#8364;<\/div><div class=\"l\">L&#237;quido venta<\/div><\/div><div><div class=\"n\" id=\"g_cost\">&#8212; &#8364;<\/div><div class=\"l\">Coste compra<\/div><\/div><div><div class=\"n\" id=\"g_dif\">&#8212; &#8364;<\/div><div class=\"l\">Diferencia a cubrir \/ sobrante<\/div><\/div><\/div><\/div><div class=\"lockover\"><div class=\"lock-ic\">&#128274;<\/div><div class=\"lock-t\">Aqu&#237; est&#225; tu balance real<\/div><div class=\"lock-s\">Lo que te queda limpio, con tus n&#250;meros reales. Te lo enviamos hoy mismo a tu WhatsApp.<\/div><button type=\"button\" class=\"lock-cta\">Recibir mi cifra por WhatsApp<\/button><\/div><\/div><div class=\"note lvl1aviso\" style=\"margin-top:18px;text-align:center\">Estimaci&#243;n aproximada. (Informaci&#243;n general, no asesoramiento fiscal.)<\/div><\/section><!-- ===================== ALERTAS \/ GANCHO ===================== --><section id=\"oportunidades\" style=\"display:none;background:var(--white);border:1px solid var(--gold);border-radius:12px;padding:24px 26px;margin:22px 0\"><div style=\"display:flex;align-items:center;gap:10px;margin-bottom:6px\"><span style=\"font-size:22px\">&#10022;<\/span><h2 style=\"font-size:21px;margin:0\">Hemos detectado posibles oportunidades en tu caso<\/h2><\/div><p style=\"font-family:Calibri,Arial,sans-serif;color:var(--gray);font-size:14px;margin-bottom:16px\">Seg&#250;n los datos que has introducido, tu operaci&#243;n podr&#237;a tener margen de optimizaci&#243;n. Las analizamos en detalle en tu informe personalizado.<\/p><div id=\"alerts\"><\/div><\/section><p class=\"disc\">Estimaci&#243;n orientativa basada en la normativa fiscal de Andaluc&#237;a vigente en 2026. Los importes se muestran con su desglose de base + IVA (21%) cuando procede. No constituye asesoramiento fiscal ni jur&#237;dico: el c&#225;lculo exacto y definitivo lo realiza el equipo de PRODEX para tu caso concreto.<\/p><!-- FUERA DE ZONA (20-ago-2026, revisado esa misma tarde). Nace oculto y lo enciende pintaModoZona() al pulsar el candado. PRIMERA VERSION: se le regalaban las cifras y se le RETIRABA el formulario. Pedro lo par&#243; antes de que llegara a la web: el ERP tiene un circuito de producto digital justo para los de fuera de zona (TipoDeCircuito), y retirar el formulario lo dejaba sin nadie que lo alimentara. Pas&#225;bamos de un lead que no podemos visitar a ning&#250;n lead. Y el euro del clic ya est&#225; pagado: el formulario no cuesta m&#225;s dinero. ASI QUE: se le pide el contacto igual, pero diciendo la verdad &#8212;no vamos a poder ir a su casa&#8212; y prometiendo lo &#250;nico que s&#237; podemos cumplir: las cifras y el informe. --><div id=\"fuera-zona\" style=\"display:none;background:var(--white);border:1px solid var(--gold);border-radius:12px;padding:22px 24px;margin:22px 0\"><div style=\"font-family:var(--serif);font-size:19px;color:var(--ink);margin-bottom:8px\">Tu vivienda queda fuera de nuestra zona.<\/div><p style=\"font-family:var(--sans);font-size:14px;color:var(--gray);line-height:1.6;margin:0\"><strong>PRODEX trabaja en M&#225;laga capital, su costa &#8212;Torremolinos, Benalm&#225;dena y Fuengirola&#8212; y el Valle del Guadalhorce<\/strong> (Alhaur&#237;n de la Torre, Alhaur&#237;n el Grande, Almog&#237;a, &#193;lora, C&#225;rtama, Co&#237;n, Pizarra y Valle de Abdalaj&#237;s). No vamos a poder ir a ver tu casa ni acompa&#241;arte en la venta, y preferimos dec&#237;rtelo aqu&#237; antes de que dejes tus datos. Lo que s&#237; es tuyo es el c&#225;lculo: la fiscalidad aplicada es la de Andaluc&#237;a y sigue siendo v&#225;lida. Tu informe con la cifra exacta y el desglose te lo enviamos igualmente por WhatsApp, desde el bot&#243;n del final de la p&#225;gina. Si tu caso cambia o la vivienda est&#225; en nuestra zona, aqu&#237; estamos &#8212; <a href=\"tel:+34951199132\" style=\"color:var(--gold)\">951 199 132<\/a>. <\/p><\/div><!-- ===================== LEAD ===================== --><!-- ENTREGA POR WHATSAPP (30-ago-2026). El resultado ya no se pinta en pantalla ni se pide el contacto por formulario: el usuario pulsa el bot&#243;n wa.me con &#171;CALCULADORA REF-XXXXXXXX&#187; precargado y Pedro le responde con el informe desde el ERP (el c&#225;lculo sube antes con esa REF, sin ning&#250;n dato personal). El formulario #l_form NO se borra: queda oculto y desconectado &#8212; el build exige sus centinelas y el parche de nivel le sigue colgando el campo trampa. --><section class=\"lead\" id=\"px-whatsapp\"><!-- EL TITULAR CONTIN&#218;A LA PROMESA DEL CANDADO, NO LA CAMBIA (10-ago-2026). El candado dice &#171;Ver mi cifra exacta&#187; y hasta hoy se aterrizaba en &#171;Pide tu plan de optimizaci&#243;n&#187;: se pulsaba para ver un n&#250;mero y lo que aparec&#237;a era una llamada. Otro trato, y en m&#243;vil el candado que hizo la promesa ya ni se ve. La llamada NO se esconde &#8212;se cobra en el tel&#233;fono de Pedro, y con un solo comercial m&#225;s volumen no es autom&#225;ticamente mejor&#8212;, pero va despu&#233;s de lo que la persona vino a buscar. --><h2 id=\"lead-h2\">Tu informe est&#225; listo<\/h2><p class=\"s\" id=\"lead-sub\">Te lo enviamos hoy mismo a tu WhatsApp: la cifra exacta de tu operaci&#243;n y el desglose de cada n&#250;mero.<\/p><!-- El bot&#243;n nace deshabilitado; lo habilita el POST del c&#225;lculo al ERP (o su fallo tras el reintento: mejor un WhatsApp sin payload que perder el contacto). El href no existe: el clic abre wa.me con la REF y emite el latido clic_whatsapp. --><button class=\"btn\" type=\"button\" id=\"px-wa-btn\" disabled>Preparando tu informe&#8230;<\/button><p class=\"wa-rgpd\">Al escribirnos nos facilitas tu n&#250;mero solo para enviarte tu informe. Responsable: Grupo Prodex. <a href=\"https:\/\/grupoprodex.com\/politica-de-privacidad\/\" target=\"_blank\">Pol&#237;tica de privacidad<\/a>.<\/p><!-- ELEMENTO form DE VERDAD (09-ago). Antes eran tres inputs sueltos sin name ni autocomplete, y el autorrelleno de contacto de iOS\/Android agrupa POR FORMULARIO: el movil no podia ofrecer el nombre ni el telefono guardados, y la barra del teclado tampoco daba las flechas para saltar de campo. Pedro (09-ago): &#171;que en formato movil puedan pulsar y que los datos se autorrellenen&#187;. novalidate NO es opcional: con required dentro de un form el navegador bloquearia el envio con su propio globo y no correrian ni los mensajes .err ni los latidos falta_nombre\/falta_tel\/falta_email\/ falta_rgpd, o sea que el embudo se quedaria ciego sin dar ni un error visible. El onsubmit es el cinturon por si el JS no cargara: nunca una recarga. #okmsg, #btn-pdf y el campo trampa l_apellido2 (que el parche cuelga de .lead) quedan FUERA a proposito: dentro del form iOS podria autorrellenar la trampa y el ERP daria el lead por bot. --><form id=\"l_form\" novalidate onsubmit=\"return false\" style=\"display:none\"><div class=\"grid\"><div class=\"f\"><label for=\"l_nombre\">Nombre y apellidos *<\/label><input type=\"text\" id=\"l_nombre\" name=\"nombre\" autocomplete=\"name\" autocapitalize=\"words\" enterkeyhint=\"next\" required><span class=\"err\" id=\"e_nombre\">Indica tu nombre y apellidos.<\/span><\/div><div class=\"f\"><label for=\"l_tel\">Tel&#233;fono * <span class=\"hint\" id=\"hint-tel\">Te llamamos a este n&#250;mero. Si es de fuera de Espa&#241;a, escr&#237;belo con su prefijo: +44 7700 900123<\/span><\/label><input type=\"tel\" inputmode=\"tel\" id=\"l_tel\" name=\"telefono\" autocomplete=\"tel\" enterkeyhint=\"next\" required><span class=\"err\" id=\"e_tel\">Escribe un tel&#233;fono al que podamos llamarte: 9 d&#237;gitos si es espa&#241;ol (600 123 456), o con su prefijo si es de fuera (+44 7700 900123).<\/span><\/div><div class=\"f full\"><label for=\"l_email\">Correo electr&#243;nico *<\/label><input type=\"email\" id=\"l_email\" name=\"email\" autocomplete=\"email\" inputmode=\"email\" autocapitalize=\"off\" spellcheck=\"false\" enterkeyhint=\"send\" required><span class=\"err\" id=\"e_email\">Indica un correo electr&#243;nico v&#225;lido.<\/span><\/div><\/div><label class=\"chk\" style=\"margin-top:14px\"><input type=\"checkbox\" id=\"l_rgpd\"><span>He le&#237;do y acepto la <a href=\"https:\/\/grupoprodex.com\/politica-de-privacidad\/\" target=\"_blank\" style=\"color:var(--gold)\">pol&#237;tica de privacidad<\/a>. Autorizo a Grupodex Management & Services, S.L. a tratar mis datos para contactarme en relaci&#243;n con esta solicitud. *<\/span><\/label><span class=\"err\" id=\"e_rgpd\">Debes aceptar la pol&#237;tica de privacidad.<\/span><p class=\"antes-enviar\">Sin coste y sin compromiso. El c&#225;lculo que tienes en pantalla es tuyo.<\/p><!-- EL BOT&#211;N VUELVE A LA PROMESA A SECAS (30-ago-2026). El plan del embudo midi&#243; el tramo candado&#8594;env&#237;o en 60&#8594;3: el bot&#243;n con la llamada dentro cobraba el compromiso dos veces. La llamada no se esconde &#8212;sigue dicha en el subt&#237;tulo y el tel&#233;fono sigue siendo obligatorio, que es el objetivo&#8212;, pero el bot&#243;n promete solo lo que la persona vino a buscar. Decisi&#243;n de Pedro (30-ago). --><button class=\"btn\" id=\"send\">Ver mis cifras<\/button><!-- EL CAMINO DEL ERROR (10-ago). Antes esto era un alert() del navegador que dec&#237;a &#171;ll&#225;manos directamente&#187; SIN DAR NING&#218;N N&#218;MERO. Quien llega aqu&#237; ya ha escrito su nombre y su tel&#233;fono: es de los que s&#237; quer&#237;an, y se perd&#237;a sin dejar rastro. El tel&#233;fono va pulsable &#8212; en un m&#243;vil se toca y llama &#8212; y es el fijo publicado en la web. --><div class=\"err-envio\" id=\"err-envio\" role=\"alert\"> No hemos podido enviar tus datos. Vuelve a intentarlo en un momento &mdash; o ll&#225;manos al <a href=\"tel:+34951199132\">951 199 132<\/a> y lo vemos contigo. <\/div><\/form><!-- EL MENSAJE DICE QUE EL PREMIO YA EST&#193; (10-ago). Antes daba las gracias y hablaba de la llamada, sin mencionar que las cifras acababan de quedar a la vista: se cumpl&#237;a la promesa en silencio. --><div class=\"ok-msg\" id=\"okmsg\">&#10003; Listo: tus cifras ya est&#225;n destapadas, aqu&#237; arriba. Un consultor de PRODEX te llamar&#225; para explicarte de d&#243;nde sale cada n&#250;mero y prepararte el plan de tu caso.<\/div><!-- EL INFORME LO COMPONE EL ERP (10-ago-2026). Antes esto era window.print(): imprim&#237;a la misma pantalla que la persona estaba mirando, sin marca y sin nada nuevo, mientras el candado promet&#237;a &#171;informe COMPLETO en PDF&#187;. Ahora el ERP devuelve un PDF con el formato de la casa, y de paso cuenta la pulsaci&#243;n en el servidor &#8212;el &#250;nico escal&#243;n del embudo que un bloqueador no puede tumbar. --><button class=\"btn-pdf\" id=\"btn-pdf\">&#11015; Descargar informe en PDF<\/button><\/section><\/div><footer class=\"foot\"> GRUPODEX MANAGEMENT & SERVICES, S.L. &#183; Gesti&#243;n y Representaci&#243;n Patrimonial<br> Estimaci&#243;n orientativa &#183; Andaluc&#237;a 2026 &#183; No constituye asesoramiento fiscal <\/footer><datalist id=\"munisData\"><\/datalist><\/div>\r\n\r\n<script>\r\n\r\n\/* ===================================================================\r\n   LA MEDIDA COMPARTIDA (12-ago-2026)\r\n   ===================================================================\r\n   Todo lo que esta p\\u00e1gina mide sale de aqu\\u00ed: la sesi\\u00f3n, el canal, la pieza\r\n   y el aparato. Antes cada bloque se lo calculaba por su cuenta y cada uno\r\n   llegaba a una respuesta distinta \\u2014 el `canal` se le\\u00eda tres veces en tres\r\n   sitios y de la forma m\\u00e1s pobre, y la sesi\\u00f3n solo exist\\u00eda en el snippet\r\n   del formulario. Un embudo cuyos escalones no se pueden unir no es un\r\n   embudo: son contadores sueltos.\r\n\r\n   SEIS COSAS QUE ARREGLA:\r\n   1. `dispositivo` en TODOS los latidos. La p\\u00e1gina entera se dise\\u00f1\\u00f3 para\r\n      m\\u00f3vil (el pliegue, el teclado que tapa el bot\\u00f3n) y ni un solo dato\r\n      dec\\u00eda en qu\\u00e9 aparato pasaba lo que pasaba.\r\n   2. UNA sola sesi\\u00f3n an\\u00f3nima por visita, compartida con el snippet de\r\n      telemetr\\u00eda del formulario (WPCode 3116): con ella el embudo se sigue\r\n      de punta a punta con un JOIN. An\\u00f3nima de verdad: hexadecimal\r\n      aleatorio, nace con la p\\u00e1gina y muere con ella. No identifica a\r\n      nadie, solo une escalones de una misma visita.\r\n   3. `canal` DE LA COOKIE, no de la URL (ver `canal()` abajo).\r\n   4. `pieza` (utm_content): sin ella se sabe qu\\u00e9 red llena el embudo,\r\n      pero no qu\\u00e9 anuncio.\r\n   5. Latidos de visibilidad y de salida (ver m\\u00e1s abajo, en su sitio).\r\n   6. Latidos de aver\\u00eda: un env\\u00edo que falla ya no se cuenta como un \\u00abno\r\n      quiso\\u00bb.\r\n\r\n   REGLA LEGAL \\u2014 NO SE RELAJA NUNCA: de aqu\\u00ed no sale JAM\\u00c1S el contenido de\r\n   un campo personal. Todo lo que se manda es vocabulario cerrado\r\n   ('movil'\/'escritorio', los nombres de evento, los tramos) o una etiqueta\r\n   validada contra un patr\\u00f3n que EXIGE UNA LETRA \\u2014 sin esa exigencia, un\r\n   tel\\u00e9fono metido en el `utm_source` de la URL viajar\\u00eda como \\u00abcanal\\u00bb. En\r\n   este bloque no hay ni un solo `.value` de un campo del formulario.\r\n\r\n   SI ESTO FALLA, LA CALCULADORA NI SE ENTERA: todo va en try\/catch y no\r\n   toca absolutamente nada de lo que ve el visitante.\r\n   =================================================================== *\/\r\nwindow.PRODEX_MEDIDA = (function(){\r\n    var API = 'https:\/\/erp.grupoprodex.com\/api\/leads\/calculadora\/evento';\r\n    var TOKEN = '9cf358810431154e5dd19054108589QYsUte3QuFMEvEg301854108';\r\n    var nacida = Date.now();\r\n\r\n    \/* Id de sesi\\u00f3n AN\\u00d3NIMO. Mismo formato que el que ya usaba el snippet del\r\n       formulario (hex de 32), para que las dos tablas casen por igualdad. *\/\r\n    var sesion = (function(){\r\n        try{\r\n            var b = new Uint8Array(16);\r\n            (window.crypto || window.msCrypto).getRandomValues(b);\r\n            var s = '';\r\n            for(var i=0;i<b.length;i++) s += (b[i]+256).toString(16).slice(1);\r\n            return s;\r\n        }catch(e){\r\n            \/* Sin crypto no hay sesi\\u00f3n: mejor ninguna que una repetida, que\r\n               unir\\u00eda por JOIN visitas que no tienen nada que ver. *\/\r\n            return null;\r\n        }\r\n    })();\r\n\r\n    \/* Una etiqueta v\\u00e1lida lleva AL MENOS UNA LETRA. Es la misma regla que el\r\n       servidor: sin ella, un tel\\u00e9fono de nueve d\\u00edgitos colado en el utm_source\r\n       de la URL acabar\\u00eda guardado como \\u00abcanal\\u00bb en la tabla an\\u00f3nima. *\/\r\n    function limpia(v){\r\n        try{\r\n            v = String(v == null ? '' : v).trim();\r\n            return \/^(?=.*[A-Za-z])[A-Za-z0-9_\\-]{1,40}$\/.test(v) ? v : null;\r\n        }catch(e){ return null; }\r\n    }\r\n\r\n    function param(n){\r\n        try{ return new URLSearchParams(location.search).get(n) || ''; }catch(e){ return ''; }\r\n    }\r\n\r\n    \/* La cookie de atribuci\\u00f3n de 90 d\\u00edas que escribe el fragmento de medici\\u00f3n\r\n       del sitio (gclid\/fbclid\/utm_* + landing + referrer). *\/\r\n    function cookie(){\r\n        try{\r\n            var m = document.cookie.match(\/(?:^|;)\\s*prodex_attr\\s*=\\s*([^;]+)\/);\r\n            return m ? JSON.parse(decodeURIComponent(m[1])) : {};\r\n        }catch(e){ return {}; }\r\n    }\r\n\r\n    \/* ====== EL `canal` MENT\\u00cdA (12-ago-2026) ======\r\n       Los latidos le\\u00edan el canal SIEMPRE del utm_source de la URL. Quien pincha\r\n       un anuncio de Meta, se va, y vuelve dos d\\u00edas despu\\u00e9s escribiendo la\r\n       direcci\\u00f3n a mano, llegaba sin utm y contaba como `canal: null` \\u2014 aunque\r\n       su cookie `prodex_attr`, que dura 90 d\\u00edas, siguiera diciendo que vino de\r\n       Meta. Resultado: el embudo infravaloraba a Meta POR DISE\\u00d1O, y justo en la\r\n       gente que m\\u00e1s se lo piensa, que es la que m\\u00e1s vale en este negocio.\r\n\r\n       El orden es EL MISMO que usa el servidor en resolveCanal(): sesi\\u00f3n de\r\n       atribuci\\u00f3n \\u2192 utm de la URL \\u2192 cookie \\u2192 deducir la red por el id de clic\r\n       de pago. Que sea el mismo importa m\\u00e1s que cu\\u00e1l sea: si la landing\r\n       atribuyera con un orden y el ERP con otro, el latido y el lead de la\r\n       MISMA persona contar\\u00edan en canales distintos y nadie entender\\u00eda el\r\n       descuadre. Y la URL va por delante de la cookie a prop\\u00f3sito: en una\r\n       visita con utm manda el clic de HOY, no el de hace dos meses. *\/\r\n    function canal(){\r\n        try{\r\n            var c = null;\r\n            if(typeof PRODEX_ATRIB !== 'undefined' && PRODEX_ATRIB) c = limpia(PRODEX_ATRIB.canal);\r\n            if(!c) c = limpia(param('utm_source'));\r\n            var k = cookie();\r\n            if(!c) c = limpia(k.utm_source);\r\n            if(!c && k.gclid) c = 'google_ads';\r\n            if(!c && k.fbclid) c = 'meta';\r\n            return c;\r\n        }catch(e){ return null; }\r\n    }\r\n\r\n    \/* La PIEZA es el anuncio concreto (utm_content, p. ej. `anuncio2`). Con el\r\n       canal solo se sabe qu\\u00e9 RED llena el embudo; con esto, qu\\u00e9 anuncio \\u2014 que\r\n       es lo que se puede apagar o duplicar. Misma cascada que el canal. *\/\r\n    function pieza(){\r\n        try{\r\n            var p = null;\r\n            if(typeof PRODEX_ATRIB !== 'undefined' && PRODEX_ATRIB) p = limpia(PRODEX_ATRIB.pieza);\r\n            if(!p) p = limpia(param('utm_content'));\r\n            if(!p) p = limpia(param('pieza'));\r\n            if(!p) p = limpia(cookie().utm_content);\r\n            return p;\r\n        }catch(e){ return null; }\r\n    }\r\n\r\n    \/* EL MISMO CORTE QUE DECIDE LA CONDUCTA DE LA P\\u00c1GINA: si midi\\u00e9ramos con un\r\n       ancho distinto del que usa el c\\u00f3digo para, por ejemplo, no robar el foco,\r\n       estar\\u00edamos midiendo una p\\u00e1gina que no es esta. Se mira en cada latido y\r\n       no una sola vez: quien gira el m\\u00f3vil a horizontal cambia de lado, y lo\r\n       que interesa es el aparato con el que se estaba cuando pas\\u00f3 la cosa. *\/\r\n    function dispositivo(){\r\n        try{\r\n            return (window.matchMedia && window.matchMedia('(max-width:680px)').matches) ? 'movil' : 'escritorio';\r\n        }catch(e){ return null; }\r\n    }\r\n\r\n    \/* Tiempo en p\\u00e1gina EN TRAMOS, nunca el segundo exacto: para saber si la\r\n       gente rebota basta la banda, y un cron\\u00f3metro por visita es el primer\r\n       ladrillo de un perfil que aqu\\u00ed no queremos tener. *\/\r\n    function tramo(){\r\n        try{\r\n            var s = (Date.now() - nacida) \/ 1000;\r\n            if(s < 10)  return '-10s';\r\n            if(s < 30)  return '10-30s';\r\n            if(s < 120) return '30-120s';\r\n            return '+120s';\r\n        }catch(e){ return null; }\r\n    }\r\n\r\n    function cuerpo(evento, extra){\r\n        var d = {\r\n            evento: evento,\r\n            nivel: (extra && extra.nivel) || null,\r\n            canal: canal(),\r\n            pieza: pieza(),\r\n            dispositivo: dispositivo(),\r\n            sesion: sesion\r\n        };\r\n        if(extra && extra.tramo) d.tramo = extra.tramo;\r\n        return d;\r\n    }\r\n\r\n    \/* Dos caminos de salida, el mismo que ya usa el snippet del formulario:\r\n       fetch keepalive para lo normal, y navigator.sendBeacon para el \\u00faltimo\r\n       suspiro de la pesta\\u00f1a. sendBeacon NO puede poner cabeceras, as\\u00ed que ah\\u00ed\r\n       el token viaja en el cuerpo (_token) y el cuerpo va como text\/plain para\r\n       no obligar a un preflight que nadie contestar\\u00eda a esas alturas. *\/\r\n    function envia(evento, extra){\r\n        try{\r\n            fetch(API, {\r\n                method:'POST',\r\n                headers:{'Content-Type':'application\/json','X-Lead-Token':TOKEN},\r\n                body: JSON.stringify(cuerpo(evento, extra)),\r\n                keepalive:true\r\n            })['catch'](function(){});\r\n        }catch(e){}\r\n    }\r\n\r\n    function enviaAlIrse(evento, extra){\r\n        try{\r\n            var d = cuerpo(evento, extra);\r\n            d._token = TOKEN;\r\n            if(navigator.sendBeacon){\r\n                navigator.sendBeacon(API, new Blob([JSON.stringify(d)], {type:'text\/plain'}));\r\n            }else{\r\n                envia(evento, extra);\r\n            }\r\n        }catch(e){}\r\n    }\r\n\r\n    return {\r\n        sesion: sesion,\r\n        canal: canal,\r\n        pieza: pieza,\r\n        dispositivo: dispositivo,\r\n        tramo: tramo,\r\n        envia: envia,\r\n        enviaAlIrse: enviaAlIrse\r\n    };\r\n})();\r\n\r\n\/* ===================================================================\r\n   LO QUE SE VIO, Y CU\\u00c1NTO SE QUED\\u00d3 (12-ago-2026)\r\n   ===================================================================\r\n   Este bloque va aparte y no depende de nada del resto de la p\\u00e1gina: si\r\n   fallara, la calculadora sigue igual. Puede leer el DOM directamente\r\n   porque el marcado est\\u00e1 ARRIBA de este \r\n;\r\n y ya existe cuando esto\r\n   corre \\u2014 no hace falta esperar a DOMContentLoaded.\r\n\r\n   POR QU\\u00c9 HAC\\u00cdA FALTA: hasta hoy, quien se iba sin tocar la calculadora\r\n   contaba exactamente igual que quien la vio entera y pas\\u00f3 de largo. Son\r\n   dos aver\\u00edas OPUESTAS: la primera se arregla subiendo el bloque (en m\\u00f3vil\r\n   el primer campo estaba a 1.211 px, pantalla y media de scroll); la\r\n   segunda, cambiando lo que el bloque dice. Sin saber cu\\u00e1l es, cualquier\r\n   cambio es una apuesta.\r\n\r\n   Se mide VISIBILIDAD, no scroll: la posici\\u00f3n de la barra no dice si algo\r\n   entr\\u00f3 en pantalla \\u2014una pantalla alta ense\\u00f1a tres bloques de golpe y una\r\n   baja ninguno\\u2014. IntersectionObserver responde a la pregunta buena: \\u00bfesto\r\n   estuvo delante de los ojos? Un solo latido por bloque y por visita; se\r\n   deja de observar en cuanto se dispara.\r\n   =================================================================== *\/\r\n(function(){\r\n    try{\r\n        if(!window.PRODEX_MEDIDA) return;\r\n\r\n        function alVerse(el, evento){\r\n            try{\r\n                if(!el) return;\r\n                if(!('IntersectionObserver' in window)){\r\n                    \/* Navegador viejo: no se inventa el dato. Un latido falso\r\n                       es peor que un hueco \\u2014 el hueco se ve, la mentira no. *\/\r\n                    return;\r\n                }\r\n                var obs = new IntersectionObserver(function(filas){\r\n                    try{\r\n                        for(var i=0;i<filas.length;i++){\r\n                            if(!filas[i].isIntersecting) continue;\r\n                            obs.disconnect();          \/* una vez por visita *\/\r\n                            PRODEX_MEDIDA.envia(evento);\r\n                            return;\r\n                        }\r\n                    }catch(e){}\r\n                }, {\r\n                    \/* Un pixel asomando no es \\u00ablo vio\\u00bb. Se exige una cuarta\r\n                       parte del bloque dentro de la pantalla, que es cuando\r\n                       una persona de verdad puede leer de qu\\u00e9 va. *\/\r\n                    threshold: 0.25\r\n                });\r\n                obs.observe(el);\r\n            }catch(e){}\r\n        }\r\n\r\n        \/* Los tres escalones que faltaban, cada uno con su pareja de \\u00abtoc\\u00f3\\u00bb:\r\n             ve_calculadora \\u2194 empieza    (vio el primer campo \/ lo toc\\u00f3)\r\n             ve_resultado   \\u2194 resultado  (vio el balance \/ le sali\\u00f3 la cifra)\r\n             ve_formulario  \\u2194 nosotros   (vio el formulario \/ entr\\u00f3 en \\u00e9l) *\/\r\n        alVerse(document.getElementById('v_pob'),       've_calculadora');\r\n        alVerse(document.querySelector('.grand'),       've_resultado');\r\n        alVerse(document.querySelector('.lead'),        've_formulario');\r\n\r\n        \/* ====== EL LATIDO DE SALIDA ======\r\n           Cu\\u00e1nto se qued\\u00f3 la gente, en tramos. Va por sendBeacon en `pagehide`\r\n           porque es lo \\u00daNICO que llega de verdad cuando se cierra la pesta\\u00f1a:\r\n           un fetch normal lo cancela el navegador a media petici\\u00f3n.\r\n           `visibilitychange` es la red de seguridad de iOS, donde `pagehide` no\r\n           siempre dispara al cambiar de app. De ah\\u00ed el cerrojo: una sola vez. *\/\r\n        var salidaMandada = false;\r\n        function alIrse(){\r\n            try{\r\n                if(salidaMandada) return;\r\n                salidaMandada = true;\r\n                PRODEX_MEDIDA.enviaAlIrse('salida', {tramo: PRODEX_MEDIDA.tramo()});\r\n            }catch(e){}\r\n        }\r\n        window.addEventListener('pagehide', alIrse);\r\n        document.addEventListener('visibilitychange', function(){\r\n            if(document.visibilityState === 'hidden') alIrse();\r\n        });\r\n\r\n        \/* ====== EL QUE LLAMA NO ERA NADIE ======\r\n           El tel\\u00e9fono publicado en el aviso de error de env\\u00edo es pulsable \\u2014en\r\n           un m\\u00f3vil se toca y llama\\u2014 y no estaba instrumentado: quien llamaba\r\n           desde aqu\\u00ed entraba por la centralita sin nada que lo relacionara con\r\n           la campa\\u00f1a que lo trajo. Un lead fantasma, y encima de los buenos:\r\n           llama quien ya ha escrito su nombre y su tel\\u00e9fono y no ha podido\r\n           enviarlos.\r\n           Se enganchan TODOS los `tel:` y `wa.me`\/`api.whatsapp.com` de la\r\n           p\\u00e1gina por selector, no por id, para que un enlace nuevo quede medido\r\n           el d\\u00eda que se a\\u00f1ada sin que nadie se acuerde de tocar esto. *\/\r\n        function midePulsacion(selector, evento){\r\n            try{\r\n                var enlaces = document.querySelectorAll(selector);\r\n                for(var i=0;i<enlaces.length;i++){\r\n                    enlaces[i].addEventListener('click', function(){\r\n                        \/* sendBeacon y no fetch: pulsar un `tel:` puede sacar\r\n                           al navegador de la p\\u00e1gina hacia la app del tel\\u00e9fono,\r\n                           y una petici\\u00f3n normal se queda a medias. *\/\r\n                        try{ PRODEX_MEDIDA.enviaAlIrse(evento); }catch(e){}\r\n                    });\r\n                }\r\n            }catch(e){}\r\n        }\r\n        midePulsacion('a[href^=\"tel:\"]', 'clic_telefono');\r\n        midePulsacion('a[href*=\"wa.me\"], a[href*=\"api.whatsapp.com\"]', 'clic_whatsapp');\r\n    }catch(e){ \/* nunca jam\\u00e1s romper la calculadora *\/ }\r\n})();\r\n\r\n    \/* ====== ENTREGA POR WHATSAPP (30-ago-2026) \\u2014 constantes ======\r\n       El resultado no se pinta: se enmascara en pantalla y los valores reales\r\n       viven en CIFRAS, de donde los lee construirPayload(). El informe llega\r\n       por WhatsApp con la REF que sube junto al c\\u00e1lculo. *\/\r\n    var WHATSAPP_PRODEX = '34613028382'; \/* WhatsApp de PRODEX (internacional, sin +) \\u2014 confirmado por Pedro 30-ago-2026 *\/\r\n    var MASCARA = '\\u2022\\u2022\\u2022 \\u20ac';\r\n    var CIFRAS = { liquido:'\\u2014 \\u20ac', coste:'\\u2014 \\u20ac', balance:'\\u2014 \\u20ac', cuota:'\\u2014 \\u20ac', desgloseVenta:[], desgloseCompra:[] };\r\n\r\n    \/* ====== DATOS ====== *\/\r\n    var MUNI ={\"Abla\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":24.0},\"Abrucena\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"Adamuz\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Adra\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Agr\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"Aguadulce\":{\"provincia\":\"Sevilla\",\"tipo\":16.0},\"Aguilar de la Frontera\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Al\\u00e1jar\":{\"provincia\":\"Huelva\",\"tipo\":23.0},\"Alameda\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":24.0},\"Alamedilla\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Alan\\u00eds\":{\"provincia\":\"Sevilla\",\"tipo\":8.0},\"Albaida del Aljarafe\":{\"provincia\":\"Sevilla\",\"tipo\":21.0},\"Alb\\u00e1nchez\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Albanchez de M\\u00e1gina\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":16.0},\"Alboloduy\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"Albolote\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Albond\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":18.0},\"Albox\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Albu\\u00f1\\u00e1n\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Albu\\u00f1ol\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Albu\\u00f1uelas\":{\"provincia\":\"Granada\",\"tipo\":21.0},\"Alcal\\u00e1 de Guada\\u00edra\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Alcal\\u00e1 de los Gazules\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":25.0},\"Alcal\\u00e1 del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Alcal\\u00e1 del Valle\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":19.0},\"Alcal\\u00e1 la Real\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":12.0},\"Alcaracejos\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Alcauc\\u00edn\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Alcaudete\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Alcolea\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":22.0},\"Alcolea del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":21.0},\"Alc\\u00f3ntar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Alcudia de Monteagud\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":22.0},\"Aldeaquemada\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Aldeire\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Alfacar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Alfarnate\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":16.0},\"Alfarnatejo\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Algaba (La)\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Alg\\u00e1mitas\":{\"provincia\":\"Sevilla\",\"tipo\":16.0},\"Algar\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":0.0},\"Algarrobo\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Algatoc\\u00edn\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Algeciras\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Algodonales\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":25.0},\"Alhabia\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":18.0},\"Alhama de Almer\\u00eda\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Alhama de Granada\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Alhaur\\u00edn de la Torre\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Alhaur\\u00edn el Grande\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":27.0},\"Alhend\\u00edn\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Alic\\u00fan\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":16.0},\"Alic\\u00fan de Ortega\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Aljaraque\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Alm\\u00e1char\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Almad\\u00e9n de la Plata\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Almargen\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Almedinilla\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":28.0},\"Almeg\\u00edjar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Almendro (El)\":{\"provincia\":\"Huelva\",\"tipo\":20.0},\"Almensilla\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Almer\\u00eda\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Alm\\u00f3cita\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Almod\\u00f3var del R\\u00edo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":26.0},\"Almog\\u00eda\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Almonaster la Real\":{\"provincia\":\"Huelva\",\"tipo\":27.0},\"Almonte\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Almu\\u00f1\\u00e9car\":{\"provincia\":\"Granada\",\"tipo\":26.0},\"\\u00c1lora\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":17.0},\"Alosno\":{\"provincia\":\"Huelva\",\"tipo\":18.5},\"Alozaina\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":15.0},\"Alpandeire\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Alpujarra de la Sierra\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Alquife\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Alsodux\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":19.0},\"And\\u00fajar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.11},\"Antas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":20.0},\"Antequera\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":28.0},\"A\\u00f1ora\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Aracena\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Arahal\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Arboleas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"\\u00c1rchez\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Archidona\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":28.0},\"Arcos de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":25.0},\"Ardales\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Arenas\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":10.0},\"Arenas del Rey\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Arjona\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Arjonilla\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Armilla\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Armu\\u00f1a de Almanzora\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Aroche\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Arquillos\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":24.0},\"Arriate\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Arroyo del Ojanco\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Arroyomolinos de Le\\u00f3n\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Atajate\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Atarfe\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Ayamonte\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Aznalc\\u00e1zar\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Aznalc\\u00f3llar\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Bacares\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Badolatosa\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Baena\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":27.0},\"Baeza\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Bail\\u00e9n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Balanegra\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Ba\\u00f1os de la Encina\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Barbate\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":28.0},\"Barrios (Los)\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":27.56},\"Bay\\u00e1rcal\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Bayarque\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Baza\":{\"provincia\":\"Granada\",\"tipo\":13.0},\"Beas\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Beas de Guadix\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Beas de Segura\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"B\\u00e9dar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Bedmar y Garc\\u00edez\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Beg\\u00edjar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Beires\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Belalc\\u00e1zar\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Belmez\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"B\\u00e9lmez de la Moraleda\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Benacaz\\u00f3n\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Benadalid\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Benahadux\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Benahav\\u00eds\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Benalm\\u00e1dena\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":27.5},\"Benal\\u00faa\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Benal\\u00faa de las Villas\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Benalup-Casas Viejas\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":28.5},\"Benamargosa\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Benamaurel\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Benamej\\u00ed\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":26.0},\"Benamocarra\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Benaocaz\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":26.0},\"Benaoj\\u00e1n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":16.0},\"Benarrab\\u00e1\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Benatae\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Benitagla\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Benizal\\u00f3n\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Bentarique\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":24.0},\"B\\u00e9rchules\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Berja\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Berrocal\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Bl\\u00e1zquez (Los)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Bollullos de la Mitaci\\u00f3n\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Bollullos Par del Condado\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Bonares\":{\"provincia\":\"Huelva\",\"tipo\":19.0},\"Borge (El)\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Bormujos\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Bornos\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":25.0},\"Bosque (El)\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Brenes\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Bubi\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Bujalance\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Burgo (El)\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Burguillos\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Busqu\\u00edstar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cabezas de San Juan (Las)\":{\"provincia\":\"Sevilla\",\"tipo\":27.0},\"Cabezas Rubias\":{\"provincia\":\"Huelva\",\"tipo\":21.0},\"Cabra\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.8},\"Cabra del Santo Cristo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Cac\\u00edn\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"C\\u00e1diar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"C\\u00e1diz\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"C\\u00e1jar\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Cala\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Calahorra (La)\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cala\\u00f1as\":{\"provincia\":\"Huelva\",\"tipo\":17.0},\"Calicasas\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Camas\":{\"provincia\":\"Sevilla\",\"tipo\":29.37},\"Cambil\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":29.0},\"Campana (La)\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Campillo (El)\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Campillo de Arenas\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"Campillos\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.13},\"Campofr\\u00edo\":{\"provincia\":\"Huelva\",\"tipo\":22.0},\"Campot\\u00e9jar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Canena\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":0.0},\"Caniles\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Canillas de Aceituno\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Canillas de Albaida\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Canj\\u00e1yar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Cantillana\":{\"provincia\":\"Sevilla\",\"tipo\":29.7},\"Cantoria\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":24.0},\"Ca\\u00f1ada Rosal\":{\"provincia\":\"Sevilla\",\"tipo\":10.0},\"C\\u00e1\\u00f1ar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Ca\\u00f1averal de Le\\u00f3n\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Ca\\u00f1ete de las Torres\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Ca\\u00f1ete la Real\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Capileira\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Carataunas\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Carboneros\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":21.0},\"Carcabuey\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":29.75},\"C\\u00e1rcheles\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Carde\\u00f1a\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Carlota (La)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":28.73},\"Carmona\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Carolina (La)\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":22.0},\"Carpio (El)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":21.0},\"Carratraca\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":24.0},\"Carri\\u00f3n de los C\\u00e9spedes\":{\"provincia\":\"Sevilla\",\"tipo\":18.0},\"Cartajima\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":19.0},\"C\\u00e1rtama\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":27.0},\"Cartaya\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Casabermeja\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":28.0},\"Casarabonela\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Casares\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Casariche\":{\"provincia\":\"Sevilla\",\"tipo\":15.0},\"Casta\\u00f1o del Robledo\":{\"provincia\":\"Huelva\",\"tipo\":22.0},\"C\\u00e1staras\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Castellar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Castellar de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Castilblanco de los Arroyos\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Castilleja de Guzm\\u00e1n\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Castilleja de la Cuesta\":{\"provincia\":\"Sevilla\",\"tipo\":29.0},\"Castilleja del Campo\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Castill\\u00e9jar\":{\"provincia\":\"Granada\",\"tipo\":17.0},\"Castillo de las Guardas (El)\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Castillo de Locub\\u00edn\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Castril\":{\"provincia\":\"Granada\",\"tipo\":18.0},\"Castro de Filabres\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Castro del R\\u00edo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":26.0},\"Cazalilla\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":23.0},\"Cazalla de la Sierra\":{\"provincia\":\"Sevilla\",\"tipo\":15.0},\"Cazorla\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Cenes de la Vega\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cerro de And\\u00e9valo (El)\":{\"provincia\":\"Huelva\",\"tipo\":21.0},\"Chauchina\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Chercos\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Chiclana de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Chiclana de Segura\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":20.0},\"Chillu\\u00e9var\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Chimeneas\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Chipiona\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":25.0},\"Chirivel\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Chucena\":{\"provincia\":\"Huelva\",\"tipo\":27.0},\"Churriana de la Vega\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cijuela\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"C\\u00f3bdar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"Cogollos de Guadix\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cogollos de la Vega\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Co\\u00edn\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Colmenar\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":28.0},\"Colomera\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Comares\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"C\\u00f3mpeta\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Conil de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":27.0},\"Conquista\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Constantina\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":27.52},\"Coria del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":26.87},\"Coripe\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Coronil (El)\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Corrales (Los)\":{\"provincia\":\"Sevilla\",\"tipo\":19.0},\"Corteconcepci\\u00f3n\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Cortegana\":{\"provincia\":\"Huelva\",\"tipo\":24.0},\"Cortelazor\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Cortes de Baza\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Cortes de la Frontera\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":23.0},\"Cortes y Graena\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Cuervo de Sevilla (El)\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Cuevas Bajas\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Cuevas de San Marcos\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Cuevas del Almanzora\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Cuevas del Becerro\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":19.0},\"Cuevas del Campo\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"C\\u00fallar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"C\\u00fallar Vega\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Cumbres de Enmedio\":{\"provincia\":\"Huelva\",\"tipo\":22.0},\"Cumbres de San Bartolom\\u00e9\":{\"provincia\":\"Huelva\",\"tipo\":28.0},\"Cumbres Mayores\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"C\\u00fatar\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":16.0},\"Dal\\u00edas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Darro\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Dehesas de Guadix\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Dehesas Viejas\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Deifontes\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Diezma\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"D\\u00edlar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"D\\u00f3lar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Domingo P\\u00e9rez de Granada\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Do\\u00f1a Menc\\u00eda\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.46},\"Dos Hermanas\":{\"provincia\":\"Sevilla\",\"tipo\":26.4},\"Dos Torres\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"D\\u00fadar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"D\\u00farcal\":{\"provincia\":\"Granada\",\"tipo\":22.0},\"\\u00c9cija\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Ejido (El)\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Encinas Reales\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Encinasola\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Enix\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Escacena del Campo\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Esca\\u00f1uela\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":24.0},\"Esc\\u00fazar\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Espartinas\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Espejo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Espel\\u00fay\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":22.0},\"Espera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":18.0},\"Espiel\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Estepa\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Estepona\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Faraj\\u00e1n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Felix\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Fern\\u00e1n-N\\u00fa\\u00f1ez\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Ferreira\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Fines\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Fi\\u00f1ana\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Fond\\u00f3n\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Fonelas\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Fornes\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Frailes\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":0.0},\"Freila\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Frigiliana\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":29.0},\"Fuengirola\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Fuensanta de Martos\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Fuente Carreteros\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":19.0},\"Fuente de Piedra\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Fuente la Lancha\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Fuente Obejuna\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Fuente Palmera\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":14.0},\"Fuente Vaqueros\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Fuenteheridos\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Fuentes de Andaluc\\u00eda\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Fuente-T\\u00f3jar\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.5},\"Fuerte del Rey\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":21.0},\"Gabias (Las)\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"G\\u00e1dor\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Galaroza\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Galera\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Gallardos (Los)\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Garrobo (El)\":{\"provincia\":\"Sevilla\",\"tipo\":22.0},\"Garrucha\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Gastor (El)\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":18.0},\"Gauc\\u00edn\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Gelves\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Genalguacil\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"G\\u00e9nave\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Gerena\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"G\\u00e9rgal\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":19.0},\"Gibrale\\u00f3n\":{\"provincia\":\"Huelva\",\"tipo\":28.0},\"Gilena\":{\"provincia\":\"Sevilla\",\"tipo\":2.0},\"Gines\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Gobernador\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"G\\u00f3jar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Gorafe\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Granada\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Granada de R\\u00edo-Tinto (La)\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Granado (El)\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Granjuela (La)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Grazalema\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":26.0},\"Guadahortuna\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Guadalcanal\":{\"provincia\":\"Sevilla\",\"tipo\":16.0},\"Guadalc\\u00e1zar\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Guadix\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Guajares (Los)\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Gualchos\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Guardia de Ja\\u00e9n (La)\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Guaro\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Guarrom\\u00e1n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"G\\u00fcejar Sierra\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"G\\u00fcev\\u00e9jar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Guijarrosa (La)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Guijo (El)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Guillena\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Herrera\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Higuera de Calatrava\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":0.0},\"Higuera de la Sierra\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Hinojales\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Hinojares\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Hinojos\":{\"provincia\":\"Huelva\",\"tipo\":2.8},\"Hinojosa del Duque\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Hornachuelos\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":17.41},\"Hornos\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"Hu\\u00e9cija\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Hu\\u00e9lago\":{\"provincia\":\"Granada\",\"tipo\":29.0},\"Huelma\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Huelva\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Hu\\u00e9neja\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"Hu\\u00e9rcal de Almer\\u00eda\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":21.0},\"Hu\\u00e9rcal-Overa\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Huesa\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"Hu\\u00e9scar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Hu\\u00e9tor de Santill\\u00e1n\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Hu\\u00e9tor T\\u00e1jar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Hu\\u00e9tor Vega\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Hu\\u00e9var del Aljarafe\":{\"provincia\":\"Sevilla\",\"tipo\":22.0},\"Humilladero\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Ibros\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Illar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":20.0},\"\\u00cdllora\":{\"provincia\":\"Granada\",\"tipo\":18.0},\"Instinci\\u00f3n\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Iruela (La)\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Isla Cristina\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Isla Mayor\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Ist\\u00e1n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":16.0},\"\\u00cdtrabo\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Izn\\u00e1jar\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Iznalloz\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Iznate\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Iznatoraf\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":0.0},\"Jabalquinto\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.47},\"Jabugo\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Ja\\u00e9n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":29.0},\"Jamilena\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"J\\u00e1tar\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Jayena\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Jerez de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Jerez del Marquesado\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Jete\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Jimena\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":20.0},\"Jimena de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"J\\u00f3dar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Jubrique\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Jun\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Juviles\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"J\\u00fazcar\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"L\\u00e1char\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Lahiguera\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":16.0},\"Lanjar\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Lanteira\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Lantejuela\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Laroya\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":17.0},\"Larva\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":20.0},\"Laujar de Andarax\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Lebrija\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Lecr\\u00edn\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Lenteg\\u00ed\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Lepe\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"L\\u00edjar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":20.0},\"Linares\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Linares de la Sierra\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"L\\u00ednea de la Concepci\\u00f3n (La)\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Lobras\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Loja\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Lopera\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Lora de Estepa\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Lora del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Lubr\\u00edn\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Lucainena de las Torres\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"L\\u00facar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Lucena\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Lucena del Puerto\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Lugros\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Luisiana (La)\":{\"provincia\":\"Sevilla\",\"tipo\":2.2},\"L\\u00fajar\":{\"provincia\":\"Granada\",\"tipo\":23.0},\"Lupi\\u00f3n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":0.0},\"Luque\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Macael\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":17.0},\"Macharaviaya\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Madro\\u00f1o (El)\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Mairena del Alcor\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Mairena del Aljarafe\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"M\\u00e1laga\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":29.0},\"Malah\\u00e1 (La)\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Mancha Real\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Manilva\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":27.0},\"Manzanilla\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Maracena\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Marbella\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":29.0},\"Marchal\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Marchena\":{\"provincia\":\"Sevilla\",\"tipo\":28.05},\"Mar\\u00eda\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Marinaleda\":{\"provincia\":\"Sevilla\",\"tipo\":22.0},\"Marines (Los)\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Marmolejo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":22.0},\"Mart\\u00edn de la Jara\":{\"provincia\":\"Sevilla\",\"tipo\":19.0},\"Martos\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Medina-Sidonia\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":28.0},\"Meng\\u00edbar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Mijas\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":22.0},\"Minas de Riotinto\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Mocl\\u00edn\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Moclinejo\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Moguer\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Moj\\u00e1car\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":20.0},\"Mojonera (La)\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Molares (Los)\":{\"provincia\":\"Sevilla\",\"tipo\":23.0},\"Mollina\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":10.0},\"Molv\\u00edzar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Monachil\":{\"provincia\":\"Granada\",\"tipo\":23.0},\"Monda\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":2.6},\"Montalb\\u00e1n de C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Montecorto\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Montefr\\u00edo\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Montejaque\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Montej\\u00edcar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Montellano\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Montemayor\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Montilla\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Montillana\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Montiz\\u00f3n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":22.0},\"Montoro\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":28.8},\"Monturque\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Moraleda de Zafayona\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Morel\\u00e1bor\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Moriles\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Mor\\u00f3n de la Frontera\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Motril\":{\"provincia\":\"Granada\",\"tipo\":25.5},\"Murtas\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Nacimiento\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Nava (La)\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Navas de la Concepci\\u00f3n (Las)\":{\"provincia\":\"Sevilla\",\"tipo\":26.0},\"Navas de San Juan\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Nerja\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":21.0},\"Nerva\":{\"provincia\":\"Huelva\",\"tipo\":0.45},\"Nevada\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Niebla\":{\"provincia\":\"Huelva\",\"tipo\":2.7},\"Nig\\u00fcelas\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"N\\u00edjar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":25.0},\"N\\u00edvar\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Noalejo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":27.0},\"Nueva Carteya\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Obejo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Og\\u00edjares\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Ohanes\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":15.0},\"Oj\\u00e9n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Olivares\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Olula de Castro\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Olula del R\\u00edo\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Olvera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":20.0},\"Orce\":{\"provincia\":\"Granada\",\"tipo\":17.0},\"Orcera\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"\\u00d3rgiva\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Oria\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Osuna\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Ot\\u00edvar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Padul\":{\"provincia\":\"Granada\",\"tipo\":27.0},\"Padules\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Palacios y Villafranca (Los)\":{\"provincia\":\"Sevilla\",\"tipo\":28.23},\"Palenciana\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Palma del Condado (La)\":{\"provincia\":\"Huelva\",\"tipo\":29.0},\"Palma del R\\u00edo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.16},\"Palmar de Troya (El)\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Palomares del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Palos de la Frontera\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Pampaneira\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Paradas\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Parauta\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Partaloa\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Paterna de Rivera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Paterna del Campo\":{\"provincia\":\"Huelva\",\"tipo\":20.0},\"Paterna del R\\u00edo\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Paymogo\":{\"provincia\":\"Huelva\",\"tipo\":15.6},\"Peal de Becerro\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":16.5},\"Pechina\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Pedrera\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Pedro Abad\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":23.0},\"Pedro Mart\\u00ednez\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Pedroche\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Pedroso (El)\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Pegalajar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":20.0},\"Peligros\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Pe\\u00f1aflor\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Pe\\u00f1arroya-Pueblonuevo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":24.84},\"Periana\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Peza (La)\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Pilas\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Pinar (El)\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Pinos Genil\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Pinos Puente\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"P\\u00ed\\u00f1ar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Pizarra\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":27.0},\"Pol\\u00edcar\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Polopos\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Porcuna\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"P\\u00f3rtugos\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Posadas\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":27.0},\"Pozo Alc\\u00f3n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Pozoblanco\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":22.66},\"Prado del Rey\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":20.0},\"Priego de C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Pruna\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Puebla de Cazalla (La)\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Puebla de Don Fadrique\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Puebla de Guzm\\u00e1n\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Puebla de los Infantes (La)\":{\"provincia\":\"Sevilla\",\"tipo\":26.0},\"Puebla del R\\u00edo (La)\":{\"provincia\":\"Sevilla\",\"tipo\":15.0},\"Puente de G\\u00e9nave\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"Puente Genil\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":30.0},\"Puerta de Segura (La)\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":23.0},\"Puerto de Santa Mar\\u00eda (El)\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Puerto Moral\":{\"provincia\":\"Huelva\",\"tipo\":22.0},\"Puerto Real\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Puerto Serrano\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":22.0},\"Pujerra\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Pulianas\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Pulp\\u00ed\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Punta Umbr\\u00eda\":{\"provincia\":\"Huelva\",\"tipo\":22.5},\"Purchena\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Purullena\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Qu\\u00e9ntar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Quesada\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"R\\u00e1gol\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Rambla (La)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":18.0},\"Real de la Jara (El)\":{\"provincia\":\"Sevilla\",\"tipo\":22.0},\"Rinc\\u00f3n de la Victoria\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Rinconada (La)\":{\"provincia\":\"Sevilla\",\"tipo\":29.37},\"Riogordo\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":30.0},\"Rioja\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":5.0},\"Rociana del Condado\":{\"provincia\":\"Huelva\",\"tipo\":14.0},\"Roda de Andaluc\\u00eda (La)\":{\"provincia\":\"Sevilla\",\"tipo\":11.0},\"Ronda\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Ronquillo (El)\":{\"provincia\":\"Sevilla\",\"tipo\":25.0},\"Roquetas de Mar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":23.0},\"Rosal de la Frontera\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Rota\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Rubio (El)\":{\"provincia\":\"Sevilla\",\"tipo\":9.45},\"Rubite\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Rus\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Rute\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":29.0},\"Sabiote\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Salar\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Salares\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Salobre\\u00f1a\":{\"provincia\":\"Granada\",\"tipo\":17.0},\"San Bartolom\\u00e9 de la Torre\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"San Fernando\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"San Jos\\u00e9 del Valle\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"San Juan de Aznalfarache\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"San Juan del Puerto\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"San Mart\\u00edn del Tesorillo\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":0.0},\"San Nicol\\u00e1s del Puerto\":{\"provincia\":\"Sevilla\",\"tipo\":8.0},\"San Roque\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":21.0},\"San Sebasti\\u00e1n de los Ballesteros\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":8.0},\"San Silvestre de Guzm\\u00e1n\":{\"provincia\":\"Huelva\",\"tipo\":26.0},\"Sanl\\u00facar de Barrameda\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Sanl\\u00facar de Guadiana\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Sanl\\u00facar la Mayor\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Santa Ana la Real\":{\"provincia\":\"Huelva\",\"tipo\":23.0},\"Santa B\\u00e1rbara de Casa\":{\"provincia\":\"Huelva\",\"tipo\":10.0},\"Santa Cruz de Marchena\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Santa Cruz del Comercio\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"Santa Elena\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":19.0},\"Santa Eufemia\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Santa Fe\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Santa Fe de Mond\\u00fajar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Santa Olalla del Cala\":{\"provincia\":\"Huelva\",\"tipo\":25.0},\"Santaella\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Santiago de Calatrava\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Santiago-Pontones\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Santiponce\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Santisteban del Puerto\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Santo Tom\\u00e9\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":19.2},\"Saucejo (El)\":{\"provincia\":\"Sevilla\",\"tipo\":0.0},\"Sayalonga\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":20.0},\"Sedella\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":10.0},\"Segura de la Sierra\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Sen\\u00e9s\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":25.0},\"Ser\\u00f3n\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Serrato\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Setenil de las Bodegas\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":16.8},\"Sevilla\":{\"provincia\":\"Sevilla\",\"tipo\":26.53},\"Sierra de Yeguas\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":16.0},\"Sierro\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Siles\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":26.0},\"Somont\\u00edn\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":16.0},\"Soport\\u00fajar\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Sorbas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Sorihuela del Guadalimar\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":18.0},\"Sorvil\\u00e1n\":{\"provincia\":\"Granada\",\"tipo\":16.0},\"Sufl\\u00ed\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Tabernas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Taberno\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"Taha (La)\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Tahal\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Tarifa\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Teba\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Terque\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":20.0},\"T\\u00edjola\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"Tocina\":{\"provincia\":\"Sevilla\",\"tipo\":14.5},\"Tomares\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Torre Alh\\u00e1quime\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":0.0},\"Torreblascopedro\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":24.0},\"Torrecampo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Torre-Cardela\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Torredelcampo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.87},\"Torredonjimeno\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Torremolinos\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":28.0},\"Torrenueva Costa\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Torreperogil\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Torres\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":22.0},\"Torres de Alb\\u00e1nchez\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":20.0},\"Torrox\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Torvizc\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Total\\u00e1n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":26.0},\"Trebujena\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":18.0},\"Tres Villas (Las)\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":16.0},\"Trev\\u00e9lez\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Trigueros\":{\"provincia\":\"Huelva\",\"tipo\":27.0},\"Tur\\u00f3n\":{\"provincia\":\"Granada\",\"tipo\":0.0},\"Turre\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"Turrillas\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"\\u00dabeda\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Ubrique\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Ug\\u00edjar\":{\"provincia\":\"Granada\",\"tipo\":18.0},\"Uleila del Campo\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":27.0},\"Umbrete\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Urr\\u00e1cal\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":0.0},\"Utrera\":{\"provincia\":\"Sevilla\",\"tipo\":28.0},\"Valdelarco\":{\"provincia\":\"Huelva\",\"tipo\":20.0},\"Valdepe\\u00f1as de Ja\\u00e9n\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":16.0},\"Valderrubio\":{\"provincia\":\"Granada\",\"tipo\":23.0},\"Valencina de la Concepci\\u00f3n\":{\"provincia\":\"Sevilla\",\"tipo\":29.0},\"Valenzuela\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":16.0},\"Valle (El)\":{\"provincia\":\"Granada\",\"tipo\":21.0},\"Valle de Abdalaj\\u00eds\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":19.25},\"Valle del Zalab\\u00ed\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"V\\u00e1lor\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Valsequillo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Valverde del Camino\":{\"provincia\":\"Huelva\",\"tipo\":29.0},\"Vegas del Genil\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Vejer de la Frontera\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":29.0},\"Velefique\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":26.0},\"V\\u00e9lez de Benaudalla\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"V\\u00e9lez-Blanco\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"V\\u00e9lez-M\\u00e1laga\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":29.0},\"V\\u00e9lez-Rubio\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"Ventas de Huelma\":{\"provincia\":\"Granada\",\"tipo\":26.0},\"Vera\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":25.0},\"Viator\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":30.0},\"V\\u00edcar\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":29.0},\"Victoria (La)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Vilches\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Villa de Otura\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Villa del R\\u00edo\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":26.0},\"Villablanca\":{\"provincia\":\"Huelva\",\"tipo\":18.0},\"Villacarrillo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Villafranca de C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Villaharta\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":16.0},\"Villalba del Alcor\":{\"provincia\":\"Huelva\",\"tipo\":25.0},\"Villaluenga del Rosario\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":20.0},\"Villamanrique de la Condesa\":{\"provincia\":\"Sevilla\",\"tipo\":20.0},\"Villamart\\u00edn\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":21.0},\"Villamena\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Villanueva de Algaidas\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":25.0},\"Villanueva de C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":25.0},\"Villanueva de la Concepci\\u00f3n\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":0.0},\"Villanueva de la Reina\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":28.0},\"Villanueva de las Cruces\":{\"provincia\":\"Huelva\",\"tipo\":15.6},\"Villanueva de las Torres\":{\"provincia\":\"Granada\",\"tipo\":15.0},\"Villanueva de los Castillejos\":{\"provincia\":\"Huelva\",\"tipo\":20.0},\"Villanueva de San Juan\":{\"provincia\":\"Sevilla\",\"tipo\":10.0},\"Villanueva de Tapia\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":22.0},\"Villanueva del Ariscal\":{\"provincia\":\"Sevilla\",\"tipo\":30.0},\"Villanueva del Arzobispo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":23.0},\"Villanueva del Duque\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Villanueva del Rey\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":20.0},\"Villanueva del R\\u00edo y Minas\":{\"provincia\":\"Sevilla\",\"tipo\":27.0},\"Villanueva del Rosario\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":29.0},\"Villanueva del Trabuco\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":24.0},\"Villanueva Mes\\u00eda\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Villaralto\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Villardompardo\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":25.0},\"Villares (Los)\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Villarrasa\":{\"provincia\":\"Huelva\",\"tipo\":19.0},\"Villatorres\":{\"provincia\":\"Ja\\u00e9n\",\"tipo\":30.0},\"Villaverde del R\\u00edo\":{\"provincia\":\"Sevilla\",\"tipo\":29.26},\"Villaviciosa de C\\u00f3rdoba\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Vi\\u00f1uela\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":18.0},\"Viso (El)\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":0.0},\"Viso del Alcor (El)\":{\"provincia\":\"Sevilla\",\"tipo\":26.56},\"V\\u00edznar\":{\"provincia\":\"Granada\",\"tipo\":25.0},\"Yunquera\":{\"provincia\":\"M\\u00e1laga\",\"tipo\":15.0},\"Zafarraya\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Zagra\":{\"provincia\":\"Granada\",\"tipo\":10.0},\"Zahara\":{\"provincia\":\"C\\u00e1diz\",\"tipo\":30.0},\"Zalamea la Real\":{\"provincia\":\"Huelva\",\"tipo\":30.0},\"Zarza-Perrunal,La\":{\"provincia\":\"Huelva\",\"tipo\":0.0},\"Zubia (La)\":{\"provincia\":\"Granada\",\"tipo\":30.0},\"Zufre\":{\"provincia\":\"Huelva\",\"tipo\":25.0},\"Zuheros\":{\"provincia\":\"C\\u00f3rdoba\",\"tipo\":16.0},\"Z\\u00fajar\":{\"provincia\":\"Granada\",\"tipo\":20.0},\"Zurgena\":{\"provincia\":\"Almer\\u00eda\",\"tipo\":28.0},\"MEDIA MUNICIPIOS ANDALUCES\":{\"provincia\":\"\",\"tipo\":21.188722580645162}};            \/* {municipio:{provincia,tipo}} tipo en % *\/\r\n    var COEF = {0:0.15,1:0.15,2:0.14,3:0.14,4:0.16,5:0.18,6:0.19,7:0.20,8:0.19,9:0.16,10:0.13,\r\n        11:0.12,12:0.12,13:0.12,14:0.12,15:0.12,16:0.13,17:0.15,18:0.17,19:0.20,20:0.22}; \/* coef. m\\u00e1x. estatales por a\\u00f1os de tenencia (2026) *\/\r\n    var TRAMOS = [[6000,0.19],[50000,0.21],[200000,0.23],[300000,0.27],[Infinity,0.30]]; \/* IRPF base ahorro 2026 *\/\r\n    var IVA = 0.21;\r\n    \/* IRNR: tipo fijo para no residentes (no aplican los tramos del ahorro) *\/\r\n    var IRNR_UE = 0.19;    \/* residentes en UE \/ Islandia \/ Noruega *\/\r\n    var IRNR_NOUE = 0.24;  \/* resto del mundo *\/\r\n\r\n    \/* ---- El municipio se busca SIN tildes y SIN mayusculas (11-ago) ----------\r\n       Emilio, el primer lead de la campana, colgo el telefono diciendo que su\r\n       pueblo \"no aparece\". Aparecia: la lista trae \"Cartama\" con tilde, y el\r\n       datalist del navegador filtra letra por letra, tilde incluida. Quien\r\n       teclea \"cartama\" en el movil no ve ninguna sugerencia y concluye que su\r\n       municipio no esta. Y no era solo la sugerencia: el calculo buscaba\r\n       MUNI[pob] por coincidencia exacta, asi que escribirlo a mano sin tilde\r\n       tampoco valia y salia \"Indica una poblacion valida\".\r\n\r\n       No es un caso raro: 29 de los 99 municipios de Malaga llevan tilde, y\r\n       entre ellos casi toda la zona de PRODEX -- Cartama, Alora, Coin, Almogia,\r\n       los dos Alhaurin -- y la propia Malaga. La primera casilla del formulario\r\n       estaba rechazando a media provincia.\r\n\r\n       Sin literales acentuados a proposito: el bloque generado es ASCII y la\r\n       normalizacion se hace por NFD, que no necesita escribir ni una tilde. *\/\r\n    var MUNI_IDX = (function(){\r\n        var idx = {};\r\n        for(var k in MUNI){ if(Object.prototype.hasOwnProperty.call(MUNI,k)) idx[normPob(k)] = k; }\r\n        return idx;\r\n    })();\r\n\r\n    \/* Minusculas y sin diacriticos. String.normalize existe en todo navegador\r\n       con datalist util; si faltara, el fallback deja el texto tal cual y el\r\n       comportamiento es el de antes, nunca peor. *\/\r\n    function normPob(s){\r\n        s = String(s == null ? '' : s).trim().toLowerCase();\r\n        return s.normalize ? s.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '') : s;\r\n    }\r\n\r\n    \/* Devuelve el nombre CANONICO del municipio (con sus tildes) o null. *\/\r\n    function municipioCanonico(s){\r\n        var n = normPob(s);\r\n        return n && Object.prototype.hasOwnProperty.call(MUNI_IDX, n) ? MUNI_IDX[n] : null;\r\n    }\r\n\r\n    \/* ---- LA ZONA DE PRODEX (20-ago-2026) ------------------------------------\r\n       Hasta hoy esta pagina no tenia ninguna. El campo de poblacion sugiere los\r\n       776 municipios de Andalucia porque de ahi sale el tipo de plusvalia, y esa\r\n       lista se estaba usando DE HECHO como si fuera la zona de trabajo. No lo\r\n       es: de los cinco leads que trajo la campana de Meta, tres tenian la casa\r\n       fuera de la provincia --Toledo, Vigo y Sevilla-- y otro en Nerja, que es\r\n       Malaga pero no es zona. Se pago por los cuatro y ninguno se podia visitar.\r\n\r\n       EL CALCULO SIGUE ABIERTO A TODA ANDALUCIA A PROPOSITO. Calcular no cuesta\r\n       dinero y la cifra es el gancho; cerrar el municipio romperia la plusvalia\r\n       de medio mapa a cambio de nada. Lo que se cierra es EL LEAD: a quien tiene\r\n       la casa fuera no se le pide el telefono --se le da su cifra y en paz--,\r\n       que es justo el gasto que se queria evitar.\r\n\r\n       Manda la poblacion de la VENTA (v_pob): es donde esta la casa, y es el\r\n       mismo criterio con el que el ERP decide captacion o producto digital\r\n       (TipoDeCircuito). La de la compra es un deseo, no una direccion.\r\n\r\n       LA LISTA CANONICA VIVE EN EL ERP, en ZonaDelLead::ZONAS. Esto es su copia,\r\n       y una copia se desincroniza: si alli se toca, aqui tambien. Va en forma\r\n       plana --minusculas y sin tildes-- para comparar con normPob() sin escribir\r\n       un solo acento, que el build exige salida ASCII. *\/\r\n    var ZONAS_PRODEX = ['malaga','torremolinos','benalmadena','fuengirola',\r\n        'alhaurin de la torre','alhaurin el grande','almogia','alora',\r\n        'cartama','coin','pizarra','valle de abdalajis'];\r\n\r\n    \/* Igualdad exacta sobre la forma plana, como ZonaDelLead::casar(): con\r\n       \\u00abempieza por\\u00bb los dos Alhaurines casarian con lo mismo. En blanco NO es\r\n       fuera de zona --todavia no ha dicho donde vive--, asi que se le deja pasar\r\n       al formulario como siempre. *\/\r\n    function fueraDeZona(){\r\n        var el = document.getElementById('v_pob');\r\n        var n  = normPob(el ? el.value : '');\r\n        return n !== '' && ZONAS_PRODEX.indexOf(n) === -1;\r\n    }\r\n\r\n    \/* Sugerencias calculadas a mano en vez de dejarselas al navegador: se\r\n       repuebla el datalist con lo que casa SIN tildes. Tope de 40 para que en\r\n       el movil la lista siga siendo util y el teclado no se atasque. *\/\r\n    (function(){\r\n        var dl = document.getElementById('munis');\r\n        var names = Object.keys(MUNI).sort();\r\n\r\n        function pinta(filtro){\r\n            var n = normPob(filtro), out = [];\r\n            for(var i=0;i<names.length && out.length<40;i++){\r\n                if(!n || normPob(names[i]).indexOf(n) === 0) out.push(names[i]);\r\n            }\r\n            \/* Segunda pasada: lo que contiene el texto sin empezar por el,\r\n               para que \"torre\" encuentre \"Alhaurin de la Torre\". *\/\r\n            if(out.length < 40 && n){\r\n                for(var j=0;j<names.length && out.length<40;j++){\r\n                    if(normPob(names[j]).indexOf(n) > 0 && out.indexOf(names[j]) === -1) out.push(names[j]);\r\n                }\r\n            }\r\n            dl.textContent = '';\r\n            var frag = document.createDocumentFragment();\r\n            for(var m=0;m<out.length;m++){ var o=document.createElement('option'); o.value=out[m]; frag.appendChild(o); }\r\n            dl.appendChild(frag);\r\n        }\r\n\r\n        pinta('');\r\n\r\n        ['v_pob','c_pob'].forEach(function(id){\r\n            var el = document.getElementById(id);\r\n            if(!el) return;\r\n            el.addEventListener('input', function(){ pinta(this.value); });\r\n            \/* Al salir del campo se deja escrito el nombre canonico: lo que ve\r\n               la persona, lo que se calcula y lo que viaja en el aviso al ERP\r\n               dicen lo mismo. Si no casa nada, no se toca lo que escribio. *\/\r\n            el.addEventListener('blur', function(){\r\n                var c = municipioCanonico(this.value);\r\n                if(c && c !== this.value){ this.value = c; if(typeof recalcula==='function') recalcula(); }\r\n            });\r\n        });\r\n    })();\r\n\r\n    \/* ====== HELPERS ====== *\/\r\n    \/* Spanish-aware number parse (bug 04-ago: \"185.000\" leia 185 \\u2014 la ganancia\r\n       fantasma de 475.815 en el caso Ana y Juanma). Reglas: coma = decimal;\r\n       punto = miles cuando le siguen exactamente 3 digitos (185.000, 1.234.567);\r\n       punto = decimal en el resto (185.5, 0.75). *\/\r\n    function esNum(s){\r\n        s = String(s == null ? '' : s).trim().replace(\/[\\s\\u00a0\\u20ac]\/g, '');\r\n        if (s === '') return NaN;\r\n        var coma = s.indexOf(',') !== -1, punto = s.indexOf('.') !== -1;\r\n        if (coma && punto) { s = s.replace(\/\\.\/g, '').replace(',', '.'); }\r\n        else if (coma) { s = s.replace(',', '.'); }\r\n        else if (punto) {\r\n            var p = s.split('.');\r\n            var miles = p.length > 2 || (p[1].length === 3 && p[0] !== '0' && p[0] !== '');\r\n            if (miles) s = p.join('');\r\n        }\r\n        return parseFloat(s);\r\n    }\r\n    function num(id){ var v=esNum(document.getElementById(id).value); return isNaN(v)?0:v; }\r\n    function intval(id){ var v=parseInt(document.getElementById(id).value,10); return isNaN(v)?0:v; }\r\n    function chk(id){ return document.getElementById(id).checked; }\r\n    \/* separador de miles SIEMPRE, tambi\\u00e9n en 4 cifras (toLocaleString('es-ES')\r\n       deja \\u00ab1560\\u00bb sin punto y \\u00ab45.000\\u00bb con \\u00e9l en la misma frase) *\/\r\n    function miles(s){ return String(s).replace(\/\\B(?=(\\d{3})+(?!\\d))\/g,'.'); }\r\n    function eur(n){ var p=(Math.round(n*100)\/100).toFixed(2).split('.'); return miles(p[0])+','+p[1]+' \\u20ac'; }\r\n    function eur0(n){ return miles(Math.round(n))+' \\u20ac'; }\r\n\r\n    \/* IRPF progresivo por tramos *\/\r\n    function irpf(ganancia){\r\n        if(ganancia<=0) return 0;\r\n        var rest=ganancia, prev=0, cuota=0;\r\n        for(var i=0;i<TRAMOS.length;i++){\r\n            var lim=TRAMOS[i][0], tipo=TRAMOS[i][1];\r\n            var tramo=Math.min(rest, lim-prev);\r\n            if(tramo>0){ cuota+=tramo*tipo; rest-=tramo; prev=lim; }\r\n            if(rest<=0) break;\r\n        }\r\n        return cuota;\r\n    }\r\n\r\n    \/* fila helper *\/\r\n    function row(lbl, sub, val, cls, extra){\r\n        var s = sub?('<small>'+sub+'<\/small>'):'';\r\n        var x = extra?('<small>'+extra+'<\/small>'):'';\r\n        return '<div class=\"crow '+(cls||'')+'\"><div class=\"lbl\">'+lbl+s+'<\/div><div class=\"val\">'+val+x+'<\/div><\/div>';\r\n    }\r\n    function ivaLine(base){ return 'base '+eur(base)+' + IVA '+eur(base*IVA); }\r\n\r\n    \/* ====== SIMULADOR DE HIPOTECA (cuota fija, sistema franc\\u00e9s) ====== *\/\r\n    function calcHipoteca(){\r\n        var P=num('h_importe'), tinPct=num('h_tin'), anios=intval('h_anios');\r\n        var out=document.getElementById('h_cuota');\r\n        if(P<=0 || anios<=0){ out.textContent='\\u2014 \\u20ac'; CIFRAS.cuota='\\u2014 \\u20ac'; return; }\r\n        var n=anios*12;\r\n        var i=tinPct\/100\/12;\r\n        var cuota;\r\n        if(i===0){ cuota=P\/n; } else { cuota = P*i\/(1-Math.pow(1+i,-n)); }\r\n        \/* La cuota real solo viaja en el payload: en pantalla, la m\\u00e1scara. *\/\r\n        CIFRAS.cuota=eur(cuota);\r\n        out.textContent=MASCARA;\r\n    }\r\n\r\n    \/* ====== PROPIETARIOS DIN\\u00c1MICOS ====== *\/\r\n    function buildOwners(){\r\n        var n = intval('v_props'); if(n<1) n=1; if(n>6) n=6;\r\n        var cont = document.getElementById('v_owners');\r\n        var prev = cont.querySelectorAll('.owner-row');\r\n        \/* conservar valores previos *\/\r\n        var oldEd=[], oldPc=[], oldRes=[];\r\n        prev.forEach(function(rw){\r\n            oldEd.push(rw.querySelector('.oedad').value);\r\n            oldPc.push(rw.querySelector('.opct').value);\r\n            var sel=rw.querySelector('.ores'); oldRes.push(sel?sel.value:'ES');\r\n        });\r\n        var eqPct = Math.round((100\/n)*100)\/100;\r\n        var html='';\r\n        for(var i=0;i<n;i++){\r\n            var ed = oldEd[i]!==undefined?oldEd[i]:'';\r\n            var pc = oldPc[i]!==undefined?oldPc[i]:eqPct;\r\n            var rs = oldRes[i]!==undefined?oldRes[i]:'ES';\r\n            html+='<div class=\"owner-row\">'\r\n                + '<span class=\"onum\">Propietario '+(i+1)+'<\/span>'\r\n                + '<input type=\"number\" class=\"oedad\" data-i=\"'+i+'\" min=\"0\" step=\"1\" placeholder=\"Edad\" value=\"'+ed+'\">'\r\n                + '<span class=\"osuf\">a\\u00f1os<\/span>'\r\n                + '<input type=\"number\" class=\"opct\" data-i=\"'+i+'\" min=\"0\" max=\"100\" step=\"any\" placeholder=\"%\" value=\"'+pc+'\">'\r\n                + '<span class=\"osuf\">% titularidad<\/span>'\r\n                \/* La residencia fiscal decide el impuesto: un no residente no\r\n                   tributa por los tramos del IRPF ni accede a la exencion de 65+. *\/\r\n                + '<select class=\"ores\" data-i=\"'+i+'\">'\r\n                +   '<option value=\"ES\"'+(rs==='ES'?' selected':'')+'>Residente en Espa\\u00f1a<\/option>'\r\n                +   '<option value=\"UE\"'+(rs==='UE'?' selected':'')+'>No residente (UE \/ EEE)<\/option>'\r\n                +   '<option value=\"NOUE\"'+(rs==='NOUE'?' selected':'')+'>No residente (fuera de la UE)<\/option>'\r\n                + '<\/select>'\r\n                + '<\/div>';\r\n        }\r\n        cont.innerHTML=html;\r\n        \/* La nota del usufructo solo tiene sentido con titularidad compartida\r\n           (Pedro, 04-ago: \"el texto no desaparece nunca\") *\/\r\n        var usunote = document.getElementById('v_usunote');\r\n        if(usunote) usunote.style.display = (n>1) ? '' : 'none';\r\n        \/* listeners de las nuevas filas *\/\r\n        cont.querySelectorAll('input,select').forEach(function(el){ el.addEventListener('input', recalc); el.addEventListener('change', recalc); });\r\n    }\r\n    \/* leer propietarios: devuelve [{edad,pct,res}], y valida suma 100 *\/\r\n    function readOwners(){\r\n        var rows = document.querySelectorAll('#v_owners .owner-row');\r\n        var arr=[], sum=0;\r\n        rows.forEach(function(rw){\r\n            var ed=parseInt(rw.querySelector('.oedad').value,10); if(isNaN(ed)) ed=0;\r\n            var pc=esNum(rw.querySelector('.opct').value); if(isNaN(pc)) pc=0;\r\n            var sel=rw.querySelector('.ores'); var rs=sel?sel.value:'ES';\r\n            arr.push({edad:ed,pct:pc,res:rs}); sum+=pc;\r\n        });\r\n        var warn=document.getElementById('v_pctwarn');\r\n        if(arr.length>0 && Math.abs(sum-100)>0.5){\r\n            warn.style.display='block';\r\n            warn.textContent='\\u26a0 Los porcentajes de titularidad suman '+(Math.round(sum*100)\/100)+'% \\u2014 deben sumar 100%.';\r\n        } else { warn.style.display='none'; }\r\n        return arr;\r\n    }\r\n\r\n    \/* ====== M\\u00d3DULO 1 ====== *\/\r\n    function calcVenta(){\r\n        var pob=document.getElementById('v_pob').value.trim();\r\n        var venta=num('v_venta'), adq=num('v_adq'), hip=num('v_hip'), deudas=num('v_deudas');\r\n        var gastosded=num('v_gastosded'), catastro=num('v_catastro'), catastroTotal=num('v_catastro_total');\r\n        var anio=intval('v_anio');\r\n        var comiPct=num('v_comi'), abogPct=num('v_abog');\r\n        var habitual=chk('v_habitual'), exreinv=chk('v_exreinv');\r\n        var notPagaComprador=chk('v_notpagacomprador');\r\n        var owners=readOwners();\r\n\r\n        \/* ---- Plusval\\u00eda municipal (objetivo vs real, el menor) ---- *\/\r\n        \/* plusAviso: aviso SIEMPRE visible (fuera del desglose) cuando la plusval\\u00eda\r\n           no se puede calcular con los datos dados. No cambia ning\\u00fan c\\u00e1lculo. *\/\r\n        \/* plusCalculada distingue \"0 euros porque no hay ganancia\" (un resultado)\r\n           de \"no lo sabemos con estos datos\" (un hueco). Sin esta bandera un cero\r\n           por falta de datos se lee como si fuese un calculo. *\/\r\n        var plus=0, plusTxt='', plusNote='', plusAviso='', plusCalculada=false;\r\n        \/* Por el nombre canonico, no por lo tecleado: \"cartama\", \"CARTAMA\" y\r\n           \"Cartama\" tienen que encontrar el mismo ayuntamiento que \"Cartama\"\r\n           con tilde. Antes solo valia la coincidencia exacta. *\/\r\n        var pobCanon = municipioCanonico(pob);\r\n        var tipoMuni = (pobCanon ? MUNI[pobCanon].tipo : null);\r\n        var anios = anio>0 ? Math.max(0, 2026-anio) : null;\r\n        if(tipoMuni===null){\r\n            plusTxt='Indica una poblaci\\u00f3n v\\u00e1lida';\r\n            plusAviso='La plusval\\u00eda municipal no se puede calcular sin una poblaci\\u00f3n v\\u00e1lida. No est\\u00e1 incluida en esta estimaci\\u00f3n.';\r\n        } else if(!(tipoMuni>0)){\r\n            \/* La tabla trae tipo 0 en los municipios cuya ordenanza no hemos\r\n               podido leer: es \"sin dato\", no un tipo del 0%. *\/\r\n            plusTxt='No tenemos el tipo de este ayuntamiento';\r\n            plusAviso='No tenemos publicado el tipo de plusval\\u00eda de '+pobCanon+', as\\u00ed que NO est\\u00e1 incluida en esta estimaci\\u00f3n. PRODEX lo comprueba en la ordenanza vigente para tu caso.';\r\n        } else if(anios===null){\r\n            plusTxt='Indica el a\\u00f1o de adquisici\\u00f3n';\r\n            plusAviso='La plusval\\u00eda municipal no se puede calcular sin el a\\u00f1o de adquisici\\u00f3n. No est\\u00e1 incluida en esta estimaci\\u00f3n.';\r\n        } else {\r\n            var coefIdx = Math.min(anios,20);\r\n            var coef = COEF[coefIdx];\r\n            var t = tipoMuni\/100;\r\n            \/* m\\u00e9todo objetivo: valor catastral del suelo x coeficiente x tipo *\/\r\n            var objetivo = null;\r\n            if(catastro>0){ objetivo = catastro * coef * t; }\r\n            \/* m\\u00e9todo real: la ganancia que corresponde AL SUELO, no la ganancia\r\n               entera. Se prorratea por el peso del suelo en el valor catastral\r\n               total (art. 104.5 TRLRHL); sin el catastral total no se puede\r\n               calcular, y estimarlo sobre la ganancia entera la sobreestima. *\/\r\n            var gananciaBruta = venta - adq;\r\n            var pctSuelo = (catastro>0 && catastroTotal>0) ? Math.min(1, catastro\/catastroTotal) : null;\r\n            var real = null;\r\n            if(gananciaBruta>0 && pctSuelo!==null){\r\n                real = gananciaBruta * pctSuelo * t;\r\n            }\r\n            if(objetivo!==null && real!==null){\r\n                plus = Math.min(objetivo, real); plusCalculada=true;\r\n                \/* Sin cifras (30-ago): el importe de cada m\\u00e9todo revela el resultado; el detalle va en el informe. *\/\r\n                plusNote = 'Se aplica el m\\u00e9todo m\\u00e1s favorable de los dos que permite la ley: por coeficientes catastrales o por ganancia real del suelo. El detalle, en tu informe.';\r\n            } else if(objetivo!==null){\r\n                plus = objetivo; plusCalculada=true;\r\n                plusNote='M\\u00e9todo objetivo ('+anios+' a\\u00f1os \\u00b7 coef. '+coef+' \\u00b7 tipo '+tipoMuni+'%)';\r\n                if(catastroTotal<=0){\r\n                    plusNote+=' Con el valor catastral TOTAL comparamos tambi\\u00e9n el m\\u00e9todo de ganancia real, que a veces sale m\\u00e1s barato.';\r\n                }\r\n            } else {\r\n                plusTxt='Introduce el valor catastral del suelo (IBI)';\r\n                plusAviso='La plusval\\u00eda municipal no se puede calcular sin el valor catastral del suelo. No est\\u00e1 incluida en esta estimaci\\u00f3n.';\r\n            }\r\n            if(gananciaBruta<=0){ plus=0; plusCalculada=true; plusNote='Sin ganancia: plusval\\u00eda 0 \\u20ac'; plusTxt=''; plusAviso=''; }\r\n        }\r\n\r\n        \/* ---- IRPF (reinversi\\u00f3n proporcional + reparto por propietario + exenci\\u00f3n 65+) ---- *\/\r\n        var gananciaNeta = Math.max(0, venta - adq - gastosded);\r\n        var reinvPct = exreinv ? Math.min(100, Math.max(0, num('v_reinvpct'))) : 0;\r\n        var fracTributable = 1 - reinvPct\/100;          \/* parte de la ganancia que NO queda exenta por reinversi\\u00f3n *\/\r\n        var baseTrib = gananciaNeta * fracTributable;    \/* ganancia que s\\u00ed puede tributar *\/\r\n        var irpfCuota = 0, irpfNote='';\r\n        var exentos=0, noRes=0, pctNoRes=0;\r\n        if(owners.length>0){\r\n            for(var oi=0; oi<owners.length; oi++){\r\n                var frac = owners[oi].pct\/100;\r\n                var res = owners[oi].res||'ES';\r\n                if(res!=='ES'){\r\n                    \/* No residente: tributa por IRNR a tipo FIJO sobre su ganancia,\r\n                       sin los tramos del ahorro. Y la exencion de 65+ exige vivienda\r\n                       habitual en Espa\\u00f1a, que por definicion no tiene. *\/\r\n                    noRes++; pctNoRes += owners[oi].pct;\r\n                    irpfCuota += baseTrib*frac*(res==='NOUE'?IRNR_NOUE:IRNR_UE);\r\n                    continue;\r\n                }\r\n                \/* exenci\\u00f3n de 65+ si es vivienda habitual y el propietario cumple la edad *\/\r\n                if(habitual && owners[oi].edad>=65){ exentos++; continue; }\r\n                irpfCuota += irpf(baseTrib*frac);\r\n            }\r\n        } else {\r\n            irpfCuota = irpf(baseTrib);\r\n        }\r\n        \/* Retenci\\u00f3n del 3% (modelo 211): el comprador la practica al no residente\r\n           y es un PAGO A CUENTA de su IRNR, no un coste adicional. Se informa\r\n           aparte y no se resta otra vez del liquido. *\/\r\n        var reten3 = venta * (Math.min(100,pctNoRes)\/100) * 0.03;\r\n        \/* Sin la cifra (30-ago): la ganancia neta es un resultado intermedio y va enmascarada. *\/\r\n        irpfNote = 'Ganancia neta calculada';\r\n        if(exreinv && reinvPct>0) irpfNote += ' \\u00b7 reinversi\\u00f3n '+reinvPct+'% exenta';\r\n        if(exentos>0) irpfNote += ' \\u00b7 '+exentos+' propietario(s) 65+ exento(s)';\r\n        if(noRes>0) irpfNote += ' \\u00b7 '+noRes+' no residente(s) al '+Math.round(IRNR_UE*100)+'%\/'+Math.round(IRNR_NOUE*100)+'% (IRNR, sin tramos)';\r\n\r\n        \/* ---- Honorarios (base + IVA) ---- *\/\r\n        var comiBase = venta*comiPct\/100, comiTot=comiBase*(1+IVA);\r\n        var abogBase = venta*abogPct\/100, abogTot=abogBase*(1+IVA);\r\n\r\n        \/* ---- Notar\\u00eda y registro: solo se restan si NO los paga el comprador ---- *\/\r\n        var notVenta = notPagaComprador ? 0 : venta*0.01;\r\n\r\n        \/* ---- L\\u00cdQUIDO ---- *\/\r\n        var liquido = venta - hip - plus - irpfCuota - comiTot - abogTot - notVenta - deudas;\r\n\r\n        \/* render \\u2014 TODO ENMASCARADO (30-ago-2026): ni los totales ni el desglose\r\n           pintan importes reales en el DOM (un blur se quita con F12; una\r\n           m\\u00e1scara no). Los valores de verdad quedan en CIFRAS y en el payload\r\n           que sube al ERP. Se conservan las etiquetas y las notas sin cifra:\r\n           dicen QU\\u00c9 entra en el c\\u00e1lculo sin decir cu\\u00e1nto. *\/\r\n        var h='';\r\n        h+=row('Valor de venta','', MASCARA,'plus');\r\n        h+=row('Cancelaci\\u00f3n de hipoteca','', '\\u2212 '+MASCARA,'minus');\r\n        h+=row('Plusval\\u00eda municipal (IIVTNU)', plusNote||plusTxt,\r\n               plusCalculada ? ('\\u2212 '+MASCARA) : 'No incluida', plusCalculada?'minus':'');\r\n        h+=row(noRes>0?'IRPF \/ IRNR (ganancia patrimonial)':'IRPF (ganancia patrimonial)', irpfNote, '\\u2212 '+MASCARA,'minus');\r\n        if(reten3>0){\r\n            h+=row('Retenci\\u00f3n del 3% al no residente', 'Modelo 211 \\u00b7 la practica el comprador y es un pago A CUENTA del impuesto anterior, no un coste extra', MASCARA,'');\r\n        }\r\n        h+=row('Comisi\\u00f3n de agencia ('+(comiPct||0)+'%)', comiBase>0?'base + IVA (21%)':'', '\\u2212 '+MASCARA,'minus');\r\n        h+=row('Abogado \/ gestor\\u00eda ('+(abogPct||0)+'%)', abogBase>0?'base + IVA (21%)':'', '\\u2212 '+MASCARA,'minus');\r\n        if(notPagaComprador){\r\n            h+=row('Notar\\u00eda y registro', 'Los paga el comprador \\u00b7 no se restan', '0,00 \\u20ac','');\r\n        } else {\r\n            h+=row('Notar\\u00eda y registro', 'Estimado 1% \\u00b7 los asumes t\\u00fa', '\\u2212 '+MASCARA,'minus');\r\n        }\r\n        h+=row('Otras deudas \/ cargas','', '\\u2212 '+MASCARA,'minus');\r\n        document.getElementById('v_calc').innerHTML=h;\r\n        CIFRAS.liquido=eur(liquido);\r\n        \/* Desglose REAL para el informe de WhatsApp (30-ago): el DOM va\r\n           enmascarado, as\\u00ed que los conceptos con su importe viajan aqu\\u00ed, como\r\n           liquido\/coste\/balance. Formato: importe = N\\u00daMERO en euros con signo\r\n           (el precio suma, los gastos restan: la suma del bloque ES el\r\n           l\\u00edquido); las l\\u00edneas informativas llevan `nota` en vez de importe.\r\n           Solo entran conceptos con importe distinto de 0 o con nota. *\/\r\n        var dv=[];\r\n        if(venta>0) dv.push({concepto:'Precio de venta', importe:venta});\r\n        if(hip>0) dv.push({concepto:'Cancelaci\\u00f3n de hipoteca', importe:-hip});\r\n        if(plusCalculada && plus>0) dv.push({concepto:'Plusval\\u00eda municipal (IIVTNU)', importe:-plus});\r\n        else if(!plusCalculada && venta>0) dv.push({concepto:'Plusval\\u00eda municipal', nota:'No incluida (falta el valor catastral del suelo)'});\r\n        if(irpfCuota>0) dv.push({concepto:(noRes>0?'IRPF \/ IRNR (ganancia patrimonial)':'IRPF (ganancia patrimonial)'), importe:-irpfCuota});\r\n        if(reten3>0) dv.push({concepto:'Retenci\\u00f3n del 3 % al no residente', nota:eur(reten3)+' \\u2014 pago a cuenta del impuesto, no un coste extra'});\r\n        if(comiTot>0) dv.push({concepto:'Comisi\\u00f3n de agencia ('+comiPct+' %, IVA incluido)', importe:-comiTot});\r\n        if(abogTot>0) dv.push({concepto:'Abogado \/ gestor\\u00eda ('+abogPct+' %, IVA incluido)', importe:-abogTot});\r\n        if(notPagaComprador && venta>0) dv.push({concepto:'Notar\\u00eda y registro', nota:'Los paga el comprador'});\r\n        else if(notVenta>0) dv.push({concepto:'Notar\\u00eda y registro (estimado 1 %)', importe:-notVenta});\r\n        if(deudas>0) dv.push({concepto:'Otras deudas \/ cargas', importe:-deudas});\r\n        CIFRAS.desgloseVenta=dv;\r\n        document.getElementById('v_total').textContent = venta>0 ? MASCARA : '\\u2014 \\u20ac';\r\n        \/* aviso de plusval\\u00eda no incluida: solo cuando hay una venta que estimar *\/\r\n        var pav=document.getElementById('v_plusaviso');\r\n        if(pav){\r\n            if(plusAviso && venta>0){ pav.textContent=plusAviso; pav.style.display='block'; }\r\n            else { pav.style.display='none'; }\r\n        }\r\n        \/* exponer datos para el motor de alertas *\/\r\n        ESTADO.venta = {\r\n            liquido:liquido, venta:venta, adq:adq, anio:anio, anios:anios,\r\n            plus:plus, plusCalculada:plusCalculada,\r\n            objetivo:(typeof objetivo!=='undefined'?objetivo:null), real:(typeof real!=='undefined'?real:null),\r\n            irpf:irpfCuota, gananciaNeta:gananciaNeta, catastro:catastro, catastroTotal:catastroTotal,\r\n            owners:owners, habitual:habitual, exreinv:exreinv, reinvPct:reinvPct, has65:(exentos>0),\r\n            noRes:noRes, reten3:reten3, numProps:owners.length\r\n        };\r\n        return liquido;\r\n    }\r\n\r\n    \/* ====== ITP DE ANDALUC\\u00cdA: tipo general y tipos reducidos ======\r\n       Son dos preguntas y mueven miles de euros, as\\u00ed que se calcula en vez de\r\n       dejar el 7% fijo. El campo sigue siendo editable: si el asesor del cliente\r\n       le ha dicho otro tipo, manda el suyo (itpTocadoAMano). *\/\r\n    var ITP_GENERAL = 7, ITP_HABITUAL = 6, ITP_REDUCIDO = 3.5;\r\n    var ITP_LIMITE = 150000;   \/* valor m\\u00e1ximo de la vivienda para los reducidos *\/\r\n    var itpTocadoAMano = false;\r\n    var itpAutoValor = null;   \/* lo \\u00faltimo que escribimos nosotros en el campo *\/\r\n\r\n    function itpAndalucia(){\r\n        var precio = num('c_compra');\r\n        var habitual = document.getElementById('c_habitual').value === 'si';\r\n        var supuesto = document.getElementById('c_supuesto').value;\r\n        var cabe = precio > 0 && precio <= ITP_LIMITE;\r\n\r\n        if(habitual && cabe && supuesto){\r\n            return {pct: ITP_REDUCIDO,\r\n                    txt: 'Te corresponde el tipo reducido del ' + ITP_REDUCIDO + '%: vivienda habitual '\r\n                       + 'por debajo de ' + miles(ITP_LIMITE) + ' \\u20ac y uno de los supuestos protegidos.'};\r\n        }\r\n        if(habitual && cabe){\r\n            return {pct: ITP_HABITUAL,\r\n                    txt: 'Te corresponde el ' + ITP_HABITUAL + '% por ser vivienda habitual por debajo de '\r\n                       + miles(ITP_LIMITE) + ' \\u20ac.'};\r\n        }\r\n        if(habitual && supuesto && precio > ITP_LIMITE){\r\n            return {pct: ITP_GENERAL,\r\n                    txt: 'Tipo general del ' + ITP_GENERAL + '%: los reducidos solo alcanzan a viviendas de hasta '\r\n                       + miles(ITP_LIMITE) + ' \\u20ac, y esta pasa de ese l\\u00edmite.'};\r\n        }\r\n        if(habitual && precio > ITP_LIMITE){\r\n            return {pct: ITP_GENERAL,\r\n                    txt: 'Tipo general del ' + ITP_GENERAL + '%: el reducido de vivienda habitual solo llega hasta '\r\n                       + miles(ITP_LIMITE) + ' \\u20ac.'};\r\n        }\r\n        return {pct: ITP_GENERAL,\r\n                txt: 'Tipo general del ' + ITP_GENERAL + '%' + (habitual ? '.' : ' (no es vivienda habitual).')};\r\n    }\r\n\r\n    function aplicarItp(){\r\n        var r = itpAndalucia();\r\n        var campo = document.getElementById('c_itp');\r\n        \/* Se detecta por VALOR, no por el orden de las escuchas: la escucha\r\n           general de inputs llama a recalc antes que la del propio campo, y si\r\n           nos fi\\u00e1ramos de ella machacar\\u00edamos el tipo que acaba de escribir. *\/\r\n        if(itpAutoValor !== null && String(campo.value) !== String(itpAutoValor)){\r\n            itpTocadoAMano = true;\r\n        }\r\n        if(!itpTocadoAMano){ campo.value = r.pct; itpAutoValor = r.pct; }\r\n        var nota = document.getElementById('c_itpnota');\r\n        if(nota){\r\n            nota.innerHTML = (itpTocadoAMano\r\n                ? 'Est\\u00e1s usando un tipo que has escrito t\\u00fa. Seg\\u00fan tus respuestas ser\\u00eda el ' + r.pct + '%.'\r\n                : r.txt)\r\n                + ' <strong>El tipo definitivo depende de requisitos que comprobamos en tu caso.<\/strong>';\r\n        }\r\n    }\r\n\r\n    \/* ====== M\\u00d3DULO 2 ====== *\/\r\n    function calcCompra(){\r\n        aplicarItp();\r\n        var compra=num('c_compra'), itpPct=num('c_itp');\r\n        var comiPct=num('c_comi'), abogPct=num('c_abog'), otros=num('c_otros');\r\n        var incNotaria=chk('c_notaria'), hipoteca=chk('c_hipoteca');\r\n\r\n        var itp = compra*itpPct\/100;\r\n        \/* notar\\u00eda + registro + gestor\\u00eda estimados ~1% del precio (los paga el comprador) *\/\r\n        var notaria = incNotaria?compra*0.01:0;\r\n        \/* gastos bancarios de hipoteca: tasaci\\u00f3n + gesti\\u00f3n, estimados ~0,4% del precio (sin AJD: en 2\\u00aa mano la compraventa no lleva AJD) *\/\r\n        var banco = hipoteca? (compra*0.004) : 0;\r\n        var comiBase=compra*comiPct\/100, comiTot=comiBase*(1+IVA);\r\n        var abogBase=compra*abogPct\/100, abogTot=abogBase*(1+IVA);\r\n\r\n        var coste = compra + itp + notaria + banco + comiTot + abogTot + otros;\r\n\r\n        \/* Mismo criterio que en la venta (30-ago): m\\u00e1scara en el DOM, valor real en CIFRAS. *\/\r\n        var h='';\r\n        h+=row('Valor de compra','', MASCARA,'plus');\r\n        h+=row('ITP ('+(itpPct||0)+'%)','Impuesto de Transmisiones \\u00b7 Andaluc\\u00eda', '+ '+MASCARA,'plus');\r\n        if(incNotaria) h+=row('Notar\\u00eda + registro + gestor\\u00eda','Los paga el comprador \\u00b7 estimado 1%', '+ '+MASCARA,'plus');\r\n        if(hipoteca) h+=row('Banco \/ hipoteca','Tasaci\\u00f3n y gesti\\u00f3n estimadas', '+ '+MASCARA,'plus');\r\n        h+=row('Comisi\\u00f3n de compra ('+(comiPct||0)+'%)', comiBase>0?'base + IVA (21%)':'', '+ '+MASCARA,'plus');\r\n        h+=row('Abogado \/ gestor\\u00eda ('+(abogPct||0)+'%)', abogBase>0?'base + IVA (21%)':'', '+ '+MASCARA,'plus');\r\n        h+=row('Otros gastos','Reforma, mudanza\\u2026', '+ '+MASCARA,'plus');\r\n        document.getElementById('c_calc').innerHTML=h;\r\n        CIFRAS.coste=eur(coste);\r\n        \/* Desglose REAL de la compra para el informe de WhatsApp (30-ago):\r\n           aqu\\u00ed todo SUMA (la suma del bloque es el coste total). Mismo formato\r\n           que en la venta: importe = n\\u00famero en euros; solo conceptos \\u2260 0. *\/\r\n        var dc=[];\r\n        if(compra>0) dc.push({concepto:'Precio de compra', importe:compra});\r\n        if(itp>0) dc.push({concepto:'ITP ('+itpPct+' %)', importe:itp});\r\n        if(notaria>0) dc.push({concepto:'Notar\\u00eda + registro + gestor\\u00eda (estimado 1 %)', importe:notaria});\r\n        if(banco>0) dc.push({concepto:'Banco \/ hipoteca (tasaci\\u00f3n y gesti\\u00f3n)', importe:banco});\r\n        if(comiTot>0) dc.push({concepto:'Comisi\\u00f3n de compra ('+comiPct+' %, IVA incluido)', importe:comiTot});\r\n        if(abogTot>0) dc.push({concepto:'Abogado \/ gestor\\u00eda ('+abogPct+' %, IVA incluido)', importe:abogTot});\r\n        if(otros>0) dc.push({concepto:'Otros gastos', importe:otros});\r\n        CIFRAS.desgloseCompra=dc;\r\n        document.getElementById('c_total').textContent = compra>0 ? MASCARA : '\\u2014 \\u20ac';\r\n        return coste;\r\n    }\r\n\r\n    \/* ====== ESTADO GLOBAL + MOTOR DE ALERTAS ====== *\/\r\n    var ESTADO = { venta:{}, compra:{} };\r\n\r\n    \/* Las alertas SUGIEREN una oportunidad de ahorro sin revelar el mecanismo ni citar leyes:\r\n       crean la necesidad de hablar con PRODEX. *\/\r\n    function buildAlerts(){\r\n        var v = ESTADO.venta || {};\r\n        var out = [];\r\n\r\n        \/* 1 \\u00b7 Adquisici\\u00f3n antigua (\\u22641994) \\u2014 coeficientes de abatimiento, sin nombrarlos *\/\r\n        if(v.anio && v.anio>0 && v.anio<=1994 && v.gananciaNeta>0){\r\n            out.push('<b>Tu vivienda fue adquirida hace tiempo.<\/b> Las viviendas adquiridas antes de 1995 pueden acogerse a un r\\u00e9gimen fiscal especial que reduce notablemente la tributaci\\u00f3n de la ganancia. Es un c\\u00e1lculo t\\u00e9cnico con requisitos concretos: aplicado correctamente, el margen puede ser muy relevante. Lo revisamos en tu caso.');\r\n        }\r\n\r\n        \/* 2 \\u00b7 Plusval\\u00eda: margen entre objetivo y real *\/\r\n        if(v.objetivo && v.real && v.objetivo > v.real*1.05){\r\n            out.push('<b>Tu plusval\\u00eda municipal admite optimizaci\\u00f3n.<\/b> Existe m\\u00e1s de una forma legal de calcularla y, seg\\u00fan tus datos, podr\\u00edas estar ante una diferencia importante a tu favor. Pocas operaciones aplican el m\\u00e9todo m\\u00e1s ventajoso: nosotros lo verificamos antes de que pagues de m\\u00e1s.');\r\n        } else if(v.plusCalculada && v.objetivo && !v.real){\r\n            out.push('<b>Tu plusval\\u00eda municipal est\\u00e1 sin afinar.<\/b> Con el valor catastral total podemos comparar el segundo m\\u00e9todo que permite la ley y comprobar si la cuota baja. En muchos casos hay margen que el c\\u00e1lculo est\\u00e1ndar no detecta.');\r\n        } else if(v.venta>0 && !v.plusCalculada){\r\n            out.push('<b>Tu plusval\\u00eda municipal no est\\u00e1 incluida en esta estimaci\\u00f3n.<\/b> Nos faltan datos para calcularla, as\\u00ed que la cifra de arriba se queda corta en ese impuesto. La cerramos con la ordenanza de tu ayuntamiento antes de que pagues nada.');\r\n        }\r\n\r\n        \/* 3 \\u00b7 IRPF alto sin reinversi\\u00f3n *\/\r\n        if(v.irpf > 3000 && !v.exreinv){\r\n            out.push('<b>Tu factura de IRPF es significativa.<\/b> Existen v\\u00edas legales de planificaci\\u00f3n \\u2014vinculadas a c\\u00f3mo y cu\\u00e1ndo se estructura la operaci\\u00f3n\\u2014 que pueden reducirla de forma sustancial. No todas requieren reinvertir: te explicamos las que encajan en tu situaci\\u00f3n.');\r\n        } else if(v.exreinv && v.reinvPct<100 && v.irpf>0){\r\n            out.push('<b>Tu reinversi\\u00f3n es parcial.<\/b> Ajustando c\\u00f3mo se planifica la parte reinvertida, es posible mejorar el resultado fiscal. Te mostramos el escenario \\u00f3ptimo para tu caso.');\r\n        }\r\n\r\n        \/* 4 \\u00b7 Varios propietarios \\u2014 reparto *\/\r\n        if(v.owners && v.owners.length>=2 && v.gananciaNeta>0){\r\n            out.push('<b>La operaci\\u00f3n tiene varios titulares.<\/b> La forma en que se reparte la titularidad influye directamente en la factura fiscal del conjunto. Una estructura bien planteada puede suponer una reducci\\u00f3n que conviene calcular antes de firmar.');\r\n        }\r\n\r\n        \/* 5 bis \\u00b7 No residentes: otro impuesto y una retenci\\u00f3n que sorprende *\/\r\n        if(v.noRes>0){\r\n            out.push('<b>Hay titulares que no residen en Espa\\u00f1a.<\/b> Su venta no tributa por la escala habitual sino a tipo fijo, el comprador est\\u00e1 obligado a retener un 3% del precio en el momento de la firma, y la devoluci\\u00f3n del exceso se pide aparte. Es uno de los casos donde m\\u00e1s dinero se queda por el camino si nadie lo prepara.');\r\n        }\r\n\r\n        \/* 5 \\u00b7 Mayores de 65 \/ vivienda habitual *\/\r\n        if(v.has65){\r\n            out.push('<b>Tu situaci\\u00f3n da acceso a beneficios fiscales relevantes.<\/b> Hay condiciones que, bien acreditadas, eliminan buena parte de la carga fiscal de la venta. Es clave aplicarlas correctamente: lo dejamos cerrado en tu informe.');\r\n        }\r\n\r\n        \/* 6 \\u00b7 Usufructo \\u2014 solo con titularidad compartida (Pedro, 04-ago: el\r\n           texto \"no desaparec\\u00eda nunca\"; con un \\u00fanico titular en plena\r\n           propiedad no aporta y resta credibilidad al bloque) *\/\r\n        if(v.numProps>1){\r\n            out.push('<b>\\u00bfExiste usufructo o nuda propiedad?<\/b> <span style=\"color:var(--warn)\">Posible reducci\\u00f3n.<\/span> Si la titularidad no es plena, el reparto y la fiscalidad cambian por completo respecto a esta estimaci\\u00f3n \\u2014 y casi siempre a mejor de lo que parece. Es uno de los puntos donde m\\u00e1s valor aporta nuestro an\\u00e1lisis.');\r\n        }\r\n\r\n        \/* render *\/\r\n        var box=document.getElementById('oportunidades');\r\n        var cont=document.getElementById('alerts');\r\n        if(out.length>0){\r\n            var html='';\r\n            for(var i=0;i<out.length;i++){\r\n                html+='<div class=\"alert\"><span class=\"ic\">\\u2726<\/span><div class=\"tx\">'+out[i]+'<\/div><\/div>';\r\n            }\r\n            cont.innerHTML=html;\r\n            box.style.display='block';\r\n        } else {\r\n            box.style.display='none';\r\n        }\r\n        ESTADO.alertas = out;\r\n    }\r\n\r\n    \/* ====== GLOBAL ====== *\/\r\n    function recalc(){\r\n        var liq=calcVenta();\r\n        var cost=calcCompra();\r\n        calcHipoteca();\r\n        buildAlerts();\r\n        var saldo=liq-cost;\r\n        \/* ENMASCARADO (30-ago): ni el saldo, ni el signo, ni la frase que lo\r\n           delataba (\\u00abte sobra\\u00bb \/ \\u00abnecesitas cubrir\\u00bb). El balance real viaja en\r\n           el payload (CIFRAS.balance) y llega en el informe de WhatsApp. *\/\r\n        CIFRAS.balance=(saldo>=0?'+':'')+eur(saldo);\r\n        var hayCifra = num('v_venta')>0 || num('c_compra')>0;\r\n        document.getElementById('g_liq').textContent = hayCifra ? MASCARA : '\\u2014 \\u20ac';\r\n        document.getElementById('g_cost').textContent = hayCifra ? MASCARA : '\\u2014 \\u20ac';\r\n        document.getElementById('g_dif').textContent = hayCifra ? MASCARA : '\\u2014 \\u20ac';\r\n        document.getElementById('g_saldo').textContent = hayCifra ? MASCARA : '\\u2014 \\u20ac';\r\n        document.getElementById('g_desc').textContent = 'L\\u00edquido de la venta menos coste de la nueva compra';\r\n        if (typeof embudoResultado === 'function') embudoResultado();\r\n        \/* Si el bloque WhatsApp ya se vio con el c\\u00e1lculo vac\\u00edo, la primera\r\n           cifra tecleada dispara el POST pendiente (definido m\\u00e1s abajo). *\/\r\n        if (typeof avisaCalculoListo === 'function') avisaCalculoListo();\r\n    }\r\n\r\n    \/* listeners en todos los inputs *\/\r\n    document.querySelectorAll('.prodex-calc input').forEach(function(el){\r\n        el.addEventListener('input', recalc);\r\n        el.addEventListener('change', recalc);\r\n    });\r\n    \/* Los desplegables del ITP: por id, que no hace falta acotarlos al bloque. *\/\r\n    document.querySelectorAll('#c_habitual,#c_supuesto').forEach(function(el){\r\n        el.addEventListener('change', recalc);\r\n    });\r\n    \/* Si el cliente escribe un tipo a mano, deja de calcularse solo. *\/\r\n    document.getElementById('c_itp').addEventListener('input', function(){ itpTocadoAMano = true; });\r\n    \/* mostrar\/ocultar % de reinversi\\u00f3n *\/\r\n    document.getElementById('v_exreinv').addEventListener('change', function(){\r\n        document.getElementById('v_reinvbox').style.display = this.checked ? 'flex' : 'none';\r\n        recalc();\r\n    });\r\n    \/* regenerar filas de propietarios al cambiar el n\\u00famero *\/\r\n    document.getElementById('v_props').addEventListener('input', function(){ buildOwners(); recalc(); });\r\n    document.getElementById('v_props').addEventListener('change', function(){ buildOwners(); recalc(); });\r\n    buildOwners();\r\n    recalc();\r\n\r\n    \/* ====== NIVELES DE PROFUNDIDAD (27-jul) ======\r\n       El motor de c\\u00e1lculo es uno solo: el nivel 1 solo PLIEGA campos (.lvl2),\r\n       nunca los quita del DOM ni cambia la l\\u00f3gica. PRODEX.nivel() la define el\r\n       fragmento de medici\\u00f3n del sitio; si no est\\u00e1, no pasa nada.\r\n       El selector tiene DOS tarjetas (profundidad de c\\u00e1lculo); dejar los datos\r\n       no es un nivel: 'nosotros' se dispara al entrar en el primer campo del\r\n       bloque de datos (#l_nombre), una vez por visita. *\/\r\n    function marcarNivel(id){\r\n        ['nivel1','nivel2'].forEach(function(n){\r\n            var el=document.getElementById(n); if(el) el.classList.toggle('activa', n===id);\r\n        });\r\n    }\r\n    var pxwrap=document.getElementById('pxwrap');\r\n    var nv1=document.getElementById('nivel1'), nv2=document.getElementById('nivel2');\r\n    if(pxwrap && nv1 && nv2){\r\n        \/* ====== EMBUDO PROPIO (29-jul) ======\r\n           Latido an\\u00f3nimo al ERP: evento + nivel + canal. Responde lo que el\r\n           p\\u00edxel no sabe: en qu\\u00e9 nivel ve la gente su cifra. Si el ERP no\r\n           contesta, no pasa nada \\u2014 la calculadora ni se entera. *\/\r\n        \/* Desde el 12-ago esto solo delega en PRODEX_MEDIDA, que es quien sabe\r\n           la sesi\\u00f3n, el canal de verdad (cookie, no URL), la pieza y el\r\n           aparato. Antes cada latido se calculaba el canal a su manera y le\r\n           faltaba todo lo dem\\u00e1s. El nombre `embudo(...)` se conserva porque lo\r\n           llaman una docena de sitios de esta p\\u00e1gina. *\/\r\n        function embudo(evento,nivel){\r\n            try{\r\n                if(window.PRODEX_MEDIDA) PRODEX_MEDIDA.envia(evento, {nivel: nivel||null});\r\n            }catch(e){}\r\n        }\r\n        var embudoVistos={};\r\n        function embudoResultado(){\r\n            var v=document.getElementById('v_venta'); if(!v||!v.value) return;\r\n            var px=document.getElementById('pxwrap');\r\n            var n=(px&&px.classList.contains('nivel-basico'))?'minuto':'reales';\r\n            if(embudoVistos[n]) return; embudoVistos[n]=1;\r\n            embudo('resultado',n);\r\n        }\r\n\r\n        nv1.addEventListener('click', function(){\r\n            embudo('nivel','minuto');\r\n            if (window.PRODEX && PRODEX.nivel) PRODEX.nivel('minuto');\r\n            pxwrap.classList.add('nivel-basico');\r\n            marcarNivel('nivel1');\r\n        });\r\n        nv2.addEventListener('click', function(){\r\n            \/* ====== DOS \\u00abNIVEL REALES\\u00bb CON EL MISMO NOMBRE (12-ago-2026) ======\r\n               Aqu\\u00ed estaba la trampa: el pixel de Meta cuenta \\u00abnivel reales\\u00bb al\r\n               DESPLEGAR el bloque \\u2014esta linea de abajo, PRODEX.nivel('reales')\\u2014\r\n               y el ERP lo contaba al ESCRIBIR dentro (mas abajo, en el listener\r\n               de 'input'). Dos numeros distintos, el mismo nombre, y puestos en\r\n               la misma mesa a compararse.\r\n\r\n               NO SE TOCA LO QUE DISPARA EL PIXEL: Meta lleva historico y la\r\n               campana esta viva; mover el disparador falsearia la serie y puede\r\n               afectar a la entrega. Se arregla por nuestro lado, nombrando los\r\n               dos momentos por separado:\r\n                 - 'nivel2_desplegado' (este) = abrio el bloque. ES EL\r\n                   EQUIVALENTE AL DEL PIXEL: si hay que cotejar con Meta, se\r\n                   coteja contra este.\r\n                 - 'nivel'+'reales' (abajo)   = escribio de verdad. Es el que\r\n                   mide uso y el que manda para decidir (Pedro, 04-ago:\r\n                   \\u00abdesplegar es curiosidad, no uso\\u00bb).\r\n               La resta de los dos es la gente que abre y no escribe, que hasta\r\n               hoy no se podia medir. *\/\r\n            embudo('nivel2_desplegado');\r\n            if (window.PRODEX && PRODEX.nivel) PRODEX.nivel('reales');\r\n            pxwrap.classList.remove('nivel-basico');\r\n            marcarNivel('nivel2');\r\n        });\r\n\r\n        \/* Embudo honesto: \\u00abreales\\u00bb = primer dato tecleado en un campo lvl2\r\n           (una vez por sesion). Delegado en el wrap porque las filas de\r\n           propietarios se regeneran. *\/\r\n        var nivelRealesContado=false;\r\n        pxwrap.addEventListener('input', function(ev){\r\n            if(nivelRealesContado) return;\r\n            var t=ev.target;\r\n            if(!t || !t.closest || !t.closest('.lvl2')) return;\r\n            if(String(t.value||'').trim()==='') return;\r\n            nivelRealesContado=true;\r\n            embudo('nivel','reales');\r\n        });\r\n\r\n        \/* Atajos \\u00abAfinar con mis numeros\\u00bb bajo los totales: mismo efecto que\r\n           la tarjeta Nivel 2, y lleva la vista al primer campo nuevo del\r\n           modulo para no obligar a subir. *\/\r\n        document.querySelectorAll('[data-afinar]').forEach(function(b){\r\n            b.addEventListener('click', function(){\r\n                nv2.click();\r\n                var mod=b.closest('.mod');\r\n                var primero=mod?mod.querySelector('.f.lvl2'):null;\r\n                if(primero) primero.scrollIntoView({behavior:'smooth',block:'center'});\r\n            });\r\n        });\r\n    }\r\n    var lNombre=document.getElementById('l_nombre');\r\n    if(lNombre){\r\n        var yaNosotros=false;\r\n        \/* EL LATIDO CONTABA NUESTRO PROPIO FOCO (10-ago-2026). 'nosotros' cuelga\r\n           del focus de este campo, y al pulsar el candado el c\\u00f3digo enfoca el\r\n           campo por su cuenta: el escal\\u00f3n marcaba \\u00ab90% llega al formulario\\u00bb y lo\r\n           \\u00fanico que med\\u00eda era que un setTimeout nuestro se hab\\u00eda ejecutado. En\r\n           m\\u00f3vil dej\\u00f3 de pasar hoy \\u2014ah\\u00ed ya no se roba el foco, porque el teclado\r\n           se llevaba el bot\\u00f3n\\u2014, pero en escritorio el foco sigue y es buena UX:\r\n           pulsas y el cursor ya est\\u00e1 en el campo.\r\n           As\\u00ed que no se quita el foco, se marca. Quien lo hace avisa antes, y el\r\n           latido ignora ESE foco y solo ese. Lo que quede contado son personas.\r\n           AVISO PARA LOS N\\u00daMEROS: 'nosotros' va a bajar tambi\\u00e9n en escritorio.\r\n           Es la correcci\\u00f3n de una medida falsa, no una ca\\u00edda. *\/\r\n        var focoNuestro=false;\r\n        var nivelNosotros=function(){\r\n            if(focoNuestro){ focoNuestro=false; return; }\r\n            if(yaNosotros) return;\r\n            yaNosotros=true;\r\n            embudo('nosotros');\r\n            if (window.PRODEX && PRODEX.nivel) PRODEX.nivel('nosotros');\r\n        };\r\n        window.PRODEX_FOCO = { esNuestro: function(){ focoNuestro=true; } };\r\n        lNombre.addEventListener('focus', nivelNosotros);\r\n        lNombre.addEventListener('click', nivelNosotros);\r\n\r\n        \/* ====== EL CANDADO ES UNA PUERTA (3-ago) ======\r\n           Medido el 2 y 3 de agosto, ya descontados los eventos de prueba: 46\r\n           personas vieron su cifra, 6 terminaron el nivel 2 entero \\u2014con el\r\n           recibo del IBI y la escritura delante, que es un compromiso alto\\u2014 y\r\n           UNA sola llego a tocar el formulario. Cero leads.\r\n\r\n           El motivo estaba a la vista: el candado deciamos que era el momento de\r\n           mas ganas (\\u00abquiero ver el desglose\\u00bb) y no se podia pulsar. Era un\r\n           cartel. El formulario estaba abajo del todo, detras del balance, de\r\n           las alertas y de un parrafo legal \\u2014 o sea, detras de justo lo que uno\r\n           deja de leer cuando ya tiene lo que venia a buscar.\r\n\r\n           Sale su propio latido ('candado') a proposito: si la gente pulsa y aun\r\n           asi no deja los datos, el problema es el formulario y no el camino, y\r\n           eso hay que poder distinguirlo sin adivinar. *\/\r\n        Array.prototype.forEach.call(document.querySelectorAll('.lock-cta'), function(b){\r\n            b.addEventListener('click', function(){\r\n                embudo('candado');\r\n                \/* Y TAMBIEN AL PIXEL (10-ago). Hasta hoy este latido solo iba al\r\n                   ERP, asi que en el Administrador de eventos de Meta el evento\r\n                   \"Candado\" NO EXISTIA -- comprobado buscandolo por nombre. Sin\r\n                   el, la unica conversion que Meta ve del embudo es \"Ver\r\n                   contenido\", que ocurre mucho antes y dice mucho menos. La\r\n                   funcion la define el fragmento de medicion (WPCode 3110), que\r\n                   ya resuelve consentimiento y manda a Google y a Meta a la vez;\r\n                   si no estuviera cargada, aqui no pasa nada. *\/\r\n                if (window.PRODEX && PRODEX.candado) PRODEX.candado(this.closest('.lockwrap') ? this.closest('.lockwrap').id : '');\r\n                \/* EL DESTINO DEL SCROLL ES EL BOTON, NO EL BLOQUE (09-ago).\r\n                   Medido a 375px de ancho: del borde superior del campo \\u00abnombre\\u00bb\r\n                   al final del boton hay 484px, y la banda que deja visible el\r\n                   teclado de iOS es de 330-370px. Subiendo el primer campo, el\r\n                   boton quedaba 364px POR DEBAJO del pliegue: nadie podia\r\n                   enviar sin adivinar que habia que seguir bajando. Con\r\n                   block:'end' el boton queda dentro en una ventana de 375x360, y\r\n                   con el teclado cerrado entran ademas los tres campos.\r\n                   Descartado acortar la cabecera (rescata 15-25px de los 130 que\r\n                   faltan) y descartado el boton sticky (en iOS el teclado no\r\n                   encoge el viewport de maquetacion: el sticky se queda DETRAS\r\n                   del teclado, justo en el aparato que hay que arreglar). *\/\r\n                \/* FUERA DE ZONA: SE LE AVISA, PERO SE LE PIDE EL CONTACTO IGUAL\r\n                   (20-ago-2026, revisado). Aqui es donde se decide, y no antes:\r\n                   quien no pulsa el candado no ha pedido nada, y quien lo pulsa\r\n                   ya escribio su municipio. Se le dice la verdad --no vamos a\r\n                   poder ir a su casa-- y se le ofrece lo unico que si podemos\r\n                   cumplir: las cifras y el informe. El formulario SE QUEDA: el\r\n                   ERP tiene un circuito de producto digital para justo esta\r\n                   gente, y el euro del clic ya esta pagado. Latido propio para\r\n                   poder contarlos: si son muchos, el problema esta en la\r\n                   segmentacion de Meta, no en la pagina. *\/\r\n                pintaModoZona();\r\n                \/* DESDE EL 30-ago EL DESTINO ES EL BLOQUE WHATSAPP: el\r\n                   formulario est\\u00e1 oculto y el trato se cierra en wa.me. El POST\r\n                   del c\\u00e1lculo se lanza aqu\\u00ed tambi\\u00e9n \\u2014pulsar el candado es pedir\r\n                   el informe\\u2014, para que la REF ya est\\u00e9 subida cuando el bot\\u00f3n\r\n                   se pueda pulsar. Vale tambi\\u00e9n para fuera de zona: su informe\r\n                   se entrega igual por WhatsApp. Y ya no se roba ning\\u00fan foco:\r\n                   no queda campo que enfocar. *\/\r\n                if(typeof subeCalculo === 'function') subeCalculo();\r\n                if(fueraDeZona()){\r\n                    embudo('fuera_zona');\r\n                    var aviso = document.getElementById('fuera-zona');\r\n                    if(aviso){ aviso.scrollIntoView({behavior:'smooth',block:'start'}); return; }\r\n                }\r\n                var destino = document.getElementById('px-whatsapp') || document.querySelector('.lead');\r\n                if(destino) destino.scrollIntoView({behavior:'smooth',block:'end'});\r\n            });\r\n        });\r\n    }\r\n\r\n    \/* ====== LEAD ====== *\/\r\n    \/* Origen del lead (27-jul): lee la cookie prodex_attr (90 dias) que escribe\r\n       el fragmento de medicion del sitio (WPCode). Nunca rompe el envio. *\/\r\n    function prodexOrigen(){\r\n        try{\r\n            var m = document.cookie.match(\/(?:^|;)\\s*prodex_attr\\s*=\\s*([^;]+)\/);\r\n            return m ? JSON.parse(decodeURIComponent(m[1])) : {};\r\n        }catch(e){ return {}; }\r\n    }\r\n    function validEmail(e){ return \/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$\/.test(e); }\r\n    \/* EL TELEFONO SE TRAGABA CUALQUIERA (09-ago). Pedro: \\u00abhe observado que el\r\n       tel\\u00e9fono se traga cualquiera, porque no tiene ni los d\\u00edgitos\r\n       necesarios\\u00bb. La regla vieja contaba CARACTERES, no d\\u00edgitos\r\n       (\/^[0-9+\\s()-]{7,}$\/): pasaban \\u00ab1234567\\u00bb, \\u00ab()()()()\\u00bb y siete guiones\r\n       seguidos. Por ah\\u00ed entr\\u00f3 el lead de Toledo con 6163201 \\u2014siete d\\u00edgitos\\u2014,\r\n       y la primera acci\\u00f3n del circuito es llamar: ese lead, ya pagado en Meta,\r\n       no arrancaba nada.\r\n\r\n       ESPEJO EXACTO de App\\Service\\Lead\\TelefonoDelLead en el ERP. Si cambia\r\n       una, cambia la otra: aqu\\u00ed se avisa antes de enviar, pero quien decide es\r\n       el servidor, y dos reglas distintas se separan solas en un mes.\r\n\r\n       1. Los espacios raros primero. El espacio DURO (U+00A0) y el fino\r\n          (U+202F) pasan a espacio normal ANTES de juzgar: ocho contactos de la\r\n          base llevan uno dentro de un +34 perfectamente bueno, porque quien\r\n          pega su n\\u00famero desde Contactos de iOS o de Google se lo trae detr\\u00e1s.\r\n          Rechazarlos ser\\u00eda decirle \\u00abtel\\u00e9fono no v\\u00e1lido\\u00bb a un n\\u00famero correcto.\r\n       2. Espa\\u00f1ol (sin prefijo, o con +34 \/ 0034 \/ 34): EXACTAMENTE 9 d\\u00edgitos y\r\n          empezando por 6, 7, 8 o 9. El 9 es la longitud que el propio ERP usa\r\n          como clave para no duplicar fichas.\r\n       3. Extranjero (con + o con 00): de 8 a 15 d\\u00edgitos (tope de E.164). No se\r\n          rechaza: 128 contactos de la casa son de fuera \\u2014Reino Unido, Pa\\u00edses\r\n          Bajos, Francia, Alemania\\u2026\\u2014 y en la Costa del Sol son el cliente de\r\n          m\\u00e1s ticket.\r\n       4. Largo pero sin prefijo: no. Se avisa en el hint del campo, antes de\r\n          fallar, que el de fuera lo escriba con su prefijo.\r\n       5. El prefijo entre par\\u00e9ntesis, \\u00ab(+34) 612 345 678\\u00bb, vale igual que\r\n          suelto: es como viene en las tarjetas, en las firmas de correo y en\r\n          media web espa\\u00f1ola, y tambi\\u00e9n el \\u00ab(+44) 7700 900123\\u00bb del ingl\\u00e9s, que\r\n          aqu\\u00ed es el cliente de m\\u00e1s ticket. Admitiendo el + solo al principio,\r\n          esa gente ve\\u00eda el error rojo sobre un n\\u00famero perfecto y se iba \\u2014 y lo\r\n          apunt\\u00e1bamos como latido falta_tel, o sea que el term\\u00f3metro dec\\u00eda \\u00abno\r\n          sabe escribir su tel\\u00e9fono\\u00bb cuando el que no sab\\u00eda leerlo era este\r\n          c\\u00f3digo. *\/\r\n    function validTel(t){\r\n        var s=String(t).replace(\/[\\s\\u00a0\\u202f]+\/g,' ').trim();\r\n        if(!\/^\\(?\\+?[0-9 .()-]+$\/.test(s)) return false;\r\n        var d=s.replace(\/[^0-9]\/g,'');\r\n        var conPrefijo = s.charAt(0)==='+' || s.slice(0,2)==='(+' || d.slice(0,2)==='00';\r\n        var es=null;\r\n        if(!conPrefijo && d.length===9) es=d;\r\n        else if(d.length===11 && d.slice(0,2)==='34') es=d.slice(2);\r\n        else if(d.length===13 && d.slice(0,4)==='0034') es=d.slice(4);\r\n        if(es!==null) return \/^[6-9][0-9]{8}$\/.test(es);\r\n        return conPrefijo && d.length>=8 && d.length<=15;\r\n    }\r\n\r\n    \/* CON EL TECLADO ABIERTO, LOS AVISOS ROJOS CAEN FUERA DE PANTALLA (09-ago).\r\n       Pulsar y que no pase NADA visible es indistinguible de que la pagina este\r\n       rota, y hasta hoy la rama de error de enviarLead() pintaba los .err y se\r\n       iba sin mover la vista. No es adorno: al poner el boton al alcance de\r\n       todos (ver mas abajo) va a pulsar MAS gente con algo a medias, asi que sin\r\n       esto la mejora se comeria a si misma.\r\n       SOLO EN MOVIL a proposito: en escritorio no hay teclado que tape nada y el\r\n       requisito es que no cambie ni un pixel.\r\n       No toca la validacion ni los latidos: solo lleva la vista al primer fallo,\r\n       que es el mismo que ya calcula 'primerFallo'.\r\n\r\n       Y NO SE HACE CON scrollIntoView (09-ago, segunda pasada): scrollIntoView\r\n       centra contra el viewport de MAQUETACION, que en iOS NO encoge con el\r\n       teclado, asi que no sabe ni que hay teclado ni cuanto tapa (David Fedor,\r\n       \"The Eccentric Ways of iOS Safari with the Keyboard\": el metodo del DOM\r\n       desconoce la presencia y la altura del teclado). Medido a 375x812 con\r\n       teclado de 336px, block:'center' dejaba el campo en 385..427 y su aviso\r\n       rojo en 435..455, o sea ENTERO detras de la barra flotante (427..476); a\r\n       375x660 con teclado de 300px, el aviso caia en 359..419, detras del\r\n       teclado. Resultado: se pulsaba el boton y no pasaba NADA visible, que es\r\n       exactamente el sintoma que este bloque venia a quitar, y encima en la\r\n       ruta que la parte A multiplica.\r\n       Se delega en PRODEX_KB.alaVista, que mide contra la franja realmente\r\n       visible (visualViewport) y descuenta la propia barra. Si esa pieza no\r\n       existe -- sin visualViewport no llega a instalarse -- se cae al\r\n       scrollIntoView de antes: sin teclado que descontar, centrar contra la\r\n       maquetacion es justo lo correcto. *\/\r\n    function mostrarPrimerError(primerFallo){\r\n        if(!(window.matchMedia && window.matchMedia('(max-width:680px)').matches)) return;\r\n        var mapa={falta_nombre:'l_nombre',falta_tel:'l_tel',falta_email:'l_email',falta_rgpd:'l_rgpd'};\r\n        var mapaErr={falta_nombre:'e_nombre',falta_tel:'e_tel',falta_email:'e_email',falta_rgpd:'e_rgpd'};\r\n        var el=document.getElementById(mapa[primerFallo]);\r\n        var aviso=document.getElementById(mapaErr[primerFallo]);\r\n        if(!el) return;\r\n        if(primerFallo==='falta_rgpd'){\r\n            \/* Aqui se CIERRA el teclado a proposito: con el abierto, el aviso de\r\n               la casilla no cabe en la franja visible. La casilla NO se marca por\r\n               nosotros: el consentimiento que el ERP archiva en\r\n               consentimiento.aceptado tiene que ser un acto deliberado. *\/\r\n            try{ if(document.activeElement && document.activeElement.blur) document.activeElement.blur(); }catch(e){}\r\n            setTimeout(function(){ el.scrollIntoView({behavior:'smooth',block:'center'}); },160);\r\n            return;\r\n        }\r\n        try{ el.focus({preventScroll:true}); }catch(e){ el.focus(); }\r\n        if(window.PRODEX_KB && PRODEX_KB.alaVista) PRODEX_KB.alaVista(el, aviso);\r\n        else el.scrollIntoView({behavior:'smooth',block:'center'});\r\n    }\r\n\r\n    \/* EL ENVIO SE MUEVE DE 'click' A 'submit' (09-ago), no se duplica. #send no\r\n       lleva type, asi que dentro de un formulario su tipo por defecto ya es\r\n       submit:\r\n       si se dejara tambien el listener de click, cada pulsacion mandaria DOS\r\n       leads. El cuerpo es el mismo de siempre, sin tocar una coma. *\/\r\n    \/* EL PAYLOAD, EN UNA FUNCION (10-ago-2026). Vivia dentro de enviarLead, y\r\n       desde que el boton del informe manda LOS MISMOS datos al ERP para que\r\n       componga el PDF harian falta en dos sitios. Duplicarlo garantiza que un\r\n       dia el informe y el lead digan cosas distintas del mismo caso. *\/\r\n    \/* EL INFORME LO PIDE AL ERP (10-ago-2026). Antes era window.print(): la\r\n       misma pantalla impresa, sin marca. Ahora el ERP devuelve un PDF con el\r\n       formato de la casa y CUENTA LA PULSACION EN EL SERVIDOR \\u2014 el \\u00fanico\r\n       escal\\u00f3n del embudo que no depende de que el navegador consiga avisar.\r\n\r\n       Si falla, se cae al comportamiento de siempre (imprimir) en vez de dejar\r\n       a la persona sin nada: peor un papel sin marca que un bot\\u00f3n muerto. *\/\r\n    function pedirInforme(btn){\r\n        var textoOriginal = btn.textContent;\r\n        btn.disabled = true;\r\n        btn.textContent = 'Preparando tu informe\\u2026';\r\n\r\n        var corta = ('AbortController' in window) ? new AbortController() : null;\r\n        \/* Misma bandera y mismo motivo que en el env\\u00edo del lead: cuando el\r\n           informe falla, la persona acaba con un window.print() \\u2014un papel sin\r\n           marca\\u2014 y hasta hoy eso no dejaba ni rastro. \\u00abDescargas \/ leads\\u00bb daba\r\n           por \\u00abno quiso\\u00bb lo que era \\u00abno pudo\\u00bb. *\/\r\n        var caducado = false;\r\n        var plazo = setTimeout(function(){ caducado = true; if(corta) corta.abort(); }, 20000);\r\n        function latidoAveriaInforme(){\r\n            try{\r\n                if(window.PRODEX_MEDIDA) PRODEX_MEDIDA.envia(caducado ? 'informe_timeout' : 'informe_error');\r\n            }catch(e){}\r\n        }\r\n\r\n        var datos = construirPayload(\r\n            (document.getElementById('l_nombre')||{}).value || '',\r\n            (document.getElementById('l_tel')||{}).value || '',\r\n            (document.getElementById('l_email')||{}).value || ''\r\n        );\r\n\r\n        fetch('https:\/\/erp.grupoprodex.com\/api\/leads\/calculadora\/informe',{\r\n            method:'POST',\r\n            headers:{'Content-Type':'application\/json','X-Lead-Token':'9cf358810431154e5dd19054108589QYsUte3QuFMEvEg301854108'},\r\n            body:JSON.stringify(datos),\r\n            signal: corta ? corta.signal : undefined\r\n        }).then(function(r){\r\n            clearTimeout(plazo);\r\n            if(!r.ok) throw new Error('respuesta ' + r.status);\r\n            return r.blob();\r\n        }).then(function(blob){\r\n            var url = URL.createObjectURL(blob);\r\n            var a = document.createElement('a');\r\n            a.href = url;\r\n            a.download = 'informe-prodex.pdf';\r\n            document.body.appendChild(a);\r\n            a.click();\r\n            document.body.removeChild(a);\r\n            \/* El objeto se libera tarde a prop\\u00f3sito: revocarlo en el mismo\r\n               instante corta la descarga en algunos navegadores. *\/\r\n            setTimeout(function(){ URL.revokeObjectURL(url); }, 4000);\r\n            btn.disabled = false;\r\n            btn.textContent = textoOriginal;\r\n        }).catch(function(){\r\n            clearTimeout(plazo);\r\n            latidoAveriaInforme();\r\n            btn.disabled = false;\r\n            btn.textContent = textoOriginal;\r\n            window.print();\r\n        });\r\n    }\r\n\r\n    function construirPayload(nombre, tel, email){\r\n        return {\r\n            nombre:nombre, telefono:tel, email:email,\r\n            venta:{\r\n                poblacion:document.getElementById('v_pob').value, num_propietarios:document.getElementById('v_props').value,\r\n                propietarios:readOwners().map(function(o,i){\r\n                    var r = o.res==='ES'?'residente':(o.res==='NOUE'?'no residente fuera UE':'no residente UE');\r\n                    return 'Prop'+(i+1)+': '+o.edad+' a\\u00f1os, '+o.pct+'%, '+r;\r\n                }).join(' | '),\r\n                anio:document.getElementById('v_anio').value,\r\n                adquisicion:document.getElementById('v_adq').value, venta:document.getElementById('v_venta').value,\r\n                hipoteca:document.getElementById('v_hip').value, deudas:document.getElementById('v_deudas').value,\r\n                gastos_deducibles:document.getElementById('v_gastosded').value, catastro_suelo:document.getElementById('v_catastro').value,\r\n                catastro_total:document.getElementById('v_catastro_total').value,\r\n                no_residentes:(ESTADO.venta.noRes||0), retencion_3pct:(ESTADO.venta.reten3||0),\r\n                plusvalia_incluida:!!ESTADO.venta.plusCalculada,\r\n                comision:document.getElementById('v_comi').value, abogado:document.getElementById('v_abog').value,\r\n                vivienda_habitual:document.getElementById('v_habitual').checked, exreinv:document.getElementById('v_exreinv').checked,\r\n                reinversion_pct:document.getElementById('v_reinvpct').value,\r\n                notaria_paga_comprador:document.getElementById('v_notpagacomprador').checked,\r\n                \/* Desde el 30-ago el DOM lleva m\\u00e1scara: los valores reales se leen de CIFRAS. *\/\r\n                liquido_total:CIFRAS.liquido\r\n            },\r\n            compra:{\r\n                poblacion:document.getElementById('c_pob').value, compra:document.getElementById('c_compra').value,\r\n                itp:document.getElementById('c_itp').value,\r\n                itp_vivienda_habitual:document.getElementById('c_habitual').value,\r\n                itp_supuesto_reducido:document.getElementById('c_supuesto').value||null,\r\n                comision:document.getElementById('c_comi').value,\r\n                abogado:document.getElementById('c_abog').value, otros:document.getElementById('c_otros').value,\r\n                notaria_incluida:document.getElementById('c_notaria').checked, hipoteca:document.getElementById('c_hipoteca').checked,\r\n                hip_importe:document.getElementById('h_importe').value, hip_tin:document.getElementById('h_tin').value,\r\n                hip_anios:document.getElementById('h_anios').value, hip_cuota:CIFRAS.cuota,\r\n                coste_total:CIFRAS.coste\r\n            },\r\n            balance:CIFRAS.balance,\r\n            \/* Desglose real en euros (30-ago): lo consume el informe de\r\n               WhatsApp del ERP. Clave NUEVA \\u2014 el lead cl\\u00e1sico la ignora. *\/\r\n            desglose:{venta:(CIFRAS.desgloseVenta||[]), compra:(CIFRAS.desgloseCompra||[])},\r\n            oportunidades_detectadas:(ESTADO.alertas||[]).map(function(a){return a.replace(\/<[^>]+>\/g,'');}),\r\n            origen:'Landing calculadora PRODEX', fecha:new Date().toISOString(),\r\n            \/* atribucion: la red y el codigo de la pieza viajan en el enlace de la\r\n               publicacion; PRODEX_ATRIB los recuerda durante la sesion *\/\r\n            canal: (typeof PRODEX_ATRIB!=='undefined' ? PRODEX_ATRIB.canal : (new URLSearchParams(location.search).get('utm_source')||'').trim())||undefined,\r\n            pieza: (typeof PRODEX_ATRIB!=='undefined' ? PRODEX_ATRIB.pieza : (new URLSearchParams(location.search).get('pieza')||new URLSearchParams(location.search).get('utm_content')||'').trim())||undefined,\r\n            \/* Sesi\\u00f3n y aparato SOLO para el latido del informe (12-ago): el\r\n               servidor apunta la pulsaci\\u00f3n de \\u00abDescargar informe\\u00bb en la tabla\r\n               del embudo, y sin estos dos ese ser\\u00eda el \\u00fanico escal\\u00f3n que se\r\n               queda fuera del JOIN por sesi\\u00f3n \\u2014 justo el que cierra el\r\n               recorrido. Son an\\u00f3nimos: hexadecimal aleatorio y una palabra de\r\n               dos. La creaci\\u00f3n del lead ignora ambas claves. *\/\r\n            sesion: (window.PRODEX_MEDIDA && PRODEX_MEDIDA.sesion) || undefined,\r\n            dispositivo: (window.PRODEX_MEDIDA && PRODEX_MEDIDA.dispositivo()) || undefined,\r\n            \/* origen del anuncio (27-jul): cookie prodex_attr de 90 dias que escribe el\r\n               fragmento de medicion del sitio (gclid\/fbclid\/utm_* + landing + referrer) *\/\r\n            prodex_attr: prodexOrigen(),\r\n            \/* evidencia RGPD: el ERP la archiva en la ficha del contacto *\/\r\n            consentimiento:{\r\n                aceptado:true,\r\n                texto:(function(){var c=document.getElementById('l_rgpd');var l=c&&c.closest('label');if(!l&&c&&c.id){l=document.querySelector('label[for=\"'+c.id+'\"]');}return l?l.textContent.replace(\/\\s+\/g,' ').trim():'Consentimiento de privacidad aceptado en la landing';})(),\r\n                version:'v1-2026-07'\r\n            }\r\n        };\r\n    }\r\n\r\n    function enviarLead(ev){\r\n        if(ev && ev.preventDefault) ev.preventDefault();\r\n        var nombre=document.getElementById('l_nombre').value.trim();\r\n        var tel=document.getElementById('l_tel').value.trim();\r\n        var email=document.getElementById('l_email').value.trim();\r\n        var rgpd=document.getElementById('l_rgpd').checked;\r\n        var ok=true;\r\n        function setErr(id,show){ document.getElementById(id).style.display=show?'block':'none'; if(show) ok=false; }\r\n        setErr('e_nombre', nombre.length<2); \/* 30-ago: 6 rechazos falta_nombre vs 3 envios; se relaja a 2 *\/\r\n        setErr('e_tel', !validTel(tel));\r\n        setErr('e_email', !validEmail(email));\r\n        setErr('e_rgpd', !rgpd);\r\n        if(!ok){\r\n            \/* DONDE SE ATRANCAN (08-ago). Entre 'nosotros' \\u2014pinchar en el\r\n               nombre\\u2014 y un lead conseguido no habia NADA: el 7 y el 8 de\r\n               agosto once personas se pusieron a escribir y ninguna termino,\r\n               y no habia forma de saber por que. La sospecha era el telefono,\r\n               pero una sospecha no es un dato.\r\n\r\n               UNO POR INTENTO, no por campo: se manda solo el PRIMERO que\r\n               falla. Pulsar con el formulario vacio mandaria cuatro latidos y\r\n               pareceria cuatro personas. El orden va de lo que se rellena\r\n               antes a lo que se rellena despues, que es como se recorre. *\/\r\n            var primerFallo = nombre.length<2 ? 'falta_nombre'\r\n                            : (!validTel(tel) ? 'falta_tel'\r\n                            : (!validEmail(email) ? 'falta_email' : 'falta_rgpd'));\r\n            embudo(primerFallo);\r\n            mostrarPrimerError(primerFallo);\r\n            return;\r\n        }\r\n\r\n        \/* recoger todos los datos de la calculadora *\/\r\n        var payload=construirPayload(nombre, tel, email);\r\n\r\n        \/* ===== ENV\\u00cdO AL ERP ===== *\/\r\n        var btn=document.getElementById('send');\r\n        \/* Se guarda el texto REAL que la persona acaba de pulsar, para poder\r\n           devolverlo tal cual si el env\\u00edo falla (ver showError). *\/\r\n        if(!btn.dataset.textoOriginal) btn.dataset.textoOriginal=btn.textContent;\r\n        \/* Se retira el aviso del intento anterior: si el segundo env\\u00edo funciona,\r\n           dejar ah\\u00ed el cartel rojo dir\\u00eda que ha fallado cuando no. *\/\r\n        var avisoPrevio=document.getElementById('err-envio');\r\n        if(avisoPrevio) avisoPrevio.style.display='none';\r\n        btn.disabled=true;\r\n        btn.textContent='Enviando\\u2026';\r\n        \/* PLAZO DE 15 SEGUNDOS (10-ago). Un fetch sin plazo no se rinde nunca: si\r\n           la red se queda a medias \\u2014lo normal en un m\\u00f3vil que entra en un garaje\r\n           o cambia de antena\\u2014 no salta ni el .then ni el .catch, y el bot\\u00f3n se\r\n           queda en \\u00abEnviando\\u2026\\u00bb y deshabilitado PARA SIEMPRE. La persona no puede\r\n           reintentar ni ve ning\\u00fan aviso: se queda mirando una p\\u00e1gina muerta. El\r\n           AbortController fuerza el catch, y de ah\\u00ed sale el aviso de abajo. *\/\r\n        var corta = ('AbortController' in window) ? new AbortController() : null;\r\n        \/* ====== LA AVER\\u00cdA ERA INVISIBLE (12-ago-2026) ======\r\n           Si esto se cae, la persona ve el aviso rojo y NO SE MANDABA NADA: un\r\n           lead perdido por una aver\\u00eda se contaba exactamente igual que uno que\r\n           no quiso dejar sus datos. Con eso, un 5xx nuestro o una red mala se\r\n           le\\u00eda como \\u00abel formulario no convence\\u00bb y se acababa cambiando el texto\r\n           de un bot\\u00f3n para arreglar un problema de servidor.\r\n           Se distingue el plazo agotado del error porque piden cosas distintas:\r\n           el timeout apunta a lentitud nuestra o a una red m\\u00f3vil que se cae en\r\n           un garaje; el error, a un 5xx o a un bloqueador. Esta bandera es la\r\n           \\u00fanica forma de saber cu\\u00e1l de los dos fue: el `catch` de un fetch\r\n           abortado y el de uno rechazado por la red se ven igual. *\/\r\n        var caducado = false;\r\n        var plazo = setTimeout(function(){ caducado = true; if(corta) corta.abort(); }, 15000);\r\n        function latidoAveria(){\r\n            try{\r\n                if(window.PRODEX_MEDIDA) PRODEX_MEDIDA.envia(caducado ? 'envio_timeout' : 'envio_error');\r\n            }catch(e){}\r\n        }\r\n        fetch('https:\/\/erp.grupoprodex.com\/api\/leads\/calculadora',{\r\n            method:'POST',\r\n            headers:{'Content-Type':'application\/json','X-Lead-Token':'9cf358810431154e5dd19054108589QYsUte3QuFMEvEg301854108'},\r\n            body:JSON.stringify(payload),\r\n            signal: corta ? corta.signal : undefined\r\n        }).then(function(r){\r\n            clearTimeout(plazo);\r\n            \/* Una respuesta que llega pero no es 2xx es un error de servidor,\r\n               nunca un plazo agotado: por eso no mira la bandera. *\/\r\n            if(r.ok){ showOk(); } else { caducado = false; latidoAveria(); showError(); }\r\n        }).catch(function(){ clearTimeout(plazo); latidoAveria(); showError(); });\r\n    }\r\n    (function(){\r\n        var f = document.getElementById('l_form');\r\n        var b = document.getElementById('send');\r\n        \/* UN SOLO listener, nunca los dos. Si el tema de WordPress o WPCode\r\n           envolvieran el bloque en otro formulario, el navegador descarta el\r\n           interior\r\n           y b.form deja de ser el nuestro: en ese caso el boton vuelve a ser un\r\n           boton normal y seguimos enviando por click, sin recargar la pagina.\r\n           Comprobacion en la pagina viva despues de pegar:\r\n               document.getElementById('send').form.id  ->  'l_form' *\/\r\n        if(f && b && b.form === f){ f.addEventListener('submit', enviarLead); }\r\n        else if(b){ b.type = 'button'; b.addEventListener('click', enviarLead); }\r\n    })();\r\n\r\n    \/* ==================================================================\r\n       EL TECLADO EN MOVIL (09-ago). Dos tecnicas independientes: si la B no\r\n       llega a activarse, la A sigue en pie.\r\n\r\n       A. LA TECLA DEL TECLADO ES EL BOTON. Encadena nombre -> telefono ->\r\n          correo -> enviar, para que nadie tenga que ALCANZAR el boton: el\r\n          boton esta en el teclado, a un dedo.\r\n          enterkeyhint es SOLO la etiqueta de la tecla (MDN: \"how the enter key\r\n          should be labeled\"); el salto hay que programarlo. Hasta ahora la\r\n          tecla hacia lo peor que podia hacer -- envio implicito desde el primer\r\n          campo y tres avisos rojos -- y no por culpa de enterkeyhint sino\r\n          porque #send no lleva type y dentro de un form su tipo por defecto ya\r\n          es submit.\r\n\r\n       B. EL BOTON FLOTA SOBRE EL TECLADO con window.visualViewport, porque en\r\n          iPhone el campo de telefono ni siquiera tiene tecla azul (type=tel\r\n          abre el teclado numerico, que no lleva Return): la A sola dejaria un\r\n          salto que obliga a alcanzar la pantalla.\r\n          HACE FALTA EN ANDROID IGUAL QUE EN iPHONE (10-ago). Se dijo lo\r\n          contrario y era falso: describia el Chrome anterior a la 108. Desde\r\n          la 108 el defecto es interactive-widget=resizes-visual, la maquetacion\r\n          no encoge y el teclado se superpone lo mismo que en iOS. Y en la\r\n          conducta antigua, que encoge, el boton tampoco quedaba bien: no lo\r\n          tapaba el teclado, se quedaba directamente por debajo del corte. Las\r\n          dos conductas estan medidas y cada una tiene su salida, sin una sola\r\n          rama por navegador: se mide el hueco real.\r\n\r\n       Ninguna de las dos toca enviarLead, ni el payload, ni validTel, ni los\r\n       latidos: la unica via de envio sigue siendo el listener de arriba.\r\n       Fuentes: MDN VisualViewport y enterkeyhint; mathix.dev y saricden (el\r\n       patron canonico de la resta); Martijn Hols (iOS no dispara eventos de\r\n       window al abrir el teclado, y tarda en devolver la altura al cerrarlo);\r\n       WCAG 2.2 SC 2.4.11 + fallo F110 (barra fija que tapa lo enfocado).\r\n       ================================================================== *\/\r\n    (function(){\r\n        var form   = document.getElementById('l_form');\r\n        var send   = document.getElementById('send');\r\n        var nombre = document.getElementById('l_nombre');\r\n        var tel    = document.getElementById('l_tel');\r\n        var email  = document.getElementById('l_email');\r\n        var rgpd   = document.getElementById('l_rgpd');\r\n        if(!form || !send || !nombre || !tel || !email || !rgpd) return;\r\n\r\n        \/* EL UNICO CORTE DE MOVIL DEL BLOQUE son 680px, no 768: es el mismo que\r\n           sube los campos a 16px para que iOS no haga zoom al enfocar. Con 768\r\n           quedaria una franja de 681-767px con el boton flotando y los campos a\r\n           15px, o sea con zoom automatico justo mientras se recoloca la barra. *\/\r\n        var MOVIL = '(max-width:680px)';\r\n        function enMovil(){ return !!(window.matchMedia && window.matchMedia(MOVIL).matches); }\r\n\r\n        \/* ---------------- A. LA TECLA ENCADENA LOS CAMPOS ---------------- *\/\r\n        var cadena = [nombre, tel, email];\r\n\r\n        \/* LA ETIQUETA DEL ULTIMO CAMPO DEPENDE DE LA CASILLA RGPD, y esto es una\r\n           decision, no un detalle. La casilla esta DEBAJO del correo, asi que\r\n           cuando alguien termina de escribirlo casi nunca la ha marcado todavia.\r\n           Si la tecla enviara siempre, el resultado NORMAL seria el aviso rojo:\r\n           exactamente el defecto que venimos a quitar, ahora en la tecla que la\r\n           gente pulsa por instinto. Por eso:\r\n             casilla marcada    -> 'send', y la tecla ENVIA.\r\n             casilla sin marcar -> 'next', y la tecla lleva el foco a la casilla.\r\n           Llevar el foco a la casilla tiene ademas un efecto lateral util: al no\r\n           ser un campo de texto, iOS CIERRA el teclado, la pagina recupera su\r\n           alto y la casilla y el boton de verdad quedan a la vista solos.\r\n           LA CASILLA NO SE MARCA POR CODIGO: el consentimiento que el ERP\r\n           archiva en consentimiento.aceptado tiene que ser un acto deliberado.\r\n           Y el aviso rojo obligatorio se conserva por construccion: sobre la\r\n           casilla no interceptamos nada (indexOf da -1 y salimos), asi que un\r\n           Intro con el foco ahi dispara el envio implicito de siempre ->\r\n           enviarLead() -> e_rgpd en rojo y sin enviar. *\/\r\n        function pintaTecla(){\r\n            email.setAttribute('enterkeyhint', rgpd.checked ? 'send' : 'next');\r\n        }\r\n        pintaTecla();\r\n        rgpd.addEventListener('change', pintaTecla);\r\n        email.addEventListener('focus', pintaTecla);   \/* iOS lee el atributo AL ENFOCAR *\/\r\n\r\n        \/* El salto va aparte porque llegan a el DOS caminos: la tecla limpia y\r\n           la tecla que llego con una composicion todavia abierta. *\/\r\n        function avanza(i){\r\n            if(i < cadena.length - 1){\r\n                try{ cadena[i+1].focus({preventScroll:true}); }catch(e){ cadena[i+1].focus(); }\r\n                repinta();\r\n                return;\r\n            }\r\n            \/* Se RELEE la casilla aqui en vez de fiarse de la etiqueta: iOS lee\r\n               enterkeyhint al enfocar y puede ir un paso por detras si alguien\r\n               marca la casilla con el correo ya enfocado. *\/\r\n            if(!rgpd.checked){\r\n                rgpd.focus();\r\n                if(rgpd.scrollIntoView) rgpd.scrollIntoView({block:'center'});\r\n                repinta();\r\n                return;\r\n            }\r\n            \/* click(), no requestSubmit(): es lo unico que funciona en LAS DOS\r\n               ramas del cableado de arriba (submit si b.form===f, click si el\r\n               tema anida formularios) y nunca dispara las dos. Y si el boton\r\n               esta deshabilitado porque ya se esta enviando, no hace nada: ese\r\n               es el guardarrail contra el lead duplicado. *\/\r\n            send.click();\r\n        }\r\n\r\n        form.addEventListener('keydown', function(ev){\r\n            if(ev.key !== 'Enter' && ev.keyCode !== 13) return;\r\n            var i = cadena.indexOf(ev.target);\r\n            if(i === -1) return;      \/* casilla y enlaces: comportamiento de siempre *\/\r\n            \/* Sin este preventDefault el navegador hace el ENVIO IMPLICITO. Y hay\r\n               un segundo motivo, mas grave: si el tema de WordPress o WPCode\r\n               envolvieran el bloque en otro <form>, el navegador descarta el\r\n               nuestro y el envio implicito seria el del TEMA -- recarga de pagina\r\n               y lead perdido sin dejar rastro.\r\n               VA ANTES QUE LA COMPROBACION DE COMPOSICION, y ese orden es el\r\n               arreglo (10-ago): Gboard con texto predictivo entrega la tecla de\r\n               accion con la composicion abierta (isComposing, o el keyCode 229\r\n               de Android), y hasta ahora se salia de aqui SIN cancelar nada. El\r\n               navegador remataba con el envio implicito y salian los cuatro\r\n               avisos rojos con el formulario a medias: el fallo que este bloque\r\n               venia a quitar, reaparecido por la puerta de atras y solo en\r\n               Android. Ahora el envio se corta SIEMPRE; despues se decide si\r\n               ademas se salta de campo. *\/\r\n            ev.preventDefault();\r\n            if(ev.isComposing || ev.keyCode === 229){\r\n                \/* La composicion la confirma esta misma tecla. Mover el foco\r\n                   AHORA se llevaria por delante las letras sin confirmar, asi\r\n                   que el salto espera al compositionend. La espera caduca a los\r\n                   300ms para que un compositionend posterior y ajeno no mueva el\r\n                   foco por sorpresa; si caduca, la siguiente pulsacion salta. *\/\r\n                var origen = ev.target, vivo = true;\r\n                var cierra = function(){\r\n                    origen.removeEventListener('compositionend', cierra);\r\n                    if(vivo){ vivo = false; avanza(i); }\r\n                };\r\n                origen.addEventListener('compositionend', cierra);\r\n                setTimeout(function(){\r\n                    vivo = false;\r\n                    origen.removeEventListener('compositionend', cierra);\r\n                }, 300);\r\n                return;\r\n            }\r\n            avanza(i);\r\n        });\r\n\r\n        \/* ---------------- B. EL BOTON FLOTA SOBRE EL TECLADO ---------------- *\/\r\n        var vv = window.visualViewport;\r\n        \/* DEGRADACION: sin visualViewport (iOS 12 y anteriores) no se activa\r\n           NADA de la parte B y el formulario queda EXACTAMENTE como esta ahora,\r\n           que ya es mejor que ayer. La parte A ya quedo enganchada arriba. *\/\r\n        function repinta(){}\r\n        if(!vv) return;\r\n\r\n        var ABRE = 150, CIERRA = 100;   \/* histeresis, para no parpadear en el\r\n                                           limite. Por debajo de 150 no es el\r\n                                           teclado: es la barra de direcciones de\r\n                                           Safari encogiendose (60-80px). Un\r\n                                           teclado no baja de 250. *\/\r\n        var flotando = false, roto = false, congelado = false, rafId = 0;\r\n        var hueco = null, altoBoton = 0, ultimo = null, activo = false, reloj = 0;\r\n        \/* EL ALTO DE LA PAGINA SIN TECLADO, que es contra lo que se compara para\r\n           saber si hay teclado. Se guarda el MAXIMO visto: la barra de\r\n           direcciones al mostrarse y ocultarse mueve esto 56-80px, muy por\r\n           debajo del umbral, asi que quedarse con el maximo no confunde nada y\r\n           evita tomar como base una medida tomada a mitad de la transicion. *\/\r\n        var altoBase = vv.height;\r\n\r\n        function esCampo(el){ return el === nombre || el === tel || el === email; }\r\n\r\n        \/* NO se usa offsetParent para saber si el boton se ve: en cuanto es\r\n           position:fixed su offsetParent pasa a null, lo apagariamos, volveria a\r\n           verse y lo encenderiamos otra vez -- parpadeo infinito. showOk() lo\r\n           oculta con style.display, asi que se mira el display calculado. *\/\r\n        function seVe(){ return window.getComputedStyle(send).display !== 'none'; }\r\n\r\n        \/* LA RESTA MIDE LA FRANJA QUE EL TECLADO TAPA, no si hay teclado. Son dos\r\n           preguntas distintas y hay que separarlas, porque hay DOS conductas de\r\n           navegador y no se reparten por sistema operativo:\r\n\r\n             SE SUPERPONE (interactive-widget: resizes-visual, que es el valor\r\n             POR DEFECTO de la especificacion). El viewport de MAQUETACION no\r\n             encoge: innerHeight se queda entero, la resta da la altura del\r\n             teclado y el boton tiene que flotar. Es iOS SIEMPRE -- y tambien\r\n             Chrome de Android desde la version 108, que se alineo con Safari.\r\n             La meta viewport de esta pagina no declara interactive-widget, o sea\r\n             que en Android moderno manda este caso. Que en Android no hacia\r\n             falta este remedio era FALSO: describia el Chrome anterior a la 108.\r\n\r\n             ENCOGE (resizes-content). innerHeight baja con la maquetacion, la\r\n             resta da 0 exacto y no hay nada que flotar. Es Chrome de Android\r\n             hasta la 107 y Firefox de Android con la conducta antigua. Ahi el\r\n             boton no queda tapado, pero TAMPOCO sube solo: se queda por debajo\r\n             del corte y de eso se encarga traeElBoton().\r\n\r\n           Por eso no hay ni una rama por navegador: se mide el hueco real y cada\r\n           conducta cae sola en su sitio. El Math.max(0,...) clava el rebote\r\n           elastico de iOS, que durante el arrastre deja la resta en negativo. *\/\r\n        function franjaTapada(){\r\n            return Math.max(0, window.innerHeight - vv.height - vv.offsetTop);\r\n        }\r\n\r\n        function apaga(){\r\n            if(!flotando) return;\r\n            flotando = false;\r\n            send.classList.remove('kb-float');\r\n            send.style.removeProperty('--kb-gap');\r\n            if(hueco) hueco.style.height = '0px';\r\n        }\r\n\r\n        \/* EL VIGILANTE. La unica red que habia eran los repasos tardios de\r\n           focusout, y el gesto de Android es justo el que nunca dispara\r\n           focusout: el boton ATRAS quita el teclado y DEJA el campo enfocado. Si\r\n           ademas el navegador no emitiera el resize del viewport visual, la\r\n           barra dorada se quedaba clavada a media pantalla hasta tocar fuera.\r\n           Con esto se vuelve a medir mientras el teclado este abierto y se\r\n           recupera sola en menos de medio segundo, dispare quien dispare. Solo\r\n           corre con el teclado abierto: fuera de ahi se para. *\/\r\n        function vigila(){ if(!reloj) reloj = setInterval(function(){ repinta(); }, 400); }\r\n        function para(){ if(reloj){ clearInterval(reloj); reloj = 0; } }\r\n        function reposo(){ apaga(); activo = false; ultimo = null; para(); }\r\n\r\n        function enciende(gap){\r\n            altoBoton = send.offsetHeight;                       \/* medir ANTES de sacarlo del flujo *\/\r\n            var cs = window.getComputedStyle(send);\r\n            if(!hueco){\r\n                \/* Un hueco del mismo alto, o al sacar el boton del flujo la\r\n                   pagina encoge de golpe y da un salto justo mientras se escribe. *\/\r\n                hueco = document.createElement('div');\r\n                hueco.setAttribute('aria-hidden', 'true');\r\n                send.parentNode.insertBefore(hueco, send);\r\n            }\r\n            hueco.style.height = (altoBoton + (parseFloat(cs.marginTop) || 0)) + 'px';\r\n            send.style.setProperty('--kb-gap', gap + 'px');\r\n            send.classList.add('kb-float');\r\n            flotando = true;\r\n            \/* COMPROBAR, NO SUPONER. position:fixed deja de anclarse a la\r\n               pantalla si algun ANCESTRO tiene transform\/filter\/perspective\/\r\n               contain: ese ancestro pasa a ser el bloque contenedor y la barra\r\n               aterriza donde no toca. En la fuente no hay ninguno (verificado),\r\n               pero el bloque se pega dentro de un tema de WordPress que no\r\n               controlamos y que puede animar secciones al hacer scroll. En vez\r\n               de recorrer ancestros adivinando propiedades, se MIDE el\r\n               resultado: con bottom:0 y el transform ya aplicado, el borde\r\n               inferior tiene que caer en innerHeight - gap. Si no cae ahi, se\r\n               deshace y no se vuelve a intentar: la pagina queda como hoy. *\/\r\n            var esperado = window.innerHeight - gap;\r\n            if(Math.abs(send.getBoundingClientRect().bottom - esperado) > 40){\r\n                roto = true;\r\n                apaga();\r\n            }\r\n        }\r\n\r\n        \/* QUE LA BARRA NO TAPE NI EL CAMPO QUE SE ESCRIBE NI LA CASILLA RGPD\r\n           (WCAG 2.2 SC 2.4.11 Focus Not Obscured, y su fallo F110: una barra\r\n           fija que oculta el elemento con el foco). Los px se MIDEN, no se\r\n           escriben a mano.\r\n           Solo se llama al encender y al CAMBIAR de campo, nunca en cada scroll:\r\n           el navegador ya sube el campo enfocado por su cuenta al abrir el\r\n           teclado, y pelearse con el arrastre del usuario da tirones dobles. *\/\r\n        function noTapes(){\r\n            var el = document.activeElement;\r\n            if(!esCampo(el) || !el.getBoundingClientRect) return;\r\n            \/* El alto se lee EN VIVO mientras flota: altoBoton se midio con el\r\n               boton en el flujo, a 262px de ancho y con el texto en dos lineas\r\n               (68px medidos); flotando ocupa el ancho entero y cabe en una (49),\r\n               asi que la variable reserva 19px de mas. *\/\r\n            var alto = flotando ? send.getBoundingClientRect().height : altoBoton;\r\n            var techo = vv.offsetTop + vv.height - alto - 10;\r\n            var abajo = el.getBoundingClientRect().bottom;\r\n            if(el === email){\r\n                \/* La casilla va justo debajo del correo: mientras se escribe el\r\n                   correo tiene que caber tambien ella, o el ultimo paso queda\r\n                   escondido detras de la barra. El limite de 140px evita que un\r\n                   cambio de maqueta futuro provoque un scroll desbocado. *\/\r\n                var caja = (rgpd.closest && rgpd.closest('label')) || rgpd;\r\n                var rb = caja.getBoundingClientRect().bottom;\r\n                if(rb > abajo && (rb - abajo) < 140) abajo = rb;\r\n            }\r\n            if(abajo > techo) window.scrollBy(0, Math.ceil(abajo - techo) + 6);\r\n        }\r\n\r\n        \/* CUANDO EL NAVEGADOR ENCOGE LA MAQUETACION no hay franja que tapar -- la\r\n           resta da 0 y no se flota nada -- pero el boton TAMPOCO sube solo: se\r\n           queda donde estaba, que ahora cae por debajo del corte, y se escribe\r\n           sin verlo. Medido a 360x800 con teclado de 290: franja visible 0..510\r\n           y el boton en 530.1..578.1, entero fuera. Aqui se arrastra la pagina lo\r\n           justo para meterlo, y nunca tanto como para sacar por arriba el campo\r\n           que se esta escribiendo: entre las dos cosas manda el campo. *\/\r\n        function traeElBoton(){\r\n            var el = document.activeElement;\r\n            if(!esCampo(el) || !el.getBoundingClientRect) return;\r\n            var techo = vv.offsetTop + vv.height - MARGEN;\r\n            var d = send.getBoundingClientRect().bottom - techo;\r\n            if(d <= 2) return;\r\n            \/* Arriba el colchon es de 4px, no de 12: cuando el boton no puede\r\n               flotar, que se vea ENTERO importa mas que el aire sobre el campo.\r\n               Medido a 360x800 con el foco en el nombre: con 12 se quedaban 4.1px\r\n               del boton por debajo del corte; con 4 entra entero y aun sobran\r\n               3.9px. Lo que no se negocia es sacar el campo por arriba. *\/\r\n            var margen = el.getBoundingClientRect().top - (vv.offsetTop + 4);\r\n            if(margen < d) d = margen;\r\n            if(d <= 2) return;\r\n            window.scrollBy(0, Math.ceil(d));\r\n        }\r\n\r\n        \/* ---------- C. EL AVISO ROJO, DENTRO DE LA FRANJA VISIBLE ----------\r\n           noTapes() no vale para esto y no es que fallara: es que hace otra\r\n           cosa. Solo corre al ENCENDER o al CAMBIAR de campo (pinta() lo\r\n           condiciona a 'recien || ultimo !== activeElement'), y cuando alguien\r\n           pulsa el boton con el mismo campo enfocado no cambia nada, asi que no\r\n           corre; y aunque corriese, solo garantiza el borde inferior del INPUT,\r\n           no el del mensaje de error, que es el que explica por que no se ha\r\n           enviado.\r\n           Aqui se coloca el GRUPO -- campo + aviso -- dentro de la franja util:\r\n             arriba = vv.offsetTop            (borde alto de lo visible; MDN:\r\n                                               offsetTop es el desplazamiento del\r\n                                               viewport visual DENTRO del de\r\n                                               maquetacion, que es el sistema de\r\n                                               coordenadas de getBoundingClientRect)\r\n             abajo  = vv.offsetTop + vv.height - la barra flotante\r\n           Lo que no cabe en esa franja no existe para el usuario.\r\n           EL SALTO ES INSTANTANEO A PROPOSITO: con 'smooth' la posicion final\r\n           llega uno o varios cuadros despues y cualquier medida posterior mide\r\n           el aire; instantaneo, lo que se calcula es lo que se ve. *\/\r\n        var MARGEN = 12;\r\n        function coloca(el, aviso, soloSiSeSale){\r\n            if(!el || !el.getBoundingClientRect || !enMovil()) return;\r\n            var r = el.getBoundingClientRect();\r\n            var top = r.top, bot = r.bottom;\r\n            if(aviso && aviso.getBoundingClientRect){\r\n                var ra = aviso.getBoundingClientRect();\r\n                \/* height 0 = sigue en display:none (p.ej. el aviso de otro\r\n                   campo): entonces el grupo es solo el input. *\/\r\n                if(ra.height > 0 && ra.bottom > bot) bot = ra.bottom;\r\n            }\r\n            var barra = (flotando && !roto && seVe()) ? send.getBoundingClientRect().height : 0;\r\n            var arriba = vv.offsetTop + MARGEN;\r\n            var abajo  = vv.offsetTop + vv.height - barra - MARGEN;\r\n            \/* Franja absurda (teclado gigante, pantalla minuscula): mas vale no\r\n               tocar el scroll que dejarlo en cualquier sitio. *\/\r\n            if(abajo - arriba < 60) return;\r\n            if(soloSiSeSale && top >= arriba && bot <= abajo) return;\r\n            var d = (bot - top) > (abajo - arriba)\r\n                    ? top - arriba                              \/* no cabe: se ancla arriba *\/\r\n                    : ((top + bot) \/ 2) - ((arriba + abajo) \/ 2); \/* cabe: centrado en la franja *\/\r\n            d = Math.round(d);\r\n            if(Math.abs(d) < 2) return;\r\n            \/* behavior:'instant' es de CSSOM-View y no existe en los Safari mas\r\n               viejos, donde el diccionario lanza TypeError: por eso el catch cae\r\n               a la forma de dos argumentos, la misma que ya usa noTapes(). *\/\r\n            try{ window.scrollBy({top:d, left:0, behavior:'instant'}); }\r\n            catch(e){ window.scrollBy(0, d); }\r\n        }\r\n        function alaVista(el, aviso){\r\n            coloca(el, aviso, false);\r\n            \/* DOS REPASOS TARDIOS, y son la mitad del arreglo: al saltar de un\r\n               campo de texto al numerico (correo -> telefono, que es la ruta del\r\n               fallo dominante 'falta_tel') iOS CAMBIA la altura del teclado y no\r\n               lo cuenta hasta uno o varios cuadros despues; ademas mostrarPrimerError\r\n               enfoca, y ese focusin acaba en pinta() -> noTapes(). Sin repasos la\r\n               correccion se perdia y no se recuperaba ni arrastrando. Corrigen\r\n               SOLO si el grupo se ha vuelto a salir, para no pelearse con el dedo\r\n               de quien ya esta desplazando la pagina. *\/\r\n            setTimeout(function(){ coloca(el, aviso, true); }, 200);\r\n            setTimeout(function(){ coloca(el, aviso, true); }, 550);\r\n        }\r\n\r\n        \/* Coalescido en requestAnimationFrame: cada tecla de estos campos\r\n           dispara recalc(), que reescribe los desgloses enteros. Recolocar la\r\n           barra sincronamente en cada evento seria tiron garantizado. *\/\r\n        function pinta(){\r\n            rafId = 0;\r\n            var gap = franjaTapada();\r\n            if(vv.height > altoBase) altoBase = vv.height;\r\n            \/* HAY TECLADO si la franja visible ha encogido respecto al alto de la\r\n               pagina sin teclado. Esta pregunta es INDEPENDIENTE de que la\r\n               maquetacion encoja o no, y por eso vale para las dos conductas: en\r\n               las dos, lo visible baja de golpe 250px o mas. La otra pregunta\r\n               -- cuanto tapa, o sea si hay que flotar -- la contesta gap. *\/\r\n            var falta = altoBase - vv.height;\r\n            var abierto = enMovil() && seVe() && esCampo(document.activeElement)\r\n                          && falta > (activo ? CIERRA : ABRE);\r\n            if(!abierto){ reposo(); return; }\r\n            var recien = !activo;\r\n            activo = true;\r\n            if(!roto){\r\n                if(gap > (flotando ? CIERRA : ABRE)){\r\n                    if(!flotando) enciende(gap);\r\n                    else send.style.setProperty('--kb-gap', gap + 'px');\r\n                }else{\r\n                    apaga();       \/* la maquetacion encogio: no hay nada que flotar *\/\r\n                }\r\n            }\r\n            if(recien || ultimo !== document.activeElement){\r\n                ultimo = document.activeElement;\r\n                if(flotando && !roto) noTapes(); else traeElBoton();\r\n            }\r\n            vigila();\r\n        }\r\n        repinta = function(){ if(!rafId && !congelado) rafId = requestAnimationFrame(pinta); };\r\n\r\n        \/* SOLO los eventos de visualViewport. iOS no dispara NINGUN resize de\r\n           window al abrir el teclado, y sin el 'scroll' del visual viewport la\r\n           barra se despega en cuanto se arrastra con el teclado abierto. *\/\r\n        vv.addEventListener('resize', repinta);\r\n        vv.addEventListener('scroll', repinta);\r\n        \/* Y el resize de window ADEMAS, no en su lugar: en la conducta que encoge\r\n           la maquetacion es este el que avisa, y en iOS no llega nunca al abrir\r\n           el teclado, asi que no estorba. *\/\r\n        window.addEventListener('resize', repinta);\r\n        form.addEventListener('focusin', repinta);\r\n        form.addEventListener('focusout', function(){\r\n            \/* iOS no devuelve la altura al cerrar el teclado inmediatamente\r\n               (Martijn Hols llega a esperar 1 s). Sin repasos tardios la barra\r\n               se queda flotando donde estaba, que es justo lo que no puede pasar. *\/\r\n            repinta();\r\n            setTimeout(repinta, 120); setTimeout(repinta, 400); setTimeout(repinta, 1000);\r\n        });\r\n        window.addEventListener('orientationchange', function(){\r\n            reposo();                      \/* las medidas de antes ya no valen *\/\r\n            altoBase = 0;                  \/* incluida la del alto sin teclado *\/\r\n            setTimeout(repinta, 300);\r\n        });\r\n        \/* EL RIESGO GORDO: en iOS el pointerdown sobre el boton dispara el blur\r\n           del campo ANTES del click. Si desflotasemos ahi mismo, el boton se\r\n           moveria bajo el dedo y el click no llegaria nunca -- justo el lead que\r\n           estamos intentando no perder. Mientras el dedo esta encima, la barra\r\n           no se mueve pase lo que pase con el viewport. *\/\r\n        send.addEventListener('pointerdown', function(){\r\n            congelado = true;\r\n            setTimeout(function(){ congelado = false; repinta(); }, 600);\r\n        });\r\n        window.addEventListener('pagehide', reposo);\r\n\r\n        \/* showOk() oculta #send: sin esto quedaria una barra fija VACIA pegada\r\n           al pie mientras se lee el mensaje de gracias. *\/\r\n        window.PRODEX_KB = { apaga: reposo, repinta: function(){ repinta(); }, alaVista: alaVista };\r\n    })();\r\n\r\n    \/* FUERA DE ZONA: la cifra sin el peaje (20-ago-2026).\r\n       La hermana pobre de showOk(): abre los mismos cinco candados y deja la\r\n       misma marca en el DOM --si no, el parche de nivel vuelve a tapar los\r\n       totales al cambiar de nivel--, pero NO le cuenta un lead a Meta (no lo\r\n       hay: contarlo seria volver al 8 contra 2 de agosto), NO ofrece el PDF\r\n       (lo compone el ERP a partir de un lead que aqui no existe) y retira el\r\n       formulario, que es el punto entero: que nadie deje un telefono que no\r\n       vamos a marcar. *\/\r\n    \/* LOS TEXTOS DE FUERA DE ZONA. El formulario es el mismo y pide lo mismo;\r\n       lo que cambia es lo que promete, porque la llamada de un consultor para\r\n       preparar la venta no se la podemos dar. Se guarda el texto original en el\r\n       dataset del propio elemento para poder DESHACERLO: alguien puede escribir\r\n       Nerja, ver el aviso, corregir a Coin y enviar -- y entonces el formulario\r\n       tiene que volver a prometer la llamada. *\/\r\n    \/* Desde el 30-ago solo cambian el titular y el subtitulo del bloque\r\n       WhatsApp: el formulario esta oculto ('hint-tel', 'send' y 'okmsg' ya no\r\n       se ven) y el informe se entrega igual, por WhatsApp, tambien fuera de\r\n       zona \\u2014 lo unico que NO se promete es acompanar la venta. *\/\r\n    var TEXTOS_FUERA_DE_ZONA = {\r\n        'lead-h2'  : 'Tu informe est\\u00e1 listo',\r\n        'lead-sub' : 'Aunque no podamos acompa\\u00f1arte en la venta, tu informe con la cifra exacta y el desglose te lo enviamos igual hoy mismo a tu WhatsApp.'\r\n    };\r\n\r\n    \/* Se enciende al pulsar el candado. Antes de eso no se toca nada: quien no\r\n       ha pulsado no ha pedido nada y no tiene por que leer una advertencia. *\/\r\n    var modoZonaEncendido = false;\r\n\r\n    function pintaModoZona(){\r\n        modoZonaEncendido = true;\r\n        var fuera = fueraDeZona();\r\n        Object.keys(TEXTOS_FUERA_DE_ZONA).forEach(function(id){\r\n            var el = document.getElementById(id);\r\n            if(!el) return;\r\n            if(typeof el.dataset.textoEnZona !== 'string') el.dataset.textoEnZona = el.innerHTML;\r\n            el.innerHTML = fuera ? TEXTOS_FUERA_DE_ZONA[id] : el.dataset.textoEnZona;\r\n        });\r\n        var aviso = document.getElementById('fuera-zona');\r\n        if(aviso) aviso.style.display = fuera ? 'block' : 'none';\r\n    }\r\n\r\n    \/* Si corrige el municipio DESPUES de ver el aviso, los textos se recalculan\r\n       solos. Sin esto, quien arregla una errata se queda con la pagina\r\n       diciendole que no trabajamos donde si trabajamos. *\/\r\n    (function(){\r\n        var pob = document.getElementById('v_pob');\r\n        if(!pob) return;\r\n        ['input','change'].forEach(function(ev){\r\n            pob.addEventListener(ev, function(){ if(modoZonaEncendido) pintaModoZona(); });\r\n        });\r\n    })();\r\n\r\n    function showOk(){\r\n        ['lock_v','lock_c','lock_vtotal','lock_ctotal','lock_balance'].forEach(function(id){\r\n            var el=document.getElementById(id); if(el) el.classList.remove('locked');\r\n        });\r\n        \/* Marca permanente en el DOM (06-ago): el candado de las CIFRAS vive en\r\n           el parche de nivel (otro closure, mas abajo en el fichero) y necesita\r\n           saber que ya se desbloqueo para no volver a tapar los totales al\r\n           cambiar de nivel. dataset vive en el elemento, no en la variable JS,\r\n           asi que lo lee cualquier closure que tenga el mismo #pxwrap. *\/\r\n        if (pxwrap) pxwrap.dataset.desbloqueado = '1';\r\n        \/* EL LEAD SE LE CUENTA A META AQU\\u00cd, QUE ES CUANDO EXISTE (10-ago-2026).\r\n           Antes lo disparaba el fragmento de medici\\u00f3n al PULSAR el bot\\u00f3n, con una\r\n           condici\\u00f3n m\\u00e1s floja que la de enviar de verdad: le bastaba nombre y\r\n           (tel\\u00e9fono O correo). Comprobado en el navegador: con el tel\\u00e9fono \\u00ab666\\u00bb\r\n           y sin correo, la calculadora no manda NADA al ERP \\u2014salen los avisos\r\n           rojos\\u2014 y el p\\u00edxel le contaba a Meta un lead de 12.000 \\u20ac. Por eso Meta\r\n           dec\\u00eda 8 y el ERP ten\\u00eda 2: Meta contaba intentos. Aqu\\u00ed solo se llega\r\n           tras un 201 del ERP, o sea que un lead contado es un lead que existe. *\/\r\n        if (window.PRODEX && PRODEX.lead) PRODEX.lead();\r\n        document.getElementById('okmsg').style.display='block';\r\n        document.getElementById('send').style.display='none';\r\n        \/* Antes de nada, bajar la barra flotante del teclado: si el boton se\r\n           oculta estando fijado, queda una barra vacia pegada al pie durante\r\n           todo el mensaje de gracias. *\/\r\n        if(window.PRODEX_KB && PRODEX_KB.apaga) PRODEX_KB.apaga();\r\n        var botonPdf = document.getElementById('btn-pdf');\r\n        botonPdf.style.display='block';\r\n        \/* Se engancha AQUI y no al cargar: el bot\\u00f3n nace oculto y solo existe\r\n           para quien ya dej\\u00f3 sus datos. Con la guarda para que dos env\\u00edos no\r\n           dejen dos oyentes y una pulsaci\\u00f3n pida dos informes. *\/\r\n        if(!botonPdf.dataset.enganchado){\r\n            botonPdf.dataset.enganchado='1';\r\n            botonPdf.addEventListener('click', function(){ pedirInforme(botonPdf); });\r\n        }\r\n        \/* LLEVA A LAS CIFRAS, NO AL MENSAJE DE GRACIAS (10-ago). El mensaje est\\u00e1\r\n           POR DEBAJO de los totales, as\\u00ed que el scroll de antes pasaba de largo\r\n           justo por lo que la persona vino a buscar: se desbloqueaban las cifras\r\n           y no se enteraba. Se va al balance, que es el n\\u00famero que resume la\r\n           operaci\\u00f3n; si no estuviera, se cae al mensaje de siempre.\r\n\r\n           NI 'smooth' NI requestAnimationFrame, y por el mismo motivo: los dos\r\n           dependen de que la pesta\\u00f1a est\\u00e9 en primer plano. Con la pesta\\u00f1a en\r\n           segundo plano el navegador no da fotogramas y la animaci\\u00f3n no corre,\r\n           as\\u00ed que la persona volver\\u00eda a la p\\u00e1gina y se encontrar\\u00eda exactamente\r\n           donde la dej\\u00f3, sin ver sus cifras. Una promesa que solo se cumple con\r\n           la pesta\\u00f1a delante no es una promesa. Se salta AHORA, sin animaci\\u00f3n.\r\n\r\n           Y se repasa a los 400 ms: destapar los cinco candados hace CRECER el\r\n           documento, y si algo llega tarde (tipograf\\u00edas, im\\u00e1genes) la maquetaci\\u00f3n\r\n           se mueve por debajo y el salto se queda corto. El repaso NO se hace si\r\n           la persona ya ha empezado a moverse por su cuenta: robarle el scroll a\r\n           alguien que est\\u00e1 leyendo es peor que dejarlo unos p\\u00edxeles descolocado. *\/\r\n        var premio = document.getElementById('lock_balance') || document.getElementById('okmsg');\r\n        function alPremio(){ premio.scrollIntoView({block:'center'}); return Math.round(window.scrollY); }\r\n        var puesto = alPremio();\r\n        setTimeout(function(){\r\n            if(Math.abs(window.scrollY - puesto) > 4) return;   \/* se ha movido: es suyo *\/\r\n            var r = premio.getBoundingClientRect();\r\n            if(r.top < 0 || r.bottom > window.innerHeight) alPremio();\r\n        }, 400);\r\n    }\r\n    function showError(){\r\n        var btn=document.getElementById('send');\r\n        btn.disabled=false;\r\n        \/* El texto se recupera del propio bot\\u00f3n, no se escribe aqu\\u00ed a mano: la\r\n           versi\\u00f3n anterior lo dejaba en un texto viejo que el usuario no hab\\u00eda\r\n           visto nunca, y quedaba distinto del que hab\\u00eda pulsado. *\/\r\n        if(btn.dataset.textoOriginal) btn.textContent=btn.dataset.textoOriginal;\r\n        \/* EN LA P\\u00c1GINA, NO EN UN alert(). El alert se cierra de un toque y no deja\r\n           nada detr\\u00e1s: la persona vuelve al formulario sin saber qu\\u00e9 hacer. El\r\n           aviso se queda donde estaba mirando, con el tel\\u00e9fono a un toque. Y se\r\n           lleva a la vista sin animaci\\u00f3n, por lo mismo que el salto a las cifras:\r\n           con la pesta\\u00f1a detr\\u00e1s no hay fotogramas. *\/\r\n        var aviso=document.getElementById('err-envio');\r\n        if(aviso){\r\n            aviso.style.display='block';\r\n            aviso.scrollIntoView({block:'center'});\r\n        }else{\r\n            alert('No hemos podido enviar tus datos. Vuelve a intentarlo o ll\\u00e1manos al 951 199 132.');\r\n        }\r\n    }\r\n\r\n    \/* ==================================================================\r\n       ENTREGA POR WHATSAPP (30-ago-2026)\r\n       ==================================================================\r\n       El trato nuevo: el resultado no se pinta y no hay formulario. El\r\n       c\\u00e1lculo COMPLETO sube al ERP con una REF an\\u00f3nima ANTES de abrir\r\n       WhatsApp; el usuario pulsa el bot\\u00f3n wa.me con \\u00abCALCULADORA REF-X\\u00bb\r\n       precargado y Pedro le responde con el informe desde el ERP, casando\r\n       por la REF. Cero datos personales en el POST: ni nombre, ni tel\\u00e9fono,\r\n       ni email, ni consentimiento (no hay formulario que lo recoja \\u2014 el\r\n       n\\u00famero lo da la persona al escribirnos, y solo para su informe).\r\n\r\n       La REF se deriva de la sesi\\u00f3n an\\u00f3nima de PRODEX_MEDIDA (8 primeros\r\n       hex en may\\u00fasculas) para que el c\\u00e1lculo case con el embudo por JOIN;\r\n       sin crypto no hay sesi\\u00f3n, y entonces la REF son 8 hex aleatorios de\r\n       Math.random que hacen tambi\\u00e9n de marcador de la visita.\r\n\r\n       El POST se lanza al mostrarse el bloque (o al pulsar un candado, que\r\n       es pedir el informe) y se reintenta UNA vez. Si aun as\\u00ed falla, el\r\n       bot\\u00f3n se habilita igual y se registra 'envio_error': mejor un\r\n       WhatsApp sin payload que perder el contacto. Idempotente por ref en\r\n       el servidor, y con cerrojo aqu\\u00ed para no subirlo dos veces.\r\n       ================================================================== *\/\r\n    var REF_CALCULO = (function(){\r\n        var s = (window.PRODEX_MEDIDA && PRODEX_MEDIDA.sesion) || null;\r\n        if(s && s.length >= 8) return s.slice(0,8).toUpperCase();\r\n        var r = '';\r\n        for(var i=0;i<8;i++) r += '0123456789ABCDEF'.charAt(Math.floor(Math.random()*16));\r\n        return r;\r\n    })();\r\n\r\n    var calculoPedido = false;\r\n    \/* El bloque ya entr\\u00f3 en pantalla al menos una vez. *\/\r\n    var bloqueWhatsAppVisto = false;\r\n\r\n    \/* Sin nada calculado no hay informe que subir: el POST espera a que haya\r\n       una cifra (venta o compra). Quien baja al bloque ANTES de teclear no\r\n       dispara nada; en cuanto teclea, recalc() llama a avisaCalculoListo(). *\/\r\n    function hayCifraQueEnviar(){\r\n        try{ return num('v_venta') > 0 || num('c_compra') > 0; }catch(e){ return true; }\r\n    }\r\n    var armaBotonRespaldo = null;\r\n    function avisaCalculoListo(){\r\n        if(bloqueWhatsAppVisto && !calculoPedido && hayCifraQueEnviar()){ subeCalculo(); return; }\r\n        \/* Red de seguridad: si el observer nunca dispara (navegador raro,\r\n           pesta\\u00f1a en segundo plano), el bot\\u00f3n NO puede quedarse muerto en\r\n           \\u00abPreparando\\u2026\\u00bb: a los 5 s de haber cifra se arma igual \\u2014 el clic\r\n           lleva su propio subeCalculo() con keepalive como \\u00faltimo cartucho. *\/\r\n        if(!armaBotonRespaldo && hayCifraQueEnviar()){\r\n            armaBotonRespaldo = setTimeout(armaBotonWhatsApp, 5000);\r\n        }\r\n    }\r\n\r\n    function armaBotonWhatsApp(){\r\n        var b = document.getElementById('px-wa-btn');\r\n        if(!b || !b.disabled) return;\r\n        b.disabled = false;\r\n        b.textContent = 'Recibir mi informe por WhatsApp';\r\n    }\r\n\r\n    function subeCalculo(){\r\n        if(calculoPedido) return;\r\n        calculoPedido = true;\r\n        var payload = null;\r\n        try{\r\n            \/* El MISMO payload del lead de siempre, SIN datos personales. *\/\r\n            payload = construirPayload('', '', '');\r\n            delete payload.nombre;\r\n            delete payload.telefono;\r\n            delete payload.email;\r\n            delete payload.consentimiento;\r\n        }catch(e){}\r\n        if(!payload){ armaBotonWhatsApp(); return; }\r\n        var cuerpo = JSON.stringify({\r\n            ref: REF_CALCULO,\r\n            sesion: (window.PRODEX_MEDIDA && PRODEX_MEDIDA.sesion) || null,\r\n            payload: payload\r\n        });\r\n        function intenta(esReintento){\r\n            fetch('https:\/\/erp.grupoprodex.com\/api\/leads\/calculadora\/calculo', {\r\n                method: 'POST',\r\n                headers: {'Content-Type':'application\/json','X-Lead-Token':'9cf358810431154e5dd19054108589QYsUte3QuFMEvEg301854108'},\r\n                body: cuerpo,\r\n                keepalive: true\r\n            }).then(function(r){\r\n                if(!r.ok) throw new Error('respuesta ' + r.status);\r\n                armaBotonWhatsApp();\r\n            })['catch'](function(){\r\n                if(!esReintento){ intenta(true); return; }\r\n                try{ if(window.PRODEX_MEDIDA) PRODEX_MEDIDA.envia('envio_error'); }catch(e){}\r\n                armaBotonWhatsApp();\r\n            });\r\n        }\r\n        intenta(false);\r\n    }\r\n\r\n    (function(){\r\n        var b = document.getElementById('px-wa-btn');\r\n        if(!b) return;\r\n        b.addEventListener('click', function(){\r\n            \/* \\u00daltimo cartucho: si el POST no ha salido a\\u00fan (p. ej. sin\r\n               IntersectionObserver y sin candado pulsado), sale ahora con\r\n               keepalive; no se espera su respuesta para abrir WhatsApp. *\/\r\n            try{ if(!calculoPedido && hayCifraQueEnviar()) subeCalculo(); }catch(e){}\r\n            \/* Mismo latido y misma v\\u00eda que midePulsacion() para los wa.me:\r\n               sendBeacon, porque el clic puede sacar de la p\\u00e1gina hacia la\r\n               app de WhatsApp y un fetch normal se queda a medias. *\/\r\n            try{ if(window.PRODEX_MEDIDA) PRODEX_MEDIDA.enviaAlIrse('clic_whatsapp'); }catch(e){}\r\n            var url = 'https:\/\/wa.me\/' + WHATSAPP_PRODEX + '?text=' + encodeURIComponent('CALCULADORA REF-' + REF_CALCULO);\r\n            var w = null;\r\n            try{ w = window.open(url, '_blank', 'noopener'); }catch(e){}\r\n            if(!w){ try{ location.href = url; }catch(e){} }\r\n        });\r\n        \/* El POST sale al ENTRAR EL BLOQUE EN PANTALLA (una vez), igual que\r\n           los latidos de visibilidad. Sin IntersectionObserver, se sube ya:\r\n           un navegador viejo no se queda sin informe por no poder mirar. *\/\r\n        try{\r\n            if('IntersectionObserver' in window){\r\n                var obs = new IntersectionObserver(function(filas){\r\n                    for(var i=0;i<filas.length;i++){\r\n                        if(!filas[i].isIntersecting) continue;\r\n                        obs.disconnect();\r\n                        bloqueWhatsAppVisto = true;\r\n                        avisaCalculoListo();\r\n                        return;\r\n                    }\r\n                }, {threshold: 0.25});\r\n                obs.observe(document.getElementById('px-whatsapp') || b);\r\n            } else {\r\n                bloqueWhatsAppVisto = true;\r\n                avisaCalculoListo();\r\n            }\r\n        }catch(e){ bloqueWhatsAppVisto = true; avisaCalculoListo(); }\r\n    })();\r\n\r\n    (function(){\r\n        function arranca(){\r\n            var px  = document.getElementById('pxwrap');\r\n            var nv1 = document.getElementById('nivel1');\r\n            var nv2 = document.getElementById('nivel2');\r\n            if(!px || !nv1 || !nv2) return;              \/* si el bloque cambia, no rompe nada *\/\r\n            if(px.dataset.parcheNivel2) return;          \/* idempotente: nunca se aplica dos veces *\/\r\n            px.dataset.parcheNivel2 = '2';\r\n\r\n            var vVenta  = document.getElementById('v_venta');\r\n            var cCompra = document.getElementById('c_compra');\r\n            var lockV   = document.getElementById('lock_v');\r\n            var lockC   = document.getElementById('lock_c');\r\n            \/* Candado de las CIFRAS (06-ago), no del desglose: lock_v\/lock_c de\r\n               arriba siempre estan bloqueados hasta dejar datos; estos tres se\r\n               abren y cierran con el nivel, y se quedan abiertos para siempre en\r\n               cuanto showOk() marca pxwrap.dataset.desbloqueado='1' (ver mas\r\n               arriba en el fichero, otro closure, mismo elemento DOM). *\/\r\n            var lockVTotal  = document.getElementById('lock_vtotal');\r\n            var lockCTotal  = document.getElementById('lock_ctotal');\r\n            var lockBalance = document.getElementById('lock_balance');\r\n\r\n            function conValor(el){ return !!(el && String(el.value).trim() !== '' && parseFloat(el.value) > 0); }\r\n            function vacio(id){ var e = document.getElementById(id); return !e || String(e.value).trim() === '' || parseFloat(e.value) === 0; }\r\n            function recalcula(){ try{ if(typeof window.recalc === 'function') window.recalc(); }catch(e){} }\r\n\r\n            \/* ---- C. el escal\\u00f3n que faltaba en el embudo: \\u00abempieza\\u00bb ----\r\n               De 365 visitas solo 56 llegaron a ver una cifra, y no sab\\u00edamos si se\r\n               iban sin tocar nada o si abandonaban a medias. Este latido sale UNA\r\n               vez por visita, la primera que se toca cualquier campo.\r\n               El token se lee del propio bloque para no tener dos copias que se\r\n               desincronicen; si no aparece, no se manda nada y no pasa nada. *\/\r\n            var yaEmpezo = false;\r\n            \/* Se deja la busqueda del token porque este bloque puede correr en\r\n               paginas donde PRODEX_MEDIDA no exista; cuando existe, manda ella\r\n               (12-ago) y el latido sale ya con sesion, canal bueno, pieza y\r\n               aparato como todos los demas. *\/\r\n            function tokenDeLaLanding(){\r\n                try{\r\n                    var s = document.querySelectorAll('script:not([src])');\r\n                    for(var i=0;i<s.length;i++){\r\n                        var m = s[i].textContent.match(\/'X-Lead-Token'\\s*:\\s*'([^']+)'\/);\r\n                        if(m) return m[1];\r\n                    }\r\n                }catch(e){}\r\n                return null;\r\n            }\r\n            function latidoEmpieza(){\r\n                if(yaEmpezo) return;\r\n                yaEmpezo = true;\r\n                if(window.PRODEX_MEDIDA){ PRODEX_MEDIDA.envia('empieza'); return; }\r\n                var t = tokenDeLaLanding();\r\n                if(!t) return;\r\n                try{\r\n                    fetch('https:\/\/erp.grupoprodex.com\/api\/leads\/calculadora\/evento', {\r\n                        method: 'POST',\r\n                        headers: {'Content-Type':'application\/json', 'X-Lead-Token': t},\r\n                        body: JSON.stringify({\r\n                            evento: 'empieza',\r\n                            nivel:  null,\r\n                            canal:  (new URLSearchParams(location.search).get('utm_source')||'').trim() || null\r\n                        }),\r\n                        keepalive: true\r\n                    }).catch(function(){});\r\n                }catch(e){}\r\n            }\r\n\r\n            \/* El IRPF no tiene id propio: se busca su fila (.crow) dentro del desglose\r\n               de la venta. OJO con dos trampas, las dos pisadas ya:\r\n               - hay que acotar a la FILA, no al contenedor del desglose: \\u00e9ste tambi\\u00e9n\r\n                 contiene \\u00abIRPF\\u00bb y su primer importe es el valor de venta;\r\n               - dentro de la fila hay dos importes (\\u00abGanancia neta 150.000 \\u20ac\\u00bb y el\r\n                 cargo). El bueno es el \\u00daLTIMO, el de la derecha.\r\n               Si el bloque cambiara, devuelve null y el texto va sin cifra. *\/\r\n            function irpfEnPantalla(){\r\n                try{\r\n                    var filas = document.querySelectorAll('#lock_v .lockblur .crow');\r\n                    for(var i=0;i<filas.length;i++){\r\n                        if(!\/IRPF\/i.test(filas[i].textContent)) continue;\r\n                        var ms = filas[i].textContent.match(\/[\\d.]+,\\d{2}\\s*\\u20ac\/g);\r\n                        if(!ms || !ms.length) return null;\r\n                        var txt = ms[ms.length - 1].replace(\/\\s*\\u20ac\\s*$\/, '').trim();\r\n                        if(parseFloat(txt.replace(\/\\.\/g,'').replace(',','.')) > 0) return txt + ' \\u20ac';\r\n                    }\r\n                }catch(e){}\r\n                return null;\r\n            }\r\n\r\n            \/* ---- 1. tarjetas = botones ---- *\/\r\n            [[nv1,'Seguir con la estimaci\\u00f3n r\\u00e1pida'],[nv2,'Afinar con mis n\\u00fameros']].forEach(function(par){\r\n                var el = par[0];\r\n                el.setAttribute('role','button');\r\n                el.setAttribute('tabindex','0');\r\n                if(!el.querySelector('.nv-cta')){\r\n                    var cta = document.createElement('div');\r\n                    cta.className = 'nv-cta';\r\n                    cta.textContent = par[1];\r\n                    el.appendChild(cta);\r\n                }\r\n                el.addEventListener('keydown', function(ev){\r\n                    if(ev.key === 'Enter' || ev.key === ' ' || ev.key === 'Spacebar'){\r\n                        ev.preventDefault();\r\n                        el.click();\r\n                    }\r\n                });\r\n            });\r\n\r\n            \/* ---- B. la pega, detr\\u00e1s del balance ---- *\/\r\n            var grand  = px.querySelector('.grand');\r\n            var afinar = null, lista = null, titulo = null;\r\n            if(grand && grand.parentNode){\r\n                afinar = document.createElement('section');\r\n                afinar.id = 'prodex-afinar';\r\n                afinar.innerHTML =\r\n                    '<div class=\"af-k\">Antes de darla por buena<\/div>' +\r\n                    '<div class=\"af-t\"><\/div>' +\r\n                    '<ul class=\"af-l\"><\/ul>' +\r\n                    '<button type=\"button\" class=\"af-b\">Ajustar estos supuestos<\/button>' +\r\n                    '<div class=\"af-n\">Tarda unos minutos y puedes parar cuando quieras. ' +\r\n                    'Para lo fino te har\\u00e1 falta el recibo del IBI, la escritura de compra ' +\r\n                    'y el saldo de tu hipoteca.<\/div>';\r\n                grand.parentNode.insertBefore(afinar, grand.nextSibling);\r\n                titulo = afinar.querySelector('.af-t');\r\n                lista  = afinar.querySelector('.af-l');\r\n\r\n                afinar.querySelector('.af-b').addEventListener('click', function(){\r\n                    nv2.click();                                     \/* dispara el handler original + el nuestro *\/\r\n                    var primero = px.querySelector('.mod .lvl2');\r\n                    if(primero && primero.scrollIntoView){\r\n                        primero.scrollIntoView({behavior:'smooth', block:'center'});\r\n                    }\r\n                });\r\n\r\n                \/* vuelta atr\\u00e1s, para que plegar los niveles no deje a nadie encerrado *\/\r\n                var volver = document.createElement('button');\r\n                volver.type = 'button';\r\n                volver.id = 'prodex-volver';\r\n                volver.textContent = '\\u2190 Volver a la estimaci\\u00f3n r\\u00e1pida';\r\n                volver.addEventListener('click', function(){\r\n                    nv1.click();\r\n                    grand.scrollIntoView({behavior:'smooth', block:'center'});\r\n                });\r\n                grand.parentNode.insertBefore(volver, grand.nextSibling);\r\n            }\r\n\r\n            \/* Qu\\u00e9 ha dado por supuesto la calculadora para llegar a esa cifra.\r\n               El orden importa: primero lo que m\\u00e1s mueve el n\\u00famero. *\/\r\n            function supuestos(){\r\n                var s = [];\r\n                var exreinv = document.getElementById('v_exreinv');\r\n                if(exreinv && !exreinv.checked){\r\n                    var irpf = irpfEnPantalla();\r\n                    s.push(irpf\r\n                        ? 'Que no reinviertes en otra vivienda habitual: por eso resta ' + irpf + ' de IRPF. Si reinviertes, puedes no pagarlo.'\r\n                        : 'Que no reinviertes en otra vivienda habitual, as\\u00ed que paga el IRPF entero.');\r\n                }\r\n                if(vacio('v_hip'))        s.push('Que no te queda hipoteca por cancelar.');\r\n                var props = document.getElementById('v_props');\r\n                if(props && parseInt(props.value,10) === 1) s.push('Que eres el \\u00fanico propietario.');\r\n                if(vacio('v_comi'))       s.push('Que vendes sin comisi\\u00f3n de agencia.');\r\n                if(vacio('v_gastosded'))  s.push('Que no hubo gastos deducibles cuando comprasteis.');\r\n                if(vacio('v_catastro'))   s.push('Que no sabemos tu valor catastral, as\\u00ed que la plusval\\u00eda municipal va sin calcular.');\r\n                return s;\r\n            }\r\n\r\n            \/* ---- D. campo trampa para bots ----\r\n               ANTES AQU\\u00cd SE REESCRIB\\u00cdAN LOS TEXTOS DEL FORMULARIO desde JavaScript\r\n               \\u2014el titular, el subt\\u00edtulo y el bot\\u00f3n\\u2014 mientras el HTML dec\\u00eda otra\r\n               cosa. Resultado: lo que se le\\u00eda en pantalla no era lo que pon\\u00eda el\r\n               c\\u00f3digo, y cualquiera que revisara los textos revisaba los\r\n               equivocados. Los textos definitivos viven ahora en el HTML, arriba,\r\n               en un \\u00fanico sitio (10-ago-2026). *\/\r\n            var lead = px.querySelector('.lead');\r\n            if(lead){\r\n                \/* ---- campo trampa para bots ----\r\n                   Fuera de pantalla, NO display:none ni hidden (muchos bots los\r\n                   detectan). Un humano no lo ve ni lo tabula; un bot lo rellena.\r\n                   El servidor responde 201 falso y no crea nada. *\/\r\n                if(!document.getElementById('l_apellido2')){\r\n                    var trampa = document.createElement('input');\r\n                    trampa.type = 'text';\r\n                    trampa.id = 'l_apellido2';\r\n                    trampa.name = 'apellido2';\r\n                    trampa.tabIndex = -1;\r\n                    trampa.autocomplete = 'off';\r\n                    trampa.setAttribute('aria-hidden', 'true');\r\n                    trampa.style.cssText = 'position:absolute;left:-9999px;width:1px;height:1px;opacity:0';\r\n                    lead.appendChild(trampa);\r\n                }\r\n            }\r\n\r\n            \/* El bloque original arma su propio payload, as\\u00ed que el campo trampa se\r\n               a\\u00f1ade al vuelo, y s\\u00f3lo en la petici\\u00f3n de alta. *\/\r\n            var fetchOriginal = window.fetch;\r\n            window.fetch = function(url, opts){\r\n                try{\r\n                    if(typeof url === 'string'\r\n                       && url.indexOf('\/api\/leads\/calculadora') > -1\r\n                       && url.indexOf('\/evento') < 0\r\n                       && url.indexOf('\/calculo') < 0\r\n                       && opts && typeof opts.body === 'string'){\r\n                        var t = document.getElementById('l_apellido2');\r\n                        var d = JSON.parse(opts.body);\r\n                        d.apellido2 = t ? t.value : '';\r\n                        opts = Object.assign({}, opts, { body: JSON.stringify(d) });\r\n                    }\r\n                }catch(e){}\r\n                return fetchOriginal.call(this, url, opts);\r\n            };\r\n\r\n            \/* ---- estado: qu\\u00e9 se ve en cada momento ---- *\/\r\n            function sincroniza(){\r\n                var basico = px.classList.contains('nivel-basico');\r\n\r\n                nv1.setAttribute('aria-pressed', basico ? 'true'  : 'false');\r\n                nv2.setAttribute('aria-pressed', basico ? 'false' : 'true');\r\n\r\n                \/* el candado s\\u00f3lo cuando hay algo debajo que ense\\u00f1ar *\/\r\n                if(lockV) lockV.classList.toggle('sin-cifra', !conValor(vVenta));\r\n                if(lockC) lockC.classList.toggle('sin-cifra', !conValor(cCompra));\r\n\r\n                \/* Candado de las CIFRAS: nivel 1 siempre abierto (es el anzuelo,\r\n                   no se toca). Nivel 2 tapado hasta dejar datos, salvo que ya se\r\n                   hayan dejado alguna vez en esta sesi\\u00f3n (dataset.desbloqueado).\r\n                   'sin-cifra' evita ense\\u00f1ar el candado sobre un '\\u2014 \\u20ac' vac\\u00edo:\r\n                   se detecta leyendo el propio texto en pantalla, no releyendo\r\n                   los campos, porque as\\u00ed vale igual para los tres (venta, compra\r\n                   y balance, que depende de las dos mitades a la vez). *\/\r\n                var yaDesbloqueado = px.dataset.desbloqueado === '1';\r\n                \/* DESDE EL 30-ago SE TAPA SIEMPRE que hay cifra: el resultado no\r\n                   se ensena en pantalla en ningun nivel \\u2014 llega por WhatsApp. El\r\n                   candado deja de ser un peaje y pasa a ser la estetica de \\u00abesto\r\n                   esta reservado\\u00bb, con su CTA llevando al bloque WhatsApp.\r\n                   (yaDesbloqueado queda sin uso a proposito: showOk() ya no es\r\n                   alcanzable sin formulario.) *\/\r\n                var tapar = true;\r\n                function tieneCifra(id){\r\n                    var t = document.getElementById(id);\r\n                    return !!(t && t.textContent && t.textContent.indexOf('\\u2014') === -1);\r\n                }\r\n                \/* Alto real del candado, MEDIDO, no adivinado (06-ago, tras un\r\n                   fallo real: un min-height fijo en px se quedaba corto en\r\n                   m\\u00f3vil estrecho -320px- porque el texto del aviso pasa a m\\u00e1s\r\n                   l\\u00edneas, y ese mismo n\\u00famero sobraba en escritorio. Un p\\u00edxel\r\n                   fijo es una apuesta sobre el ancho de pantalla; esto no\r\n                   apuesta, mide: icono+t\\u00edtulo+subt\\u00edtulo+bot\\u00f3n tal y como han\r\n                   quedado colocados de verdad, con el ancho real del momento,\r\n                   y reserva exactamente eso. Vuelve a medir en cada sincroniza\r\n                   (se llama en cada input\/change y en cada cambio de nivel),\r\n                   as\\u00ed que un giro de m\\u00f3vil o un cambio de fuente no lo rompe. *\/\r\n                function ajustarAlto(wrap){\r\n                    if(!wrap) return;\r\n                    var over = wrap.querySelector('.lockover');\r\n                    if(!over) return;\r\n                    \/* .lockover centra su contenido en vertical (justify-content:\r\n                       center). Mientras la caja sigue corta, ese centrado reparte\r\n                       el contenido mitad por encima y mitad por debajo del borde\r\n                       \\u2014 medir \"de arriba de la caja a abajo del bot\\u00f3n\" solo\r\n                       capturaba la mitad de abajo y se quedaba corto (bug real,\r\n                       06-ago: 136px medidos, 157-189px necesarios de verdad).\r\n                       Se saca el overlay del position:absolute un instante para\r\n                       leer su alto NATURAL en flujo normal, sin el centrado\r\n                       falseando la medida, y se devuelve tal y como estaba. *\/\r\n                    var prevPos = over.style.position;\r\n                    over.style.position = 'static';\r\n                    var alto = over.getBoundingClientRect().height;\r\n                    over.style.position = prevPos;\r\n                    var necesario = Math.ceil(alto) + 12; \/* margen de respiro *\/\r\n                    if(necesario > 0) wrap.style.minHeight = necesario + 'px';\r\n                }\r\n                \/* Nivel 1 (o ya desbloqueado) no tapa nada: el min-height que\r\n                   dejo puesto abajo se quita, si no, al volver de nivel 2 el\r\n                   n\\u00famero se ve suelto dentro de un hueco vac\\u00edo que ya no\r\n                   protege nada \\u2014 abierto tiene que verse tan compacto como\r\n                   antes de tocar nivel 2. *\/\r\n                if(lockVTotal){\r\n                    var vConCifra = tieneCifra('v_total');\r\n                    lockVTotal.classList.toggle('locked', tapar);\r\n                    lockVTotal.classList.toggle('sin-cifra', !vConCifra);\r\n                    if(tapar && vConCifra) ajustarAlto(lockVTotal); else lockVTotal.style.minHeight = '';\r\n                }\r\n                if(lockCTotal){\r\n                    var cConCifra = tieneCifra('c_total');\r\n                    lockCTotal.classList.toggle('locked', tapar);\r\n                    lockCTotal.classList.toggle('sin-cifra', !cConCifra);\r\n                    if(tapar && cConCifra) ajustarAlto(lockCTotal); else lockCTotal.style.minHeight = '';\r\n                }\r\n                if(lockBalance){\r\n                    var bConCifra = tieneCifra('g_saldo');\r\n                    lockBalance.classList.toggle('locked', tapar);\r\n                    lockBalance.classList.toggle('sin-cifra', !bConCifra);\r\n                    if(tapar && bConCifra) ajustarAlto(lockBalance); else lockBalance.style.minHeight = '';\r\n                }\r\n\r\n                var volver = document.getElementById('prodex-volver');\r\n                if(volver) volver.style.display = basico ? 'none' : 'block';\r\n\r\n                if(!afinar) return;\r\n\r\n                var s = supuestos();\r\n                \/* sin cifra, o ya en nivel 2, o sin nada que objetar: no se ense\\u00f1a *\/\r\n                if(!basico || !conValor(vVenta) || !s.length){\r\n                    afinar.style.display = 'none';\r\n                    return;\r\n                }\r\n                titulo.textContent = s.length === 1\r\n                    ? 'Esta cifra da por hecha una cosa que quiz\\u00e1 no sea la tuya:'\r\n                    : 'Para llegar a esta cifra hemos dado por hechas ' + s.length + ' cosas:';\r\n                lista.innerHTML = '';\r\n                s.forEach(function(t){\r\n                    var li = document.createElement('li');\r\n                    li.textContent = t;\r\n                    lista.appendChild(li);\r\n                });\r\n                afinar.style.display = 'block';\r\n            }\r\n\r\n            \/* ---- 2. cambiar de nivel recalcula (y por tanto se cuenta en el ERP) ---- *\/\r\n            function trasCambiarNivel(){ recalcula(); sincroniza(); }\r\n            nv1.addEventListener('click', trasCambiarNivel);         \/* van despu\\u00e9s de los originales *\/\r\n            nv2.addEventListener('click', trasCambiarNivel);\r\n\r\n            \/* ---- 3. el desplegable de residencia del propietario no recalculaba ---- *\/\r\n            var ownersbox = document.getElementById('v_ownersbox');\r\n            if(ownersbox){\r\n                ownersbox.addEventListener('change', function(ev){\r\n                    if(ev.target && ev.target.tagName === 'SELECT') recalcula();\r\n                });\r\n            }\r\n\r\n            px.addEventListener('input',  function(ev){ latidoEmpieza(); sincroniza(); });\r\n            px.addEventListener('change', function(ev){ latidoEmpieza(); sincroniza(); });\r\n            sincroniza();\r\n\r\n            \/* La pagina usa Google Fonts: en la primera pasada el candado se\r\n               mide con la fuente de reserva (mas compacta) porque la de verdad\r\n               puede llegar despues. document.fonts.ready se resuelve cuando ya\r\n               ha cargado todo, y ahi se vuelve a medir \\u2014 si no, en una conexion\r\n               lenta el candado se queda corto de alto con la fuente definitiva\r\n               y vuelve el mismo problema que el pixel fijo, solo que mas raro\r\n               de reproducir (no falla siempre, solo la primera vez que carga\r\n               la fuente tarde). *\/\r\n            if(document.fonts && document.fonts.ready){\r\n                document.fonts.ready.then(function(){ sincroniza(); }).catch(function(){});\r\n            }\r\n        }\r\n\r\n        if(document.readyState === 'loading'){\r\n            document.addEventListener('DOMContentLoaded', arranca);\r\n        }else{\r\n            arranca();\r\n        }\r\n    })();\r\n\r\n<\/script>\r\n\r\n<!-- PRODEX calculadora - fin del bloque - VERSION 2026-08-30 - 66837546076d -->\r\n\n","protected":false},"excerpt":{"rendered":"Gesti&#243;n y Representaci&#243;n Patrimonial&#191;Cu&#225;nto te queda de verdad al cambiar de vivienda?Calcula en un minuto la liquidez real de tu [...]","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"site-sidebar-layout":"no-sidebar","site-content-layout":"","ast-site-content-layout":"full-width-container","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"disabled","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"disabled","site-post-title":"disabled","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"disabled","ast-disable-related-posts":"","theme-transparent-header-meta":"default","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"set","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"_themeisle_gutenberg_block_has_review":false,"footnotes":""},"class_list":["post-3091","page","type-page","status-publish","hentry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.8 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Calculadora de reinversi\u00f3n - PRODEX<\/title>\n<meta name=\"description\" content=\"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/grupoprodex.com\/en\/calculadora-de-reinversion\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Calculadora de reinversi\u00f3n - PRODEX\" \/>\n<meta property=\"og:description\" content=\"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/grupoprodex.com\/en\/calculadora-de-reinversion\/\" \/>\n<meta property=\"og:site_name\" content=\"PRODEX\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-30T19:51:07+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/grupoprodex.com\/wp-content\/uploads\/2026\/08\/og-image-web.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/calculadora-de-reinversion\\\/\",\"url\":\"https:\\\/\\\/grupoprodex.com\\\/calculadora-de-reinversion\\\/\",\"name\":\"Calculadora de reinversi\u00f3n - PRODEX\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#website\"},\"datePublished\":\"2026-07-04T20:29:53+00:00\",\"dateModified\":\"2026-08-30T19:51:07+00:00\",\"description\":\"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/calculadora-de-reinversion\\\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/grupoprodex.com\\\/calculadora-de-reinversion\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/calculadora-de-reinversion\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Portada\",\"item\":\"https:\\\/\\\/grupoprodex.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Calculadora de reinversi\u00f3n\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#website\",\"url\":\"https:\\\/\\\/grupoprodex.com\\\/\",\"name\":\"PRODEX\",\"description\":\"Verificaci\u00f3n y direcci\u00f3n de compraventas en M\u00e1laga y el Valle del Guadalhorce\",\"publisher\":{\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/grupoprodex.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":[\"Organization\",\"ProfessionalService\"],\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#organization\",\"name\":\"PRODEX\",\"alternateName\":\"Grupo Prodex\",\"url\":\"https:\\\/\\\/grupoprodex.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/grupoprodex.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/og-image-web.png\",\"contentUrl\":\"https:\\\/\\\/grupoprodex.com\\\/wp-content\\\/uploads\\\/2026\\\/08\\\/og-image-web.png\",\"width\":1200,\"height\":630,\"caption\":\"PRODEX\"},\"image\":{\"@id\":\"https:\\\/\\\/grupoprodex.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"legalName\":\"GRUPODEX MANAGEMENT & SERVICES, S.L.\",\"description\":\"PRODEX, una empresa de representaci\u00f3n patrimonial en M\u00e1laga.\",\"telephone\":\"+34951199132\",\"address\":{\"@type\":\"PostalAddress\",\"streetAddress\":\"Calle Palestina 1, Oficina E\",\"postalCode\":\"29007\",\"addressLocality\":\"M\u00e1laga\",\"addressRegion\":\"M\u00e1laga\",\"addressCountry\":\"ES\"},\"areaServed\":[{\"@type\":\"City\",\"name\":\"M\u00e1laga\"},{\"@type\":\"City\",\"name\":\"Torremolinos\"},{\"@type\":\"City\",\"name\":\"Benalm\u00e1dena\"},{\"@type\":\"City\",\"name\":\"Fuengirola\"},{\"@type\":\"City\",\"name\":\"Alhaur\u00edn de la Torre\"},{\"@type\":\"City\",\"name\":\"Alhaur\u00edn el Grande\"},{\"@type\":\"City\",\"name\":\"Almog\u00eda\"},{\"@type\":\"City\",\"name\":\"\u00c1lora\"},{\"@type\":\"City\",\"name\":\"C\u00e1rtama\"},{\"@type\":\"City\",\"name\":\"Co\u00edn\"},{\"@type\":\"City\",\"name\":\"Pizarra\"},{\"@type\":\"City\",\"name\":\"Valle de Abdalaj\u00eds\"}]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Calculadora de reinversi\u00f3n - PRODEX","description":"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/grupoprodex.com\/en\/calculadora-de-reinversion\/","og_locale":"en_GB","og_type":"article","og_title":"Calculadora de reinversi\u00f3n - PRODEX","og_description":"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.","og_url":"https:\/\/grupoprodex.com\/en\/calculadora-de-reinversion\/","og_site_name":"PRODEX","article_modified_time":"2026-08-30T19:51:07+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/grupoprodex.com\/wp-content\/uploads\/2026\/08\/og-image-web.png","type":"image\/png"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/grupoprodex.com\/calculadora-de-reinversion\/","url":"https:\/\/grupoprodex.com\/calculadora-de-reinversion\/","name":"Calculadora de reinversi\u00f3n - PRODEX","isPartOf":{"@id":"https:\/\/grupoprodex.com\/#website"},"datePublished":"2026-07-04T20:29:53+00:00","dateModified":"2026-08-30T19:51:07+00:00","description":"Calcula cu\u00e1nto te queda de verdad al vender tu casa: impuestos, plusval\u00eda municipal, hipoteca y gastos. La cifra en pantalla, con tus n\u00fameros.","breadcrumb":{"@id":"https:\/\/grupoprodex.com\/calculadora-de-reinversion\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/grupoprodex.com\/calculadora-de-reinversion\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/grupoprodex.com\/calculadora-de-reinversion\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Portada","item":"https:\/\/grupoprodex.com\/"},{"@type":"ListItem","position":2,"name":"Calculadora de reinversi\u00f3n"}]},{"@type":"WebSite","@id":"https:\/\/grupoprodex.com\/#website","url":"https:\/\/grupoprodex.com\/","name":"PRODEX","description":"Verificaci\u00f3n y direcci\u00f3n de compraventas en M\u00e1laga y el Valle del Guadalhorce","publisher":{"@id":"https:\/\/grupoprodex.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/grupoprodex.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":["Organization","ProfessionalService"],"@id":"https:\/\/grupoprodex.com\/#organization","name":"PRODEX","alternateName":"Grupo Prodex","url":"https:\/\/grupoprodex.com\/","logo":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/grupoprodex.com\/#\/schema\/logo\/image\/","url":"https:\/\/grupoprodex.com\/wp-content\/uploads\/2026\/08\/og-image-web.png","contentUrl":"https:\/\/grupoprodex.com\/wp-content\/uploads\/2026\/08\/og-image-web.png","width":1200,"height":630,"caption":"PRODEX"},"image":{"@id":"https:\/\/grupoprodex.com\/#\/schema\/logo\/image\/"},"legalName":"GRUPODEX MANAGEMENT & SERVICES, S.L.","description":"PRODEX, una empresa de representaci\u00f3n patrimonial en M\u00e1laga.","telephone":"+34951199132","address":{"@type":"PostalAddress","streetAddress":"Calle Palestina 1, Oficina E","postalCode":"29007","addressLocality":"M\u00e1laga","addressRegion":"M\u00e1laga","addressCountry":"ES"},"areaServed":[{"@type":"City","name":"M\u00e1laga"},{"@type":"City","name":"Torremolinos"},{"@type":"City","name":"Benalm\u00e1dena"},{"@type":"City","name":"Fuengirola"},{"@type":"City","name":"Alhaur\u00edn de la Torre"},{"@type":"City","name":"Alhaur\u00edn el Grande"},{"@type":"City","name":"Almog\u00eda"},{"@type":"City","name":"\u00c1lora"},{"@type":"City","name":"C\u00e1rtama"},{"@type":"City","name":"Co\u00edn"},{"@type":"City","name":"Pizarra"},{"@type":"City","name":"Valle de Abdalaj\u00eds"}]}]}},"_links":{"self":[{"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/pages\/3091","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/comments?post=3091"}],"version-history":[{"count":1,"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/pages\/3091\/revisions"}],"predecessor-version":[{"id":3093,"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/pages\/3091\/revisions\/3093"}],"wp:attachment":[{"href":"https:\/\/grupoprodex.com\/en\/wp-json\/wp\/v2\/media?parent=3091"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}