{"id":11077,"date":"2026-06-22T09:27:01","date_gmt":"2026-06-22T07:27:01","guid":{"rendered":"https:\/\/vincesconsulting.com\/fren\/"},"modified":"2026-06-24T10:50:14","modified_gmt":"2026-06-24T08:50:14","slug":"fren","status":"publish","type":"page","link":"https:\/\/vincesconsulting.com\/en\/technology\/fren\/","title":{"rendered":"Fren"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"11077\" class=\"elementor elementor-11077 elementor-11011\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-fb775a4 e-con-full e-flex e-con e-parent\" data-id=\"fb775a4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5d28f2b elementor-widget elementor-widget-html\" data-id=\"5d28f2b\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n\n<!--\n  Dashboard Vinces \u00b7 FREN \u00b7 p\u00e1gina standalone (versi\u00f3n redise\u00f1ada para WP)\n  Endpoint API: https:\/\/vision360.vincesconsulting.com\/api\/dashboard-publico\/fren\/\n  Refresco cliente: 30 min \u00b7 Cach\u00e9 backend: 1h.\n-->\n<html lang=\"en\"><head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n<title>FREN: Diario de Sesiones, Congress votes and question of the day \u00b7 AI regulatory intelligence \u00b7 Vinces<\/title>\n<meta name=\"description\" content=\"FREN: asistente IA para Public Affairs en Espa\u00f1a. 5 m\u00f3dulos (Leyes, Decisores, Votaciones, Diarios de Sesiones, Actualidad), 15 d\u00edas gratis, RGPD UE. Consulta el \u00faltimo Diario de Sesiones del Congreso, el hemiciclo de la \u00faltima votaci\u00f3n del Pleno y la pregunta del d\u00eda respondida con IA y fuentes oficiales.\">\n<meta name=\"keywords\" content=\"FREN, diario de sesiones, \u00faltimo diario de sesiones, Congreso, Senado, parlamentos auton\u00f3micos, hemiciclo, votaci\u00f3n del pleno, voto del diputado, asistente regulatorio IA, asistente IA pol\u00edtica, Vinces consulting\">\n<meta name=\"author\" content=\"Vinces Consulting\">\n<meta name=\"robots\" content=\"index,follow,max-image-preview:large,max-snippet:-1\">\n<link rel=\"canonical\" href=\"https:\/\/fren.democrata.es\/\">\n\n<meta property=\"og:type\" content=\"website\">\n<meta property=\"og:site_name\" content=\"Vinces Consulting\">\n<meta property=\"og:title\" content=\"FREN \u00b7 Asistente IA Regulatorio \u00b7 Vinces\">\n<meta property=\"og:description\" content=\"Asistente IA para Public Affairs en Espa\u00f1a. 5 m\u00f3dulos (Leyes, Decisores, Votaciones, Diarios de Sesiones, Actualidad). 15 d\u00edas gratis, sin tarjeta. RGPD \u00b7 UE.\">\n<meta property=\"og:locale\" content=\"en_GB\">\n<meta property=\"og:url\" content=\"https:\/\/fren.democrata.es\/\">\n\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link rel=\"stylesheet\" href=\"https:\/\/fonts.googleapis.com\/css2?family=Libre+Franklin:wght@400;500;600;700;800&family=Lato:wght@400;700&display=swap\">\n<script defer src=\"https:\/\/cdn.jsdelivr.net\/npm\/chart.js@4.4.1\/dist\/chart.umd.min.js\"><\/script>\n\n<style>\n\/* =========================================================\n   Vinces \u00b7 Dashboards p\u00fablicos \u2014 capa visual redesign\n   Mantiene todas las clases del HTML\/JS original e\n   incorpora la identidad de vincesconsulting.com (hero\n   navy, kicker naranja, tipograf\u00eda display, CTAs p\u00edldora).\n   ========================================================= *\/\n:root{\n  --navy:#1F2547;\n  --navy-deep:#161A36;\n  --navy-light:#2A3158;\n  --navy-soft:#E9ECF3;\n  --orange:#F3A53A;\n  --orange-soft:#FFF1DA;\n  --green:#1FA886;\n  --bg:#EEF1F3;\n  --bg-alt:#F6F8FA;\n  --card:#FFFFFF;\n  --ink:#1F2547;\n  --ink-soft:#5A6076;\n  --muted:#8A90A3;\n  --line:#E2E6EE;\n  --line-soft:#EDF0F5;\n  --good:#1FA886;\n  --warn:#E5A040;\n  --bad:#C0392B;\n\n  \/* Aliases para mantener compatibilidad con el CSS original *\/\n  --vinces-primary:var(--navy);\n  --vinces-secondary:var(--orange);\n  --vinces-dark:var(--navy-deep);\n  --panel:var(--card);\n  --panel-soft:var(--bg-alt);\n  --brand:var(--navy);\n  --brand-soft:var(--navy-soft);\n  --brand-light:var(--navy-light);\n  --accent:var(--orange);\n  --accent-soft:var(--orange-soft);\n  --accent-dark:#D88E25;\n\n  --radius:18px;\n  --radius-sm:10px;\n  --radius-pill:999px;\n  --shadow:0 1px 2px rgba(31,37,71,.04), 0 8px 24px rgba(31,37,71,.06);\n  --shadow-lg:0 4px 12px rgba(31,37,71,.06), 0 24px 60px rgba(31,37,71,.12);\n\n  --hm-0:#F1F3F7;--hm-1:#DDE2EE;--hm-2:#B8C2D9;\n  --hm-3:#7889B0;--hm-4:#42507A;--hm-5:#1F2547;\n\n  --font-display:\"Libre Franklin\",-apple-system,BlinkMacSystemFont,sans-serif;\n  --font-body:\"Lato\",\"Libre Franklin\",-apple-system,BlinkMacSystemFont,sans-serif;\n}\n\n*{box-sizing:border-box}\nhtml,body{margin:0;padding:0}\nbody{\n  font-family:var(--font-body);\n  background:var(--bg);\n  color:var(--ink);\n  line-height:1.65;\n  -webkit-font-smoothing:antialiased;\n  text-rendering:optimizeLegibility;\n}\n\n\/* =========================================================\n   HERO full-bleed (estilo Vinces)\n   ========================================================= *\/\n.vc-hero{\n  position:relative;\n  background:var(--navy);\n  color:#fff;\n  padding:96px 24px 88px;\n  overflow:hidden;\n}\n.vc-hero::before{\n  content:\"\";\n  position:absolute;inset:0;\n  background:\n    radial-gradient(circle at 88% 18%, rgba(243,165,58,.10), transparent 45%),\n    radial-gradient(circle at 8% 95%, rgba(31,168,134,.07), transparent 50%);\n  pointer-events:none;\n}\n.vc-hero::after{\n  content:\"\";\n  position:absolute;left:0;right:0;bottom:0;height:1px;\n  background:linear-gradient(90deg, transparent, rgba(243,165,58,.45), transparent);\n}\n.vc-hero-inner{\n  position:relative;\n  max-width:1240px;margin:0 auto;\n  display:grid;grid-template-columns:1fr;gap:28px;\n}\n@media(min-width:880px){\n  .vc-hero-inner{grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:48px}\n}\n.vc-kicker{\n  display:inline-block;\n  color:var(--orange);\n  font-family:var(--font-display);\n  font-weight:700;font-size:.78rem;\n  letter-spacing:.18em;text-transform:uppercase;\n  margin:0 0 22px;line-height:1.2;\n}\n.vc-kicker::before{content:\"\/\/ \"}\n.vc-hero h1{\n  font-family:var(--font-display);\n  font-size:clamp(2.4rem,5.6vw,4.8rem);\n  font-weight:800;\n  letter-spacing:-.035em;\n  line-height:.98;\n  margin:0 0 24px;\n  color:#fff;\n  text-wrap:balance;\n}\n.vc-hero-lead{\n  font-size:clamp(1rem,1.25vw,1.15rem);\n  color:rgba(255,255,255,.78);\n  max-width:680px;\n  line-height:1.55;\n  margin:0 0 28px;\n}\n.vc-hero-lead strong{color:#fff;font-weight:600}\n.vc-hero-meta{\n  display:inline-flex;align-items:center;gap:10px;\n  background:rgba(255,255,255,.05);\n  border:1px solid rgba(255,255,255,.14);\n  padding:9px 18px;\n  border-radius:999px;\n  font-size:.8rem;\n  color:rgba(255,255,255,.85);\n  backdrop-filter:blur(6px);\n}\n.vc-hero-meta .dot{\n  width:8px;height:8px;border-radius:50%;\n  background:#4ADE80;\n  box-shadow:0 0 0 4px rgba(74,222,128,.18);\n}\n.vc-hero-meta.err .dot{background:#FCA5A5;box-shadow:0 0 0 4px rgba(252,165,165,.18)}\n.vc-hero-meta.err{color:#FCA5A5;border-color:rgba(252,165,165,.25)}\n\n.vc-hero-cta{\n  display:inline-flex;align-items:center;gap:14px;\n  background:#fff;color:var(--navy);\n  padding:14px 16px 14px 26px;\n  border-radius:999px;\n  text-decoration:none;\n  font-family:var(--font-display);\n  font-weight:700;font-size:1.45rem;\n  transition:transform .15s ease, box-shadow .2s ease;\n  align-self:start;\n}\n.vc-hero-cta:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(0,0,0,.25)}\n.vc-hero-cta .ico{\n  width:30px;height:30px;border-radius:50%;\n  background:var(--navy);color:#fff;\n  display:inline-flex;align-items:center;justify-content:center;\n  font-size:1.45rem;font-weight:400;\n  transition:transform .2s ease;\n}\n.vc-hero-cta:hover .ico{transform:rotate(-12deg)}\n\n\/* =========================================================\n   Layout principal (sustituye al .wrap inline)\n   ========================================================= *\/\n.wrap{max-width:1240px;margin:0 auto;padding:56px 24px 96px}\n@media(max-width:768px){.wrap{padding:40px 18px 64px}}\n\n\/* Cabecera vieja: la ocultamos porque ahora vive en el hero *\/\nheader.page{display:none}\n\nnoscript .nojs{\n  background:#FFF7ED;border:1px solid #FED7AA;color:#9A3412;\n  padding:16px 20px;border-radius:var(--radius-sm);margin-bottom:24px;font-size:.9rem;\n}\n\n\/* =========================================================\n   PRODUCT SECTION \u2014 card grande con dos columnas\n   ========================================================= *\/\n.product-section{\n  margin:0 0 40px;\n  background:var(--card);\n  border:1px solid var(--line);\n  border-radius:var(--radius);\n  box-shadow:var(--shadow);\n  overflow:hidden;\n  position:relative;\n}\n.product-section::before{\n  content:\"\";position:absolute;top:0;left:0;\n  width:160px;height:4px;\n  background:linear-gradient(90deg,var(--navy) 0%,var(--orange) 100%);\n}\n.product-grid{display:grid;grid-template-columns:1fr;gap:0}\n@media(min-width:1020px){\n  .product-grid{grid-template-columns:360px 1fr}\n  .product-grid.reverse{grid-template-columns:1fr 360px}\n}\n.product-info{\n  padding:48px 38px;\n  background:linear-gradient(180deg,#FAFBFC 0%, #F0F3F7 100%);\n  border-right:1px solid var(--line);\n  display:flex;flex-direction:column;gap:18px;\n}\n.product-grid.reverse .product-info{\n  border-right:0;border-left:1px solid var(--line);order:2;\n}\n@media(max-width:1019px){\n  .product-info{padding:32px 24px;border-right:0;border-left:0;border-bottom:1px solid var(--line)}\n  .product-grid.reverse .product-info{order:0;border-left:0;border-bottom:1px solid var(--line)}\n}\n\n.product-badge{\n  display:inline-block;\n  color:var(--orange);\n  font-family:var(--font-display);\n  font-weight:700;font-size:.72rem;\n  letter-spacing:.16em;text-transform:uppercase;\n  background:transparent;padding:0;border:0;\n  align-self:flex-start;line-height:1.2;\n}\n.product-badge::before{content:\"\/\/ \"}\n.product-badge.brand{color:var(--navy)}\n\n.product-name{\n  font-family:var(--font-display);\n  font-size:clamp(1.7rem,2.4vw,2.2rem);\n  font-weight:800;color:var(--navy);\n  margin:0;letter-spacing:-.025em;line-height:1.05;\n  text-wrap:balance;\n}\n.product-tagline{\n  font-size:1rem;color:var(--ink-soft);\n  margin:0;line-height:1.7;\n}\n.product-tagline strong{color:var(--ink);font-weight:600}\n\n.product-content{padding:42px 38px;min-width:0}\n@media(max-width:1019px){.product-content{padding:30px 24px}}\n.product-content-header{\n  display:flex;align-items:flex-start;justify-content:space-between;\n  gap:16px;flex-wrap:wrap;margin-bottom:22px;\n}\n.product-content-header h3{\n  margin:0;\n  font-family:var(--font-display);\n  font-size:clamp(1.25rem,1.6vw,1.55rem);\n  font-weight:700;color:var(--navy);\n  letter-spacing:-.02em;line-height:1.2;\n}\n.product-content-header .sub{\n  font-size:1.45rem;color:var(--ink-soft);\n  margin:8px 0 0;line-height:1.55;max-width:62ch;\n}\n\n\/* =========================================================\n   CTA p\u00edldora \u2014 el patr\u00f3n Vinces \"Contacta con nosotros \u2197\"\n   ========================================================= *\/\n.pane-cta,\n.product-cta,\n.dl-btn{\n  display:inline-flex;align-items:center;gap:12px;\n  background:var(--navy);color:#fff;\n  padding:13px 18px 13px 26px;\n  border-radius:999px;\n  text-decoration:none;\n  font-family:var(--font-display);\n  font-weight:700;font-size:.88rem;\n  transition:transform .15s ease, box-shadow .2s ease, background .2s ease;\n  border:0;cursor:pointer;line-height:1;\n  white-space:nowrap;\n}\n.pane-cta::after,\n.product-cta::after{content:none}\n.pane-cta::before,\n.product-cta::before,\n.dl-btn::before{\n  order:2;\n  content:\"\\2197\";\n  width:26px;height:26px;border-radius:50%;\n  background:rgba(255,255,255,.16);\n  display:inline-flex;align-items:center;justify-content:center;\n  font-size:1.25rem;font-weight:500;\n  transition:transform .25s ease;\n}\n.pane-cta:hover,\n.product-cta:hover,\n.dl-btn:hover{\n  background:var(--navy-light);\n  transform:translateY(-2px);\n  box-shadow:0 10px 24px rgba(31,37,71,.18);\n  color:#fff;\n}\n.pane-cta:hover::before,\n.product-cta:hover::before{transform:translate(2px,-2px) rotate(-8deg)}\n\n.pane-cta-row{\n  margin-top:28px;padding:22px 24px;\n  background:var(--bg-alt);\n  border:1px solid var(--line-soft);\n  border-radius:var(--radius-sm);\n  display:flex;flex-wrap:wrap;align-items:center;gap:18px;\n}\n.pane-cta-row .pane-cta-text{\n  flex:1;min-width:240px;\n  font-size:1.45rem;color:var(--ink-soft);\n  margin:0;line-height:1.6;\n}\n.pane-cta-row .pane-cta-text strong{color:var(--ink);font-weight:700}\n.content-cta-row{\n  margin-top:32px;padding:24px 26px;\n  background:linear-gradient(135deg, var(--navy) 0%, var(--navy-deep) 100%);\n  color:#fff;\n  border-radius:var(--radius-sm);\n  display:flex;flex-wrap:wrap;align-items:center;gap:20px;\n}\n.content-cta-row .pane-cta-text{\n  flex:1;min-width:240px;\n  font-size:1.45rem;color:rgba(255,255,255,.78);\n  margin:0;line-height:1.6;\n}\n.content-cta-row .pane-cta-text strong{color:#fff;font-weight:700}\n.content-cta-row .pane-cta{\n  background:#fff;color:var(--navy);\n}\n.content-cta-row .pane-cta::before{background:var(--navy);color:#fff}\n.content-cta-row .pane-cta:hover{background:var(--orange);color:var(--navy)}\n.content-cta-row .pane-cta:hover::before{background:var(--navy);color:#fff}\n\n\/* Updated pill compatible con c\u00f3digo original (queda oculto en hero, pero el JS a\u00fan escribe en \u00e9l) *\/\n.updated{\n  font-size:.8rem;color:var(--ink-soft);\n  background:var(--card);border:1px solid var(--line);\n  padding:8px 16px;border-radius:999px;\n  display:inline-flex;align-items:center;gap:8px;\n}\n.updated .dot{width:8px;height:8px;border-radius:50%;background:var(--good);box-shadow:0 0 0 4px rgba(31,168,134,.15)}\n.updated.err .dot{background:var(--bad);box-shadow:0 0 0 4px rgba(192,57,43,.15)}\n.updated.err{color:var(--bad);border-color:#FECACA}\n\n\/* =========================================================\n   Carrusel de tabs (PolicyAlert)\n   ========================================================= *\/\n.car-tabs{\n  display:flex;gap:6px;margin:-8px 0 28px;\n  padding:6px;background:var(--bg-alt);\n  border:1px solid var(--line-soft);border-radius:999px;\n  flex-wrap:wrap;width:fit-content;max-width:100%;\n}\n.car-tab{\n  background:transparent;border:0;\n  padding:10px 20px;font-weight:600;font-size:1.25rem;\n  color:var(--ink-soft);cursor:pointer;\n  font-family:var(--font-display);\n  border-radius:999px;\n  transition:all .15s ease;\n  line-height:1.2;\n}\n.car-tab:hover{color:var(--navy)}\n.car-tab.active{\n  background:var(--navy);color:#fff;\n  box-shadow:0 2px 8px rgba(31,37,71,.18);\n}\n.car-pane{display:none}\n.car-pane.active{display:block;animation:vc-fade .35s cubic-bezier(.16,1,.3,1)}\n@keyframes vc-fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}\n\n\/* =========================================================\n   Nombramientos (Poder Digital)\n   ========================================================= *\/\n.nomb-list{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:12px}\n.nomb-list-grid{display:grid;grid-template-columns:1fr;gap:14px}\n@media(min-width:780px){.nomb-list-grid{grid-template-columns:1fr 1fr}}\n.nomb-item{\n  display:flex;align-items:flex-start;gap:16px;\n  padding:22px;background:#fff;\n  border:1px solid var(--line);border-radius:var(--radius-sm);\n  transition:all .18s ease;\n  position:relative;\n}\n.nomb-item::before{\n  content:\"\";position:absolute;left:0;top:22px;bottom:22px;width:3px;\n  background:var(--orange);border-radius:3px;opacity:0;transition:opacity .18s ease;\n}\n.nomb-item:hover{\n  border-color:var(--navy);\n  box-shadow:0 8px 24px rgba(31,37,71,.08);\n  transform:translateY(-2px);\n}\n.nomb-item:hover::before{opacity:1}\n.nomb-item .badge{\n  flex:0 0 auto;width:48px;height:48px;border-radius:50%;\n  background:linear-gradient(135deg, var(--navy) 0%, var(--navy-light) 100%);\n  color:#fff;\n  display:flex;align-items:center;justify-content:center;\n  font-weight:700;font-size:.9rem;\n  font-family:var(--font-display);\n  box-shadow:0 4px 10px rgba(31,37,71,.18);\n}\n.nomb-item .body{flex:1;min-width:0}\n.nomb-item .name{\n  font-family:var(--font-display);\n  font-weight:700;font-size:1rem;\n  margin:0 0 6px;color:var(--navy);line-height:1.25;\n  letter-spacing:-.01em;\n}\n.nomb-item .cargo{font-size:.88rem;color:var(--ink-soft);margin:0;line-height:1.5}\n.nomb-item time{\n  font-size:.72rem;color:var(--muted);\n  display:inline-block;margin-top:10px;\n  text-transform:uppercase;letter-spacing:.06em;font-weight:600;\n}\n.nomb-item a{\n  display:inline-block;margin-top:8px;\n  color:var(--navy);text-decoration:none;\n  font-size:.78rem;font-weight:600;\n}\n.nomb-item a:hover{color:var(--orange)}\n\n\/* =========================================================\n   Leyes \/ stepper (PolicyAlert)\n   ========================================================= *\/\n.ley{\n  padding:24px 26px;margin-bottom:14px;\n  border:1px solid var(--line);border-radius:var(--radius-sm);\n  background:#fff;transition:all .18s ease;\n}\n.ley:last-child{margin-bottom:0}\n.ley:hover{\n  border-color:var(--navy);\n  box-shadow:0 8px 24px rgba(31,37,71,.08);\n  transform:translateY(-2px);\n}\n.ley .ley-title{\n  font-family:var(--font-display);\n  font-weight:700;font-size:1.05rem;\n  color:var(--navy);display:block;margin-bottom:8px;\n  letter-spacing:-.015em;line-height:1.35;\n  text-decoration:none;\n}\n.ley .ley-meta{font-size:1.1rem;color:var(--ink-soft);margin:0 0 18px;line-height:1.5}\n.ley .ley-meta strong{color:var(--navy);font-weight:700}\n.stepper{display:flex;flex-wrap:wrap;align-items:flex-start;width:100%;gap:2px 0}\n.stepper .step{\n  flex:1 1 auto;min-width:60px;text-align:center;position:relative;\n  font-size:.64rem;color:var(--muted);padding:0 4px;\n  text-transform:uppercase;letter-spacing:.04em;font-weight:600;\n}\n.stepper .step .dot{\n  width:12px;height:12px;border-radius:50%;\n  background:var(--line);border:2px solid #fff;\n  margin:0 auto 6px;position:relative;z-index:1;\n  box-shadow:0 0 0 1px var(--line);\n}\n.stepper .step.done .dot{background:var(--good);box-shadow:0 0 0 1px var(--good)}\n.stepper .step.current .dot{\n  background:var(--orange);\n  box-shadow:0 0 0 1px var(--orange), 0 0 0 4px rgba(243,165,58,.25);\n  width:14px;height:14px;\n}\n.stepper .step.done,.stepper .step.current{color:var(--ink)}\n.stepper .step.current{font-weight:700;color:var(--navy)}\n.stepper .step::before{content:\"\";position:absolute;top:5px;left:0;right:50%;height:2px;background:var(--line);z-index:0}\n.stepper .step::after{content:\"\";position:absolute;top:5px;left:50%;right:0;height:2px;background:var(--line);z-index:0}\n.stepper .step.done::before,.stepper .step.done::after{background:var(--good)}\n.stepper .step.current::before{background:var(--good)}\n.stepper .step:first-child::before{display:none}\n.stepper .step:last-child::after{display:none}\n\n\/* =========================================================\n   KPIs (PolicyAlert)\n   ========================================================= *\/\n.kpi-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin:8px 0 0}\n@media(min-width:760px){.kpi-grid{grid-template-columns:repeat(4,1fr)}}\n.kpi-card{\n  padding:24px 22px;background:#fff;\n  border:1px solid var(--line);border-radius:var(--radius-sm);\n  display:flex;flex-direction:column;gap:6px;\n  position:relative;overflow:hidden;\n  transition:all .18s ease;\n}\n.kpi-card::before{\n  content:\"\";position:absolute;top:0;left:0;right:0;height:3px;\n  background:var(--navy);\n}\n.kpi-card.accent::before{background:var(--orange)}\n.kpi-card.green::before{background:var(--good)}\n.kpi-card.purple::before{background:#7C3AED}\n.kpi-card:hover{box-shadow:0 12px 28px rgba(31,37,71,.08);transform:translateY(-2px)}\n.kpi-label{\n  font-size:1.1rem;color:var(--muted);\n  text-transform:uppercase;letter-spacing:.08em;font-weight:700;margin:0;\n}\n.kpi-value{\n  font-family:var(--font-display);\n  font-size:2.4rem;font-weight:800;\n  color:var(--navy);line-height:1;margin:6px 0 2px;\n  letter-spacing:-.03em;font-variant-numeric:tabular-nums;\n}\n.kpi-card.accent .kpi-value{color:var(--orange)}\n.kpi-card.green .kpi-value{color:var(--good)}\n.kpi-card.purple .kpi-value{color:#7C3AED}\n.kpi-sub{font-size:.8rem;color:var(--ink-soft);margin:0;line-height:1.4}\n\n\/* =========================================================\n   Temas conversaci\u00f3n (WATS): filtros, leyenda, chart\n   ========================================================= *\/\n.tc-meta{\n  font-size:1.25rem;color:var(--muted);margin:8px 0 0;\n  font-weight:500;\n}\n.tc-filters{\n  display:flex;flex-wrap:wrap;gap:6px;\n  margin:20px 0 24px;\n  padding:0 0 18px;border-bottom:1px solid var(--line);\n}\n.tc-filters button{\n  border:1px solid var(--line);background:#fff;\n  color:var(--ink-soft);\n  padding:8px 16px;border-radius:999px;\n  cursor:pointer;font-size:1.1rem;font-weight:600;\n  transition:all .15s ease;line-height:1.2;\n  font-family:var(--font-display);\n}\n.tc-filters button:hover{border-color:var(--navy);color:var(--navy)}\n.tc-filters button.active{\n  background:var(--navy);color:#fff;border-color:var(--navy);\n  box-shadow:0 2px 8px rgba(31,37,71,.16);\n}\n.tc-filters button[disabled]{opacity:.35;cursor:not-allowed}\n.tc-chart-wrap{position:relative;min-height:380px;margin-bottom:16px}\n.tc-legend{\n  display:flex;flex-wrap:wrap;gap:18px;align-items:center;\n  padding:14px 18px;background:var(--bg-alt);\n  border:1px solid var(--line-soft);\n  border-radius:var(--radius-sm);\n  font-size:.8rem;color:var(--ink-soft);\n}\n.tc-legend .lg{display:inline-flex;align-items:center;gap:8px;font-weight:500}\n.tc-legend .sw{width:12px;height:12px;border-radius:3px;display:inline-block}\n\n\/* =========================================================\n   Temas activos (PolicyAlert)\n   ========================================================= *\/\n.temas-act-wrap{position:relative;height:340px;margin:14px 0 14px}\n.temas-act-summary{\n  display:flex;flex-wrap:wrap;gap:10px;align-items:center;\n  margin-bottom:14px;font-size:.88rem;color:var(--ink-soft);\n}\n.temas-act-summary .pill{\n  background:var(--orange);color:var(--navy);\n  padding:6px 14px;border-radius:999px;\n  font-weight:700;font-size:.78rem;\n  font-family:var(--font-display);letter-spacing:.01em;\n}\n.temas-act-legend{\n  display:flex;flex-wrap:wrap;gap:8px;\n  margin-top:14px;padding-top:18px;border-top:1px solid var(--line);\n}\n.temas-act-legend .ta-tag{\n  display:inline-flex;align-items:center;gap:7px;\n  padding:7px 14px;\n  border:1px solid var(--line);border-radius:999px;\n  background:#fff;cursor:pointer;\n  font-family:var(--font-display);\n  font-size:.78rem;font-weight:600;color:var(--ink);\n  transition:all .14s ease;user-select:none;line-height:1.2;\n}\n.temas-act-legend .ta-tag:hover{border-color:var(--navy)}\n.temas-act-legend .ta-tag .sw{width:10px;height:10px;border-radius:50%;display:inline-block;flex-shrink:0;border:1.5px solid transparent}\n.temas-act-legend .ta-tag.off{opacity:.45;background:var(--bg-alt);text-decoration:line-through;color:var(--muted)}\n.temas-act-legend .ta-tag.off .sw{border-color:var(--muted);background:transparent !important}\n.temas-act-legend .ta-actions{display:inline-flex;gap:8px;margin-left:auto;align-items:center;font-size:.78rem}\n.temas-act-legend .ta-actions a{color:var(--navy);font-weight:700;cursor:pointer;text-decoration:none;font-family:var(--font-display)}\n.temas-act-legend .ta-actions a:hover{color:var(--orange)}\n.temas-act-legend .ta-actions span{color:var(--muted)}\n\n\/* =========================================================\n   FAQ\n   ========================================================= *\/\n.faq-section{\n  margin:0 0 40px;padding:56px 48px;\n  background:var(--card);border:1px solid var(--line);\n  border-radius:var(--radius);box-shadow:var(--shadow);\n}\n@media(max-width:768px){.faq-section{padding:36px 24px}}\n.faq-section > h2{\n  font-family:var(--font-display);\n  font-size:clamp(1.8rem,2.6vw,2.3rem);color:var(--navy);\n  font-weight:800;margin:0 0 14px;letter-spacing:-.025em;line-height:1.1;\n  text-wrap:balance;\n}\n.faq-intro{font-size:1.05rem;color:var(--ink-soft);margin:0 0 36px;line-height:1.6;max-width:62ch}\n.faq-block{margin-bottom:36px;padding-top:32px;border-top:1px solid var(--line);scroll-margin-top:24px}\n.faq-block:first-of-type{border-top:0;padding-top:0}\n.faq-block-title{\n  font-family:var(--font-display);\n  font-size:1.35rem;color:var(--navy);\n  font-weight:800;margin:0 0 22px;letter-spacing:-.015em;\n  display:flex;align-items:center;gap:14px;flex-wrap:wrap;\n}\n.faq-badge{\n  display:inline-block;\n  background:var(--navy);color:#fff;\n  font-family:var(--font-display);\n  font-size:.68rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;\n  padding:5px 12px;border-radius:999px;\n}\n.faq-badge.brand{background:var(--orange);color:var(--navy)}\n.faq-list{display:grid;grid-template-columns:1fr;gap:10px}\n@media(min-width:1100px){.faq-list{grid-template-columns:1fr 1fr;gap:10px 24px}}\n.faq-list details{\n  border:1px solid var(--line);border-radius:var(--radius-sm);\n  background:#fff;transition:all .18s ease;overflow:hidden;\n}\n.faq-list details:hover{border-color:var(--navy-light);box-shadow:0 4px 14px rgba(31,37,71,.06)}\n.faq-list details[open]{border-color:var(--navy);box-shadow:0 8px 24px rgba(31,37,71,.10)}\n.faq-list details summary{\n  padding:16px 22px;cursor:pointer;font-weight:600;\n  color:var(--ink);font-size:1.45rem;list-style:none;line-height:1.45;\n}\n.faq-list details summary::after{\n  content:\"+\";float:right;font-weight:300;color:var(--orange);\n  font-size:1.6rem;line-height:.7;margin-left:8px;transition:transform .2s ease;\n}\n.faq-list details[open] summary::after{content:\"\\2212\";color:var(--navy)}\n.faq-list details p{padding:0 22px 18px;margin:0;font-size:.9rem;color:var(--ink-soft);line-height:1.7}\n.faq-list details p a{color:var(--navy);font-weight:700}\n\n\/* =========================================================\n   SEO block \u2014 texto largo indexable\n   ========================================================= *\/\n.seo-block{\n  margin:0 0 32px;padding:56px 48px;\n  background:var(--card);border:1px solid var(--line);\n  border-radius:var(--radius);box-shadow:var(--shadow);\n}\n@media(max-width:768px){.seo-block{padding:36px 24px}}\n.seo-block h2{\n  font-family:var(--font-display);\n  font-size:clamp(1.7rem,2.4vw,2.1rem);color:var(--navy);\n  font-weight:800;margin:0 0 32px;letter-spacing:-.025em;\n  padding-bottom:18px;border-bottom:3px solid var(--orange);\n  line-height:1.15;display:inline-block;\n}\n.seo-grid{display:grid;grid-template-columns:1fr;gap:40px;margin-top:0}\n@media(min-width:960px){.seo-grid{grid-template-columns:1fr 1fr;gap:48px}}\n.seo-item h3{\n  font-family:var(--font-display);\n  font-size:1.25rem;color:var(--navy);font-weight:800;\n  margin:0 0 14px;line-height:1.25;letter-spacing:-.015em;\n}\n.seo-item h4{\n  font-family:var(--font-display);\n  font-size:.78rem;color:var(--orange);font-weight:700;\n  margin:24px 0 12px;text-transform:uppercase;letter-spacing:.12em;\n}\n.seo-item h4::before{content:\"\/\/ \"}\n.seo-item p{font-size:1rem;color:var(--ink-soft);margin:0 0 14px;line-height:1.75}\n.seo-item p strong{color:var(--ink);font-weight:600}\n.seo-item a{color:var(--navy);font-weight:600;text-decoration:none;border-bottom:1px solid currentColor}\n.seo-item a:hover{color:var(--orange)}\n.seo-table{\n  width:100%;border-collapse:collapse;font-size:.86rem;margin-top:12px;\n  background:#fff;border:1px solid var(--line);border-radius:var(--radius-sm);overflow:hidden;\n}\n.seo-table caption{\n  text-align:left;padding:14px 16px;background:var(--navy);color:#fff;\n  font-family:var(--font-display);font-weight:700;font-size:1.25rem;\n  letter-spacing:-.005em;\n}\n.seo-table th,.seo-table td{padding:12px 16px;text-align:left;border-bottom:1px solid var(--line)}\n.seo-table th{\n  background:var(--bg-alt);color:var(--ink);\n  font-family:var(--font-display);font-weight:700;\n  font-size:.76rem;text-transform:uppercase;letter-spacing:.06em;\n}\n.seo-table tr:last-child td{border-bottom:0}\n.seo-table tr:nth-child(even){background:#FAFBFC}\n.seo-table .muted-cell{color:var(--muted);font-style:italic;text-align:center;padding:20px}\n\n\/* =========================================================\n   Utilidades + estados\n   ========================================================= *\/\n.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.skeleton{background:linear-gradient(90deg,#F1F5F9 25%,#E2E8F0 50%,#F1F5F9 75%);background-size:200% 100%;animation:sk 1.4s infinite;border-radius:6px;color:transparent}\n@keyframes sk{0%{background-position:200% 0}100%{background-position:-200% 0}}\n.error{\n  background:#FEF2F2;border:1px solid #FECACA;color:#991B1B;\n  padding:14px 18px;border-radius:var(--radius-sm);font-size:.92rem;line-height:1.55;\n}\n.empty{color:var(--muted);font-size:.9rem;font-style:italic}\n\n\/* Di\u00e1logos (snippets) *\/\ndialog.snippets{border:none;border-radius:var(--radius);padding:0;max-width:560px;width:90%;box-shadow:var(--shadow-lg)}\ndialog.snippets::backdrop{background:rgba(22,26,54,.55)}\ndialog.snippets .head{padding:20px 24px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;gap:12px}\ndialog.snippets .head h3{margin:0;font-size:1.05rem;color:var(--navy);font-family:var(--font-display);font-weight:800}\ndialog.snippets .head button{border:0;background:transparent;font-size:1.5rem;cursor:pointer;color:var(--ink-soft)}\ndialog.snippets .body{padding:20px 24px;max-height:50vh;overflow:auto}\ndialog.snippets blockquote{margin:0 0 14px;padding:14px 18px;border-left:3px solid var(--orange);background:var(--bg-alt);border-radius:6px;font-size:.92rem;color:var(--ink-soft)}\n\n\/* Heatmap (por si lo activan) *\/\n.hm-meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center;font-size:.84rem;color:var(--ink-soft);margin-bottom:16px}\n.hm-meta .pill{background:var(--navy);color:#fff;padding:5px 14px;border-radius:999px;font-weight:600;font-size:.74rem;font-family:var(--font-display)}\n.hm-meta .pill.pill-id{background:var(--navy-soft);color:var(--navy)}\n.hm-meta .pill.pill-fallback{background:var(--orange);color:var(--navy)}\n.hm-scroll{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;max-width:100%}\ntable.heatmap{border-collapse:separate;border-spacing:3px;width:max-content;min-width:100%;font-size:.82rem}\ntable.heatmap th, table.heatmap td{padding:0}\ntable.heatmap thead th{font-weight:700;color:var(--ink-soft);text-align:center;padding:8px 6px;writing-mode:horizontal-tb;white-space:nowrap;font-family:var(--font-display);font-size:.74rem;text-transform:uppercase;letter-spacing:.04em}\ntable.heatmap tbody th{text-align:left;padding:8px 14px 8px 0;color:var(--ink);font-weight:500;white-space:nowrap;max-width:280px;overflow:hidden;text-overflow:ellipsis}\ntable.heatmap tbody th.person-cell .person-info{display:flex;align-items:center;gap:10px}\ntable.heatmap tbody th .avatar{width:36px !important;height:36px !important;border-radius:50%;object-fit:cover;flex:0 0 36px;border:2px solid var(--line)}\ntable.heatmap tbody th .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:var(--navy-soft);color:var(--navy);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:.72rem;flex:0 0 36px;border:2px solid var(--line);font-family:var(--font-display)}\ntable.heatmap tbody th .person-text{display:flex;flex-direction:column;min-width:0}\ntable.heatmap tbody th .person-name{font-weight:600;color:var(--navy);overflow:hidden;text-overflow:ellipsis;font-family:var(--font-display);letter-spacing:-.005em}\ntable.heatmap tbody th .grupo{display:flex;align-items:center;gap:5px;font-size:1.1rem;color:var(--muted);font-weight:500;overflow:hidden;text-overflow:ellipsis;margin-top:2px}\ntable.heatmap tbody th .group-logo{width:14px !important;height:14px !important;object-fit:contain;flex:0 0 14px}\ntable.heatmap td.cell{width:46px;height:36px;border-radius:6px;text-align:center;color:#0F172A;cursor:pointer;transition:transform .12s ease;font-variant-numeric:tabular-nums;font-size:.72rem;font-weight:600}\ntable.heatmap td.cell:hover{transform:scale(1.08);outline:2px solid var(--navy)}\ntable.heatmap td.cell[data-level=\"0\"]{background:var(--hm-0);color:var(--muted)}\ntable.heatmap td.cell[data-level=\"1\"]{background:var(--hm-1)}\ntable.heatmap td.cell[data-level=\"2\"]{background:var(--hm-2)}\ntable.heatmap td.cell[data-level=\"3\"]{background:var(--hm-3);color:#fff}\ntable.heatmap td.cell[data-level=\"4\"]{background:var(--hm-4);color:#fff}\ntable.heatmap td.cell[data-level=\"5\"]{background:var(--hm-5);color:#fff}\n.hm-legend{display:flex;align-items:center;gap:8px;margin-top:14px;font-size:.75rem;color:var(--ink-soft)}\n.hm-legend .scale{display:flex;border-radius:4px;overflow:hidden;border:1px solid var(--line)}\n.hm-legend .scale span{width:24px;height:12px;display:block}\n\n\/* Hemiciclo groups (pills bajo el SVG) *\/\n.hemi-groups{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}\n.hemi-group{display:inline-flex;align-items:center;gap:7px;font-size:.78rem;padding:7px 12px;background:#fff;border:1px solid var(--line);border-radius:999px;color:var(--ink);transition:border-color .12s ease;line-height:1.2}\n.hemi-group:hover{border-color:var(--navy)}\n.hemi-group .logo{width:18px !important;height:18px !important;object-fit:contain;flex:0 0 18px;border-radius:50%;background:transparent}\n.hemi-group .dot{width:10px;height:10px;border-radius:50%;display:inline-block;flex:0 0 10px}\n.hemi-group .nm{font-weight:600;font-family:var(--font-display)}\n.hemi-group .ct{color:var(--muted);margin-left:2px;font-weight:600;font-variant-numeric:tabular-nums}\n.hemi-wrap{position:relative;width:100%;max-width:580px;margin:0 auto}\n.hemi-wrap svg{width:100%;height:auto;display:block}\n.hemi-wrap .seat{transition:stroke-width .12s ease, filter .12s ease;cursor:pointer}\n.hemi-wrap .seat:hover{stroke:var(--navy);stroke-width:1.5;filter:brightness(1.15) drop-shadow(0 1px 2px rgba(0,0,0,.25))}\n.hemi-tooltip{position:absolute;pointer-events:none;background:rgba(31,37,71,.97);color:#fff;padding:11px 13px;border-radius:8px;font-size:.78rem;line-height:1.4;z-index:10;opacity:0;transition:opacity .1s;box-shadow:0 8px 20px rgba(0,0,0,.25);font-family:var(--font-display);max-width:260px}\n.hemi-tooltip.visible{opacity:1}\n.hemi-tooltip .ttl{font-weight:700;margin-bottom:4px;line-height:1.25}\n.hemi-tooltip .grp{display:flex;align-items:center;gap:6px;opacity:.95;font-size:.72rem;margin-bottom:6px}\n.hemi-tooltip .grp img{width:16px !important;height:16px !important;object-fit:contain;background:#fff;border-radius:50%;padding:1px;flex:0 0 16px}\n.hemi-tooltip .vt{display:inline-block;padding:3px 9px;border-radius:999px;font-weight:700;font-size:.7rem}\n.hemi-tooltip .vt.yes{background:#22C55E;color:#fff}\n.hemi-tooltip .vt.no{background:#EF4444;color:#fff}\n.hemi-tooltip .vt.abstention{background:#F59E0B;color:#fff}\n.hemi-tooltip .vt.no_vote{background:#111827;color:#fff}\n.hemi-meta .pill.aprobada{background:#16A34A}\n.hemi-meta .pill.rechazada{background:#DC2626}\n\n\/* Hemiciclo *\/\n.hemi-wrap{position:relative;width:100%;max-width:580px;margin:0 auto}\n.hemi-wrap svg{width:100%;height:auto;display:block}\n.hemi-totals{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:16px 0 16px}\n.hemi-total{padding:14px 12px;border-radius:var(--radius-sm);background:#fff;border:1px solid var(--line);text-align:center}\n.hemi-total .lbl{font-size:.68rem;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-weight:700}\n.hemi-total .val{font-family:var(--font-display);font-size:1.4rem;font-weight:800;display:block;margin-top:4px;color:var(--navy);letter-spacing:-.02em}\n.hemi-total.yes .val{color:#16A34A}\n.hemi-total.no .val{color:#DC2626}\n.hemi-total.abs .val{color:var(--orange)}\n\n\/* Pregunta del d\u00eda *\/\n.pregunta-card{\n  padding:26px 28px;background:#fff;\n  border:1px solid var(--line);border-radius:var(--radius-sm);\n  border-top:4px solid var(--orange);\n}\n.pregunta-meta{font-size:.72rem;color:var(--muted);margin-bottom:10px;text-transform:uppercase;letter-spacing:.08em;font-weight:700;font-family:var(--font-display)}\n.pregunta-q{\n  font-family:var(--font-display);font-size:1.2rem;font-weight:800;\n  color:var(--navy);margin:0 0 14px;line-height:1.3;letter-spacing:-.015em;\n}\n.pregunta-a{font-size:1.45rem;color:var(--ink);line-height:1.7;border-left:3px solid var(--navy);padding:4px 0 4px 18px;margin-top:6px}\n\n\n\n\/* ==========================================================\n   AJUSTES UX (compact hero \u00b7 loading pill a la derecha \u00b7\n   sin aside repetido) \u2014 aplicado a todos los productos\n   ========================================================== *\/\n.vc-hero{\n  padding:42px 24px 34px !important;\n}\n.vc-hero::before{opacity:.85}\n.vc-hero-inner{\n  gap:18px;\n  align-items:center !important;\n}\n@media(min-width:880px){\n  .vc-hero-inner{\n    grid-template-columns:minmax(0,1fr) auto !important;\n    align-items:center !important;\n    gap:32px;\n  }\n}\n.vc-kicker{\n  margin:0 0 10px !important;\n  font-size:.72rem !important;\n  letter-spacing:.16em !important;\n}\n.vc-hero h1{\n  font-size:clamp(1.65rem,3.2vw,2.4rem) !important;\n  margin:0 0 10px !important;\n  line-height:1.08 !important;\n  letter-spacing:-.02em !important;\n}\n.vc-hero-lead{\n  font-size:clamp(.92rem,1.05vw,1rem) !important;\n  margin:0 !important;\n  max-width:72ch;\n  line-height:1.5 !important;\n}\n\n\/* Columna derecha: pill arriba, CTA debajo *\/\n.vc-hero-right{\n  display:flex;flex-direction:column;align-items:flex-end;gap:12px;\n}\n.vc-hero-cta{\n  padding:11px 14px 11px 22px !important;\n  font-size:.88rem !important;\n  align-self:flex-end !important;\n}\n.vc-hero-cta .ico{width:26px;height:26px;font-size:.85rem}\n\n\/* Loading \/ \"Actualizado\u2026\" pill \u2014 sin posici\u00f3n absoluta *\/\n.vc-hero #updated-pill{\n  position:static !important;\n  display:inline-flex;align-items:center;gap:8px;\n  background:rgba(255,255,255,.06) !important;\n  border:1px solid rgba(255,255,255,.16) !important;\n  color:rgba(255,255,255,.82) !important;\n  padding:5px 12px;border-radius:999px;\n  font-size:1.1rem;letter-spacing:.04em;\n  margin:0 !important;\n  max-width:min(80vw,360px);\n  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\n  align-self:flex-end;\n}\n.vc-hero #updated-pill .dot{\n  width:7px;height:7px;border-radius:50%;\n  background:#4ADE80;\n  box-shadow:0 0 0 3px rgba(74,222,128,.16);\n  flex-shrink:0;\n}\n.vc-hero #updated-pill.err{\n  background:rgba(252,165,165,.08) !important;\n  border-color:rgba(252,165,165,.28) !important;\n  color:#FCA5A5 !important;\n}\n.vc-hero #updated-pill.err .dot{\n  background:#FCA5A5;\n  box-shadow:0 0 0 3px rgba(252,165,165,.18);\n}\n@media(max-width:879px){\n  .vc-hero{padding:32px 18px 26px !important;}\n  .vc-hero-right{flex-direction:row;flex-wrap:wrap;align-items:center;gap:10px;align-self:flex-start}\n  .vc-hero #updated-pill{align-self:auto}\n}\n\n\/* Quitar la aside \"product-info\" que repite los textos del hero \u00b7 grid a 1 columna *\/\n.product-grid .product-info,\n.product-grid.reverse .product-info{\n  display:none !important;\n}\n.product-grid,\n.product-grid.reverse{\n  grid-template-columns:1fr !important;\n}\n.product-grid > .product-content{\n  order:1 !important;\n  border:0 !important;\n}\n\n\/* Acercar los gr\u00e1ficos al header *\/\n.wrap{padding-top:32px !important;}\n@media(max-width:768px){.wrap{padding-top:22px !important}}\n\n\/* =========================================================\n   FREN \u00b7 Sankey temas\u2192partidos + Afinidad de voto (Vinces)\n   ========================================================= *\/\n.tc-filters{display:flex;flex-wrap:wrap;gap:7px;margin:16px 0 20px;padding-bottom:18px;border-bottom:1px solid var(--line)}\n.tc-btn{border:1px solid var(--line);background:#fff;color:var(--ink-soft);border-radius:999px;padding:6px 15px;font-size:.8rem;cursor:pointer;font-family:var(--font-display);font-weight:600;transition:all .15s;line-height:1.2}\n.tc-btn:hover{border-color:var(--navy);color:var(--navy)}\n.tc-btn.active{background:var(--navy);border-color:var(--navy);color:#fff}\n.sankey-wrap{position:relative;width:100%;min-height:360px}\n.sk-svg{width:100%;height:auto;display:block}\n.sk-label{font:600 11px var(--font-display),sans-serif;fill:var(--ink-soft)}\n.sk-node{cursor:pointer}\n.sk-link{transition:stroke-opacity .12s;cursor:pointer}\n.sk-link:hover{stroke-opacity:.72}\n.sk-tema:hover .sk-label{fill:var(--ink)}\n.afinidad-wrap{position:relative;min-height:520px;width:100%}\n.afi-svg{width:100%;height:auto;display:block}\n.afi-label{font:600 11px var(--font-display),sans-serif;fill:var(--ink-soft);transition:opacity .2s}\n.afi-link{cursor:pointer;transition:stroke-opacity .2s,stroke-width .2s,stroke .2s}\n.afi-tooltip{position:absolute;pointer-events:none;background:var(--navy);color:#fff;padding:9px 12px;border-radius:9px;font-size:.78rem;line-height:1.4;max-width:240px;opacity:0;transition:opacity .12s;z-index:5;box-shadow:0 8px 24px rgba(31,37,71,.28)}\n.afi-tooltip.visible{opacity:1}\n.afi-tooltip .ttl{font-weight:700;margin-bottom:2px;font-family:var(--font-display)}\n.afi-legend{display:flex;flex-wrap:wrap;gap:18px;font-size:.78rem;color:var(--ink-soft);margin-top:14px}\n.afi-legend .dash{display:inline-block;width:22px;border-top:3px solid var(--muted);vertical-align:middle;margin-right:4px}\n.tema-hint{font-size:1.1rem;color:var(--muted);margin:12px 0 0}\n.tema-detail{margin-top:16px;border:1px solid var(--line);border-radius:14px;padding:18px 20px;background:var(--bg-alt)}\n.tema-detail .td-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}\n.tema-detail .td-dot{width:14px;height:14px;border-radius:4px;display:inline-block}\n.tema-detail h4{margin:0;font-size:1.05rem;font-family:var(--font-display);color:var(--navy)}\n\/* Bot\u00f3n \"Saber m\u00e1s\" en cabeceras de detalle + modal de contacto *\/\n.saber-mas{margin-left:auto;display:inline-flex;align-items:center;gap:5px;border:1.5px solid var(--orange);background:var(--orange-soft);color:var(--accent-dark);font-family:var(--font-display);font-weight:700;font-size:.72rem;line-height:1;padding:5px 12px;border-radius:999px;cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,transform .15s}\n.saber-mas:hover{background:var(--orange);color:var(--navy);transform:translateY(-1px)}\n.vc-cmodal{position:fixed;inset:0;z-index:9999;display:none;align-items:center;justify-content:center;background:rgba(22,26,54,.55);backdrop-filter:blur(3px);padding:20px}\n.vc-cmodal.open{display:flex}\n.vc-cmodal .box{background:#fff;border-radius:18px;max-width:430px;width:100%;padding:30px 30px 26px;box-shadow:0 24px 60px rgba(31,37,71,.3)}\n.vc-cmodal h4{font-family:var(--font-display);font-weight:800;font-size:1.3rem;color:var(--navy);margin:0 0 10px;letter-spacing:-.02em}\n.vc-cmodal p{font-family:var(--font-body);font-size:1.45rem;color:var(--ink-soft);line-height:1.55;margin:0 0 22px}\n.vc-cmodal .acts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}\n.vc-cmodal .cta{display:inline-flex;align-items:center;gap:9px;background:var(--orange);color:var(--navy);font-family:var(--font-display);font-weight:700;font-size:1.45rem;padding:12px 24px;border-radius:999px;text-decoration:none;border:0;cursor:pointer;transition:transform .15s,background .2s;box-shadow:0 6px 18px rgba(243,165,58,.28)}\n.vc-cmodal .cta:hover{transform:translateY(-1px);background:#f7b35a}\n.vc-cmodal .x{background:none;border:0;cursor:pointer;font-family:var(--font-display);font-weight:600;font-size:.92rem;color:var(--ink-soft);padding:10px 8px;margin-left:auto}\n.vc-cmodal .x:hover{color:var(--navy)}\n.tema-detail h5{margin:0 0 10px;font-size:1.1rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-family:var(--font-display);font-weight:700}\n.td-cols{display:flex;gap:28px;flex-wrap:wrap}\n.td-col{flex:1 1 240px;min-width:220px}\n.td-bar-row{display:flex;align-items:center;gap:10px;margin-bottom:8px;font-size:.84rem}\n.td-bar-lbl{flex:0 0 120px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.td-bar{flex:1;height:10px;background:var(--line);border-radius:5px;overflow:hidden}\n.td-bar-fill{display:block;height:100%;border-radius:5px}\n.td-bar-val{flex:0 0 auto;color:var(--muted);font-variant-numeric:tabular-nums}\n.td-iv{display:flex;align-items:center;gap:11px;margin-bottom:10px}\n.td-rank{flex:0 0 18px;text-align:center;font-weight:700;color:var(--muted);font-size:.8rem}\n.td-avatar{width:36px;height:36px;border-radius:50%;object-fit:cover;flex:0 0 36px}\n.td-avatar-ph{display:flex;align-items:center;justify-content:center;background:var(--navy);color:#fff;font-size:.72rem;font-weight:700}\n.td-iv-text{display:flex;flex-direction:column;min-width:0;flex:1}\n.td-iv-name{font-weight:600;font-size:.86rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.td-iv-group{font-size:.75rem;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.afinidad-layout{display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start}\n.afinidad-layout .afinidad-wrap{flex:1 1 420px;min-width:300px}\n.afinidad-detail{flex:0 0 290px;border:1px solid var(--line);border-radius:14px;padding:18px 20px;background:var(--bg-alt);align-self:stretch}\n.afi-detail-hint{font-size:.84rem;color:var(--muted);margin:0}\n.afinidad-detail h4{margin:0;font-size:1.02rem;font-family:var(--font-display);color:var(--navy)}\n.afinidad-detail h5{margin:16px 0 10px;font-size:1.1rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-family:var(--font-display);font-weight:700}\n.ad-head{display:flex;align-items:center;gap:11px}\n.ad-logo{width:40px;height:40px;border-radius:50%;object-fit:contain;background:#fff;flex:0 0 40px;border:1px solid var(--line)}\n.ad-sub{font-size:.75rem;color:var(--muted)}\n.ad-vote,.ad-coes{display:flex;align-items:center;gap:9px;margin-bottom:7px;font-size:.8rem}\n.ad-vote-lbl,.ad-coes-lbl{flex:0 0 92px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.ad-vote-bar,.ad-coes-bar{flex:1;height:9px;background:var(--line);border-radius:5px;overflow:hidden}\n.ad-vote-bar>span,.ad-coes-bar>span{display:block;height:100%;border-radius:5px;background:var(--navy)}\n.ad-vote.favor .ad-vote-bar>span{background:#22C55E}\n.ad-vote.contra .ad-vote-bar>span{background:#EF4444}\n.ad-vote.abs .ad-vote-bar>span{background:#F59E0B}\n.ad-vote.nv .ad-vote-bar>span{background:#111827}\n.ad-vote-val,.ad-coes-val{flex:0 0 auto;color:var(--muted);font-variant-numeric:tabular-nums}\n.afi-node{cursor:pointer;transition:opacity .2s}\n.muted-cell{color:var(--muted);font-style:italic}\n\n.temas-act-wrap,\n.tc-chart-wrap{width:100% !important;}\n.temas-act-wrap canvas,\n.tc-chart-wrap canvas{\n  width:100% !important;\n  max-width:100% !important;\n}\n\n\/* FREN \u2014 Reparto tem\u00e1tico global como barras horizontales (sustituye al donut) *\/\n#fren-summary{display:block !important}\n#fren-summary .donut-mini{display:none !important}\n#fren-top-temas{\n  display:block;\n  margin:8px 0 18px;\n}\n#fren-top-temas h4{\n  font-family:var(--font-display);\n  font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;\n  color:var(--muted);font-weight:700;\n  margin:0 0 12px;\n}\n.tema-bar-row{\n  display:grid;\n  grid-template-columns:minmax(140px,180px) 1fr 52px;\n  align-items:center;gap:14px;\n  padding:7px 0;\n  border-bottom:1px solid rgba(0,0,0,.05);\n  font-size:.88rem;\n}\n.tema-bar-row:last-child{border-bottom:0}\n.tema-bar-row .nm{\n  color:var(--ink);font-weight:600;\n  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;\n}\n.tema-bar-row .bar{\n  position:relative;height:10px;\n  background:rgba(0,0,0,.05);\n  border-radius:999px;overflow:hidden;\n}\n.tema-bar-row .bar .fill{\n  display:block;height:100%;\n  border-radius:999px;\n  transition:width .6s cubic-bezier(.2,.7,.2,1);\n}\n.tema-bar-row .pct{\n  font-variant-numeric:tabular-nums;\n  font-weight:700;font-family:var(--font-display);\n  color:var(--navy);text-align:right;\n  font-size:1.1rem;\n}\n@media(max-width:640px){\n  .tema-bar-row{grid-template-columns:1fr 44px;gap:8px;row-gap:4px}\n  .tema-bar-row .nm{grid-column:1\/-1;font-size:.82rem}\n  .tema-bar-row .bar{grid-column:1\/2}\n  .tema-bar-row .pct{grid-column:2\/3;font-size:.78rem}\n}\n\n\/* ==========================================================\n   HERO IMPACTO \u2014 titular grande, LIVE pulsante, malla + glow,\n   chips de alcance. Sobrescribe los ajustes \"compact\".\n   ========================================================== *\/\n.vc-hero{\n  padding:46px 24px 40px !important;\n  background:\n    radial-gradient(120% 140% at 85% -10%, var(--navy-light) 0%, var(--navy) 42%, var(--navy-deep) 100%) !important;\n}\n\/* Malla de puntos sutil sobre el navy *\/\n.vc-hero::before{\n  background:\n    radial-gradient(circle at 88% 12%, rgba(243,165,58,.16), transparent 42%),\n    radial-gradient(circle at 4% 100%, rgba(31,168,134,.10), transparent 52%),\n    radial-gradient(rgba(255,255,255,.05) 1px, transparent 1px) !important;\n  background-size:auto, auto, 22px 22px !important;\n  opacity:1 !important;\n}\n.vc-hero::after{\n  height:2px !important;\n  background:linear-gradient(90deg, transparent, var(--orange), transparent) !important;\n  opacity:.6;\n}\n.vc-hero-inner{\n  align-items:center !important;\n  gap:28px !important;\n}\n@media(min-width:880px){\n  .vc-hero-inner{\n    grid-template-columns:minmax(0,1fr) auto !important;\n    align-items:center !important;\n    gap:56px !important;\n  }\n}\n\n\/* Kicker con punto LIVE pulsante *\/\n.vc-kicker{\n  display:inline-flex !important;align-items:center;gap:9px;\n  margin:0 0 13px !important;\n  font-size:.72rem !important;\n  letter-spacing:.18em !important;\n  padding:6px 14px 6px 11px;\n  border:1px solid rgba(243,165,58,.32);\n  border-radius:999px;\n  background:rgba(243,165,58,.08);\n  color:var(--orange);\n}\n.vc-kicker::before{content:none !important}\n.vc-live-dot{\n  position:relative;width:8px;height:8px;border-radius:50%;\n  background:#4ADE80;flex-shrink:0;\n}\n.vc-live-dot::after{\n  content:\"\";position:absolute;inset:0;border-radius:50%;\n  background:#4ADE80;\n}\n@media(prefers-reduced-motion:no-preference){\n  .vc-live-dot::after{animation:vcLivePulse 1.8s ease-out infinite}\n}\n@keyframes vcLivePulse{\n  0%{transform:scale(1);opacity:.7}\n  70%{transform:scale(3);opacity:0}\n  100%{transform:scale(3);opacity:0}\n}\n\n\/* Titular grande con segunda l\u00ednea destacada *\/\n.vc-hero h1{\n  font-size:clamp(1.7rem,3.3vw,2.7rem) !important;\n  font-weight:800 !important;\n  line-height:1 !important;\n  letter-spacing:-.035em !important;\n  margin:0 0 14px !important;\n  white-space:nowrap;\n}\n.vc-hero h1 .hl{\n  position:relative;\n  color:var(--orange);\n  white-space:nowrap;\n}\n.vc-hero h1 .hl::after{\n  content:\"\";position:absolute;left:0;right:0;bottom:.04em;height:.09em;\n  background:linear-gradient(90deg,var(--orange),rgba(243,165,58,.15));\n  border-radius:999px;\n}\n\n.vc-hero-lead{\n  font-size:clamp(.9rem,1vw,1.02rem) !important;\n  color:rgba(255,255,255,.82) !important;\n  max-width:60ch !important;\n  line-height:1.58 !important;\n  margin:0 0 20px !important;\n}\n\n\/* Fila de chips de alcance *\/\n.vc-hero-chips{\n  display:flex;flex-wrap:wrap;gap:10px;margin:0;padding:0;list-style:none;\n}\n.vc-hero-chips li{\n  display:inline-flex;align-items:center;gap:8px;\n  font-family:var(--font-display);\n  font-size:1.1rem;font-weight:600;\n  color:rgba(255,255,255,.9);\n  background:rgba(255,255,255,.06);\n  border:1px solid rgba(255,255,255,.14);\n  padding:8px 14px;border-radius:999px;\n  backdrop-filter:blur(6px);\n  transition:transform .15s ease, border-color .15s ease, background .15s ease;\n}\n.vc-hero-chips li:hover{\n  transform:translateY(-2px);\n  border-color:rgba(243,165,58,.4);\n  background:rgba(243,165,58,.1);\n}\n.vc-hero-chips .chip-ico{\n  color:var(--orange);display:inline-flex;flex-shrink:0;\n}\n@media(prefers-reduced-motion:reduce){\n  .vc-hero-chips li:hover{transform:none}\n}\n\n\/* CTA m\u00e1s presente *\/\n.vc-hero-cta{\n  padding:14px 18px 14px 26px !important;\n  font-size:.95rem !important;\n  box-shadow:0 8px 24px rgba(0,0,0,.22);\n}\n.vc-hero-cta .ico{width:30px;height:30px;font-size:.95rem}\n\n@media(max-width:879px){\n  .vc-hero{padding:44px 18px 36px !important;}\n  .vc-hero h1{font-size:clamp(1.7rem,7vw,2.6rem) !important;white-space:normal}\n  .vc-hero h1 .hl{white-space:normal}\n}\n\n\/* ==========================================================\n   RUEDA VERTICAL (estilo cr\u00e9ditos) \u2014 rail derecho del hero\n   ========================================================== *\/\n\/* Rail: pill arriba, tarjeta de la rueda en medio, CTA abajo *\/\n.vc-hero-right{\n  gap:18px !important;\n  align-self:center !important;\n}\n\/* Tarjeta que enmarca la rueda *\/\n.vc-hero-rail-card{\n  width:286px;\n  background:rgba(255,255,255,.045);\n  border:1px solid rgba(255,255,255,.12);\n  border-radius:16px;\n  padding:16px 16px 8px;\n  backdrop-filter:blur(6px);\n}\n.vc-wheel-label{\n  display:block;\n  font-family:var(--font-display);\n  font-size:.85rem;font-weight:700;\n  letter-spacing:.18em;text-transform:uppercase;\n  color:var(--orange);\n  margin:0 0 12px 2px;\n}\n.vc-hero-wheel{\n  position:relative;\n  height:184px;\n  overflow:hidden;\n  -webkit-mask-image:linear-gradient(180deg,transparent 0,#000 20%,#000 80%,transparent 100%);\n  mask-image:linear-gradient(180deg,transparent 0,#000 18%,#000 82%,transparent 100%);\n}\n.vc-hero-wheel-track{\n  display:flex;flex-direction:column;\n  will-change:transform;\n}\n@media(prefers-reduced-motion:no-preference){\n  .vc-hero-wheel-track{animation:vcWheelScroll 16s linear infinite}\n}\n.vc-hero-wheel:hover .vc-hero-wheel-track{animation-play-state:paused}\n@keyframes vcWheelScroll{\n  from{transform:translateY(0)}\n  to{transform:translateY(-50%)}\n}\n.vc-wheel-chip{\n  display:flex;align-items:center;gap:10px;\n  margin-bottom:10px;\n  font-family:var(--font-display);\n  font-size:1.1rem;font-weight:600;line-height:1.25;\n  color:rgba(255,255,255,.92);\n  background:rgba(255,255,255,.06);\n  border:1px solid rgba(255,255,255,.14);\n  padding:10px 14px;border-radius:12px;\n  backdrop-filter:blur(6px);\n}\n.vc-wheel-chip .chip-ico{\n  color:var(--orange);display:inline-flex;flex-shrink:0;\n}\n\/* Acciones bajo el texto: bot\u00f3n-logo + badge \"Actualizado\" *\/\n.vc-hero-actions{\n  display:flex;align-items:center;flex-wrap:wrap;\n  gap:16px;margin-top:22px;\n}\n.vc-hero-actions .vc-hero-cta{align-self:auto !important}\n.vc-hero-actions #updated-pill{align-self:auto !important}\n\/* Bot\u00f3n \"Contacta\" \u2014 ocupa el hueco a la derecha de la fila de acciones *\/\n.vc-hero-contact{\n  margin-left:auto;align-self:auto !important;\n  display:inline-flex;align-items:center;gap:9px;\n  padding:11px 22px;border-radius:999px;\n  background:var(--orange);color:var(--navy);\n  font:700 .95rem var(--font-display),sans-serif;letter-spacing:.01em;\n  text-decoration:none;border:1px solid transparent;white-space:nowrap;\n  transition:transform .15s ease,box-shadow .2s ease,background .2s ease;\n  box-shadow:0 6px 18px rgba(243,165,58,.26);\n}\n.vc-hero-contact:hover{transform:translateY(-1px);background:#f7b35a;box-shadow:0 10px 24px rgba(243,165,58,.36)}\n.vc-hero-contact svg{width:17px;height:17px;display:block}\n@media(max-width:879px){.vc-hero-contact{margin-left:0}}\n\n\/* Bot\u00f3n = logotipo completo de PolicyAlert (sin texto) *\/\n.vc-hero-cta{\n  padding:10px 18px !important;\n  gap:10px !important;\n}\n.vc-cta-logo{display:inline-flex;align-items:center;flex-shrink:0}\n.vc-cta-logo svg{height:30px;width:auto;display:block}\n\/* Icono \"abrir en nueva ventana\": badge circular navy \u2192 se\u00f1ala que es un enlace *\/\n.vc-cta-ext{\n  width:26px;height:26px;border-radius:50%;flex-shrink:0;\n  background:var(--navy);color:#fff;\n  display:inline-flex;align-items:center;justify-content:center;\n  transition:transform .15s ease, background .2s ease;\n}\n.vc-cta-ext svg{width:14px;height:14px;display:block}\n.vc-hero-cta:hover .vc-cta-ext{background:var(--navy-light);transform:translate(1px,-1px)}\n\n@media(max-width:879px){\n  .vc-hero-rail-card{width:100%}\n  .vc-hero-wheel{height:148px}\n  .vc-cta-logo svg{height:30px}\n}\n\n<\/style>\n<style id=\"rubrika-type\">\n\/* === Sistema tipogr\u00e1fico Rubrika + hero padding (jun 2026) ===\n   T\u00edtulos\/Subt\u00edtulos: Libre Franklin \u00b7 Textos\/Botones: Lato *\/\n.vc-hero{ padding:150px 24px 100px !important; }\n@media(max-width:879px){ .vc-hero{ padding:110px 18px 64px !important; } }\n\/* T\u00edtulos \u2014 Libre Franklin 600 \u00b7 48\/58 *\/\n.vc-hero h1{\n  font-family:\"Libre Franklin\",sans-serif !important;\n  font-weight:600 !important; font-size:48px !important;\n  line-height:58px !important; letter-spacing:-.01em !important;\n  white-space:normal !important;\n}\n@media(max-width:879px){ .vc-hero h1{ font-size:34px !important; line-height:42px !important; } }\n\/* Subt\u00edtulos \u2014 Libre Franklin 600 \u00b7 32\/38 *\/\n.product-name, .product-content-header h3, .faq-section > h2, .seo-block h2{\n  font-family:\"Libre Franklin\",sans-serif !important;\n  font-weight:600 !important; font-size:32px !important;\n  line-height:38px !important; letter-spacing:-.01em !important;\n}\n@media(max-width:879px){\n  .product-name, .product-content-header h3, .faq-section > h2, .seo-block h2{\n    font-size:25px !important; line-height:31px !important;\n  }\n}\n\/* Textos \u2014 Lato 400 \u00b7 16\/24 *\/\n.vc-hero-lead, .product-content-header .sub, .faq-intro,\n.faq-list details p, .pane-cta-text, .seo-item p{\n  font-family:\"Lato\",sans-serif !important;\n  font-weight:400 !important; font-size:16px !important; line-height:24px !important;\n}\n\/* Botones \u2014 Lato 700 \u00b7 15\/15 *\/\n.vc-hero-cta, .vc-hero-contact, .pane-cta, .vc-cmodal .cta,\n.wt-cta-btn, .saber-mas, .ley-follow{\n  font-family:\"Lato\",sans-serif !important;\n  font-weight:700 !important; font-size:15px !important; line-height:15px !important;\n}\n<\/style>\n<\/head>\n<body>\n\n<!-- HERO (estilo Vinces) -->\n<section class=\"vc-hero\" aria-label=\"Cabecera de producto\" data-vc-hero-injected=\"1\">\n  <div class=\"vc-hero-inner\">\n    <div>\n      <span class=\"vc-kicker\"><span class=\"vc-live-dot\"><\/span>FREN \u00b7 AI for Public Affairs<\/span>\n      <h1>All of Spanish politics, in one <span class=\"hl\">question<\/span><\/h1>\n      <p class=\"vc-hero-lead\"><strong>FREN<\/strong> is the AI assistant specialised in politics, regulation and public affairs. Ask it in natural language and get reasoned answers \u2014with charts, hemicycles and tables\u2014 always citing the official source.<\/p>\n      <div class=\"vc-hero-actions\">\n        <a class=\"vc-hero-cta\" href=\"https:\/\/fren.democrata.es\/\" target=\"_blank\" rel=\"noopener\" aria-label=\"Open FREN\">\n          <span class=\"vc-cta-logo\" aria-hidden=\"true\"><svg width=\"1600\" height=\"413\" viewBox=\"0 0 1600 413\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n<path d=\"M1323.7 403.902V85.5322H1399.43V403.902H1323.7ZM1399.43 219.176H1375.17C1377.15 187.102 1383.26 160.571 1393.51 139.584C1403.77 118.597 1417.38 102.955 1434.34 92.6598C1451.69 81.9683 1471.41 76.6226 1493.5 76.6226C1514.8 76.6226 1533.34 80.9784 1549.12 89.69C1565.29 98.0056 1577.71 110.677 1586.39 127.704C1595.46 144.732 1600 166.115 1600 191.853V403.902H1524.27V207.297C1524.27 192.645 1522.1 180.37 1517.76 170.47C1513.82 160.175 1507.7 152.453 1499.42 147.305C1491.14 142.158 1480.49 139.584 1467.47 139.584C1457.22 139.584 1447.75 141.762 1439.07 146.117C1430.79 150.077 1423.69 155.819 1417.77 163.343C1411.86 170.47 1407.32 178.984 1404.16 188.884C1401.01 198.387 1399.43 208.485 1399.43 219.176Z\" fill=\"#021666\"><\/path>\n<path d=\"M1259.27 212.642L1215.49 243.529C1215.49 209.871 1209.18 184.132 1196.56 166.313C1183.94 148.097 1165.79 138.99 1142.13 138.99C1118.07 138.99 1099.73 148.097 1087.1 166.313C1074.48 184.528 1068.17 210.861 1068.17 245.311C1068.17 278.969 1074.68 304.906 1087.7 323.121C1101.11 341.337 1120.24 350.444 1145.09 350.444C1162.83 350.444 1177.63 346.286 1189.46 337.971C1201.69 329.655 1210.17 316.984 1214.9 299.956H1294.77C1286.1 336.783 1268.35 364.898 1241.52 384.301C1215.1 403.308 1182.95 412.811 1145.09 412.811C1113.53 412.811 1086.12 406.08 1062.85 392.616C1039.57 379.153 1021.63 359.948 1009.01 335.001C996.778 309.658 990.665 279.761 990.665 245.311C990.665 209.277 996.778 178.786 1009.01 153.839C1021.63 128.496 1039.38 109.291 1062.25 96.2237C1085.13 83.1563 1111.76 76.6226 1142.13 76.6226C1172.89 76.6226 1199.52 83.3543 1222 96.8177C1244.48 110.281 1261.84 129.288 1274.07 153.839C1286.29 178.39 1292.41 207.891 1292.41 242.341V260.16H1022.02V212.642H1259.27Z\" fill=\"#021666\"><\/path>\n<path d=\"M792.156 403.902V85.5322H867.887V403.902H792.156ZM867.887 234.619H844.221C845.799 197.397 851.715 167.302 861.971 144.336C872.62 120.973 886.031 103.945 902.203 93.2538C918.375 82.1663 935.927 76.6226 954.86 76.6226C961.171 76.6226 967.087 77.4146 972.609 78.9985C978.526 80.5824 982.865 82.7603 985.626 85.5322V151.463C980.498 149.879 975.173 148.889 969.651 148.493C964.129 147.701 957.818 147.305 950.718 147.305C936.519 147.305 924.094 149.483 913.444 153.839C903.189 157.799 894.709 163.739 888.003 171.658C881.298 179.182 876.17 188.29 872.62 198.981C869.465 209.277 867.887 221.156 867.887 234.619Z\" fill=\"#021666\"><\/path>\n<path d=\"M491.455 403.902V0H571.328V403.902H491.455ZM549.437 63.5551V0H774.264V63.5551H549.437ZM549.437 241.153V177.598H751.781V241.153H549.437Z\" fill=\"#021666\"><\/path>\n<path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M158.177 56.8847C161.891 48.5492 171.442 34.9247 191.903 34.9247C212.364 34.9247 221.909 48.5492 225.623 56.8847C276.075 170.119 311.514 238.738 377.368 350.791C395.105 378.863 373.34 408.807 346.341 407.131C305.492 405.094 259.976 380.641 217.245 357.684C208.665 353.075 200.198 348.526 191.903 344.231C183.611 348.525 175.146 353.072 166.569 357.68C123.835 380.637 78.3108 405.094 37.4591 407.131C10.4598 408.807 -11.3052 378.863 6.43224 350.791C72.2859 238.738 107.725 170.119 158.177 56.8847Z\" fill=\"#2069E7\"><\/path>\n<\/svg><\/span>\n          <span class=\"vc-cta-ext\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"\/><polyline points=\"15 3 21 3 21 9\"\/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"\/><\/svg><\/span>\n        <\/a>\n        <span class=\"updated\" id=\"updated-pill\" aria-live=\"polite\" style=\"background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.14);color:rgba(255,255,255,.85)\">\n          <span class=\"dot\"><\/span>\n          <span id=\"updated-text\">Loading data\u2026<\/span>\n        <\/span>\n        <a class=\"vc-hero-contact\" href=\"https:\/\/vincesconsulting.com\/contacto\/\" target=\"_blank\" rel=\"noopener\" aria-label=\"Contact Vinces\">\n          <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z\"\/><polyline points=\"22,6 12,13 2,6\"\/><\/svg>\n          <span>Contact<\/span>\n        <\/a>\n      <\/div>\n    <\/div>\n    <div class=\"vc-hero-rail-card\" id=\"vc-hero-wheel\" role=\"group\" aria-label=\"What FREN answers\">\n      <span class=\"vc-wheel-label\">What it answers<\/span>\n      <div class=\"vc-hero-wheel\">\n        <div class=\"vc-hero-wheel-track\">\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 4h18M7 4v16M3 8h4M17 4v16M17 8h4M3 12h4M17 12h4M3 16h4M17 16h4\"\/><\/svg><\/span>Laws &amp; regulation<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"\/><circle cx=\"9\" cy=\"7\" r=\"4\"\/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"\/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"\/><\/svg><\/span>Decision-makers &amp; stakeholders<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 12l2 2 4-4\"\/><path d=\"M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z\"\/><\/svg><\/span>Congress votes<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><path d=\"M14 2v6h6M8 13h8M8 17h8\"\/><\/svg><\/span>Diarios de Sesiones<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2zm0 0a2 2 0 0 1-2-2v-9h4\"\/><path d=\"M18 14h-8M15 18h-5M10 6h8v4h-8z\"\/><\/svg><\/span>Political news<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"\/><\/svg><\/span>Answers with cited sources<\/span>\n          <span class=\"vc-wheel-chip\"><span class=\"chip-ico\" aria-hidden=\"true\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13 2 3 14h9l-1 8 10-12h-9z\"\/><\/svg><\/span>Official data in real time<\/span>\n          <!-- duplicado para loop infinito sin salto -->\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 4h18M7 4v16M3 8h4M17 4v16M17 8h4M3 12h4M17 12h4M3 16h4M17 16h4\"\/><\/svg><\/span>Laws &amp; regulation<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2\"\/><circle cx=\"9\" cy=\"7\" r=\"4\"\/><path d=\"M23 21v-2a4 4 0 0 0-3-3.87\"\/><path d=\"M16 3.13a4 4 0 0 1 0 7.75\"\/><\/svg><\/span>Decision-makers &amp; stakeholders<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M9 12l2 2 4-4\"\/><path d=\"M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z\"\/><\/svg><\/span>Congress votes<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><path d=\"M14 2v6h6M8 13h8M8 17h8\"\/><\/svg><\/span>Diarios de Sesiones<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2zm0 0a2 2 0 0 1-2-2v-9h4\"\/><path d=\"M18 14h-8M15 18h-5M10 6h8v4h-8z\"\/><\/svg><\/span>Political news<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"\/><\/svg><\/span>Answers with cited sources<\/span>\n          <span class=\"vc-wheel-chip\" aria-hidden=\"true\"><span class=\"chip-ico\"><svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M13 2 3 14h9l-1 8 10-12h-9z\"\/><\/svg><\/span>Official data in real time<\/span>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/section>\n\n<div class=\"wrap\">\n\n  <noscript>\n    <div class=\"nojs\">\n      This page uses JavaScript to show interactive charts. If you have it disabled, you can view the raw data at\n      <a href=\"\/api\/dashboard-publico\/fren\/\">\/api\/dashboard-publico\/fren\/<\/a>.\n    <\/div>\n  <\/noscript>\n\n  <section class=\"product-section\" aria-labelledby=\"fren-title\" itemscope itemtype=\"https:\/\/schema.org\/Dataset\">\n    <div class=\"product-grid\">\n      <aside class=\"product-info\">\n        <span class=\"product-badge\">FREN \u00b7 Artificial intelligence for Public Affairs<\/span>\n        <h2 class=\"product-name\" id=\"fren-title\" itemprop=\"name\">All of Spanish politics, in one question<\/h2>\n        <p class=\"product-tagline\" itemprop=\"description\">\n          <strong>FREN<\/strong> combines <strong>5 modules<\/strong> \u2014Laws, Decision-makers, Votes, Diarios de Sesiones and News\u2014 to turn hours of research into seconds, always citing the official source.\n        <\/p>\n      <\/aside>\n\n      <div class=\"product-content\" data-carousel>\n        <nav class=\"car-tabs\" role=\"tablist\" aria-label=\"Vistas FREN\">\n          <button type=\"button\" class=\"car-tab active\" data-pane=\"pane-temas\" role=\"tab\" aria-selected=\"true\">Hot topics<\/button>\n          <button type=\"button\" class=\"car-tab\" data-pane=\"pane-afinidad\" role=\"tab\" aria-selected=\"false\">Who votes with whom<\/button>\n          <button type=\"button\" class=\"car-tab\" data-pane=\"pane-pregunta\" role=\"tab\" aria-selected=\"false\">Question of the day<\/button>\n        <\/nav>\n\n        <div class=\"car-pane active\" id=\"pane-temas\" role=\"tabpanel\">\n          <div class=\"product-content-header\">\n            <div>\n              <h3>Hot topics of the last 3 months<\/h3>\n              <p class=\"sub\">The topics most present in the Diarios de Sesiones over the last 3 months, and which parties drive them. The thickness of each flow shows its prominence in the debate. Filter by chamber or click on a topic or party.<\/p>\n            <\/div>\n          <\/div>\n          <div class=\"hm-meta\" id=\"temas-meta\"><\/div>\n          <div class=\"tc-filters\" id=\"temas-filters\"><\/div>\n          <div class=\"sankey-wrap\" id=\"temas-wrap\"><p class=\"empty\" style=\"padding:24px 0\">Loading topics\u2026<\/p><\/div>\n          <p class=\"tema-hint\" id=\"tema-hint\">Click on a topic to see which parties are driving it and its main speakers.<\/p>\n          <div class=\"tema-detail\" id=\"tema-detail\" hidden><\/div>\n          <div class=\"pane-cta-row\">\n            <p class=\"pane-cta-text\">\n              <strong>Want to dig deeper into the analysis of the Diarios de Sesiones by your topics?<\/strong>\n              Ask FREN: it has every Diario from the Congreso, Senado and regional parliaments indexed and analysed by topic.\n            <\/p>\n            <a class=\"pane-cta\" href=\"https:\/\/fren.democrata.es\/\" rel=\"noopener\">Explore topics in FREN<\/a>\n          <\/div>\n        <\/div>\n\n        <div class=\"car-pane\" id=\"pane-afinidad\" role=\"tabpanel\">\n          <div class=\"product-content-header\">\n            <div>\n              <h3>Who votes with whom<\/h3>\n              <p class=\"sub\">Affinity between groups based on how often they vote the same way in the Pleno del Congreso (last 3 months). The thicker and shorter the connection, the greater the agreement.<\/p>\n            <\/div>\n          <\/div>\n          <div class=\"hm-meta\" id=\"afinidad-meta\"><\/div>\n          <div class=\"afinidad-layout\">\n            <div class=\"afinidad-wrap\" id=\"afinidad-wrap\"><p class=\"empty\" style=\"padding:24px 0\">Loading voting affinity\u2026<\/p><\/div>\n            <aside class=\"afinidad-detail\" id=\"afinidad-detail\"><p class=\"afi-detail-hint\">Click on a group to see its voting detail and its affinities with the rest.<\/p><\/aside>\n          <\/div>\n          <div class=\"afi-legend\">\n            <span><span class=\"dash\"><\/span> Thickness = % of voting agreement<\/span>\n            <span>Node size = number of MPs in the group<\/span>\n          <\/div>\n          <div class=\"pane-cta-row\">\n            <p class=\"pane-cta-text\">\n              <strong>Want to understand voting patterns by topic and anticipate future behaviour?<\/strong>\n              Ask FREN and explore alliances, voting discipline and majority scenarios in any vote of the Congreso or the Senado.\n            <\/p>\n            <a class=\"pane-cta\" href=\"https:\/\/fren.democrata.es\/\" rel=\"noopener\">Explore votes in FREN<\/a>\n          <\/div>\n        <\/div>\n\n        <div class=\"car-pane\" id=\"pane-pregunta\" role=\"tabpanel\">\n          <div class=\"product-content-header\">\n            <div>\n              <h3>Question of the day, answered by AI<\/h3>\n              <p class=\"sub\">Each day FREN picks a relevant news story and answers it citing official sources and posts by political officials.<\/p>\n            <\/div>\n          <\/div>\n          <div id=\"pregunta-content\">\n            <p class=\"empty\" style=\"padding:24px 0\">Loading question of the day\u2026<\/p>\n          <\/div>\n          <div class=\"pane-cta-row\">\n            <p class=\"pane-cta-text\">\n              <strong>Have your own questions?<\/strong>\n              Start your free 15-day trial of FREN, no card and no commitment.\n            <\/p>\n            <a class=\"pane-cta\" href=\"https:\/\/fren.democrata.es\/\" rel=\"noopener\">Try FREN for free<\/a>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <!-- FAQ -->\n  <section class=\"faq-section\" aria-labelledby=\"faq-title\" id=\"faq\">\n    <h2 id=\"faq-title\">Frequently asked questions<\/h2>\n    <p class=\"faq-intro\">Everything you need to know about FREN, the AI assistant from Vinces and Dem\u00f3crata for politics, regulation and public affairs.<\/p>\n    <article class=\"faq-block\">\n      <h3 class=\"faq-block-title\"><span class=\"faq-badge\">FREN<\/span> The AI assistant for Public Affairs<\/h3>\n      <div class=\"faq-list\">\n    <details><summary>What is FREN?<\/summary><p>The AI assistant specialised and expert in politics, regulation and public affairs. You ask in natural language and it works, researches and gives you the latest in these fields, always citing the official source.<\/p><\/details>\n    <details><summary>Who is it for?<\/summary><p>For Public Affairs departments, legal and compliance teams, consultancies and law firms, associations, researchers, journalists and any decision-maker who needs to follow political news rigorously.<\/p><\/details>\n    <details><summary>What can I ask it?<\/summary><p>Anything about regulation, decision-makers, votes, session diaries or political news, just as you would ask an expert. You don\u2019t need exact searches or filters.<\/p><\/details>\n    <details><summary>What modules does it have?<\/summary><p>Five: Laws &amp; Regulation, Decision-makers, Votes, Diarios de Sesiones and Political news. Each connects to different official sources and they can be combined in a single query.<\/p><\/details>\n    <details><summary>What does the Laws &amp; Regulation module do?<\/summary><p>It tracks initiatives and legislation in real time (Congreso, Senado, regional chambers, ministries, regional departments and official gazettes), identifies what affects you and anticipates changes.<\/p><\/details>\n    <details><summary>And the Decision-makers module?<\/summary><p>It identifies and tracks public decision-makers: the structure of chambers and governments, career history, social media presence and stated positions.<\/p><\/details>\n    <details><summary>And the Votes module?<\/summary><p>It maps the hemicycle with each group\u2019s and MP\u2019s vote, and analyses patterns, alliances and possible majorities.<\/p><\/details>\n    <details><summary>And Diarios de Sesiones?<\/summary><p>It analyses the full parliamentary debate: who spoke, on what topic and with what intensity, with verbatim quotes and a link to the source.<\/p><\/details>\n    <details><summary>And Political news?<\/summary><p>Press releases from ministries and agencies, the institutional agenda and regulatory updates, filtered by sector.<\/p><\/details>\n    <details><summary>How does it work?<\/summary><p>You ask the question in natural language; FREN retrieves information from official sources in real time, cross-references data across modules and builds a reasoned answer with text, charts, hemicycles and tables.<\/p><\/details>\n    <details><summary>What is semantic search?<\/summary><p>FREN doesn\u2019t search for exact words but for the meaning of your query. If you ask about \u201cclean energy\u201d, it also finds \u201crenewables\u201d, \u201cgreen transition\u201d or \u201cdecarbonisation\u201d, even if you don\u2019t use those words.<\/p><\/details>\n    <details><summary>Is it reliable? How does it avoid AI hallucinations?<\/summary><p>FREN first retrieves information from official sources and then generates the answer over those documents (RAG technique), citing the exact source. If it doesn\u2019t have enough data, it says so instead of making things up.<\/p><\/details>\n    <details><summary>What are Pro and Think modes?<\/summary><p>Two ways of answering depending on the question: <strong>Pro<\/strong> runs a deep analysis with intermediate reasoning (~1 min); <strong>Think<\/strong> researches exhaustively, checking several sources (several minutes). The answer streams in as it is generated.<\/p><\/details>\n    <details><summary>How do credits work?<\/summary><p>FREN uses a credit system consumed on each query: simple questions cost less than those crossing several sources, and Pro mode uses fewer than Think. You can start with the free trial and then buy the package that fits your usage.<\/p><\/details>\n    <details><summary>Does it cite sources?<\/summary><p>Always. Every answer cites the exact source (parliamentary session, document URL or social media post) so you can verify it. All information is official and verified.<\/p><\/details>\n    <details><summary>How is it different from ChatGPT?<\/summary><p>FREN accesses Spanish official sources in real time (BOE, session diaries, initiatives, votes) and cites the exact source; a general-purpose chatbot can\u2019t reach those sources.<\/p><\/details>\n    <details><summary>Does it cover the regional parliaments?<\/summary><p>Yes. The Cortes Generales and all regional parliaments and governments.<\/p><\/details>\n    <details><summary>Are there team and company accounts?<\/summary><p>Yes. Each user has their own profile, chats and private history, while credits are shared across the organisation, with an administrator account that manages them centrally.<\/p><\/details>\n    <details><summary>Are my queries private and is it GDPR-compliant?<\/summary><p>Yes. Your queries are private and are not used to train models; data is stored encrypted in the European Union in accordance with the GDPR.<\/p><\/details>\n    <details><summary>How do I start?<\/summary><p>Free 15-day trial, no card and no commitment, at <a href=\"https:\/\/fren.democrata.es\/\" rel=\"noopener\">fren.democrata.es<\/a>. Cancel whenever you want.<\/p><\/details>\n      <\/div>\n    <\/article>\n  <\/section>\n\n<\/div>\n\n<!-- Snippets dialog -->\n<dialog class=\"snippets\" id=\"snippets-dialog\" aria-labelledby=\"snippets-title\">\n  <div class=\"head\">\n    <h3 id=\"snippets-title\">Excerpts<\/h3>\n    <button type=\"button\" id=\"snippets-close\" aria-label=\"Close\">\u2715<\/button>\n  <\/div>\n  <div class=\"body\" id=\"snippets-body\"><\/div>\n<\/dialog>\n\n<script defer src=\"https:\/\/cdn.jsdelivr.net\/npm\/d3@7\/dist\/d3.min.js\"><\/script>\n<script defer src=\"https:\/\/cdn.jsdelivr.net\/npm\/d3-sankey@0.12.3\/dist\/d3-sankey.min.js\"><\/script>\n<script>\n\/*\n * Helpers compartidos por todas las p\u00e1ginas de producto (FREN, PolicyAlert,\n * Poder Digital, WATS). Se exponen en `window.VincesDash` para que cada\n * p\u00e1gina pueda invocarlos sin duplicar c\u00f3digo.\n *\n * IMPORTANT: las p\u00e1ginas standalone descargadas por \"Descargar HTML externo\"\n * inlinean este script. Mantenerlo autocontenido (sin imports).\n *\/\n(function () {\n  \"use strict\";\n\n  const LOCALE = \"en-GB\";\n  const REFRESH_MS = 30 * 60 * 1000;\n\n  const $ = (s, r) => (r || document).querySelector(s);\n  const $$ = (s, r) => Array.from((r || document).querySelectorAll(s));\n\n  function fmtDate(iso) {\n    if (!iso) return \"\";\n    try {\n      return new Date(iso).toLocaleDateString(LOCALE, {\n        day: \"2-digit\", month: \"long\", year: \"numeric\",\n      });\n    } catch { return iso; }\n  }\n  function fmtDateTime(iso) {\n    if (!iso) return \"\";\n    try {\n      return new Date(iso).toLocaleString(LOCALE, {\n        day: \"2-digit\", month: \"short\", year: \"numeric\",\n        hour: \"2-digit\", minute: \"2-digit\",\n      });\n    } catch { return iso; }\n  }\n  function escapeHtml(s) {\n    return String(s == null ? \"\" : s)\n      .replace(\/&\/g, \"&amp;\").replace(\/<\/g, \"&lt;\").replace(\/>\/g, \"&gt;\")\n      .replace(\/\"\/g, \"&quot;\").replace(\/'\/g, \"&#39;\");\n  }\n  function initials(name) {\n    return (name || \"?\").trim().split(\/\\s+\/).slice(0, 2).map(p => p[0]).join(\"\").toUpperCase();\n  }\n\n  \/**\n   * Carga est\u00e1ndar de los endpoints p\u00fablicos del dashboard.\n   * - apiUrl: URL absoluta o relativa al endpoint \/api\/dashboard-publico\/...\n   * - onData: callback(data) llamado en \u00e9xito.\n   * - onError: callback(message, kind) llamado en error. kind: \"network\" | \"http\" | \"json\" | \"render\".\n   *\/\n  async function loadDashboardData(apiUrl, onData, onError) {\n    let response;\n    try {\n      response = await fetch(apiUrl, {\n        method: \"GET\",\n        headers: { Accept: \"application\/json\" },\n        mode: \"cors\",\n        cache: \"no-cache\",\n      });\n    } catch (err) {\n      console.error(\"[Dashboard] Network\/CORS error:\", err);\n      const isFileProtocol = window.location.protocol === \"file:\";\n      const msg = isFileProtocol\n        ? `No se pudieron cargar los datos desde <code>${escapeHtml(apiUrl)}<\/code>. <br>Si has abierto este HTML como archivo local, s\u00edrvelo desde un servidor web (no <code>file:\/\/<\/code>) o aseg\u00farate de que el endpoint permite el origen <code>null<\/code>.`\n        : `No se pudieron cargar los datos desde <code>${escapeHtml(apiUrl)}<\/code>. <br>Comprueba la configuraci\u00f3n CORS del backend para el origen actual: <code>${escapeHtml(window.location.origin)}<\/code>.`;\n      onError && onError(msg, \"network\");\n      return;\n    }\n    if (!response.ok) {\n      console.error(\"[Dashboard] HTTP error:\", response.status);\n      onError && onError(`El servidor devolvi\u00f3 HTTP ${response.status} al consultar <code>${escapeHtml(apiUrl)}<\/code>.`, \"http\");\n      return;\n    }\n    let data;\n    try {\n      data = await response.json();\n    } catch (err) {\n      console.error(\"[Dashboard] JSON parse error:\", err);\n      onError && onError(\"La respuesta no es JSON v\u00e1lido.\", \"json\");\n      return;\n    }\n    try {\n      onData && onData(data);\n    } catch (err) {\n      console.error(\"[Dashboard] Render error:\", err);\n      onError && onError(\"Error al renderizar los datos.\", \"render\");\n    }\n  }\n\n  \/**\n   * Pinta el pill de \"Actualizado...\" en la cabecera y gestiona el estado de error.\n   *\/\n  function setUpdatedPill(data, errMessage) {\n    const pill = $(\"#updated-pill\");\n    const txt = $(\"#updated-text\");\n    if (!pill || !txt) return;\n    if (errMessage) {\n      pill.classList.add(\"err\");\n      txt.textContent = errMessage;\n    } else {\n      pill.classList.remove(\"err\");\n      txt.textContent = \"Updated \" + fmtDateTime((data && data.generated_at) || new Date().toISOString());\n    }\n  }\n\n  \/**\n   * Bot\u00f3n \"Descargar HTML externo\": clona el DOM, elimina elementos de test,\n   * sustituye la API_URL relativa por una absoluta y descarga como fichero\n   * \u00fanico listo para WordPress.\n   *\/\n  function bindDownloadExternalHtml(absApiUrl) {\n    const btn = $(\"#download-html-btn\");\n    if (!btn) return;\n    btn.addEventListener(\"click\", () => {\n      try {\n        btn.dataset.state = \"generating\";\n        btn.querySelector(\".lbl\").textContent = \"Generando\u2026\";\n\n        const cloned = document.documentElement.cloneNode(true);\n        cloned.querySelectorAll(\".testbar, #download-html-btn\").forEach(el => el.remove());\n\n        cloned.querySelectorAll(\"script\").forEach(s => {\n          const txt = s.textContent || \"\";\n          if (txt.includes(\"DEFAULT_API_URL\")) {\n            s.textContent = txt.replace(\n              \/const\\s+DEFAULT_API_URL\\s*=\\s*[\"'][^\"']*[\"'];\/,\n              `const DEFAULT_API_URL = ${JSON.stringify(absApiUrl)};`\n            );\n          }\n        });\n\n        const banner =\n          \"\\n<!--\\n\" +\n          \"  Dashboard Vinces \u00b7 p\u00e1gina standalone por producto\\n\" +\n          \"  Generado: \" + new Date().toISOString() + \"\\n\" +\n          \"  Endpoint API: \" + absApiUrl + \"\\n\" +\n          \"\\n\" +\n          \"  C\u00f3mo integrarlo en WordPress:\\n\" +\n          \"    1. Editor de la p\u00e1gina \u2192 bloque 'HTML personalizado'.\\n\" +\n          \"    2. Pega el fichero completo (todo, incluyendo <html>, <head>, <body>).\\n\" +\n          \"       En la mayor\u00eda de temas WP basta con pegar a partir de <body>.\\n\" +\n          \"    3. Aseg\u00farate de que el dominio WP est\u00e1 incluido en CORS del backend.\\n\" +\n          \"    4. El cliente refresca cada 30 min; el backend cachea 1h.\\n\" +\n          \"-->\\n\";\n        const html = \"<!DOCTYPE html>\\n\" + cloned.outerHTML.replace(\/^<html\/, banner + \"<html\");\n\n        const blob = new Blob([html], { type: \"text\/html;charset=utf-8\" });\n        const url = URL.createObjectURL(blob);\n        const a = document.createElement(\"a\");\n        a.href = url;\n        const stamp = new Date().toISOString().slice(0, 10);\n        a.download = `dashboard-vinces-${(window.VincesDash && window.VincesDash.product) || \"page\"}-${stamp}.html`;\n        document.body.appendChild(a);\n        a.click();\n        document.body.removeChild(a);\n        setTimeout(() => URL.revokeObjectURL(url), 2000);\n\n        btn.dataset.state = \"done\";\n        btn.querySelector(\".lbl\").textContent = \"\u2714 Descargado\";\n        setTimeout(() => {\n          btn.removeAttribute(\"data-state\");\n          btn.querySelector(\".lbl\").textContent = \"Download standalone HTML\";\n        }, 2500);\n      } catch (err) {\n        console.error(\"[download-html] error:\", err);\n        btn.removeAttribute(\"data-state\");\n        btn.querySelector(\".lbl\").textContent = \"Generation error\";\n        setTimeout(() => { btn.querySelector(\".lbl\").textContent = \"Download standalone HTML\"; }, 2500);\n      }\n    });\n  }\n\n  \/**\n   * Inicializa carruseles tipo tabs sobre cualquier elemento [data-carousel].\n   * Cuando un pane se activa, ejecuta el callback registrado en\n   * window.VincesDash.onPaneActivated[paneId] (si existe).\n   *\/\n  function initCarousels() {\n    document.querySelectorAll(\"[data-carousel]\").forEach(scope => {\n      const tabs = scope.querySelectorAll(\".car-tab\");\n      const panes = scope.querySelectorAll(\".car-pane\");\n      tabs.forEach(btn => {\n        btn.addEventListener(\"click\", () => {\n          const target = btn.getAttribute(\"data-pane\");\n          tabs.forEach(b => {\n            b.classList.toggle(\"active\", b === btn);\n            b.setAttribute(\"aria-selected\", b === btn ? \"true\" : \"false\");\n          });\n          panes.forEach(p => {\n            p.classList.toggle(\"active\", p.id === target);\n          });\n          const cb = window.VincesDash && window.VincesDash.onPaneActivated && window.VincesDash.onPaneActivated[target];\n          if (typeof cb === \"function\") cb();\n        });\n      });\n    });\n  }\n\n  window.VincesDash = Object.assign(window.VincesDash || {}, {\n    LOCALE,\n    REFRESH_MS,\n    $, $$,\n    fmtDate, fmtDateTime, escapeHtml, initials,\n    loadDashboardData,\n    setUpdatedPill,\n    bindDownloadExternalHtml,\n    initCarousels,\n    onPaneActivated: {},\n  });\n})();\n<\/script>\n\n<script>\n(() => {\n  \"use strict\";\n\n  const DEFAULT_API_URL = \"https:\/\/vision360.vincesconsulting.com\/api\/dashboard-publico\/fren\/\";\n  const {\n    $, $$, fmtDate, fmtDateTime, escapeHtml, initials,\n    loadDashboardData, setUpdatedPill, bindDownloadExternalHtml, initCarousels,\n  } = window.VincesDash;\n  window.VincesDash.product = \"fren\";\n\n  function getApiUrl() {\n    const diarioId = ($(\"#diario-id-input\")?.value || \"\").trim();\n    if (diarioId) return DEFAULT_API_URL + \"?diario_id=\" + encodeURIComponent(diarioId);\n    return DEFAULT_API_URL;\n  }\n\n  function levelFor(w) {\n    if (w >= 0.40) return 5;\n    if (w >= 0.25) return 4;\n    if (w >= 0.15) return 3;\n    if (w >= 0.07) return 2;\n    if (w >  0.00) return 1;\n    return 0;\n  }\n\n  \/\/ ===== Estado + helper de pane activo =====\n  const STATE = {};\n  function isPaneActive(id){ const el = document.getElementById(id); return !!(el && el.classList.contains(\"active\")); }\n\n\/\/ ===== Temas calientes (Sankey temas \u2192 partidos) =====\n  let temasChamberKey = \"todos\";\n  let _temasByName = {};   \/\/ tema-nombre -> objeto del tema de la c\u00e1mara actual (para el clic)\n  let _partidosById = {};  \/\/ pid -> {id, name, color, logo_url, temas:[{topic,color,score}]}\n  let _sankeySel = null;   \/\/ selecci\u00f3n activa de resaltado (tema o partido)\n\n  function renderTemasCalientes(tc) {\n    const filters = $(\"#temas-filters\"), meta = $(\"#temas-meta\"), wrap = $(\"#temas-wrap\");\n    const hasData = tc && tc.by_chamber && Array.isArray(tc.by_chamber.todos) && tc.by_chamber.todos.length;\n    if (!hasData) {\n      if (filters) filters.innerHTML = \"\";\n      if (meta) meta.innerHTML = \"\";\n      _resetTemaDetail();\n      if (wrap) wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">A\u00fan no hay Diarios de Sesiones analizados en los \u00faltimos 3 meses.<\/p>';\n      return;\n    }\n    if (meta) {\n      const v = tc.ventana || {};\n      meta.innerHTML = `\n        <span class=\"pill\">${tc.n_diarios} diarios analizados<\/span>\n        ${v.desde ? `<span>\u00b7 del <time datetime=\"${escapeHtml(v.desde)}\">${fmtDate(v.desde)}<\/time> al <time datetime=\"${escapeHtml(v.hasta || \"\")}\">${fmtDate(v.hasta)}<\/time><\/span>` : \"\"}\n      `;\n    }\n    if (filters) {\n      const chambers = tc.ambitos_chamber || [];\n      const valid = temasChamberKey === \"todos\" || chambers.some(c => String(c.chamber_id) === temasChamberKey);\n      if (!valid) temasChamberKey = \"todos\";\n      filters.innerHTML =\n        `<button type=\"button\" class=\"tc-btn ${temasChamberKey === \"todos\" ? \"active\" : \"\"}\" data-key=\"todos\">All chambers<\/button>` +\n        chambers.map(c => `<button type=\"button\" class=\"tc-btn ${temasChamberKey === String(c.chamber_id) ? \"active\" : \"\"}\" data-key=\"${c.chamber_id}\">${escapeHtml(c.chamber)}<\/button>`).join(\"\");\n      filters.querySelectorAll(\".tc-btn\").forEach(b => b.addEventListener(\"click\", () => {\n        temasChamberKey = b.dataset.key;\n        filters.querySelectorAll(\".tc-btn\").forEach(x => x.classList.toggle(\"active\", x === b));\n        drawSankey(tc);\n      }));\n    }\n    drawSankey(tc);\n  }\n\n  function drawSankey(tc) {\n    const wrap = $(\"#temas-wrap\");\n    const list = (tc.by_chamber && tc.by_chamber[temasChamberKey]) || [];\n    _resetTemaDetail();\n    _sankeySel = null;\n    if (!wrap) return;\n    if (!list.length) {\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">No hay temas registrados para esta c\u00e1mara en los \u00faltimos 3 meses.<\/p>';\n      return;\n    }\n    if (!window.d3 || !window.d3.sankey) {\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">Cargando librer\u00eda de gr\u00e1ficos\u2026<\/p>';\n      return;\n    }\n    try {\n      const d3 = window.d3;\n      const TOP_PARTIES = 5;   \/\/ por tema, m\u00e1ximo de partidos en el flujo\n      const MIN_SHARE = 0.04;  \/\/ descarta flujos < 4% del tema (anti-saturaci\u00f3n)\n\n      _temasByName = {};\n      _partidosById = {};\n      const temaNodes = [], partidoMap = {}, links = [];\n      list.forEach(t => {\n        const tid = \"t:\" + t.topic;\n        _temasByName[t.topic] = t;\n        temaNodes.push({ id: tid, name: t.topic, color: t.color || \"#9aa0a6\", kind: \"tema\", topic: t.topic });\n        const pps = t.por_partido || [];\n        const total = pps.reduce((a, b) => a + (b.score || 0), 0) || 1;\n        pps.slice(0, TOP_PARTIES).forEach(p => {\n          if (!p.score || (p.score \/ total) < MIN_SHARE) return;\n          const pid = \"p:\" + (p.group_id || p.group || \"?\");\n          if (!partidoMap[pid]) partidoMap[pid] = { id: pid, name: p.group || \"\u2014\", color: p.color || \"#9aa0a6\", kind: \"partido\", logo_url: p.logo_url || null };\n          if (!_partidosById[pid]) _partidosById[pid] = { id: pid, name: p.group || \"\u2014\", color: p.color || \"#9aa0a6\", logo_url: p.logo_url || null, temas: [] };\n          _partidosById[pid].temas.push({ topic: t.topic, color: t.color || \"#9aa0a6\", score: p.score });\n          links.push({ source: pid, target: tid, value: p.score, color: p.color || \"#9aa0a6\" });\n        });\n      });\n      const nodes = temaNodes.concat(Object.values(partidoMap));\n      if (!links.length) {\n        wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">Sin datos de partidos por tema.<\/p>';\n        return;\n      }\n\n      const W = 820, LEFT = 200, RIGHT = 230;\n      const partyCount = Object.keys(partidoMap).length;\n      \/\/ Alto suficiente para que los logos de partido (22px) no se solapen,\n      \/\/ incl. en \"todas las c\u00e1maras\" donde hay m\u00e1s partidos a la derecha.\n      const H = Math.max(380, Math.max(temaNodes.length, partyCount) * 50);\n      const sankey = d3.sankey()\n        .nodeId(d => d.id)\n        .nodeWidth(13)\n        .nodePadding(26)\n        .extent([[LEFT, 18], [W - RIGHT, H - 18]]);\n      const graph = sankey({\n        nodes: nodes.map(d => Object.assign({}, d)),\n        links: links.map(d => Object.assign({}, d)),\n      });\n\n      const linkPath = d3.sankeyLinkHorizontal();\n      let linkEls = \"\", nodeEls = \"\";\n      graph.links.forEach(l => {\n        const topic = (l.target && l.target.topic) || \"\";\n        const party = (l.source && l.source.id) || \"\";\n        linkEls += `<path class=\"sk-link\" d=\"${linkPath(l)}\" fill=\"none\" stroke=\"${escapeHtml(l.color)}\" stroke-opacity=\"0.5\" stroke-width=\"${Math.max(1, l.width).toFixed(1)}\" data-topic=\"${escapeHtml(topic)}\" data-party=\"${escapeHtml(party)}\"><title>${escapeHtml(l.target.name)} \u2192 ${escapeHtml(l.source.name)}: ${l.value}<\/title><\/path>`;\n      });\n      graph.nodes.forEach(n => {\n        const h = Math.max(1, n.y1 - n.y0), wdt = (n.x1 - n.x0);\n        const cy = (n.y0 + n.y1) \/ 2;\n        const isTema = n.kind === \"tema\";\n        nodeEls += `<g class=\"sk-node ${isTema ? \"sk-tema\" : \"sk-partido\"}\"${isTema ? ` data-topic=\"${escapeHtml(n.topic)}\"` : ` data-party=\"${escapeHtml(n.id)}\"`}>`;\n        nodeEls += `<rect x=\"${n.x0.toFixed(1)}\" y=\"${n.y0.toFixed(1)}\" width=\"${wdt.toFixed(1)}\" height=\"${h.toFixed(1)}\" rx=\"2\" fill=\"${escapeHtml(n.color)}\"><title>${escapeHtml(n.name)}<\/title><\/rect>`;\n        if (isTema) {\n          \/\/ Tema a la DERECHA: etiqueta a la derecha del nodo.\n          nodeEls += `<text class=\"sk-label\" x=\"${(n.x1 + 8).toFixed(1)}\" y=\"${cy.toFixed(1)}\" dy=\"0.35em\" text-anchor=\"start\">${escapeHtml(n.name)}<\/text>`;\n        } else {\n          \/\/ Partido a la IZQUIERDA (extremo del Sankey): logo + nombre a la izquierda.\n          const sz = 22, lx = n.x0 - 8 - sz;\n          if (n.logo_url) {\n            nodeEls += `<image href=\"${escapeHtml(n.logo_url)}\" x=\"${lx.toFixed(1)}\" y=\"${(cy - sz \/ 2).toFixed(1)}\" width=\"${sz}\" height=\"${sz}\" preserveAspectRatio=\"xMidYMid meet\"><title>${escapeHtml(n.name)}<\/title><\/image>`;\n          } else {\n            nodeEls += `<circle cx=\"${(n.x0 - 8 - 8).toFixed(1)}\" cy=\"${cy.toFixed(1)}\" r=\"8\" fill=\"${escapeHtml(n.color)}\"><\/circle>`;\n          }\n          nodeEls += `<text class=\"sk-label\" x=\"${(lx - 5).toFixed(1)}\" y=\"${cy.toFixed(1)}\" dy=\"0.35em\" text-anchor=\"end\">${escapeHtml(_shortGroup(n.name))}<\/text>`;\n        }\n        nodeEls += `<\/g>`;\n      });\n\n      wrap.innerHTML =\n        `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" class=\"sk-svg\" role=\"img\" aria-label=\"Diagrama Sankey de temas a partidos\">` +\n        `<g class=\"sk-links\">${linkEls}<\/g>${nodeEls}<\/svg>`;\n    } catch (err) {\n      console.error(\"[FREN] sankey error:\", err);\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">The topics diagram could not be drawn.<\/p>';\n    }\n  }\n\n  \/\/ Resalta solo los flujos del tema\/partido seleccionado (aten\u00faa el resto).\n  function _sankeyHighlight(attr, val) {\n    const svg = $(\"#temas-wrap .sk-svg\");\n    if (!svg || !val) return;\n    const sel = attr + \"=\" + val;\n    if (_sankeySel === sel) { _sankeyClearHighlight(); return; }   \/\/ re-clic = quitar\n    _sankeySel = sel;\n    svg.querySelectorAll(\"path.sk-link\").forEach(l => {\n      l.style.strokeOpacity = (l.getAttribute(attr) === val) ? \"0.9\" : \"0.05\";\n    });\n  }\n  function _sankeyClearHighlight() {\n    _sankeySel = null;\n    const svg = $(\"#temas-wrap .sk-svg\");\n    if (!svg) return;\n    svg.querySelectorAll(\"path.sk-link\").forEach(l => { l.style.strokeOpacity = \"\"; });\n  }\n\n  function _resetTemaDetail() {\n    const box = $(\"#tema-detail\"), hint = $(\"#tema-hint\");\n    if (box) { box.hidden = true; box.innerHTML = \"\"; }\n    if (hint) hint.hidden = false;\n  }\n\n  function renderTemaDetail(tema) {\n    const box = $(\"#tema-detail\"), hint = $(\"#tema-hint\");\n    if (!box || !tema) return;\n    const ivs = tema.top_intervinientes || [];\n    const pp = tema.por_partido || [];\n    const maxP = Math.max(...pp.map(p => p.score || 0), 0.0001);\n    box.innerHTML = `\n      <div class=\"td-head\">\n        <span class=\"td-dot\" style=\"background:${escapeHtml(tema.color || '#9aa0a6')}\"><\/span>\n        <h4>${escapeHtml(tema.topic)}<\/h4>\n        <button type=\"button\" class=\"saber-mas\" data-vc-contact=\"${escapeHtml(tema.topic)}\">Learn more \u2197<\/button>\n      <\/div>\n      <div class=\"td-cols\">\n        <div class=\"td-col\">\n          <h5>Which parties drive it<\/h5>\n          ${pp.length ? pp.map(p => `\n            <div class=\"td-bar-row\">\n              <span class=\"td-bar-lbl\" title=\"${escapeHtml(p.group || '\u2014')}\">${escapeHtml(_shortGroup(p.group || '\u2014'))}<\/span>\n              <span class=\"td-bar\"><span class=\"td-bar-fill\" style=\"width:${Math.round((p.score \/ maxP) * 100)}%;background:${escapeHtml(p.color || '#9aa0a6')}\"><\/span><\/span>\n            <\/div>`).join(\"\") : '<p class=\"muted-cell\">No data.<\/p>'}\n        <\/div>\n        <div class=\"td-col\">\n          <h5>Top speakers<\/h5>\n          ${ivs.length ? ivs.map((p, i) => `\n            <div class=\"td-iv\">\n              <span class=\"td-rank\">${i + 1}<\/span>\n              ${p.photo_url\n                ? `<img decoding=\"async\" class=\"td-avatar\" src=\"${escapeHtml(p.photo_url)}\" alt=\"\" loading=\"lazy\">`\n                : `<span class=\"td-avatar td-avatar-ph\">${escapeHtml(initials(p.name || '?'))}<\/span>`}\n              <span class=\"td-iv-text\">\n                <span class=\"td-iv-name\">${escapeHtml(p.name || '\u2014')}<\/span>\n                ${p.group ? `<span class=\"td-iv-group\">${escapeHtml(p.group)}<\/span>` : \"\"}\n              <\/span>\n            <\/div>`).join(\"\") : '<p class=\"muted-cell\">No data.<\/p>'}\n        <\/div>\n      <\/div>`;\n    box.hidden = false;\n    if (hint) hint.hidden = true;\n  }\n\n  function renderPartidoDetail(pid) {\n    const box = $(\"#tema-detail\"), hint = $(\"#tema-hint\");\n    const pd = _partidosById[pid];\n    if (!box || !pd) return;\n    const temas = (pd.temas || []).slice().sort((a, b) => (b.score || 0) - (a.score || 0));\n    const maxS = Math.max(...temas.map(t => t.score || 0), 0.0001);\n    box.innerHTML = `\n      <div class=\"td-head\">\n        ${pd.logo_url\n          ? `<img decoding=\"async\" src=\"${escapeHtml(pd.logo_url)}\" alt=\"\" style=\"width:26px;height:26px;object-fit:contain\">`\n          : `<span class=\"td-dot\" style=\"background:${escapeHtml(pd.color || '#9aa0a6')}\"><\/span>`}\n        <h4>${escapeHtml(_shortGroup(pd.name))}<\/h4>\n        <button type=\"button\" class=\"saber-mas\" data-vc-contact=\"${escapeHtml(pd.name)}\">Learn more \u2197<\/button>\n      <\/div>\n      <div class=\"td-cols\">\n        <div class=\"td-col\" style=\"flex-basis:100%\">\n          <h5>Topics it drives most<\/h5>\n          ${temas.length ? temas.map(t => `\n            <div class=\"td-bar-row\">\n              <span class=\"td-bar-lbl\" title=\"${escapeHtml(t.topic)}\">${escapeHtml(t.topic)}<\/span>\n              <span class=\"td-bar\"><span class=\"td-bar-fill\" style=\"width:${Math.round((t.score \/ maxS) * 100)}%;background:${escapeHtml(t.color || '#9aa0a6')}\"><\/span><\/span>\n            <\/div>`).join(\"\") : '<p class=\"muted-cell\">No data.<\/p>'}\n        <\/div>\n      <\/div>`;\n    box.hidden = false;\n    if (hint) hint.hidden = true;\n  }\n\n  \/\/ ===== Afinidad de voto (grafo de bloques, d3-force) =====\n  function _shortGroup(label) {\n    return (label || \"\").replace(\/Grupo Parlamentario\/gi, \"GP\").replace(\/\\s+\/g, \" \").trim();\n  }\n  function _nodeRadius(size, maxSize) {\n    const MIN = 16, MAX = 42;\n    const s = Math.sqrt(Math.max(size || 1, 1)) \/ Math.sqrt(Math.max(maxSize || 1, 1));\n    return MIN + (MAX - MIN) * s;\n  }\n\n  function renderAfinidad(af) {\n    const wrap = $(\"#afinidad-wrap\"), meta = $(\"#afinidad-meta\");\n    if (!wrap) return;\n    const nodes0 = (af && af.nodes) || [], edges0 = (af && af.edges) || [];\n    if (meta) {\n      meta.innerHTML = (af && af.n_votaciones)\n        ? `<span class=\"pill\">${af.n_votaciones} recorded votes<\/span><span>\u00b7 last 3 months \u00b7 % of voting agreement between groups<\/span>`\n        : \"\";\n    }\n    if (!window.d3) { wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">Cargando librer\u00eda de gr\u00e1ficos\u2026<\/p>'; return; }\n    if (!nodes0.length) {\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">No hay suficientes votaciones nominales en el periodo para calcular afinidades entre grupos.<\/p>';\n      return;\n    }\n    try {\n      const d3 = window.d3;\n      _afiSel = null;\n      const W = Math.max(wrap.clientWidth || 640, 320), H = 540;\n      const maxSize = Math.max(...nodes0.map(n => n.size || 1), 1);\n      const nodes = nodes0.map(n => ({ ...n, r: _nodeRadius(n.size, maxSize) }));\n      const links = edges0.map(e => ({ source: e.a, target: e.b, w: e.w, shared: e.shared, agree: e.agree }));\n\n      const sim = d3.forceSimulation(nodes)\n        .force(\"link\", d3.forceLink(links).id(d => d.id).distance(l => 115 + (1 - l.w) * 215).strength(l => 0.08 + l.w * 0.5))\n        .force(\"charge\", d3.forceManyBody().strength(-980))\n        .force(\"center\", d3.forceCenter(W \/ 2, H \/ 2))\n        .force(\"collide\", d3.forceCollide(d => d.r + 26))\n        .stop();\n      for (let i = 0; i < 400; i++) sim.tick();\n      nodes.forEach(n => {\n        n.x = Math.max(n.r + 4, Math.min(W - n.r - 4, n.x));\n        n.y = Math.max(n.r + 4, Math.min(H - n.r - 16, n.y));\n      });\n      const byId = {}; nodes.forEach(n => { byId[n.id] = n; });\n\n      const linkEls = links.map(l => {\n        const a = byId[(l.source && l.source.id) || l.source], b = byId[(l.target && l.target.id) || l.target];\n        if (!a || !b) return \"\";\n        const sw = (1 + l.w * 7).toFixed(2), op = (0.12 + l.w * 0.5).toFixed(2);\n        return `<line x1=\"${a.x.toFixed(1)}\" y1=\"${a.y.toFixed(1)}\" x2=\"${b.x.toFixed(1)}\" y2=\"${b.y.toFixed(1)}\" stroke=\"#94a3b8\" stroke-width=\"${sw}\" stroke-opacity=\"${op}\" class=\"afi-link\" data-a=\"${escapeHtml(_shortGroup(a.label))}\" data-b=\"${escapeHtml(_shortGroup(b.label))}\" data-w=\"${Math.round(l.w * 100)}\" data-shared=\"${l.shared}\" data-src=\"${escapeHtml((l.source && l.source.id) || l.source)}\" data-tgt=\"${escapeHtml((l.target && l.target.id) || l.target)}\"><\/line>`;\n      }).join(\"\");\n\n      let defs = \"\", nodeEls = \"\";\n      nodes.forEach((n, i) => {\n        const cid = `afclip${i}`, ri = (n.r - 2).toFixed(1);\n        defs += `<clipPath id=\"${cid}\"><circle cx=\"${n.x.toFixed(1)}\" cy=\"${n.y.toFixed(1)}\" r=\"${ri}\"><\/circle><\/clipPath>`;\n        const dash = n.heterogeneo ? ` stroke-dasharray=\"4 3\"` : \"\";\n        nodeEls += `<g class=\"afi-node\" data-id=\"${escapeHtml(n.id)}\">`;\n        nodeEls += `<circle cx=\"${n.x.toFixed(1)}\" cy=\"${n.y.toFixed(1)}\" r=\"${n.r.toFixed(1)}\" fill=\"${escapeHtml(n.color || \"#64748b\")}\" stroke=\"#fff\" stroke-width=\"2\"${dash}><\/circle>`;\n        if (n.logo_url) {\n          const d2 = ((n.r - 2) * 2).toFixed(1);\n          nodeEls += `<image href=\"${escapeHtml(n.logo_url)}\" x=\"${(n.x - (n.r - 2)).toFixed(1)}\" y=\"${(n.y - (n.r - 2)).toFixed(1)}\" width=\"${d2}\" height=\"${d2}\" clip-path=\"url(#${cid})\" preserveAspectRatio=\"xMidYMid slice\"><\/image>`;\n        }\n        nodeEls += `<text x=\"${n.x.toFixed(1)}\" y=\"${(n.y + n.r + 13).toFixed(1)}\" text-anchor=\"middle\" class=\"afi-label\">${escapeHtml(_shortGroup(n.label))}<\/text>`;\n        nodeEls += `<\/g>`;\n      });\n\n      wrap.innerHTML =\n        `<svg viewBox=\"0 0 ${W} ${H}\" preserveAspectRatio=\"xMidYMid meet\" class=\"afi-svg\" role=\"img\" aria-label=\"Grafo de afinidad de voto entre grupos parlamentarios\">` +\n        `<defs>${defs}<\/defs><g class=\"afi-links\">${linkEls}<\/g>${nodeEls}<\/svg>` +\n        `<div class=\"afi-tooltip\" id=\"afi-tooltip\" role=\"tooltip\"><\/div>`;\n\n      const tip = $(\"#afi-tooltip\");\n      wrap.addEventListener(\"mousemove\", e => {\n        const ln = e.target.closest && e.target.closest(\"line.afi-link\");\n        if (!ln || !tip) { if (tip) tip.classList.remove(\"visible\"); return; }\n        tip.innerHTML = `<div class=\"ttl\">${ln.getAttribute(\"data-a\")} \u2194 ${ln.getAttribute(\"data-b\")}<\/div><span class=\"vt\">${ln.getAttribute(\"data-w\")}% coinciden \u00b7 ${ln.getAttribute(\"data-shared\")} votaciones<\/span>`;\n        const rect = wrap.getBoundingClientRect();\n        let left = e.clientX - rect.left + 14, top = e.clientY - rect.top - 10;\n        if (left + 230 > rect.width) left = e.clientX - rect.left - 230;\n        tip.style.left = left + \"px\"; tip.style.top = Math.max(0, top) + \"px\";\n        tip.classList.add(\"visible\");\n      });\n      wrap.addEventListener(\"mouseleave\", () => { if (tip) tip.classList.remove(\"visible\"); });\n\n      wrap.addEventListener(\"click\", e => {\n        const g = e.target.closest && e.target.closest(\"g.afi-node\");\n        if (g) {\n          const id = g.getAttribute(\"data-id\");\n          if (_afiSel === id) { _afiClearHighlight(); _resetAfinidadDetail(); }\n          else { _afiHighlight(id); renderAfinidadDetail(id, af); }\n        } else { _afiClearHighlight(); _resetAfinidadDetail(); }\n      });\n      _resetAfinidadDetail();\n    } catch (err) {\n      console.error(\"[FREN] afinidad error:\", err);\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">The affinity graph could not be drawn.<\/p>';\n    }\n  }\n\n  \/\/ Resalta el nodo seleccionado, sus aristas y sus vecinos; aten\u00faa el resto.\n  let _afiSel = null;\n  function _afiHighlight(id) {\n    const svg = $(\"#afinidad-wrap .afi-svg\");\n    if (!svg || !id) return;\n    _afiSel = id;\n    const neighbors = new Set([id]);\n    svg.querySelectorAll(\"line.afi-link\").forEach(l => {\n      const on = l.getAttribute(\"data-src\") === id || l.getAttribute(\"data-tgt\") === id;\n      if (on) { neighbors.add(l.getAttribute(\"data-src\")); neighbors.add(l.getAttribute(\"data-tgt\")); }\n      const base = parseFloat(l.getAttribute(\"stroke-width\")) || 1.5;\n      l.style.stroke = on ? \"var(--orange)\" : \"#cbd5e1\";\n      l.style.strokeOpacity = on ? \"0.95\" : \"0.05\";\n      l.style.strokeWidth = on ? (base + 1.6).toFixed(2) : base.toFixed(2);\n    });\n    svg.querySelectorAll(\"g.afi-node\").forEach(g => {\n      g.style.opacity = neighbors.has(g.getAttribute(\"data-id\")) ? \"1\" : \"0.2\";\n    });\n  }\n  function _afiClearHighlight() {\n    _afiSel = null;\n    const svg = $(\"#afinidad-wrap .afi-svg\");\n    if (!svg) return;\n    svg.querySelectorAll(\"line.afi-link\").forEach(l => { l.style.stroke = \"\"; l.style.strokeOpacity = \"\"; l.style.strokeWidth = \"\"; });\n    svg.querySelectorAll(\"g.afi-node\").forEach(g => { g.style.opacity = \"\"; });\n  }\n  function _resetAfinidadDetail() {\n    const box = $(\"#afinidad-detail\");\n    if (box) box.innerHTML = '<p class=\"afi-detail-hint\">Click on a group to see its voting detail and its affinities with the rest.<\/p>';\n  }\n\n  function renderAfinidadDetail(id, af) {\n    const box = $(\"#afinidad-detail\");\n    if (!box || !af || !id) return;\n    const node = (af.nodes || []).find(n => n.id === id);\n    if (!node) return;\n    const labelOf = {};\n    (af.nodes || []).forEach(n => { labelOf[n.id] = n.label; });\n    const coes = (af.edges || [])\n      .filter(e => e.a === id || e.b === id)\n      .map(e => ({ other: e.a === id ? e.b : e.a, w: e.w, shared: e.shared }))\n      .sort((a, b) => b.w - a.w);\n    const v = node.votes || { favor: 0, contra: 0, abstencion: 0, no_voto: 0 };\n    const totalV = (v.favor + v.contra + v.abstencion + v.no_voto) || 1;\n    const voteRow = (lbl, val, cls) =>\n      `<div class=\"ad-vote ${cls}\"><span class=\"ad-vote-lbl\">${lbl}<\/span><span class=\"ad-vote-bar\"><span style=\"width:${Math.round(val \/ totalV * 100)}%\"><\/span><\/span><span class=\"ad-vote-val\">${val}<\/span><\/div>`;\n    box.innerHTML = `\n      <div class=\"ad-head\">\n        ${node.logo_url\n          ? `<img decoding=\"async\" class=\"ad-logo\" src=\"${escapeHtml(node.logo_url)}\" alt=\"\">`\n          : `<span class=\"ad-logo\" style=\"background:${escapeHtml(node.color || '#64748b')}\"><\/span>`}\n        <div>\n          <h4>${escapeHtml(node.label)}<\/h4>\n          <span class=\"ad-sub\">${node.size || 0} MPs \u00b7 ${node.n_votaciones || totalV} votes<\/span>\n        <\/div>\n        <button type=\"button\" class=\"saber-mas\" data-vc-contact=\"${escapeHtml(node.label)}\">Learn more \u2197<\/button>\n      <\/div>\n      <h5>How they vote (group majority)<\/h5>\n      ${voteRow(\"In favour\", v.favor, \"favor\")}\n      ${voteRow(\"Against\", v.contra, \"contra\")}\n      ${voteRow(\"Abstention\", v.abstencion, \"abs\")}\n      ${voteRow(\"Did not vote\", v.no_voto, \"nv\")}\n      <h5>Votes the same as\u2026<\/h5>\n      ${coes.length ? coes.map(c => `\n        <div class=\"ad-coes\">\n          <span class=\"ad-coes-lbl\" title=\"${escapeHtml(labelOf[c.other] || c.other)}\">${escapeHtml(_shortGroup(labelOf[c.other] || c.other))}<\/span>\n          <span class=\"ad-coes-bar\"><span style=\"width:${Math.round(c.w * 100)}%\"><\/span><\/span>\n          <span class=\"ad-coes-val\">${Math.round(c.w * 100)}%<\/span>\n        <\/div>`).join(\"\") : '<p class=\"muted-cell\">Not enough matches.<\/p>'}\n    `;\n  }\n\n  \/\/ Clic en un tema (nodo o flujo del Sankey) \u2192 detalle de intervinientes.\n  $(\"#temas-wrap\")?.addEventListener(\"click\", e => {\n    if (!e.target.closest) return;\n    const partido = e.target.closest(\"g.sk-partido\");\n    if (partido) { const pid = partido.getAttribute(\"data-party\"); _sankeyHighlight(\"data-party\", pid); renderPartidoDetail(pid); return; }\n    const el = e.target.closest(\"[data-topic]\");\n    if (el) {\n      const topic = el.getAttribute(\"data-topic\");\n      _sankeyHighlight(\"data-topic\", topic);\n      let t = _temasByName[topic];\n      if (!t && STATE.temas && STATE.temas.by_chamber) {\n        t = (STATE.temas.by_chamber[temasChamberKey] || STATE.temas.by_chamber.todos || []).find(x => x.topic === topic);\n      }\n      if (t) renderTemaDetail(t);\n      return;\n    }\n    _sankeyClearHighlight();\n  });\n\n  \/\/ ===== Modal de contacto (botones \"Saber m\u00e1s\") =====\n  function vcContact(label){\n    let m=document.getElementById(\"vcContactModal\");\n    if(!m){\n      m=document.createElement(\"div\"); m.className=\"vc-cmodal\"; m.id=\"vcContactModal\";\n      m.setAttribute(\"role\",\"dialog\"); m.setAttribute(\"aria-modal\",\"true\");\n      m.innerHTML='<div class=\"box\"><h4>Discover FREN<\/h4><p id=\"vcContactMsg\"><\/p><div class=\"acts\"><a class=\"cta\" href=\"https:\/\/fren.democrata.es\/\" target=\"_blank\" rel=\"noopener\">Try FREN<\/a><button class=\"x\" type=\"button\">Cerrar<\/button><\/div><\/div>';\n      document.body.appendChild(m);\n      m.addEventListener(\"click\",ev=>{ if(ev.target===m||ev.target.classList.contains(\"x\")){ m.classList.remove(\"open\"); m.setAttribute(\"aria-hidden\",\"true\"); } });\n      document.addEventListener(\"keydown\",ev=>{ if(ev.key===\"Escape\"){ m.classList.remove(\"open\"); m.setAttribute(\"aria-hidden\",\"true\"); } });\n    }\n    m.querySelector(\"#vcContactMsg\").textContent = label\n      ? 'Want the full analysis on \u201c'+label+'\u201d? Request a FREN demo and get the detail.'\n      : 'Request a FREN demo and get the full analysis.';\n    m.classList.add(\"open\"); m.setAttribute(\"aria-hidden\",\"false\");\n  }\n  document.addEventListener(\"click\", e=>{\n    const b = e.target.closest && e.target.closest(\"[data-vc-contact]\");\n    if(b){ e.preventDefault(); e.stopPropagation(); vcContact(b.getAttribute(\"data-vc-contact\")); }\n  });\n\n  \/\/ ===== Heatmap =====\n  function renderHeatmap(diario) {\n    if (!diario) {\n      $(\"#hm-scroll\").innerHTML = '<p class=\"empty\">No hay diario de sesiones disponible.<\/p>';\n      $(\"#hm-meta\").innerHTML = \"\";\n      return;\n    }\n    if (diario.analysis_pending || !diario.intervinientes?.length) {\n      const meta = $(\"#hm-meta\");\n      meta.innerHTML = `\n        ${diario.chamber ? `<span class=\"pill\">${escapeHtml(diario.chamber)}<\/span>` : \"\"}\n        ${diario.id ? `<span class=\"pill pill-id\">ID: ${diario.id}<\/span>` : \"\"}\n        <span>${escapeHtml(diario.title || \"\")}<\/span>\n        ${diario.comision ? `<span>\u00b7 ${escapeHtml(diario.comision)}<\/span>` : \"\"}\n        ${diario.fecha ? `<span>\u00b7 <time datetime=\"${escapeHtml(diario.fecha)}\">${fmtDate(diario.fecha)}<\/time><\/span>` : \"\"}\n      `;\n      const status = diario.analysis_status || \"pending\";\n      $(\"#hm-scroll\").innerHTML = `\n        <p class=\"empty\">\n          El an\u00e1lisis tem\u00e1tico de este Diario a\u00fan no est\u00e1 disponible (estado: <code>${escapeHtml(status)}<\/code>).\n          Se est\u00e1 calculando en segundo plano. Vuelve a recargar en unos minutos.\n        <\/p>`;\n      return;\n    }\n    const meta = $(\"#hm-meta\");\n    meta.innerHTML = `\n      <span class=\"pill\">${escapeHtml(diario.chamber || \"\")}<\/span>\n      ${diario.id ? `<span class=\"pill pill-id\">ID: ${diario.id}<\/span>` : \"\"}\n      ${diario.is_fallback ? `<span class=\"pill pill-fallback\" title=\"The latest Diario is still being processed; we show the most recent one with analysis available.\">Latest available<\/span>` : \"\"}\n      <span>${escapeHtml(diario.title || \"\")}<\/span>\n      ${diario.comision ? `<span>\u00b7 ${escapeHtml(diario.comision)}<\/span>` : \"\"}\n      <span>\u00b7 <time datetime=\"${escapeHtml(diario.fecha)}\">${fmtDate(diario.fecha)}<\/time><\/span>\n      ${diario.url_fuente ? `<span>\u00b7 <a href=\"${escapeHtml(diario.url_fuente)}\" rel=\"noopener\" target=\"_blank\">Official source<\/a><\/span>` : \"\"}\n    `;\n    const allTopics = diario.topics || [];\n    const intervs = diario.intervinientes || [];\n    const topicsWithData = allTopics\n      .map((topic, idx) => ({ topic, idx, hasData: intervs.some(p => (p.weights?.[idx] || 0) > 0) }))\n      .filter(t => t.hasData);\n    if (topicsWithData.length === 0) {\n      $(\"#hm-scroll\").innerHTML = '<p class=\"empty\">No hay datos tem\u00e1ticos para mostrar.<\/p>';\n      return;\n    }\n    let html = '<table class=\"heatmap\"><thead><tr><th scope=\"col\">Interviniente<\/th>';\n    topicsWithData.forEach(t => { html += `<th scope=\"col\" title=\"${escapeHtml(t.topic)}\">${escapeHtml(t.topic)}<\/th>`; });\n    html += \"<\/tr><\/thead><tbody>\";\n    intervs.forEach((p, rowIdx) => {\n      const photoHtml = p.photo_url\n        ? `<img decoding=\"async\" src=\"${escapeHtml(p.photo_url)}\" alt=\"\" class=\"avatar\" loading=\"lazy\">`\n        : `<span class=\"avatar-placeholder\">${escapeHtml(initials(p.name || \"?\"))}<\/span>`;\n      const groupLogoHtml = p.group_photo_url\n        ? `<img decoding=\"async\" src=\"${escapeHtml(p.group_photo_url)}\" alt=\"\" class=\"group-logo\" loading=\"lazy\">`\n        : \"\";\n      html += `<tr><th scope=\"row\" class=\"person-cell\">\n        <div class=\"person-info\">\n          ${photoHtml}\n          <div class=\"person-text\">\n            <span class=\"person-name\">${escapeHtml(p.name || \"\u2014\")}<\/span>\n            ${p.group ? `<span class=\"grupo\">${groupLogoHtml}${escapeHtml(p.group)}<\/span>` : \"\"}\n          <\/div>\n        <\/div>\n      <\/th>`;\n      topicsWithData.forEach(({ topic, idx: colIdx }) => {\n        const w = (p.weights && p.weights[colIdx]) || 0;\n        const lvl = levelFor(w);\n        const pct = Math.round(w * 100);\n        html += `<td class=\"cell\" data-level=\"${lvl}\" data-row=\"${rowIdx}\"\n                     data-topic=\"${escapeHtml(topic)}\" data-person=\"${escapeHtml(p.name || \"\")}\"\n                     title=\"${escapeHtml(p.name)} \u00b7 ${escapeHtml(topic)}: ${pct}%\"\n                     aria-label=\"${escapeHtml(p.name)} habl\u00f3 de ${escapeHtml(topic)} un ${pct} por ciento\">\n                  ${pct > 0 ? pct + \"%\" : \"\"}\n                <\/td>`;\n      });\n      html += \"<\/tr>\";\n    });\n    html += \"<\/tbody><\/table>\";\n    $(\"#hm-scroll\").innerHTML = html;\n    $(\"#hm-legend\").hidden = false;\n\n    $$(\"#hm-scroll td.cell\").forEach(td => {\n      td.addEventListener(\"click\", () => {\n        const row = parseInt(td.dataset.row, 10);\n        const topic = td.dataset.topic;\n        const person = td.dataset.person;\n        const snips = (intervs[row]?.snippets || {})[topic] || [];\n        $(\"#snippets-title\").textContent = `${person} sobre \"${topic}\"`;\n        $(\"#snippets-body\").innerHTML = snips.length\n          ? snips.map(s => `<blockquote>${escapeHtml(s)}<\/blockquote>`).join(\"\")\n          : '<p class=\"empty\">No hay extractos disponibles.<\/p>';\n        $(\"#snippets-dialog\").showModal();\n      });\n    });\n  }\n\n  \/\/ ===== Reparto tem\u00e1tico (bar chart horizontal nativo, sin Chart.js) =====\n  const FREN_DONUT_COLORS = [\"#1F2547\",\"#F3A53A\",\"#42507A\",\"#1FA886\",\"#7889B0\",\"#D88E25\",\"#5A8DC0\",\"#8A6B4A\",\"#6B8A7A\",\"#9B7B5A\",\"#B8C2D9\"];\n  function renderDonut(diario) {\n    const summary = $(\"#fren-summary\");\n    if (!diario?.topics?.length) { if (summary) summary.hidden = true; return; }\n    const allTotals = diario.topics.map((_, i) =>\n      (diario.intervinientes || []).reduce((acc, p) => acc + ((p.weights && p.weights[i]) || 0), 0)\n    );\n    const filtered = diario.topics\n      .map((topic, i) => ({ topic, total: allTotals[i] }))\n      .filter(item => item.total > 0)\n      .sort((a, b) => b.total - a.total);\n    if (filtered.length === 0) { if (summary) summary.hidden = true; return; }\n    if (summary) summary.hidden = false;\n    const total = filtered.reduce((a, b) => a + b.total, 0) || 1;\n    const colors = filtered.map((_, i) => FREN_DONUT_COLORS[i % FREN_DONUT_COLORS.length]);\n    \/\/ Ocultamos el donut antiguo si existe\n    const donutWrap = document.querySelector(\"#fren-summary .donut-mini\");\n    if (donutWrap) donutWrap.style.display = \"none\";\n\n    const list = $(\"#fren-top-temas\");\n    if (list) {\n      const top = filtered.slice(0, 8);\n      const maxPct = (top[0].total \/ total) * 100 || 1;\n      list.innerHTML = '<h4>Reparto tem\u00e1tico global<\/h4>' + top.map((f, i) => {\n        const pct = (f.total \/ total) * 100;\n        const w = (pct \/ maxPct) * 100;\n        return `\n        <div class=\"tema-bar-row\">\n          <span class=\"nm\" title=\"${escapeHtml(f.topic)}\">${escapeHtml(f.topic)}<\/span>\n          <span class=\"bar\"><span class=\"fill\" style=\"width:${w.toFixed(1)}%;background:${colors[i]}\"><\/span><\/span>\n          <span class=\"pct\">${pct.toFixed(1)}%<\/span>\n        <\/div>`;\n      }).join(\"\");\n    }\n  }\n\n  \/\/ ===== Hemiciclo =====\n  const HEMI_ROWS = [\n    { radius: 70,  count: 22 }, { radius: 90,  count: 28 }, { radius: 110, count: 34 },\n    { radius: 130, count: 40 }, { radius: 150, count: 46 }, { radius: 170, count: 52 },\n    { radius: 190, count: 58 }, { radius: 210, count: 62 }, { radius: 230, count: 8  },\n  ];\n  const HEMI_VIEWBOX = \"-10 -10 480 270\";\n  const HEMI_CENTER_X = 235, HEMI_CENTER_Y = 245, HEMI_SEAT_R = 5.2;\n  function _generateHemiSeatPositions() {\n    const positions = [];\n    HEMI_ROWS.forEach((row, rowIdx) => {\n      const rowPositions = [];\n      for (let i = 0; i < row.count; i++) {\n        const t = (i + 0.5) \/ row.count;\n        const angle = Math.PI - t * Math.PI;\n        const x = HEMI_CENTER_X + row.radius * Math.cos(angle);\n        const y = HEMI_CENTER_Y - row.radius * Math.sin(angle);\n        rowPositions.push({ x, y, angle, rowIdx });\n      }\n      positions.push(rowPositions);\n    });\n    return positions;\n  }\n  const HEMI_POS = _generateHemiSeatPositions();\n  const HEMI_FLAT = (() => {\n    const flat = [];\n    HEMI_POS.forEach((row, rowIdx) => {\n      if (rowIdx === HEMI_POS.length - 1) return;\n      row.forEach((p, i) => flat.push({ ...p, rowIdx, idxInRow: i }));\n    });\n    flat.sort((a, b) => (b.angle - a.angle) || (a.rowIdx - b.rowIdx));\n    return flat;\n  })();\n  const GROUP_ORDER = [\"bildu\",\"erc\",\"junts\",\"pnv\",\"sumar\",\"podemos\",\"psoe\",\"mixto\",\"ciudadanos\",\"pp\",\"vox\",\"otro\"];\n  function _voteColor(v) {\n    return ({ yes: \"#22C55E\", no: \"#EF4444\", abstention: \"#F59E0B\", \"no vote\": \"#111827\", \"no_vote\": \"#111827\" })[v] || \"#D1D5DB\";\n  }\n  function renderHemiciclo(votacion) {\n    const wrap = $(\"#hemi-wrap\"), meta = $(\"#hemi-meta\");\n    const totalsRow = $(\"#hemi-totals\"), groupsBox = $(\"#hemi-groups\");\n    if (!wrap) return;\n    if (!votacion) {\n      wrap.innerHTML = '<p class=\"empty\" style=\"padding:20px 0\">No hay votaciones recientes con detalle disponible.<\/p>';\n      meta.innerHTML = \"\"; totalsRow.hidden = true; groupsBox.innerHTML = \"\"; return;\n    }\n    const fecha = votacion.fecha ? fmtDate(votacion.fecha) : \"\";\n    const resultClass = votacion.result === \"aprobada\" ? \"aprobada\" : \"rechazada\";\n    const resultLabel = votacion.result === \"aprobada\" ? \"Aprobada\" : \"Rechazada\";\n    meta.innerHTML = `\n      <span class=\"pill ${resultClass}\">${resultLabel}<\/span>\n      <span class=\"pill\">Sesi\u00f3n ${votacion.session}, votaci\u00f3n n\u00ba ${votacion.num_voting}<\/span>\n      <span>\u00b7 <time datetime=\"${escapeHtml(votacion.fecha || '')}\">${escapeHtml(fecha)}<\/time><\/span>\n      ${votacion.legislature ? `<span>\u00b7 ${escapeHtml(votacion.legislature)} legislatura<\/span>` : \"\"}\n    `;\n    const subt = $(\"#hemi-subtitle\");\n    if (subt) {\n      const t = votacion.title || (votacion.initiative_title || \"\");\n      subt.textContent = t ? (t.length > 200 ? t.slice(0, 197) + \"\u2026\" : t) : \"Votaci\u00f3n m\u00e1s reciente del Pleno del Congreso.\";\n    }\n    totalsRow.hidden = false;\n    [\"yes\",\"no\",\"abstention\",\"no_vote\"].forEach(k => {\n      const el = totalsRow.querySelector(`[data-k=\"${k}\"]`);\n      if (el) el.textContent = (votacion.totals && votacion.totals[k] != null) ? votacion.totals[k] : 0;\n    });\n\n    const votes = (votacion.votes || []).filter(v => v && v.vote);\n    const buckets = {};\n    GROUP_ORDER.forEach(s => buckets[s] = []);\n    votes.forEach(v => {\n      const slug = v.group_slug || \"otro\";\n      if (!buckets[slug]) buckets[slug] = [];\n      buckets[slug].push(v);\n    });\n    Object.keys(buckets).forEach(s => buckets[s].sort((a,b) => (a.author_name || \"\").localeCompare(b.author_name || \"\")));\n    const orderedVotes = [];\n    GROUP_ORDER.forEach(s => { (buckets[s] || []).forEach(v => orderedVotes.push(v)); });\n    Object.keys(buckets).forEach(s => {\n      if (!GROUP_ORDER.includes(s)) buckets[s].forEach(v => orderedVotes.push(v));\n    });\n    const seats = HEMI_FLAT.map((pos, idx) => ({ pos, vote: orderedVotes[idx] || null }));\n\n    const svgParts = [];\n    svgParts.push(`<svg viewBox=\"${HEMI_VIEWBOX}\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-labelledby=\"hemi-title\">`);\n    svgParts.push(`<title>Hemiciclo del Congreso \u2013 \u00faltima votaci\u00f3n<\/title>`);\n    seats.forEach(({ pos, vote }) => {\n      const color = vote ? _voteColor(vote.vote) : \"#D1D5DB\";\n      const dataAttrs = vote\n        ? `data-name=\"${escapeHtml(vote.author_name || '')}\" data-group=\"${escapeHtml(vote.group_name || '')}\" data-vote=\"${escapeHtml(vote.vote)}\" data-logo=\"${escapeHtml(vote.group_logo_url || '')}\"`\n        : \"\";\n      svgParts.push(`<circle class=\"seat\" cx=\"${pos.x.toFixed(2)}\" cy=\"${pos.y.toFixed(2)}\" r=\"${HEMI_SEAT_R}\" fill=\"${color}\" stroke=\"#fff\" stroke-width=\".5\" ${dataAttrs}\/>`);\n    });\n    svgParts.push(`<\/svg>`);\n    svgParts.push(`<div class=\"hemi-tooltip\" id=\"hemi-tooltip\" role=\"tooltip\"><\/div>`);\n    wrap.innerHTML = svgParts.join(\"\");\n\n    const tip = $(\"#hemi-tooltip\");\n    let _hovered = null;\n    wrap.addEventListener(\"mouseover\", e => {\n      const c = e.target.closest(\"circle.seat\");\n      if (!c || c === _hovered) return;\n      _hovered = c;\n      const name = c.getAttribute(\"data-name\");\n      if (!name) { tip.classList.remove(\"visible\"); return; }\n      const group = c.getAttribute(\"data-group\") || \"\";\n      const logo = c.getAttribute(\"data-logo\") || \"\";\n      const vote = c.getAttribute(\"data-vote\") || \"\";\n      const voteCls = (vote.replace(\" \", \"_\")) || \"\";\n      const voteLabel = { yes:\"A favor\", no:\"En contra\", abstention:\"Abstenci\u00f3n\", \"no vote\":\"No vot\u00f3\", \"no_vote\":\"No vot\u00f3\" }[vote] || vote;\n      tip.innerHTML = `\n        <div class=\"ttl\">${escapeHtml(name)}<\/div>\n        ${group ? `<div class=\"grp\">${logo ? `<img decoding=\"async\" src=\"${escapeHtml(logo)}\" alt=\"\">` : \"\"}<span>${escapeHtml(group)}<\/span><\/div>` : \"\"}\n        <span class=\"vt ${voteCls}\">${escapeHtml(voteLabel)}<\/span>\n      `;\n      tip.classList.add(\"visible\");\n    });\n    wrap.addEventListener(\"mousemove\", e => {\n      if (!tip.classList.contains(\"visible\")) return;\n      const wrapRect = wrap.getBoundingClientRect();\n      const x = e.clientX - wrapRect.left, y = e.clientY - wrapRect.top;\n      const w = tip.offsetWidth || 240, h = tip.offsetHeight || 80;\n      let left = x + 14, top = y - h - 8;\n      if (left + w > wrapRect.width) left = x - w - 14;\n      if (top < 0) top = y + 14;\n      tip.style.left = left + \"px\"; tip.style.top = top + \"px\";\n    });\n    wrap.addEventListener(\"mouseout\", e => {\n      const to = e.relatedTarget;\n      if (!to || !to.closest || !to.closest(\".hemi-wrap\")) { _hovered = null; tip.classList.remove(\"visible\"); }\n      else if (!to.closest(\"circle.seat\")) { _hovered = null; tip.classList.remove(\"visible\"); }\n    });\n\n    groupsBox.innerHTML = (votacion.groups_breakdown || []).map(g => {\n      const majMap = { favor: \"\u2713 A favor\", contra: \"\u2717 En contra\", abstencion: \"\u25cb Abstenci\u00f3n\", no_voto: \"\u2014 No vot\u00f3\", mixto: \"Mixto\" };\n      const maj = majMap[g.majority_vote] || \"\";\n      const logo = g.logo_url\n        ? `<img decoding=\"async\" class=\"logo\" src=\"${escapeHtml(g.logo_url)}\" alt=\"${escapeHtml(g.group_name)}\" loading=\"lazy\">`\n        : `<span class=\"dot\" style=\"background:${escapeHtml(g.color || '#9aa0a6')}\"><\/span>`;\n      return `<span class=\"hemi-group\" title=\"${escapeHtml(g.group_name)} \u00b7 ${maj}\">\n        ${logo}\n        <span class=\"nm\">${escapeHtml(g.group_name)}<\/span>\n        <span class=\"ct\">(${g.total_members})<\/span>\n      <\/span>`;\n    }).join(\"\");\n  }\n\n  \/\/ ===== Pregunta del d\u00eda =====\n  function _extractPlainAnswer(html, maxLen) {\n    const tmp = document.createElement(\"div\");\n    tmp.innerHTML = html || \"\";\n    const txt = (tmp.textContent || \"\").trim().replace(\/\\s+\/g, \" \");\n    if (txt.length <= maxLen) return { preview: txt, isLong: false };\n    return { preview: txt.slice(0, maxLen).trim() + \"\u2026\", isLong: true };\n  }\n\n  function renderPreguntaDelDia(qa) {\n    const cont = $(\"#pregunta-content\");\n    if (!cont) return;\n    if (!qa || !qa.question) {\n      cont.innerHTML = '<p class=\"empty\" style=\"padding:24px 0\">Hoy no hay pregunta destacada disponible.<\/p>';\n      return;\n    }\n    const fechaNoticia = qa.noticia_date ? fmtDate(qa.noticia_date) : \"\";\n    const fuente = [];\n    if (qa.noticia_source) fuente.push(escapeHtml(qa.noticia_source));\n    if (fechaNoticia) fuente.push(`<time datetime=\"${escapeHtml(qa.noticia_date)}\">${escapeHtml(fechaNoticia)}<\/time>`);\n    const fuenteHtml = fuente.length ? fuente.join(\" \u00b7 \") : \"Noticia reciente\";\n\n    const { preview, isLong } = _extractPlainAnswer(qa.answer_html, 220);\n\n    cont.innerHTML = `\n      <article class=\"pregunta-card\" itemscope itemtype=\"https:\/\/schema.org\/Question\">\n        <div class=\"pregunta-meta\">Inspired by a news story today<\/div>\n        <h4 class=\"pregunta-q\" itemprop=\"name\">${escapeHtml(qa.question)}<\/h4>\n        <div class=\"pregunta-a-collapsed\">\n          <p class=\"pregunta-preview\">${escapeHtml(preview)}<\/p>\n          ${isLong ? `\n            <button type=\"button\" class=\"pregunta-toggle\" aria-expanded=\"false\">\n              See full answer\n            <\/button>\n            <div class=\"pregunta-a\" itemprop=\"acceptedAnswer\" itemscope itemtype=\"https:\/\/schema.org\/Answer\" hidden>\n              <div itemprop=\"text\">${qa.answer_html}<\/div>\n            <\/div>` : `\n            <div class=\"pregunta-a\" itemprop=\"acceptedAnswer\" itemscope itemtype=\"https:\/\/schema.org\/Answer\" hidden>\n              <meta itemprop=\"text\" content=\"${escapeHtml(preview)}\">\n            <\/div>`}\n        <\/div>\n        <div class=\"pregunta-fuente\">\n          <strong>Source:<\/strong>\n          ${qa.noticia_url\n            ? `<a href=\"${escapeHtml(qa.noticia_url)}\" rel=\"noopener\" target=\"_blank\">${escapeHtml(qa.noticia_title || 'Ver noticia')}<\/a> \u00b7 ${fuenteHtml}`\n            : `${escapeHtml(qa.noticia_title || 'Noticia reciente')} \u00b7 ${fuenteHtml}`}\n        <\/div>\n      <\/article>\n    `;\n\n    const toggle = cont.querySelector(\".pregunta-toggle\");\n    if (toggle) {\n      toggle.addEventListener(\"click\", () => {\n        const full = cont.querySelector(\".pregunta-a\");\n        const preview = cont.querySelector(\".pregunta-preview\");\n        const open = toggle.getAttribute(\"aria-expanded\") === \"true\";\n        if (open) {\n          full.hidden = true; preview.hidden = false;\n          toggle.setAttribute(\"aria-expanded\", \"false\");\n          toggle.textContent = \"See full answer\";\n        } else {\n          full.hidden = false; preview.hidden = true;\n          toggle.setAttribute(\"aria-expanded\", \"true\");\n          toggle.textContent = \"Hide answer\";\n        }\n      });\n    }\n  }\n\n  \/\/ ===== Tablas SEO =====\n  function renderSeoTables(data) {\n    const diarioBody = document.querySelector(\"#seo-table-diario tbody\");\n    if (diarioBody) {\n      const d = data.diario_sesiones;\n      if (!d || !(d.intervinientes || []).length) {\n        diarioBody.innerHTML = '<tr><td colspan=\"3\" class=\"muted-cell\">El \u00faltimo Diario de Sesiones a\u00fan se est\u00e1 procesando.<\/td><\/tr>';\n      } else {\n        const topics = d.topics || [];\n        diarioBody.innerHTML = (d.intervinientes || []).map(p => {\n          const tags = topics\n            .map((t, i) => ({ t, w: (p.weights || [])[i] || 0 }))\n            .filter(x => x.w > 0)\n            .sort((a, b) => b.w - a.w)\n            .map(x => `${escapeHtml(x.t)} (${Math.round(x.w * 100)}%)`)\n            .join(\", \");\n          return `<tr itemscope itemtype=\"https:\/\/schema.org\/Person\">\n            <td itemprop=\"name\">${escapeHtml(p.name || \"\u2014\")}<\/td>\n            <td itemprop=\"memberOf\">${escapeHtml(p.group || \"\u2014\")}<\/td>\n            <td>${tags || '<span class=\"muted-cell\">\u2014<\/span>'}<\/td>\n          <\/tr>`;\n        }).join(\"\");\n      }\n    }\n    const votBody = document.querySelector(\"#seo-table-votacion tbody\");\n    if (votBody) {\n      const v = data.ultima_votacion;\n      const groups = v && v.groups_breakdown || [];\n      if (!groups.length) {\n        votBody.innerHTML = '<tr><td colspan=\"7\" class=\"muted-cell\">No hay votaci\u00f3n reciente con detalle individual disponible.<\/td><\/tr>';\n      } else {\n        const majMap = { favor: \"A favor\", contra: \"En contra\", abstencion: \"Abstenci\u00f3n\", no_voto: \"No vot\u00f3\", mixto: \"Mixto\" };\n        votBody.innerHTML = groups.map(g => `\n          <tr>\n            <td><strong>${escapeHtml(g.group_name || \"\u2014\")}<\/strong><\/td>\n            <td>${(g.votes && g.votes.yes) || 0}<\/td>\n            <td>${(g.votes && g.votes.no) || 0}<\/td>\n            <td>${(g.votes && g.votes.abstention) || 0}<\/td>\n            <td>${(g.votes && g.votes.no_vote) || 0}<\/td>\n            <td>${g.total_members || 0}<\/td>\n            <td>${escapeHtml(majMap[g.majority_vote] || g.majority_vote || \"\u2014\")}<\/td>\n          <\/tr>`).join(\"\");\n      }\n    }\n  }\n\n  \/\/ ===== Load =====\n  function load() {\n    const apiUrl = getApiUrl();\n    loadDashboardData(apiUrl, data => {\n      STATE.temas = data.temas_calientes_diarios;\n      STATE.afinidad = data.afinidad_votos;\n      renderTemasCalientes(STATE.temas);\n      if (isPaneActive(\"pane-afinidad\")) renderAfinidad(STATE.afinidad);\n      renderPreguntaDelDia(data.pregunta_del_dia);\n      renderSeoTables(data);\n      setUpdatedPill(data, null);\n    }, (msg, kind) => {\n      setUpdatedPill(null, kind === \"network\" ? \"Connection error\" : kind === \"http\" ? \"HTTP error\" : \"Error\");\n      const w = $(\"#temas-wrap\"); if (w) w.innerHTML = `<p class=\"error\">${msg}<\/p>`;\n    });\n  }\n\n  $(\"#snippets-close\").addEventListener(\"click\", () => $(\"#snippets-dialog\").close());\n  $(\"#snippets-dialog\").addEventListener(\"click\", e => {\n    if (e.target === $(\"#snippets-dialog\")) $(\"#snippets-dialog\").close();\n  });\n  $(\"#reload-btn\")?.addEventListener(\"click\", load);\n  $(\"#diario-id-input\")?.addEventListener(\"keypress\", e => {\n    if (e.key === \"Enter\") { e.preventDefault(); load(); }\n  });\n\n  window.VincesDash.onPaneActivated = window.VincesDash.onPaneActivated || {};\n  window.VincesDash.onPaneActivated[\"pane-temas\"] = () => renderTemasCalientes(STATE.temas);\n  window.VincesDash.onPaneActivated[\"pane-afinidad\"] = () => renderAfinidad(STATE.afinidad);\n\n  initCarousels();\n  const absUrl = `${window.location.protocol}\/\/${window.location.host}\/api\/dashboard-publico\/fren\/`;\n  bindDownloadExternalHtml(absUrl);\n\n  load();\n  setInterval(load, window.VincesDash.REFRESH_MS);\n})();\n<\/script>\n\n\n<script>\n(function(){\n  function wrapHeroRight(){\n    return; \/* hero ya estructurado con .vc-hero-actions + rueda *\/\n    var pill = document.getElementById('updated-pill');\n    var cta = document.querySelector('.vc-hero-cta');\n    if (!pill || !cta || cta.parentNode.classList.contains('vc-hero-right')) return;\n    var wrap = document.createElement('div');\n    wrap.className = 'vc-hero-right';\n    cta.parentNode.insertBefore(wrap, cta);\n    wrap.appendChild(pill);\n    wrap.appendChild(cta);\n  }\n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', wrapHeroRight);\n  } else {\n    wrapHeroRight();\n  }\n})();\n<\/script>\n\n<\/body><\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>FREN: Diario de Sesiones, Congress votes and question of the day \u00b7 AI regulatory intelligence \u00b7 Vinces FREN \u00b7 AI for Public Affairs All of Spanish politics, in one question FREN is the AI assistant specialised in politics, regulation and public affairs. Ask it in natural language and get reasoned answers \u2014with charts, hemicycles and [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4958,"parent":11881,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-11077","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/pages\/11077","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/comments?post=11077"}],"version-history":[{"count":0,"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/pages\/11077\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/pages\/11881"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/media\/4958"}],"wp:attachment":[{"href":"https:\/\/vincesconsulting.com\/en\/wp-json\/wp\/v2\/media?parent=11077"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}