PageSourceSearch

https://www.enisa.es/assets/js/enisa-main.js

js enisa.es collected 2026-10-02 02:53:15 UTC 469,631 bytes, 17,003 lines download raw bytes

1// JS: enisa main. Compilado automaticamente desde js/modules/.
2// Compilado automaticamente. No editar directamente.
3// Regenerar con: pwsh js/build.ps1
4
5/*
6  ENISA | JS Module
7  Modulo: 01-utils
8  Descripcion: Logica del modulo.
9*/
10document.documentElement.classList.add("js");
11
12(function () {
13  var root = document.documentElement;
14  var keyboardIntentKeys = {
15    Tab: true,
16    Enter: true,
17    " ": true,
18    Spacebar: true,
19    ArrowUp: true,
20    ArrowRight: true,
21    ArrowDown: true,
22    ArrowLeft: true,
23    Home: true,
24    End: true,
25    PageUp: true,
26    PageDown: true,
27    Escape: true
28  };
29
30  function hasKeyboardIntent(event) {
31    if (!event || event.altKey || event.ctrlKey || event.metaKey) return false;
32    return keyboardIntentKeys[event.key] === true;
33  }
34
35  function enableKeyboardFocus(event) {
36    if (!hasKeyboardIntent(event)) return;
37    root.classList.add("enisa-using-keyboard");
38  }
39
40  function disableKeyboardFocus() {
41    root.classList.remove("enisa-using-keyboard");
42  }
43
44  window.addEventListener("keydown", enableKeyboardFocus, true);
45  window.addEventListener("mousedown", disableKeyboardFocus, true);
46  window.addEventListener("pointerdown", disableKeyboardFocus, true);
47  window.addEventListener("touchstart", disableKeyboardFocus, true);
48})();
49
50(function () {
51  var BREAKPOINTS = {
52    mobile: 768,
53    tablet: 1023.98,
54    desktop: 1279.98,
55    newsCarousel: 768
56  };
57
58  function prefersReducedMotion() {
59    return !!(
60      window.matchMedia &&
61      window.matchMedia("(prefers-reduced-motion: reduce)").matches
62    );
63  }
64
65  function isMobileViewport(bp) {
66    var threshold = bp !== undefined ? bp : BREAKPOINTS.mobile;
67    if (window.matchMedia) return window.matchMedia("(max-width: " + threshold + "px)").matches;
68    return window.innerWidth <= threshold;
69  }
70
71  function debounce(fn, ms) {
72    var timer;
73    return function () {
74      var args = arguments;
75      var context = this;
76
77      clearTimeout(timer);
78      timer = setTimeout(function () {
79        fn.apply(context, args);
80      }, ms !== undefined ? ms : 150);
81    };
82  }
83
84  var FOCUSABLE_SELECTOR = [
85    "a[href]",
86    "area[href]",
87    "button:not([disabled])",
88    "input:not([disabled]):not([type='hidden'])",
89    "select:not([disabled])",
90    "textarea:not([disabled])",
91    "summary",
92    "iframe",
93    "object",
94    "embed",
95    "[contenteditable='true']",
96    "[tabindex]:not([tabindex='-1'])"
97  ].join(",");
98
99  var PROGRAMMATIC_FOCUS_SELECTOR = FOCUSABLE_SELECTOR + ",[tabindex='-1']";
100  var supportsPreventScroll = null;
101
102  function detectPreventScrollSupport() {
103    var supported = false;
104    var probe;
105    var options;
106
107    if (supportsPreventScroll !== null) return supportsPreventScroll;
108
109    try {
110      probe = document.createElement("button");
111      probe.type = "button";
112      probe.style.cssText = "position:absolute;left:-9999px;top:0;width:1px;height:1px;";
113      options = Object.defineProperty({}, "preventScroll", {
114        get: function () {
115          supported = true;
116          return true;
117        }
118      });
119
120      document.body.appendChild(probe);
121      probe.focus(options);
122      probe.remove();
123    } catch (error) {
124      if (probe && probe.parentNode) probe.parentNode.removeChild(probe);
125    }
126
127    supportsPreventScroll = supported;
128    return supportsPreventScroll;
129  }
130
131  function isElementVisible(element) {
132    if (!element || element.hidden) return false;
133    if (element.getAttribute("aria-hidden") === "true") return false;
134    if (element.closest("[hidden], [aria-hidden='true'], [inert]")) return false;
135    if (typeof window.getComputedStyle === "function") {
136      var style = window.getComputedStyle(element);
137      if (style.display === "none" || style.visibility === "hidden") return false;
138    }
139    if (
140      element !== document.documentElement &&
141      element !== document.body &&
142      element.getClientRects &&
143      element.getClientRects().length === 0 &&
144      element !== document.activeElement
145    ) {
146      return false;
147    }
148    return true;
149  }
150
151  function getFocusableElements(scope, includeHidden) {
152    if (!scope || !scope.querySelectorAll) return [];
153
154    return [].slice.call(scope.querySelectorAll(FOCUSABLE_SELECTOR)).filter(function (element) {
155      var tabindex = element.getAttribute("tabindex");
156
157      if (tabindex && parseInt(tabindex, 10) < 0) return false;
158      if (element.disabled || element.getAttribute("aria-disabled") === "true") return false;
159      if (!includeHidden && !isElementVisible(element)) return false;
160
161      return true;
162    });
163  }
164
165  function focusWithoutScroll(element) {
166    return safeFocus(element);
167  }
168
169  function canReceiveFocus(element) {
170    if (!element || typeof element.focus !== "function") return false;
171    if (element.disabled || element.getAttribute("aria-disabled") === "true") return false;
172    if (!isElementVisible(element)) return false;
173    if (!element.matches) return false;
174
175    return element.matches(PROGRAMMATIC_FOCUS_SELECTOR);
176  }
177
178  function safeFocus(element, options) {
179    var opts = options || {};
180    var preventScroll = opts.preventScroll !== false;
181    var scrollX = window.scrollX || window.pageXOffset || 0;
182    var scrollY = window.scrollY || window.pageYOffset || 0;
183
184    if (!canReceiveFocus(element)) return false;
185
186    try {
187      if (preventScroll && detectPreventScrollSupport()) {
188        element.focus({ preventScroll: true });
189      } else {
190        element.focus();
191        if (preventScroll) restoreScrollPosition(scrollY, scrollX);
192      }
193    } catch (error) {
194      element.focus();
195      if (preventScroll) restoreScrollPosition(scrollY, scrollX);
196    }
197
198    if (preventScroll && ((window.scrollY || window.pageYOffset || 0) !== scrollY || (window.scrollX || window.pageXOffset || 0) !== scrollX)) {
199      restoreScrollPosition(scrollY, scrollX);
200    }
201
202    return true;
203  }
204
205  function focusIfActiveInside(container, fallback) {
206    if (
207      !container ||
208      !fallback ||
209      typeof fallback.focus !== "function" ||
210      !container.contains(document.activeElement)
211    ) {
212      return false;
213    }
214
215    return safeFocus(fallback);
216  }
217
218  function restoreScrollPosition(scrollY, scrollX) {
219    var top = Math.max(0, scrollY || 0);
220    var left = Math.max(0, scrollX || window.scrollX || window.pageXOffset || 0);
221
222    try {
223      window.scrollTo({ top: top, left: left, behavior: "auto" });
224    } catch (error) {
225      window.scrollTo(left, top);
226    }
227  }
228
229  function setSubtreeTabbable(root, isTabbable) {
230    if (!root || !root.querySelectorAll) return;
231
232    var focusables = [].slice.call(root.querySelectorAll(FOCUSABLE_SELECTOR));
233
234    focusables.forEach(function (element) {
235      if (isTabbable) {
236        if (element.hasAttribute("data-enisa-prev-tabindex")) {
237          var previous = element.getAttribute("data-enisa-prev-tabindex");
238
239          if (previous === "__none__") {
240            element.removeAttribute("tabindex");
241          } else {
242            element.setAttribute("tabindex", previous);
243          }
244
245          element.removeAttribute("data-enisa-prev-tabindex");
246        }
247
248        return;
249      }
250
251      if (!element.hasAttribute("data-enisa-prev-tabindex")) {
252        element.setAttribute(
253          "data-enisa-prev-tabindex",
254          element.hasAttribute("tabindex") ? element.getAttribute("tabindex") : "__none__"
255        );
256      }
257
258      element.setAttribute("tabindex", "-1");
259    });
260  }
261
262  function getFocusableOutside(excludedRoot) {
263    var candidates = [].slice.call(document.querySelectorAll(FOCUSABLE_SELECTOR));
264
265    return candidates.filter(function (candidate) {
266      return (
267        candidate &&
268        candidate !== excludedRoot &&
269        (!excludedRoot || !excludedRoot.contains(candidate)) &&
270        canReceiveFocus(candidate)
271      );
272    })[0] || null;
273  }
274
275  function setInert(element, isInert) {
276    if (!element) return;
277
278    if (isInert) {
279      if (element.contains(document.activeElement)) {
280        if (!safeFocus(getFocusableOutside(element)) && document.activeElement && typeof document.activeElement.blur === "function") {
281          document.activeElement.blur();
282        }
283      }
284
285      if (!element.hasAttribute("data-enisa-prev-aria-hidden")) {
286        element.setAttribute(
287          "data-enisa-prev-aria-hidden",
288          element.hasAttribute("aria-hidden") ? element.getAttribute("aria-hidden") : "__none__"
289        );
290      }
291
292      element.setAttribute("aria-hidden", "true");
293      setSubtreeTabbable(element, false);
294
295      if ("inert" in element) {
296        element.inert = true;
297      }
298
299      return;
300    }
301
302    if (element.hasAttribute("data-enisa-prev-aria-hidden")) {
303      var previous = element.getAttribute("data-enisa-prev-aria-hidden");
304
305      if (previous === "__none__") {
306        element.removeAttribute("aria-hidden");
307      } else {
308        element.setAttribute("aria-hidden", previous);
309      }
310
311      element.removeAttribute("data-enisa-prev-aria-hidden");
312    }
313
314    setSubtreeTabbable(element, true);
315
316    if ("inert" in element) {
317      element.inert = false;
318    }
319  }
320
321  function getHeaderOffset() {
322    var header = document.querySelector("[data-header-root], .enisa-header");
323    var style;
324
325    if (!header) return 0;
326
327    style = window.getComputedStyle ? window.getComputedStyle(header) : null;
328    if (!style || (style.position !== "fixed" && style.position !== "sticky")) return 0;
329
330    return Math.ceil(header.getBoundingClientRect().height);
331  }
332
333  function scrollToElement(element, options) {
334    var opts = options || {};
335    var block = opts.block || "nearest";
336    var extraOffset = opts.offset || 16;
337    var behavior = prefersReducedMotion() ? "auto" : (opts.behavior || "smooth");
338    var rect;
339    var headerOffset;
340    var viewportHeight;
341    var top;
342    var bottom;
343
344    if (!element || !element.getBoundingClientRect || !isElementVisible(element)) return false;
345
346    rect = element.getBoundingClientRect();
347    headerOffset = getHeaderOffset();
348    viewportHeight = window.innerHeight || document.documentElement.clientHeight;
349    top = rect.top - headerOffset - extraOffset;
350    bottom = rect.bottom + extraOffset;
351
352    if (top >= 0 && bottom <= viewportHeight) return false;
353
354    if (block === "nearest" && rect.top < headerOffset + extraOffset) {
355      top = Math.max(0, rect.top + window.pageYOffset - headerOffset - extraOffset);
356    } else if (block === "nearest") {
357      top = Math.max(0, rect.bottom + window.pageYOffset - viewportHeight + extraOffset);
358    } else {
359      top = Math.max(0, rect.top + window.pageYOffset - headerOffset - extraOffset);
360    }
361
362    try {
363      window.scrollTo({ top: top, left: window.pageXOffset || 0, behavior: behavior });
364    } catch (error) {
365      window.scrollTo(window.pageXOffset || 0, top);
366    }
367
368    return true;
369  }
370
371  function createScrollCarousel(opts) {
372    opts = opts || {};
373
374    var viewport  = opts.viewport;
375    var items     = opts.items     || [];
376    var prevBtn   = opts.prevBtn   || null;
377    var nextBtn   = opts.nextBtn   || null;
378    var dots      = opts.dots      || [];
379    var dotClass  = opts.activeDotClass || 'is-active';
380    var maxVisibleDots = Math.max(1, opts.maxVisibleDots || 6);
381    var perPage   = Math.max(1, opts.perPage || 1);
382    var current   = 0;
383    var ticking   = false;
384    var dotWindowStart = 0;
385    var DOT_INIT_ATTR = "data-enisa-carousel-dot-init";
386
387    function clamp(n) {
388      return Math.max(0, Math.min(items.length - 1, n));
389    }
390
391    function numPages() {
392      return Math.ceil(items.length / perPage);
393    }
394
395    function getDotPageIndex(dotIndex) {
396      return Math.floor(dotIndex / Math.max(1, perPage));
397    }
398
399    function syncDotState(dot, active) {
400      if (!dot) return;
401
402      dot.classList.toggle(dotClass, active);
403      dot.setAttribute('aria-current', active ? 'true' : 'false');
404
405      if (dot.getAttribute('role') === 'tab' || dot.hasAttribute('aria-selected')) {
406        dot.setAttribute('aria-selected', active ? 'true' : 'false');
407      }
408
409      if (!dot.disabled && !dot.hidden) {
410        dot.setAttribute('tabindex', active ? '0' : '-1');
411      } else {
412        dot.setAttribute('tabindex', '-1');
413      }
414    }
415
416    function getVisibleDots() {
417      return dots.filter(function (dot) {
418        return dot && !dot.hidden && dot.style.display !== 'none' && !dot.disabled;
419      });
420    }
421
422    function updateDotWindow(page) {
423      var availableDots = dots.filter(function (dot) {
424        return dot && !dot.hidden;
425      });
426      var maxWindowStart = Math.max(0, availableDots.length - maxVisibleDots);
427
428      if (availableDots.length <= maxVisibleDots) {
429        dotWindowStart = 0;
430      } else if (page <= dotWindowStart) {
431        dotWindowStart = page;
432      } else if (page >= dotWindowStart + maxVisibleDots - 1) {
433        dotWindowStart = page;
434      }
435
436      dotWindowStart = Math.max(0, Math.min(dotWindowStart, maxWindowStart));
437
438      var visibleStart = dotWindowStart;
439      var visibleEnd = visibleStart + maxVisibleDots;
440      var availableIndex = 0;
441
442      dots.forEach(function (dot) {
443        if (!dot) return;
444        if (dot.hidden) {
445          dot.style.display = '';
446          return;
447        }
448
449        var isVisible = availableIndex >= visibleStart && availableIndex < visibleEnd;
450        dot.style.display = isVisible ? '' : 'none';
451        availableIndex += 1;
452      });
453    }
454
455    function updateState(index) {
456      current = clamp(index);
457      var page = Math.floor(current / perPage);
458      var pages = numPages();
459      if (prevBtn) prevBtn.disabled = page === 0;
460      if (nextBtn) nextBtn.disabled = page >= pages - 1;
461      updateDotWindow(page);
462      dots.forEach(function (dot, i) {
463        syncDotState(dot, i === page);
464      });
465    }
466
467    function goTo(index, behavior) {
468      var next = clamp(index);
469      var item = items[next];
470      if (!item) return;
471      var offset = next === 0 ? 0 : (
472        (window.ENISA && window.ENISA.cardsCarousel && typeof window.ENISA.cardsCarousel.getCardOffset === "function")
473          ? window.ENISA.cardsCarousel.getCardOffset(viewport, item)
474          : (function () {
475              var vRect = viewport.getBoundingClientRect();
476              var iRect = item.getBoundingClientRect();
477              return iRect.left - vRect.left + viewport.scrollLeft;
478            }())
479      );
480
481      viewport.scrollTo({
482        left: offset,
483        top: 0,
484        behavior: behavior || (prefersReducedMotion() ? 'auto' : 'smooth')
485      });
486      updateState(next);
487    }
488
489    function syncFromScroll() {
490      var maxScroll = viewport.scrollWidth - viewport.clientWidth;
491      if (maxScroll > 0 && viewport.scrollLeft >= maxScroll - 1) {
492        updateState(items.length - 1);
493        return;
494      }
495      var vLeft   = viewport.getBoundingClientRect().left;
496      var closest = 0;
497      var minDist = Infinity;
498      items.forEach(function (item, i) {
499        var dist = Math.abs(item.getBoundingClientRect().left - vLeft);
500        if (dist < minDist) { minDist = dist; closest = i; }
501      });
502      updateState(closest);
503    }
504
505    function reset() {
506      viewport.scrollLeft = 0;
507      updateState(0);
508    }
509
510    function wireDots() {
511      dots.forEach(function (dot, i) {
512        if (!dot || dot.getAttribute(DOT_INIT_ATTR) === 'true') return;
513
514        dot.setAttribute(DOT_INIT_ATTR, 'true');
515        dot.addEventListener('click', function () { goTo(i * perPage); });
516        dot.addEventListener('keydown', function (event) {
517          var visibleDots = getVisibleDots();
518          var currentDotIndex = visibleDots.indexOf(dot);
519          var nextDotIndex = currentDotIndex;
520
521          if (!visibleDots.length || currentDotIndex === -1) return;
522
523          switch (event.key) {
524            case 'ArrowRight':
525            case 'ArrowDown':
526              nextDotIndex = (currentDotIndex + 1) % visibleDots.length;
527              break;
528            case 'ArrowLeft':
529            case 'ArrowUp':
530              nextDotIndex = (currentDotIndex - 1 + visibleDots.length) % visibleDots.length;
531              break;
532            case 'Home':
533              nextDotIndex = 0;
534              break;
535            case 'End':
536              nextDotIndex = visibleDots.length - 1;
537              break;
538            default:
539              return;
540          }
541
542          event.preventDefault();
543          goTo(getDotPageIndex(dots.indexOf(visibleDots[nextDotIndex])) * perPage);
544          safeFocus(visibleDots[nextDotIndex]);
545        });
546      });
547    }
548
549    viewport.addEventListener('scroll', function () {
550      if (ticking) return;
551      ticking = true;
552      window.requestAnimationFrame(function () { syncFromScroll(); ticking = false; });
553    }, { passive: true });
554
555    if (prevBtn) prevBtn.addEventListener('click', function () {
556      goTo(Math.max(0, current - perPage));
557    });
558    if (nextBtn) nextBtn.addEventListener('click', function () {
559      goTo(Math.min(items.length - 1, current + perPage));
560    });
561    wireDots();
562
563    function setDots(newDots, newClass) {
564      dots = newDots || [];
565      if (newClass) dotClass = newClass;
566      dotWindowStart = 0;
567      wireDots();
568      updateState(current);
569    }
570
571    function setPerPage(n) {
572      perPage = Math.max(1, n || 1);
573      updateState(current);
574    }
575
576    function sync() { syncFromScroll(); }
577
578    updateState(0);
579    return { goTo: goTo, reset: reset, updateState: updateState, sync: sync, setDots: setDots, setPerPage: setPerPage };
580  }
581
582  function clearButtonProgressTimer(button) {
583    if (!button || !button.__enisaProgressTimer) return;
584    window.clearInterval(button.__enisaProgressTimer);
585    button.__enisaProgressTimer = null;
586  }
587
588  function resetButtonProgress(button) {
589    if (!button) return;
590    clearButtonProgressTimer(button);
591    button.style.removeProperty("--enisa-btn-loader-overlay-w");
592  }
593
594  function startButtonProgress(button) {
595    var current = 8;
596    var target = 86;
597
598    if (!button) return;
599
600    resetButtonProgress(button);
601    button.style.setProperty("--enisa-btn-loader-overlay-w", current + "%");
602
603    button.__enisaProgressTimer = window.setInterval(function () {
604      current = Math.min(target, current + Math.max((target - current) * 0.18, 1.4));
605      button.style.setProperty("--enisa-btn-loader-overlay-w", current.toFixed(2) + "%");
606
607      if (current >= target) {
608        clearButtonProgressTimer(button);
609      }
610    }, 160);
611  }
612
613  function completeButtonProgress(button) {
614    if (!button) return;
615    clearButtonProgressTimer(button);
616    button.style.setProperty("--enisa-btn-loader-overlay-w", "100%");
617  }
618
619  window.ENISA = window.ENISA || {};
620  window.ENISA.utils = {
621    BREAKPOINTS: BREAKPOINTS,
622    prefersReducedMotion: prefersReducedMotion,
623    isMobileViewport: isMobileViewport,
624    debounce: debounce,
625    getFocusableElements: getFocusableElements,
626    isElementVisible: isElementVisible,
627    safeFocus: safeFocus,
628    focusWithoutScroll: focusWithoutScroll,
629    focusIfActiveInside: focusIfActiveInside,
630    restoreScrollPosition: restoreScrollPosition,
631    scrollToElement: scrollToElement,
632    setSubtreeTabbable: setSubtreeTabbable,
633    setInert: setInert,
634    createScrollCarousel: createScrollCarousel,
635    startButtonProgress: startButtonProgress,
636    completeButtonProgress: completeButtonProgress,
637    resetButtonProgress: resetButtonProgress
638  };
639})();
640
641(function () {
642  "use strict";
643
644  var MOBILE_CARDS_BP = 768;
645
646  function isMobileCardsViewport() {
647    if (window.matchMedia) return window.matchMedia("(max-width: 768px)").matches;
648    return window.innerWidth <= MOBILE_CARDS_BP;
649  }
650
651  window.ENISA = window.ENISA || {};
652  window.ENISA.cardsCarousel = window.ENISA.cardsCarousel || {};
653  window.ENISA.cardsCarousel.MOBILE_BP = MOBILE_CARDS_BP;
654  window.ENISA.cardsCarousel.isMobileViewport = isMobileCardsViewport;
655  window.ENISA.cardsCarousel.getCardOffset = function (viewport, item) {
656    if (!viewport || !item) return 0;
657    var vRect = viewport.getBoundingClientRect();
658    var iRect = item.getBoundingClientRect();
659    var styles = window.getComputedStyle ? window.getComputedStyle(viewport) : null;
660    var paddingLeft = styles ? parseFloat(styles.paddingLeft || "0") : 0;
661    var scrollPaddingLeft = styles
662      ? parseFloat(styles.scrollPaddingInlineStart || styles.scrollPaddingLeft)
663      : NaN;
664    var leadingGutter = isNaN(scrollPaddingLeft) ? (paddingLeft || 0) : scrollPaddingLeft;
665
666    return Math.max(0, iRect.left - vRect.left + viewport.scrollLeft - leadingGutter);
667  };
668  window.ENISA.cardsCarousel.syncEqualHeights = function (items) {
669    if (!items || !items.length) return;
670
671    items.forEach(function (item) {
672      if (!item || !item.style) return;
673      item.style.minHeight = "";
674      item.style.height = "";
675    });
676
677    var tallest = 0;
678    items.forEach(function (item) {
679      if (!item) return;
680      var nextHeight = Math.ceil(item.getBoundingClientRect().height);
681      if (nextHeight > tallest) tallest = nextHeight;
682    });
683
684    if (!tallest) return;
685
686    items.forEach(function (item) {
687      if (!item || !item.style) return;
688      item.style.minHeight = tallest + "px";
689    });
690  };
691})();
692
693/*
694  ENISA | JS Module
695  Modulo: 02-skip-link
696  Descripcion:
697  Gestion robusta del skip link en Power Pages.
698  - El skip link apunta siempre a #main-content.
699  - No depende de que main sea el siguiente nodo tras header.
700  - Enfoca el primer contenido util dentro del main.
701  - Respeta modales de cookies abiertos.
702*/
703(function () {
704  "use strict";
705
706  var SKIP_SELECTOR = ".enisa-skip-link";
707  var SKIP_TARGET_ID = "main-content";
708  var SKIP_TARGET_HASH = "#" + SKIP_TARGET_ID;
709
710  var HEADER_SELECTOR = "[data-header-root], .enisa-header, header[role='banner'], body > header";
711  var FOOTER_SELECTOR = ".enisa-footer, footer";
712  var GLOBAL_NAV_SELECTOR = ".enisa-header, .enisa-nav, .navbar, .enisa-header__mobile-nav, [data-header-root]";
713  var COOKIE_SELECTOR = "[data-cookie-consent], .cmp-cookie-consent";
714
715  var INIT_ATTR = "data-skip-init";
716  var FOCUS_ADDED_ATTR = "data-enisa-skip-focus-added";
717
718  var LANDING_TARGET_SELECTOR = [
719    "[data-enisa-skip-target]",
720    "[data-skip-target]",
721    "h1",
722    "h2",
723    "h3"
724  ].join(",");
725
726  var FOCUSABLE_SELECTOR = [
727    "a[href]",
728    "button:not([disabled])",
729    "input:not([disabled]):not([type='hidden'])",
730    "select:not([disabled])",
731    "textarea:not([disabled])",
732    "summary",
733    "[tabindex]:not([tabindex='-1'])"
734  ].join(",");
735
736  var NON_CONTENT_SELECTOR = [
737    "script",
738    "style",
739    "template",
740    "[hidden]",
741    "[aria-hidden='true']",
742    "[inert]",
743    "header",
744    "footer",
745    "nav",
746    "[role='navigation']",
747    "[role='banner']",
748    "[role='contentinfo']",
749    "[data-header-root]",
750    ".enisa-header",
751    ".enisa-nav",
752    ".navbar",
753    ".enisa-header__mobile-nav",
754    ".enisa-overlay",
755    "#searchOverlay",
756    ".enisa-global-breadcrumb",
757    ".breadcrumb",
758    ".cmp-cookie-consent",
759    "[data-cookie-consent]",
760    ".enisa-skip-link",
761    ".sr-only",
762    ".enisa-sr-only",
763    ".u-visually-hidden",
764    ".xrm-attribute",
765    ".xrm-editable-html"
766  ].join(",");
767
768  function toArray(list) {
769    return Array.prototype.slice.call(list || []);
770  }
771
772  function normalizeSpaces(value) {
773    return String(value || "").replace(/\s+/g, " ").trim();
774  }
775
776  function isElementVisible(element) {
777    var style;
778
779    if (!element || element.nodeType !== 1) return false;
780    if (element.hidden || element.getAttribute("aria-hidden") === "true") return false;
781    if (element.closest("[hidden], [aria-hidden='true'], [inert]")) return false;
782
783    if (window.getComputedStyle) {
784      style = window.getComputedStyle(element);
785      if (style.display === "none" || style.visibility === "hidden") return false;
786    }
787
788    if (element.getClientRects && element.getClientRects().length === 0) return false;
789
790    return true;
791  }
792
793  function isNonContentElement(element) {
794    return !!(
795      element &&
796      element.nodeType === 1 &&
797      element.matches &&
798      element.matches(NON_CONTENT_SELECTOR)
799    );
800  }
801
802  function isInsideNonContent(element, root) {
803    var current = element;
804
805    while (current && current !== root && current.nodeType === 1) {
806      if (isNonContentElement(current)) return true;
807      current = current.parentElement;
808    }
809
810    return false;
811  }
812
813  function getUsefulText(element) {
814    var walker;
815    var parts = [];
816
817    if (!element) return "";
818
819    if (!document.createTreeWalker) {
820      return normalizeSpaces(element.textContent);
821    }
822
823    walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, {
824      acceptNode: function (node) {
825        var parent = node.parentElement;
826
827        if (!parent || isInsideNonContent(parent, element)) {
828          return NodeFilter.FILTER_REJECT;
829        }
830
831        return normalizeSpaces(node.nodeValue).length > 0
832          ? NodeFilter.FILTER_ACCEPT
833          : NodeFilter.FILTER_REJECT;
834      }
835    });
836
837    while (walker.nextNode()) {
838      parts.push(walker.currentNode.nodeValue);
839    }
840
841    return normalizeSpaces(parts.join(" "));
842  }
843
844  function hasUsefulContent(element) {
845    if (!isElementVisible(element)) return false;
846    if (isNonContentElement(element)) return false;
847
848    return getUsefulText(element).length > 0;
849  }
850
851  function canUseAsLandingTarget(element, main) {
852    if (!element || !main) return false;
853    if (!hasUsefulContent(element)) return false;
854    if (isInsideNonContent(element, main)) return false;
855
856    return true;
857  }
858
859  function getHeader() {
860    return document.querySelector(HEADER_SELECTOR);
861  }
862
863  function getFooter() {
864    return document.querySelector(FOOTER_SELECTOR);
865  }
866
867  function getMain() {
868    return document.getElementById(SKIP_TARGET_ID);
869  }
870
871  function getHeaderOffset() {
872    var header = getHeader();
873    var style;
874
875    if (!header || !header.getBoundingClientRect) return 0;
876
877    style = window.getComputedStyle ? window.getComputedStyle(header) : null;
878
879    if (!style || (style.position !== "fixed" && style.position !== "sticky")) {
880      return 0;
881    }
882
883    return Math.ceil(header.getBoundingClientRect().height);
884  }
885
886  function prefersReducedMotion() {
887    return !!(
888      window.matchMedia &&
889      window.matchMedia("(prefers-reduced-motion: reduce)").matches
890    );
891  }
892
893  function getOpenCookieDialog() {
894    var dialogs = toArray(document.querySelectorAll(COOKIE_SELECTOR));
895
896    for (var i = 0; i < dialogs.length; i += 1) {
897      if (isElementVisible(dialogs[i])) return dialogs[i];
898    }
899
900    return null;
901  }
902
903  function ensureProgrammaticFocus(element) {
904    if (!element || typeof element.focus !== "function") return false;
905
906    if (!element.hasAttribute("tabindex")) {
907      element.setAttribute("tabindex", "-1");
908      element.setAttribute(FOCUS_ADDED_ATTR, "true");
909    }
910
911    return true;
912  }
913
914  function focusElement(element) {
915    if (!ensureProgrammaticFocus(element)) return false;
916
917    if (
918      window.ENISA &&
919      window.ENISA.utils &&
920      typeof window.ENISA.utils.safeFocus === "function"
921    ) {
922      return window.ENISA.utils.safeFocus(element);
923    }
924
925    try {
926      element.focus({ preventScroll: true });
927    } catch (error) {
928      element.focus();
929    }
930
931    return document.activeElement === element;
932  }
933
934  function scrollToLandingTarget(target) {
935    var headerOffset;
936    var top;
937    var behavior;
938
939    if (!target || !target.getBoundingClientRect) return false;
940
941    if (
942      window.ENISA &&
943      window.ENISA.utils &&
944      typeof window.ENISA.utils.scrollToElement === "function"
945    ) {
946      return window.ENISA.utils.scrollToElement(target, { block: "start" });
947    }
948
949    headerOffset = getHeaderOffset();
950    top = Math.max(
951      0,
952      target.getBoundingClientRect().top + (window.pageYOffset || 0) - headerOffset - 16
953    );
954    behavior = prefersReducedMotion() ? "auto" : "smooth";
955
956    try {
957      window.scrollTo({
958        top: top,
959        left: window.pageXOffset || 0,
960        behavior: behavior
961      });
962    } catch (error) {
963      window.scrollTo(window.pageXOffset || 0, top);
964    }
965
966    return true;
967  }
968
969  function focusCookieDialog(dialog) {
970    var target = null;
971
972    if (!dialog) return false;
973
974    if (
975      window.ENISA &&
976      window.ENISA.utils &&
977      typeof window.ENISA.utils.getFocusableElements === "function"
978    ) {
979      target = window.ENISA.utils.getFocusableElements(dialog)[0] || null;
980    }
981
982    if (!target && dialog.querySelectorAll) {
983      target = toArray(dialog.querySelectorAll(FOCUSABLE_SELECTOR)).filter(isElementVisible)[0] || null;
984    }
985
986    target = target || dialog;
987
988    if (target === dialog && !dialog.hasAttribute("tabindex")) {
989      dialog.setAttribute("tabindex", "-1");
990    }
991
992    return focusElement(target);
993  }
994
995  function findFirstUsefulHeading(scope, main) {
996    var headings;
997
998    if (!scope || !scope.querySelectorAll) return null;
999
1000    headings = toArray(scope.querySelectorAll(LANDING_TARGET_SELECTOR));
1001
1002    for (var i = 0; i < headings.length; i += 1) {
1003      if (canUseAsLandingTarget(headings[i], main)) {
1004        return headings[i];
1005      }
1006    }
1007
1008    return null;
1009  }
1010
1011  function findFirstUsefulDescendant(root, main) {
1012    var children;
1013    var child;
1014    var heading;
1015    var nested;
1016
1017    if (!root || !root.children) return null;
1018
1019    children = toArray(root.children);
1020
1021    for (var i = 0; i < children.length; i += 1) {
1022      child = children[i];
1023
1024      if (!isElementVisible(child) || isNonContentElement(child)) {
1025        continue;
1026      }
1027
1028      if (canUseAsLandingTarget(child, main)) {
1029        if (child.matches && child.matches(LANDING_TARGET_SELECTOR)) {
1030          return child;
1031        }
1032
1033        heading = findFirstUsefulHeading(child, main);
1034        return heading || child;
1035      }
1036
1037      nested = findFirstUsefulDescendant(child, main);
1038      if (nested) return nested;
1039    }
1040
1041    return null;
1042  }
1043
1044  function getSkipLandingTarget(main) {
1045    var explicitTarget;
1046    var usefulDescendant;
1047    var heading;
1048
1049    if (!main) return null;
1050
1051    explicitTarget = main.querySelector("[data-enisa-skip-target], [data-skip-target]");
1052    if (explicitTarget && canUseAsLandingTarget(explicitTarget, main)) {
1053      return explicitTarget;
1054    }
1055
1056    heading = findFirstUsefulHeading(main, main);
1057    if (heading) return heading;
1058
1059    usefulDescendant = findFirstUsefulDescendant(main, main);
1060    if (usefulDescendant) return usefulDescendant;
1061
1062    return main;
1063  }
1064
1065  function ensureSkipLinkHref(link) {
1066    if (!link) return;
1067
1068    if (link.getAttribute("href") !== SKIP_TARGET_HASH) {
1069      link.setAttribute("href", SKIP_TARGET_HASH);
1070    }
1071  }
1072
1073  function ensureSkipLinkBeforeHeader(link) {
1074    var header = getHeader();
1075
1076    if (!link || !header || !document.body) return;
1077
1078    if (link.compareDocumentPosition(header) & Node.DOCUMENT_POSITION_FOLLOWING) {
1079      return;
1080    }
1081
1082    document.body.insertBefore(link, header);
1083  }
1084
1085  function activateSkipLink(event) {
1086    var cookieDialog;
1087    var main;
1088    var landingTarget;
1089
1090    if (event) {
1091      event.preventDefault();
1092    }
1093
1094    cookieDialog = getOpenCookieDialog();
1095
1096    if (cookieDialog) {
1097      focusCookieDialog(cookieDialog);
1098      return;
1099    }
1100
1101    main = getMain();
1102
1103    if (!main) {
1104      return;
1105    }
1106
1107    landingTarget = getSkipLandingTarget(main) || main;
1108
1109    if (!focusElement(landingTarget)) {
1110      focusElement(main);
1111      landingTarget = main;
1112    }
1113
1114    scrollToLandingTarget(landingTarget);
1115  }
1116
1117  function getHeadLeaksBeforeMain() {
1118    var main = getMain();
1119    var leaks;
1120
1121    if (!main || !document.body) return [];
1122
1123    leaks = toArray(
1124      document.body.querySelectorAll(
1125        ":scope > meta, :scope > title, :scope > head, :scope > body, :scope > script[src*='googletagmanager']"
1126      )
1127    );
1128
1129    return leaks
1130      .filter(function (element) {
1131        return !!(element.compareDocumentPosition(main) & Node.DOCUMENT_POSITION_FOLLOWING);
1132      })
1133      .map(function (element) {
1134        return {
1135          tagName: element.tagName.toLowerCase(),
1136          id: element.id || "",
1137          src: element.getAttribute("src") || "",
1138          text: normalizeSpaces(element.textContent).slice(0, 120)
1139        };
1140      });
1141  }
1142
1143  function getVisibleMainLikeElements() {
1144    return toArray(document.querySelectorAll("main, [role='main']")).filter(isElementVisible);
1145  }
1146
1147  function audit() {
1148    var links = toArray(document.querySelectorAll(SKIP_SELECTOR));
1149    var link = links[0] || null;
1150    var main = getMain();
1151    var header = getHeader();
1152    var footer = getFooter();
1153    var cookieDialog = getOpenCookieDialog();
1154    var landingTarget = main ? getSkipLandingTarget(main) : null;
1155    var headLeaksBeforeMain = getHeadLeaksBeforeMain();
1156    var visibleMainLikeElements = getVisibleMainLikeElements();
1157
1158    return {
1159      skipLinks: links.length,
1160      skipHref: link ? link.getAttribute("href") : null,
1161      skipTargetsMain: !!(link && main && document.querySelector(link.getAttribute("href")) === main),
1162      skipLinkBeforeHeader: !!(
1163        link &&
1164        header &&
1165        (link.compareDocumentPosition(header) & Node.DOCUMENT_POSITION_FOLLOWING)
1166      ),
1167      mainExists: !!main,
1168      mainCountById: document.querySelectorAll("#" + SKIP_TARGET_ID).length,
1169      visibleMainLikeCount: visibleMainLikeElements.length,
1170      mainHasTabindexMinusOne: !!(main && main.getAttribute("tabindex") === "-1"),
1171      headerInsideMain: !!(main && header && main.contains(header)),
1172      globalNavigationInsideMain: !!(main && main.querySelector(GLOBAL_NAV_SELECTOR)),
1173      footerInsideMain: !!(main && footer && main.contains(footer)),
1174      cookieDialogOpen: !!cookieDialog,
1175      bodyHasHeadLeak: headLeaksBeforeMain.length > 0,
1176      headLeaksBeforeMain: headLeaksBeforeMain,
1177      landingTarget: landingTarget
1178        ? {
1179            tagName: landingTarget.tagName.toLowerCase(),
1180            id: landingTarget.id || "",
1181            className: String(landingTarget.className || ""),
1182            text: normalizeSpaces(landingTarget.textContent).slice(0, 120)
1183          }
1184        : null
1185    };
1186  }
1187
1188  function initLink(link) {
1189    if (!link) return;
1190
1191    ensureSkipLinkHref(link);
1192    ensureSkipLinkBeforeHeader(link);
1193
1194    if (link.getAttribute(INIT_ATTR) === "true") return;
1195
1196    link.setAttribute(INIT_ATTR, "true");
1197    link.addEventListener("click", activateSkipLink);
1198  }
1199
1200  function init() {
1201    var links = document.querySelectorAll(SKIP_SELECTOR);
1202
1203    toArray(links).forEach(initLink);
1204
1205    window.ENISA = window.ENISA || {};
1206    window.ENISA.skipLink = {
1207      activate: activateSkipLink,
1208      audit: audit
1209    };
1210  }
1211
1212  if (document.readyState === "loading") {
1213    document.addEventListener("DOMContentLoaded", init);
1214  } else {
1215    init();
1216  }
1217})();
1218
1219/*
1220  ENISA | JS Module
1221  Modulo: 03-header
1222  Descripcion: Logica del modulo.
1223*/
1224(function () {
1225  "use strict";
1226  var DESKTOP_NAV_BREAKPOINT = 1023.98;
1227  var PRESEARCH_MIN_CHARS = 4;
1228  var SEARCH_FOCUS_INTENT_KEY = "enisa:global-search:focus-results";
1229
1230  function initHeader() {
1231    var header = document.querySelector("[data-header-root]") || document.querySelector(".enisa-header");
1232    if (!header) return;
1233
1234    var root = document.documentElement;
1235    var headerBar = header.querySelector(".enisa-header__bar");
1236    var headerContainer = header.querySelector(".enisa-header__container");
1237    var overlay =
1238      document.getElementById("searchOverlay") ||
1239      header.querySelector("#searchOverlay") ||
1240      header.querySelector(".enisa-overlay");
1241
1242    function ensureOverlayMarkup() {
1243      if (!overlay) return;
1244
1245      if (!overlay.querySelector(".enisa-overlay__panel")) {
1246        overlay.innerHTML = [
1247        '<div class="enisa-overlay__panel">',
1248        '  <div class="enisa-overlay__body">',
1249        '    <button class="enisa-overlay__close js-close-search" type="button" aria-label="Cerrar búsqueda">',
1250        '      <img class="enisa-overlay__close-icon" src="/assets/icons/close.svg" alt="" width="20" height="20" aria-hidden="true">',
1251        '    </button>',
1252        '    <h2 id="enisa-search-title" class="enisa-overlay__title">¿En qué podemos <span class="enisa-overlay__title-mark">ayudarte</span><span class="enisa-overlay__title-punctuation">?</span></h2>',
1253        '    <form class="enisa-overlay__form" action="/buscar" autocomplete="off" method="get" role="search" novalidate>',
1254        '      <div class="enisa-overlay__field">',
1255        '        <label class="sr-only" for="SearchTerm">Buscar</label>',
1256        '        <input class="enisa-overlay__input" id="SearchTerm" name="q" type="search" value="" placeholder="Buscar" autocomplete="off" maxlength="200">',
1257        '        <input type="hidden" name="SearchTerm" value="">',
1258        '        <div class="enisa-overlay__actions">',
1259        '          <button class="enisa-overlay__action enisa-overlay__action--clear js-search-clear" type="button" aria-label="Borrar búsqueda" hidden>',
1260        '            <img class="enisa-overlay__action-icon" src="/assets/icons/close.svg" alt="" width="20" height="20" aria-hidden="true">',
1261        '          </button>',
1262        '          <button class="enisa-overlay__action enisa-overlay__action--submit" type="submit" aria-label="Buscar">',
1263        '            <img class="enisa-overlay__action-icon" src="/assets/icons/search.svg" alt="" width="20" height="20" aria-hidden="true">',
1264        '          </button>',
1265        '        </div>',
1266        "      </div>",
1267        "    </form>",
1268        '    <p id="presearch-status" class="sr-only" aria-live="polite" aria-atomic="true"></p>',
1269        '    <div id="presearch-result" class="enisa-overlay__results" role="list"></div>',
1270        "  </div>",
1271        "</div>",
1272        ].join("");
1273      }
1274
1275      var body = overlay.querySelector(".enisa-overlay__body") || overlay;
1276      var title = overlay.querySelector(".enisa-overlay__title");
1277      var result = overlay.querySelector("#presearch-result, .enisa-overlay__results");
1278
1279      if (!overlay.querySelector(".js-close-search")) {
1280        var close = document.createElement("button");
1281        close.className = "enisa-overlay__close js-close-search";
1282        close.type = "button";
1283        close.setAttribute("aria-label", "Cerrar búsqueda");
1284        close.innerHTML = '<img class="enisa-overlay__close-icon" src="/assets/icons/close.svg" alt="" width="20" height="20" aria-hidden="true">';
1285        body.insertBefore(close, title || body.firstChild);
1286      }
1287
1288      if (!overlay.querySelector("#presearch-status")) {
1289        var status = document.createElement("p");
1290        status.id = "presearch-status";
1291        status.className = "sr-only";
1292        status.setAttribute("role", "status");
1293        status.setAttribute("aria-live", "polite");
1294        status.setAttribute("aria-atomic", "true");
1295        body.insertBefore(status, result || null);
1296      }
1297    }
1298
1299    ensureOverlayMarkup();
1300
1301    var openBtns = [].slice.call(
1302      header.querySelectorAll(".js-toggle-search, [aria-controls='searchOverlay']")
1303    );
1304    var closeBtn = header.querySelector(".js-close-search");
1305    var searchInput = document.getElementById("SearchTerm");
1306    var searchClearBtn =
1307      header.querySelector(".js-search-clear") ||
1308      (overlay ? overlay.querySelector(".js-search-clear") : null);
1309    var searchSubmitBtn = overlay
1310      ? overlay.querySelector(".enisa-overlay__action--submit")
1311      : null;
1312    var presearchResult = document.getElementById("presearch-result");
1313    var presearchStatus = document.getElementById("presearch-status");
1314    var overlayForm = overlay ? overlay.querySelector(".enisa-overlay__form") : null;
1315    var overlayCompatSearchInput =
1316      overlayForm && overlayForm.querySelector("input[name='SearchTerm']");
1317    var mobileSearchForms = [].slice.call(
1318      header.querySelectorAll(
1319        ".enisa-header__mobile-search-box, .enisa-header__mobile-help form[action*='buscar']"
1320      )
1321    );
1322    var dropdownToggles = [].slice.call(
1323      header.querySelectorAll("[data-bs-toggle='dropdown']")
1324    );
1325    var dropdownMenus = [].slice.call(
1326      header.querySelectorAll(".enisa-nav__dropdown.dropdown-menu")
1327    );
1328    var mobileToggles = [].slice.call(
1329      header.querySelectorAll(".js-mobile-menu-toggle")
1330    );
1331    var mobilePanel = document.getElementById("headerMobilePanel");
1332    var mobileDetails = [].slice.call(
1333      header.querySelectorAll(".enisa-header__mobile-details")
1334    );
1335      var mobileLockScrollY = 0;
1336    var mobileLockScrollX = 0;
1337    var lastScrollY = window.scrollY || window.pageYOffset || 0;
1338    var lastSearchTrigger = null;
1339    var presearchDebounce = null;
1340    var presearchReqSeq = 0;
1341    var presearchAnnounced = "";
1342    var searchBackgroundNodes = [];
1343    var mobileBackgroundNodes = [];
1344
1345    function getFocusableElements(scope) {
1346      if (
1347        window.ENISA &&
1348        window.ENISA.utils &&
1349        typeof window.ENISA.utils.getFocusableElements === "function"
1350      ) {
1351        return window.ENISA.utils.getFocusableElements(scope);
1352      }
1353
1354      return [].slice.call(
1355        scope.querySelectorAll(
1356          "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])"
1357        )
1358      ).filter(function (el) {
1359        return !el.hasAttribute("hidden") && el.offsetParent !== null;
1360      });
1361    }
1362
1363    function safeFocus(element) {
1364      if (
1365        window.ENISA &&
1366        window.ENISA.utils &&
1367        typeof window.ENISA.utils.safeFocus === "function"
1368      ) {
1369        return window.ENISA.utils.safeFocus(element);
1370      }
1371
1372      if (!element || typeof element.focus !== "function") return false;
1373
1374      try {
1375        element.focus({ preventScroll: true });
1376      } catch (error) {
1377        element.focus();
1378      }
1379
1380      return true;
1381    }
1382
1383    function setInert(element, isInert) {
1384      if (
1385        window.ENISA &&
1386        window.ENISA.utils &&
1387        typeof window.ENISA.utils.setInert === "function"
1388      ) {
1389        window.ENISA.utils.setInert(element, isInert);
1390      }
1391    }
1392
1393    function setNodesInert(nodes, isInert) {
1394      nodes.forEach(function (node) {
1395        setInert(node, isInert);
1396      });
1397    }
1398
1399    function collectBodySiblings(exceptElement) {
1400      return [].slice.call(document.body.children).filter(function (child) {
1401        return child !== exceptElement && !child.contains(exceptElement);
1402      });
1403    }
1404
1405    function collectHeaderSiblings(exceptElement) {
1406      if (!exceptElement || !exceptElement.parentElement) return [];
1407
1408      return [].slice.call(exceptElement.parentElement.children).filter(function (child) {
1409        return child !== exceptElement && !child.contains(exceptElement);
1410      });
1411    }
1412
1413    function setSearchBackgroundHidden(isHidden) {
1414      if (!overlay) return;
1415
1416      if (isHidden) {
1417        searchBackgroundNodes = collectBodySiblings(header).concat(collectHeaderSiblings(overlay));
1418        setNodesInert(searchBackgroundNodes, true);
1419        return;
1420      }
1421
1422      setNodesInert(searchBackgroundNodes, false);
1423      searchBackgroundNodes = [];
1424    }
1425
1426    function setMobileBackgroundHidden(isHidden) {
1427      if (!mobilePanel) return;
1428
1429      if (isHidden) {
1430        mobileBackgroundNodes = collectBodySiblings(header).concat(collectHeaderSiblings(mobilePanel));
1431        setNodesInert(mobileBackgroundNodes, true);
1432        return;
1433      }
1434
1435      setNodesInert(mobileBackgroundNodes, false);
1436      mobileBackgroundNodes = [];
1437    }
1438
1439    function ensurePresearchStatus() {
1440      if (presearchStatus || !overlay) return;
1441
1442      presearchStatus = document.createElement("p");
1443      presearchStatus.id = "presearch-status";
1444      presearchStatus.className = "sr-only";
1445      presearchStatus.setAttribute("aria-live", "polite");
1446      presearchStatus.setAttribute("aria-atomic", "true");
1447
1448      if (presearchResult && presearchResult.parentNode) {
1449        presearchResult.parentNode.insertBefore(presearchStatus, presearchResult);
1450      } else {
1451        overlay.appendChild(presearchStatus);
1452      }
1453    }
1454
1455    function normalizeSearchValue(value) {
1456      return (value || "").replace(/\s+/g, " ").trim();
1457    }
1458
1459    function isTrustedUserAction(event) {
1460      return !event || event.isTrusted !== false;
1461    }
1462
1463    function markSearchSubmitIntent() {
1464      try {
1465        window.sessionStorage.setItem(SEARCH_FOCUS_INTENT_KEY, "1");
1466      } catch (error) {}
1467    }
1468
1469    function setMobileTogglesExpanded(isExpanded) {
1470      mobileToggles.forEach(function (toggleBtn) {
1471        if (toggleBtn.hasAttribute("aria-expanded")) {
1472          toggleBtn.setAttribute("aria-expanded", isExpanded ? "true" : "false");
1473        }
1474      });
1475    }
1476
1477    function syncSearchCompatInputs(value) {
1478      var normalized = normalizeSearchValue(value);
1479      if (overlayCompatSearchInput) {
1480        overlayCompatSearchInput.value = normalized;
1481      }
1482
1483      mobileSearchForms.forEach(function (form) {
1484        var compatInput = form.querySelector("input[name='SearchTerm']");
1485        var visibleInput = form.querySelector("input[name='q']");
1486        if (visibleInput && visibleInput.value !== normalized) {
1487          visibleInput.value = normalized;
1488        }
1489        if (compatInput) {
1490          compatInput.value = normalized;
1491        }
1492      });
1493    }
1494
1495    function renderPresearch(items) {
1496      if (!presearchResult) return;
1497      presearchResult.innerHTML = "";
1498
1499      if (!items.length) {
1500        presearchResult.classList.remove("is-visible");
1501        if (presearchStatus) presearchStatus.textContent = "";
1502        return;
1503      }
1504
1505      var frag = document.createDocumentFragment();
1506      var addedCount = 0;
1507
1508      items.forEach(function (item) {
1509        var name = typeof item === "string" ? item : (item && item.name || "");
1510        var url = (typeof item === "object" && item) ? (item.url || null) : null;
1511        var normalizedName = normalizeSearchValue(name);
1512        if (!normalizedName) return;
1513
1514        var row = document.createElement("div");
1515        row.className = "enisa-overlay__result-item";
1516        row.setAttribute("role", "listitem");
1517
1518        if (url) {
1519          var link = document.createElement("a");
1520          link.className = "enisa-overlay__result-button";
1521          link.href = url;
1522          link.textContent = normalizedName;
1523          row.appendChild(link);
1524        } else {
1525          var form = document.createElement("form");
1526          form.className = "enisa-overlay__result-form";
1527          form.action =
1528            (overlayForm && overlayForm.getAttribute("action")) || "/buscar";
1529          form.method =
1530            (overlayForm && overlayForm.getAttribute("method")) || "get";
1531          form.setAttribute("autocomplete", "off");
1532
1533          var qInput = document.createElement("input");
1534          qInput.type = "hidden";
1535          qInput.name = "q";
1536          qInput.value = normalizedName;
1537
1538          var compatInput = document.createElement("input");
1539          compatInput.type = "hidden";
1540          compatInput.name = "SearchTerm";
1541          compatInput.value = normalizedName;
1542
1543          var btn = document.createElement("button");
1544          btn.className = "enisa-overlay__result-button";
1545          btn.type = "submit";
1546          btn.textContent = normalizedName;
1547          btn.addEventListener("click", markSearchSubmitIntent);
1548
1549          form.appendChild(qInput);
1550          form.appendChild(compatInput);
1551          form.appendChild(btn);
1552          row.appendChild(form);
1553        }
1554
1555        frag.appendChild(row);
1556        addedCount += 1;
1557      });
1558
1559      presearchResult.appendChild(frag);
1560      var visible = presearchResult.children.length > 0;
1561      presearchResult.classList.toggle("is-visible", visible);
1562      if (presearchStatus) {
1563        var message = visible
1564          ? addedCount + (addedCount === 1 ? " sugerencia encontrada" : " sugerencias encontradas")
1565          : "";
1566        if (message !== presearchAnnounced) {
1567          presearchStatus.textContent = message;
1568          presearchAnnounced = message;
1569        }
1570      }
1571    }
1572
1573    function clearPresearch() {
1574      if (!presearchResult) return;
1575      presearchResult.innerHTML = "";
1576      presearchResult.classList.remove("is-visible");
1577      if (presearchStatus) presearchStatus.textContent = "";
1578      presearchAnnounced = "";
1579    }
1580
1581    function getPresearchActions() {
1582      if (!presearchResult) return [];
1583
1584      return [].slice.call(
1585        presearchResult.querySelectorAll("a[href], button:not([disabled])")
1586      ).filter(function (element) {
1587        return element.offsetParent !== null;
1588      });
1589    }
1590
1591    function updateOverlaySearchUi() {
1592      if (!searchInput) return;
1593      var hasText = normalizeSearchValue(searchInput.value).length > 0;
1594      if (searchClearBtn) {
1595        searchClearBtn.hidden = !hasText;
1596      }
1597      if (!hasText) {
1598        clearPresearch();
1599      }
1600    }
1601
1602    function parsePresearchPayload(payload) {
1603      if (!payload) return [];
1604      if (typeof payload === "string") {
1605        var raw = payload.trim();
1606        try {
1607          payload = JSON.parse(raw);
1608        } catch (e) {
1609          var tmp = document.createElement("div");
1610          tmp.innerHTML = raw;
1611          var htmlTerms = [];
1612          var hiddenTerms = [].slice.call(
1613            tmp.querySelectorAll("input[name='SearchTerm']")
1614          ).map(function (input) {
1615            return (input.value || "").trim();
1616          });
1617          var buttonTerms = [].slice
1618            .call(tmp.querySelectorAll("button"))
1619            .map(function (btn) {
1620              return (btn.textContent || "").trim();
1621            });
1622          htmlTerms = hiddenTerms.concat(buttonTerms).filter(Boolean);
1623          return htmlTerms
1624            .filter(function (term, index, arr) {
1625              return arr.indexOf(term) === index;
1626            })
1627            .map(function (term) {
1628              return { name: term, url: null };
1629            })
1630            .slice(0, 18);
1631        }
1632      }
1633
1634      if (Array.isArray(payload)) {
1635        return payload
1636          .map(function (item) {
1637            if (typeof item === "string") return { name: item, url: null };
1638            var name = item && (item.Name || item.name || item.term || item.Term) || "";
1639            var url = item && (item.Url || item.url || item.URL) || null;
1640            return name ? { name: name, url: url || null } : null;
1641          })
1642          .filter(Boolean)
1643          .slice(0, 18);
1644      }
1645
1646      if (payload.results && Array.isArray(payload.results)) {
1647        return parsePresearchPayload(payload.results);
1648      }
1649
1650      if (payload.value && Array.isArray(payload.value)) {
1651        return parsePresearchPayload(payload.value);
1652      }
1653
1654      if (payload.d) {
1655        return parsePresearchPayload(payload.d);
1656      }
1657
1658      return [];
1659    }
1660
1661    function fetchPresearch(term) {
1662      var currentReq = ++presearchReqSeq;
1663      var url = "/buscar/presearch?q=" + encodeURIComponent(term);
1664
1665      fetch(url, {
1666        method: "GET",
1667        credentials: "same-origin",
1668      })
1669        .then(function (res) {
1670          if (!res.ok) throw new Error("presearch request failed");
1671          return res.text();
1672        })
1673        .then(function (rawPayload) {
1674          if (currentReq !== presearchReqSeq) return;
1675          renderPresearch(parsePresearchPayload(rawPayload));
1676        })
1677        .catch(function () {
1678          if (currentReq !== presearchReqSeq) return;
1679          clearPresearch();
1680        });
1681    }
1682
1683    function schedulePresearch() {
1684      if (!searchInput) return;
1685      var normalized = normalizeSearchValue(searchInput.value);
1686      syncSearchCompatInputs(normalized);
1687      updateOverlaySearchUi();
1688
1689      clearTimeout(presearchDebounce);
1690
1691      if (normalized.length < PRESEARCH_MIN_CHARS) {
1692        clearPresearch();
1693        return;
1694      }
1695
1696      presearchDebounce = setTimeout(function () {
1697        fetchPresearch(normalized);
1698      }, 180);
1699    }
1700
1701    function parsePx(value) {
1702      var parsed = parseFloat(value || "0");
1703      return Number.isFinite(parsed) ? parsed : 0;
1704    }
1705
1706    function measureActionLabelWidth(label) {
1707      if (!label || !label.textContent) return 0;
1708
1709      var probe = document.createElement("span");
1710      probe.className = "enisa-header__action-label";
1711      probe.textContent = normalizeSearchValue(label.textContent);
1712      probe.style.position = "absolute";
1713      probe.style.left = "-9999px";
1714      probe.style.top = "0";
1715      probe.style.maxWidth = "none";
1716      probe.style.opacity = "1";
1717      probe.style.transform = "none";
1718      probe.style.visibility = "hidden";
1719      probe.style.pointerEvents = "none";
1720      document.body.appendChild(probe);
1721
1722      var width = Math.ceil(probe.getBoundingClientRect().width);
1723      probe.remove();
1724      return width;
1725    }
1726
1727    function syncHeaderActionWidths() {
1728      var revealActions = [].slice.call(
1729        header.querySelectorAll(
1730          ".enisa-header__actions--desktop .enisa-header__action--reveal:not(.enisa-header__action--help)"
1731        )
1732      );
1733
1734      if (!revealActions.length) return;
1735
1736      var rootStyles = window.getComputedStyle(root);
1737      var actionSize = parsePx(
1738        rootStyles.getPropertyValue("--enisa-header-action-size-desktop")
1739      );
1740      var iconSize = parsePx(
1741        rootStyles.getPropertyValue("--enisa-header-action-icon-size")
1742      );
1743      var rightPad = 14;
1744      var gap = 4;
1745      var leftPad = Math.max(0, (actionSize - iconSize) / 2);
1746
1747      revealActions.forEach(function (action) {
1748        var label = action.querySelector(".enisa-header__action-label");
1749        if (!label) return;
1750
1751        var labelWidth = measureActionLabelWidth(label);
1752        var expandedWidth = Math.ceil(
1753          leftPad + iconSize + gap + labelWidth + rightPad + 4
1754        );
1755
1756        action.style.setProperty(
1757          "--enisa-header-action-expand-width",
1758          expandedWidth + "px"
1759        );
1760        action.style.setProperty(
1761          "--enisa-header-action-label-max",
1762          labelWidth + "px"
1763        );
1764      });
1765    }
1766
1767    function syncHeaderMetrics() {
1768      header.style.removeProperty("--enisa-header-open-offset");
1769      root.style.removeProperty("--enisa-header-open-offset");
1770    }
1771
1772    function toggleScrolledHeader() {
1773      var currentScrollY = window.scrollY || window.pageYOffset || 0;
1774      var isOverlayOpen = overlay && !overlay.hidden;
1775      var isMobilePanelOpen = mobilePanel && !mobilePanel.hidden;
1776      var isDropdownOpen = header.classList.contains("has-open-dropdown");
1777
1778      header.classList.toggle("is-scrolled", currentScrollY > 8);
1779
1780      if (
1781        currentScrollY <= 8 ||
1782        isOverlayOpen ||
1783        isMobilePanelOpen ||
1784        isDropdownOpen
1785      ) {
1786        header.classList.remove("is-scroll-down");
1787        header.classList.add("is-scroll-up");
1788        lastScrollY = currentScrollY;
1789        return;
1790      }
1791
1792      if (currentScrollY > lastScrollY + 2) {
1793        header.classList.add("is-scroll-down");
1794        header.classList.remove("is-scroll-up");
1795      } else if (currentScrollY < lastScrollY - 2) {
1796        header.classList.remove("is-scroll-down");
1797        header.classList.add("is-scroll-up");
1798      }
1799
1800      lastScrollY = currentScrollY;
1801    }
1802
1803    var navVeil = null;
1804
1805    function syncDropdownState() {
1806      var openItem = header.querySelector(".enisa-nav__item.dropdown.show");
1807      header.classList.toggle("has-open-dropdown", !!openItem);
1808      document.body.classList.toggle("header-dropdown-is-open", !!openItem);
1809
1810      if (!navVeil) {
1811        navVeil = document.createElement("div");
1812        navVeil.className = "enisa-nav-veil";
1813        document.body.appendChild(navVeil);
1814      }
1815
1816      navVeil.classList.toggle("is-active", !!openItem);
1817
1818      if (openItem) {
1819        header.classList.remove("is-scroll-down");
1820        header.classList.add("is-scroll-up");
1821      }
1822    }
1823
1824    function openMobileMenu() {
1825      if (!mobilePanel || !mobileToggles.length) return;
1826
1827      mobileLockScrollY = window.scrollY || window.pageYOffset || 0;
1828      mobileLockScrollX = window.scrollX || window.pageXOffset || 0;
1829      mobilePanel.hidden = false;
1830      mobilePanel.setAttribute("aria-hidden", "false");
1831      setMobileTogglesExpanded(true);
1832      header.classList.add("has-open-mobile-panel");
1833      header.classList.remove("is-scroll-down");
1834      header.classList.add("is-scroll-up");
1835      document.body.classList.add("mobile-menu-is-open");
1836      document.body.style.position = "fixed";
1837      document.body.style.top = "-" + mobileLockScrollY + "px";
1838      document.body.style.left = "0";
1839      document.body.style.right = "0";
1840      document.body.style.width = "100%";
1841
1842      var firstFocusable = getFocusableElements(mobilePanel)[0];
1843
1844      if (firstFocusable) {
1845        requestAnimationFrame(function () {
1846          safeFocus(firstFocusable);
1847          setMobileBackgroundHidden(true);
1848        });
1849      } else {
1850        setMobileBackgroundHidden(true);
1851      }
1852    }
1853
1854    function closeMobileMenu(options) {
1855      var shouldRestoreFocus;
1856      var focusTarget;
1857
1858      if (!mobilePanel || !mobileToggles.length) return;
1859
1860      shouldRestoreFocus = !!(options && options.restoreFocus);
1861      focusTarget = (options && options.focusTarget) || mobileToggles[0];
1862
1863      setMobileBackgroundHidden(false);
1864
1865      if (shouldRestoreFocus && focusTarget && typeof focusTarget.focus === "function") {
1866        safeFocus(focusTarget);
1867      }
1868
1869      mobilePanel.hidden = true;
1870      mobilePanel.setAttribute("aria-hidden", "true");
1871      setMobileTogglesExpanded(false);
1872      header.classList.remove("has-open-mobile-panel");
1873      document.body.classList.remove("mobile-menu-is-open");
1874      document.body.style.position = "";
1875      document.body.style.top = "";
1876      document.body.style.left = "";
1877      document.body.style.right = "";
1878      document.body.style.width = "";
1879      if (
1880        window.ENISA &&
1881        window.ENISA.utils &&
1882        typeof window.ENISA.utils.restoreScrollPosition === "function"
1883      ) {
1884        window.ENISA.utils.restoreScrollPosition(mobileLockScrollY, mobileLockScrollX);
1885      } else {
1886        window.scrollTo(mobileLockScrollX, mobileLockScrollY);
1887      }
1888    }
1889
1890    function toggleMobileMenu() {
1891      if (!mobilePanel || !mobileToggles.length) return;
1892
1893      if (mobilePanel.hidden) {
1894        openMobileMenu();
1895      } else {
1896        var activeIsInsidePanel = mobilePanel.contains(document.activeElement);
1897        closeMobileMenu({ restoreFocus: activeIsInsidePanel });
1898      }
1899    }
1900
1901    function openSearch() {
1902      if (!overlay) return;
1903
1904      closeMobileMenu();
1905      closeOpenDropdowns(false);
1906
1907      overlay.hidden = false;
1908      overlay.removeAttribute("hidden");
1909      overlay.style.display = "block";
1910      overlay.setAttribute("aria-hidden", "false");
1911      syncSearchCompatInputs(searchInput ? searchInput.value : "");
1912      updateOverlaySearchUi();
1913
1914      openBtns.forEach(function (btn) {
1915        btn.setAttribute("aria-expanded", "true");
1916      });
1917
1918      document.body.classList.add("search-overlay-is-open");
1919
1920      if (searchInput) {
1921        safeFocus(searchInput);
1922      }
1923
1924      setSearchBackgroundHidden(true);
1925    }
1926
1927    function closeSearch(restoreFocus) {
1928      if (!overlay) return;
1929      if (typeof restoreFocus === "undefined") restoreFocus = true;
1930
1931      clearTimeout(presearchDebounce);
1932      presearchReqSeq += 1;
1933      clearPresearch();
1934
1935      openBtns.forEach(function (btn) {
1936        btn.setAttribute("aria-expanded", "false");
1937      });
1938
1939      document.body.classList.remove("search-overlay-is-open");
1940      setSearchBackgroundHidden(false);
1941
1942      if (restoreFocus && lastSearchTrigger) {
1943        safeFocus(lastSearchTrigger);
1944      }
1945
1946      overlay.hidden = true;
1947      overlay.setAttribute("hidden", "");
1948      overlay.style.display = "";
1949      overlay.setAttribute("aria-hidden", "true");
1950    }
1951
1952    function closeOpenDropdowns(restoreFocus) {
1953      if (typeof restoreFocus === "undefined") restoreFocus = true;
1954
1955      dropdownToggles.forEach(function (toggle) {
1956        var isExpanded = toggle.getAttribute("aria-expanded") === "true";
1957        if (!isExpanded) return;
1958
1959        if (window.bootstrap && window.bootstrap.Dropdown) {
1960          var instance = window.bootstrap.Dropdown.getOrCreateInstance(toggle);
1961          instance.hide();
1962        } else {
1963          var parentItem = toggle.closest("[data-nav-item]");
1964          var menu = parentItem && parentItem.querySelector(".dropdown-menu");
1965          if (parentItem) parentItem.classList.remove("show");
1966          toggle.setAttribute("aria-expanded", "false");
1967          if (menu) menu.classList.remove("show");
1968        }
1969
1970        if (restoreFocus) {
1971          safeFocus(toggle);
1972        }
1973      });
1974    }
1975
1976    syncHeaderMetrics();
1977    syncHeaderActionWidths();
1978    ensurePresearchStatus();
1979
1980    if (document.fonts && document.fonts.ready) {
1981      document.fonts.ready.then(syncHeaderActionWidths);
1982    }
1983
1984    toggleScrolledHeader();
1985    syncDropdownState();
1986    syncSearchCompatInputs(searchInput ? searchInput.value : "");
1987    updateOverlaySearchUi();
1988
1989    window.addEventListener(
1990      "scroll",
1991      function () {
1992        toggleScrolledHeader();
1993        syncHeaderMetrics();
1994      },
1995      { passive: true }
1996    );
1997
1998    window.addEventListener("resize", window.ENISA.utils.debounce(function () {
1999      syncHeaderMetrics();
2000      syncHeaderActionWidths();
2001
2002      if (window.innerWidth > DESKTOP_NAV_BREAKPOINT) {
2003        closeMobileMenu();
2004      } else {
2005        closeOpenDropdowns(false);
2006      }
2007
2008      syncDropdownState();
2009    }, 150));
2010
2011    openBtns.forEach(function (btn) {
2012      btn.addEventListener("click", function (e) {
2013        e.preventDefault();
2014        lastSearchTrigger = btn;
2015        var isOpen = overlay && !overlay.hidden;
2016
2017        if (isOpen) {
2018          closeSearch();
2019        } else {
2020          openSearch();
2021        }
2022      });
2023    });
2024
2025    if (searchInput) {
2026      searchInput.addEventListener("input", schedulePresearch);
2027      searchInput.addEventListener("change", schedulePresearch);
2028      searchInput.addEventListener("keydown", function (e) {
2029        if (e.key === "Escape") {
2030          if (presearchResult && presearchResult.classList.contains("is-visible")) {
2031            e.stopPropagation();
2032          }
2033
2034          clearPresearch();
2035          return;
2036        }
2037
2038        if (e.key === "ArrowDown") {
2039          var actions = getPresearchActions();
2040          if (!actions.length) return;
2041
2042          e.preventDefault();
2043          safeFocus(actions[0]);
2044        }
2045      });
2046    }
2047
2048    if (searchClearBtn && searchInput) {
2049      searchClearBtn.addEventListener("click", function (e) {
2050        e.preventDefault();
2051        searchInput.value = "";
2052        syncSearchCompatInputs("");
2053        updateOverlaySearchUi();
2054        clearPresearch();
2055        safeFocus(searchInput);
2056      });
2057    }
2058
2059    if (overlayForm && searchInput) {
2060      overlayForm.addEventListener("submit", function (event) {
2061        syncSearchCompatInputs(searchInput.value);
2062
2063        if (isTrustedUserAction(event) && normalizeSearchValue(searchInput.value).length > 0) {
2064          markSearchSubmitIntent();
2065        }
2066      });
2067    }
2068
2069    mobileSearchForms.forEach(function (mobileForm) {
2070      var mobileInput = mobileForm.querySelector("input[name='q']");
2071      mobileForm.addEventListener("submit", function (event) {
2072        syncSearchCompatInputs(mobileInput ? mobileInput.value : "");
2073
2074        if (
2075          isTrustedUserAction(event) &&
2076          normalizeSearchValue(mobileInput ? mobileInput.value : "").length > 0
2077        ) {
2078          markSearchSubmitIntent();
2079        }
2080      });
2081    });
2082
2083    mobileToggles.forEach(function (mobileToggle) {
2084      mobileToggle.addEventListener("click", function (e) {
2085        e.preventDefault();
2086        toggleMobileMenu();
2087      });
2088    });
2089
2090    if (closeBtn) {
2091      closeBtn.addEventListener("click", function () {
2092        closeSearch();
2093      });
2094    }
2095
2096    mobileDetails.forEach(function (detail) {
2097      detail.addEventListener("toggle", function () {
2098        var item = detail.closest(".enisa-header__mobile-item");
2099        if (!item) return;
2100
2101        if (detail.open) {
2102          item.classList.add("is-open");
2103        } else {
2104          item.classList.remove("is-open");
2105        }
2106      });
2107    });
2108
2109    dropdownToggles.forEach(function (toggle) {
2110      toggle.addEventListener("shown.bs.dropdown", function () {
2111        var parentItem = this.closest("[data-nav-item]");
2112        if (parentItem) parentItem.classList.add("show");
2113        this.setAttribute("aria-expanded", "true");
2114        syncHeaderMetrics();
2115        syncDropdownState();
2116      });
2117
2118      toggle.addEventListener("hidden.bs.dropdown", function () {
2119        var parentItem = this.closest("[data-nav-item]");
2120        if (parentItem) parentItem.classList.remove("show");
2121        this.setAttribute("aria-expanded", "false");
2122        setTimeout(function () {
2123          syncHeaderMetrics();
2124          syncDropdownState();
2125        }, 0);
2126      });
2127
2128      toggle.addEventListener("keydown", function (e) {
2129        var key = e.key;
2130        if (
2131          key !== "ArrowDown" &&
2132          key !== "ArrowUp" &&
2133          key !== "Enter" &&
2134          key !== " "
2135        ) {
2136          return;
2137        }
2138
2139        var parentItem = toggle.closest("[data-nav-item]");
2140        var menu =
2141          parentItem &&
2142          parentItem.querySelector(".enisa-nav__dropdown.dropdown-menu");
2143        var links = [].slice.call(
2144          (parentItem || header).querySelectorAll(".enisa-nav__dropdown-link-row")
2145        );
2146
2147        if (!menu || !links.length) return;
2148
2149        e.preventDefault();
2150
2151        if (window.bootstrap && window.bootstrap.Dropdown) {
2152          var dd = window.bootstrap.Dropdown.getOrCreateInstance(toggle);
2153          dd.show();
2154        }
2155
2156        if (key === "ArrowUp") {
2157          safeFocus(links[links.length - 1]);
2158        } else {
2159          safeFocus(links[0]);
2160        }
2161      });
2162    });
2163
2164    dropdownMenus.forEach(function (menu) {
2165      menu.addEventListener("keydown", function (e) {
2166        var links = [].slice.call(
2167          menu.querySelectorAll(".enisa-nav__dropdown-link-row")
2168        );
2169        if (!links.length) return;
2170
2171        var currentIndex = links.indexOf(document.activeElement);
2172        if (currentIndex < 0) return;
2173
2174        if (e.key === "ArrowDown") {
2175          e.preventDefault();
2176          safeFocus(links[(currentIndex + 1) % links.length]);
2177          return;
2178        }
2179
2180        if (e.key === "ArrowUp") {
2181          e.preventDefault();
2182          safeFocus(links[(currentIndex - 1 + links.length) % links.length]);
2183          return;
2184        }
2185
2186        if (e.key === "Home") {
2187          e.preventDefault();
2188          safeFocus(links[0]);
2189          return;
2190        }
2191
2192        if (e.key === "End") {
2193          e.preventDefault();
2194          safeFocus(links[links.length - 1]);
2195          return;
2196        }
2197
2198        if (e.key === "Escape") {
2199          e.preventDefault();
2200          closeOpenDropdowns(true);
2201        }
2202      });
2203    });
2204
2205    if (overlay) {
2206      overlay.addEventListener("keydown", function (e) {
2207        if (e.key === "ArrowDown" || e.key === "ArrowUp") {
2208          var suggestionActions = getPresearchActions();
2209          var activeIndex = suggestionActions.indexOf(document.activeElement);
2210
2211          if (activeIndex > -1) {
2212            e.preventDefault();
2213            var nextIndex = e.key === "ArrowDown"
2214              ? (activeIndex + 1) % suggestionActions.length
2215              : (activeIndex - 1 + suggestionActions.length) % suggestionActions.length;
2216            safeFocus(suggestionActions[nextIndex]);
2217            return;
2218          }
2219        }
2220
2221        if (e.key !== "Tab") return;
2222
2223        var focusable = getFocusableElements(overlay);
2224
2225        if (focusable.length < 2) return;
2226
2227        var first = focusable[0];
2228        var last = focusable[focusable.length - 1];
2229
2230        if (e.shiftKey) {
2231          if (document.activeElement === first) {
2232            e.preventDefault();
2233            safeFocus(last);
2234          }
2235        } else {
2236          if (document.activeElement === last) {
2237            e.preventDefault();
2238            safeFocus(first);
2239          }
2240        }
2241      });
2242    }
2243
2244    if (mobilePanel) {
2245      mobilePanel.addEventListener("keydown", function (e) {
2246        if (mobilePanel.hidden) return;
2247        if (e.key !== "Tab") return;
2248
2249        var focusable = getFocusableElements(mobilePanel);
2250
2251        if (focusable.length < 2) return;
2252
2253        var first = focusable[0];
2254        var last = focusable[focusable.length - 1];
2255
2256        if (e.shiftKey) {
2257          if (document.activeElement === first) {
2258            e.preventDefault();
2259            safeFocus(last);
2260          }
2261        } else {
2262          if (document.activeElement === last) {
2263            e.preventDefault();
2264            safeFocus(first);
2265          }
2266        }
2267      });
2268    }
2269
2270    document.addEventListener("keydown", function (e) {
2271      if (e.key === "Escape") {
2272        var overlayOpen = overlay && !overlay.hidden;
2273        var mobileOpen = mobilePanel && !mobilePanel.hidden;
2274        var hasOpenDropdown = header.classList.contains("has-open-dropdown");
2275
2276        if (overlayOpen) {
2277          closeSearch(true);
2278          return;
2279        }
2280
2281        if (mobileOpen) {
2282          closeMobileMenu({ restoreFocus: mobileToggles.length > 0 });
2283          return;
2284        }
2285
2286        if (hasOpenDropdown) {
2287          closeOpenDropdowns(true);
2288        }
2289      }
2290    });
2291
2292    document.addEventListener("click", function (e) {
2293      if (e.target.closest("[data-history-back]")) {
2294        e.preventDefault();
2295        history.back();
2296        return;
2297      }
2298
2299      if (overlay && overlay.getAttribute("aria-hidden") === "false") {
2300        var insideOverlay = overlay.contains(e.target);
2301        var searchToggle = e.target.closest(".js-toggle-search");
2302        if (!insideOverlay && !searchToggle) {
2303          closeSearch();
2304        }
2305      }
2306
2307      if (mobilePanel && !mobilePanel.hidden) {
2308        var insideMobilePanel = mobilePanel.contains(e.target);
2309        var mobileMenuToggle = e.target.closest(".js-mobile-menu-toggle");
2310        if (!insideMobilePanel && !mobileMenuToggle) {
2311          closeMobileMenu();
2312        }
2313      }
2314
2315      setTimeout(function () {
2316        syncHeaderMetrics();
2317        syncDropdownState();
2318      }, 0);
2319    });
2320  }
2321
2322  if (document.readyState === "loading") {
2323    document.addEventListener("DOMContentLoaded", initHeader);
2324  } else {
2325    initHeader();
2326  }
2327})();
2328
2329(function () {
2330  "use strict";
2331
2332  var SEARCH_FOCUS_INTENT_KEY = "enisa:global-search:focus-results";
2333
2334  function initGlobalSearchPage() {
2335    var searchInput = document.querySelector("[data-search-input]");
2336    var suggestionsContainer = document.querySelector("[data-search-suggestions]");
2337    if (!searchInput || !suggestionsContainer) return;
2338
2339    var form = searchInput.closest("form");
2340    var clearBtn = form
2341      ? form.querySelector("[data-search-clear]")
2342      : document.querySelector(".enisa-global-search [data-search-clear]");
2343    var debounceTimer = null;
2344    var reqSeq = 0;
2345
2346    function normalizeVal(v) {
2347      return (v || "").replace(/\s+/g, " ").trim();
2348    }
2349
2350    function isTrustedUserAction(event) {
2351      return !event || event.isTrusted !== false;
2352    }
2353
2354    function markSearchSubmitIntent() {
2355      try {
2356        window.sessionStorage.setItem(SEARCH_FOCUS_INTENT_KEY, "1");
2357      } catch (error) {}
2358    }
2359
2360    function updateUi() {
2361      var hasText = normalizeVal(searchInput.value).length > 0;
2362      if (clearBtn) clearBtn.hidden = !hasText;
2363    }
2364
2365    function clearSuggestions() {
2366      suggestionsContainer.innerHTML = "";
2367      suggestionsContainer.classList.remove("is-visible");
2368    }
2369
2370    function getSuggestionActions() {
2371      return [].slice.call(
2372        suggestionsContainer.querySelectorAll("a[href], button:not([disabled])")
2373      ).filter(function (element) {
2374        return element.offsetParent !== null;
2375      });
2376    }
2377
2378    function renderSuggestions(items) {
2379      suggestionsContainer.innerHTML = "";
2380
2381      if (!items || !items.length) {
2382        suggestionsContainer.classList.remove("is-visible");
2383        return;
2384      }
2385
2386      var frag = document.createDocumentFragment();
2387
2388      items.forEach(function (item) {
2389        var name = typeof item === "string" ? item : (item && item.name || "");
2390        var url = (typeof item === "object" && item) ? (item.url || null) : null;
2391        var normalizedName = normalizeVal(name);
2392        if (!normalizedName) return;
2393
2394        var row = document.createElement("div");
2395        row.className = "enisa-global-search__suggestion-item";
2396        row.setAttribute("role", "listitem");
2397
2398        if (url) {
2399          var link = document.createElement("a");
2400          link.className = "enisa-global-search__suggestion-btn";
2401          link.href = url;
2402          link.textContent = normalizedName;
2403          row.appendChild(link);
2404        } else {
2405          var btn = document.createElement("button");
2406          btn.className = "enisa-global-search__suggestion-btn";
2407          btn.type = "button";
2408          btn.textContent = normalizedName;
2409          btn.addEventListener("click", function () {
2410            searchInput.value = normalizedName;
2411            updateUi();
2412            clearSuggestions();
2413            markSearchSubmitIntent();
2414            if (form) form.submit();
2415          });
2416          row.appendChild(btn);
2417        }
2418
2419        frag.appendChild(row);
2420      });
2421
2422      suggestionsContainer.appendChild(frag);
2423      suggestionsContainer.classList.toggle("is-visible", suggestionsContainer.children.length > 0);
2424    }
2425
2426    function fetchSuggestions(term) {
2427      var currentReq = ++reqSeq;
2428
2429      fetch("/buscar/presearch?q=" + encodeURIComponent(term), {
2430        method: "GET",
2431        credentials: "same-origin",
2432      })
2433        .then(function (res) {
2434          if (!res.ok) throw new Error("presearch failed");
2435          return res.text();
2436        })
2437        .then(function (raw) {
2438          if (currentReq !== reqSeq) return;
2439
2440          var parsed;
2441          try {
2442            parsed = JSON.parse(raw.trim());
2443          } catch (e) {
2444            parsed = [];
2445          }
2446
2447          var items = Array.isArray(parsed) ? parsed : (parsed.results || parsed.value || []);
2448          renderSuggestions(items);
2449        })
2450        .catch(function () {
2451          if (currentReq !== reqSeq) return;
2452          clearSuggestions();
2453        });
2454    }
2455
2456    searchInput.addEventListener("input", function () {
2457      var val = normalizeVal(searchInput.value);
2458      updateUi();
2459      clearTimeout(debounceTimer);
2460
2461      if (val.length < 4) {
2462        clearSuggestions();
2463        return;
2464      }
2465
2466      debounceTimer = setTimeout(function () {
2467        fetchSuggestions(val);
2468      }, 180);
2469    });
2470
2471    searchInput.addEventListener("keydown", function (event) {
2472      if (event.key === "Escape") {
2473        if (suggestionsContainer.classList.contains("is-visible")) {
2474          event.stopPropagation();
2475        }
2476
2477        clearSuggestions();
2478        return;
2479      }
2480
2481      if (event.key === "ArrowDown") {
2482        var actions = getSuggestionActions();
2483        if (!actions.length) return;
2484
2485        event.preventDefault();
2486        safeFocus(actions[0]);
2487      }
2488    });
2489
2490    suggestionsContainer.addEventListener("keydown", function (event) {
2491      var actions = getSuggestionActions();
2492      var currentIndex = actions.indexOf(document.activeElement);
2493      var nextIndex = currentIndex;
2494
2495      if (currentIndex < 0) return;
2496
2497      if (event.key === "ArrowDown") {
2498        nextIndex = (currentIndex + 1) % actions.length;
2499      } else if (event.key === "ArrowUp") {
2500        nextIndex = (currentIndex - 1 + actions.length) % actions.length;
2501      } else if (event.key === "Home") {
2502        nextIndex = 0;
2503      } else if (event.key === "End") {
2504        nextIndex = actions.length - 1;
2505      } else if (event.key === "Escape") {
2506        event.preventDefault();
2507        clearSuggestions();
2508        safeFocus(searchInput);
2509        return;
2510      } else {
2511        return;
2512      }
2513
2514      event.preventDefault();
2515      safeFocus(actions[nextIndex]);
2516    });
2517
2518    if (clearBtn) {
2519      clearBtn.addEventListener("click", function (e) {
2520        e.preventDefault();
2521        searchInput.value = "";
2522        updateUi();
2523        clearSuggestions();
2524        safeFocus(searchInput);
2525      });
2526    }
2527
2528    if (form) {
2529      form.addEventListener("submit", function (event) {
2530        if (isTrustedUserAction(event) && normalizeVal(searchInput.value).length > 0) {
2531          markSearchSubmitIntent();
2532        }
2533      });
2534    }
2535
2536    updateUi();
2537  }
2538
2539  if (document.readyState === "loading") {
2540    document.addEventListener("DOMContentLoaded", initGlobalSearchPage);
2541  } else {
2542    initGlobalSearchPage();
2543  }
2544})();
2545
2546/*
2547  ENISA | JS Module
2548  Modulo: 04-footer
2549  Descripcion: Logica del modulo.
2550*/
2551(function () {
2552  "use strict";
2553
2554  var FOOTER_ACCORDION_MOBILE_BREAKPOINT = 575.98;
2555
2556  function isMobileViewport() {
2557    return window.innerWidth <= FOOTER_ACCORDION_MOBILE_BREAKPOINT;
2558  }
2559
2560  function initFooterAccordionBehavior() {
2561    var footer = document.querySelector(".enisa-footer");
2562    if (!footer) return;
2563    if (footer.getAttribute("data-footer-accordion-init") === "true") return;
2564    footer.setAttribute("data-footer-accordion-init", "true");
2565
2566    var accordions = [].slice.call(
2567      footer.querySelectorAll(".enisa-footer__accordion"),
2568    );
2569    if (!accordions.length) return;
2570
2571    function setAccordionState(accordion, expanded) {
2572      var summary = accordion.querySelector(".enisa-footer__accordion-summary");
2573      var panel = accordion.querySelector(".enisa-footer__accordion-panel");
2574      if (!summary || !panel) return;
2575
2576      accordion.classList.toggle("is-open", expanded);
2577      summary.setAttribute("aria-expanded", expanded ? "true" : "false");
2578      panel.hidden = !expanded;
2579    }
2580
2581    function toggleAccordion(accordion) {
2582      var summary = accordion.querySelector(".enisa-footer__accordion-summary");
2583      if (!summary || !isMobileViewport()) return;
2584
2585      var isExpanded = summary.getAttribute("aria-expanded") === "true";
2586      setAccordionState(accordion, !isExpanded);
2587    }
2588
2589    accordions.forEach(function (accordion) {
2590      var summary = accordion.querySelector(".enisa-footer__accordion-summary");
2591      var panel = accordion.querySelector(".enisa-footer__accordion-panel");
2592      if (!summary || !panel) return;
2593
2594      summary.addEventListener("click", function () {
2595        toggleAccordion(accordion);
2596      });
2597
2598      summary.addEventListener("keydown", function (event) {
2599        if (!isMobileViewport()) return;
2600        if (event.key !== "Enter" && event.key !== " ") return;
2601
2602        event.preventDefault();
2603        toggleAccordion(accordion);
2604      });
2605    });
2606
2607    var lastIsMobile = null;
2608
2609    function syncAccordionState(force) {
2610      var mobile = isMobileViewport();
2611      if (!force && lastIsMobile === mobile) return;
2612
2613      accordions.forEach(function (accordion) {
2614        var summary = accordion.querySelector(".enisa-footer__accordion-summary");
2615        var panel = accordion.querySelector(".enisa-footer__accordion-panel");
2616        if (!summary || !panel) return;
2617
2618        if (mobile) {
2619          summary.setAttribute("role", "button");
2620          summary.setAttribute("aria-controls", panel.id);
2621          summary.setAttribute("tabindex", "0");
2622          summary.removeAttribute("aria-disabled");
2623          setAccordionState(accordion, false);
2624          return;
2625        }
2626
2627        accordion.classList.add("is-open");
2628        panel.hidden = false;
2629        summary.removeAttribute("role");
2630        summary.removeAttribute("aria-controls");
2631        summary.removeAttribute("aria-expanded");
2632        summary.removeAttribute("aria-disabled");
2633        summary.removeAttribute("tabindex");
2634      });
2635
2636      lastIsMobile = mobile;
2637    }
2638
2639    syncAccordionState(true);
2640
2641    window.addEventListener("resize", window.ENISA.utils.debounce(function () {
2642      syncAccordionState(false);
2643    }, 150));
2644  }
2645
2646  if (document.readyState === "loading") {
2647    document.addEventListener("DOMContentLoaded", initFooterAccordionBehavior);
2648  } else {
2649    initFooterAccordionBehavior();
2650  }
2651})();
2652
2653(function () {
2654  "use strict";
2655
2656  var STORAGE_KEY = "enisa_cookie_consent";
2657  var ROOT_SELECTOR = "[data-cookie-consent]";
2658  var ACCEPT_SELECTOR = "[data-cookie-accept]";
2659  var REJECT_SELECTOR = "[data-cookie-reject]";
2660  var CLOSE_SELECTOR = "[data-cookie-close]";
2661  var SETTINGS_ACCEPT_SELECTOR = "[data-cookie-settings-accept]";
2662  var SETTINGS_REJECT_SELECTOR = "[data-cookie-settings-reject]";
2663  var SETTINGS_STATUS_SELECTOR = "[data-cookie-settings-status]";
2664  var OPEN_SETTINGS_SELECTOR = "[data-cookie-open-settings]";
2665  var PENDING_SCRIPT_SELECTOR = "script[type='text/plain'][data-cookie-category]";
2666  var NON_ESSENTIAL_CATEGORIES = ["analytics", "marketing", "preferences", "third-party"];
2667
2668  var NON_ESSENTIAL_COOKIES = [
2669    "_ga",
2670    "_gid",
2671    "_gat",
2672    "_gcl_au",
2673    "_fbp",
2674    "_clck",
2675    "_clsk"
2676  ];
2677
2678  function readConsent() {
2679    try {
2680      var raw = window.localStorage.getItem(STORAGE_KEY);
2681      return raw ? JSON.parse(raw) : null;
2682    } catch (error) {
2683      return null;
2684    }
2685  }
2686
2687  function writeConsent(status, version) {
2688    var payload = {
2689      status: status,
2690      version: version,
2691      date: new Date().toISOString()
2692    };
2693
2694    try {
2695      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(payload));
2696    } catch (error) {}
2697
2698    return payload;
2699  }
2700
2701  function removeConsent() {
2702    try {
2703      window.localStorage.removeItem(STORAGE_KEY);
2704    } catch (error) {}
2705  }
2706
2707  function getCurrentVersion() {
2708    var root = document.querySelector(ROOT_SELECTOR);
2709    return root ? root.getAttribute("data-consent-version") || "1" : "1";
2710  }
2711
2712  function hasValidConsent(version) {
2713    var consent = readConsent();
2714
2715    return Boolean(
2716      consent &&
2717      consent.version === version &&
2718      (consent.status === "accepted" || consent.status === "rejected")
2719    );
2720  }
2721
2722  function isAccepted(version) {
2723    var consent = readConsent();
2724
2725    return Boolean(
2726      consent &&
2727      consent.version === version &&
2728      consent.status === "accepted"
2729    );
2730  }
2731
2732  function activatePendingScripts() {
2733    var scripts = Array.prototype.slice.call(
2734      document.querySelectorAll(PENDING_SCRIPT_SELECTOR)
2735    );
2736
2737    scripts.forEach(function (placeholder) {
2738      var category = placeholder.getAttribute("data-cookie-category");
2739
2740      if (NON_ESSENTIAL_CATEGORIES.indexOf(category) === -1) {
2741        return;
2742      }
2743
2744      var script = document.createElement("script");
2745      var src = placeholder.getAttribute("data-src");
2746
2747      if (src) {
2748        script.src = src;
2749        script.async = placeholder.hasAttribute("data-async");
2750        script.defer = placeholder.hasAttribute("data-defer");
2751      } else {
2752        script.text = placeholder.textContent || "";
2753      }
2754
2755      Array.prototype.slice.call(placeholder.attributes).forEach(function (attr) {
2756        if (attr.name.indexOf("data-") === 0 || attr.name === "type") {
2757          return;
2758        }
2759
2760        script.setAttribute(attr.name, attr.value);
2761      });
2762
2763      placeholder.parentNode.insertBefore(script, placeholder);
2764      placeholder.parentNode.removeChild(placeholder);
2765    });
2766  }
2767
2768  function deleteCookie(name) {
2769    var hostname = window.location.hostname;
2770    var domainParts = hostname.split(".");
2771    var domains = [hostname];
2772    var path = "/";
2773
2774    function expireCookie(value) {
2775      try {
2776        document.cookie = value;
2777      } catch (error) {}
2778    }
2779
2780    if (hostname.indexOf(".") !== -1) {
2781      domains.push("." + hostname);
2782
2783      if (domainParts.length > 2) {
2784        domains.push("." + domainParts.slice(-2).join("."));
2785      }
2786    }
2787
2788    domains.forEach(function (domain) {
2789      expireCookie(
2790        name +
2791        "=; Max-Age=0; path=" +
2792        path +
2793        "; domain=" +
2794        domain +
2795        "; SameSite=Lax"
2796      );
2797
2798      expireCookie(
2799        name +
2800        "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
2801        path +
2802        "; domain=" +
2803        domain +
2804        "; SameSite=Lax"
2805      );
2806    });
2807
2808    expireCookie(
2809      name +
2810      "=; Max-Age=0; path=" +
2811      path +
2812      "; SameSite=Lax"
2813    );
2814
2815    expireCookie(
2816      name +
2817      "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=" +
2818      path +
2819      "; SameSite=Lax"
2820    );
2821  }
2822
2823  function clearKnownNonEssentialCookies() {
2824    NON_ESSENTIAL_COOKIES.forEach(deleteCookie);
2825  }
2826
2827  function getFocusableElements(root) {
2828    if (
2829      window.ENISA &&
2830      window.ENISA.utils &&
2831      typeof window.ENISA.utils.getFocusableElements === "function"
2832    ) {
2833      return window.ENISA.utils.getFocusableElements(root);
2834    }
2835
2836    return Array.prototype.slice.call(
2837      root.querySelectorAll(
2838        "a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not
2838([tabindex='-1'])"
2839      )
2840    ).filter(function (element) {
2841      return element.offsetParent !== null || element === document.activeElement;
2842    });
2843  }
2844
2845  function safeFocus(element) {
2846    if (
2847      window.ENISA &&
2848      window.ENISA.utils &&
2849      typeof window.ENISA.utils.safeFocus === "function"
2850    ) {
2851      return window.ENISA.utils.safeFocus(element);
2852    }
2853
2854    if (!element || typeof element.focus !== "function") return false;
2855
2856    try {
2857      element.focus({ preventScroll: true });
2858    } catch (error) {
2859      element.focus();
2860    }
2861
2862    return true;
2863  }
2864
2865  function focusInitialElement(root) {
2866    var focusableElements = getFocusableElements(root);
2867    var target = focusableElements[0] || root;
2868
2869    if (target === root && !root.hasAttribute("tabindex")) {
2870      root.setAttribute("tabindex", "-1");
2871    }
2872
2873    safeFocus(target);
2874  }
2875
2876  function trapFocus(root, event) {
2877    if (!root || root.hidden || event.key !== "Tab") {
2878      return;
2879    }
2880
2881    var focusableElements = getFocusableElements(root);
2882
2883    if (!focusableElements.length) {
2884      event.preventDefault();
2885      safeFocus(root);
2886      return;
2887    }
2888
2889    var firstElement = focusableElements[0];
2890    var lastElement = focusableElements[focusableElements.length - 1];
2891
2892    if (!root.contains(document.activeElement)) {
2893      event.preventDefault();
2894      safeFocus(event.shiftKey ? lastElement : firstElement);
2895      return;
2896    }
2897
2898    if (event.shiftKey && document.activeElement === firstElement) {
2899      event.preventDefault();
2900      safeFocus(lastElement);
2901      return;
2902    }
2903
2904    if (!event.shiftKey && document.activeElement === lastElement) {
2905      event.preventDefault();
2906      safeFocus(firstElement);
2907    }
2908  }
2909
2910  var resizeHandler = null;
2911  var activeFocusTrapRoot = null;
2912  var isRedirectingFocus = false;
2913  var bgLockScrollY = 0;
2914  var bgLockScrollX = 0;
2915  var lastSettingsTrigger = null;
2916  var focusBeforeBanner = null;
2917  var BG_LOCK_ATTR = "data-cookie-bg-hidden";
2918  var BG_SELECTORS = ".enisa-skip-link, #main-content, .page-layout__main, [data-header-root], .enisa-header, footer, .enisa-footer";
2919
2920  function getFocusTrapFallback(root, useLast) {
2921    var focusableElements = getFocusableElements(root);
2922    return focusableElements[useLast ? focusableElements.length - 1 : 0] || root;
2923  }
2924
2925  function handleDocumentFocusTrapKeydown(event) {
2926    if (!activeFocusTrapRoot) return;
2927    trapFocus(activeFocusTrapRoot, event);
2928  }
2929
2930  function handleDocumentFocusTrapFocusin(event) {
2931    if (
2932      !activeFocusTrapRoot ||
2933      isRedirectingFocus ||
2934      activeFocusTrapRoot.hidden ||
2935      activeFocusTrapRoot.contains(event.target)
2936    ) {
2937      return;
2938    }
2939
2940    isRedirectingFocus = true;
2941
2942    try {
2943      safeFocus(getFocusTrapFallback(activeFocusTrapRoot, false));
2944    } finally {
2945      isRedirectingFocus = false;
2946    }
2947  }
2948
2949  function enableFocusTrap(root) {
2950    if (activeFocusTrapRoot === root) return;
2951
2952    activeFocusTrapRoot = root;
2953    document.addEventListener("keydown", handleDocumentFocusTrapKeydown, true);
2954    document.addEventListener("focusin", handleDocumentFocusTrapFocusin, true);
2955  }
2956
2957  function disableFocusTrap(root) {
2958    if (activeFocusTrapRoot !== root) return;
2959
2960    document.removeEventListener("keydown", handleDocumentFocusTrapKeydown, true);
2961    document.removeEventListener("focusin", handleDocumentFocusTrapFocusin, true);
2962    activeFocusTrapRoot = null;
2963  }
2964
2965  function canRestoreFocus(element, root) {
2966    if (!element || typeof element.focus !== "function") return false;
2967    if (root && root.contains(element)) return false;
2968    if (!document.documentElement.contains(element)) return false;
2969
2970    if (
2971      window.ENISA &&
2972      window.ENISA.utils &&
2973      typeof window.ENISA.utils.isElementVisible === "function"
2974    ) {
2975      return window.ENISA.utils.isElementVisible(element);
2976    }
2977
2978    return !element.hidden && element.offsetParent !== null;
2979  }
2980
2981  function releaseFocus(root) {
2982    var target = null;
2983
2984    if (!root || !root.contains(document.activeElement)) return;
2985
2986    if (canRestoreFocus(lastSettingsTrigger, root)) {
2987      target = lastSettingsTrigger;
2988    } else if (canRestoreFocus(focusBeforeBanner, root)) {
2989      target = focusBeforeBanner;
2990    }
2991
2992    if (!target) {
2993      target = document.getElementById("main-content");
2994    }
2995
2996    if (target) {
2997      safeFocus(target);
2998    }
2999  }
3000
3001  function setElementInert(element, isInert) {
3002    if (
3003      window.ENISA &&
3004      window.ENISA.utils &&
3005      typeof window.ENISA.utils.setInert === "function"
3006    ) {
3007      window.ENISA.utils.setInert(element, isInert);
3008      return;
3009    }
3010
3011    if (isInert) {
3012      element.setAttribute("aria-hidden", "true");
3013      if ("inert" in element) element.inert = true;
3014      return;
3015    }
3016
3017    element.removeAttribute("aria-hidden");
3018    if ("inert" in element) element.inert = false;
3019  }
3020
3021  function lockBackground(root) {
3022    [].slice.call(document.querySelectorAll(BG_SELECTORS)).forEach(function (el) {
3023      if (!el || el === root || root.contains(el) || el.contains(root)) return;
3024      el.setAttribute(BG_LOCK_ATTR, el.getAttribute("aria-hidden") || "");
3025      setElementInert(el, true);
3026    });
3027
3028    bgLockScrollY = window.scrollY || window.pageYOffset || 0;
3029    bgLockScrollX = window.scrollX || window.pageXOffset || 0;
3030    document.body.style.position = "fixed";
3031    document.body.style.top = "-" + bgLockScrollY + "px";
3032    document.body.style.width = "100%";
3033  }
3034
3035  function unlockBackground() {
3036    [].slice.call(document.querySelectorAll("[" + BG_LOCK_ATTR + "]")).forEach(function (el) {
3037      var prev = el.getAttribute(BG_LOCK_ATTR);
3038      el.removeAttribute(BG_LOCK_ATTR);
3039      setElementInert(el, false);
3040      if (prev) el.setAttribute("aria-hidden", prev);
3041    });
3042
3043    document.body.style.position = "";
3044    document.body.style.top = "";
3045    document.body.style.width = "";
3046    if (
3047      window.ENISA &&
3048      window.ENISA.utils &&
3049      typeof window.ENISA.utils.restoreScrollPosition === "function"
3050    ) {
3051      window.ENISA.utils.restoreScrollPosition(bgLockScrollY, bgLockScrollX);
3052    } else {
3053      window.scrollTo(bgLockScrollX, bgLockScrollY);
3054    }
3055  }
3056
3057  function syncCookieOffset(root) {
3058    if (!root) return;
3059
3060    var height = root.getBoundingClientRect().height;
3061    var safeBottom = 0;
3062
3063    try {
3064      var style = window.getComputedStyle(document.documentElement);
3065      var raw = style.getPropertyValue("--enisa-safe-area-bottom").trim();
3066      safeBottom = parseFloat(raw) || 0;
3067    } catch (error) {}
3068
3069    document.documentElement.style.setProperty(
3070      "--enisa-cookie-banner-offset",
3071      height + 32 + safeBottom + "px"
3072    );
3073  }
3074
3075  function clearCookieOffset() {
3076    document.documentElement.style.removeProperty("--enisa-cookie-banner-offset");
3077  }
3078
3079  function showBanner(root) {
3080    if (!root) return;
3081
3082    focusBeforeBanner = document.activeElement && !root.contains(document.activeElement)
3083      ? document.activeElement
3084      : null;
3085
3086    root.hidden = false;
3087    root.removeAttribute("hidden");
3088    root.removeAttribute("aria-hidden");
3089    document.body.classList.add("cookie-consent-is-open");
3090
3091    lockBackground(root);
3092    enableFocusTrap(root);
3093    focusInitialElement(root);
3094    syncCookieOffset(root);
3095
3096    requestAnimationFrame(function () {
3097      requestAnimationFrame(function () {
3098        syncCookieOffset(root);
3099      });
3100    });
3101
3102    if (!resizeHandler) {
3103      resizeHandler = function () {
3104        if (!root.hidden) {
3105          syncCookieOffset(root);
3106        }
3107      };
3108
3109      window.addEventListener("resize", resizeHandler, { passive: true });
3110      window.addEventListener("orientationchange", resizeHandler);
3111    }
3112  }
3113
3114  function hideBanner(root) {
3115    if (!root) return;
3116
3117    document.body.classList.remove("cookie-consent-is-open");
3118    clearCookieOffset();
3119    disableFocusTrap(root);
3120    unlockBackground();
3121
3122    if (resizeHandler) {
3123      window.removeEventListener("resize", resizeHandler);
3124      window.removeEventListener("orientationchange", resizeHandler);
3125      resizeHandler = null;
3126    }
3127
3128    releaseFocus(root);
3129    lastSettingsTrigger = null;
3130    focusBeforeBanner = null;
3131
3132    root.hidden = true;
3133    root.setAttribute("hidden", "");
3134    root.setAttribute("aria-hidden", "true");
3135  }
3136
3137  function updateSettingsStatus(status) {
3138    var statusNodes = Array.prototype.slice.call(
3139      document.querySelectorAll(SETTINGS_STATUS_SELECTOR)
3140    );
3141
3142    if (!statusNodes.length) {
3143      return;
3144    }
3145
3146    var message = "Puedes modificar tu consentimiento en cualquier momento.";
3147
3148    if (status === "accepted") {
3149      message = "Has aceptado las cookies analíticas.";
3150    }
3151
3152    if (status === "rejected") {
3153      message = "Has rechazado las cookies analíticas.";
3154    }
3155
3156    statusNodes.forEach(function (node) {
3157      node.textContent = message;
3158    });
3159  }
3160
3161  function acceptCookies(version) {
3162    var root = document.querySelector(ROOT_SELECTOR);
3163    var activeVersion = version || getCurrentVersion();
3164
3165    writeConsent("accepted", activeVersion);
3166    activatePendingScripts();
3167    updateSettingsStatus("accepted");
3168    hideBanner(root);
3169  }
3170
3171  function rejectCookies(version) {
3172    var root = document.querySelector(ROOT_SELECTOR);
3173    var activeVersion = version || getCurrentVersion();
3174
3175    writeConsent("rejected", activeVersion);
3176    clearKnownNonEssentialCookies();
3177    updateSettingsStatus("rejected");
3178    hideBanner(root);
3179  }
3180
3181  function openCookieSettings(trigger) {
3182    var root = document.querySelector(ROOT_SELECTOR);
3183
3184    lastSettingsTrigger = trigger || document.activeElement || null;
3185    removeConsent();
3186    clearKnownNonEssentialCookies();
3187    updateSettingsStatus(null);
3188    showBanner(root);
3189  }
3190
3191  function bindCookieSettingsTrigger() {
3192    document.addEventListener("click", function (event) {
3193      var trigger = event.target.closest(OPEN_SETTINGS_SELECTOR);
3194
3195      if (!trigger) {
3196        return;
3197      }
3198
3199      event.preventDefault();
3200      openCookieSettings(trigger);
3201    });
3202  }
3203
3204  function bindSettingsPageActions() {
3205    document.addEventListener("click", function (event) {
3206      var acceptTrigger = event.target.closest(SETTINGS_ACCEPT_SELECTOR);
3207      var rejectTrigger = event.target.closest(SETTINGS_REJECT_SELECTOR);
3208
3209      if (acceptTrigger) {
3210        event.preventDefault();
3211        acceptCookies(getCurrentVersion());
3212        return;
3213      }
3214
3215      if (rejectTrigger) {
3216        event.preventDefault();
3217        rejectCookies(getCurrentVersion());
3218      }
3219    });
3220  }
3221
3222  function bindBannerActions(root, version) {
3223    if (!root || root.getAttribute("data-cookie-banner-actions-init") === "true") {
3224      return;
3225    }
3226
3227    root.setAttribute("data-cookie-banner-actions-init", "true");
3228
3229    root.addEventListener("click", function (event) {
3230      if (event.target.closest(ACCEPT_SELECTOR)) {
3231        event.preventDefault();
3232        acceptCookies(version);
3233        return;
3234      }
3235
3236      if (event.target.closest(REJECT_SELECTOR)) {
3237        event.preventDefault();
3238        rejectCookies(version);
3239        return;
3240      }
3241
3242      if (event.target.closest(CLOSE_SELECTOR)) {
3243        event.preventDefault();
3244        rejectCookies(version);
3245      }
3246    });
3247
3248    root.addEventListener("keydown", function (event) {
3249      if (event.key === "Escape" || event.key === "Esc") {
3250        event.preventDefault();
3251        rejectCookies(version);
3252        return;
3253      }
3254
3255      trapFocus(root, event);
3256    });
3257  }
3258
3259  function syncInitialSettingsStatus(version) {
3260    var consent = readConsent();
3261
3262    if (!consent || consent.version !== version) {
3263      updateSettingsStatus(null);
3264      return;
3265    }
3266
3267    updateSettingsStatus(consent.status);
3268  }
3269
3270  function initCookieConsent() {
3271    var root = document.querySelector(ROOT_SELECTOR);
3272    var version = root ? root.getAttribute("data-consent-version") || "1" : "1";
3273
3274    syncInitialSettingsStatus(version);
3275
3276    if (!root) {
3277      return;
3278    }
3279
3280    bindBannerActions(root, version);
3281
3282    if (root.getAttribute("data-cookie-consent-init") === "true") {
3283      if (isAccepted(version)) {
3284        activatePendingScripts();
3285      }
3286
3287      return;
3288    }
3289
3290    root.setAttribute("data-cookie-consent-init", "true");
3291
3292    if (isAccepted(version)) {
3293      activatePendingScripts();
3294      return;
3295    }
3296
3297    if (!hasValidConsent(version)) {
3298      showBanner(root);
3299    }
3300  }
3301
3302  bindCookieSettingsTrigger();
3303  bindSettingsPageActions();
3304
3305  window.ENISA = window.ENISA || {};
3306  window.ENISA.cookies = {
3307    readConsent: readConsent,
3308    accept: acceptCookies,
3309    reject: rejectCookies,
3310    openSettings: openCookieSettings,
3311    clearKnownNonEssentialCookies: clearKnownNonEssentialCookies
3312  };
3313
3314  if (document.readyState === "loading") {
3315    document.addEventListener("DOMContentLoaded", initCookieConsent);
3316  } else {
3317    initCookieConsent();
3318  }
3319})();
3320
3321(function () {
3322  "use strict";
3323
3324  var INIT_ATTR = "data-enisa-external-web-link-init";
3325  var SKIP_ATTR = "data-skip-external-link";
3326  var MODULE_NAMESPACE = "externalWebLinks";
3327  var OBSERVER_DEBOUNCE = 150;
3328
3329  function isElement(node) {
3330    return node && node.nodeType === 1;
3331  }
3332
3333  function isWebHref(href) {
3334    return /^https?:\/\//i.test((href || "").trim());
3335  }
3336
3337  function normalizeOrigin(origin) {
3338    return (origin || "").replace(/\/$/, "").toLowerCase();
3339  }
3340
3341  function isSameOrigin(url) {
3342    return (
3343      normalizeOrigin(url.origin) === normalizeOrigin(window.location.origin)
3344    );
3345  }
3346
3347  function shouldSkipLink(link) {
3348    if (!link || link.hasAttribute(SKIP_ATTR)) {
3349      return true;
3350    }
3351
3352    var href = link.getAttribute("href");
3353
3354    if (!isWebHref(href)) {
3355      return true;
3356    }
3357
3358    var url;
3359
3360    try {
3361      url = new URL(href);
3362    } catch (error) {
3363      return true;
3364    }
3365
3366    return isSameOrigin(url);
3367  }
3368
3369  function mergeRel(link) {
3370    var relValues = (link.getAttribute("rel") || "")
3371      .split(/\s+/)
3372      .map(function (value) {
3373        return value.trim().toLowerCase();
3374      })
3375      .filter(Boolean);
3376
3377    if (relValues.indexOf("noopener") === -1) {
3378      relValues.push("noopener");
3379    }
3380
3381    if (relValues.indexOf("noreferrer") === -1) {
3382      relValues.push("noreferrer");
3383    }
3384
3385    link.setAttribute("rel", relValues.join(" "));
3386  }
3387
3388  function normalizeText(value) {
3389    return String(value || "")
3390      .replace(/\s+/g, " ")
3391      .trim()
3392      .toLowerCase()
3393      .replace(/[áàäâ]/g, "a")
3394      .replace(/[éèëê]/g, "e")
3395      .replace(/[íìïî]/g, "i")
3396      .replace(/[óòöô]/g, "o")
3397      .replace(/[úùüû]/g, "u")
3398      .replace(/ñ/g, "n");
3399  }
3400
3401  function hasNewWindowAnnouncement(value) {
3402    var text = normalizeText(value);
3403
3404    return (
3405      text.indexOf("nueva ventana") !== -1 ||
3406      text.indexOf("nueva pestana") !== -1 ||
3407      text.indexOf("pestana nueva") !== -1
3408    );
3409  }
3410
3411  function addNewTabAnnouncement(link) {
3412    var alreadyHas = link.querySelector("[data-new-tab-hint], [data-new-window-hint]");
3413    if (alreadyHas) return;
3414    if (hasNewWindowAnnouncement(link.getAttribute("aria-label"))) return;
3415    if (hasNewWindowAnnouncement(link.getAttribute("title"))) return;
3416    if (hasNewWindowAnnouncement(link.textContent)) return;
3417
3418    var hint = document.createElement("span");
3419    hint.setAttribute("data-new-tab-hint", "");
3420    hint.className = "enisa-sr-only";
3421    hint.textContent = " (se abre en una nueva ventana)";
3422    link.appendChild(hint);
3423  }
3424
3425  function enhanceLink(link) {
3426    if (!link || link.getAttribute(INIT_ATTR) === "true") {
3427      return;
3428    }
3429
3430    link.setAttribute(INIT_ATTR, "true");
3431
3432    if (shouldSkipLink(link)) {
3433      return;
3434    }
3435
3436    if (!link.hasAttribute("target")) {
3437      link.setAttribute("target", "_blank");
3438    }
3439
3440    mergeRel(link);
3441    addNewTabAnnouncement(link);
3442  }
3443
3444  function getLinksFromScope(scope) {
3445    var root = scope || document;
3446
3447    if (!root.querySelectorAll) {
3448      return [];
3449    }
3450
3451    return Array.prototype.slice.call(
3452      root.querySelectorAll("a[href]:not([" + INIT_ATTR + "])"),
3453    );
3454  }
3455
3456  function initExternalWebLinks(scope) {
3457    var links = getLinksFromScope(scope);
3458
3459    links.forEach(function (link) {
3460      enhanceLink(link);
3461    });
3462  }
3463
3464  function nodeHasLinks(node) {
3465    if (!isElement(node)) {
3466      return false;
3467    }
3468
3469    if (node.matches && node.matches("a[href]")) {
3470      return true;
3471    }
3472
3473    return !!(node.querySelector && node.querySelector("a[href]"));
3474  }
3475
3476  function initMutationObserver() {
3477    if (!("MutationObserver" in window) || !document.body) {
3478      return;
3479    }
3480
3481    var timer = null;
3482
3483    var observer = new MutationObserver(function (mutations) {
3484      var hasNewLinks = mutations.some(function (mutation) {
3485        return Array.prototype.slice
3486          .call(mutation.addedNodes)
3487          .some(nodeHasLinks);
3488      });
3489
3490      if (!hasNewLinks) {
3491        return;
3492      }
3493
3494      clearTimeout(timer);
3495
3496      timer = setTimeout(function () {
3497        initExternalWebLinks(document);
3498      }, OBSERVER_DEBOUNCE);
3499    });
3500
3501    observer.observe(document.body, {
3502      childList: true,
3503      subtree: true,
3504    });
3505  }
3506
3507  function init() {
3508    initExternalWebLinks(document);
3509    initMutationObserver();
3510  }
3511
3512  if (document.readyState === "loading") {
3513    document.addEventListener("DOMContentLoaded", init);
3514  } else {
3515    init();
3516  }
3517
3518  window.ENISA = window.ENISA || {};
3519  window.ENISA[MODULE_NAMESPACE] = {
3520    init: initExternalWebLinks,
3521  };
3522})();
3523
3524/*
3525  ENISA | JS Module
3526  Modulo: 07-card-links
3527  Descripcion: Hace foco y activacion por teclado sobre la card, usando un enlace primario interno como destino.
3528*/
3529(function () {
3530  "use strict";
3531
3532  var ROOT_SELECTOR = "[data-card-link]";
3533  var TARGET_SELECTOR = "[data-card-link-target]";
3534  var INIT_ATTR = "data-card-link-init";
3535  var FOCUS_CLASS = "is-card-focus-visible";
3536  var INTERACTIVE_SELECTOR =
3537    "a[href], button, input, select, textarea, summary, [role='button'], [role='link']";
3538
3539  function getTarget(root) {
3540    return root ? root.querySelector(TARGET_SELECTOR) : null;
3541  }
3542
3543  function isModifiedEvent(event) {
3544    return !!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0);
3545  }
3546
3547  function isInteractiveDescendant(target, root) {
3548    if (!target || !root || target === root || !target.closest) return false;
3549
3550    var interactive = target.closest(INTERACTIVE_SELECTOR);
3551    return !!(interactive && root.contains(interactive) && interactive !== root);
3552  }
3553
3554  function activateTarget(target) {
3555    if (!target) return;
3556    target.click();
3557  }
3558
3559  function isKeyboardFocus() {
3560    return document.documentElement.classList.contains("enisa-using-keyboard");
3561  }
3562
3563  function isTargetFocus(eventTarget, root) {
3564    if (!eventTarget || !root || !eventTarget.matches) return false;
3565    return eventTarget.matches(TARGET_SELECTOR) && root.contains(eventTarget);
3566  }
3567
3568  function isVisibleFocus(eventTarget) {
3569    if (!eventTarget || !eventTarget.matches) return false;
3570
3571    try {
3572      return eventTarget.matches(":focus-visible");
3573    } catch (error) {
3574      return false;
3575    }
3576  }
3577
3578  function initRoot(root) {
3579    var target;
3580
3581    if (!root || root.getAttribute(INIT_ATTR) === "true") return;
3582
3583    target = getTarget(root);
3584    if (!target) return;
3585
3586    root.setAttribute(INIT_ATTR, "true");
3587
3588    root.addEventListener("click", function (event) {
3589      if (isModifiedEvent(event) || isInteractiveDescendant(event.target, root)) return;
3590      activateTarget(target);
3591    });
3592
3593    root.addEventListener("focusin", function (event) {
3594      if (isTargetFocus(event.target, root) && (isKeyboardFocus() || isVisibleFocus(event.target))) {
3595        root.classList.add(FOCUS_CLASS);
3596      }
3597    });
3598
3599    root.addEventListener("focusout", function (event) {
3600      if (!root.contains(event.relatedTarget)) {
3601        root.classList.remove(FOCUS_CLASS);
3602      }
3603    });
3604  }
3605
3606  function toArray(list) {
3607    return Array.prototype.slice.call(list || []);
3608  }
3609
3610  function initScope(scope) {
3611    var roots = [];
3612
3613    if (!scope || !scope.querySelectorAll) return;
3614
3615    if (scope.matches && scope.matches(ROOT_SELECTOR)) {
3616      roots.push(scope);
3617    }
3618
3619    roots = roots.concat(toArray(scope.querySelectorAll(ROOT_SELECTOR)));
3620    roots.forEach(initRoot);
3621  }
3622
3623  function init() {
3624    initScope(document);
3625
3626    if (!("MutationObserver" in window) || !document.body) return;
3627
3628    var observer = new MutationObserver(function (mutations) {
3629      mutations.forEach(function (mutation) {
3630        toArray(mutation.addedNodes).forEach(function (node) {
3631          if (node && node.nodeType === 1) initScope(node);
3632        });
3633      });
3634    });
3635
3636    observer.observe(document.body, {
3637      childList: true,
3638      subtree: true
3639    });
3640  }
3641
3642  if (document.readyState === "loading") {
3643    document.addEventListener("DOMContentLoaded", init);
3644  } else {
3645    init();
3646  }
3647})();
3648
3649/*
3650  ENISA | JS Module
3651  Modulo: 08-hero
3652  Descripcion: Secuencia de entrada de heroes y señal global para sincronizar reveal.
3653*/
3654(function () {
3655  "use strict";
3656
3657  var HERO_SELECTOR =
3658    "[data-hero-sequence], .enisa-hero, .enisa-page-hero, .enisa-hero-simple, .enisa-editorial-hero, .enisa-404";
3659
3660  var HERO_READY_CLASS = "is-hero-ready";
3661  var HERO_ANIMATED_CLASS = "is-animated";
3662  var HERO_READY_EVENT = "enisa:hero:ready";
3663  var FIRST_HERO_READY_ATTR = "data-enisa-first-hero-ready";
3664
3665  function prefersReducedMotion() {
3666    return window.matchMedia &&
3667      window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3668  }
3669
3670  function getFirstVisibleHero(heroes) {
3671    var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
3672
3673    return heroes.find(function (hero) {
3674      if (!hero || !hero.getClientRects || !hero.getClientRects().length) return false;
3675
3676      var rect = hero.getBoundingClientRect();
3677      return rect.bottom > 0 && rect.top < viewportHeight;
3678    }) || heroes[0] || null;
3679  }
3680
3681  function getHeroReadyDelay(hero) {
3682    if (!hero) return 0;
3683
3684    var fromAttr = parseInt(hero.getAttribute("data-hero-ready-ms") || "", 10);
3685    if (!isNaN(fromAttr) && fromAttr >= 0) return fromAttr;
3686
3687    var fromCSS = parseFloat(
3688      window.getComputedStyle(hero).getPropertyValue("--enisa-hero-ready-ms") || ""
3689    );
3690    if (!isNaN(fromCSS) && fromCSS >= 0) return fromCSS;
3691
3692    if (hero.matches(".enisa-hero--slider")) return 980;
3693    if (hero.matches(".enisa-page-hero")) return 760;
3694    if (hero.matches(".enisa-hero-simple")) return 880;
3695    if (hero.matches(".enisa-editorial-hero")) return 780;
3696    if (hero.matches(".enisa-hero")) return 980;
3697
3698    return 700;
3699  }
3700
3701  function dispatchHeroReady(hero) {
3702    if (!hero || hero.classList.contains(HERO_READY_CLASS)) return;
3703
3704    hero.classList.add(HERO_READY_CLASS);
3705
3706    if (document.documentElement.getAttribute(FIRST_HERO_READY_ATTR) !== "true") {
3707      document.documentElement.setAttribute(FIRST_HERO_READY_ATTR, "true");
3708
3709      document.dispatchEvent(new CustomEvent(HERO_READY_EVENT, {
3710        detail: {
3711          hero: hero
3712        }
3713      }));
3714    }
3715  }
3716
3717  function animateHero(hero, isFirstHero) {
3718    if (!hero) return;
3719
3720    if (hero.classList.contains(HERO_ANIMATED_CLASS)) {
3721      if (isFirstHero) {
3722        window.setTimeout(function () {
3723          dispatchHeroReady(hero);
3724        }, getHeroReadyDelay(hero));
3725      }
3726      return;
3727    }
3728
3729    hero.classList.add(HERO_ANIMATED_CLASS);
3730
3731    if (isFirstHero) {
3732      window.setTimeout(function () {
3733        dispatchHeroReady(hero);
3734      }, getHeroReadyDelay(hero));
3735    }
3736  }
3737
3738  function initHeroSequence() {
3739    var heroes = [].slice.call(document.querySelectorAll(HERO_SELECTOR));
3740    if (!heroes.length) {
3741      document.documentElement.setAttribute(FIRST_HERO_READY_ATTR, "true");
3742      document.dispatchEvent(new CustomEvent(HERO_READY_EVENT, { detail: { hero: null } }));
3743      return;
3744    }
3745
3746    var firstHero = getFirstVisibleHero(heroes);
3747
3748    if (prefersReducedMotion() || !("IntersectionObserver" in window)) {
3749      heroes.forEach(function (hero) {
3750        hero.classList.add(HERO_ANIMATED_CLASS);
3751      });
3752      dispatchHeroReady(firstHero);
3753      return;
3754    }
3755
3756    var io = new IntersectionObserver(
3757      function (entries) {
3758        entries.forEach(function (entry) {
3759          if (!entry.isIntersecting) return;
3760
3761          animateHero(entry.target, entry.target === firstHero);
3762          io.unobserve(entry.target);
3763        });
3764      },
3765      {
3766        threshold: 0,
3767        rootMargin: "0px 0px -8% 0px"
3768      }
3769    );
3770
3771    heroes.forEach(function (hero) {
3772      var rect = hero.getBoundingClientRect();
3773
3774      if (rect.top < window.innerHeight) {
3775        animateHero(hero, hero === firstHero);
3776        return;
3777      }
3778
3779      io.observe(hero);
3780    });
3781  }
3782
3783  if (document.readyState === "loading") {
3784    document.addEventListener("DOMContentLoaded", initHeroSequence);
3785  } else {
3786    initHeroSequence();
3787  }
3788})();
3789
3790(function () {
3791  "use strict";
3792
3793  var HERO_SELECTOR = "[data-editorial-hero]";
3794  var STORAGE_KEY = "enisa_editorial_saved";
3795
3796  function readSaved() {
3797    try {
3798      var raw = window.localStorage.getItem(STORAGE_KEY);
3799      if (!raw) return [];
3800      var parsed = JSON.parse(raw);
3801      return Array.isArray(parsed) ? parsed : [];
3802    } catch (e) {
3803      return [];
3804    }
3805  }
3806
3807  function writeSaved(list) {
3808    try {
3809      window.localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
3810    } catch (e) {}
3811  }
3812
3813  function initEditorialHero(root) {
3814    if (!root || root.getAttribute("data-editorial-hero-init") === "true") return;
3815    root.setAttribute("data-editorial-hero-init", "true");
3816
3817    var shareBtn = root.querySelector("[data-editorial-share]");
3818    var saveBtn = root.querySelector("[data-editorial-save]");
3819    var saveLabel = root.querySelector("[data-editorial-save-label]");
3820    var slug = (root.getAttribute("data-editorial-slug") || "").trim();
3821    var shareUrl = (root.getAttribute("data-editorial-share-url") || window.location.href || "").trim();
3822    var titleEl =
3823      root.querySelector(".enisa-editorial-hero__title") ||
3824      root.querySelector(".enisa-page-hero__title");
3825    var shareTitle = titleEl ? titleEl.textContent.trim() : document.title;
3826
3827    function updateSaveState() {
3828      if (!saveBtn || !saveLabel || !slug) return;
3829      var saved = readSaved();
3830      var isSaved = saved.indexOf(slug) !== -1;
3831      saveBtn.classList.toggle("is-saved", isSaved);
3832      saveBtn.setAttribute("aria-pressed", isSaved ? "true" : "false");
3833      saveLabel.textContent = isSaved ? "Guardado" : "Guardar";
3834    }
3835
3836    if (shareBtn) {
3837      shareBtn.addEventListener("click", function () {
3838        if (navigator.share) {
3839          navigator.share({ title: shareTitle, url: shareUrl }).catch(function () {});
3840          return;
3841        }
3842
3843        if (navigator.clipboard && navigator.clipboard.writeText) {
3844          navigator.clipboard.writeText(shareUrl).then(function () {
3845            shareBtn.classList.add("is-saved");
3846            setTimeout(function () {
3847              shareBtn.classList.remove("is-saved");
3848            }, 1200);
3849          }).catch(function () {});
3850        }
3851      });
3852    }
3853
3854    if (saveBtn && slug) {
3855      saveBtn.addEventListener("click", function () {
3856        var saved = readSaved();
3857        var index = saved.indexOf(slug);
3858        if (index === -1) {
3859          saved.push(slug);
3860        } else {
3861          saved.splice(index, 1);
3862        }
3863        writeSaved(saved);
3864        updateSaveState();
3865      });
3866    }
3867
3868    updateSaveState();
3869  }
3870
3871  function normalizeMediaUrl(url) {
3872    var value = (url || "").trim();
3873    if (!value) return "";
3874    var noQuery = value.split("?")[0].split("#")[0];
3875    return noQuery.toLowerCase();
3876  }
3877
3878  function initProseDedupImages() {
3879    var proseBlocks = [].slice.call(document.querySelectorAll("[data-prose-dedup-image]"));
3880    if (!proseBlocks.length) return;
3881
3882    proseBlocks.forEach(function (block) {
3883      if (!block || block.getAttribute("data-prose-dedup-init") === "true") return;
3884      block.setAttribute("data-prose-dedup-init", "true");
3885
3886      var target = normalizeMediaUrl(block.getAttribute("data-prose-dedup-image"));
3887      if (!target) return;
3888
3889      var firstDuplicate = null;
3890
3891      [].slice.call(block.querySelectorAll("img")).forEach(function (img) {
3892        if (!img || firstDuplicate) return;
3893
3894        var src = normalizeMediaUrl(img.getAttribute("src") || img.currentSrc || "");
3895        if (!src || src !== target) return;
3896
3897        firstDuplicate = img;
3898      });
3899
3900      if (!firstDuplicate) return;
3901
3902      var wrapper =
3903        firstDuplicate.closest("figure, p, div") ||
3904        firstDuplicate.parentElement ||
3905        firstDuplicate;
3906
3907      if (wrapper && wrapper !== block) {
3908        wrapper.setAttribute("hidden", "");
3909      } else {
3910        firstDuplicate.setAttribute("hidden", "");
3911      }
3912    });
3913  }
3914
3915  function init() {
3916    var heroes = document.querySelectorAll(HERO_SELECTOR);
3917
3918    if (heroes.length) {
3919      [].forEach.call(heroes, initEditorialHero);
3920    }
3921
3922    initProseDedupImages();
3923  }
3924
3925  if (document.readyState === "loading") {
3926    document.addEventListener("DOMContentLoaded", init);
3927  } else {
3928    init();
3929  }
3930})();
3931
3932/*
3933  ENISA | JS Module
3934  Modulo: 09-reveal
3935  Descripcion: Reveal progresivo sincronizado con el primer hero visible.
3936*/
3937(function () {
3938  "use strict";
3939
3940  var REVEAL_SELECTOR        = ".is-reveal, .is-bg-reveal";
3941  var VISIBLE_CLASS          = "is-visible";
3942  var INIT_ATTR              = "data-reveal-init";
3943  var REVEAL_GROUP_ATTR      = "data-reveal-group";
3944  var REVEAL_CHILD_INIT_ATTR = "data-reveal-child-init";
3945  var STAGGER_STEP           = 70;
3946  var STAGGER_MAX            = 8;
3947  var REVEAL_GAP_MS          = 16;
3948  var HERO_READY_EVENT       = "enisa:hero:ready";
3949  var HERO_READY_ATTR        = "data-enisa-first-hero-ready";
3950  var FALLBACK_GATE_MS       = 1100;
3951
3952  var SKIP_SELECTOR =
3953    ".enisa-hero, .enisa-page-hero, .enisa-hero-simple, .enisa-editorial-hero, .enisa-404";
3954
3955  var AUTO_REVEAL_SCOPE =
3956    "#main-content > [class^='enisa-'], #main-content > [class*=' enisa-'], " +
3957    "#main-content > [class^='enisa-'], #main-content > [class*=' enisa-'], " +
3958    ".page-layout__content > [class^='enisa-'], .page-layout__content > [class*=' enisa-'], " +
3959    ".page-layout__main > [class^='enisa-'], .page-layout__main > [class*=' enisa-']";
3960
3961  var observer = null;
3962  var moTimer  = null;
3963  var fallbackTimer = null;
3964  var revealStarted = false;
3965  var MO_SCOPE = document.querySelector(".page-layout__content") || document.body;
3966  var revealQueue = [];
3967  var revealQueuedSet = new WeakSet();
3968  var revealTimer = null;
3969
3970  function prefersReducedMotion() {
3971    return window.matchMedia &&
3972      window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3973  }
3974
3975  function isAuthoringOrStudioContext() {
3976    var docEl = document.documentElement;
3977    var body  = document.body;
3978    var host  = (window.location.hostname || "").toLowerCase();
3979    var query = (window.location.search || "").toLowerCase();
3980    var ref   = (document.referrer || "").toLowerCase();
3981    var snap  = ((docEl && docEl.className) || "") + " " + ((body && body.className) || "");
3982
3983    if (/\b(studio|author|editor|editable|design-mode)\b/.test(snap)) return true;
3984    if (/[?&](studio|authoring|editor|editmode|design)=/.test(query)) return true;
3985    if (host.indexOf("make.powerpages") !== -1) return true;
3986
3987    if (window.self !== window.top) {
3988      return ["powerpages", "make.powerapps", "make.powerpages", "portalstudio"].some(function (signal) {
3989        return ref.indexOf(signal) !== -1;
3990      });
3991    }
3992
3993    return false;
3994  }
3995
3996  function shouldSkipAutoReveal(node) {
3997    if (!node || !node.matches) return true;
3998    if (node.getAttribute(REVEAL_GROUP_ATTR) === "skip") return true;
3999    return node.matches(SKIP_SELECTOR);
4000  }
4001
4002  function registerAutoRevealGroups() {
4003    [].slice.call(document.querySelectorAll(AUTO_REVEAL_SCOPE)).forEach(function (root) {
4004      if (shouldSkipAutoReveal(root)) return;
4005
4006      if (!root.hasAttribute(REVEAL_GROUP_ATTR)) {
4007        root.setAttribute(REVEAL_GROUP_ATTR, "true");
4008      }
4009    });
4010  }
4011
4012  function hydrateRevealGroups() {
4013    var selector = "[" + REVEAL_GROUP_ATTR + "]:not([" + REVEAL_GROUP_ATTR + "='skip'])";
4014
4015    [].slice.call(document.querySelectorAll(selector)).forEach(function (group) {
4016      if (shouldSkipAutoReveal(group)) return;
4017      if (group.querySelector(".is-reveal, .is-bg-reveal")) return;
4018
4019      [].slice.call(group.children)
4020        .filter(function (child) {
4021          return child && child.nodeType === 1 && !child.matches("script, style, template");
4022        })
4023        .forEach(function (child, index) {
4024          if (child.getAttribute(REVEAL_CHILD_INIT_ATTR) === "true") return;
4025
4026          child.setAttribute(REVEAL_CHILD_INIT_ATTR, "true");
4027          child.classList.add("is-reveal");
4028
4029          if (
4030            !child.hasAttribute("data-reveal-delay") &&
4031            !child.style.getPropertyValue("--enisa-reveal-delay")
4032          ) {
4033            child.style.setProperty(
4034              "--enisa-reveal-delay",
4035              Math.min(index, STAGGER_MAX) * STAGGER_STEP + "ms"
4036            );
4037          }
4038        });
4039    });
4040  }
4041
4042  function normalizeRevealDelay(el) {
4043    if (!el || !el.getAttribute || !el.style) return;
4044    if (el.style.getPropertyValue("--enisa-reveal-delay")) return;
4045
4046    var raw = (el.getAttribute("data-reveal-delay") || "").trim();
4047    if (!raw) return;
4048
4049    var parsed = parseInt(raw, 10);
4050    if (isNaN(parsed) || parsed < 0) return;
4051
4052    if (parsed <= 10) {
4053      el.style.setProperty("--enisa-reveal-delay", parsed * STAGGER_STEP + "ms");
4054    } else {
4055      el.style.setProperty("--enisa-reveal-delay", parsed + "ms");
4056    }
4057  }
4058
4059  function hasRenderableBox(el) {
4060    if (!el || !el.getClientRects) return false;
4061    if (el.closest && el.closest("[hidden]")) return false;
4062    return el.getClientRects().length > 0;
4063  }
4064
4065  function isInViewport(el) {
4066    if (!hasRenderableBox(el)) return false;
4067
4068    var rect = el.getBoundingClientRect();
4069    var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;
4070
4071    return rect.bottom > 0 && rect.top < viewportHeight;
4072  }
4073
4074  function sortQueueByViewportOrder() {
4075    revealQueue.sort(function (a, b) {
4076      var ar = a.getBoundingClientRect();
4077      var br = b.getBoundingClientRect();
4078
4079      if (Math.abs(ar.top - br.top) > 4) return ar.top - br.top;
4080      return ar.left - br.left;
4081    });
4082  }
4083
4084  function processRevealQueue() {
4085    if (revealTimer || !revealQueue.length) return;
4086
4087    revealTimer = window.setTimeout(function () {
4088      revealTimer = null;
4089      sortQueueByViewportOrder();
4090
4091      while (revealQueue.length) {
4092        var el = revealQueue.shift();
4093
4094        if (!el || el.classList.contains(VISIBLE_CLASS)) continue;
4095        el.classList.add(VISIBLE_CLASS);
4096      }
4097    }, REVEAL_GAP_MS);
4098  }
4099
4100  function enqueueReveal(el) {
4101    if (!el || revealQueuedSet.has(el) || el.classList.contains(VISIBLE_CLASS)) return;
4102
4103    revealQueuedSet.add(el);
4104    revealQueue.push(el);
4105    processRevealQueue();
4106  }
4107
4108  function getObserver() {
4109    if (observer) return observer;
4110
4111    observer = new IntersectionObserver(
4112      function (entries) {
4113        entries.forEach(function (entry) {
4114          if (!entry.isIntersecting) return;
4115
4116          enqueueReveal(entry.target);
4117          observer.unobserve(entry.target);
4118        });
4119      },
4120      {
4121        rootMargin: "0px 0px -12% 0px",
4122        threshold: 0.08
4123      }
4124    );
4125
4126    return observer;
4127  }
4128
4129  function prepareRevealDom() {
4130    registerAutoRevealGroups();
4131    hydrateRevealGroups();
4132
4133    [].slice.call(document.querySelectorAll(REVEAL_SELECTOR)).forEach(function (el) {
4134      normalizeRevealDelay(el);
4135    });
4136  }
4137
4138  function startReveal() {
4139    if (revealStarted) return;
4140    revealStarted = true;
4141
4142    if (fallbackTimer) {
4143      window.clearTimeout(fallbackTimer);
4144      fallbackTimer = null;
4145    }
4146
4147    prepareRevealDom();
4148
4149    var elements = [].slice.call(document.querySelectorAll(REVEAL_SELECTOR));
4150    if (!elements.length) return;
4151
4152    if (prefersReducedMotion() || !("IntersectionObserver" in window)) {
4153      elements.forEach(function (el) {
4154        el.setAttribute(INIT_ATTR, "true");
4155        el.classList.add(VISIBLE_CLASS);
4156      });
4157      return;
4158    }
4159
4160    var obs = getObserver();
4161
4162    elements.forEach(function (el) {
4163      if (el.getAttribute(INIT_ATTR) === "true") return;
4164
4165      el.setAttribute(INIT_ATTR, "true");
4166
4167      if (isInViewport(el)) {
4168        enqueueReveal(el);
4169        return;
4170      }
4171
4172      obs.observe(el);
4173    });
4174  }
4175
4176  function initReveal() {
4177    prepareRevealDom();
4178
4179    if (isAuthoringOrStudioContext() || prefersReducedMotion()) {
4180      startReveal();
4181      return;
4182    }
4183
4184    if (document.documentElement.getAttribute(HERO_READY_ATTR) === "true") {
4185      startReveal();
4186      return;
4187    }
4188
4189    document.addEventListener(HERO_READY_EVENT, startReveal, { once: true });
4190
4191    fallbackTimer = window.setTimeout(function () {
4192      startReveal();
4193    }, FALLBACK_GATE_MS);
4194  }
4195
4196  if (document.readyState === "loading") {
4197    document.addEventListener("DOMContentLoaded", initReveal);
4198  } else {
4199    initReveal();
4200  }
4201
4202  if ("MutationObserver" in window) {
4203    var mo = new MutationObserver(function (mutations) {
4204      if (!mutations.some(function (m) { return m.addedNodes.length > 0; })) return;
4205
4206      window.clearTimeout(moTimer);
4207      moTimer = window.setTimeout(function () {
4208        prepareRevealDom();
4209
4210        if (revealStarted) {
4211          startReveal();
4212        }
4213      }, 150);
4214    });
4215
4216    mo.observe(MO_SCOPE, {
4217      childList: true,
4218      subtree: true
4219    });
4220  }
4221})();
4222
4223/*
4224  ENISA | JS Module
4225  Modulo: 10-hero-slider
4226  Descripcion: Logica del modulo.
4227*/
4228(function () {
4229  "use strict";
4230
4231  var ROOT_SELECTOR = "[data-hero-slider]";
4232  var SLIDE_SELECTOR = "[data-hero-slide]";
4233  var DOT_SELECTOR = "[data-hero-dot]";
4234  var PREV_SELECTOR = "[data-hero-prev]";
4235  var NEXT_SELECTOR = "[data-hero-next]";
4236  var STATUS_SELECTOR = "[data-hero-status]";
4237  var INIT_ATTR = "data-hero-slider-init";
4238  var ACTIVE_CLASS = "is-active";
4239  var ENTER_CLASS = "is-entering";
4240
4241  function prefersReducedMotion() {
4242    return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
4243  }
4244
4245  function toBoolean(value) {
4246    if (!value) return false;
4247    var normalized = String(value).toLowerCase().trim();
4248    return normalized === "true" || normalized === "1" || normalized === "yes";
4249  }
4250
4251  function getInterval(value) {
4252    var parsed = parseInt(value, 10);
4253    if (isNaN(parsed)) return 7000;
4254    return Math.min(20000, Math.max(3000, parsed));
4255  }
4256
4257  function setSlideFocusableState(slide, active) {
4258    var focusables = [].slice.call(
4259      slide.querySelectorAll("a[href], button, input, select, textarea, [tabindex]")
4260    );
4261
4262    focusables.forEach(function (el) {
4263      if (active) {
4264        if (el.hasAttribute("data-hero-tabindex")) {
4265          var stored = el.getAttribute("data-hero-tabindex");
4266          if (stored === "") {
4267            el.removeAttribute("tabindex");
4268          } else {
4269            el.setAttribute("tabindex", stored);
4270          }
4271          el.removeAttribute("data-hero-tabindex");
4272        }
4273        return;
4274      }
4275
4276      if (!el.hasAttribute("data-hero-tabindex")) {
4277        var current = el.getAttribute("tabindex");
4278        if (current === null) {
4279          el.setAttribute("data-hero-tabindex", "");
4280        } else {
4281          el.setAttribute("data-hero-tabindex", current);
4282        }
4283      }
4284
4285      el.setAttribute("tabindex", "-1");
4286    });
4287  }
4288
4289  function safeFocus(element) {
4290    if (
4291      window.ENISA &&
4292      window.ENISA.utils &&
4293      typeof window.ENISA.utils.safeFocus === "function"
4294    ) {
4295      return window.ENISA.utils.safeFocus(element);
4296    }
4297
4298    if (!element || typeof element.focus !== "function") return false;
4299
4300    try {
4301      element.focus({ preventScroll: true });
4302    } catch (error) {
4303      element.focus();
4304    }
4305
4306    return true;
4307  }
4308
4309  function initSlider(root) {
4310    if (!root || root.getAttribute(INIT_ATTR) === "true") return;
4311    root.setAttribute(INIT_ATTR, "true");
4312
4313    var slides = [].slice.call(root.querySelectorAll(SLIDE_SELECTOR));
4314    var dots = [].slice.call(root.querySelectorAll(DOT_SELECTOR));
4315    var prevBtn = root.querySelector(PREV_SELECTOR);
4316    var nextBtn = root.querySelector(NEXT_SELECTOR);
4317    var status = root.querySelector(STATUS_SELECTOR);
4318    var total = slides.length;
4319    if (!total) return;
4320
4321    var reducedMotion = prefersReducedMotion();
4322    var autoplayEnabled = toBoolean(root.getAttribute("data-autoplay"));
4323    var autoplayInterval = getInterval(root.getAttribute("data-autoplay-interval"));
4324    var current = 0;
4325    var autoplayTimer = null;
4326    var isHoverPaused = false;
4327    var isFocusPaused = false;
4328
4329    slides.forEach(function (slide, index) {
4330      if (slide.classList.contains(ACTIVE_CLASS)) {
4331        current = index;
4332      }
4333    });
4334
4335    function stopAutoplay() {
4336      if (!autoplayTimer) return;
4337      clearTimeout(autoplayTimer);
4338      autoplayTimer = null;
4339    }
4340
4341    function canAutoplay() {
4342      return (
4343        autoplayEnabled &&
4344        total > 1 &&
4345        !reducedMotion &&
4346        !document.hidden &&
4347        !isHoverPaused &&
4348        !isFocusPaused
4349      );
4350    }
4351
4352    function scheduleAutoplay() {
4353      stopAutoplay();
4354      if (!canAutoplay()) return;
4355
4356      autoplayTimer = setTimeout(function () {
4357        goTo(current + 1);
4358      }, autoplayInterval);
4359    }
4360
4361    function restartEntryAnimation(slide) {
4362      slide.classList.remove(ENTER_CLASS);
4363      if (!reducedMotion) {
4364        void slide.offsetWidth;
4365      }
4366      slide.classList.add(ENTER_CLASS);
4367    }
4368
4369    function updateStatus() {
4370      if (!status) return;
4371      var activeSlide = slides[current];
4372      var label = activeSlide && activeSlide.getAttribute("aria-label");
4373      status.textContent = label || ("Diapositiva " + (current + 1) + " de " + total);
4374    }
4375
4376    // animate=false en el primer render: respetamos la clase is-entering que
4377    // ya viene del markup del servidor. Reiniciarla aqui es lo que provoca el
4378    // corte "aparece / desaparece / reaparece" en el hero.
4379    function updateUI(animate) {
4380      slides.forEach(function (slide, index) {
4381        var active = index === current;
4382        slide.classList.toggle(ACTIVE_CLASS, active);
4383        if (!active) {
4384          slide.classList.remove(ENTER_CLASS);
4385        }
4386        slide.setAttribute("aria-hidden", active ? "false" : "true");
4387        setSlideFocusableState(slide, active);
4388        if (active && animate) {
4389          restartEntryAnimation(slide);
4390        }
4391      });
4392
4393      dots.forEach(function (dot, index) {
4394        var active = index === current;
4395        dot.classList.toggle(ACTIVE_CLASS, active);
4396        dot.setAttribute("aria-current", active ? "true" : "false");
4397        dot.setAttribute("aria-selected", active ? "true" : "false");
4398        dot.setAttribute("tabindex", active ? "0" : "-1");
4399      });
4400
4401      updateStatus();
4402    }
4403
4404    function goTo(index) {
4405      if (total < 2) return;
4406      var nextIndex = (index + total) % total;
4407      if (nextIndex === current) {
4408        scheduleAutoplay();
4409        return;
4410      }
4411
4412      current = nextIndex;
4413      updateUI(true);
4414      scheduleAutoplay();
4415    }
4416
4417    if (prevBtn) {
4418      prevBtn.addEventListener("click", function () {
4419        goTo(current - 1);
4420      });
4421    }
4422
4423    if (nextBtn) {
4424      nextBtn.addEventListener("click", function () {
4425        goTo(current + 1);
4426      });
4427    }
4428
4429    dots.forEach(function (dot) {
4430      dot.addEventListener("click", function () {
4431        var index = parseInt(dot.getAttribute("data-hero-dot"), 10);
4432        if (!isNaN(index)) {
4433          goTo(index);
4434        }
4435      });
4436
4437      dot.addEventListener("keydown", function (event) {
4438        var nextIndex = null;
4439
4440        switch (event.key) {
4441          case "ArrowRight":
4442          case "ArrowDown":
4443            nextIndex = (current + 1) % total;
4444            break;
4445          case "ArrowLeft":
4446          case "ArrowUp":
4447            nextIndex = (current - 1 + total) % total;
4448            break;
4449          case "Home":
4450            nextIndex = 0;
4451            break;
4452          case "End":
4453            nextIndex = total - 1;
4454            break;
4455          default:
4456            return;
4457        }
4458
4459        event.preventDefault();
4460        event.stopPropagation();
4461        goTo(nextIndex);
4462        if (dots[nextIndex]) {
4463          safeFocus(dots[nextIndex]);
4464        }
4465      });
4466    });
4467
4468    if (autoplayEnabled) {
4469      root.addEventListener("mouseenter", function () {
4470        isHoverPaused = true;
4471        stopAutoplay();
4472      });
4473
4474      root.addEventListener("mouseleave", function () {
4475        isHoverPaused = false;
4476        scheduleAutoplay();
4477      });
4478
4479      root.addEventListener("focusin", function () {
4480        isFocusPaused = true;
4481        stopAutoplay();
4482      });
4483
4484      root.addEventListener("focusout", function (event) {
4485        if (root.contains(event.relatedTarget)) return;
4486        isFocusPaused = false;
4487        scheduleAutoplay();
4488      });
4489
4490      document.addEventListener("visibilitychange", function () {
4491        if (document.hidden) {
4492          stopAutoplay();
4493          return;
4494        }
4495        scheduleAutoplay();
4496      });
4497    }
4498
4499    updateUI(false);
4500    scheduleAutoplay();
4501  }
4502
4503  function init() {
4504    var sliders = [].slice.call(document.querySelectorAll(ROOT_SELECTOR));
4505    if (!sliders.length) return;
4506    sliders.forEach(initSlider);
4507  }
4508
4509  if (document.readyState === "loading") {
4510    document.addEventListener("DOMContentLoaded", init);
4511  } else {
4512    init();
4513  }
4514})();
4515
4516/*
4517  ENISA | JS Module
4518  Modulo: 11-cta
4519  Descripcion: Logica del modulo.
4520*/
4521(function () {
4522  "use strict";
4523
4524  var CTA_SELECTOR = "[data-cta-sequence]";
4525
4526  function initCtaSequence() {
4527    var banners = [].slice.call(document.querySelectorAll(CTA_SELECTOR));
4528    if (!banners.length) return;
4529
4530    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
4531      banners.forEach(function (banner) {
4532        banner.classList.add("is-animated");
4533      });
4534      return;
4535    }
4536
4537    if (!("IntersectionObserver" in window)) {
4538      banners.forEach(function (banner) {
4539        banner.classList.add("is-animated");
4540      });
4541      return;
4542    }
4543
4544    var io = new IntersectionObserver(
4545      function (entries) {
4546        entries.forEach(function (entry) {
4547          if (!entry.isIntersecting) return;
4548          entry.target.classList.add("is-animated");
4549          io.unobserve(entry.target);
4550        });
4551      },
4552      {
4553        threshold: 0.12,
4554      }
4555    );
4556
4557    banners.forEach(function (banner) {
4558      var rect = banner.getBoundingClientRect();
4559      if (rect.top < window.innerHeight) {
4560        banner.classList.add("is-animated");
4561      } else {
4562        io.observe(banner);
4563      }
4564    });
4565  }
4566
4567  if (document.readyState === "loading") {
4568    document.addEventListener("DOMContentLoaded", initCtaSequence);
4569  } else {
4570    initCtaSequence();
4571  }
4572})();
4573
4574(function () {
4575  "use strict";
4576
4577  var EDITORIAL_LISTING_SELECTOR = "[data-editorial-listing], [data-news-listing]";
4578  var MOBILE_CAROUSEL_BP = window.ENISA.cardsCarousel.MOBILE_BP;
4579
4580  function initEditorialListing(root) {
4581    if (!root) return;
4582    if (root.getAttribute("data-editorial-init") === "true") return;
4583
4584    root.setAttribute("data-editorial-init", "true");
4585
4586    var isNewsMode = root.hasAttribute("data-news-listing");
4587    var attrPrefix = isNewsMode ? "news" : "editorial";
4588    var featuredClass = isNewsMode ? "enisa-news-card--featured" : "enisa-editorial-card--featured";
4589    var disableFeatured = root.getAttribute("data-disable-featured") === "true";
4590
4591    var pageSize = parseInt(root.getAttribute("data-page-size"), 10);
4592    if (isNaN(pageSize) || pageSize < 1) pageSize = 7;
4593
4594    var counterSingular = root.getAttribute("data-counter-singular") || "noticia";
4595    var counterPlural = root.getAttribute("data-counter-plural") || "noticias";
4596    var initialFilter = root.getAttribute("data-initial-filter") || "all";
4597
4598    var filterButtons = [].slice.call(root.querySelectorAll("[data-" + attrPrefix + "-filter]"));
4599    var cards = [].slice.call(root.querySelectorAll("[data-" + attrPrefix + "-card]"));
4600    var counter = root.querySelector("[data-" + attrPrefix + "-counter]");
4601    if (counter && !counter.getAttribute("aria-live")) {
4602      counter.setAttribute("aria-live", "polite");
4603      counter.setAttribute("aria-atomic", "true");
4604    }
4605    var pagination = root.querySelector("[data-" + attrPrefix + "-pagination]");
4606    var grid = root.querySelector("[data-" + attrPrefix + "-grid]");
4607
4608    if (!cards.length || !grid) return;
4609
4610    var currentFilter = "all";
4611    var currentPage = 1;
4612
4613    function normalizeFeaturedValue(value) {
4614      return String(value || "").toLowerCase().trim();
4615    }
4616
4617    function isCardMarkedAsFeatured(card) {
4618      var value = normalizeFeaturedValue(card.getAttribute("data-" + attrPrefix + "-featured"));
4619
4620      return (
4621        value === "true" ||
4622        value === "1" ||
4623        value === "yes" ||
4624        value === "si" ||
4625        value === "sí"
4626      );
4627    }
4628
4629    function hideEmptySecondaryBadges() {
4630      cards.forEach(function (card) {
4631        var secondaryBadges = [].slice.call(
4632          card.querySelectorAll(".enisa-editorial-card__badge--secondary, .enisa-news-badge--tag")
4633        );
4634
4635        secondaryBadges.forEach(function (badge) {
4636          if (!badge || !badge.textContent || !badge.textContent.trim()) {
4637            badge.setAttribute("hidden", "");
4638          }
4639        });
4640      });
4641    }
4642
4643    function getFilteredCards() {
4644      if (currentFilter === "all") return cards.slice();
4645
4646      return cards.filter(function (card) {
4647        return (card.getAttribute("data-category") || "") === currentFilter;
4648      });
4649    }
4650
4651    function getFeaturedCardForPage(pageCards) {
4652      if (disableFeatured || !pageCards.length) {
4653        return null;
4654      }
4655
4656      var markedFeatured = pageCards.find(isCardMarkedAsFeatured);
4657
4658      if (markedFeatured) {
4659        return markedFeatured;
4660      }
4661
4662      return pageCards[0];
4663    }
4664
4665    function getOrderedPageCards(pageCards, featuredCard) {
4666      if (!featuredCard) {
4667        return pageCards.slice();
4668      }
4669
4670      var ordered = pageCards.filter(function (card) {
4671        return card !== featuredCard;
4672      });
4673
4674      var featuredPosition = Math.min(3, ordered.length);
4675
4676      ordered.splice(featuredPosition, 0, featuredCard);
4677
4678      return ordered;
4679    }
4680
4681    function isMobileCarouselViewport() {
4682      return window.innerWidth <= MOBILE_CAROUSEL_BP;
4683    }
4684
4685    function scrollFilterToStart(activeButton) {
4686      if (!activeButton || !isMobileCarouselViewport()) return;
4687
4688      var filterContainer = activeButton.parentElement;
4689      if (!filterContainer) return;
4690
4691      var nextLeft = Math.max(0, activeButton.offsetLeft - filterContainer.offsetLeft);
4692
4693      filterContainer.scrollTo({
4694        left: nextLeft,
4695        behavior: window.ENISA.utils.prefersReducedMotion() ? "auto" : "smooth"
4696      });
4697    }
4698
4699    function safeFocus(element) {
4700      if (
4701        window.ENISA &&
4702        window.ENISA.utils &&
4703        typeof window.ENISA.utils.safeFocus === "function"
4704      ) {
4705        return window.ENISA.utils.safeFocus(element);
4706      }
4707
4708      if (!element || typeof element.focus !== "function") return false;
4709
4710      try {
4711        element.focus({ preventScroll: true });
4712      } catch (error) {
4713        element.focus();
4714      }
4715
4716      return true;
4717    }
4718
4719    function setFilterState(filter, shouldScrollActive) {
4720      currentFilter = filter || "all";
4721      currentPage = 1;
4722
4723      var activeButton = null;
4724
4725      filterButtons.forEach(function (btn) {
4726        var buttonFilter = btn.getAttribute("data-" + attrPrefix + "-filter") || "";
4727        var isActive = buttonFilter === currentFilter;
4728
4729        btn.classList.toggle("is-active", isActive);
4730        btn.setAttribute("aria-pressed", isActive ? "true" : "false");
4731
4732        if (isActive) {
4733          activeButton = btn;
4734        }
4735      });
4736
4737      if (!activeButton && currentFilter !== "all") {
4738        currentFilter = "all";
4739
4740        filterButtons.forEach(function (btn) {
4741          var buttonFilter = btn.getAttribute("data-" + attrPrefix + "-filter") || "";
4742          var isActive = buttonFilter === "all";
4743
4744          btn.classList.toggle("is-active", isActive);
4745          btn.setAttribute("aria-pressed", isActive ? "true" : "false");
4746
4747          if (isActive) {
4748            activeButton = btn;
4749          }
4750        });
4751      }
4752
4753      if (shouldScrollActive) {
4754        scrollFilterToStart(activeButton);
4755      }
4756    }
4757
4758    function updateCounter(total, from, to) {
4759      if (!counter) return;
4760
4761      var label = total === 1 ? counterSingular : counterPlural;
4762
4763      if (total <= 0) {
4764        counter.textContent = "Mostrado 0 " + counterPlural;
4765        return;
4766      }
4767
4768      counter.textContent = "Mostrado " + from + "-" + to + " de " + total + " " + label;
4769    }
4770
4771    function renderPagination(totalPages) {
4772      if (!pagination) return;
4773
4774      pagination.innerHTML = "";
4775
4776      if (totalPages <= 1) {
4777        pagination.setAttribute("hidden", "");
4778        return;
4779      }
4780
4781      pagination.removeAttribute("hidden");
4782
4783      var WINDOW = 3;
4784      var half = Math.floor(WINDOW / 2);
4785      var start = Math.max(1, currentPage - half);
4786      var end = Math.min(totalPages, start + WINDOW - 1);
4787
4788      if (end - start < WINDOW - 1) {
4789        start = Math.max(1, end - WINDOW + 1);
4790      }
4791
4792      var prevBtn = document.createElement("button");
4793      prevBtn.type = "button";
4794      prevBtn.className = "enisa-pagination__btn enisa-pagination__btn--arrow is-prev";
4795      prevBtn.setAttribute("aria-label", "Página anterior");
4796      prevBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
4797      prevBtn.disabled = currentPage === 1;
4798
4799      prevBtn.addEventListener("click", function () {
4800        if (currentPage > 1) {
4801          currentPage -= 1;
4802          render({ type: "prev" });
4803        }
4804      });
4805
4806      pagination.appendChild(prevBtn);
4807
4808      var pageList = document.createElement("div");
4809      pageList.className = "enisa-pagination__list";
4810      pageList.setAttribute("role", "list");
4811
4812      for (var i = start; i <= end; i += 1) {
4813        var pageBtn = document.createElement("button");
4814        pageBtn.type = "button";
4815        pageBtn.className = "enisa-pagination__btn" + (i === currentPage ? " is-active" : "");
4816        pageBtn.textContent = String(i);
4817        pageBtn.setAttribute("aria-label", "Página " + i);
4818        if (i === currentPage) {
4819          pageBtn.setAttribute("aria-current", "page");
4820        }
4821
4822        (function (pageNumber) {
4823          pageBtn.addEventListener("click", function () {
4824            currentPage = pageNumber;
4825            render({ type: "page", page: pageNumber });
4826          });
4827        })(i);
4828
4829        pageList.appendChild(pageBtn);
4830      }
4831
4832      pagination.appendChild(pageList);
4833
4834      var nextBtn = document.createElement("button");
4835      nextBtn.type = "button";
4836      nextBtn.className = "enisa-pagination__btn enisa-pagination__btn--arrow is-next";
4837      nextBtn.setAttribute("aria-label", "Página siguiente");
4838      nextBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
4839      nextBtn.disabled = currentPage === totalPages;
4840
4841      nextBtn.addEventListener("click", function () {
4842        if (currentPage < totalPages) {
4843          currentPage += 1;
4844          render({ type: "next" });
4845        }
4846      });
4847
4848      pagination.appendChild(nextBtn);
4849    }
4850
4851    function getCardRows(pageCards) {
4852      var rows = [];
4853      var current = [];
4854
4855      pageCards.forEach(function (card) {
4856        if (card.classList.contains(featuredClass)) {
4857          if (current.length) {
4858            rows.push(current.slice());
4859            current = [];
4860          }
4861
4862          rows.push([card]);
4863          return;
4864        }
4865
4866        current.push(card);
4867
4868        if (current.length === 3) {
4869          rows.push(current.slice());
4870          current = [];
4871        }
4872      });
4873
4874      if (current.length) {
4875        rows.push(current.slice());
4876      }
4877
4878      return rows;
4879    }
4880
4881    function createRowCarouselControls(viewport, rowCards, rowIndex) {
4882      var controls = document.createElement("div");
4883      controls.className = "enisa-editorial-row-controls";
4884      controls.setAttribute("role", "group");
4885      controls.setAttribute("aria-label", "Controles de carrusel fila " + (rowIndex + 1));
4886
4887      var prevBtn = document.createElement("button");
4888      prevBtn.type = "button";
4889      prevBtn.className = "enisa-editorial-row-controls__btn is-prev";
4890      prevBtn.setAttribute("aria-label", "Tarjeta anterior");
4891      prevBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
4892
4893      var dotsEl = document.createElement("div");
4894      dotsEl.className = "enisa-editorial-row-controls__dots";
4895      dotsEl.setAttribute("role", "group");
4896      dotsEl.setAttribute("aria-label", "Páginas del carrusel");
4897
4898      var nextBtn = document.createElement("button");
4899      nextBtn.type = "button";
4900      nextBtn.className = "enisa-editorial-row-controls__btn is-next";
4901      nextBtn.setAttribute("aria-label", "Tarjeta siguiente");
4902      nextBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
4903
4904      var dotButtons = rowCards.map(function (card, idx) {
4905        var dot = document.createElement("button");
4906        dot.type = "button";
4907        dot.className = "enisa-editorial-row-controls__dot";
4908        dot.setAttribute("aria-label", "Ir a tarjeta " + (idx + 1));
4909        dot.setAttribute("tabindex", idx === 0 ? "0" : "-1");
4910
4911        if (idx === 0) {
4912          dot.classList.add("is-active");
4913        }
4914
4915        dotsEl.appendChild(dot);
4916        return dot;
4917      });
4918
4919      controls.appendChild(prevBtn);
4920      controls.appendChild(dotsEl);
4921      controls.appendChild(nextBtn);
4922
4923      window.ENISA.utils.createScrollCarousel({
4924        viewport: viewport,
4925        items: rowCards,
4926        prevBtn: prevBtn,
4927        nextBtn: nextBtn,
4928        dots: dotButtons,
4929        activeDotClass: "is-active"
4930      });
4931
4932      return controls;
4933    }
4934
4935    function renderEditorialRows(pageCards) {
4936      grid.innerHTML = "";
4937
4938      var rows = getCardRows(pageCards);
4939
4940      rows.forEach(function (rowCards, rowIndex) {
4941        var row = document.createElement("div");
4942        var isCarousel = rowCards.length > 1;
4943
4944        row.className = "enisa-editorial-grid__row " + (isCarousel ? "is-carousel" : "is-single");
4945
4946        if (isCarousel) {
4947          var viewport = document.createElement("div");
4948          viewport.className = "enisa-editorial-grid__viewport";
4949          viewport.setAttribute("data-editorial-row-viewport", "");
4950
4951          var track = document.createElement("div");
4952          track.className = "enisa-editorial-grid__track";
4953
4954          rowCards.forEach(function (card) {
4955            card.hidden = false;
4956            card.style.minHeight = "";
4957            track.appendChild(card);
4958          });
4959
4960          viewport.appendChild(track);
4961          row.appendChild(viewport);
4962          row.appendChild(createRowCarouselControls(viewport, rowCards, rowIndex));
4963        } else {
4964          rowCards.forEach(function (card) {
4965            card.hidden = false;
4966            card.style.minHeight = "";
4967            row.appendChild(card);
4968          });
4969        }
4970
4971        grid.appendChild(row);
4972      });
4973    }
4974
4975    function syncMobileRowHeights() {
4976      var isMobile = window.innerWidth <= MOBILE_CAROUSEL_BP;
4977      var carouselRows = [].slice.call(grid.querySelectorAll(".enisa-editorial-grid__row.is-carousel"));
4978
4979      carouselRows.forEach(function (row) {
4980        var rowCards = [].slice.call(row.querySelectorAll(".enisa-editorial-card, .enisa-news-card"));
4981
4982        rowCards.forEach(function (card) {
4983          card.style.minHeight = "";
4984        });
4985
4986        if (!isMobile || rowCards.length < 2) return;
4987
4988        window.ENISA.cardsCarousel.syncEqualHeights(rowCards);
4989      });
4990    }
4991
4992    function getPaginationFocusTarget(focusTarget) {
4993      if (!focusTarget || !pagination) return null;
4994
4995      if (focusTarget.type === "prev") {
4996        return pagination.querySelector(".enisa-pagination__btn--arrow.is-prev");
4997      }
4998
4999      if (focusTarget.type === "next") {
5000        return pagination.querySelector(".enisa-pagination__btn--arrow.is-next");
5001      }
5002
5003      if (focusTarget.type === "page") {
5004        return [].slice.call(pagination.querySelectorAll(".enisa-pagination__list .enisa-pagination__btn")).find(function (btn) {
5005          return btn.textContent === String(focusTarget.page);
5006        }) || null;
5007      }
5008
5009      return null;
5010    }
5011
5012    function render(focusTarget) {
5013      var filtered = getFilteredCards();
5014      var total = filtered.length;
5015      var totalPages = Math.max(1, Math.ceil(total / pageSize));
5016      var activeCard = document.activeElement && document.activeElement.closest
5017        ? document.activeElement.closest("[data-" + attrPrefix + "-card]")
5018        : null;
5019
5020      if (currentPage > totalPages) currentPage = totalPages;
5021      if (currentPage < 1) currentPage = 1;
5022
5023      var start = (currentPage - 1) * pageSize;
5024      var end = start + pageSize;
5025      var pageCards = filtered.slice(start, end);
5026      var featuredCard = getFeaturedCardForPage(pageCards);
5027      var orderedPageCards = getOrderedPageCards(pageCards, featuredCard);
5028
5029      if (activeCard && orderedPageCards.indexOf(activeCard) === -1) {
5030        var fallbackFocus = getPaginationFocusTarget(focusTarget) ||
5031          filterButtons.filter(function (button) {
5032            return button.getAttribute("aria-pressed") === "true";
5033          })[0] ||
5034          filterButtons[0];
5035
5036        if (
5037          fallbackFocus &&
5038          window.ENISA &&
5039          window.ENISA.utils &&
5040          typeof window.ENISA.utils.focusWithoutScroll === "function"
5041        ) {
5042          window.ENISA.utils.focusWithoutScroll(fallbackFocus);
5043        } else if (fallbackFocus && typeof fallbackFocus.focus === "function") {
5044          safeFocus(fallbackFocus);
5045        }
5046      }
5047
5048      cards.forEach(function (card) {
5049        card.hidden = true;
5050        card.classList.remove(featuredClass);
5051      });
5052
5053      if (featuredCard) {
5054        featuredCard.classList.add(featuredClass);
5055      }
5056
5057      renderEditorialRows(orderedPageCards);
5058
5059      var from = total ? start + 1 : 0;
5060      var to = total ? start + pageCards.length : 0;
5061
5062      updateCounter(total, from, to);
5063      renderPagination(totalPages);
5064
5065      window.requestAnimationFrame(function () {
5066        syncMobileRowHeights();
5067
5068        var nextFocusTarget = getPaginationFocusTarget(focusTarget);
5069
5070        if (nextFocusTarget && typeof nextFocusTarget.focus === "function" && document.contains(nextFocusTarget)) {
5071          safeFocus(nextFocusTarget);
5072        }
5073      });
5074    }
5075
5076    filterButtons.forEach(function (btn) {
5077      btn.addEventListener("click", function () {
5078        var nextFilter = btn.getAttribute("data-" + attrPrefix + "-filter") || "all";
5079
5080        setFilterState(nextFilter, true);
5081        render(null);
5082      });
5083    });
5084
5085    hideEmptySecondaryBadges();
5086    setFilterState(initialFilter, false);
5087    render(null);
5088
5089    window.addEventListener("resize", window.ENISA.utils.debounce(function () {
5090      syncMobileRowHeights();
5091    }, 150));
5092  }
5093
5094  function init() {
5095    var listings = document.querySelectorAll(EDITORIAL_LISTING_SELECTOR);
5096
5097    if (!listings.length) return;
5098
5099    [].forEach.call(listings, initEditorialListing);
5100  }
5101
5102  if (document.readyState === "loading") {
5103    document.addEventListener("DOMContentLoaded", init);
5104  } else {
5105    init();
5106  }
5107})();
5108
5109/*
5110  ENISA | JS Module
5111  Modulo: 13-carousels-latest
5112  Descripcion: Logica del modulo.
5113*/
5114(function () {
5115  "use strict";
5116
5117  var DEFAULT_DESKTOP_MIN_CARD_W = 328;
5118  var DEFAULT_MOBILE_MIN_CARD_W = 268;
5119  var DEFAULT_CAROUSEL_GAP = 24;
5120
5121  function initSimpleLatestCarousel(root, config) {
5122    if (!root) return;
5123    if (root.getAttribute(config.initAttr) === "true") return;
5124    root.setAttribute(config.initAttr, "true");
5125
5126    var viewport = root.querySelector(config.viewportSelector);
5127    if (!viewport) return;
5128
5129    var cards = [].slice.call(viewport.querySelectorAll(config.cardSelector));
5130    if (cards.length < 2) return;
5131
5132    var controls = document.createElement("div");
5133    controls.className = "enisa-editorial-row-controls";
5134    controls.setAttribute("role", "group");
5135    controls.setAttribute("aria-label", config.controlsLabel);
5136
5137    var prevBtn = document.createElement("button");
5138    prevBtn.type = "button";
5139    prevBtn.className = "enisa-editorial-row-controls__btn is-prev";
5140    prevBtn.setAttribute("aria-label", "Elemento anterior. " + config.controlsLabel);
5141    prevBtn.innerHTML = '<span class="enisa-icon" aria-hidden="true"></span>';
5142
5143    var dotsEl = document.createElement("div");
5144    dotsEl.className = "enisa-editorial-row-controls__dots";
5145    dotsEl.setAttribute("role", "group");
5146    dotsEl.setAttribute("aria-label", "Páginas del carrusel");
5147
5148    var nextBtn = document.createElement("button");
5149    nextBtn.type = "button";
5150    nextBtn.className = "enisa-editorial-row-controls__btn is-next";
5151    nextBtn.setAttribute("aria-label", "Elemento siguiente. " + config.controlsLabel);
5152    nextBtn.innerHTML = '<span class="enisa-icon" aria-hidden="true"></span>';
5153
5154    controls.appendChild(prevBtn);
5155    controls.appendChild(dotsEl);
5156    controls.appendChild(nextBtn);
5157    viewport.insertAdjacentElement("afterend", controls);
5158    controls.setAttribute("hidden", "");
5159
5160    var carousel = window.ENISA.utils.createScrollCarousel({
5161      viewport: viewport,
5162      items: cards,
5163      prevBtn: prevBtn,
5164      nextBtn: nextBtn,
5165      dots: [],
5166      activeDotClass: "is-active"
5167    });
5168
5169    var mobileBreakpoint = typeof config.mobileBreakpoint === "number"
5170      ? config.mobileBreakpoint
5171      : window.ENISA.cardsCarousel.MOBILE_BP;
5172
5173    var lastPerPage = 0;
5174    var shouldSyncEqualHeights = typeof config.syncEqualHeights === "boolean"
5175      ? config.syncEqualHeights
5176      : true;
5177
5178    function getAvailableWidth() {
5179      var container = viewport.parentElement || root;
5180      var styles = window.getComputedStyle ? window.getComputedStyle(container) : null;
5181      var padding = styles
5182        ? parseFloat(styles.paddingLeft || "0") + parseFloat(styles.paddingRight || "0")
5183        : 0;
5184      return Math.floor((container.getBoundingClientRect().width || container.clientWidth) - padding);
5185    }
5186
5187    function shouldUseCarousel() {
5188      if (config.forceBelowBreakpoint) {
5189        var isCoarsePointer = window.matchMedia
5190          ? window.matchMedia("(pointer: coarse)").matches
5191          : false;
5192
5193        if (window.innerWidth > mobileBreakpoint && !isCoarsePointer) {
5194          return false;
5195        }
5196      }
5197
5198      var styles = window.getComputedStyle ? window.getComputedStyle(viewport) : null;
5199      var desktopMinCardW = styles
5200        ? parseFloat(styles.getPropertyValue("--enisa-carousel-min-card-w") || "")
5201        : NaN;
5202      var mobileMinCardW = styles
5203        ? parseFloat(styles.getPropertyValue("--enisa-carousel-min-card-w-mobile") || "")
5204        : NaN;
5205      var minCardW = window.innerWidth <= mobileBreakpoint
5206        ? (isNaN(mobileMinCardW) || mobileMinCardW <= 0
5207            ? (isNaN(desktopMinCardW) || desktopMinCardW <= 0
5208                ? DEFAULT_MOBILE_MIN_CARD_W
5209                : desktopMinCardW)
5210            : mobileMinCardW)
5211        : (isNaN(desktopMinCardW) || desktopMinCardW <= 0
5212            ? DEFAULT_DESKTOP_MIN_CARD_W
5213            : desktopMinCardW);
5214      var gap = styles
5215        ? parseFloat(styles.getPropertyValue("--enisa-carousel-gap") || "")
5216        : NaN;
5217      if (isNaN(gap) || gap <= 0) gap = DEFAULT_CAROUSEL_GAP;
5218      var n = cards.length;
5219      var availableW = getAvailableWidth();
5220      var required = n * minCardW + (n - 1) * gap;
5221      return availableW > 0 && required > availableW + 1;
5222    }
5223
5224    function calcPerPage() {
5225      if (!cards.length) return 1;
5226      var styles = window.getComputedStyle ? window.getComputedStyle(viewport) : null;
5227      var gap = styles ? parseFloat(styles.getPropertyValue("--enisa-carousel-gap") || "24") : 24;
5228      var cardW = cards[0].getBoundingClientRect().width;
5229      if (!cardW) return 1;
5230      var containerW = viewport.getBoundingClientRect().width || viewport.clientWidth;
5231      return Math.max(1, Math.floor((containerW + gap) / (cardW + ga
5231p)));
5232    }
5233
5234    function buildPageDots(perPage) {
5235      var n = cards.length;
5236      var numPages = Math.ceil(n / perPage);
5237      dotsEl.innerHTML = "";
5238      var newDots = [];
5239      for (var p = 0; p < numPages; p++) {
5240        var dot = document.createElement("button");
5241        dot.type = "button";
5242        dot.className = "enisa-editorial-row-controls__dot";
5243        dot.setAttribute("aria-label", "Ir al elemento " + (p + 1) + " de " + numPages);
5244        dot.setAttribute("aria-current", p === 0 ? "true" : "false");
5245        if (p === 0) dot.classList.add("is-active");
5246        dotsEl.appendChild(dot);
5247        newDots.push(dot);
5248      }
5249      return newDots;
5250    }
5251
5252    function syncCardHeights() {
5253      cards.forEach(function (card) { card.style.minHeight = ""; });
5254      if (root.classList.contains("is-carousel") && shouldSyncEqualHeights) {
5255        window.ENISA.cardsCarousel.syncEqualHeights(cards);
5256      }
5257    }
5258
5259    function setTrailingPadding() {
5260      viewport.style.paddingInlineEnd = "";
5261    }
5262
5263    function setControlsVisibility() {
5264      var useCarousel = shouldUseCarousel();
5265      root.classList.toggle("is-carousel", useCarousel);
5266      if (useCarousel) {
5267        var perPage = calcPerPage();
5268        if (perPage !== lastPerPage) {
5269          lastPerPage = perPage;
5270          var newDots = buildPageDots(perPage);
5271          carousel.setDots(newDots, "is-active");
5272          carousel.setPerPage(perPage);
5273        }
5274        controls.removeAttribute("hidden");
5275        [].slice.call(controls.querySelectorAll("button")).forEach(function (b) { b.removeAttribute("tabindex"); });
5276        syncCardHeights();
5277        setTrailingPadding();
5278        carousel.sync();
5279      } else {
5280        cards.forEach(function (card) { card.style.minHeight = ""; });
5281        lastPerPage = 0;
5282        viewport.style.paddingInlineEnd = "";
5283        carousel.reset();
5284        [].slice.call(controls.querySelectorAll("button")).forEach(function (b) { b.setAttribute("tabindex", "-1"); });
5285        controls.setAttribute("hidden", "");
5286      }
5287    }
5288
5289    setControlsVisibility();
5290    window.addEventListener("load", setControlsVisibility, { once: true });
5291    window.addEventListener("resize", window.ENISA.utils.debounce(setControlsVisibility, 150));
5292  }
5293
5294  function initStudiesLatest(root) {
5295    initSimpleLatestCarousel(root, {
5296      initAttr: "data-studies-latest-init",
5297      viewportSelector: "[data-studies-latest-carousel]",
5298      cardSelector: "[data-studies-latest-card], .enisa-studies-card",
5299      controlsLabel: "Controles del carrusel de estudios",
5300      mobileBreakpoint: window.ENISA.cardsCarousel.MOBILE_BP
5301    });
5302  }
5303
5304  function initNewsLatest(root) {
5305    var isBulletinsLatest = root.classList.contains("enisa-bulletins-latest");
5306    var isGalleryLatest = root.classList.contains("enisa-news-latest--gallery");
5307    initSimpleLatestCarousel(root, {
5308      initAttr: "data-news-latest-init",
5309      viewportSelector: "[data-news-latest-carousel]",
5310      cardSelector: isGalleryLatest
5311        ? "[data-news-latest-card], .enisa-press-media__card"
5312        : "[data-news-latest-card], .enisa-news-card",
5313      controlsLabel: isGalleryLatest
5314        ? "Controles del carrusel de galeria"
5315        : "Controles del carrusel de ultimas noticias",
5316      mobileBreakpoint: isGalleryLatest ? 991.98 : window.ENISA.cardsCarousel.MOBILE_BP,
5317      syncEqualHeights: !isBulletinsLatest && !isGalleryLatest,
5318      forceBelowBreakpoint: isGalleryLatest
5319    });
5320  }
5321
5322  function initCardsRecursos(root) {
5323    initSimpleLatestCarousel(root, {
5324      initAttr: "data-cards-recursos-init",
5325      viewportSelector: ".enisa-cards-recursos__grid, .enisa-cards-recursos__grid--2, .enisa-cards-recursos__grid--3, .enisa-cards-recursos__grid--4",
5326      cardSelector: ".enisa-cards-recursos__card",
5327      controlsLabel: "Controles del carrusel de recursos",
5328      mobileBreakpoint: 1199.98
5329    });
5330  }
5331
5332  function initBlogLatest(root) {
5333    initSimpleLatestCarousel(root, {
5334      initAttr: "data-blog-latest-init",
5335      viewportSelector: ".enisa-editorial-latest__grid",
5336      cardSelector: "[data-editorial-latest-card], .enisa-editorial-latest-card",
5337      controlsLabel: "Controles del carrusel de blog",
5338      mobileBreakpoint: 1023
5339    });
5340  }
5341
5342  function initPodcastLatest(root) {
5343    initSimpleLatestCarousel(root, {
5344      initAttr: "data-podcast-latest-init",
5345      viewportSelector: ".enisa-podcast-programas__list",
5346      cardSelector: ".enisa-podcast-programa-card",
5347      controlsLabel: "Controles del carrusel de podcast",
5348      mobileBreakpoint: window.ENISA.cardsCarousel.MOBILE_BP
5349    });
5350  }
5351
5352  function initNewsRelated(root) {
5353    initSimpleLatestCarousel(root, {
5354      initAttr: "data-news-related-init",
5355      viewportSelector: "[data-news-related-grid], .enisa-news-related__grid",
5356      cardSelector: "[data-news-related-card], .enisa-news-card",
5357      controlsLabel: "Controles del carrusel de noticias relacionadas",
5358      mobileBreakpoint: window.ENISA.cardsCarousel.MOBILE_BP
5359    });
5360  }
5361
5362  function init() {
5363    var studiesSections = document.querySelectorAll("[data-studies-latest]");
5364    [].forEach.call(studiesSections, initStudiesLatest);
5365
5366    var newsSections = document.querySelectorAll("[data-news-latest]");
5367    [].forEach.call(newsSections, initNewsLatest);
5368
5369    var recursosSections = document.querySelectorAll("[data-cmp-cards-recursos]");
5370    [].forEach.call(recursosSections, initCardsRecursos);
5371
5372    var blogSections = document.querySelectorAll(".enisa-editorial-latest");
5373    [].forEach.call(blogSections, initBlogLatest);
5374
5375    var podcastSections = document.querySelectorAll("[data-podcast-latest]");
5376    [].forEach.call(podcastSections, initPodcastLatest);
5377
5378    var relatedSections = document.querySelectorAll("[data-news-related]");
5379    [].forEach.call(relatedSections, initNewsRelated);
5380  }
5381
5382  if (document.readyState === "loading") {
5383    document.addEventListener("DOMContentLoaded", init);
5384  } else {
5385    init();
5386  }
5387})();
5388
5389/*
5390  ENISA | JS Module
5391  Modulo: 15-carousels-fin
5392  Descripcion: Logica del modulo.
5393*/
5394(function () {
5395  "use strict";
5396
5397  var SECTION_SELECTOR = "[data-fin-sectores], [data-fin-detalles], .cmp-hero-metricas";
5398  var TRACK_SELECTOR = "[data-fin-track]";
5399  var DOT_SELECTOR = "[data-fin-dot]";
5400  var PREV_SELECTOR = "[data-fin-prev]";
5401  var NEXT_SELECTOR = "[data-fin-next]";
5402  var METRICAS_TRACK_SELECTOR = ".cmp-hero-metricas__track";
5403  var METRICAS_CARD_WIDTH = 295.5;
5404  var METRICAS_GAP = 8;
5405  var CAROUSEL_BP = window.ENISA.cardsCarousel.MOBILE_BP;
5406  var DEFAULT_ACTIVE_DOT_CLASS = "is-active";
5407  var INIT_ATTR = "data-fin-carousel-init";
5408  var WATCH_ATTR = "data-fin-carousel-watch";
5409
5410  function initCarousel(section) {
5411    if (!section || section.getAttribute(INIT_ATTR) === "true") return true;
5412
5413    var isMetricas = section.classList.contains("cmp-hero-metricas");
5414    var track = section.querySelector(TRACK_SELECTOR) || section.querySelector(METRICAS_TRACK_SELECTOR);
5415    var dots = [].slice.call(section.querySelectorAll(DOT_SELECTOR));
5416    var prevBtn = section.querySelector(PREV_SELECTOR);
5417    var nextBtn = section.querySelector(NEXT_SELECTOR);
5418    var items   = [].slice.call(track ? track.children : []);
5419
5420    if (!track || !items.length) return false;
5421
5422    section.setAttribute(INIT_ATTR, "true");
5423
5424    var activeDotClass = isMetricas ? "is-active" : DEFAULT_ACTIVE_DOT_CLASS;
5425    var customCarouselBp = parseFloat(section.getAttribute("data-fin-carousel-bp") || "");
5426    var effectiveCarouselBp = !isNaN(customCarouselBp) && customCarouselBp > 0
5427      ? customCarouselBp
5428      : CAROUSEL_BP;
5429    if (!isMetricas && dots.length) {
5430      var detected = [].slice.call(dots[0].classList).find(function (cls) {
5431        return /--active$/.test(cls);
5432      });
5433      if (detected) activeDotClass = detected;
5434    }
5435
5436    var carousel = window.ENISA.utils.createScrollCarousel({
5437      viewport: track,
5438      items: items,
5439      prevBtn: prevBtn,
5440      nextBtn: nextBtn,
5441      dots: dots,
5442      activeDotClass: activeDotClass
5443    });
5444
5445    var lastSectorsPerPage = 0;
5446
5447    function shouldUseMetricasCarousel() {
5448      return window.innerWidth <= effectiveCarouselBp;
5449    }
5450
5451    function shouldUseSectorsCarousel() {
5452      var styles = window.getComputedStyle ? window.getComputedStyle(track) : null;
5453      var minCardW = styles
5454        ? parseFloat(styles.getPropertyValue("--enisa-carousel-min-card-w") || "0")
5455        : 0;
5456      if (!minCardW || minCardW <= 0) return window.innerWidth <= effectiveCarouselBp;
5457      var gap = styles ? parseFloat(styles.getPropertyValue("--enisa-carousel-gap") || "24") : 24;
5458      var container = track.parentElement || section;
5459      var cStyles = window.getComputedStyle ? window.getComputedStyle(container) : null;
5460      var padding = cStyles
5461        ? parseFloat(cStyles.paddingLeft || "0") + parseFloat(cStyles.paddingRight || "0")
5462        : 0;
5463      var availableW = Math.floor((container.getBoundingClientRect().width || container.clientWidth) - padding);
5464      var required = items.length * minCardW + (items.length - 1) * gap;
5465      return availableW > 0 && required > availableW + 1;
5466    }
5467
5468    function calcSectorsPerPage() {
5469      if (!items.length) return 1;
5470      var styles = window.getComputedStyle ? window.getComputedStyle(track) : null;
5471      var gap = styles ? parseFloat(styles.getPropertyValue("--enisa-carousel-gap") || "24") : 24;
5472      var cardW = items[0].getBoundingClientRect().width;
5473      if (!cardW) return 1;
5474      var containerW = track.getBoundingClientRect().width || track.clientWidth;
5475      return Math.max(1, Math.floor((containerW + gap) / (cardW + ga
5475p)));
5476    }
5477
5478    function syncSectorsDots(perPage) {
5479      var numPages = Math.ceil(items.length / perPage);
5480      dots.forEach(function (dot, i) {
5481        dot.hidden = i >= numPages;
5482      });
5483      carousel.setPerPage(perPage);
5484    }
5485
5486    function isCarouselViewport() {
5487      if (isMetricas) return shouldUseMetricasCarousel();
5488      return shouldUseSectorsCarousel();
5489    }
5490
5491    function syncCardHeights() {
5492      items.forEach(function (item) {
5493        item.style.minHeight = "";
5494      });
5495
5496      if (isMetricas) return;
5497      if (!isCarouselViewport()) return;
5498      window.ENISA.cardsCarousel.syncEqualHeights(items);
5499    }
5500
5501    function setTrailingPadding() {
5502      if (isMetricas || !items.length) return;
5503      track.style.paddingInlineEnd = "";
5504    }
5505
5506    function syncCarouselMode() {
5507      var useCarousel = isCarouselViewport();
5508      section.classList.toggle("is-carousel", useCarousel);
5509
5510      var htmlControls = section.querySelector("[data-fin-controls]");
5511      if (htmlControls) {
5512        var controlBtns = [].slice.call(htmlControls.querySelectorAll("button"));
5513        if (useCarousel) {
5514          htmlControls.removeAttribute("hidden");
5515          htmlControls.removeAttribute("aria-hidden");
5516          controlBtns.forEach(function (b) { b.removeAttribute("tabindex"); });
5517        } else {
5518          htmlControls.setAttribute("hidden", "");
5519          htmlControls.setAttribute("aria-hidden", "true");
5520          controlBtns.forEach(function (b) { b.setAttribute("tabindex", "-1"); });
5521        }
5522      }
5523
5524      if (!isMetricas && useCarousel) {
5525        var perPage = calcSectorsPerPage();
5526        if (perPage !== lastSectorsPerPage) {
5527          lastSectorsPerPage = perPage;
5528          syncSectorsDots(perPage);
5529        }
5530        setTrailingPadding(true);
5531      } else if (!isMetricas) {
5532        setTrailingPadding(false);
5533      }
5534    }
5535
5536    syncCarouselMode();
5537    syncCardHeights();
5538    if (isCarouselViewport()) carousel.sync();
5539
5540    window.addEventListener("load", function () {
5541      syncCarouselMode();
5542      syncCardHeights();
5543      if (isCarouselViewport()) carousel.sync();
5544    }, { once: true });
5545    window.addEventListener("resize", window.ENISA.utils.debounce(function () {
5546      syncCarouselMode();
5547      if (!isCarouselViewport()) {
5548        items.forEach(function (item) {
5549          item.style.minHeight = "";
5550        });
5551        lastSectorsPerPage = 0;
5552        dots.forEach(function (dot) { dot.hidden = false; });
5553        carousel.reset();
5554        return;
5555      }
5556
5557      syncCardHeights();
5558      carousel.sync();
5559    }, 150));
5560
5561    return true;
5562  }
5563
5564  function watchSectionUntilReady(section) {
5565    if (!section || section.getAttribute(INIT_ATTR) === "true") return;
5566    if (section.getAttribute(WATCH_ATTR) === "true") return;
5567    section.setAttribute(WATCH_ATTR, "true");
5568
5569    var observer = new MutationObserver(function () {
5570      if (section.getAttribute(INIT_ATTR) === "true") {
5571        observer.disconnect();
5572        return;
5573      }
5574      if (initCarousel(section)) {
5575        observer.disconnect();
5576      }
5577    });
5578
5579    observer.observe(section, { childList: true, subtree: true });
5580
5581    window.setTimeout(function () {
5582      if (section.getAttribute(INIT_ATTR) !== "true") {
5583        initCarousel(section);
5584      }
5585      observer.disconnect();
5586    }, 4000);
5587  }
5588
5589  function init() {
5590    var sections = [].slice.call(document.querySelectorAll(SECTION_SELECTOR));
5591    sections.forEach(function (section) {
5592      if (!initCarousel(section)) {
5593        watchSectionUntilReady(section);
5594      }
5595    });
5596  }
5597
5598  if (document.readyState === "loading") {
5599    document.addEventListener("DOMContentLoaded", init);
5600  } else {
5601    init();
5602  }
5603})();
5604
5605
5606
5607
5608
5609/*
5610  ENISA | JS Module
5611  Modulo: 16-tabs-process
5612  Descripcion: Logica del modulo.
5613*/
5614(function () {
5615  "use strict";
5616
5617  var SECTION_SELECTOR = "[data-tabs-section]";
5618
5619  function initProcessTabs(section) {
5620    if (!section || section.getAttribute("data-process-tabs-init") === "true") return;
5621    section.setAttribute("data-process-tabs-init", "true");
5622
5623    var nav = section.querySelector("[data-tabs-nav]");
5624    var panelsContainer = section.querySelector("[data-tabs-panels]");
5625    var dotsContainer = section.querySelector("[data-tabs-dots]");
5626    var mobileMediaQuery = window.matchMedia("(max-width: 575.98px)");
5627
5628    if (!nav || !panelsContainer) return;
5629
5630    var tabs = [].slice.call(nav.querySelectorAll("[data-tab-index]"));
5631    var panels = [].slice.call(panelsContainer.querySelectorAll("[data-panel-index]"));
5632    var dots = dotsContainer
5633      ? [].slice.call(dotsContainer.querySelectorAll("[data-dot-index]"))
5634      : [];
5635    var mobileAnchors = [];
5636    var navLabelledBy = nav.getAttribute("aria-labelledby") || "";
5637    var activeIndex = 0;
5638
5639    if (!tabs.length || tabs.length !== panels.length) return;
5640
5641    function isMobileLayout() {
5642      return mobileMediaQuery.matches;
5643    }
5644
5645    function ensureMobileAnchors() {
5646      if (mobileAnchors.length) return;
5647
5648      tabs.forEach(function (tab, index) {
5649        var anchor = document.createElement("div");
5650        anchor.className = "enisa-process-tabs__mobile-panel-anchor";
5651        anchor.setAttribute("data-mobile-panel-anchor", String(index));
5652        tab.insertAdjacentElement("afterend", anchor);
5653        mobileAnchors.push(anchor);
5654      });
5655    }
5656
5657    function syncPanelPlacement() {
5658      ensureMobileAnchors();
5659
5660      var isMobile = mobileMediaQuery.matches;
5661
5662      panels.forEach(function (panel, index) {
5663        var targetParent = isMobile ? mobileAnchors[index] : panelsContainer;
5664
5665        if (targetParent && panel.parentNode !== targetParent) {
5666          targetParent.appendChild(panel);
5667        }
5668      });
5669
5670      panelsContainer.hidden = isMobile;
5671      panelsContainer.setAttribute("aria-hidden", isMobile ? "true" : "false");
5672
5673      mobileAnchors.forEach(function (anchor) {
5674        anchor.hidden = !isMobile;
5675      });
5676    }
5677
5678    function syncAccessibilityState(index) {
5679      var isMobile = isMobileLayout();
5680
5681      if (isMobile) {
5682        nav.removeAttribute("role");
5683        nav.removeAttribute("aria-orientation");
5684        if (navLabelledBy) {
5685          nav.removeAttribute("aria-labelledby");
5686        }
5687      } else {
5688        nav.setAttribute("role", "tablist");
5689        nav.setAttribute("aria-orientation", "vertical");
5690        if (navLabelledBy) {
5691          nav.setAttribute("aria-labelledby", navLabelledBy);
5692        }
5693      }
5694
5695      tabs.forEach(function (tab, i) {
5696        var active = i === index;
5697
5698        if (isMobile) {
5699          tab.removeAttribute("role");
5700          tab.removeAttribute("aria-selected");
5701          tab.setAttribute("aria-expanded", active ? "true" : "false");
5702          tab.setAttribute("tabindex", "0");
5703        } else {
5704          tab.setAttribute("role", "tab");
5705          tab.setAttribute("aria-selected", active ? "true" : "false");
5706          tab.removeAttribute("aria-expanded");
5707          tab.setAttribute("tabindex", active ? "0" : "-1");
5708        }
5709      });
5710
5711      panels.forEach(function (panel, i) {
5712        var active = i === index;
5713
5714        if (isMobile) {
5715          panel.removeAttribute("role");
5716        } else {
5717          panel.setAttribute("role", "tabpanel");
5718        }
5719
5720        panel.setAttribute("aria-labelledby", tabs[i].id);
5721        panel.setAttribute("aria-hidden", active ? "false" : "true");
5722        panel.removeAttribute("tabindex");
5723      });
5724    }
5725
5726    function updateTabState(tab, active, isMobile) {
5727      tab.classList.toggle("enisa-process-tabs__item--active", active);
5728
5729      if (isMobile) {
5730        tab.removeAttribute("role");
5731        tab.removeAttribute("aria-selected");
5732        tab.setAttribute("aria-expanded", active ? "true" : "false");
5733        tab.setAttribute("tabindex", "0");
5734      } else {
5735        tab.setAttribute("role", "tab");
5736        tab.setAttribute("aria-selected", active ? "true" : "false");
5737        tab.removeAttribute("aria-expanded");
5738        tab.setAttribute("tabindex", active ? "0" : "-1");
5739      }
5740    }
5741
5742    function updatePanelState(panel, tab, active, isMobile) {
5743      if (isMobile) {
5744        panel.removeAttribute("role");
5745      } else {
5746        panel.setAttribute("role", "tabpanel");
5747      }
5748
5749      panel.setAttribute("aria-labelledby", tab.id);
5750      panel.setAttribute("aria-hidden", active ? "false" : "true");
5751      panel.hidden = !active;
5752      panel.removeAttribute("tabindex");
5753    }
5754
5755    function enhancePanelLinks() {
5756      panels.forEach(function (panel) {
5757        var linkRows = [].slice.call(
5758          panel.querySelectorAll(".enisa-transparencia-inline-links")
5759        );
5760
5761        linkRows.forEach(function (row) {
5762          var links = [].slice.call(row.querySelectorAll("a"));
5763
5764          links.forEach(function (link) {
5765            if (!link || link.classList.contains("enisa-link")) return;
5766
5767            link.classList.add("enisa-link", "enisa-link--arrow");
5768
5769            var label = document.createElement("span");
5770            label.className = "enisa-link__label";
5771
5772            while (link.firstChild) {
5773              label.appendChild(link.firstChild);
5774            }
5775
5776            var arrow = document.createElement("span");
5777            arrow.className = "enisa-link__arrow enisa-icon enisa-icon--arrow-right";
5778            arrow.setAttribute("aria-hidden", "true");
5779
5780            link.appendChild(label);
5781            link.appendChild(arrow);
5782          });
5783        });
5784      });
5785    }
5786
5787    function setActive(index, moveFocus) {
5788      if (index < 0 || index >= tabs.length) return;
5789      activeIndex = index;
5790      var isMobile = isMobileLayout();
5791
5792      syncPanelPlacement();
5793      syncAccessibilityState(activeIndex);
5794
5795      tabs.forEach(function (tab, i) {
5796        var active = i === index;
5797        updateTabState(tab, active, isMobile);
5798
5799        if (active) {
5800          section.setAttribute(
5801            "data-current-color",
5802            tab.getAttribute("data-tab-color") || "yellow"
5803          );
5804
5805          if (moveFocus) {
5806            if (window.ENISA && window.ENISA.utils && typeof window.ENISA.utils.safeFocus === "function") {
5807              window.ENISA.utils.safeFocus(tab);
5808            } else {
5809              try {
5810                tab.focus({ preventScroll: true });
5811              } catch (error) {
5812                tab.focus();
5813              }
5814            }
5815          }
5816        }
5817      });
5818
5819      panels.forEach(function (panel, i) {
5820        var active = i === activeIndex;
5821
5822        if (
5823          !active &&
5824          window.ENISA &&
5825          window.ENISA.utils &&
5826          typeof window.ENISA.utils.focusIfActiveInside === "function"
5827        ) {
5828          window.ENISA.utils.focusIfActiveInside(panel, tabs[activeIndex]);
5829        } else if (
5830          !active &&
5831          panel.contains(document.activeElement) &&
5832          tabs[activeIndex] &&
5833          typeof tabs[activeIndex].focus === "function"
5834        ) {
5835          if (window.ENISA && window.ENISA.utils && typeof window.ENISA.utils.safeFocus === "function") {
5836            window.ENISA.utils.safeFocus(tabs[activeIndex]);
5837          } else {
5838            try {
5839              tabs[activeIndex].focus({ preventScroll: true });
5840            } catch (error) {
5841              tabs[activeIndex].focus();
5842            }
5843          }
5844        }
5845
5846        updatePanelState(panel, tabs[i], active, isMobile);
5847        panel.classList.toggle("enisa-process-tabs__panel--active", active);
5848
5849        if (active) {
5850          panel.setAttribute("data-visible", "");
5851        } else {
5852          panel.removeAttribute("data-visible");
5853        }
5854      });
5855
5856      dots.forEach(function (dot, i) {
5857        var active = i === activeIndex;
5858        dot.classList.toggle("enisa-process-tabs__dot--active", active);
5859        dot.setAttribute("aria-current", active ? "true" : "false");
5860      });
5861    }
5862
5863    function activateTab(index, moveFocus) {
5864      setActive(index, !!moveFocus);
5865    }
5866
5867    nav.addEventListener("click", function (e) {
5868      var tab = e.target.closest("[data-tab-index]");
5869      if (!tab) return;
5870
5871      var index = Number(tab.getAttribute("data-tab-index"));
5872      if (isNaN(index)) return;
5873
5874      activateTab(index, false);
5875    });
5876
5877    nav.addEventListener("keydown", function (e) {
5878      var tab = e.target.closest("[data-tab-index]");
5879      if (!tab) return;
5880
5881      var current = Number(tab.getAttribute("data-tab-index"));
5882      if (isNaN(current)) return;
5883
5884      if (isMobileLayout()) {
5885        return;
5886      }
5887
5888      var next = current;
5889
5890      switch (e.key) {
5891        case "ArrowUp":
5892        case "ArrowLeft":
5893          next = current === 0 ? tabs.length - 1 : current - 1;
5894          break;
5895        case "ArrowDown":
5896        case "ArrowRight":
5897          next = current === tabs.length - 1 ? 0 : current + 1;
5898          break;
5899        case "Home":
5900          next = 0;
5901          break;
5902        case "End":
5903          next = tabs.length - 1;
5904          break;
5905        case "Enter":
5906        case " ":
5907        case "Spacebar":
5908          e.preventDefault();
5909          activateTab(current, true);
5910          return;
5911        default:
5912          return;
5913      }
5914
5915      e.preventDefault();
5916      activateTab(next, true);
5917    });
5918
5919    if (dotsContainer) {
5920      dotsContainer.addEventListener("click", function (e) {
5921        var dot = e.target.closest("[data-dot-index]");
5922        if (!dot) return;
5923
5924        var index = Number(dot.getAttribute("data-dot-index"));
5925        if (isNaN(index)) return;
5926
5927        activateTab(index, false);
5928      });
5929    }
5930
5931    window.addEventListener("resize", function () {
5932      activateTab(activeIndex, false);
5933    });
5934
5935    if (typeof mobileMediaQuery.addEventListener === "function") {
5936      mobileMediaQuery.addEventListener("change", function () {
5937        activateTab(activeIndex, false);
5938      });
5939    }
5940
5941    window.addEventListener("enisa:transparency-tab-change", function () {
5942      activateTab(activeIndex, false);
5943    });
5944
5945    enhancePanelLinks();
5946    activateTab(0, false);
5947  }
5948
5949  function init() {
5950    var sections = document.querySelectorAll(SECTION_SELECTOR);
5951    if (!sections.length) return;
5952
5953    [].forEach.call(sections, initProcessTabs);
5954  }
5955
5956  if (document.readyState === "loading") {
5957    document.addEventListener("DOMContentLoaded", init);
5958  } else {
5959    init();
5960  }
5961})();
5962
5963/*
5964  ENISA | JS Module
5965  Modulo: 18-recursos-tabs
5966  Descripcion: Tabs de recursos/transparencia, paginacion, fallback de imagenes y ajuste de ratios.
5967*/
5968(function () {
5969  "use strict";
5970
5971  var ROOT_SELECTOR =
5972    "[data-recursos-tabs], .enisa-cmp-transparencia-tabs, .enisa-recursos-detalle__tabs";
5973  var TAB_SELECTOR = "[data-resource-tab]";
5974  var RECURSOS_DETALLE_SELECTOR = "[data-cmp-recursos-detalle]";
5975  var PANEL_SELECTOR = "[data-resource-panel]";
5976  var RESOURCE_ITEM_SELECTOR = "[data-resource-item]";
5977  var RESOURCE_IMAGE_FALLBACK_SELECTOR =
5978    ".enisa-recursos-detalle__image[data-fallback-src]";
5979  var RESOURCE_DETAIL_IMAGE_SELECTOR = ".enisa-recursos-detalle__image";
5980  var RESOURCE_DETAIL_ITEM_SELECTOR = ".enisa-recursos-detalle__item";
5981  var RESOURCE_DETAIL_MEDIA_SELECTOR = ".enisa-recursos-detalle__media";
5982
5983  var TABS_INIT_ATTR = "data-recursos-tabs-anchor-init";
5984  var LEGACY_TABS_INIT_ATTR = "data-recursos-tabs-init";
5985  var PAGINATION_INIT_ATTR = "data-recursos-pagination-init";
5986  var FALLBACK_INIT_ATTR = "data-recursos-image-fallback-init";
5987  var RATIO_INIT_ATTR = "data-recursos-media-ratio-init";
5988
5989  var hasInitialized = false;
5990  var hasAppliedInitialRequestedTab = false;
5991  var userHasInteractedWithResourceTabs = false;
5992
5993  var ANCHOR_ALIASES = {
5994    "logos-enisa": ["logos-enisa", "logo-enisa", "logotipos", "logotipos-enisa"],
5995    "sellos-clienteenisa": [
5996      "sellos-clienteenisa",
5997      "sellos-cliente-enisa",
5998      "sellos-clientesenisa",
5999      "sello-clienteenisa",
6000      "sello-cliente-enisa",
6001      "selloclienteenisa",
6002      "clienteenisa",
6003      "clientes-enisa",
6004      "clientesenisa"
6005    ],
6006    "sellos-cliente-enisa": [
6007      "sellos-cliente-enisa",
6008      "sellos-clienteenisa",
6009      "sellos-clientesenisa",
6010      "sello-clienteenisa",
6011      "sello-cliente-enisa",
6012      "selloclienteenisa",
6013      "clienteenisa",
6014      "clientes-enisa",
6015      "clientesenisa"
6016    ],
6017    "sellos-clientesenisa": [
6018      "sellos-clientesenisa",
6019      "sellos-clienteenisa",
6020      "sellos-cliente-enisa",
6021      "sello-clienteenisa",
6022      "sello-cliente-enisa",
6023      "selloclienteenisa",
6024      "clienteenisa",
6025      "clientes-enisa",
6026      "clientesenisa"
6027    ],
6028    "sellos-empresa-emergente-soystartup": [
6029      "sellos-empresa-emergente-soystartup",
6030      "sello-empresa-emergente-soystartup",
6031      "empresa-emergente-soystartup",
6032      "empresa-emergente",
6033      "soystartup"
6034    ],
6035    "sello-financiada-por-enisa": [
6036      "sello-financiada-por-enisa",
6037      "sello-financiador-enisa",
6038      "sello-financiadapor-enisa",
6039      "sello-financiadaporenisa"
6040    ],
6041    "sello-financiador-enisa": [
6042      "sello-financiador-enisa",
6043      "sello-financiada-por-enisa",
6044      "sello-financiadapor-enisa",
6045      "sello-financiadaporenisa"
6046    ],
6047    "sello-financiadapor-enisa": [
6048      "sello-financiadapor-enisa",
6049      "sello-financiada-por-enisa",
6050      "sello-financiador-enisa",
6051      "sello-financiadaporenisa"
6052    ],
6053    "sello-financiadaporenisa": [
6054      "sello-financiadaporenisa",
6055      "sello-financiada-por-enisa",
6056      "sello-financiador-enisa",
6057      "sello-financiadapor-enisa"
6058    ]
6059  };
6060
6061  function prefersReducedMotion() {
6062    return !!(
6063      window.matchMedia &&
6064      window.matchMedia("(prefers-reduced-motion: reduce)").matches
6065    );
6066  }
6067
6068  function debounce(fn, delay) {
6069    var timer = null;
6070
6071    return function () {
6072      var context = this;
6073      var args = arguments;
6074
6075      clearTimeout(timer);
6076      timer = setTimeout(function () {
6077        fn.apply(context, args);
6078      }, delay || 150);
6079    };
6080  }
6081
6082  function afterNextPaint(callback) {
6083    window.requestAnimationFrame(function () {
6084      window.requestAnimationFrame(callback);
6085    });
6086  }
6087
6088  function normalizeAnchor(value) {
6089    var text = String(value || "").replace(/^#/, "").trim();
6090
6091    try {
6092      text = decodeURIComponent(text);
6093    } catch (error) {}
6094
6095    return text
6096      .toLowerCase()
6097      .replace(/&amp;/g, " ")
6098      .replace(/&/g, " ")
6099      .replace(/á/g, "a")
6100      .replace(/é/g, "e")
6101      .replace(/í/g, "i")
6102      .replace(/ó/g, "o")
6103      .replace(/ú/g, "u")
6104      .replace(/ñ/g, "n")
6105      .replace(/[áàäâ]/g, "a")
6106      .replace(/[éèëê]/g, "e")
6107      .replace(/[íìïî]/g, "i")
6108      .replace(/[óòöô]/g, "o")
6109      .replace(/[úùüû]/g, "u")
6110      .replace(/ñ/g, "n")
6111      .replace(/ç/g, "c")
6112      .replace(/#/g, "")
6113      .replace(/[^\w\s-]/g, "")
6114      .replace(/_/g, "-")
6115      .replace(/\s+/g, "-")
6116      .replace(/-+/g, "-")
6117      .replace(/^-|-$/g, "");
6118  }
6119
6120  function getQueryParam(name) {
6121    if (!window.URLSearchParams) return "";
6122
6123    var params = new URLSearchParams(window.location.search || "");
6124    return params.get(name) || "";
6125  }
6126
6127  function getRequestedTabAnchor() {
6128    return getQueryParam("tab") || "";
6129  }
6130
6131  function getAnchorCandidates(value) {
6132    var cleanValue = normalizeAnchor(value);
6133    var candidates = [];
6134
6135    if (!cleanValue) return candidates;
6136
6137    candidates.push(cleanValue);
6138
6139    if (ANCHOR_ALIASES[cleanValue]) {
6140      candidates = candidates.concat(ANCHOR_ALIASES[cleanValue]);
6141    }
6142
6143    Object.keys(ANCHOR_ALIASES).forEach(function (key) {
6144      if (ANCHOR_ALIASES[key].indexOf(cleanValue) !== -1) {
6145        candidates.push(key);
6146        candidates = candidates.concat(ANCHOR_ALIASES[key]);
6147      }
6148    });
6149
6150    return candidates
6151      .map(normalizeAnchor)
6152      .filter(function (candidate, index, list) {
6153        return candidate && list.indexOf(candidate) === index;
6154      });
6155  }
6156
6157  function matchesAnchor(requestedValue, elementValue) {
6158    var requestedCandidates = getAnchorCandidates(requestedValue);
6159    var elementCandidates = getAnchorCandidates(elementValue);
6160
6161    if (!requestedCandidates.length || !elementCandidates.length) return false;
6162
6163    return requestedCandidates.some(function (requestedCandidate) {
6164      return elementCandidates.indexOf(requestedCandidate) !== -1;
6165    });
6166  }
6167
6168  function focusTab(tab) {
6169    if (!tab) return;
6170
6171    if (
6172      window.ENISA &&
6173      window.ENISA.utils &&
6174      typeof window.ENISA.utils.safeFocus === "function"
6175    ) {
6176      window.ENISA.utils.safeFocus(tab);
6177      return;
6178    }
6179
6180    try {
6181      tab.focus({ preventScroll: true });
6182    } catch (error) {}
6183  }
6184
6185  function setPanelTabbables(panel, isActive) {
6186    if (!panel) return;
6187
6188    if (
6189      window.ENISA &&
6190      window.ENISA.utils &&
6191      typeof window.ENISA.utils.setSubtreeTabbable === "function"
6192    ) {
6193      window.ENISA.utils.setSubtreeTabbable(panel, isActive);
6194    }
6195  }
6196
6197  function getTabGeneratedAnchor(tab) {
6198    if (!tab) return "";
6199
6200    return normalizeAnchor(
6201      tab.getAttribute("data-resource-anchor") ||
6202        tab.getAttribute("data-anchor") ||
6203        tab.getAttribute("id") ||
6204        tab.getAttribute("aria-controls") ||
6205        tab.textContent ||
6206        ""
6207    );
6208  }
6209
6210  function getPanelGeneratedAnchor(panel) {
6211    if (!panel) return "";
6212
6213    return normalizeAnchor(
6214      panel.getAttribute("data-resource-anchor") ||
6215        panel.getAttribute("id") ||
6216        panel.getAttribute("aria-labelledby") ||
6217        ""
6218    );
6219  }
6220
6221  function hydrateMissingAnchors(tabs, panels) {
6222    tabs.forEach(function (tab, index) {
6223      var currentTabAnchor = normalizeAnchor(tab.getAttribute("data-resource-anchor"));
6224      var generatedTabAnchor = normalizeAnchor(tab.textContent || tab.getAttribute("id") || "");
6225      var panel = panels[index];
6226      var currentPanelAnchor;
6227      var generatedPanelAnchor;
6228
6229      if (!currentTabAnchor && generatedTabAnchor) {
6230        tab.setAttribute("data-resource-anchor", generatedTabAnchor);
6231      }
6232
6233      if (!panel) return;
6234
6235      currentPanelAnchor = normalizeAnchor(panel.getAttribute("data-resource-anchor"));
6236      generatedPanelAnchor = normalizeAnchor(
6237        currentTabAnchor || generatedTabAnchor || panel.getAttribute("id") || ""
6238      );
6239
6240      if (!currentPanelAnchor && generatedPanelAnchor) {
6241        panel.setAttribute("data-resource-anchor", generatedPanelAnchor);
6242      }
6243    });
6244  }
6245
6246  function getIndexFromAnchor(tabs, panels, requestedAnchor) {
6247    var cleanRequested = normalizeAnchor(requestedAnchor);
6248
6249    if (!cleanRequested) return -1;
6250
6251    for (var i = 0; i < tabs.length; i += 1) {
6252      var tab = tabs[i];
6253      var panel = panels[i];
6254
6255      if (
6256        matchesAnchor(cleanRequested, tab.getAttribute("data-resource-anchor")) ||
6257        matchesAnchor(cleanRequested, tab.textContent) ||
6258        matchesAnchor(cleanRequested, tab.getAttribute("id")) ||
6259        matchesAnchor(cleanRequested, tab.getAttribute("aria-controls")) ||
6260        matchesAnchor(cleanRequested, getTabGeneratedAnchor(tab)) ||
6261        matchesAnchor(cleanRequested, panel ? panel.getAttribute("data-resource-anchor") : "") ||
6262        matchesAnchor(cleanRequested, panel ? panel.getAttribute("id") : "") ||
6263        matchesAnchor(cleanRequested, panel ? panel.getAttribute("aria-labelledby") : "") ||
6264        matchesAnchor(cleanRequested, getPanelGeneratedAnchor(panel))
6265      ) {
6266        return i;
6267      }
6268    }
6269
6270    return -1;
6271  }
6272
6273  function dispatchCustomEvent(target, eventName, detail) {
6274    var event;
6275
6276    if (!target) return;
6277
6278    if (typeof window.CustomEvent === "function") {
6279      event = new CustomEvent(eventName, {
6280        bubbles: true,
6281        detail: detail || {}
6282      });
6283    } else {
6284      event = document.createEvent("CustomEvent");
6285      event.initCustomEvent(eventName, true, false, detail || {});
6286    }
6287
6288    target.dispatchEvent(event);
6289  }
6290
6291  function setTabState(root, tabs, panels, index, options) {
6292    var opts = options || {};
6293
6294    if (index < 0 || index >= tabs.length) return;
6295
6296    tabs.forEach(function (tab, tabIndex) {
6297      var isActive = tabIndex === index;
6298
6299      tab.classList.toggle("is-active", isActive);
6300      tab.setAttribute("aria-selected", isActive ? "true" : "false");
6301      tab.setAttribute("tabindex", isActive ? "0" : "-1");
6302
6303      if (!normalizeAnchor(tab.getAttribute("data-resource-anchor"))) {
6304        tab.setAttribute("data-resource-anchor", normalizeAnchor(tab.textContent));
6305      }
6306
6307      if (isActive && opts.moveFocus) {
6308        focusTab(tab);
6309      }
6310    });
6311
6312    panels.forEach(function (panel, panelIndex) {
6313      var isActive = panelIndex === index;
6314
6315      if (
6316        !isActive &&
6317        panel.contains(document.activeElement) &&
6318        tabs[index] &&
6319        typeof tabs[index].focus === "function"
6320      ) {
6321        focusTab(tabs[index]);
6322      }
6323
6324      panel.classList.toggle("is-active", isActive);
6325      panel.hidden = !isActive;
6326      panel.setAttribute("aria-hidden", isActive ? "false" : "true");
6327      panel.removeAttribute("tabindex");
6328      setPanelTabbables(panel, isActive);
6329
6330      if (!normalizeAnchor(panel.getAttribute("data-resource-anchor")) && tabs[panelIndex]) {
6331        panel.setAttribute("data-resource-anchor", normalizeAnchor(tabs[panelIndex].textContent));
6332      }
6333
6334      if (isActive) {
6335        dispatchCustomEvent(panel, "enisa:resource-tab-visible", {
6336          tab: tabs[index],
6337          panel: panel
6338        });
6339      }
6340    });
6341
6342    dispatchCustomEvent(root, "enisa:resource-tab-change", {
6343      tab: tabs[index],
6344      panel: panels[index]
6345    });
6346  }
6347
6348  function isTabAlreadyActive(tabs, panels, index) {
6349    return !!(
6350      tabs[index] &&
6351      panels[index] &&
6352      tabs[index].classList.contains("is-active") &&
6353      tabs[index].getAttribute("aria-selected") === "true" &&
6354      panels[index].classList.contains("is-active") &&
6355      panels[index].hidden === false &&
6356      panels[index].getAttribute("aria-hidden") === "false"
6357    );
6358  }
6359
6360  function initRecursosTabs(root) {
6361    if (!root || root.getAttribute(TABS_INIT_ATTR) === "true") return;
6362
6363    root.setAttribute(TABS_INIT_ATTR, "true");
6364    root.setAttribute(LEGACY_TABS_INIT_ATTR, "true");
6365
6366    var tabs = [].slice.call(root.querySelectorAll(TAB_SELECTOR));
6367    if (!tabs.length) return;
6368
6369    var panels = tabs
6370      .map(function (tab) {
6371        var id = tab.getAttribute("aria-controls");
6372        return id ? document.getElementById(id) : null;
6373      })
6374      .filter(Boolean);
6375
6376    if (!panels.length) return;
6377
6378    hydrateMissingAnchors(tabs, panels);
6379
6380    var isTransparencyTabs = root.classList.contains("enisa-cmp-transparencia-tabs");
6381    var isRecursosDetalleTabs = !!root.closest(RECURSOS_DETALLE_SELECTOR);
6382    var tabsNavInner = isTransparencyTabs
6383      ? root.querySelector(".enisa-cmp-transparencia-tabs__nav-inner")
6384      : root;
6385    var requested = parseInt(root.getAttribute("data-active-tab"), 10);
6386    var activeIndex = isTransparencyTabs
6387      ? 0
6388      : isNaN(requested)
6389        ? 0
6390        : Math.min(Math.max(requested - 1, 0), tabs.length - 1);
6391
6392    function notifyLayoutRefresh() {
6393      if (!isTransparencyTabs) return;
6394
6395      afterNextPaint(function () {
6396        window.dispatchEvent(new Event("enisa:transparency-tab-change"));
6397      });
6398    }
6399
6400    function alignActiveTabToStart(behavior) {
6401      var activeTab;
6402      var targetLeft;
6403
6404      if (!tabsNavInner) return;
6405      if (tabsNavInner.scrollWidth <= tabsNavInner.clientWidth) return;
6406
6407      activeTab = tabs[activeIndex];
6408      if (!activeTab) return;
6409
6410      targetLeft = Math.max(0, activeTab.offsetLeft - tabsNavInner.offsetLeft);
6411
6412      tabsNavInner.scrollTo({
6413        left: targetLeft,
6414        behavior: prefersReducedMotion() ? "auto" : behavior || "auto"
6415      });
6416    }
6417
6418    function setActive(index, options) {
6419      var opts = options || {};
6420
6421      if (index < 0 || index >= tabs.length) return;
6422
6423      activeIndex = index;
6424
6425      if (!isTabAlreadyActive(tabs, panels, activeIndex)) {
6426        setTabState(root, tabs, panels, activeIndex, opts);
6427      }
6428
6429      alignActiveTabToStart(opts.moveFocus ? "smooth" : "auto");
6430      notifyLayoutRefresh();
6431    }
6432
6433    function activateFromRequestedAnchor(options) {
6434      var requestedAnchor;
6435      var requestedIndex;
6436
6437      if (!isRecursosDetalleTabs) return false;
6438
6439      requestedAnchor = getRequestedTabAnchor();
6440      requestedIndex = getIndexFromAnchor(tabs, panels, requestedAnchor);
6441
6442      if (requestedIndex === -1) return false;
6443
6444      setActive(requestedIndex, {
6445        moveFocus: false,
6446        scroll: !!(options && options.scroll),
6447        scrollBehavior: options && options.scrollBehavior ? options.scrollBehavior : "smooth"
6448      });
6449
6450      return true;
6451    }
6452
6453    tabs.forEach(function (tab, i) {
6454      tab.addEventListener("click", function (event) {
6455        event.preventDefault();
6456        userHasInteractedWithResourceTabs = true;
6457
6458        setActive(i, {
6459          moveFocus: false
6460        });
6461      });
6462
6463      tab.addEventListener("keydown", function (event) {
6464        var next = activeIndex;
6465
6466        switch (event.key) {
6467          case "ArrowRight":
6468          case "ArrowDown":
6469            next = (activeIndex + 1) % tabs.length;
6470            break;
6471          case "ArrowLeft":
6472          case "ArrowUp":
6473            next = (activeIndex - 1 + tabs.length) % tabs.length;
6474            break;
6475          case "Home":
6476            next = 0;
6477            break;
6478          case "End":
6479            next = tabs.length - 1;
6480            break;
6481          default:
6482            return;
6483        }
6484
6485        event.preventDefault();
6486        userHasInteractedWithResourceTabs = true;
6487
6488        setActive(next, {
6489          moveFocus: true,
6490          scroll: false
6491        });
6492      });
6493    });
6494
6495    if (!activateFromRequestedAnchor({ scroll: false })) {
6496      setActive(activeIndex, {
6497        moveFocus: false,
6498        scroll: false
6499      });
6500    }
6501  }
6502
6503  function initRecursosDetallePagination(componentRoot) {
6504    if (!componentRoot || componentRoot.getAttribute(PAGINATION_INIT_ATTR) === "true") {
6505      return;
6506    }
6507
6508    componentRoot.setAttribute(PAGINATION_INIT_ATTR, "true");
6509
6510    var pageSize = parseInt(componentRoot.getAttribute("data-items-per-page"), 10);
6511    if (isNaN(pageSize) || pageSize < 1) pageSize = 10;
6512    if (pageSize > 10) pageSize = 10;
6513
6514    var tabs = [].slice.call(componentRoot.querySelectorAll(TAB_SELECTOR));
6515    var panels = [].slice.call(componentRoot.querySelectorAll(PANEL_SELECTOR));
6516    if (!panels.length) return;
6517
6518    panels.forEach(function (panel) {
6519      var items = [].slice.call(panel.querySelectorAll(RESOURCE_ITEM_SELECTOR));
6520      var pagination = panel.querySelector("[data-resource-pagination]");
6521      var currentPage = 1;
6522
6523      if (!items.length) {
6524        if (pagination) pagination.setAttribute("hidden", "");
6525        return;
6526      }
6527
6528      function renderPagination(totalPages) {
6529        var windowSize = 3;
6530        var half = Math.floor(windowSize / 2);
6531        var start = Math.max(1, currentPage - half);
6532        var end = Math.min(totalPages, start + windowSize - 1);
6533        var prevBtn;
6534        var pageList;
6535        var nextBtn;
6536        var i;
6537
6538        if (!pagination) return;
6539
6540        pagination.innerHTML = "";
6541
6542        if (totalPages <= 1) {
6543          pagination.setAttribute("hidden", "");
6544          return;
6545        }
6546
6547        pagination.removeAttribute("hidden");
6548
6549        if (end - start < windowSize - 1) {
6550          start = Math.max(1, end - windowSize + 1);
6551        }
6552
6553        prevBtn = document.createElement("button");
6554        prevBtn.type = "button";
6555        prevBtn.className = "enisa-pagination__btn enisa-pagination__btn--arrow is-prev";
6556        prevBtn.setAttribute("aria-label", "Página anterior");
6557        prevBtn.innerHTML =
6558          '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
6559        prevBtn.disabled = currentPage === 1;
6560        prevBtn.addEventListener("click", function () {
6561          if (currentPage <= 1) return;
6562
6563          currentPage -= 1;
6564          render({ type: "prev" });
6565        });
6566        pagination.appendChild(prevBtn);
6567
6568        pageList = document.createElement("div");
6569        pageList.className = "enisa-pagination__list";
6570
6571        for (i = start; i <= end; i += 1) {
6572          (function (pageNumber) {
6573            var pageBtn = document.createElement("button");
6574            var isCurrentPage = pageNumber === currentPage;
6575
6576            pageBtn.type = "button";
6577            pageBtn.className = "enisa-pagination__btn" + (isCurrentPage ? " is-active" : "");
6578            pageBtn.textContent = String(pageNumber);
6579            pageBtn.setAttribute(
6580              "aria-label",
6581              isCurrentPage
6582                ? "Página " + pageNumber + ", actual"
6583                : "Ir a la página " + pageNumber
6584            );
6585
6586            if (isCurrentPage) {
6587              pageBtn.setAttribute("aria-current", "page");
6588            }
6589
6590            pageBtn.addEventListener("click", function () {
6591              if (currentPage === pageNumber) return;
6592
6593              currentPage = pageNumber;
6594              render({ type: "page", page: pageNumber });
6595            });
6596
6597            pageList.appendChild(pageBtn);
6598          })(i);
6599        }
6600
6601        pagination.appendChild(pageList);
6602
6603        nextBtn = document.createElement("button");
6604        nextBtn.type = "button";
6605        nextBtn.className = "enisa-pagination__btn enisa-pagination__btn--arrow is-next";
6606        nextBtn.setAttribute("aria-label", "Página siguiente");
6607        nextBtn.innerHTML =
6608          '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
6609        nextBtn.disabled = currentPage === totalPages;
6610        nextBtn.addEventListener("click", function () {
6611          if (currentPage >= totalPages) return;
6612
6613          currentPage += 1;
6614          render({ type: "next" });
6615        });
6616        pagination.appendChild(nextBtn);
6617      }
6618
6619      function getPaginationFocusTarget(focusTarget) {
6620        if (!focusTarget || !pagination) return null;
6621
6622        if (focusTarget.type === "prev") {
6623          return pagination.querySelector(".enisa-pagination__btn--arrow.is-prev");
6624        }
6625
6626        if (focusTarget.type === "next") {
6627          return pagination.querySelector(".enisa-pagination__btn--arrow.is-next");
6628        }
6629
6630        if (focusTarget.type === "page") {
6631          return (
6632            [].slice
6633              .call(pagination.querySelectorAll(".enisa-pagination__list .enisa-pagination__btn"))
6634              .filter(function (button) {
6635                return button.textContent === String(focusTarget.page);
6636              })[0] || null
6637          );
6638        }
6639
6640        return null;
6641      }
6642
6643      function render(focusTarget) {
6644        var totalPages = Math.max(1, Math.ceil(items.length / pageSize));
6645        var activeItem =
6646          document.activeElement && document.activeElement.closest
6647            ? document.activeElement.closest(RESOURCE_ITEM_SELECTOR)
6648            : null;
6649        var start;
6650        var end;
6651
6652        if (currentPage > totalPages) currentPage = totalPages;
6653        if (currentPage < 1) currentPage = 1;
6654
6655        start = (currentPage - 1) * pageSize;
6656        end = start + pageSize;
6657
6658        if (
6659          activeItem &&
6660          items.indexOf(activeItem) > -1 &&
6661          (items.indexOf(activeItem) < start || items.indexOf(activeItem) >= end)
6662        ) {
6663          var fallbackFocus =
6664            getPaginationFocusTarget(focusTarget) ||
6665            tabs.filter(function (tab) {
6666              return tab.getAttribute("aria-selected") === "true";
6667            })[0] ||
6668            tabs[0];
6669
6670          if (
6671            fallbackFocus &&
6672            window.ENISA &&
6673            window.ENISA.utils &&
6674            typeof window.ENISA.utils.focusWithoutScroll === "function"
6675          ) {
6676            window.ENISA.utils.focusWithoutScroll(fallbackFocus);
6677          } else if (fallbackFocus && typeof fallbackFocus.focus === "function") {
6678            focusTab(fallbackFocus);
6679          }
6680        }
6681
6682        items.forEach(function (item, index) {
6683          item.hidden = index < start || index >= end;
6684        });
6685
6686        renderPagination(totalPages);
6687
6688        if (focusTarget) {
6689          afterNextPaint(function () {
6690            var nextFocusTarget = getPaginationFocusTarget(focusTarget);
6691
6692            if (
6693              nextFocusTarget &&
6694              typeof nextFocusTarget.focus === "function" &&
6695              document.contains(nextFocusTarget)
6696            ) {
6697              focusTab(nextFocusTarget);
6698            }
6699          });
6700        }
6701      }
6702
6703      panel.addEventListener("enisa:resource-tab-visible", function () {
6704        currentPage = 1;
6705        render();
6706      });
6707
6708      render();
6709    });
6710  }
6711
6712  function initResourceImageFallbacks(scope) {
6713    var root = scope && scope.querySelectorAll ? scope : document;
6714    var images = root.querySelectorAll(RESOURCE_IMAGE_FALLBACK_SELECTOR);
6715
6716    [].forEach.call(images, function (image) {
6717      if (!image || image.getAttribute(FALLBACK_INIT_ATTR) === "true") return;
6718
6719      image.setAttribute(FALLBACK_INIT_ATTR, "true");
6720      image.addEventListener("error", function () {
6721        var fallbackSrc = image.getAttribute("data-fallback-src");
6722
6723        if (!fallbackSrc) return;
6724
6725        image.removeAttribute("data-fallback-src");
6726
6727        if (image.getAttribute("src") === fallbackSrc || image.src === fallbackSrc) {
6728          return;
6729        }
6730
6731        image.src = fallbackSrc;
6732      });
6733    });
6734  }
6735
6736  function clearResourceMediaRatioClasses(item, media) {
6737    var classes = [
6738      "enisa-recursos-detalle__item--media-portrait",
6739      "enisa-recursos-detalle__item--media-square",
6740      "enisa-recursos-detalle__item--media-landscape",
6741      "enisa-recursos-detalle__item--media-panorama",
6742      "enisa-recursos-detalle__media--portrait",
6743      "enisa-recursos-detalle__media--square",
6744      "enisa-recursos-detalle__media--landscape",
6745      "enisa-recursos-detalle__media--panorama"
6746    ];
6747
6748    classes.forEach(function (className) {
6749      if (item) item.classList.remove(className);
6750      if (media) media.classList.remove(className);
6751    });
6752  }
6753
6754  function getResourceItemText(image, item) {
6755    var title = item.querySelector(".enisa-recursos-detalle__title");
6756    var downloadLabel = item.querySelector(".enisa-recursos-detalle__download-label");
6757
6758    return [
6759      image.getAttribute("alt") || "",
6760      image.getAttribute("src") || "",
6761      title ? title.textContent : "",
6762      downloadLabel ? downloadLabel.textContent : ""
6763    ]
6764      .join(" ")
6765      .toLowerCase();
6766  }
6767
6768  function applyResourceMediaRatioClass(image) {
6769    var media;
6770    var item;
6771    var itemText;
6772    var width;
6773    var height;
6774    var ratio;
6775    var type = "landscape";
6776
6777    if (!image) return;
6778
6779    media = image.closest(RESOURCE_DETAIL_MEDIA_SELECTOR);
6780    item = image.closest(RESOURCE_DETAIL_ITEM_SELECTOR);
6781
6782    if (!media || !item) return;
6783
6784    itemText = getResourceItemText(image, item);
6785
6786    if (
6787      media.classList.contains("enisa-recursos-detalle__media--conjunto-asset") ||
6788      image.classList.contains("enisa-recursos-detalle__image--conjunto-asset") ||
6789      itemText.indexOf("conjunto") !== -1
6790    ) {
6791      clearResourceMediaRatioClasses(item, media);
6792
6793      item.classList.add("enisa-recursos-detalle__item--media-landscape");
6794      item.classList.add("enisa-recursos-detalle__item--conjunto-asset");
6795
6796      media.classList.add("enisa-recursos-detalle__media--landscape");
6797      media.classList.add("enisa-recursos-detalle__media--conjunto-asset");
6798      media.setAttribute("data-media-ratio", "landscape");
6799
6800      image.classList.add("enisa-recursos-detalle__image--conjunto-asset");
6801
6802      return;
6803    }
6804
6805    width = image.naturalWidth || 0;
6806    height = image.naturalHeight || 0;
6807
6808    if (!width || !height) return;
6809
6810    ratio = width / height;
6811
6812    if (ratio < 0.85) {
6813      type = "portrait";
6814    } else if (ratio <= 1.2) {
6815      type = "square";
6816    } else if (ratio >= 2.2) {
6817      type = "panorama";
6818    }
6819
6820    clearResourceMediaRatioClasses(item, media);
6821    item.classList.add("enisa-recursos-detalle__item--media-" + type);
6822    media.classList.add("enisa-recursos-detalle__media--" + type);
6823    media.setAttribute("data-media-ratio", type);
6824  }
6825
6826  function initResourceMediaRatios(scope) {
6827    var root = scope && scope.querySelectorAll ? scope : document;
6828    var images = root.querySelectorAll(RESOURCE_DETAIL_IMAGE_SELECTOR);
6829
6830    [].forEach.call(images, function (image) {
6831      if (!image) return;
6832
6833      if (image.complete && image.naturalWidth) {
6834        applyResourceMediaRatioClass(image);
6835      }
6836
6837      if (image.getAttribute(RATIO_INIT_ATTR) === "true") return;
6838
6839      image.setAttribute(RATIO_INIT_ATTR, "true");
6840
6841      image.addEventListener("load", function () {
6842        applyResourceMediaRatioClass(image);
6843      });
6844    });
6845  }
6846
6847  function applyRequestedTab(options) {
6848    var opts = options || {};
6849    var requestedAnchor = getRequestedTabAnchor();
6850    var roots;
6851    var applied = false;
6852
6853    if (!normalizeAnchor(requestedAnchor)) return false;
6854    if (userHasInteractedWithResourceTabs && !opts.force) return false;
6855
6856    roots = [].slice.call(document.querySelectorAll(ROOT_SELECTOR));
6857
6858    roots.forEach(function (root) {
6859      var isRecursosDetalleTabs = !!root.closest(RECURSOS_DETALLE_SELECTOR);
6860      var tabs;
6861      var panels;
6862      var index;
6863
6864      if (!isRecursosDetalleTabs) return;
6865
6866      tabs = [].slice.call(root.querySelectorAll(TAB_SELECTOR));
6867      if (!tabs.length) return;
6868
6869      panels = tabs
6870        .map(function (tab) {
6871          var id = tab.getAttribute("aria-controls");
6872          return id ? document.getElementById(id) : null;
6873        })
6874        .filter(Boolean);
6875
6876      if (!panels.length) return;
6877
6878      hydrateMissingAnchors(tabs, panels);
6879      index = getIndexFromAnchor(tabs, panels, requestedAnchor);
6880
6881      if (index < 0) return;
6882
6883      if (!isTabAlreadyActive(tabs, panels, index)) {
6884        setTabState(root, tabs, panels, index, { moveFocus: false });
6885      }
6886
6887      applied = true;
6888      hasAppliedInitialRequestedTab = true;
6889    });
6890
6891    return applied;
6892  }
6893
6894  function initScope(scope) {
6895    var root = scope && scope.querySelectorAll ? scope : document;
6896    var tabRoots = [];
6897    var recursosDetalleRoots = [];
6898
6899    if (root.matches && root.matches(ROOT_SELECTOR)) {
6900      tabRoots.push(root);
6901    }
6902
6903    tabRoots = tabRoots.concat([].slice.call(root.querySelectorAll(ROOT_SELECTOR)));
6904    tabRoots.forEach(initRecursosTabs);
6905
6906    if (root.matches && root.matches(RECURSOS_DETALLE_SELECTOR)) {
6907      recursosDetalleRoots.push(root);
6908    }
6909
6910    recursosDetalleRoots = recursosDetalleRoots.concat(
6911      [].slice.call(root.querySelectorAll(RECURSOS_DETALLE_SELECTOR))
6912    );
6913
6914    recursosDetalleRoots.forEach(function (componentRoot) {
6915      initRecursosDetallePagination(componentRoot);
6916      initResourceImageFallbacks(componentRoot);
6917      initResourceMediaRatios(componentRoot);
6918    });
6919
6920    initResourceImageFallbacks(root);
6921    initResourceMediaRatios(root);
6922  }
6923
6924  function init() {
6925    if (hasInitialized) {
6926      applyRequestedTab({ force: false, scroll: false });
6927      return;
6928    }
6929
6930    hasInitialized = true;
6931    initScope(document);
6932    applyRequestedTab({ force: true, scroll: false });
6933  }
6934
6935  if (document.readyState === "loading") {
6936    document.addEventListener("DOMContentLoaded", init);
6937  } else {
6938    init();
6939  }
6940
6941  window.addEventListener("load", function () {
6942    if (hasAppliedInitialRequestedTab || userHasInteractedWithResourceTabs) return;
6943    applyRequestedTab({ force: false, scroll: false });
6944  });
6945
6946  window.addEventListener("popstate", function () {
6947    userHasInteractedWithResourceTabs = false;
6948    applyRequestedTab({ force: true, scroll: false });
6949  });
6950
6951  window.addEventListener(
6952    "resize",
6953    debounce(function () {
6954      initResourceMediaRatios(document);
6955    }, 150)
6956  );
6957
6958  if ("MutationObserver" in window) {
6959    var mutationTimer = null;
6960    var mutationRoot =
6961      document.querySelector(".page-layout__content") ||
6962      document.querySelector("#main-content") ||
6963      document.querySelector("#main-content") ||
6964      document.body;
6965
6966    var observer = new MutationObserver(function (mutations) {
6967      var addedNodes = [];
6968
6969      mutations.forEach(function (mutation) {
6970        if (!mutation.addedNodes || !mutation.addedNodes.length) return;
6971
6972        [].forEach.call(mutation.addedNodes, function (node) {
6973          if (node && node.nodeType === 1) {
6974            addedNodes.push(node);
6975          }
6976        });
6977      });
6978
6979      if (!addedNodes.length) return;
6980
6981      clearTimeout(mutationTimer);
6982      mutationTimer = setTimeout(function () {
6983        addedNodes.forEach(initScope);
6984        applyRequestedTab({ force: false, scroll: false });
6985      }, 150);
6986    });
6987
6988    if (mutationRoot) {
6989      observer.observe(mutationRoot, {
6990        childList: true,
6991        subtree: true
6992      });
6993    }
6994  }
6995})();
6996
6997/*
6998  ENISA | JS Module
6999  Modulo: 19-transparency
7000  Descripcion: Logica del modulo de transparencia.
7001*/
7002(function () {
7003  "use strict";
7004
7005  var TABLE_SELECTOR = "[data-transparency-table], .enisa-transparency-table";
7006  var TRANSPARENCY_SCOPE_SELECTOR =
7007    "[data-transparency-documents], .enisa-cmp-transparencia-documents, .enisa-transparencia-documents";
7008  var RESOURCE_DETAIL_SELECTOR = ".enisa-recursos-detalle--transparencia";
7009  var SELECT_SELECTOR = "[data-enisa-select], .enisa-select-control";
7010  var RESOURCE_ITEM_SELECTOR =
7011    ".enisa-recursos-detalle__item:not(.enisa-recursos-detalle__item--heading)";
7012  var RESOURCE_LIST_SELECTOR = ".enisa-recursos-detalle__list";
7013  var YEAR_FILTER_ATTR = "data-transparency-year";
7014  var INIT_SELECT_ATTR = "data-enisa-select-init";
7015  var INIT_TABLE_ATTR = "data-transparency-init";
7016  var INIT_DETAIL_ATTR = "data-transparency-detail-init";
7017  var DETAIL_PAGE_SIZE = 10;
7018
7019  var MONTHS_ES = {
7020    enero: 0,
7021    febrero: 1,
7022    marzo: 2,
7023    abril: 3,
7024    mayo: 4,
7025    junio: 5,
7026    julio: 6,
7027    agosto: 7,
7028    septiembre: 8,
7029    setiembre: 8,
7030    octubre: 9,
7031    noviembre: 10,
7032    diciembre: 11
7033  };
7034
7035  function normalizeText(value) {
7036    return (value || "").toString().replace(/\s+/g, " ").trim();
7037  }
7038
7039  function normalizeFilenameLabel(value) {
7040    return normalizeText(value).replace(/_/g, " ");
7041  }
7042
7043  function getTextContent(node) {
7044    return normalizeText(node ? node.textContent : "");
7045  }
7046
7047  function safeFocus(element) {
7048    if (
7049      window.ENISA &&
7050      window.ENISA.utils &&
7051      typeof window.ENISA.utils.safeFocus === "function"
7052    ) {
7053      return window.ENISA.utils.safeFocus(element);
7054    }
7055
7056    if (!element || typeof element.focus !== "function") return false;
7057
7058    try {
7059      element.focus({ preventScroll: true });
7060    } catch (error) {
7061      element.focus();
7062    }
7063
7064    return true;
7065  }
7066
7067  function parseYear(value) {
7068    var text = normalizeText(value);
7069    var match = text.match(/\b(19|20)\d{2}\b/);
7070
7071    return match ? match[0] : "";
7072  }
7073
7074  function normalizeMonthName(value) {
7075    return normalizeText(value)
7076      .toLowerCase()
7077      .replace(/[áàäâ]/g, "a")
7078      .replace(/[éèëê]/g, "e")
7079      .replace(/[íìïî]/g, "i")
7080      .replace(/[óòöô]/g, "o")
7081      .replace(/[úùüû]/g, "u");
7082  }
7083
7084  function parseSpanishDate(value) {
7085    var text = normalizeText(value).toLowerCase();
7086
7087    var fullDateMatch = text.match(
7088      /\b(\d{1,2})\s+(?:de\s+)?([a-záéíóúüñ]+)\s+de\s+((?:19|20)\d{2})\b/i
7089    );
7090
7091    if (fullDateMatch) {
7092      var day = parseInt(fullDateMatch[1], 10);
7093      var month = MONTHS_ES[normalizeMonthName(fullDateMatch[2])];
7094      var year = parseInt(fullDateMatch[3], 10);
7095
7096      if (!isNaN(day) && !isNaN(year) && typeof month === "number") {
7097        return new Date(year, month, day).getTime();
7098      }
7099    }
7100
7101    var numericDateMatch = text.match(/\b(\d{1,2})[/-](\d{1,2})[/-]((?:19|20)\d{2})\b/);
7102
7103    if (numericDateMatch) {
7104      var numericDay = parseInt(numericDateMatch[1], 10);
7105      var numericMonth = parseInt(numericDateMatch[2], 10) - 1;
7106      var numericYear = parseInt(numericDateMatch[3], 10);
7107
7108      if (!isNaN(numericDay) && !isNaN(numericMonth) && !isNaN(numericYear)) {
7109        return new Date(numericYear, numericMonth, numericDay).getTime();
7110      }
7111    }
7112
7113    var yearOnly = parseYear(text);
7114
7115    if (yearOnly) {
7116      return new Date(parseInt(yearOnly, 10), 0, 1).getTime();
7117    }
7118
7119    return 0;
7120  }
7121
7122  function getItemYear(item) {
7123    if (!item) return "";
7124
7125    var explicitYear = normalizeText(item.getAttribute("data-year"));
7126    if (explicitYear) return explicitYear;
7127
7128    var dateAttr =
7129      normalizeText(item.getAttribute("data-date")) ||
7130      normalizeText(item.getAttribute("data-transparency-date"));
7131
7132    if (dateAttr) {
7133      var yearFromDateAttr = parseYear(dateAttr);
7134      if (yearFromDateAttr) return yearFromDateAttr;
7135    }
7136
7137    return parseYear(getTextContent(item));
7138  }
7139
7140  function getSortTimestamp(item) {
7141    if (!item) return 0;
7142
7143    var explicitDate =
7144      normalizeText(item.getAttribute("data-date")) ||
7145      normalizeText(item.getAttribute("data-transparency-date"));
7146
7147    if (explicitDate) {
7148      var explicitTimestamp = parseSpanishDate(explicitDate);
7149      if (explicitTimestamp) return explicitTimestamp;
7150    }
7151
7152    return parseSpanishDate(getTextContent(item));
7153  }
7154
7155  function getItemOrder(item) {
7156    if (!item) return Number.MAX_SAFE_INTEGER;
7157
7158    var rawOrder =
7159      normalizeText(item.getAttribute("data-order")) ||
7160      normalizeText(item.getAttribute("data-transparency-order"));
7161
7162    var order = parseInt(rawOrder, 10);
7163
7164    return isNaN(order) ? Number.MAX_SAFE_INTEGER : order;
7165  }
7166
7167  function getColorSequenceIndex(index) {
7168    var COLOR_COUNT = 7;
7169    var safeIndex = parseInt(index, 10);
7170
7171    if (isNaN(safeIndex) || safeIndex < 1) {
7172      safeIndex = 1;
7173    }
7174
7175    return ((safeIndex - 1) % COLOR_COUNT) + 1;
7176  }
7177
7178  function collectYears(items) {
7179    var seen = {};
7180    var years = [];
7181
7182    items.forEach(function (item) {
7183      var year = getItemYear(item);
7184
7185      if (!year || seen[year]) return;
7186
7187      seen[year] = true;
7188      years.push(year);
7189    });
7190
7191    years.sort(function (a, b) {
7192      return parseInt(b, 10) - parseInt(a, 10);
7193    });
7194
7195    return years;
7196  }
7197
7198  function syncNativeSelectOptions(nativeSelect, years, placeholder) {
7199    if (!nativeSelect) return;
7200
7201    var currentValue = normalizeText(nativeSelect.value);
7202    var safePlaceholder = placeholder || "Todos";
7203
7204    nativeSelect.innerHTML = "";
7205
7206    var allOption = document.createElement("option");
7207    allOption.value = "";
7208    allOption.textContent = safePlaceholder;
7209    nativeSelect.appendChild(allOption);
7210
7211    years.forEach(function (year) {
7212      var option = document.createElement("option");
7213      option.value = year;
7214      option.textContent = year;
7215      nativeSelect.appendChild(option);
7216    });
7217
7218    nativeSelect.value = years.indexOf(currentValue) !== -1 ? currentValue : "";
7219  }
7220
7221  function syncCustomSelect(selectRoot) {
7222    if (!selectRoot) return;
7223
7224    var nativeSelect = selectRoot.querySelector("select");
7225    var label = selectRoot.querySelector(".enisa-select-control__label");
7226    var menu = selectRoot.querySelector(".enisa-select-control__menu");
7227
7228    if (!nativeSelect || !label || !menu) return;
7229
7230    var selectedOption = nativeSelect.options[nativeSelect.selectedIndex];
7231    label.textContent = selectedOption ? selectedOption.textContent : "";
7232
7233    var currentValue = normalizeText(nativeSelect.value);
7234    var hasValue = currentValue !== "";
7235    var trigger = selectRoot.querySelector(".enisa-select-control__trigger");
7236
7237    selectRoot.classList.toggle("has-value", hasValue);
7238
7239    if (trigger) {
7240      trigger.classList.toggle("has-value", hasValue);
7241    }
7242
7243    [].forEach.call(menu.querySelectorAll(".enisa-select-control__option"), function (item) {
7244      var itemValue = normalizeText(item.getAttribute("data-value"));
7245      var isSelected = itemValue === currentValue;
7246
7247      item.classList.toggle("is-selected", isSelected);
7248      item.setAttribute("aria-selected", isSelected ? "true" : "false");
7249    });
7250  }
7251
7252  function initCustomSelect(selectRoot) {
7253    if (!selectRoot || selectRoot.getAttribute(INIT_SELECT_ATTR) === "true") return;
7254
7255    var nativeSelect = selectRoot.querySelector("select");
7256    var trigger = selectRoot.querySelector(".enisa-select-control__trigger");
7257    var label = selectRoot.querySelector(".enisa-select-control__label");
7258    var menu = selectRoot.querySelector(".enisa-select-control__menu");
7259
7260    if (!nativeSelect || !trigger || !label || !menu) return;
7261
7262    selectRoot.setAttribute(INIT_SELECT_ATTR, "true");
7263    nativeSelect.setAttribute("tabindex", "-1");
7264    nativeSelect.setAttribute("aria-hidden", "true");
7265
7266    if (!label.id) label.id = (nativeSelect.id || "enisa-select") + "-value";
7267    var fieldLabel = nativeSelect.id ? document.querySelector("label[for='" + nativeSelect.id.replace(/'/g, "\\'") + "']") : null;
7268    if (fieldLabel && !fieldLabel.id) fieldLabel.id = nativeSelect.id + "-label";
7269
7270    trigger.setAttribute("aria-labelledby", (fieldLabel ? fieldLabel.id + " " : "") + label.id);
7271    trigger.setAttribute("aria-haspopup", "listbox");
7272    trigger.setAttribute("aria-expanded", "false");
7273    trigger.setAttribute("aria-controls", menu.id || (nativeSelect.id + "-listbox"));
7274    if (!menu.id) menu.id = nativeSelect.id + "-listbox";
7275    menu.setAttribute("role", "listbox");
7276    menu.setAttribute("aria-labelledby", (fieldLabel ? fieldLabel.id + " " : "") + label.id);
7277    menu.hidden = true;
7278
7279    function closeMenu() {
7280      selectRoot.classList.remove("is-open");
7281      trigger.setAttribute("aria-expanded", "false");
7282      menu.hidden = true;
7283    }
7284
7285    function openMenu() {
7286      selectRoot.classList.add("is-open");
7287      trigger.setAttribute("aria-expanded", "true");
7288      menu.hidden = false;
7289    }
7290
7291    function getOptions() {
7292      return [].slice.call(menu.querySelectorAll(".enisa-select-control__option"));
7293    }
7294
7295    function focusOption(index) {
7296      var options = getOptions();
7297      if (!options.length) return;
7298
7299      var safeIndex = Math.max(0, Math.min(index, options.length - 1));
7300      safeFocus(options[safeIndex]);
7301    }
7302
7303    function getSelectedIndex() {
7304      var currentValue = normalizeText(nativeSelect.value);
7305      var options = getOptions();
7306
7307      for (var i = 0; i < options.length; i += 1) {
7308        var itemValue = normalizeText(options[i].getAttribute("data-value"));
7309        if (itemValue === currentValue) return i;
7310      }
7311
7312      return 0;
7313    }
7314
7315    function renderOptions() {
7316      menu.innerHTML = "";
7317
7318      [].forEach.call(nativeSelect.options, function (opt) {
7319        var optionValue = normalizeText(opt.value);
7320
7321        var item = document.createElement("div");
7322        item.className =
7323          "enisa-select-control__option" +
7324          (optionValue === "" ? " enisa-select-control__option--reset" : "");
7325        item.setAttribute("role", "option");
7326        item.setAttribute("tabindex", "-1");
7327        item.setAttribute("data-value", optionValue);
7328        item.textContent = opt.textContent;
7329
7330        item.addEventListener("click", function () {
7331          nativeSelect.value = optionValue;
7332          nativeSelect.dispatchEvent(new Event("change", { bubbles: true }));
7333          syncCustomSelect(selectRoot);
7334          closeMenu();
7335          safeFocus(trigger);
7336        });
7337
7338        item.addEventListener("keydown", function (event) {
7339          var options = getOptions();
7340          var index = options.indexOf(item);
7341
7342          if (event.key === "ArrowDown") {
7343            event.preventDefault();
7344            focusOption(index + 1);
7345            return;
7346          }
7347
7348          if (event.key === "ArrowUp") {
7349            event.preventDefault();
7350            focusOption(index - 1);
7351            return;
7352          }
7353
7354          if (event.key === "Home") {
7355            event.preventDefault();
7356            focusOption(0);
7357            return;
7358          }
7359
7360          if (event.key === "End") {
7361            event.preventDefault();
7362            focusOption(options.length - 1);
7363            return;
7364          }
7365
7366          if (event.key === "Escape") {
7367            event.preventDefault();
7368            closeMenu();
7369            safeFocus(trigger);
7370            return;
7371          }
7372
7373          if (event.key === "Enter" || event.key === " ") {
7374            event.preventDefault();
7375            item.click();
7376          }
7377        });
7378
7379        menu.appendChild(item);
7380      });
7381
7382      syncCustomSelect(selectRoot);
7383    }
7384
7385    trigger.addEventListener("click", function () {
7386      if (selectRoot.classList.contains("is-open")) {
7387        closeMenu();
7388      } else {
7389        openMenu();
7390      }
7391    });
7392
7393    trigger.addEventListener("keydown", function (event) {
7394      if (event.key === "ArrowDown" || event.key === "ArrowUp") {
7395        event.preventDefault();
7396        openMenu();
7397        focusOption(getSelectedIndex());
7398        return;
7399      }
7400
7401      if (event.key === "Enter" || event.key === " ") {
7402        event.preventDefault();
7403
7404        if (selectRoot.classList.contains("is-open")) {
7405          closeMenu();
7406        } else {
7407          openMenu();
7408          focusOption(getSelectedIndex());
7409        }
7410
7411        return;
7412      }
7413
7414      if (event.key === "Escape") {
7415        closeMenu();
7416      }
7417    });
7418
7419    nativeSelect.addEventListener("change", function () {
7420      syncCustomSelect(selectRoot);
7421    });
7422
7423    document.addEventListener("click", function (event) {
7424      if (!selectRoot.contains(event.target)) {
7425        closeMenu();
7426      }
7427    });
7428
7429    renderOptions();
7430    selectRoot.enisaRenderOptions = renderOptions;
7431  }
7432
7433  function rerenderCustomSelect(selectRoot) {
7434    if (!selectRoot) return;
7435
7436    if (typeof selectRoot.enisaRenderOptions === "function") {
7437      selectRoot.enisaRenderOptions();
7438      return;
7439    }
7440
7441    syncCustomSelect(selectRoot);
7442  }
7443
7444  function sortItemsByDateDesc(items) {
7445    var indexed = items.map(function (item, index) {
7446      return {
7447        item: item,
7448        index: index,
7449        timestamp: getSortTimestamp(item),
7450        year: parseInt(getItemYear(item) || "0", 10),
7451        order: getItemOrder(item)
7452      };
7453    });
7454
7455    indexed.sort(function (a, b) {
7456      if (b.year !== a.year) return b.year - a.year;
7457      if (b.timestamp !== a.timestamp) return b.timestamp - a.timestamp;
7458      if (a.order !== b.order) return a.order - b.order;
7459      return a.index - b.index;
7460    });
7461
7462    return indexed.map(function (entry) {
7463      return entry.item;
7464    });
7465  }
7466
7467  function setRowVisibility(row, isVisible) {
7468    row.hidden = !isVisible;
7469  }
7470
7471  function ensureStatus(scope, key) {
7472    var root = scope || document;
7473    var attr = "data-enisa-status-" + key;
7474    var status = root.querySelector ? root.querySelector("[" + attr + "]") : null;
7475
7476    if (status) return status;
7477
7478    status = document.createElement("p");
7479    status.className = "enisa-sr-only";
7480    status.setAttribute(attr, "");
7481    status.setAttribute("role", "status");
7482    status.setAttribute("aria-live", "polite");
7483    status.setAttribute("aria-atomic", "true");
7484
7485    if (root.appendChild && root.nodeType === 1) {
7486      root.appendChild(status);
7487    } else {
7488      document.body.appendChild(status);
7489    }
7490
7491    return status;
7492  }
7493
7494  function announce(status, message) {
7495    if (!status) return;
7496    status.textContent = "";
7497    window.setTimeout(function () {
7498      status.textContent = message || "";
7499    }, 20);
7500  }
7501
7502  function getTransparencyScope(root) {
7503    if (!root) return document;
7504
7505    return root.closest(TRANSPARENCY_SCOPE_SELECTOR) || root.parentElement || document;
7506  }
7507
7508  function syncTableCards(scope) {
7509    if (
7510      window.ENISA &&
7511      window.ENISA.tables &&
7512      typeof window.ENISA.tables.sync === "function"
7513    ) {
7514      window.ENISA.tables.sync(scope || document);
7515    }
7516  }
7517
7518  function normalizeVisibleDocumentLabels(scope) {
7519    if (!scope) return;
7520
7521    var labels = scope.querySelectorAll(
7522      ".enisa-recursos-detalle__download-label, " +
7523        ".enisa-transparencia-documents__cell:first-child, " +
7524        ".enisa-transparencia-documents__mobile-title, " +
7525        ".enisa-transparencia-documents-card__title, " +
7526        ".enisa-transparencia-documents td:first-child, " +
7527        ".enisa-table-card__item--primary .enisa-table-card__value"
7528    );
7529
7530    [].forEach.call(labels, function (label) {
7531      if (!label || !label.textContent) return;
7532      label.textContent = normalizeFilenameLabel(label.textContent);
7533    });
7534  }
7535
7536  function initTransparencyTable(tableRoot) {
7537    if (!tableRoot || tableRoot.getAttribute(INIT_TABLE_ATTR) === "true") return;
7538
7539    var scope = getTransparencyScope(tableRoot);
7540    var rows = [].slice.call(tableRoot.querySelectorAll("[data-transparency-row]"));
7541    var yearSelect =
7542      scope.querySelector("[" + YEAR_FILTER_ATTR + "]") ||
7543      tableRoot.querySelector("[" + YEAR_FILTER_ATTR + "]");
7544    var emptyState =
7545      scope.querySelector("[data-transparency-empty]") ||
7546      tableRoot.querySelector("[data-transparency-empty]");
7547    var status = ensureStatus(scope, "transparency-table");
7548
7549    if (!rows.length) return;
7550
7551    tableRoot.setAttribute(INIT_TABLE_ATTR, "true");
7552
7553    rows.forEach(function (row) {
7554      var year = getItemYear(row);
7555
7556      if (year) {
7557        row.setAttribute("data-year", year);
7558      }
7559    });
7560
7561    var sortedRows = sortItemsByDateDesc(rows);
7562
7563    sortedRows.forEach(function (row) {
7564      if (row.parentNode) {
7565        row.parentNode.appendChild(row);
7566      }
7567    });
7568
7569    if (yearSelect) {
7570      var years = collectYears(sortedRows);
7571      var selectRoot = yearSelect.closest(SELECT_SELECTOR);
7572
7573      initCustomSelect(selectRoot);
7574
7575      if (years.length > 1) {
7576        syncNativeSelectOptions(yearSelect, years, "Todos");
7577        rerenderCustomSelect(selectRoot);
7578      } else if (selectRoot) {
7579        var filterRoot = selectRoot.closest(".enisa-transparencia-documents__filter");
7580
7581        if (filterRoot) {
7582          filterRoot.setAttribute("hidden", "");
7583        }
7584      }
7585    }
7586
7587    function applyYearFilter() {
7588      var selected = yearSelect ? normalizeText(yearSelect.value) : "";
7589      var visibleCount = 0;
7590      var activeRow = document.activeElement && document.activeElement.closest
7591        ? document.activeElement.closest("[data-transparency-row]")
7592        : null;
7593      var activeRowWillHide = false;
7594
7595      sortedRows.forEach(function (row) {
7596        var rowYear = getItemYear(row);
7597        var isVisible = selected === "" || selected === rowYear;
7598
7599        if (row === activeRow && !isVisible) {
7600          activeRowWillHide = true;
7601        }
7602
7603        setRowVisibility(row, isVisible);
7604
7605        if (isVisible) {
7606          visibleCount += 1;
7607        }
7608      });
7609
7610      if (activeRowWillHide && yearSelect) {
7611        var focusTarget = (yearSelect.closest(SELECT_SELECTOR) || scope)
7612          .querySelector(".enisa-select-control__trigger") || yearSelect;
7613
7614        if (
7615          focusTarget &&
7616          window.ENISA &&
7617          window.ENISA.utils &&
7618          typeof window.ENISA.utils.focusWithoutScroll === "function"
7619        ) {
7620          window.ENISA.utils.focusWithoutScroll(focusTarget);
7621        } else if (focusTarget && typeof focusTarget.focus === "function") {
7622          safeFocus(focusTarget);
7623        }
7624      }
7625
7626      if (emptyState) {
7627        emptyState.hidden = visibleCount > 0;
7628      }
7629
7630      syncTableCards(scope);
7631      announce(status, visibleCount + (visibleCount === 1 ? " documento visible." : " documentos visibles."));
7632    }
7633
7634    if (yearSelect) {
7635      yearSelect.addEventListener("change", applyYearFilter);
7636    }
7637
7638    normalizeVisibleDocumentLabels(scope);
7639    applyYearFilter();
7640  }
7641
7642  function createDetailYearPills(detailRoot, years) {
7643    if (!detailRoot || !years || years.length <= 1) return null;
7644
7645    var list = detailRoot.querySelector(RESOURCE_LIST_SELECTOR);
7646    if (!list) return null;
7647
7648    var existing = detailRoot.querySelector("[data-transparency-detail-year-pills]");
7649    if (existing) return existing;
7650
7651    var heading =
7652      list.querySelector(".enisa-recursos-detalle__item--heading-with-filter") ||
7653      list.querySelector(".enisa-recursos-detalle__item--heading");
7654
7655    var wrapper = document.createElement("div");
7656    wrapper.className = "enisa-recursos-detalle__year-pills enisa-news-filters";
7657    wrapper.setAttribute("data-transparency-detail-year-pills", "");
7658    wrapper.setAttribute("role", "group");
7659    wrapper.setAttribute("aria-label", "Filtrar documentos por año");
7660
7661    function createButton(label, value, index, active) {
7662      var button = document.createElement("button");
7663
7664      button.type = "button";
7665      button.className = "enisa-news-filters__pill" + (active ? " is-active" : "");
7666      button.setAttribute("data-transparency-detail-year-pill", value);
7667      button.setAttribute("data-color-index", String(getColorSequenceIndex(index)));
7668      button.setAttribute("aria-pressed", active ? "true" : "false");
7669      button.textContent = label;
7670
7671      return button;
7672    }
7673
7674    wrapper.appendChild(createButton("Todos", "", 1, true));
7675
7676    years.forEach(function (year, index) {
7677      wrapper.appendChild(createButton(year, year, index + 2, false));
7678    });
7679
7680    if (heading && heading.nextSibling) {
7681      list.insertBefore(wrapper, heading.nextSibling);
7682    } else if (heading) {
7683      list.appendChild(wrapper);
7684    } else {
7685      list.insertBefore(wrapper, list.firstChild);
7686    }
7687
7688    return wrapper;
7689  }
7690
7691  function setPillsState(pillsRoot, selectedYear) {
7692    if (!pillsRoot) return;
7693
7694    var selected = normalizeText(selectedYear);
7695
7696    [].forEach.call(
7697      pillsRoot.querySelectorAll("[data-transparency-detail-year-pill]"),
7698      function (button) {
7699        var value = normalizeText(button.getAttribute("data-transparency-detail-year-pill"));
7700        var isActive = value === selected;
7701
7702        button.classList.toggle("is-active", isActive);
7703        button.setAttribute("aria-pressed", isActive ? "true" : "false");
7704      }
7705    );
7706  }
7707
7708  function getDetailResourceItems(detailRoot) {
7709    if (!detailRoot) return [];
7710
7711    return [].slice
7712      .call(detailRoot.querySelectorAll(RESOURCE_ITEM_SELECTOR))
7713      .filter(function (item) {
7714        return (
7715          !item.matches("[data-transparency-detail-filter], .enisa-recursos-detalle__empty") &&
7716          item.getAttribute("data-transparency-detail-filterable") === "true"
7717        );
7718      });
7719  }
7720
7721  function createDetailPagination(detailRoot) {
7722    if (!detailRoot) return null;
7723
7724    var existing = detailRoot.querySelector("[data-transparency-detail-pagination]");
7725    if (existing) return existing;
7726
7727    var list = detailRoot.querySelector(RESOURCE_LIST_SELECTOR);
7728    if (!list) return null;
7729
7730    var pagination = document.createElement("nav");
7731    pagination.className = "enisa-recursos-detalle__pagination enisa-news-filters";
7732    pagination.setAttribute("data-transparency-detail-pagination", "");
7733    pagination.setAttribute("aria-label", "Paginación de documentos");
7734
7735    list.parentNode.insertBefore(pagination, list.nextSibling);
7736
7737    detailRoot.enisaTransparencyPagination = {
7738      root: pagination,
7739      currentPage: 1,
7740      pageSize: DETAIL_PAGE_SIZE,
7741      focusPageAfterRender: false
7742    };
7743
7744    pagination.addEventListener("click", function (event) {
7745      var button = event.target.closest("[data-transparency-detail-page]");
7746      if (!button || !pagination.contains(button)) return;
7747
7748      var nextPage = parseInt(button.getAttribute("data-transparency-detail-page"), 10);
7749      if (isNaN(nextPage)) return;
7750
7751      detailRoot.enisaTransparencyPagination.currentPage = nextPage;
7752      detailRoot.enisaTransparencyPagination.focusPageAfterRender = true;
7753      updateDetailPagination(detailRoot);
7754    });
7755
7756    return pagination;
7757  }
7758
7759  function setPaginationButtonState(button, isCurrent) {
7760    if (!button) return;
7761
7762    button.classList.toggle("is-active", isCurrent);
7763    button.setAttribute("aria-current", isCurrent ? "page" : "false");
7764    button.setAttribute("aria-label", isCurrent ? "Página " + button.textContent + ", página actual" : "Ir a la página " + button.textContent);
7765  }
7766
7767  function updateSectionHeadingsVisibility(detailRoot) {
7768    if (!detailRoot) return;
7769
7770    var headings = [].slice.call(
7771      detailRoot.querySelectorAll(".enisa-recursos-detalle__item--heading")
7772    );
7773
7774    headings.forEach(function (heading) {
7775      var node = heading.nextElementSibling;
7776      var hasVisibleItem = false;
7777
7778      while (node && !node.classList.contains("enisa-recursos-detalle__item--heading")) {
7779        if (
7780          node.classList.contains("enisa-recursos-detalle__item") &&
7781          !node.matches(".enisa-recursos-detalle__empty") &&
7782          node.hidden === false
7783        ) {
7784          hasVisibleItem = true;
7785          break;
7786        }
7787
7788        node = node.nextElementSibling;
7789      }
7790
7791      heading.hidden = !hasVisibleItem;
7792    });
7793  }
7794
7795  function renderDetailPaginationControls(controller, pages) {
7796    if (!controller || !controller.root) return;
7797
7798    var root = controller.root;
7799    root.innerHTML = "";
7800
7801    if (pages <= 1) {
7802      root.hidden = true;
7803      controller.focusPageAfterRender = false;
7804      return;
7805    }
7806
7807    root.hidden = false;
7808
7809    for (var i = 1; i <= pages; i += 1) {
7810      var button = document.createElement("button");
7811      button.type = "button";
7812      button.className = "enisa-news-filters__pill";
7813      button.setAttribute("data-transparency-detail-page", String(i));
7814      button.setAttribute("data-color-index", String(getColorSequenceIndex(i)));
7815      button.textContent = String(i);
7816      setPaginationButtonState(button, i === controller.currentPage);
7817      root.appendChild(button);
7818    }
7819
7820    if (controller.focusPageAfterRender) {
7821      var focusTarget = root.querySelector(
7822        "[data-transparency-detail-page='" + controller.currentPage + "']"
7823      );
7824
7825      controller.focusPageAfterRender = false;
7826
7827      if (
7828        focusTarget &&
7829        window.ENISA &&
7830        window.ENISA.utils &&
7831        typeof window.ENISA.utils.focusWithoutScroll === "function"
7832      ) {
7833        window.ENISA.utils.focusWithoutScroll(focusTarget);
7834      } else if (focusTarget && typeof focusTarget.focus === "function") {
7835        safeFocus(focusTarget);
7836      }
7837    }
7838  }
7839
7840  function updateDetailPagination(detailRoot) {
7841    if (!detailRoot || !detailRoot.enisaTransparencyPagination) return;
7842
7843    var controller = detailRoot.enisaTransparencyPagination;
7844    var items = getDetailResourceItems(detailRoot);
7845    var availableItems = items.filter(function (item) {
7846      return item.getAttribute("data-transparency-filter-hidden") !== "true";
7847    });
7848
7849    var pages = Math.max(1, Math.ceil(availableItems.length / controller.pageSize));
7850
7851    if (controller.currentPage > pages) controller.currentPage = pages;
7852    if (controller.currentPage < 1) controller.currentPage = 1;
7853
7854    var start = (controller.currentPage - 1) * controller.pageSize;
7855    var end = start + controller.pageSize;
7856    var activeItem = document.activeElement && document.activeElement.closest
7857      ? document.activeElement.closest(RESOURCE_ITEM_SELECTOR)
7858      : null;
7859
7860    if (
7861      activeItem &&
7862      items.indexOf(activeItem) > -1 &&
7863      availableItems.indexOf(activeItem) !== -1 &&
7864      (availableItems.indexOf(activeItem) < start || availableItems.indexOf(activeItem) >= end)
7865    ) {
7866      controller.focusPageAfterRender = true;
7867    }
7868
7869    items.forEach(function (item) {
7870      var isFilteredHidden = item.getAttribute("data-transparency-filter-hidden") === "true";
7871      var availableIndex = availableItems.indexOf(item);
7872      var isOnPage = availableIndex >= start && availableIndex < end;
7873
7874      item.hidden = isFilteredHidden || !isOnPage;
7875    });
7876
7877    renderDetailPaginationControls(controller, pages);
7878    updateSectionHeadingsVisibility(detailRoot);
7879  }
7880
7881  function initDetailPagination(detailRoot) {
7882    if (!detailRoot) return;
7883
7884    var items = getDetailResourceItems(detailRoot);
7885    if (!items.length) return;
7886
7887    items.forEach(function (item) {
7888      if (!item.hasAttribute("data-transparency-filter-hidden")) {
7889        item.setAttribute("data-transparency-filter-hidden", "false");
7890      }
7891    });
7892
7893    createDetailPagination(detailRoot);
7894    updateDetailPagination(detailRoot);
7895  }
7896
7897  function initTransparencyResourceDetail(detailRoot) {
7898    if (!detailRoot || detailRoot.getAttribute(INIT_DETAIL_ATTR) === "true") return;
7899
7900    var list = detailRoot.querySelector(RESOURCE_LIST_SELECTOR);
7901    if (!list) return;
7902
7903    var allItems = [].slice
7904      .call(list.querySelectorAll(RESOURCE_ITEM_SELECTOR))
7905      .filter(function (item) {
7906        return !item.matches("[data-transparency-detail-filter], .enisa-recursos-detalle__empty");
7907      });
7908
7909    if (!allItems.length) return;
7910
7911    detailRoot.setAttribute(INIT_DETAIL_ATTR, "true");
7912
7913    var items = allItems.filter(function (item) {
7914      return item.getAttribute("data-transparency-detail-filterable") === "true";
7915    });
7916
7917    if (!items.length) {
7918      initDetailPagination(detailRoot);
7919      normalizeVisibleDocumentLabels(detailRoot);
7920      return;
7921    }
7922
7923    items.forEach(function (item) {
7924      var year = getItemYear(item);
7925
7926      if (year) {
7927        item.setAttribute("data-year", year);
7928      }
7929    });
7930
7931    var sortedItems = sortItemsByDateDesc(items);
7932
7933    sortedItems.forEach(function (item) {
7934      list.appendChild(item);
7935    });
7936
7937    initDetailPagination(detailRoot);
7938
7939    var years = collectYears(sortedItems);
7940    var pillsRoot = createDetailYearPills(detailRoot, years);
7941    var currentYear = "";
7942    var status = ensureStatus(detailRoot, "transparency-detail");
7943
7944    function getEmptyState() {
7945      var empty = detailRoot.querySelector("[data-transparency-detail-empty]");
7946
7947      if (empty) return empty;
7948
7949      empty = document.createElement("div");
7950      empty.className = "enisa-recursos-detalle__empty";
7951      empty.setAttribute("data-transparency-detail-empty", "");
7952      empty.hidden = true;
7953
7954      var emptyText = document.createElement("p");
7955      emptyText.className = "enisa-recursos-detalle__empty-text";
7956      emptyText.textContent = "No hay documentos para el año seleccionado.";
7957
7958      empty.appendChild(emptyText);
7959      list.appendChild(empty);
7960
7961      return empty;
7962    }
7963
7964    function applyYearFilter(selectedYear) {
7965      currentYear = normalizeText(selectedYear);
7966      var visibleCount = 0;
7967
7968      sortedItems.forEach(function (item) {
7969        var itemYear = getItemYear(item);
7970        var isVisible = currentYear === "" || currentYear === itemYear;
7971
7972        item.setAttribute("data-transparency-filter-hidden", isVisible ? "false" : "true");
7973        item.hidden = !isVisible;
7974
7975        if (isVisible) {
7976          visibleCount += 1;
7977        }
7978      });
7979
7980      setPillsState(pillsRoot, currentYear);
7981
7982      var empty = getEmptyState();
7983      empty.hidden = visibleCount > 0;
7984
7985      updateDetailPagination(detailRoot);
7986      announce(status, visibleCount + (visibleCount === 1 ? " documento visible." : " documentos visibles."));
7987    }
7988
7989    if (pillsRoot) {
7990      pillsRoot.addEventListener("click", function (event) {
7991        var button = event.target.closest("[data-transparency-detail-year-pill]");
7992        if (!button || !pillsRoot.contains(button)) return;
7993
7994        applyYearFilter(button.getAttribute("data-transparency-detail-year-pill"));
7995      });
7996
7997      pillsRoot.addEventListener("keydown", function (event) {
7998        var currentButton = event.target.closest("[data-transparency-detail-year-pill]");
7999        if (!currentButton) return;
8000
8001        var buttons = [].slice.call(
8002          pillsRoot.querySelectorAll("[data-transparency-detail-year-pill]")
8003        );
8004
8005        var currentIndex = buttons.indexOf(currentButton);
8006        var nextIndex = currentIndex;
8007
8008        if (event.key === "ArrowRight" || event.key === "ArrowDown") {
8009          nextIndex = (currentIndex + 1) % buttons.length;
8010        } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
8011          nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
8012        } else if (event.key === "Home") {
8013          nextIndex = 0;
8014        } else if (event.key === "End") {
8015          nextIndex = buttons.length - 1;
8016        } else {
8017          return;
8018        }
8019
8020        event.preventDefault();
8021
8022        if (buttons[nextIndex]) {
8023          safeFocus(buttons[nextIndex]);
8024        }
8025      });
8026    }
8027
8028    normalizeVisibleDocumentLabels(detailRoot);
8029    applyYearFilter(currentYear);
8030  }
8031
8032  function markLinkAsNewTab(link) {
8033    if (!link) return;
8034
8035    link.setAttribute("target", "_blank");
8036    link.setAttribute("rel", "noopener noreferrer");
8037
8038    var currentLabel = normalizeText(link.getAttribute("aria-label"));
8039    var suffix = "se abre en una nueva ventana";
8040
8041    if (currentLabel) {
8042      if (currentLabel.toLowerCase().indexOf(suffix) === -1) {
8043        link.setAttribute("aria-label", currentLabel + " (" + suffix + ")");
8044      }
8045
8046      return;
8047    }
8048
8049    var text = getTextContent(link);
8050
8051    if (text) {
8052      link.setAttribute("aria-label", text + " (" + suffix + ")");
8053    }
8054  }
8055
8056  function initDocumentLinks(scope) {
8057    var links = scope.querySelectorAll(
8058      ".enisa-transparencia-documents a[href], " +
8059        ".enisa-transparencia-table a[href], " +
8060        ".enisa-recursos-detalle--transparencia .enisa-recursos-detalle__download[href]"
8061    );
8062
8063    [].forEach.call(links, markLinkAsNewTab);
8064  }
8065
8066  function formatBytes(bytes) {
8067    if (!bytes || isNaN(bytes) || bytes <= 0) return "-";
8068
8069    var units = ["B", "KB", "MB", "GB"];
8070    var size = bytes;
8071    var unitIndex = 0;
8072
8073    while (size >= 1024 && unitIndex < units.length - 1) {
8074      size = size / 1024;
8075      unitIndex += 1;
8076    }
8077
8078    var decimals = unitIndex === 0 ? 0 : size >= 10 ? 1 : 2;
8079
8080    return size.toFixed(decimals).replace(".", ",") + " " + units[unitIndex];
8081  }
8082
8083  function canResolveDocumentSize(url) {
8084    if (!url) return false;
8085
8086    var normalizedUrl = url.toString().trim().toLowerCase();
8087
8088    if (!normalizedUrl) return false;
8089    if (normalizedUrl.indexOf("mailto:") === 0) return false;
8090    if (normalizedUrl.indexOf("tel:") === 0) return false;
8091
8092    return (
8093      normalizedUrl.indexOf("/") === 0 ||
8094      normalizedUrl.indexOf(window.location.origin.toLowerCase()) === 0
8095    );
8096  }
8097
8098  function resolveDocumentSize(sizeNode) {
8099    if (!sizeNode || sizeNode.getAttribute("data-document-size-init") === "true") return;
8100
8101    var url = normalizeText(sizeNode.getAttribute("data-document-size-url"));
8102
8103    if (!canResolveDocumentSize(url)) return;
8104
8105    sizeNode.setAttribute("data-document-size-init", "true");
8106
8107    fetch(url, {
8108      method: "HEAD",
8109      credentials: "same-origin"
8110    })
8111      .then(function (response) {
8112        if (!response || !response.ok) return null;
8113
8114        var length = response.headers.get("content-length");
8115        var parsedLength = length ? parseInt(length, 10) : 0;
8116
8117        return parsedLength > 0 ? parsedLength : null;
8118      })
8119      .then(function (bytes) {
8120        if (!bytes) return;
8121
8122        sizeNode.textContent = formatBytes(bytes);
8123
8124        syncTableCards(document);
8125      })
8126      .catch(function () {
8127        sizeNode.textContent = "-";
8128      });
8129  }
8130
8131  function initDocumentSizes(scope) {
8132    var sizeNodes = scope.querySelectorAll("[data-document-size]");
8133
8134    [].forEach.call(sizeNodes, resolveDocumentSize);
8135  }
8136
8137  function init() {
8138    var tables = document.querySelectorAll(TABLE_SELECTOR);
8139    [].forEach.call(tables, initTransparencyTable);
8140
8141    var details = document.querySelectorAll(RESOURCE_DETAIL_SELECTOR);
8142    [].forEach.call(details, initTransparencyResourceDetail);
8143
8144    normalizeVisibleDocumentLabels(document);
8145    initDocumentLinks(document);
8146    initDocumentSizes(document);
8147  }
8148
8149  if (document.readyState === "loading") {
8150    document.addEventListener("DOMContentLoaded", init);
8151  } else {
8152    init();
8153  }
8154})();
8155
8156/*
8157  ENISA | JS Module
8158  Modulo: 20-accordion
8159  Descripcion: Acordeones fluidos, accesibles y estables para Power Pages.
8160*/
8161(function () {
8162  'use strict';
8163
8164  var INIT_FLAG = 'data-accord-init';
8165  var FAQS_INIT_FLAG = 'data-faqs-init';
8166  var FAQ_TRIGGER_SELECTOR = '[data-faq-btn], [data-faq-trigger]';
8167  var FIN_TRIGGER_SELECTOR = '[data-fin-accord-btn]';
8168  var DEFAULT_DURATION = '0.42s';
8169  var DEFAULT_DURATION_MS = 420;
8170  var prefersReducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
8171
8172  function toArray(list) {
8173    return Array.prototype.slice.call(list || []);
8174  }
8175
8176  function debounce(fn, ms) {
8177    var timer;
8178
8179    return function () {
8180      var args = arguments;
8181      var context = this;
8182
8183      clearTimeout(timer);
8184
8185      timer = setTimeout(function () {
8186        fn.apply(context, args);
8187      }, ms || 150);
8188    };
8189  }
8190
8191  function parseDurationToMs(rawDuration) {
8192    var value = (rawDuration || '').toString().trim();
8193
8194    if (!value) return DEFAULT_DURATION_MS;
8195    if (value.indexOf('ms') > -1) return parseFloat(value) || DEFAULT_DURATION_MS;
8196    if (value.indexOf('s') > -1) return (parseFloat(value) || 0.42) * 1000;
8197
8198    return parseFloat(value) || DEFAULT_DURATION_MS;
8199  }
8200
8201  function getPanelDuration(panel) {
8202    if (prefersReducedMotion) return '0.001s';
8203    if (!panel || !window.getComputedStyle) return DEFAULT_DURATION;
8204
8205    var fromCss = window.getComputedStyle(panel).getPropertyValue('--enisa-accordion-duration');
8206
8207    return (fromCss || '').trim() || DEFAULT_DURATION;
8208  }
8209
8210  function getPanelDurationMs(panel) {
8211    return parseDurationToMs(getPanelDuration(panel));
8212  }
8213
8214  function resolvePanel(panelId, root) {
8215    if (!panelId) return null;
8216
8217    var panel = document.getElementById(panelId);
8218
8219    if (panel) return panel;
8220    if (!root || !root.querySelector) return null;
8221
8222    return root.querySelector('[id="' + panelId.replace(/"/g, '\\"') + '"]');
8223  }
8224
8225  function getControlledPanel(btn, root) {
8226    return btn ? resolvePanel(btn.getAttribute('aria-controls'), root || document) : null;
8227  }
8228
8229  function setPanelInteractive(panel, isInteractive) {
8230    if (
8231      window.ENISA &&
8232      window.ENISA.utils &&
8233      typeof window.ENISA.utils.setSubtreeTabbable === 'function'
8234    ) {
8235      window.ENISA.utils.setSubtreeTabbable(panel, isInteractive);
8236    }
8237  }
8238
8239  function safeFocus(element) {
8240    if (
8241      window.ENISA &&
8242      window.ENISA.utils &&
8243      typeof window.ENISA.utils.safeFocus === 'function'
8244    ) {
8245      return window.ENISA.utils.safeFocus(element);
8246    }
8247
8248    if (!element || typeof element.focus !== 'function') return false;
8249
8250    try {
8251      element.focus({ preventScroll: true });
8252    } catch (error) {
8253      element.focus();
8254    }
8255
8256    return true;
8257  }
8258
8259  function getItem(btn) {
8260    return btn ? (btn.closest('[data-fin-accord], [data-faq-item]') || btn.parentNode) : null;
8261  }
8262
8263  function setVisualState(btn, panel, isOpen) {
8264    if (!btn || !panel) return;
8265
8266    var item = getItem(btn);
8267
8268    btn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
8269    panel.setAttribute('aria-hidden', isOpen ? 'false' : 'true');
8270    panel.classList.toggle('is-open', !!isOpen);
8271
8272    if (item && item.classList) {
8273      item.classList.toggle('is-open', !!isOpen);
8274    }
8275  }
8276
8277  function clearPanelTimers(panel) {
8278    if (!panel) return;
8279
8280    if (panel._enisaEndHandler) {
8281      panel.removeEventListener('transitionend', panel._enisaEndHandler);
8282      panel._enisaEndHandler = null;
8283    }
8284
8285    if (panel._enisaSafetyTimer) {
8286      clearTimeout(panel._enisaSafetyTimer);
8287      panel._enisaSafetyTimer = null;
8288    }
8289
8290    if (panel._enisaResizeObserver) {
8291      panel._enisaResizeObserver.disconnect();
8292      panel._enisaResizeObserver = null;
8293    }
8294  }
8295
8296  function resetPanelInlineStyles(panel) {
8297    if (!panel) return;
8298
8299    panel.style.height = '';
8300    panel.style.overflow = '';
8301    panel.style.opacity = '';
8302    panel.style.transition = '';
8303    panel.removeAttribute('data-accord-animating');
8304  }
8305
8306  function stopPanelAnimation(panel) {
8307    if (!panel) return;
8308
8309    clearPanelTimers(panel);
8310    resetPanelInlineStyles(panel);
8311  }
8312
8313  function setupDynamicHeightSync(panel) {
8314    if (!panel || !('ResizeObserver' in window)) return;
8315
8316    var inner = panel.firstElementChild || panel;
8317
8318    panel._enisaResizeObserver = new ResizeObserver(function () {
8319      if (panel.getAttribute('data-accord-animating') !== 'opening') return;
8320
8321      panel.style.height = panel.scrollHeight + 'px';
8322    });
8323
8324    panel._enisaResizeObserver.observe(inner);
8325  }
8326
8327  function finishOpen(panel, callback) {
8328    clearPanelTimers(panel);
8329    resetPanelInlineStyles(panel);
8330    panel.classList.add('is-open');
8331
8332    if (typeof callback === 'function') callback();
8333  }
8334
8335  function finishClose(panel, callback) {
8336    clearPanelTimers(panel);
8337    panel.setAttribute('hidden', '');
8338    panel.classList.remove('is-open');
8339    resetPanelInlineStyles(panel);
8340
8341    toArray(panel.querySelectorAll('.enisa-accordion__grid')).forEac
8341h(function (grid) {
8342      grid.scrollLeft = 0;
8343    });
8344
8345    if (typeof callback === 'function') callback();
8346  }
8347
8348  function expandPanel(panel, callback) {
8349    if (!panel) return;
8350
8351    stopPanelAnimation(panel);
8352
8353    panel.removeAttribute('hidden');
8354    panel.classList.add('is-open');
8355
8356    if (prefersReducedMotion) {
8357      finishOpen(panel, callback);
8358      return;
8359    }
8360
8361    var duration = getPanelDuration(panel);
8362    var durationMs = getPanelDurationMs(panel);
8363    var targetHeight = panel.scrollHeight;
8364
8365    if (!targetHeight) {
8366      finishOpen(panel, callback);
8367      return;
8368    }
8369
8370    panel.setAttribute('data-accord-animating', 'opening');
8371    panel.style.overflow = 'hidden';
8372    panel.style.height = '0px';
8373    panel.style.opacity = '0';
8374
8375    setupDynamicHeightSync(panel);
8376
8377    panel.offsetHeight;
8378    panel.style.transition = 'height ' + duration + ' cubic-bezier(0.22, 1, 0.36, 1), opacity ' + duration + ' ease-out';
8379    panel.style.height = targetHeight + 'px';
8380    panel.style.opacity = '1';
8381
8382    function onEnd(event) {
8383      if (event && event.target !== panel) return;
8384      if (event && event.propertyName !== 'height') return;
8385
8386      finishOpen(panel, callback);
8387    }
8388
8389    panel._enisaEndHandler = onEnd;
8390    panel.addEventListener('transitionend', onEnd);
8391
8392    panel._enisaSafetyTimer = setTimeout(function () {
8393      finishOpen(panel, callback);
8394    }, durationMs + 90);
8395  }
8396
8397  function collapsePanel(panel, callback) {
8398    if (!panel) return;
8399
8400    stopPanelAnimation(panel);
8401
8402    if (prefersReducedMotion || panel.hasAttribute('hidden')) {
8403      finishClose(panel, callback);
8404      return;
8405    }
8406
8407    var duration = getPanelDuration(panel);
8408    var durationMs = getPanelDurationMs(panel);
8409    var startHeight = Math.max(panel.getBoundingClientRect().height || 0, panel.scrollHeight || 0);
8410
8411    if (!startHeight) {
8412      finishClose(panel, callback);
8413      return;
8414    }
8415
8416    panel.setAttribute('data-accord-animating', 'closing');
8417    panel.style.overflow = 'hidden';
8418    panel.style.height = startHeight + 'px';
8419    panel.style.opacity = '1';
8420
8421    panel.offsetHeight;
8422    panel.style.transition = 'height ' + duration + ' cubic-bezier(0.22, 1, 0.36, 1), opacity ' + duration + ' ease-in';
8423    panel.style.height = '0px';
8424    panel.style.opacity = '0';
8425
8426    function onEnd(event) {
8427      if (event && event.target !== panel) return;
8428      if (event && event.propertyName !== 'height') return;
8429
8430      finishClose(panel, callback);
8431    }
8432
8433    panel._enisaEndHandler = onEnd;
8434    panel.addEventListener('transitionend', onEnd);
8435
8436    panel._enisaSafetyTimer = setTimeout(function () {
8437      finishClose(panel, callback);
8438    }, durationMs + 90);
8439  }
8440
8441  function closeButton(btn, root, callback) {
8442    var panel = getControlledPanel(btn, root);
8443
8444    if (!panel || btn.getAttribute('aria-expanded') !== 'true') {
8445      if (typeof callback === 'function') callback();
8446      return;
8447    }
8448
8449    if (
8450      window.ENISA &&
8451      window.ENISA.utils &&
8452      typeof window.ENISA.utils.focusIfActiveInside === 'function'
8453    ) {
8454      window.ENISA.utils.focusIfActiveInside(panel, btn);
8455    } else if (panel.contains(document.activeElement) && typeof btn.focus === 'function') {
8456      safeFocus(btn);
8457    }
8458
8459    setVisualState(btn, panel, false);
8460    setPanelInteractive(panel, false);
8461    collapsePanel(panel, callback);
8462  }
8463
8464  function openButton(btn, root) {
8465    var panel = getControlledPanel(btn, root);
8466
8467    if (!panel || btn.getAttribute('aria-expanded') === 'true') return;
8468
8469    initAccordionGridCarousels(panel);
8470    setVisualState(btn, panel, true);
8471    setPanelInteractive(panel, true);
8472    expandPanel(panel);
8473  }
8474
8475  var BREAKPOINT_MOBILE = 767.98;
8476  var BREAKPOINT_TABLET = 1023.98;
8477
8478  function getCurrentLayout() {
8479    var w = window.innerWidth || document.documentElement.clientWidth || 0;
8480
8481    if (w <= BREAKPOINT_MOBILE) return 'mobile';
8482    if (w <= BREAKPOINT_TABLET) return 'tablet';
8483
8484    return 'desktop';
8485  }
8486
8487  function applyBalancedLayout(container, itemSelector, halfClass, fullClass, thirdClass) {
8488    if (!container) return;
8489
8490    var items = toArray(container.querySelectorAll(itemSelector));
8491    var layout = getCurrentLayout();
8492    var n = items.length;
8493    var i;
8494
8495    items.forEach(function (item) {
8496      item.classList.remove(halfClass, fullClass, thirdClass);
8497    });
8498
8499    if (!n || layout === 'mobile') return;
8500
8501    if (n === 1) {
8502      items[0].classList.add(fullClass);
8503      return;
8504    }
8505
8506    if (n === 2) {
8507      items[0].classList.add(halfClass);
8508      items[1].classList.add(halfClass);
8509      return;
8510    }
8511
8512    if (layout === 'tablet') {
8513      if (n === 3) {
8514        for (i = 0; i < n; i += 1) items[i].classList.add(thirdClass);
8515        return;
8516      }
8517
8518      if (n % 2 !== 0) {
8519        items[n - 3].classList.add(thirdClass);
8520        items[n - 2].classList.add(thirdClass);
8521        items[n - 1].classList.add(thirdClass);
8522      }
8523
8524      return;
8525    }
8526
8527    if (n % 3 === 2) {
8528      items[n - 2].classList.add(halfClass);
8529      items[n - 1].classList.add(halfClass);
8530      return;
8531    }
8532
8533    if (n % 3 === 1) {
8534      if (n === 4) {
8535        for (i = 0; i < n; i += 1) items[i].classList.add(halfClass);
8536        return;
8537      }
8538
8539      items[n - 4].classList.add(halfClass);
8540      items[n - 3].classList.add(halfClass);
8541      items[n - 2].classList.add(halfClass);
8542      items[n - 1].classList.add(halfClass);
8543    }
8544  }
8545
8546  function balanceAccordionLayouts(scope) {
8547    toArray((scope || document).querySelectorAll('.enisa-accordion')).forEach(function (wrapper) {
8548      toArray(wrapper.querySelectorAll('.enisa-accordion__grid')).forEach(function (grid) {
8549        applyBalancedLayout(grid, '.enisa-accordion__card', 'is-layout-half', 'is-layout-full', 'is-layout-third');
8550      });
8551
8552      toArray(wrapper.querySelectorAll('.enisa-accordion__downloads')).forEach(function (downloads) {
8553        applyBalancedLayout(downloads, '.enisa-accordion__download-btn', 'is-layout-half', 'is-layout-full', 'is-layout-third');
8554      });
8555    });
8556  }
8557
8558  function initAccordionGridCarousels(panel) {
8559    if (!panel) return;
8560
8561    toArray(panel.querySelectorAll('.enisa-accordion__grid')).forEach(function (grid) {
8562      if (grid.getAttribute('data-accord-carousel-init') === 'true') return;
8563
8564      grid.setAttribute('data-accord-carousel-init', 'true');
8565
8566      var cards = toArray(grid.querySelectorAll('.enisa-accordion__card'));
8567
8568      if (cards.length < 2 || !grid.parentNode) return;
8569
8570      var controls = document.createElement('div');
8571      controls.className = 'enisa-editorial-row-controls';
8572      controls.setAttribute('role', 'group');
8573      controls.setAttribute('aria-label', 'Controles del carrusel');
8574
8575      var prevBtn = document.createElement('button');
8576      prevBtn.type = 'button';
8577      prevBtn.className = 'enisa-editorial-row-controls__btn is-prev';
8578      prevBtn.setAttribute('aria-label', 'Tarjeta anterior del acordeón');
8579      prevBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
8580
8581      var dotsEl = document.createElement('div');
8582      dotsEl.className = 'enisa-editorial-row-controls__dots';
8583      dotsEl.setAttribute('role', 'group');
8584      dotsEl.setAttribute('aria-label', 'Páginas del carrusel');
8585
8586      var nextBtn = document.createElement('button');
8587      nextBtn.type = 'button';
8588      nextBtn.className = 'enisa-editorial-row-controls__btn is-next';
8589      nextBtn.setAttribute('aria-label', 'Tarjeta siguiente del acordeón');
8590      nextBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
8591
8592      var dots = cards.map(function (_, i) {
8593        var dot = document.createElement('button');
8594
8595        dot.type = 'button';
8596        dot.className = 'enisa-editorial-row-controls__dot';
8597        dot.setAttribute('aria-label', 'Ir a tarjeta ' + (i + 1) + ' de ' + cards.length);
8598        dot.setAttribute('aria-current', i === 0 ? 'true' : 'false');
8599
8600        if (i === 0) dot.classList.add('is-active');
8601
8602        dotsEl.appendChild(dot);
8603
8604        return dot;
8605      });
8606
8607      controls.appendChild(prevBtn);
8608      controls.appendChild(dotsEl);
8609      controls.appendChild(nextBtn);
8610      grid.parentNode.insertBefore(controls, grid.nextSibling);
8611      controls.setAttribute('hidden', '');
8612
8613      function getActiveIndex() {
8614        var scrollLeft = grid.scrollLeft;
8615        var best = 0;
8616        var bestDist = Infinity;
8617
8618        cards.forEach(function (card, index) {
8619          var dist = Math.abs(card.offsetLeft - scrollLeft);
8620
8621          if (dist < bestDist) {
8622            bestDist = dist;
8623            best = index;
8624          }
8625        });
8626
8627        return best;
8628      }
8629
8630      function setActiveDot(index) {
8631        dots.forEach(function (dot, dotIndex) {
8632          var active = dotIndex === index;
8633
8634          dot.classList.toggle('is-active', active);
8635          dot.setAttribute('aria-current', active ? 'true' : 'false');
8636          dot.setAttribute('tabindex', active ? '0' : '-1');
8637        });
8638
8639        prevBtn.disabled = index === 0;
8640        nextBtn.disabled = index === cards.length - 1;
8641      }
8642
8643      function scrollToCard(index) {
8644        if (index < 0 || index >= cards.length) return;
8645
8646        grid.scrollTo({
8647          left: cards[index].offsetLeft,
8648          behavior: prefersReducedMotion ? 'auto' : 'smooth'
8649        });
8650
8651        setActiveDot(index);
8652      }
8653
8654      function shouldUseCarousel() {
8655        if ((window.innerWidth || 0) < 768) return true;
8656
8657        var styles = window.getComputedStyle ? window.getComputedStyle(grid) : null;
8658        var minCardWidth = styles ? parseFloat(styles.getPropertyValue('--enisa-accordion-carousel-min-card-w') || '268') : 268;
8659        var gap = styles ? parseFloat(styles.getPropertyValue('--enisa-accordion-grid-gap') || '16') : 16;
8660        var availableWidth = Math.floor(grid.getBoundingClientRect().width || grid.clientWidth || 0);
8661
8662        return availableWidth > 0 && ((minCardWidth * 2) + gap) > availableWidth + 1;
8663      }
8664
8665      function syncCarouselState() {
8666        var enabled = shouldUseCarousel();
8667
8668        grid.classList.toggle('is-carousel', enabled);
8669
8670        if (enabled) {
8671          controls.removeAttribute('hidden');
8672
8673          toArray(controls.querySelectorAll('button')).forEach(function (button) {
8674            button.removeAttribute('tabindex');
8675          });
8676
8677          setActiveDot(getActiveIndex());
8678          return;
8679        }
8680
8681        grid.scrollLeft = 0;
8682        dotsEl.scrollLeft = 0;
8683        controls.setAttribute('hidden', '');
8684
8685        toArray(controls.querySelectorAll('button')).forEach(function (button) {
8686          button.setAttribute('tabindex', '-1');
8687        });
8688
8689        setActiveDot(0);
8690      }
8691
8692      dots.forEach(function (dot, index) {
8693        dot.addEventListener('click', function () {
8694          scrollToCard(index);
8695        });
8696
8697        dot.addEventListener('keydown', function (event) {
8698          var nextIndex = index;
8699
8700          switch (event.key) {
8701            case 'ArrowRight':
8702            case 'ArrowDown':
8703              nextIndex = (index + 1) % dots.length;
8704              break;
8705            case 'ArrowLeft':
8706            case 'ArrowUp':
8707              nextIndex = (index - 1 + dots.length) % dots.length;
8708              break;
8709            case 'Home':
8710              nextIndex = 0;
8711              break;
8712            case 'End':
8713              nextIndex = dots.length - 1;
8714              break;
8715            default:
8716              return;
8717          }
8718
8719          event.preventDefault();
8720          scrollToCard(nextIndex);
8721          safeFocus(dots[nextIndex]);
8722        });
8723      });
8724
8725      prevBtn.addEventListener('click', function () {
8726        scrollToCard(getActiveIndex() - 1);
8727      });
8728
8729      nextBtn.addEventListener('click', function () {
8730        scrollToCard(getActiveIndex() + 1);
8731      });
8732
8733      var scrollTimer = null;
8734
8735      grid.addEventListener('scroll', function () {
8736        if (!grid.classList.contains('is-carousel')) return;
8737
8738        clearTimeout(scrollTimer);
8739
8740        scrollTimer = setTimeout(function () {
8741          setActiveDot(getActiveIndex());
8742        }, 80);
8743      }, { passive: true });
8744
8745      syncCarouselState();
8746
8747      window.addEventListener('resize', debounce(syncCarouselState, 150));
8748    });
8749  }
8750
8751  function initFinAccordions(scope) {
8752    toArray((scope || document).querySelectorAll('.enisa-accordion')).forEach(function (wrapper) {
8753      var buttons = toArray(wrapper.querySelectorAll(FIN_TRIGGER_SELECTOR));
8754
8755      if (!buttons.length) return;
8756
8757      buttons.forEach(function (btn) {
8758        if (btn.getAttribute(INIT_FLAG) === 'true') return;
8759
8760        btn.setAttribute(INIT_FLAG, 'true');
8761
8762        var panel = getControlledPanel(btn, wrapper);
8763
8764        if (!panel) return;
8765
8766        var isOpen = btn.getAttribute('aria-expanded') === 'true' && !panel.hasAttribute('hidden');
8767
8768        setVisualState(btn, panel, isOpen);
8769        setPanelInteractive(panel, isOpen);
8770
8771        btn.addEventListener('click', function () {
8772          var isOpen = btn.getAttribute('aria-expanded') === 'true';
8773
8774          if (isOpen) {
8775            closeButton(btn, wrapper);
8776            return;
8777          }
8778
8779          openButton(btn, wrapper);
8780        });
8781      });
8782
8783      buttons.forEach(function (btn) {
8784        var panel = getControlledPanel(btn, wrapper);
8785
8786        if (!panel) return;
8787
8788        if (btn.getAttribute('aria-expanded') === 'true' && !panel.hasAttribute('hidden')) {
8789          panel.removeAttribute('hidden');
8790          setVisualState(btn, panel, true);
8791          setPanelInteractive(panel, true);
8792          initAccordionGridCarousels(panel);
8793        }
8794      });
8795    });
8796  }
8797
8798  function getFaqRoot(btn) {
8799    return btn
8800      ? (btn.closest('[data-cmp-faqs], [data-cmp-faq], .enisa-faqs, .enisa-faq') || document)
8801      : document;
8802  }
8803
8804  function closeFaqItem(itemOrBtn, root) {
8805    var btn = itemOrBtn && itemOrBtn.matches && itemOrBtn.matches(FAQ_TRIGGER_SELECTOR)
8806      ? itemOrBtn
8807      : itemOrBtn && itemOrBtn.querySelector
8808        ? itemOrBtn.querySelector(FAQ_TRIGGER_SELECTOR)
8809        : null;
8810
8811    if (!btn) return;
8812
8813    closeButton(btn, root || getFaqRoot(btn));
8814  }
8815
8816  function initFaqAccordions(scope) {
8817    var buttons = toArray((scope || document).querySelectorAll(FAQ_TRIGGER_SELECTOR));
8818
8819    buttons.forEach(function (btn) {
8820      if (btn.getAttribute(INIT_FLAG) === 'true') return;
8821
8822      btn.setAttribute(INIT_FLAG, 'true');
8823
8824      var root = getFaqRoot(btn);
8825      var panel = getControlledPanel(btn, root);
8826
8827      if (!panel) return;
8828
8829      var isOpen = btn.getAttribute('aria-expanded') === 'true' && !panel.hasAttribute('hidden');
8830
8831      setVisualState(btn, panel, isOpen);
8832      setPanelInteractive(panel, isOpen);
8833
8834      btn.addEventListener('click', function () {
8835        var isOpen = btn.getAttribute('aria-expanded') === 'true';
8836
8837        if (isOpen) {
8838          closeButton(btn, root);
8839          return;
8840        }
8841
8842        openButton(btn, root);
8843      });
8844    });
8845  }
8846
8847  function normalizeText(value) {
8848    return (value || '').toString().toLowerCase()
8849      .replace(/[áàäâ]/g, 'a')
8850      .replace(/[éèëê]/g, 'e')
8851      .replace(/[íìïî]/g, 'i')
8852      .replace(/[óòöô]/g, 'o')
8853      .replace(/[úùüû]/g, 'u')
8854      .replace(/ñ/g, 'n')
8855      .replace(/\s+/g, ' ')
8856      .trim();
8857  }
8858
8859  function initFaqFilters(scope) {
8860    toArray((scope || document).querySelectorAll('[data-cmp-faqs], [data-cmp-faq]')).forEach(function (root) {
8861      if (root.getAttribute(FAQS_INIT_FLAG) === 'true') return;
8862
8863      root.setAttribute(FAQS_INIT_FLAG, 'true');
8864
8865      var searchInput = root.querySelector('[data-faq-search]');
8866      var clearBtn = root.querySelector('[data-search-clear]');
8867      var searchIcon = root.querySelector('[data-search-icon]');
8868      var categoryButtons = toArray(root.querySelectorAll('[data-filter]'));
8869      var items = toArray(root.querySelectorAll('[data-faq-item]'));
8870      var pagination = root.querySelector('[data-pagination]');
8871      var pageSize = parseInt(root.getAttribute('data-page-size'), 10) || 6;
8872      var activeCategory = normalizeText(root.getAttribute('data-default-category') || '');
8873      var searchTerm = '';
8874      var currentPage = 1;
8875      var hasRendered = false;
8876      var status = document.createElement('p');
8877
8878      status.className = 'enisa-sr-only';
8879      status.setAttribute('role', 'status');
8880      status.setAttribute('aria-live', 'polite');
8881      status.setAttribute('aria-atomic', 'true');
8882      root.appendChild(status);
8883
8884      if (!items.length) return;
8885
8886      var availableCategories = categoryButtons
8887        .map(function (btn) {
8888          return normalizeText(btn.getAttribute('data-filter') || '');
8889        })
8890        .filter(function (value, index, arr) {
8891          return value && arr.indexOf(value) === index;
8892        });
8893
8894      if (!activeCategory && availableCategories.indexOf('financiacion') > -1) activeCategory = 'financiacion';
8895      if (!activeCategory && availableCategories.length) activeCategory = availableCategories[0];
8896      if (activeCategory && availableCategories.length && availableCategories.indexOf(activeCategory) === -1) activeCategory = availableCategories[0];
8897
8898      function applyCategoryState() {
8899        categoryButtons.forEach(function (btn) {
8900          var key = normalizeText(btn.getAttribute('data-filter') || '');
8901          var isActive = !searchTerm && key === activeCategory;
8902
8903          btn.classList.toggle('is-active', isActive);
8904          btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
8905        });
8906      }
8907
8908      function getFilteredItems() {
8909        return items.filter(function (item) {
8910          var itemCategory = normalizeText(item.getAttribute('data-faq-category') || '');
8911          var question = normalizeText(item.getAttribute('data-faq-question') || item.textContent || '');
8912          var answerNode = item.querySelector('.enisa-faq__answer, .enisa-faqs__answer');
8913          var answerText = normalizeText(answerNode ? answerNode.textContent : '');
8914          var matchesCategory = searchTerm ? true : (!activeCategory || itemCategory === activeCategory);
8915          var matchesSearch = !searchTerm || question.indexOf(searchTerm) > -1 || answerText.indexOf(searchTerm) > -1;
8916
8917          return matchesCategory && matchesSearch;
8918        });
8919      }
8920
8921      function renderPagination(totalPages) {
8922        if (!pagination) return;
8923
8924        pagination.innerHTML = '';
8925
8926        if (totalPages <= 1) {
8927          pagination.setAttribute('hidden', '');
8928          return;
8929        }
8930
8931        pagination.removeAttribute('hidden');
8932
8933        var prevBtn = document.createElement('button');
8934        prevBtn.type = 'button';
8935        prevBtn.className = 'enisa-pagination__btn enisa-pagination__btn--arrow is-prev';
8936        prevBtn.setAttribute('aria-label', 'Página anterior');
8937        prevBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
8938        prevBtn.disabled = currentPage <= 1;
8939
8940        prevBtn.addEventListener('click', function () {
8941          if (currentPage <= 1) return;
8942
8943          currentPage -= 1;
8944          render({ type: 'prev' });
8945        });
8946
8947        pagination.appendChild(prevBtn);
8948
8949        var pageList = document.createElement('div');
8950        pageList.className = 'enisa-pagination__list';
8951        pageList.setAttribute('role', 'list');
8952
8953        for (var i = 1; i <= totalPages; i += 1) {
8954          var pageBtn = document.createElement('button');
8955
8956          pageBtn.type = 'button';
8957          pageBtn.className = 'enisa-pagination__btn' + (i === currentPage ? ' is-active' : '');
8958          pageBtn.textContent = String(i);
8959          pageBtn.setAttribute('aria-label', 'Página ' + i);
8960          if (i === currentPage) {
8961            pageBtn.setAttribute('aria-current', 'page');
8962          }
8963
8964          (function (pageNumber) {
8965            pageBtn.addEventListener('click', function () {
8966              currentPage = pageNumber;
8967              render({ type: 'page', page: pageNumber });
8968            });
8969          })(i);
8970
8971          pageList.appendChild(pageBtn);
8972        }
8973
8974        pagination.appendChild(pageList);
8975
8976        var nextBtn = document.createElement('button');
8977        nextBtn.type = 'button';
8978        nextBtn.className = 'enisa-pagination__btn enisa-pagination__btn--arrow is-next';
8979        nextBtn.setAttribute('aria-label', 'Página siguiente');
8980        nextBtn.innerHTML = '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
8981        nextBtn.disabled = currentPage >= totalPages;
8982
8983        nextBtn.addEventListener('click', function () {
8984          if (currentPage >= totalPages) return;
8985
8986          currentPage += 1;
8987          render({ type: 'next' });
8988        });
8989
8990        pagination.appendChild(nextBtn);
8991      }
8992
8993      function getPaginationFocusTarget(focusTarget) {
8994        if (!focusTarget || !pagination) return null;
8995
8996        if (focusTarget.type === 'prev') {
8997          return pagination.querySelector('.enisa-pagination__btn--arrow.is-prev');
8998        }
8999
9000        if (focusTarget.type === 'next') {
9001          return pagination.querySelector('.enisa-pagination__btn--arrow.is-next');
9002        }
9003
9004        if (focusTarget.type === 'page') {
9005          return toArray(pagination.querySelectorAll('.enisa-pagination__list .enisa-pagination__btn')).filter(function (button) {
9006            return button.textContent === String(focusTarget.page);
9007          })[0] || null;
9008        }
9009
9010        return null;
9011      }
9012
9013      function render(focusTarget) {
9014        var filtered = getFilteredItems();
9015        var totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
9016        var activeItem = document.activeElement && document.activeElement.closest
9017          ? document.activeElement.closest('[data-faq-item]')
9018          : null;
9019        var start;
9020        var end;
9021        var visibleSlice;
9022
9023        if (currentPage > totalPages) currentPage = totalPages;
9024        if (currentPage < 1) currentPage = 1;
9025
9026        start = (currentPage - 1) * pageSize;
9027        end = start + pageSize;
9028        visibleSlice = filtered.slice(start, end);
9029
9030        if (activeItem && visibleSlice.indexOf(activeItem) === -1) {
9031          var fallbackFocus = getPaginationFocusTarget(focusTarget) ||
9032            searchInput ||
9033            categoryButtons.filter(function (button) {
9034              return button.getAttribute('aria-pressed') === 'true';
9035            })[0] ||
9036            categoryButtons[0];
9037
9038          if (
9039            fallbackFocus &&
9040            window.ENISA &&
9041            window.ENISA.utils &&
9042            typeof window.ENISA.utils.focusWithoutScroll === 'function'
9043          ) {
9044            window.ENISA.utils.focusWithoutScroll(fallbackFocus);
9045          } else if (fallbackFocus && typeof fallbackFocus.focus === 'function') {
9046            safeFocus(fallbackFocus);
9047          }
9048        }
9049
9050        items.forEach(function (item) {
9051          var visible = visibleSlice.indexOf(item) > -1;
9052
9053          item.hidden = !visible;
9054
9055          if (!visible) {
9056            closeFaqItem(item, root);
9057          }
9058        });
9059
9060        renderPagination(totalPages);
9061        applyCategoryState();
9062
9063        if (hasRendered) {
9064          status.textContent = '';
9065          setTimeout(function () {
9066            status.textContent = filtered.length + (filtered.length === 1 ? ' pregunta disponible.' : ' preguntas disponibles.');
9067          }, 20);
9068        }
9069
9070        hasRendered = true;
9071
9072        if (focusTarget) {
9073          window.requestAnimationFrame(function () {
9074            var nextFocusTarget = getPaginationFocusTarget(focusTarget);
9075
9076            if (nextFocusTarget && typeof nextFocusTarget.focus === 'function' && document.contains(nextFocusTarget)) {
9077              safeFocus(nextFocusTarget);
9078            }
9079          });
9080        }
9081
9082        if (searchIcon) searchIcon.hidden = !!searchTerm;
9083
9084        if (clearBtn) {
9085          clearBtn.hidden = !searchTerm;
9086        }
9087      }
9088
9089      categoryButtons.forEach(function (btn) {
9090        btn.addEventListener('click', function () {
9091          activeCategory = normalizeText(btn.getAttribute('data-filter') || '');
9092          currentPage = 1;
9093          applyCategoryState();
9094          render();
9095        });
9096      });
9097
9098      if (searchInput) {
9099        searchInput.addEventListener('input', function () {
9100          searchTerm = normalizeText(searchInput.value || '');
9101          currentPage = 1;
9102          render();
9103        });
9104      }
9105
9106      if (clearBtn && searchInput) {
9107        clearBtn.addEventListener('click', function () {
9108        searchInput.value = '';
9109        searchTerm = '';
9110        currentPage = 1;
9111        safeFocus(searchInput);
9112        render();
9113      });
9114      }
9115
9116      applyCategoryState();
9117      render();
9118    });
9119  }
9120
9121  function init(scope) {
9122    var currentScope = scope || document;
9123
9124    balanceAccordionLayouts(currentScope);
9125    initFinAccordions(currentScope);
9126    initFaqAccordions(currentScope);
9127    initFaqFilters(currentScope);
9128  }
9129
9130  if (document.readyState === 'loading') {
9131    document.addEventListener('DOMContentLoaded', function () {
9132      init(document);
9133    });
9134  } else {
9135    init(document);
9136  }
9137
9138  window.addEventListener('resize', debounce(function () {
9139    balanceAccordionLayouts(document);
9140  }, 150));
9141
9142  if ('MutationObserver' in window) {
9143    var timer = null;
9144    var observer = new MutationObserver(function (mutations) {
9145      var shouldInit = mutations.some(function (mutation) {
9146        return toArray(mutation.addedNodes).some(function (node) {
9147          if (!node || node.nodeType !== 1) return false;
9148
9149          return (
9150            (node.matches &&
9151              (
9152                node.matches('.enisa-accordion, [data-cmp-faqs], [data-cmp-faq]') ||
9153                node.matches(FIN_TRIGGER_SELECTOR) ||
9154                node.matches(FAQ_TRIGGER_SELECTOR)
9155              )
9156            ) ||
9157            (node.querySelector &&
9158              (
9159                node.querySelector('.enisa-accordion, [data-cmp-faqs], [data-cmp-faq]') ||
9160                node.querySelector(FIN_TRIGGER_SELECTOR) ||
9161                node.querySelector(FAQ_TRIGGER_SELECTOR)
9162              )
9163            )
9164          );
9165        });
9166      });
9167
9168      if (!shouldInit) return;
9169
9170      clearTimeout(timer);
9171
9172      timer = setTimeout(function () {
9173        init(document);
9174      }, 150);
9175    });
9176
9177    observer.observe(document.body, {
9178      childList: true,
9179      subtree: true
9180    });
9181  }
9182})();
9183
9184/*
9185  ENISA | JS Module
9186  Modulo: 21-tables
9187  Descripcion: Convierte tablas semanticas en cards mobile con carrusel accesible.
9188*/
9189(function () {
9190  "use strict";
9191
9192  var TABLE_SELECTOR =
9193    "table[data-enisa-table], .enisa-prose table:not([data-enisa-table-disabled])";
9194
9195  var INIT_ATTR = "data-enisa-table-init";
9196  var MOBILE_BP =
9197    window.ENISA && window.ENISA.cardsCarousel
9198      ? window.ENISA.cardsCarousel.MOBILE_BP
9199      : 768;
9200
9201  function isMobileViewport() {
9202    if (window.matchMedia) {
9203      return window.matchMedia("(max-width: " + MOBILE_BP + "px)").matches;
9204    }
9205
9206    return window.innerWidth <= MOBILE_BP;
9207  }
9208
9209  function normalizeText(value) {
9210    return (value || "").toString().replace(/\s+/g, " ").trim();
9211  }
9212
9213  function getHeaders(table) {
9214    var headers = [].slice.call(table.querySelectorAll("thead th"));
9215
9216    return headers.map(function (header, index) {
9217      var text = normalizeText(header.textContent);
9218      return text || "Columna " + (index + 1);
9219    });
9220  }
9221
9222  function getTableLabel(table) {
9223    var caption = table.querySelector("caption");
9224    var captionText = caption ? normalizeText(caption.textContent) : "";
9225
9226    if (captionText) return captionText;
9227
9228    var ariaLabel = normalizeText(table.getAttribute("aria-label"));
9229    if (ariaLabel) return ariaLabel;
9230
9231    return "tabla";
9232  }
9233
9234  function hasInteractiveContent(cell) {
9235    return !!cell.querySelector("a[href], button, input, select, textarea");
9236  }
9237
9238  function isActionHeader(label) {
9239    var normalized = normalizeText(label).toLowerCase();
9240
9241    return (
9242      normalized === "acción" ||
9243      normalized === "accion" ||
9244      normalized === "descarga" ||
9245      normalized === "descargar" ||
9246      normalized === "enlace"
9247    );
9248  }
9249
9250  function isRowVisible(row) {
9251    if (!row) return false;
9252    if (row.hidden) return false;
9253    if (row.hasAttribute("hidden")) return false;
9254    if (row.style && row.style.display === "none") return false;
9255
9256    return true;
9257  }
9258
9259  function createControls(total, label) {
9260    var controls = document.createElement("div");
9261    controls.className = "enisa-editorial-row-controls";
9262    controls.setAttribute(
9263      "aria-label",
9264      label || "Controles del carrusel de tabla",
9265    );
9266
9267    var prevBtn = document.createElement("button");
9268    prevBtn.type = "button";
9269    prevBtn.className = "enisa-editorial-row-controls__btn is-prev";
9270    prevBtn.setAttribute("aria-label", "Fila anterior");
9271    prevBtn.innerHTML =
9272      '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
9273
9274    var dotsEl = document.createElement("div");
9275    dotsEl.className = "enisa-editorial-row-controls__dots";
9276    dotsEl.setAttribute("role", "tablist");
9277    dotsEl.setAttribute("aria-label", "Páginas del carrusel");
9278
9279    var nextBtn = document.createElement("button");
9280    nextBtn.type = "button";
9281    nextBtn.className = "enisa-editorial-row-controls__btn is-next";
9282    nextBtn.setAttribute("aria-label", "Fila siguiente");
9283    nextBtn.innerHTML =
9284      '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
9285
9286    for (var i = 0; i < total; i += 1) {
9287      var dot = document.createElement("button");
9288
9289      dot.type = "button";
9290      dot.className = "enisa-editorial-row-controls__dot";
9291      dot.setAttribute("role", "tab");
9292      dot.setAttribute("aria-label", "Ir a fila " + (i + 1));
9293      dot.setAttribute("aria-selected", i === 0 ? "true" : "false");
9294
9295      if (i === 0) {
9296        dot.classList.add("is-active");
9297      }
9298
9299      dotsEl.appendChild(dot);
9300    }
9301
9302    controls.appendChild(prevBtn);
9303    controls.appendChild(dotsEl);
9304    controls.appendChild(nextBtn);
9305
9306    return {
9307      root: controls,
9308      prevBtn: prevBtn,
9309      nextBtn: nextBtn,
9310      dots: [].slice.call(
9311        dotsEl.querySelectorAll(".enisa-editorial-row-controls__dot"),
9312      ),
9313    };
9314  }
9315
9316  function cloneCellContent(cell) {
9317    var fragment = document.createDocumentFragment();
9318
9319    [].slice.call(cell.childNodes).forEach(function (node) {
9320      fragment.appendChild(node.cloneNode(true));
9321    });
9322
9323    return fragment;
9324  }
9325
9326  function buildMobileCards(table, wrapper, viewport, track, rows, headers) {
9327    var tableLabel = getTableLabel(table);
9328
9329    track.innerHTML = "";
9330
9331    rows.forEach(function (row, rowIndex) {
9332      var cells = [].slice.call(row.children);
9333      var card = document.createElement("article");
9334
9335      card.className = "enisa-table-card";
9336      card.setAttribute("data-enisa-table-card", "");
9337      card.setAttribute(
9338        "aria-label",
9339        "Fila " + (rowIndex + 1) + " de " + tableLabel,
9340      );
9341
9342      cells.forEach(function (cell, cellIndex) {
9343        var labelText = headers[cellIndex] || "Columna " + (cellIndex + 1);
9344        var isAction = isActionHeader(labelText) || hasInteractiveContent(cell);
9345
9346        var item = document.createElement("div");
9347        item.className = "enisa-table-card__item";
9348
9349        if (cellIndex === 0) {
9350          item.classList.add("enisa-table-card__item--primary");
9351        }
9352
9353        if (isAction) {
9354          item.classList.add("enisa-table-card__item--action");
9355        }
9356
9357        var label = document.createElement("span");
9358        label.className = "enisa-table-card__label";
9359        label.textContent = labelText;
9360
9361        var value = document.createElement("div");
9362        value.className = "enisa-table-card__value";
9363        value.appendChild(cloneCellContent(cell));
9364
9365        if (!isAction) {
9366          item.appendChild(label);
9367        }
9368
9369        item.appendChild(value);
9370        card.appendChild(item);
9371      });
9372
9373      track.appendChild(card);
9374    });
9375
9376    wrapper.hidden = !isMobileViewport();
9377  }
9378
9379  function initTable(table) {
9380    if (!table || table.getAttribute(INIT_ATTR) === "true") return;
9381
9382    var headers = getHeaders(table);
9383    var rows = [].slice.call(table.querySelectorAll("tbody tr"));
9384
9385    if (!headers.length || !rows.length) return;
9386
9387    table.setAttribute(INIT_ATTR, "true");
9388    table.classList.add("enisa-table");
9389    table.classList.add("has-enisa-table-source");
9390
9391    if (table.parentElement) {
9392      table.parentElement.classList.add("has-enisa-table-cards");
9393    }
9394
9395    var wrapper = document.createElement("div");
9396    wrapper.className = "enisa-table-mobile";
9397    wrapper.setAttribute("data-enisa-table-mobile", "");
9398    wrapper.hidden = true;
9399
9400    var viewport = document.createElement("div");
9401    viewport.className = "enisa-table-mobile__viewport";
9402
9403    var track = document.createElement("div");
9404    track.className = "enisa-table-mobile__track";
9405
9406    viewport.appendChild(track);
9407    wrapper.appendChild(viewport);
9408    table.insertAdjacentElement("afterend", wrapper);
9409
9410    var controlsApi = null;
9411    var carouselApi = null;
9412
9413    function removeControls() {
9414      if (controlsApi && controlsApi.root && controlsApi.root.parentNode) {
9415        controlsApi.root.parentNode.removeChild(controlsApi.root);
9416      }
9417
9418      controlsApi = null;
9419      carouselApi = null;
9420    }
9421
9422    function getCards() {
9423      return [].slice.call(track.querySelectorAll("[data-enisa-table-card]"));
9424    }
9425
9426    function getVisibleCards() {
9427      var cards = getCards();
9428
9429      return cards.filter(function (card, index) {
9430        var row = rows[index];
9431        var visible = isRowVisible(row);
9432
9433        card.hidden = !visible;
9434
9435        return visible;
9436      });
9437    }
9438
9439    function rebuildControls() {
9440      removeControls();
9441
9442      if (!isMobileViewport()) {
9443        wrapper.hidden = true;
9444        return;
9445      }
9446
9447      var visibleCards = getVisibleCards();
9448
9449      wrapper.hidden = visibleCards.length === 0;
9450
9451      if (visibleCards.length < 2) return;
9452
9453      controlsApi = createControls(
9454        visibleCards.length,
9455        table.getAttribute("data-enisa-table-controls-label") ||
9456          "Controles del carrusel de tabla",
9457      );
9458
9459      wrapper.appendChild(controlsApi.root);
9460
9461      if (
9462        window.ENISA &&
9463        window.ENISA.utils &&
9464        typeof window.ENISA.utils.createScrollCarousel === "function"
9465      ) {
9466        carouselApi = window.ENISA.utils.createScrollCarousel({
9467          viewport: viewport,
9468          items: visibleCards,
9469          prevBtn: controlsApi.prevBtn,
9470          nextBtn: controlsApi.nextBtn,
9471          dots: controlsApi.dots,
9472          activeDotClass: "is-active",
9473        });
9474      }
9475    }
9476
9477    function sync() {
9478      if (!isMobileViewport()) {
9479        wrapper.hidden = true;
9480        removeControls();
9481        return;
9482      }
9483
9484      getVisibleCards();
9485      rebuildControls();
9486
9487      if (carouselApi && typeof carouselApi.sync === "function") {
9488        carouselApi.sync();
9489      }
9490    }
9491
9492    buildMobileCards(table, wrapper, viewport, track, rows, headers);
9493
9494    table.enisaTableSync = sync;
9495
9496    sync();
9497
9498    window.addEventListener("load", sync, { once: true });
9499
9500    if (
9501      window.ENISA &&
9502      window.ENISA.utils &&
9503      typeof window.ENISA.utils.debounce === "function"
9504    ) {
9505      window.addEventListener("resize", window.ENISA.utils.debounce(sync, 150));
9506    } else {
9507      window.addEventListener("resize", sync);
9508    }
9509  }
9510
9511  function syncTables(scope) {
9512    var root = scope || document;
9513    var tables = [];
9514
9515    if (root.tagName && root.tagName.toLowerCase() === "table") {
9516      tables = [root];
9517    } else {
9518      tables = [].slice.call(
9519        root.querySelectorAll(
9520          "table.has-enisa-table-source, table[data-enisa-table], .enisa-prose table:not([data-enisa-table-disabled])",
9521        ),
9522      );
9523    }
9524
9525    tables.forEach(function (table) {
9526      if (typeof table.enisaTableSync === "function") {
9527        table.enisaTableSync();
9528      }
9529    });
9530  }
9531
9532  function init(scope) {
9533    var root = scope || document;
9534    var tables = [].slice.call(root.querySelectorAll(TABLE_SELECTOR));
9535
9536    tables.forEach(initTable);
9537  }
9538
9539  window.ENISA = window.ENISA || {};
9540  window.ENISA.tables = window.ENISA.tables || {};
9541  window.ENISA.tables.init = init;
9542  window.ENISA.tables.sync = syncTables;
9543
9544  if (document.readyState === "loading") {
9545    document.addEventListener("DOMContentLoaded", function () {
9546      init(document);
9547    });
9548  } else {
9549    init(document);
9550  }
9551})();
9552
9553/*
9554  ENISA | JS Module
9555  Modulo: 22-search
9556  Descripcion: Busqueda global, ranking por relevancia, paginacion y resaltado.
9557  Nota:
9558  - No abre acordeones.
9559  - No activa tabs.
9560  - Restaura una sola vez el foco al contexto de resultados tras una busqueda iniciada por el usuario.
9561  - Conserva el foco en la paginacion tras cambiar de pagina.
9562  - No interpreta destinos.
9563  - La navegacion usa directamente el href definido en Dataverse.
9564*/
9565(function () {
9566  "use strict";
9567
9568  var SEARCH_FOCUS_INTENT_KEY = "enisa:global-search:focus-results";
9569  var STATUS_ANNOUNCEMENT_DELAY = 80;
9570
9571  function safeFocus(element, options) {
9572    var opts = options || {};
9573
9574    if (
9575      window.ENISA &&
9576      window.ENISA.utils &&
9577      typeof window.ENISA.utils.safeFocus === "function"
9578    ) {
9579      return window.ENISA.utils.safeFocus(element, opts);
9580    }
9581
9582    if (!element || typeof element.focus !== "function") return false;
9583
9584    try {
9585      if (opts.preventScroll === false) {
9586        element.focus();
9587      } else {
9588        element.focus({ preventScroll: true });
9589      }
9590    } catch (error) {
9591      element.focus();
9592    }
9593
9594    return true;
9595  }
9596
9597  function scrollToElement(element, options) {
9598    if (
9599      window.ENISA &&
9600      window.ENISA.utils &&
9601      typeof window.ENISA.utils.scrollToElement === "function"
9602    ) {
9603      return window.ENISA.utils.scrollToElement(element, options);
9604    }
9605
9606    if (!element || typeof element.scrollIntoView !== "function") {
9607      return false;
9608    }
9609
9610    try {
9611      element.scrollIntoView({
9612        block: options && options.block ? options.block : "start",
9613        behavior: options && options.behavior ? options.behavior : "smooth"
9614      });
9615    } catch (error) {
9616      element.scrollIntoView();
9617    }
9618
9619    return true;
9620  }
9621
9622  function getVisibleRange(state) {
9623    var total = state && state.items ? state.items.length : 0;
9624    var totalPages;
9625    var currentPage;
9626    var start;
9627    var end;
9628
9629    if (!total) {
9630      return {
9631        start: 0,
9632        end: 0,
9633        total: 0
9634      };
9635    }
9636
9637    totalPages = Math.max(1, Math.ceil(total / state.pageSize));
9638    currentPage = Math.min(Math.max(state.currentPage, 1), totalPages);
9639    start = (currentPage - 1) * state.pageSize + 1;
9640    end = Math.min(currentPage * state.pageSize, total);
9641
9642    return {
9643      start: start,
9644      end: end,
9645      total: total
9646    };
9647  }
9648
9649  function buildResultsMessage(total, term, state) {
9650    var safeTerm = normalizeSpaces(term);
9651    var range;
9652
9653    if (total === 0) {
9654      return "No se han encontrado resultados para “" + safeTerm + "”.";
9655    }
9656
9657    if (state && state.pageSize && total > state.pageSize) {
9658      range = getVisibleRange(state);
9659
9660      return (
9661        "Mostrando resultados " +
9662        range.start +
9663        "-" +
9664        range.end +
9665        " de " +
9666        range.total +
9667        " para “" +
9668        safeTerm +
9669        "”."
9670      );
9671    }
9672
9673    return (
9674      "Se han encontrado " +
9675      total +
9676      " resultado" +
9677      (total !== 1 ? "s" : "") +
9678      " para “" +
9679      safeTerm +
9680      "”."
9681    );
9682  }
9683
9684  function alignInitialHashTarget() {
9685    if (!window.location.hash) {
9686      return;
9687    }
9688
9689    var rawHash = window.location.hash.replace(/^#/, "");
9690
9691    if (!rawHash) {
9692      return;
9693    }
9694
9695    try {
9696      rawHash = decodeURIComponent(rawHash);
9697    } catch (error) {}
9698
9699    var target = document.getElementById(rawHash);
9700
9701    if (!target) {
9702      return;
9703    }
9704
9705    function scrollToTarget() {
9706      scrollToElement(target, {
9707        block: "start",
9708        behavior: "auto",
9709        offset: 24
9710      });
9711    }
9712
9713    var root = document.documentElement;
9714    var previousScrollBehavior = root.style.scrollBehavior;
9715
9716    root.style.scrollBehavior = "auto";
9717
9718    window.requestAnimationFrame(function () {
9719      scrollToTarget();
9720
9721      window.setTimeout(function () {
9722        scrollToTarget();
9723
9724        root.style.scrollBehavior = previousScrollBehavior;
9725      }, 80);
9726    });
9727  }
9728
9729  var SELECTORS = {
9730    root: "[data-global-search]",
9731    form: ".enisa-global-search__form",
9732    queryInput: ".enisa-global-search__input[name='q']",
9733    compatInput: "[data-global-search-compat-input]",
9734    item: "[data-global-search-item]",
9735    entryItem: ".enisa-global-search__result--entry",
9736    resultLink: "[data-global-search-result-link]",
9737    pagination: "[data-global-search-pagination]",
9738    results: "[data-global-search-results]",
9739    resultsTitle: "[data-global-search-results-title]",
9740    counter: "[data-global-search-counter]",
9741    status: "[data-global-search-status]",
9742    emptyResults: "[data-global-search-empty-results]",
9743    highlightTarget: "[data-search-highlight]"
9744  };
9745
9746  var CLASSES = {
9747    highlight: "enisa-global-search__highlight",
9748    hiddenByPage: "is-hidden-by-page",
9749    paginationButton: "enisa-pagination__btn",
9750    paginationArrow: "enisa-pagination__btn--arrow",
9751    paginationList: "enisa-pagination__list",
9752    active: "is-active"
9753  };
9754
9755  var DEFAULT_PAGE_SIZE = 10;
9756  var PAGINATION_WINDOW_SIZE = 3;
9757  var MIN_HIGHLIGHT_LENGTH = 2;
9758
9759  var STOP_WORDS = {
9760    a: true,
9761    al: true,
9762    de: true,
9763    del: true,
9764    el: true,
9765    en: true,
9766    es: true,
9767    la: true,
9768    las: true,
9769    lo: true,
9770    los: true,
9771    o: true,
9772    para: true,
9773    por: true,
9774    que: true,
9775    se: true,
9776    un: true,
9777    una: true,
9778    y: true
9779  };
9780
9781  var ACCENT_GROUPS = {
9782    a: "aáàäâãåā",
9783    e: "eéèëêē",
9784    i: "iíìïîī",
9785    o: "oóòöôõō",
9786    u: "uúùüûū",
9787    n: "nñ",
9788    c: "cç"
9789  };
9790
9791  function escapeRegExp(value) {
9792    return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9793  }
9794
9795  function normalizeSpaces(value) {
9796    return String(value || "").replace(/\s+/g, " ").trim();
9797  }
9798
9799  function markSearchFocusIntent() {
9800    try {
9801      window.sessionStorage.setItem(SEARCH_FOCUS_INTENT_KEY, "1");
9802    } catch (error) {}
9803  }
9804
9805  function consumeSearchFocusIntent() {
9806    var shouldRestoreFocus = false;
9807
9808    try {
9809      shouldRestoreFocus =
9810        window.sessionStorage.getItem(SEARCH_FOCUS_INTENT_KEY) === "1";
9811
9812      window.sessionStorage.removeItem(SEARCH_FOCUS_INTENT_KEY);
9813    } catch (error) {}
9814
9815    return shouldRestoreFocus;
9816  }
9817
9818  function normalizeText(value) {
9819    return normalizeSpaces(value)
9820      .toLowerCase()
9821      .normalize("NFD")
9822      .replace(/[\u0300-\u036f]/g, "");
9823  }
9824
9825  function getDatasetValue(item, name) {
9826    if (!item || !item.dataset) {
9827      return "";
9828    }
9829
9830    return item.dataset[name] || "";
9831  }
9832
9833  function getNumberDatasetValue(item, name, fallback) {
9834    var parsed = parseInt(getDatasetValue(item, name), 10);
9835
9836    return Number.isFinite(parsed) ? parsed : fallback;
9837  }
9838
9839  function getPageSize(root) {
9840    var pageSize = parseInt(
9841      root.getAttribute("data-global-search-page-size"),
9842      10
9843    );
9844
9845    return Number.isFinite(pageSize) && pageSize > 0
9846      ? pageSize
9847      : DEFAULT_PAGE_SIZE;
9848  }
9849
9850  function getSearchTerm(root) {
9851    return normalizeSpaces(root.getAttribute("data-search-term") || "");
9852  }
9853
9854  function syncCompatInput(root) {
9855    var queryInput = root.querySelector(SELECTORS.queryInput);
9856    var compatInput = root.querySelector(SELECTORS.compatInput);
9857
9858    if (!queryInput || !compatInput) {
9859      return;
9860    }
9861
9862    compatInput.value = normalizeSpaces(queryInput.value || "");
9863  }
9864
9865  function bindForm(root) {
9866    var form = root.querySelector(SELECTORS.form);
9867    var queryInput = root.querySelector(SELECTORS.queryInput);
9868
9869    if (!form) {
9870      return;
9871    }
9872
9873    form.addEventListener("submit", function (event) {
9874      var query = queryInput ? normalizeSpaces(queryInput.value) : "";
9875
9876      syncCompatInput(root);
9877
9878      if (!query) {
9879        event.preventDefault();
9880        safeFocus(queryInput);
9881        return;
9882      }
9883
9884      if (event.isTrusted !== false) {
9885        markSearchFocusIntent();
9886      }
9887    });
9888  }
9889
9890  function isModifiedPointerEvent(event) {
9891    return Boolean(
9892      event &&
9893      (
9894        event.button !== 0 ||
9895        event.metaKey ||
9896        event.ctrlKey ||
9897        event.shiftKey ||
9898        event.altKey
9899      )
9900    );
9901  }
9902
9903  function isInteractiveTarget(target, root) {
9904    var interactive;
9905
9906    if (!target || !root || !target.closest) {
9907      return false;
9908    }
9909
9910    interactive = target.closest(
9911      "a[href], button, input, select, textarea, summary, [role='button'], [role='link']"
9912    );
9913
9914    return Boolean(interactive && root.contains(interactive));
9915  }
9916
9917  function bindEntryPointerNavigation(root) {
9918    var entries = [].slice.call(root.querySelectorAll(SELECTORS.entryItem));
9919
9920    entries.forEach(function (entry) {
9921      if (entry.getAttribute("data-global-search-entry-init") === "true") {
9922        return;
9923      }
9924
9925      entry.setAttribute("data-global-search-entry-init", "true");
9926
9927      entry.addEventListener("click", function (event) {
9928        var link;
9929
9930        if (
9931          isModifiedPointerEvent(event) ||
9932          isInteractiveTarget(event.target, entry)
9933        ) {
9934          return;
9935        }
9936
9937        link = entry.querySelector(SELECTORS.resultLink);
9938
9939        if (link && typeof link.click === "function") {
9940          link.click();
9941        }
9942      });
9943    });
9944  }
9945
9946  function isDocumentFocusIdle() {
9947    var activeElement = document.activeElement;
9948
9949    return (
9950      !activeElement ||
9951      activeElement === document.body ||
9952      activeElement === document.documentElement
9953    );
9954  }
9955
9956  function focusResultsContext(root) {
9957    var resultsTitle = root.querySelector(SELECTORS.resultsTitle);
9958
9959    if (
9960      !resultsTitle ||
9961      !document.contains(resultsTitle) ||
9962      !isDocumentFocusIdle()
9963    ) {
9964      return;
9965    }
9966
9967    window.requestAnimationFrame(function () {
9968      window.requestAnimationFrame(function () {
9969        if (
9970          !document.contains(resultsTitle) ||
9971          !isDocumentFocusIdle()
9972        ) {
9973          return;
9974        }
9975
9976        safeFocus(resultsTitle, {
9977          preventScroll: false
9978        });
9979      });
9980    });
9981  }
9982
9983  function getPaginationFocusTarget(root, focusTarget) {
9984    var pagination = root.querySelector(SELECTORS.pagination);
9985    var currentPageButton;
9986    var requestedButton;
9987
9988    if (!pagination || !focusTarget) {
9989      return null;
9990    }
9991
9992    currentPageButton = pagination.querySelector('[aria-current="page"]');
9993
9994    if (focusTarget.type === "prev") {
9995      requestedButton = pagination.querySelector(
9996        "." + CLASSES.paginationArrow + ".is-prev"
9997      );
9998    } else if (focusTarget.type === "next") {
9999      requestedButton = pagination.querySelector(
10000        "." + CLASSES.paginationArrow + ".is-next"
10001      );
10002    } else if (focusTarget.type === "page") {
10003      requestedButton = [].slice.call(
10004        pagination.querySelectorAll(
10005          "." + CLASSES.paginationList + " ." + CLASSES.paginationButton
10006        )
10007      ).filter(function (button) {
10008        return button.textContent === String(focusTarget.page);
10009      })[0] || null;
10010    }
10011
10012    if (requestedButton && !requestedButton.disabled) {
10013      return requestedButton;
10014    }
10015
10016    return currentPageButton || null;
10017  }
10018
10019  function restorePaginationFocus(root, focusTarget) {
10020    window.requestAnimationFrame(function () {
10021      window.requestAnimationFrame(function () {
10022        var target = getPaginationFocusTarget(root, focusTarget);
10023
10024        if (target && document.contains(target)) {
10025          safeFocus(target);
10026        }
10027      });
10028    });
10029  }
10030
10031  function announceResultsUpdate(root, total, term, state) {
10032    var status = root.querySelector(SELECTORS.status);
10033    var message;
10034
10035    if (!status) {
10036      return;
10037    }
10038
10039    message = buildResultsMessage(total, term, state);
10040
10041    if (root.__enisaGlobalSearchStatusTimer) {
10042      window.clearTimeout(root.__enisaGlobalSearchStatusTimer);
10043    }
10044
10045    status.textContent = "";
10046
10047    root.__enisaGlobalSearchStatusTimer = window.setTimeout(function () {
10048      status.textContent = message;
10049      root.__enisaGlobalSearchStatusTimer = null;
10050    }, STATUS_ANNOUNCEMENT_DELAY);
10051  }
10052
10053  function getSearchTokens(term) {
10054    var normalized = normalizeText(term);
10055
10056    return normalized
10057      .split(/[^a-z0-9ñ]+/i)
10058      .map(function (token) {
10059        return token.trim();
10060      })
10061      .filter(function (token) {
10062        if (!token) {
10063          return false;
10064        }
10065
10066        if (STOP_WORDS[token]) {
10067          return false;
10068        }
10069
10070        return token.length >= MIN_HIGHLIGHT_LENGTH;
10071      });
10072  }
10073
10074  function getItemSearchParts(item) {
10075    return {
10076      title: normalizeText(getDatasetValue(item, "searchTitle")),
10077      subtitle: normalizeText(getDatasetValue(item, "searchSubtitle")),
10078      page: normalizeText(getDatasetValue(item, "searchPage")),
10079      summary: normalizeText(getDatasetValue(item, "searchSummary")),
10080      content: normalizeText(getDatasetValue(item, "searchContent")),
10081      keywords: normalizeText(getDatasetValue(item, "searchKeywords")),
10082      text: normalizeText(getDatasetValue(item, "searchText"))
10083    };
10084  }
10085
10086  function containsAllTokens(text, tokens) {
10087    if (!tokens.length) {
10088      return false;
10089    }
10090
10091    return tokens.every(function (token) {
10092      return text.indexOf(token) !== -1;
10093    });
10094  }
10095
10096  function containsAnyToken(text, tokens) {
10097    if (!tokens.length) {
10098      return false;
10099    }
10100
10101    return tokens.some(function (token) {
10102      return text.indexOf(token) !== -1;
10103    });
10104  }
10105
10106  function itemMatches(parts, normalizedTerm, tokens) {
10107    if (
10108      !normalizedTerm ||
10109      normalizedTerm.length < MIN_HIGHLIGHT_LENGTH
10110    ) {
10111      return false;
10112    }
10113
10114    if (parts.text.indexOf(normalizedTerm) !== -1) {
10115      return true;
10116    }
10117
10118    if (containsAllTokens(parts.text, tokens)) {
10119      return true;
10120    }
10121
10122    if (tokens.length === 1) {
10123      return containsAnyToken(
10124        parts.title + " " + parts.keywords + " " + parts.summary,
10125        tokens
10126      );
10127    }
10128
10129    return false;
10130  }
10131
10132  function scoreExactPhrase(parts, normalizedTerm) {
10133    var score = 0;
10134
10135    if (parts.title.indexOf(normalizedTerm) !== -1) {
10136      score += 260;
10137    }
10138
10139    if (parts.keywords.indexOf(normalizedTerm) !== -1) {
10140      score += 240;
10141    }
10142
10143    if (parts.subtitle.indexOf(normalizedTerm) !== -1) {
10144      score += 150;
10145    }
10146
10147    if (parts.summary.indexOf(normalizedTerm) !== -1) {
10148      score += 120;
10149    }
10150
10151    if (parts.page.indexOf(normalizedTerm) !== -1) {
10152      score += 90;
10153    }
10154
10155    if (parts.content.indexOf(normalizedTerm) !== -1) {
10156      score += 50;
10157    }
10158
10159    return score;
10160  }
10161
10162  function scoreTokens(parts, tokens) {
10163    var score = 0;
10164
10165    tokens.forEach(function (token) {
10166      if (parts.title.indexOf(token) !== -1) {
10167        score += 75;
10168      }
10169
10170      if (parts.keywords.indexOf(token) !== -1) {
10171        score += 70;
10172      }
10173
10174      if (parts.subtitle.indexOf(token) !== -1) {
10175        score += 45;
10176      }
10177
10178      if (parts.summary.indexOf(token) !== -1) {
10179        score += 35;
10180      }
10181
10182      if (parts.page.indexOf(token) !== -1) {
10183        score += 25;
10184      }
10185
10186      if (parts.content.indexOf(token) !== -1) {
10187        score += 12;
10188      }
10189    });
10190
10191    if (
10192      tokens.length > 1 &&
10193      containsAllTokens(parts.title + " " + parts.keywords, tokens)
10194    ) {
10195      score += 120;
10196    }
10197
10198    if (
10199      tokens.length > 1 &&
10200      containsAllTokens(parts.summary + " " + parts.content, tokens)
10201    ) {
10202      score += 60;
10203    }
10204
10205    return score;
10206  }
10207
10208  function getBaseBoost(item) {
10209    var source = getDatasetValue(item, "searchSource");
10210    var title = normalizeText(getDatasetValue(item, "searchTitle"));
10211    var subtitle = normalizeText(getDatasetValue(item, "searchSubtitle"));
10212    var keywords = normalizeText(getDatasetValue(item, "searchKeywords"));
10213    var text = title + " " + subtitle + " " + keywords;
10214
10215    if (source === "faq") {
10216      return 95;
10217    }
10218
10219    if (
10220      text.indexOf("financiacion") !== -1 ||
10221      text.indexOf("certificacion") !== -1 ||
10222      text.indexOf("trabaja en enisa") !== -1 ||
10223      text.indexOf("empleo") !== -1
10224    ) {
10225      return 100;
10226    }
10227
10228    if (
10229      text.indexOf("requisitos") !== -1 ||
10230      text.indexOf("condiciones") !== -1 ||
10231      text.indexOf("obligaciones") !== -1 ||
10232      text.indexOf("proceso") !== -1
10233    ) {
10234      return 90;
10235    }
10236
10237    if (
10238      text.indexOf("contacto") !== -1 ||
10239      text.indexOf("ayuda") !== -1 ||
10240      text.indexOf("sistema interno") !== -1 ||
10241      text.indexOf("canal etico") !== -1
10242    ) {
10243      return 85;
10244    }
10245
10246    if (
10247      text.indexOf("privacidad") !== -1 ||
10248      text.indexOf("cookies") !== -1 ||
10249      text.indexOf("accesibilidad") !== -1 ||
10250      text.indexOf("aviso legal") !== -1
10251    ) {
10252      return 65;
10253    }
10254
10255    return 55;
10256  }
10257
10258  function getItemScore(item, normalizedTerm, tokens) {
10259    var parts = getItemSearchParts(item);
10260
10261    if (!itemMatches(parts, normalizedTerm, tokens)) {
10262      return -1;
10263    }
10264
10265    return (
10266      getBaseBoost(item) +
10267      scoreExactPhrase(parts, normalizedTerm) +
10268      scoreTokens(parts, tokens)
10269    );
10270  }
10271
10272  function sortItems(items) {
10273    return items.sort(function (a, b) {
10274      var aScore = getNumberDatasetValue(a, "computedScore", 0);
10275      var bScore = getNumberDatasetValue(b, "computedScore", 0);
10276
10277      if (bScore !== aScore) {
10278        return bScore - aScore;
10279      }
10280
10281      var aOrder = getNumberDatasetValue(a, "searchOrder", 9999);
10282      var bOrder = getNumberDatasetValue(b, "searchOrder", 9999);
10283
10284      if (aOrder !== bOrder) {
10285        return aOrder - bOrder;
10286      }
10287
10288      return normalizeText(
10289        getDatasetValue(a, "searchTitle")
10290      ).localeCompare(
10291        normalizeText(getDatasetValue(b, "searchTitle"))
10292      );
10293    });
10294  }
10295
10296  function updateCounter(root, total, term, isReady, state) {
10297    var counter = root.querySelector(SELECTORS.counter);
10298    var message;
10299
10300    if (!counter) {
10301      return;
10302    }
10303
10304    if (!isReady) {
10305      counter.textContent = "";
10306      counter.classList.add("is-loading");
10307      counter.setAttribute("aria-busy", "true");
10308      return;
10309    }
10310
10311    message = buildResultsMessage(total, term, state);
10312    counter.textContent = message;
10313
10314    counter.classList.remove("is-loading");
10315    counter.setAttribute("aria-busy", "false");
10316  }
10317
10318  function toggleEmptyState(root, hasResults, isReady) {
10319    var emptyResults = root.querySelector(SELECTORS.emptyResults);
10320    var results = root.querySelector(SELECTORS.results);
10321
10322    if (emptyResults) {
10323      emptyResults.hidden = !isReady || hasResults;
10324    }
10325
10326    if (!results) {
10327      return;
10328    }
10329
10330    if (!isReady) {
10331      results.hidden = true;
10332      results.classList.add("is-loading");
10333      results.setAttribute("aria-busy", "true");
10334      return;
10335    }
10336
10337    results.hidden = !hasResults;
10338    results.classList.remove("is-loading");
10339    results.setAttribute("aria-busy", "false");
10340  }
10341
10342  function filterAndRankItems(root, state) {
10343    var term = getSearchTerm(root);
10344    var normalizedTerm = normalizeText(term);
10345    var tokens = getSearchTokens(term);
10346    var matchedItems = [];
10347
10348    state.allItems.forEach(function (item) {
10349      var score = getItemScore(item, normalizedTerm, tokens);
10350
10351      if (score < 0) {
10352        item.dataset.computedScore = "0";
10353        item.setAttribute("hidden", "");
10354        item.classList.add(CLASSES.hiddenByPage);
10355        return;
10356      }
10357
10358      item.dataset.computedScore = String(score);
10359      matchedItems.push(item);
10360    });
10361
10362    state.items = sortItems(matchedItems);
10363    state.currentPage = 1;
10364
10365    if (state.results) {
10366      state.items.forEach(function (item) {
10367        state.results.appendChild(item);
10368      });
10369    }
10370  }
10371
10372  function buildAccentInsensitivePattern(term) {
10373    var value = String(term || "");
10374    var pattern = "";
10375
10376    for (var i = 0; i < value.length; i += 1) {
10377      var char = value.charAt(i);
10378      var normalized = normalizeText(char);
10379
10380      if (/\s/.test(char)) {
10381        pattern += "\\s+";
10382        continue;
10383      }
10384
10385      if (ACCENT_GROUPS[normalized]) {
10386        pattern += "[" + escapeRegExp(ACCENT_GROUPS[normalized]) + "]";
10387        continue;
10388      }
10389
10390      pattern += escapeRegExp(char);
10391    }
10392
10393    return pattern;
10394  }
10395
10396  function buildHighlightRegex(term) {
10397    var safeTerm = buildAccentInsensitivePattern(term);
10398
10399    if (/\s/.test(term)) {
10400      return new RegExp("(" + safeTerm + ")", "gi");
10401    }
10402
10403    return new RegExp(
10404      "(^|[^A-Za-zÀ-ÖØ-öø-ÿ0-9_\\-])([A-Za-zÀ-ÖØ-öø-ÿ0-9_\\-]*" +
10405        safeTerm +
10406        "[A-Za-zÀ-ÖØ-öø-ÿ0-9_\\-]*)",
10407      "gi"
10408    );
10409  }
10410
10411  function clearHighlights(root) {
10412    var highlights = [].slice.call(
10413      root.querySelectorAll("." + CLASSES.highlight)
10414    );
10415
10416    highlights.forEach(function (highlight) {
10417      var parent = highlight.parentNode;
10418
10419      if (!parent) {
10420        return;
10421      }
10422
10423      parent.replaceChild(
10424        document.createTextNode(highlight.textContent || ""),
10425        highlight
10426      );
10427
10428      parent.normalize();
10429    });
10430  }
10431
10432  function highlightTextNode(node, term, regex) {
10433    var text = node.nodeValue;
10434
10435    if (
10436      !text ||
10437      normalizeText(text).indexOf(normalizeText(term)) === -1
10438    ) {
10439      return;
10440    }
10441
10442    var fragment = document.createDocumentFragment();
10443    var lastIndex = 0;
10444    var match;
10445
10446    regex.lastIndex = 0;
10447
10448    while ((match = regex.exec(text)) !== null) {
10449      var prefix = match[1] || "";
10450      var word = match[2] || "";
10451      var matchStart = match.index;
10452      var wordStart = matchStart + prefix.length;
10453
10454      if (/\s/.test(term)) {
10455        prefix = "";
10456        word = match[1] || "";
10457        wordStart = matchStart;
10458      }
10459
10460      if (matchStart > lastIndex) {
10461        fragment.appendChild(
10462          document.createTextNode(text.slice(lastIndex, matchStart))
10463        );
10464      }
10465
10466      if (prefix) {
10467        fragment.appendChild(document.createTextNode(prefix));
10468      }
10469
10470      if (word) {
10471        var mark = document.createElement("span");
10472
10473        mark.className = CLASSES.highlight;
10474        mark.textContent = word;
10475
10476        fragment.appendChild(mark);
10477      }
10478
10479      lastIndex = wordStart + word.length;
10480
10481      if (regex.lastIndex === match.index) {
10482        regex.lastIndex += 1;
10483      }
10484    }
10485
10486    if (lastIndex === 0) {
10487      return;
10488    }
10489
10490    if (lastIndex < text.length) {
10491      fragment.appendChild(
10492        document.createTextNode(text.slice(lastIndex))
10493      );
10494    }
10495
10496    node.parentNode.replaceChild(fragment, node);
10497  }
10498
10499  function highlightElement(element, term, regex) {
10500    var walker = document.createTreeWalker(
10501      element,
10502      NodeFilter.SHOW_TEXT,
10503      {
10504        acceptNode: function (node) {
10505          if (!node.nodeValue || !node.nodeValue.trim()) {
10506            return NodeFilter.FILTER_REJECT;
10507          }
10508
10509          if (
10510            node.parentElement &&
10511            node.parentElement.closest("." + CLASSES.highlight)
10512          ) {
10513            return NodeFilter.FILTER_REJECT;
10514          }
10515
10516          return NodeFilter.FILTER_ACCEPT;
10517        }
10518      }
10519    );
10520
10521    var nodes = [];
10522    var currentNode = walker.nextNode();
10523
10524    while (currentNode) {
10525      nodes.push(currentNode);
10526      currentNode = walker.nextNode();
10527    }
10528
10529    nodes.forEach(function (node) {
10530      highlightTextNode(node, term, regex);
10531    });
10532  }
10533
10534  function applyHighlights(root, state) {
10535    var term = getSearchTerm(root);
10536    var tokens = getSearchTokens(term);
10537    var termsToHighlight = [];
10538
10539    clearHighlights(root);
10540
10541    if (normalizeSpaces(term).length >= MIN_HIGHLIGHT_LENGTH) {
10542      termsToHighlight.push(term);
10543    }
10544
10545    tokens.forEach(function (token) {
10546      if (termsToHighlight.indexOf(token) === -1) {
10547        termsToHighlight.push(token);
10548      }
10549    });
10550
10551    termsToHighlight.forEach(function (highlightTerm) {
10552      var regex = buildHighlightRegex(highlightTerm);
10553
10554      var visibleItems = state.items.filter(function (item) {
10555        return !item.hasAttribute("hidden");
10556      });
10557
10558      visibleItems.forEach(function (item) {
10559        var targets = [].slice.call(
10560          item.querySelectorAll(SELECTORS.highlightTarget)
10561        );
10562
10563        targets.forEach(function (target) {
10564          highlightElement(target, highlightTerm, regex);
10565        });
10566      });
10567    });
10568  }
10569
10570  function createPaginationArrow(type, disabled, onClick) {
10571    var button = document.createElement("button");
10572
10573    button.type = "button";
10574    button.className =
10575      CLASSES.paginationButton +
10576      " " +
10577      CLASSES.paginationArrow +
10578      " " +
10579      (type === "prev" ? "is-prev" : "is-next");
10580
10581    button.setAttribute(
10582      "aria-label",
10583      type === "prev" ? "Página anterior" : "Página siguiente"
10584    );
10585
10586    button.innerHTML =
10587      '<span class="enisa-icon enisa-pagination__arrow-icon" aria-hidden="true"></span>';
10588
10589    button.disabled = disabled;
10590    button.addEventListener("click", onClick);
10591
10592    return button;
10593  }
10594
10595  function renderPagination(root, state) {
10596    var totalPages = Math.max(
10597      1,
10598      Math.ceil(state.items.length / state.pageSize)
10599    );
10600
10601    var pagination = state.pagination;
10602
10603    if (!pagination) {
10604      return;
10605    }
10606
10607    pagination.innerHTML = "";
10608
10609    if (state.items.length === 0 || totalPages <= 1) {
10610      pagination.setAttribute("hidden", "");
10611      return;
10612    }
10613
10614    pagination.removeAttribute("hidden");
10615
10616    var half = Math.floor(PAGINATION_WINDOW_SIZE / 2);
10617    var start = Math.max(1, state.currentPage - half);
10618    var end = Math.min(
10619      totalPages,
10620      start + PAGINATION_WINDOW_SIZE - 1
10621    );
10622
10623    if (end - start < PAGINATION_WINDOW_SIZE - 1) {
10624      start = Math.max(
10625        1,
10626        end - PAGINATION_WINDOW_SIZE + 1
10627      );
10628    }
10629
10630    pagination.appendChild(
10631      createPaginationArrow(
10632        "prev",
10633        state.currentPage === 1,
10634        function () {
10635          if (state.currentPage <= 1) {
10636            return;
10637          }
10638
10639          state.currentPage -= 1;
10640          render(root, state, {
10641            type: "pagination",
10642            focusTarget: { type: "prev" }
10643          });
10644        }
10645      )
10646    );
10647
10648    var pageList = document.createElement("div");
10649
10650    pageList.className = CLASSES.paginationList;
10651
10652    for (var i = start; i <= end; i += 1) {
10653      var pageButton = document.createElement("button");
10654
10655      pageButton.type = "button";
10656      pageButton.className =
10657        CLASSES.paginationButton +
10658        (i === state.currentPage ? " " + CLASSES.active : "");
10659
10660      pageButton.textContent = String(i);
10661      pageButton.setAttribute("aria-label", "Página " + i);
10662
10663      if (i === state.currentPage) {
10664        pageButton.setAttribute("aria-current", "page");
10665      }
10666
10667      (function (pageNumber) {
10668        pageButton.addEventListener("click", function () {
10669          state.currentPage = pageNumber;
10670          render(root, state, {
10671            type: "pagination",
10672            focusTarget: { type: "page", page: pageNumber }
10673          });
10674        });
10675      })(i);
10676
10677      pageList.appendChild(pageButton);
10678    }
10679
10680    pagination.appendChild(pageList);
10681
10682    pagination.appendChild(
10683      createPaginationArrow(
10684        "next",
10685        state.currentPage === totalPages,
10686        function () {
10687          if (state.currentPage >= totalPages) {
10688            return;
10689          }
10690
10691          state.currentPage += 1;
10692          render(root, state, {
10693            type: "pagination",
10694            focusTarget: { type: "next" }
10695          });
10696        }
10697      )
10698    );
10699  }
10700
10701  function renderItems(state) {
10702    var totalPages = Math.max(
10703      1,
10704      Math.ceil(state.items.length / state.pageSize)
10705    );
10706
10707    if (state.currentPage > totalPages) {
10708      state.currentPage = totalPages;
10709    }
10710
10711    if (state.currentPage < 1) {
10712      state.currentPage = 1;
10713    }
10714
10715    var start = (state.currentPage - 1) * state.pageSize;
10716    var end = start + state.pageSize;
10717
10718    state.allItems.forEach(function (item) {
10719      item.setAttribute("hidden", "");
10720      item.classList.add(CLASSES.hiddenByPage);
10721    });
10722
10723    state.items.forEach(function (item, index) {
10724      var isVisible = index >= start && index < end;
10725
10726      item.classList.toggle(
10727        CLASSES.hiddenByPage,
10728        !isVisible
10729      );
10730
10731      if (isVisible) {
10732        item.removeAttribute("hidden");
10733      } else {
10734        item.setAttribute("hidden", "");
10735      }
10736    });
10737  }
10738
10739  function render(root, state, interaction) {
10740    var term = getSearchTerm(root);
10741
10742    renderItems(state);
10743    renderPagination(root, state);
10744    applyHighlights(root, state);
10745    toggleEmptyState(
10746      root,
10747      state.items.length > 0,
10748      true
10749    );
10750
10751    updateCounter(
10752      root,
10753      state.items.length,
10754      term,
10755      true,
10756      state
10757    );
10758
10759    if (interaction && interaction.type === "pagination") {
10760      restorePaginationFocus(root, interaction.focusTarget);
10761      announceResultsUpdate(
10762        root,
10763        state.items.length,
10764        term,
10765        state
10766      );
10767    }
10768  }
10769
10770  function initGlobalSearch(root) {
10771    if (
10772      !root ||
10773      root.getAttribute("data-global-search-init") === "true"
10774    ) {
10775      return;
10776    }
10777
10778    root.setAttribute(
10779      "data-global-search-init",
10780      "true"
10781    );
10782
10783    var shouldRestoreSearchFocus =
10784      consumeSearchFocusIntent() &&
10785      getSearchTerm(root).length > 0;
10786
10787    bindForm(root);
10788    bindEntryPointerNavigation(root);
10789
10790    var allItems = [].slice.call(
10791      root.querySelectorAll(SELECTORS.item)
10792    );
10793
10794    var pagination = root.querySelector(
10795      SELECTORS.pagination
10796    );
10797
10798    var results = root.querySelector(
10799      SELECTORS.results
10800    );
10801
10802    toggleEmptyState(root, false, false);
10803    updateCounter(
10804      root,
10805      0,
10806      getSearchTerm(root),
10807      false
10808    );
10809
10810    if (!allItems.length || !results) {
10811      toggleEmptyState(root, false, true);
10812
10813      updateCounter(
10814        root,
10815        0,
10816        getSearchTerm(root),
10817        true
10818      );
10819
10820      if (shouldRestoreSearchFocus) {
10821        focusResultsContext(root);
10822      }
10823
10824      return;
10825    }
10826
10827    var state = {
10828      currentPage: 1,
10829      pageSize: getPageSize(root),
10830      allItems: allItems,
10831      items: [],
10832      pagination: pagination,
10833      results: results
10834    };
10835
10836    filterAndRankItems(root, state);
10837    render(root, state, null);
10838
10839    if (shouldRestoreSearchFocus) {
10840      focusResultsContext(root);
10841    }
10842  }
10843
10844  function init() {
10845    alignInitialHashTarget();
10846
10847    var roots = [].slice.call(
10848      document.querySelectorAll(SELECTORS.root)
10849    );
10850
10851    roots.forEach(initGlobalSearch);
10852  }
10853
10854  if (document.readyState === "loading") {
10855    document.addEventListener(
10856      "DOMContentLoaded",
10857      init
10858    );
10859
10860    return;
10861  }
10862
10863  init();
10864})();
10865
10866/*
10867  ENISA | JS Module
10868  Modulo: 23-breadcrumb-back
10869  Descripcion: Navegacion "volver" inteligente para enlaces [data-back-smart].
10870*/
10871(function () {
10872  "use strict";
10873
10874  var STORAGE_LAST_URL_KEY = "enisa:last-url";
10875  var STORAGE_PREV_URL_KEY = "enisa:prev-url";
10876
10877  function getCurrentRelativeUrl() {
10878    return window.location.pathname + window.location.search + window.location.hash;
10879  }
10880
10881  function safeStorageGet(key) {
10882    try {
10883      return window.sessionStorage.getItem(key) || "";
10884    } catch (error) {
10885      return "";
10886    }
10887  }
10888
10889  function safeStorageSet(key, value) {
10890    try {
10891      window.sessionStorage.setItem(key, value);
10892    } catch (error) {
10893    }
10894  }
10895
10896  function syncInternalNavigationState() {
10897    var currentUrl = getCurrentRelativeUrl();
10898    var lastUrl = safeStorageGet(STORAGE_LAST_URL_KEY);
10899
10900    if (lastUrl && lastUrl !== currentUrl) {
10901      safeStorageSet(STORAGE_PREV_URL_KEY, lastUrl);
10902    }
10903
10904    safeStorageSet(STORAGE_LAST_URL_KEY, currentUrl);
10905  }
10906
10907  function shouldUseHistoryBack() {
10908    return window.history.length > 1;
10909  }
10910
10911  function getStoredPreviousUrl() {
10912    var previousUrl = safeStorageGet(STORAGE_PREV_URL_KEY);
10913    var currentUrl = getCurrentRelativeUrl();
10914    if (!previousUrl || previousUrl === currentUrl) return "";
10915    return previousUrl;
10916  }
10917
10918  function handleSmartBackClick(event) {
10919    if (shouldUseHistoryBack()) {
10920      event.preventDefault();
10921      window.history.back();
10922      return;
10923    }
10924
10925    var previousUrl = getStoredPreviousUrl();
10926    if (!previousUrl) return;
10927
10928    event.preventDefault();
10929    window.location.assign(previousUrl);
10930  }
10931
10932  function initSmartBackLinks() {
10933    var links = document.querySelectorAll("[data-back-smart]");
10934    if (!links.length) return;
10935
10936    var i;
10937    var link;
10938    for (i = 0; i < links.length; i++) {
10939      link = links[i];
10940      if (link.getAttribute("data-back-smart-init") === "true") continue;
10941      link.setAttribute("data-back-smart-init", "true");
10942      link.addEventListener("click", handleSmartBackClick);
10943    }
10944  }
10945
10946  if (document.readyState === "loading") {
10947    document.addEventListener("DOMContentLoaded", function () {
10948      syncInternalNavigationState();
10949      initSmartBackLinks();
10950    });
10951  } else {
10952    syncInternalNavigationState();
10953    initSmartBackLinks();
10954  }
10955})();
10956
10957(function () {
10958  "use strict";
10959
10960  var SELECTOR_SECTION = ".enisa-equipo-directivo";
10961  var SELECTOR_TOGGLE = "[data-equipo-toggle]";
10962  var SELECTOR_BIO = ".enisa-directivo__bio";
10963  var INIT_ATTR = "data-equipo-init";
10964  var LABEL_OPEN = "Ver trayectoria";
10965  var LABEL_CLOSE = "Ocultar trayectoria";
10966  var BLOCK_TAGS_REGEX = /<(p|div|ul|ol|li|table|thead|tbody|tr|td|th|blockquote|section|article|h[1-6])(\s|>)/i;
10967
10968  function normalizeBioParagraphs(section) {
10969    var bios = section.querySelectorAll(SELECTOR_BIO);
10970    for (var i = 0; i < bios.length; i++) {
10971      var bio = bios[i];
10972      var html = (bio.innerHTML || "").trim();
10973      if (!html) continue;
10974      if (bio.querySelector("p")) continue;
10975      if (BLOCK_TAGS_REGEX.test(html)) continue;
10976
10977      bio.innerHTML = "<p>" + html + "</p>";
10978    }
10979  }
10980
10981  function initEquipo(section) {
10982    if (section.getAttribute(INIT_ATTR)) return;
10983    section.setAttribute(INIT_ATTR, "true");
10984    normalizeBioParagraphs(section);
10985
10986    section.addEventListener("click", function (e) {
10987      var btn = e.target.closest(SELECTOR_TOGGLE);
10988      if (!btn) return;
10989
10990      var expanded = btn.getAttribute("aria-expanded") === "true";
10991      var panelId = btn.getAttribute("aria-controls");
10992      var panel = panelId ? document.getElementById(panelId) : null;
10993      var labelEl = btn.querySelector("[data-equipo-label]");
10994
10995      if (!panel) return;
10996
10997      if (expanded) {
10998        btn.setAttribute("aria-expanded", "false");
10999        panel.classList.remove("is-open");
11000        if (labelEl) labelEl.textContent = LABEL_OPEN;
11001      } else {
11002        btn.setAttribute("aria-expanded", "true");
11003        panel.classList.add("is-open");
11004        if (labelEl) labelEl.textContent = LABEL_CLOSE;
11005      }
11006    });
11007  }
11008
11009  function boot() {
11010    var sections = document.querySelectorAll(SELECTOR_SECTION);
11011    for (var i = 0; i < sections.length; i++) {
11012      initEquipo(sections[i]);
11013    }
11014  }
11015
11016  if (document.readyState === "loading") {
11017    document.addEventListener("DOMContentLoaded", boot);
11018  } else {
11019    boot();
11020  }
11021})();
11022
11023(function () {
11024  "use strict";
11025
11026  var ROOT_SELECTOR = ".enisa-shareholders";
11027  var NAME_SELECTOR = ".enisa-shareholders__name";
11028  var LINE_CLASS = "enisa-shareholders__name-line";
11029  var TOP_LINE_CLASS = "enisa-shareholders__name-line--top";
11030  var BOTTOM_LINE_CLASS = "enisa-shareholders__name-line--bottom";
11031  var ORIGINAL_ATTR = "data-enisa-shareholder-name-original";
11032  var RESIZE_DEBOUNCE = 120;
11033
11034  function getOriginalText(name) {
11035    var original = name.getAttribute(ORIGINAL_ATTR);
11036
11037    if (original) {
11038      return original;
11039    }
11040
11041    original = (name.textContent || "").replace(/\s+/g, " ").trim();
11042    name.setAttribute(ORIGINAL_ATTR, original);
11043    return original;
11044  }
11045
11046  function createMeasureNode(name) {
11047    var measure = document.createElement("span");
11048    var styles = window.getComputedStyle(name);
11049
11050    measure.setAttribute("aria-hidden", "true");
11051    measure.style.position = "absolute";
11052    measure.style.visibility = "hidden";
11053    measure.style.pointerEvents = "none";
11054    measure.style.whiteSpace = "nowrap";
11055    measure.style.left = "-9999px";
11056    measure.style.top = "0";
11057    measure.style.fontFamily = styles.fontFamily;
11058    measure.style.fontSize = styles.fontSize;
11059    measure.style.fontWeight = styles.fontWeight;
11060    measure.style.letterSpacing = styles.letterSpacing;
11061    measure.style.textTransform = styles.textTransform;
11062
11063    document.body.appendChild(measure);
11064    return measure;
11065  }
11066
11067  function getCssLength(name, variableName, fallbackValue) {
11068    var value = window.getComputedStyle(name).getPropertyValue(variableName).trim();
11069
11070    if (!value) {
11071      return fallbackValue;
11072    }
11073
11074    var test = document.createElement("div");
11075    test.style.position = "absolute";
11076    test.style.visibility = "hidden";
11077    test.style.width = value;
11078    name.appendChild(test);
11079    var width = test.getBoundingClientRect().width || fallbackValue;
11080    name.removeChild(test);
11081    return width;
11082  }
11083
11084  function measureTextWidth(measureNode, text) {
11085    measureNode.textContent = text;
11086    return measureNode.getBoundingClientRect().width;
11087  }
11088
11089  function pickSplit(words, topWidth, bottomWidth, measureNode) {
11090    var bestFit = null;
11091    var fallback = null;
11092    var index;
11093
11094    for (index = 1; index < words.length; index += 1) {
11095      var topText = words.slice(0, index).join(" ");
11096      var bottomText = words.slice(index).join(" ");
11097      var topTextWidth = measureTextWidth(measureNode, topText);
11098      var bottomTextWidth = measureTextWidth(measureNode, bottomText);
11099      var candidate = {
11100        top: topText,
11101        bottom: bottomText,
11102        topOverflow: Math.max(0, topTextWidth - topWidth),
11103        bottomOverflow: Math.max(0, bottomTextWidth - bottomWidth),
11104        bottomFill: bottomTextWidth / Math.max(bottomWidth, 1),
11105      };
11106
11107      if (candidate.topOverflow === 0 && candidate.bottomOverflow === 0) {
11108        return candidate;
11109      }
11110
11111      if (
11112        !fallback ||
11113        candidate.bottomOverflow < fallback.bottomOverflow ||
11114        (
11115          candidate.bottomOverflow === fallback.bottomOverflow &&
11116          candidate.topOverflow < fallback.topOverflow
11117        ) ||
11118        (
11119          candidate.bottomOverflow === fallback.bottomOverflow &&
11120          candidate.topOverflow === fallback.topOverflow &&
11121          candidate.bottomFill > fallback.bottomFill
11122        )
11123      ) {
11124        fallback = candidate;
11125      }
11126
11127      if (
11128        candidate.topOverflow === 0 &&
11129        (!bestFit || candidate.bottomFill > bestFit.bottomFill)
11130      ) {
11131        bestFit = candidate;
11132      }
11133    }
11134
11135    return bestFit || fallback;
11136  }
11137
11138  function renderSplit(name, split) {
11139    if (!split || !split.top || !split.bottom) {
11140      return;
11141    }
11142
11143    var topLine = document.createElement("span");
11144    var bottomLine = document.createElement("span");
11145
11146    topLine.className = LINE_CLASS + " " + TOP_LINE_CLASS;
11147    bottomLine.className = LINE_CLASS + " " + BOTTOM_LINE_CLASS;
11148    topLine.textContent = split.top;
11149    bottomLine.textContent = split.bottom;
11150
11151    name.textContent = "";
11152    name.appendChild(topLine);
11153    name.appendChild(bottomLine);
11154  }
11155
11156  function splitName(name) {
11157    if (!name || !document.body) {
11158      return;
11159    }
11160
11161    var originalText = getOriginalText(name);
11162    var words = originalText.split(" ").filter(Boolean);
11163
11164    if (words.length < 2) {
11165      name.textContent = originalText;
11166      return;
11167    }
11168
11169    name.textContent = originalText;
11170
11171    var totalWidth = name.getBoundingClientRect().width;
11172    if (!totalWidth) {
11173      return;
11174    }
11175
11176    var arrowWidth = getCssLength(name, "--enisa-link-arrow-w", 12);
11177    var gapWidth = getCssLength(name, "--enisa-shareholder-link-gap", 8);
11178    var topWidth = totalWidth;
11179    var bottomWidth = Math.max(0, totalWidth - arrowWidth - gapWidth);
11180    var measureNode = createMeasureNode(name);
11181    var split;
11182
11183    try {
11184      split = pickSplit(words, topWidth, bottomWidth, measureNode);
11185    } finally {
11186      document.body.removeChild(measureNode);
11187    }
11188
11189    if (!split) {
11190      name.textContent = originalText;
11191      return;
11192    }
11193
11194    renderSplit(name, split);
11195  }
11196
11197  function splitAllNames() {
11198    var names = document.querySelectorAll(ROOT_SELECTOR + " " + NAME_SELECTOR);
11199
11200    Array.prototype.forEach.call(names, splitName);
11201  }
11202
11203  function initResizeHandling() {
11204    var timer = null;
11205
11206    function rerender() {
11207      clearTimeout(timer);
11208      timer = setTimeout(splitAllNames, RESIZE_DEBOUNCE);
11209    }
11210
11211    window.addEventListener("resize", rerender);
11212
11213    if (document.fonts && document.fonts.ready) {
11214      document.fonts.ready.then(splitAllNames).catch(function () {});
11215    }
11216  }
11217
11218  function init() {
11219    if (!document.querySelector(ROOT_SELECTOR)) {
11220      return;
11221    }
11222
11223    splitAllNames();
11224    initResizeHandling();
11225  }
11226
11227  if (document.readyState === "loading") {
11228    document.addEventListener("DOMContentLoaded", init);
11229  } else {
11230    init();
11231  }
11232})();
11233
11234/*
11235  ENISA | JS Module
11236  Modulo: 27-newsletter
11237  Descripcion: Gestiona la suscripcion al boletin y crea/reactiva registros en Dataverse mediante Power Pages Web API.
11238*/
11239(function () {
11240  "use strict";
11241
11242  var FORM_SELECTOR = "[data-newsletter-form]";
11243  var INIT_ATTR = "data-newsletter-init";
11244  var DEFAULT_WEBAPI_TABLE = "/_api/enisa_newslettersubscriptions";
11245  var TOKEN_ENDPOINT = "/_layout/tokenhtml";
11246  var RECAPTCHA_SCRIPT_ID = "enisa-google-recaptcha-api";
11247  var RECAPTCHA_ONLOAD_CALLBACK = "enisaNewsletterRecaptchaReady";
11248  var RECAPTCHA_ACTION = "newsletter_submit";
11249  var formInstanceCount = 0;
11250
11251  var ID_FIELD = "enisa_newslettersubscriptionid";
11252  var EMAIL_FIELD = "enisa_email";
11253  var EMAIL_NORMALIZED_FIELD = "enisa_emailnormalized";
11254  var PAGE_URL_FIELD = "enisa_pageurl";
11255  var PRIVACY_FIELD = "enisa_privacyaccepted";
11256  var SOURCE_FIELD = "enisa_source";
11257  var SUBSCRIBED_ON_FIELD = "enisa_subscribedon";
11258  var STATUS_FIELD = "enisa_subscriptionstatus";
11259
11260  var STATUS_ACTIVE = 694380000;
11261  var STATUS_UNSUBSCRIBED = 694380001;
11262
11263  var MESSAGES = {
11264    required: "Introduce tu correo electrónico.",
11265    invalid: "Introduce un correo electrónico válido.",
11266    loading: "Estamos procesando tu suscripción...",
11267    success: "Tu suscripción se ha enviado correctamente. Revisa tu buzón de correo electrónico.",
11268    reactivated: "Tu suscripción se ha enviado correctamente. Revisa tu buzón de correo electrónico.",
11269    duplicated: "Este correo ya está suscrito al boletín.",
11270    error: "No hemos podido registrar tu suscripción. Inténtalo de nuevo.",
11271    recaptcha: "No se ha podido validar la protección anti-spam. Inténtalo de nuevo."
11272  };
11273
11274  var SENT_SUBMIT_LABEL = "Enviado";
11275
11276  function init() {
11277    var forms = [].slice.call(document.querySelectorAll(FORM_SELECTOR));
11278    if (!forms.length) return;
11279
11280    forms.forEach(initNewsletterForm);
11281  }
11282
11283  function initNewsletterForm(form) {
11284    if (!form || form.getAttribute(INIT_ATTR) === "true") return;
11285
11286    var emailInput = getEmailInput(form);
11287    var submitBtn = getSubmitButton(form);
11288    var feedback = getFeedbackElement(form);
11289
11290    if (!emailInput || !submitBtn || !feedback) return;
11291
11292    form.setAttribute(INIT_ATTR, "true");
11293    ensureScopedAccessibilityIds(form, emailInput, feedback);
11294
11295    if (!feedback.hasAttribute("aria-live")) {
11296      feedback.setAttribute("aria-live", "polite");
11297    }
11298
11299    if (!feedback.hasAttribute("role")) {
11300      feedback.setAttribute("role", "status");
11301    }
11302
11303    feedback.setAttribute("aria-atomic", "true");
11304    storeBaseDescribedBy(emailInput, feedback);
11305
11306    submitBtn.setAttribute(
11307      "data-newsletter-default-label",
11308      getButtonLabel(submitBtn)
11309    );
11310
11311    emailInput.addEventListener("input", function () {
11312      form.classList.remove("is-success", "is-duplicate", "is-submit-error", "is-error");
11313      clearFieldError(emailInput, feedback);
11314      resetSubmitState(submitBtn);
11315    });
11316
11317    emailInput.addEventListener("blur", function () {
11318      validateEmailField(emailInput, feedback, false);
11319    });
11320
11321    form.addEventListener("submit", function (event) {
11322      event.preventDefault();
11323
11324      form.classList.remove("is-success", "is-duplicate", "is-submit-error", "is-error");
11325      resetSubmitState(submitBtn);
11326
11327      if (isHoneypotFilled(form)) {
11328        renderSuccess(form, emailInput, submitBtn, feedback, MESSAGES.success);
11329        return;
11330      }
11331
11332      if (!validateEmailField(emailInput, feedback, true)) {
11333        return;
11334      }
11335
11336      submitNewsletter(form, emailInput, submitBtn, feedback);
11337    });
11338  }
11339
11340  function getEmailInput(form) {
11341    return form.querySelector("input[type='email'][name='email']");
11342  }
11343
11344  function getSubmitButton(form) {
11345    return (
11346      form.querySelector("[data-newsletter-submit]") ||
11347      form.querySelector("button[type='submit']")
11348    );
11349  }
11350
11351  function getFeedbackElement(form) {
11352    return (
11353      form.querySelector("[data-newsletter-feedback]") ||
11354      form.querySelector(
11355        ".enisa-newsletter__feedback, .enisa-newsletter-banner__feedback"
11356      )
11357    );
11358  }
11359
11360  function safeFocus(element) {
11361    if (
11362      window.ENISA &&
11363      window.ENISA.utils &&
11364      typeof window.ENISA.utils.safeFocus === "function"
11365    ) {
11366      return window.ENISA.utils.safeFocus(element);
11367    }
11368
11369    if (!element || typeof element.focus !== "function") return false;
11370
11371    try {
11372      element.focus({ preventScroll: true });
11373    } catch (error) {
11374      element.focus();
11375    }
11376
11377    return true;
11378  }
11379
11380  function findLabelByFor(container, id) {
11381    if (!container || !id) return null;
11382
11383    var labels = container.querySelectorAll("label[for]");
11384
11385    for (var i = 0; i < labels.length; i += 1) {
11386      if (labels[i].getAttribute("for") === id) {
11387        return labels[i];
11388      }
11389    }
11390
11391    return null;
11392  }
11393
11394  function findFirstDescribedByTarget(container, input) {
11395    if (!container || !input) return null;
11396
11397    var ids = (input.getAttribute("aria-describedby") || "").split(/\s+/);
11398
11399    for (var i = 0; i < ids.length; i += 1) {
11400      if (!ids[i]) continue;
11401
11402      var candidate = container.querySelector("[id='" + ids[i] + "']");
11403
11404      if (candidate) {
11405        return candidate;
11406      }
11407    }
11408
11409    return null;
11410  }
11411
11412  function buildScopedId(baseId, suffix) {
11413    var normalizedBase = String(baseId || "newsletter").replace(
11414      /[^A-Za-z0-9\-_:.]/g,
11415      "-"
11416    );
11417    var normalizedSuffix = String(suffix || "instance").replace(
11418      /[^A-Za-z0-9\-_:.]/g,
11419      "-"
11420    );
11421
11422    return normalizedBase + "-" + normalizedSuffix;
11423  }
11424
11425  function remapElementId(element, fallbackBase, suffix, idMap) {
11426    if (!element) return "";
11427
11428    var currentId = (element.id || "").trim();
11429    var nextId = buildScopedId(currentId || fallbackBase, suffix);
11430
11431    if (currentId) {
11432      idMap[currentId] = nextId;
11433    }
11434
11435    element.id = nextId;
11436    return nextId;
11437  }
11438
11439  function replaceIdReferences(value, idMap) {
11440    if (!value) return "";
11441
11442    var ids = value.split(/\s+/);
11443    var resolved = [];
11444
11445    ids.forEach(function (id) {
11446      var nextId = idMap[id] || id;
11447
11448      if (nextId && resolved.indexOf(nextId) === -1) {
11449        resolved.push(nextId);
11450      }
11451    });
11452
11453    return resolved.join(" ");
11454  }
11455
11456  function ensureScopedAccessibilityIds(form, emailInput, feedback) {
11457    if (!form || !emailInput || !feedback) return;
11458
11459    formInstanceCount += 1;
11460
11461    var suffix = getSource(form) + "-" + formInstanceCount;
11462    var idMap = {};
11463    var section = form.closest(".enisa-newsletter-banner, .enisa-newsletter");
11464    var title =
11465      section &&
11466      section.querySelector(
11467        ".enisa-newsletter-banner__title, .enisa-newsletter__title"
11468      );
11469    var legal = form.querySelector(
11470      ".enisa-newsletter-banner__legal, .enisa-newsletter__legal"
11471    );
11472    var originalEmailId = emailInput.id;
11473    var emailLabel = findLabelByFor(form, originalEmailId);
11474    var emailHint = findFirstDescribedByTarget(form, emailInput);
11475    var honeypotInput = form.querySelector("[data-newsletter-honeypot]");
11476    var honeypotLabel = honeypotInput
11477      ? findLabelByFor(form, honeypotInput.id)
11478      : null;
11479
11480    remapElementId(title, "newsletter-title", suffix, idMap);
11481    remapElementId(emailHint, "newsletter-email-hint", suffix, idMap);
11482    remapElementId(feedback, "newsletter-feedback", suffix, idMap);
11483    remapElementId(legal, "newsletter-legal", suffix, idMap);
11484    remapElementId(emailInput, "newsletter-email", suffix, idMap);
11485    remapElementId(honeypotInput, "newsletter-website", suffix, idMap);
11486
11487    if (section && section.hasAttribute("aria-labelledby")) {
11488      section.setAttribute(
11489        "aria-labelledby",
11490        replaceIdReferences(section.getAttribute("aria-labelledby"), idMap)
11491      );
11492    }
11493
11494    if (emailLabel) {
11495      emailLabel.setAttribute("for", emailInput.id);
11496    }
11497
11498    if (honeypotLabel && honeypotInput) {
11499      honeypotLabel.setAttribute("for", honeypotInput.id);
11500    }
11501
11502    if (emailInput.hasAttribute("aria-describedby")) {
11503      emailInput.setAttribute(
11504        "aria-describedby",
11505        replaceIdReferences(emailInput.getAttribute("aria-describedby"), idMap)
11506      );
11507    }
11508
11509    if (form.hasAttribute("aria-describedby")) {
11510      form.setAttribute(
11511        "aria-describedby",
11512        replaceIdReferences(form.getAttribute("aria-describedby"), idMap)
11513      );
11514    }
11515  }
11516
11517  function isHoneypotFilled(form) {
11518    var honeypot = form.querySelector("[data-newsletter-honeypot]");
11519    return !!(honeypot && honeypot.value && honeypot.value.trim().length > 0);
11520  }
11521
11522  function normalizeEmail(value) {
11523    return (value || "").replace(/\s+/g, "").trim().toLowerCase();
11524  }
11525
11526  function isValidEmail(value) {
11527    var email = normalizeEmail(value);
11528    return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email);
11529  }
11530
11531  function escapeODataString(value) {
11532    return String(value || "").replace(/'/g, "''");
11533  }
11534
11535  function getSource(form) {
11536    return (
11537      form.getAttribute("data-source") ||
11538      (form.closest(".enisa-newsletter-banner") && "newsletter-banner") ||
11539      (form.closest(".enisa-newsletter") && "newsletter-default") ||
11540      "newsletter"
11541    );
11542  }
11543
11544  function getWebApiTable(form) {
11545    var endpoint = (
11546      form.getAttribute("data-webapi-table") || DEFAULT_WEBAPI_TABLE
11547    ).trim();
11548
11549    return endpoint || DEFAULT_WEBAPI_TABLE;
11550  }
11551
11552  function getCurrentPageUrl() {
11553    return window.location.href || "";
11554  }
11555
11556  function getRecaptchaSiteKey(form) {
11557    return (form.getAttribute("data-recaptcha-site-key") || "").trim();
11558  }
11559
11560  function getRecaptchaAction(form) {
11561    return (form.getAttribute("data-recaptcha-action") || RECAPTCHA_ACTION).trim();
11562  }
11563
11564  function getRecaptchaTokenField(form) {
11565    return (form.getAttribute("data-recaptcha-token-field") || "").trim();
11566  }
11567
11568  function hasRecaptchaEnabled(form) {
11569    return !!getRecaptchaSiteKey(form);
11570  }
11571
11572  function loadRecaptchaApi(siteKey) {
11573    return new Promise(function (resolve, reject) {
11574      var maxAttempts = 80;
11575      var attempt = 0;
11576
11577      function resolveWhenReady() {
11578        attempt += 1;
11579
11580        if (
11581          window.grecaptcha &&
11582          typeof window.grecaptcha.ready === "function" &&
11583          typeof window.grecaptcha.execute === "function"
11584        ) {
11585          window.grecaptcha.ready(function () {
11586            resolve(window.grecaptcha);
11587          });
11588          return;
11589        }
11590
11591        if (attempt >= maxAttempts) {
11592          reject(new Error("reCAPTCHA API unavailable"));
11593          return;
11594        }
11595
11596        window.setTimeout(resolveWhenReady, 100);
11597      }
11598
11599      if (
11600        window.grecaptcha &&
11601        typeof window.grecaptcha.ready === "function" &&
11602        typeof window.grecaptcha.execute === "function"
11603      ) {
11604        window.grecaptcha.ready(function () {
11605          resolve(window.grecaptcha);
11606        });
11607        return;
11608      }
11609
11610      var existingScript = document.getElementById(RECAPTCHA_SCRIPT_ID);
11611
11612      window[RECAPTCHA_ONLOAD_CALLBACK] = function () {
11613        resolveWhenReady();
11614      };
11615
11616      if (existingScript) {
11617        resolveWhenReady();
11618        return;
11619      }
11620
11621      var script = document.createElement("script");
11622      script.id = RECAPTCHA_SCRIPT_ID;
11623      script.src =
11624        "https://www.google.com/recaptcha/api.js?onload=" +
11625        encodeURIComponent(RECAPTCHA_ONLOAD_CALLBACK) +
11626        "&render=" +
11627        encodeURIComponent(siteKey) +
11628        "&hl=es";
11629      script.async = true;
11630      script.defer = true;
11631      script.onerror = function () {
11632        reject(new Error("reCAPTCHA script load error"));
11633      };
11634
11635      document.head.appendChild(script);
11636    });
11637  }
11638
11639  function getRecaptchaToken(form) {
11640    var siteKey = getRecaptchaSiteKey(form);
11641
11642    if (!siteKey) {
11643      return Promise.resolve("");
11644    }
11645
11646    return loadRecaptchaApi(siteKey).then(function (grecaptcha) {
11647      return grecaptcha.execute(siteKey, {
11648        action: getRecaptchaAction(form)
11649      });
11650    });
11651  }
11652
11653  function getButtonLabel(button) {
11654    if (!button) return "";
11655
11656    var label = button.querySelector(".enisa-btn__label");
11657    return (label ? label.textContent : button.textContent || "").trim();
11658  }
11659
11660  function setButtonLabel(button, label) {
11661    if (!button || !label) return;
11662
11663    var labelNode = button.querySelector(".enisa-btn__label");
11664
11665    if (labelNode) {
11666      labelNode.textContent = label;
11667      return;
11668    }
11669
11670    labelNode = document.createElement("span");
11671    labelNode.className = "enisa-btn__label";
11672    labelNode.textContent = label;
11673
11674    button.textContent = "";
11675    button.appendChild(labelNode);
11676  }
11677
11678  function resetSubmitState(submitBtn) {
11679    if (!submitBtn) return;
11680
11681    submitBtn.classList.remove("is-loading", "is-loaded", "is-done", "is-busy");
11682
11683    if (
11684      window.ENISA &&
11685      window.ENISA.utils &&
11686      typeof window.ENISA.utils.resetButtonProgress === "function"
11687    ) {
11688      window.ENISA.utils.resetButtonProgress(submitBtn);
11689    } else {
11690      submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
11691    }
11692
11693    submitBtn.disabled = false;
11694    submitBtn.setAttribute("aria-busy", "false");
11695
11696    var defaultLabel = submitBtn.getAttribute("data-newsletter-default-label");
11697
11698    if (defaultLabel) {
11699      setButtonLabel(submitBtn, defaultLabel);
11700    }
11701  }
11702
11703  function setBusy(form, submitBtn, feedback, isBusy) {
11704    form.classList.toggle("is-loading", isBusy);
11705
11706    if (submitBtn) {
11707      submitBtn.disabled = isBusy;
11708      submitBtn.classList.toggle("is-busy", isBusy);
11709      submitBtn.classList.remove("is-loading", "is-loaded", "is-done");
11710      submitBtn.setAttribute("aria-busy", isBusy ? "true" : "false");
11711
11712      if (isBusy) {
11713        submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
11714      } else if (
11715        window.ENISA &&
11716        window.ENISA.utils &&
11717        typeof window.ENISA.utils.resetButtonProgress === "function"
11718      ) {
11719        window.ENISA.utils.resetButtonProgress(submitBtn);
11720      } else {
11721        submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
11722      }
11723    }
11724
11725    if (isBusy) {
11726      setFeedback(feedback, MESSAGES.loading, "status");
11727    }
11728  }
11729
11730  function completeSubmitProgress(submitBtn) {
11731    if (!submitBtn) return;
11732
11733    submitBtn.disabled = false;
11734    submitBtn.classList.remove("is-loading", "is-loaded", "is-done", "is-busy");
11735
11736    if (
11737      window.ENISA &&
11738      window.ENISA.utils &&
11739      typeof window.ENISA.utils.resetButtonProgress === "function"
11740    ) {
11741      window.ENISA.utils.resetButtonProgress(submitBtn);
11742    } else {
11743      submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
11744    }
11745
11746    setButtonLabel(submitBtn, SENT_SUBMIT_LABEL);
11747    submitBtn.setAttribute("aria-busy", "false");
11748  }
11749
11750  function setFeedback(feedback, message, mode) {
11751    if (!feedback) return;
11752
11753    feedback.textContent = message || "";
11754    feedback.hidden = !message;
11755    feedback.classList.remove("is-error", "is-success");
11756    feedback.removeAttribute("role");
11757
11758    if (mode === "error") {
11759      feedback.classList.add("is-error");
11760      feedback.setAttribute("role", "alert");
11761      return;
11762    }
11763
11764    if (mode === "status" || mode === "success") {
11765      feedback.setAttribute("role", "status");
11766    }
11767
11768    if (mode === "success") {
11769      feedback.classList.add("is-success");
11770    }
11771  }
11772
11773  function getFieldWrapper(input) {
11774    if (!input) return null;
11775
11776    return input.closest(
11777      ".enisa-newsletter__field, .enisa-newsletter-banner__form-row, .enisa-field-group"
11778    );
11779  }
11780
11781  function storeBaseDescribedBy(input, feedback) {
11782    if (!input) return;
11783
11784    var feedbackId = feedback && feedback.id ? feedback.id : "";
11785    var current = (input.getAttribute("aria-describedby") || "").split(/\s+/);
11786    var baseIds = current.filter(function (id) {
11787      return id && id !== feedbackId;
11788    });
11789
11790    input.setAttribute("data-newsletter-base-describedby", baseIds.join(" "));
11791    updateInputDescribedBy(input, feedback, false);
11792  }
11793
11794  function updateInputDescribedBy(input, feedback, includeFeedback) {
11795    if (!input) return;
11796
11797    var ids = [];
11798    var baseIds = (
11799      input.getAttribute("data-newsletter-base-describedby") || ""
11800    ).split(/\s+/);
11801
11802    baseIds.forEach(function (id) {
11803      if (id && ids.indexOf(id) === -1) {
11804        ids.push(id);
11805      }
11806    });
11807
11808    if (
11809      includeFeedback &&
11810      feedback &&
11811      feedback.id &&
11812      ids.indexOf(feedback.id) === -1
11813    ) {
11814      ids.push(feedback.id);
11815    }
11816
11817    if (ids.length) {
11818      input.setAttribute("aria-describedby", ids.join(" "));
11819      return;
11820    }
11821
11822    input.removeAttribute("aria-describedby");
11823  }
11824
11825  function setFieldError(input, feedback, message, shouldFocus) {
11826    input.setAttribute("aria-invalid", "true");
11827    updateInputDescribedBy(input, feedback, true);
11828
11829    var form = input.closest(FORM_SELECTOR);
11830
11831    if (form) {
11832      form.classList.add("is-error");
11833    }
11834
11835    var field = getFieldWrapper(input);
11836
11837    if (field) {
11838      field.classList.add("is-error");
11839    }
11840
11841    setFeedback(feedback, message, "error");
11842
11843    if (shouldFocus) {
11844      safeFocus(input);
11845    }
11846  }
11847
11848  function clearFieldError(input, feedback) {
11849    input.setAttribute("aria-invalid", "false");
11850    updateInputDescribedBy(input, feedback, false);
11851
11852    var form = input.closest(FORM_SELECTOR);
11853
11854    if (form) {
11855      form.classList.remove("is-error");
11856    }
11857
11858    var field = getFieldWrapper(input);
11859
11860    if (field) {
11861      field.classList.remove("is-error");
11862    }
11863
11864    if (feedback && feedback.classList.contains("is-error")) {
11865      setFeedback(feedback, "", null);
11866    }
11867  }
11868
11869  function validateEmailField(input, feedback, shouldFocus) {
11870    var rawValue = input.value || "";
11871    var email = normalizeEmail(rawValue);
11872
11873    if (!email) {
11874      setFieldError(input, feedback, MESSAGES.required, shouldFocus);
11875      return false;
11876    }
11877
11878    if (!isValidEmail(email)) {
11879      setFieldError(input, feedback, MESSAGES.invalid, shouldFocus);
11880      return false;
11881    }
11882
11883    input.value = email;
11884    clearFieldError(input, feedback);
11885    return true;
11886  }
11887
11888  function getRequestVerificationToken() {
11889    if (
11890      window.shell &&
11891      typeof window.shell.getTokenDeferred === "function"
11892    ) {
11893      return new Promise(function (resolve, reject) {
11894        window.shell
11895          .getTokenDeferred()
11896          .done(function (token) {
11897            if (!token) {
11898              reject(new Error("Empty Power Pages security token"));
11899              return;
11900            }
11901
11902            resolve(token);
11903          })
11904          .fail(function () {
11905            reject(new Error("Unable to get Power Pages security token"));
11906          });
11907      });
11908    }
11909
11910    return fetch(TOKEN_ENDPOINT, {
11911      method: "GET",
11912      credentials: "same-origin",
11913      headers: {
11914        Accept: "text/html"
11915      }
11916    })
11917      .then(function (response) {
11918        if (!response.ok) {
11919          throw createWebApiError(
11920            response.status,
11921            "Token request failed: " + response.status
11922          );
11923        }
11924
11925        return response.text();
11926      })
11927      .then(function (html) {
11928        var wrapper = document.createElement("div");
11929        wrapper.innerHTML = html;
11930
11931        var tokenInput = wrapper.querySelector(
11932          "input[name='__RequestVerificationToken']"
11933        );
11934        var token = tokenInput ? tokenInput.value : "";
11935
11936        if (!token) {
11937          throw new Error("Request verification token not found");
11938        }
11939
11940        return token;
11941      });
11942  }
11943
11944  function createWebApiError(status, responseText) {
11945    var error = new Error(
11946      "Newsletter Web API error " + status + ": " + responseText
11947    );
11948    error.status = status;
11949    error.responseText = responseText || "";
11950    return error;
11951  }
11952
11953  function requestWebApi(url, options, token) {
11954    var method = options && options.method ? options.method : "GET";
11955    var payload =
11956      options && Object.prototype.hasOwnProperty.call(options, "payload")
11957        ? options.payload
11958        : null;
11959
11960    var headers = {
11961      Accept: "application/json",
11962      "OData-MaxVersion": "4.0",
11963      "OData-Version": "4.0",
11964      __RequestVerificationToken: token
11965    };
11966
11967    if (method !== "GET") {
11968      headers["Content-Type"] = "application/json; charset=utf-8";
11969    }
11970
11971    if (method === "PATCH") {
11972      headers["If-Match"] = "*";
11973    }
11974
11975    return fetch(url, {
11976      method: method,
11977      credentials: "same-origin",
11978      headers: headers,
11979      body: payload ? JSON.stringify(payload) : undefined
11980    }).then(function (response) {
11981      if (response.ok) {
11982        if (response.status === 204) {
11983          return null;
11984        }
11985
11986        return response.text().then(function (text) {
11987          return text ? JSON.parse(text) : null;
11988        });
11989      }
11990
11991      return response.text().then(function (text) {
11992        throw createWebApiError(response.status, text);
11993      });
11994    });
11995  }
11996
11997  function findExistingSubscription(endpoint, email, token) {
11998    var normalizedEmail = normalizeEmail(email);
11999    var filter =
12000      EMAIL_NORMALIZED_FIELD + " eq '" + escapeODataString(normalizedEmail) + "'";
12001    var select = [ID_FIELD, EMAIL_NORMALIZED_FIELD, STATUS_FIELD].join(",");
12002
12003    var url =
12004      endpoint +
12005      "?$select=" +
12006      encodeURIComponent(select) +
12007      "&$filter=" +
12008      encodeURIComponent(filter) +
12009      "&$top=1";
12010
12011    return requestWebApi(url, { method: "GET" }, token).then(function (response) {
12012      var records = response && response.value ? response.value : [];
12013      return records.length ? records[0] : null;
12014    });
12015  }
12016
12017  function buildCreatePayload(form, email, recaptchaToken) {
12018    var payload = {};
12019
12020    payload[EMAIL_FIELD] = email;
12021    payload[EMAIL_NORMALIZED_FIELD] = email;
12022    payload[PAGE_URL_FIELD] = getCurrentPageUrl();
12023    payload[PRIVACY_FIELD] = true;
12024    payload[SOURCE_FIELD] = getSource(form);
12025    payload[SUBSCRIBED_ON_FIELD] = new Date().toISOString();
12026    payload[STATUS_FIELD] = STATUS_ACTIVE;
12027
12028    var recaptchaTokenField = getRecaptchaTokenField(form);
12029
12030    if (recaptchaToken && recaptchaTokenField) {
12031      payload[recaptchaTokenField] = recaptchaToken;
12032    }
12033
12034    return payload;
12035  }
12036
12037  function buildReactivatePayload(form, recaptchaToken) {
12038    var payload = {};
12039
12040    payload[PAGE_URL_FIELD] = getCurrentPageUrl();
12041    payload[PRIVACY_FIELD] = true;
12042    payload[SOURCE_FIELD] = getSource(form);
12043    payload[SUBSCRIBED_ON_FIELD] = new Date().toISOString();
12044    payload[STATUS_FIELD] = STATUS_ACTIVE;
12045
12046    var recaptchaTokenField = getRecaptchaTokenField(form);
12047
12048    if (recaptchaToken && recaptchaTokenField) {
12049      payload[recaptchaTokenField] = recaptchaToken;
12050    }
12051
12052    return payload;
12053  }
12054
12055  function createSubscription(endpoint, payload, token) {
12056    return requestWebApi(
12057      endpoint,
12058      {
12059        method: "POST",
12060        payload: payload
12061      },
12062      token
12063    );
12064  }
12065
12066  function updateSubscription(endpoint, recordId, payload, token) {
12067    return requestWebApi(
12068      endpoint + "(" + recordId + ")",
12069      {
12070        method: "PATCH",
12071        payload: payload
12072      },
12073      token
12074    );
12075  }
12076
12077  function isActiveSubscription(record) {
12078    return Number(record && record[STATUS_FIELD]) === STATUS_ACTIVE;
12079  }
12080
12081  function isUnsubscribedSubscription(record) {
12082    return Number(record && record[STATUS_FIELD]) === STATUS_UNSUBSCRIBED;
12083  }
12084
12085  function isDuplicateError(error) {
12086    if (!error) return false;
12087
12088    var status = Number(error.status);
12089    var text = String(error.responseText || error.message || "").toLowerCase();
12090
12091    return (
12092      status === 409 ||
12093      status === 412 ||
12094      text.indexOf("duplicate") !== -1 ||
12095      text.indexOf("alternate key") !== -1 ||
12096      text.indexOf("already exists") !== -1 ||
12097      text.indexOf("ya existe") !== -1
12098    );
12099  }
12100
12101  function submitNewsletter(form, emailInput, submitBtn, feedback) {
12102    var email = normalizeEmail(emailInput.value);
12103    var endpoint = getWebApiTable(form);
12104
12105    setBusy(form, submitBtn, feedback, true);
12106
12107    getRecaptchaToken(form)
12108      .then(function (recaptchaToken) {
12109        return getRequestVerificationToken().then(function (token) {
12110          return {
12111            recaptchaToken: recaptchaToken,
12112            token: token
12113          };
12114        });
12115      })
12116      .then(function (context) {
12117        var recaptchaToken = context.recaptchaToken;
12118        var token = context.token;
12119
12120        return findExistingSubscription(endpoint, email, token).then(function (
12121          existingRecord
12122        ) {
12123          if (existingRecord && isActiveSubscription(existingRecord)) {
12124            renderDuplicate(form, emailInput, submitBtn, feedback);
12125            return null;
12126          }
12127
12128          if (existingRecord && isUnsubscribedSubscription(existingRecord)) {
12129            return updateSubscription(
12130              endpoint,
12131              existingRecord[ID_FIELD],
12132              buildReactivatePayload(form, recaptchaToken),
12133              token
12134            ).then(function () {
12135              renderSuccess(
12136                form,
12137                emailInput,
12138                submitBtn,
12139                feedback,
12140                MESSAGES.reactivated
12141              );
12142            });
12143          }
12144
12145          if (existingRecord) {
12146            return updateSubscription(
12147              endpoint,
12148              existingRecord[ID_FIELD],
12149              buildReactivatePayload(form, recaptchaToken),
12150              token
12151            ).then(function () {
12152              renderSuccess(
12153                form,
12154                emailInput,
12155                submitBtn,
12156                feedback,
12157                MESSAGES.reactivated
12158              );
12159            });
12160          }
12161
12162          return createSubscription(
12163            endpoint,
12164            buildCreatePayload(form, email, recaptchaToken),
12165            token
12166          )
12167            .then(function () {
12168              renderSuccess(
12169                form,
12170                emailInput,
12171                submitBtn,
12172                feedback,
12173                MESSAGES.success
12174              );
12175            })
12176            .catch(function (error) {
12177              if (isDuplicateError(error)) {
12178                renderDuplicate(form, emailInput, submitBtn, feedback);
12179                return null;
12180              }
12181
12182              throw error;
12183            });
12184        });
12185      })
12186      .catch(function (error) {
12187        if (window.console) {
12188          window.console.error("[ENISA newsletter]", error);
12189        }
12190
12191        if (isDuplicateError(error)) {
12192          renderDuplicate(form, emailInput, submitBtn, feedback);
12193          return;
12194        }
12195
12196        if (hasRecaptchaEnabled(form)) {
12197          var errorMessage = String(error && error.message ? error.message : "").toLowerCase();
12198
12199          if (
12200            errorMessage.indexOf("recaptcha") !== -1 ||
12201            errorMessage.indexOf("captcha") !== -1
12202          ) {
12203            renderSubmitError(form, emailInput, submitBtn, feedback, MESSAGES.recaptcha);
12204            return;
12205          }
12206        }
12207
12208        renderSubmitError(form, emailInput, submitBtn, feedback);
12209      });
12210  }
12211
12212  function renderSuccess(form, emailInput, submitBtn, feedback, message) {
12213    setBusy(form, submitBtn, feedback, false);
12214
12215    form.classList.add("is-success");
12216    form.classList.remove("is-duplicate", "is-submit-error", "is-error");
12217    setFeedback(feedback, message || MESSAGES.success, "success");
12218
12219    if (emailInput) {
12220      emailInput.value = "";
12221      emailInput.setAttribute("aria-invalid", "false");
12222
12223      var field = getFieldWrapper(emailInput);
12224
12225      if (field) {
12226        field.classList.remove("is-error");
12227      }
12228    }
12229
12230    completeSubmitProgress(submitBtn);
12231  }
12232
12233  function renderDuplicate(form, emailInput, submitBtn, feedback) {
12234    setBusy(form, submitBtn, feedback, false);
12235    resetSubmitState(submitBtn);
12236
12237    form.classList.remove("is-success", "is-submit-error");
12238    form.classList.add("is-duplicate");
12239    setFieldError(emailInput, feedback, MESSAGES.duplicated, true);
12240  }
12241
12242  function renderSubmitError(form, emailInput, submitBtn, feedback, message) {
12243    setBusy(form, submitBtn, feedback, false);
12244    resetSubmitState(submitBtn);
12245
12246    form.classList.remove("is-success", "is-duplicate");
12247    form.classList.add("is-submit-error");
12248    setFieldError(emailInput, feedback, message || MESSAGES.error, true);
12249  }
12250
12251  window.ENISA = window.ENISA || {};
12252  window.ENISA.newsletter = {
12253    init: init
12254  };
12255
12256  if (document.readyState === "loading") {
12257    document.addEventListener("DOMContentLoaded", init);
12258  } else {
12259    init();
12260  }
12261})();
12262
12263/*
12264  ENISA | JS Module
12265  Modulo: 28-newsletter-unsubscribe
12266  Descripcion: Gestiona la baja del boletin y actualiza el estado funcional de la suscripcion en Dataverse mediante Power Pages Web API.
12267*/
12268(function () {
12269  "use strict";
12270
12271  var ROOT_SELECTOR = "[data-newsletter-unsubscribe]";
12272  var INIT_ATTR = "data-newsletter-unsubscribe-init";
12273  var DEFAULT_WEBAPI_TABLE = "/_api/enisa_newslettersubscriptions";
12274  var TOKEN_ENDPOINT = "/_layout/tokenhtml";
12275
12276  var ID_FIELD = "enisa_newslettersubscriptionid";
12277  var EMAIL_NORMALIZED_FIELD = "enisa_emailnormalized";
12278  var STATUS_FIELD = "enisa_subscriptionstatus";
12279  var SOURCE_FIELD = "enisa_source";
12280  var PAGE_URL_FIELD = "enisa_pageurl";
12281
12282  var STATUS_ACTIVE = 694380000;
12283  var STATUS_UNSUBSCRIBED = 694380001;
12284
12285  var MESSAGES = {
12286    required: "Introduce tu correo electrónico.",
12287    invalid: "Introduce un correo electrónico válido.",
12288    loading: "Estamos procesando tu solicitud...",
12289    success: "La baja de tu suscripción se ha tramitado correctamente. Revisa tu buzón de correo electrónico.",
12290    notFound: "Lo sentimos, este correo no consta como suscrito a nuestro boletín.",
12291    alreadyUnsubscribed: "Este correo ya estaba dado de baja del boletín.",
12292    error: "No hemos podido procesar la solicitud en este momento. Inténtalo de nuevo más tarde."
12293  };
12294  var SENT_SUBMIT_LABEL = "Enviado";
12295
12296  function init() {
12297    var roots = [].slice.call(document.querySelectorAll(ROOT_SELECTOR));
12298    if (!roots.length) return;
12299
12300    roots.forEach(initUnsubscribeComponent);
12301  }
12302
12303  function initUnsubscribeComponent(root) {
12304    if (!root || root.getAttribute(INIT_ATTR) === "true") return;
12305
12306    var form = getForm(root);
12307    var emailInput = getEmailInput(root);
12308    var submitBtn = getSubmitButton(root);
12309    var feedback = getFeedbackElement(root);
12310
12311    if (!form || !emailInput || !submitBtn || !feedback) return;
12312
12313    root.setAttribute(INIT_ATTR, "true");
12314
12315    if (!feedback.hasAttribute("aria-live")) {
12316      feedback.setAttribute("aria-live", "polite");
12317    }
12318
12319    if (!feedback.hasAttribute("role")) {
12320      feedback.setAttribute("role", "status");
12321    }
12322
12323    feedback.setAttribute("aria-atomic", "true");
12324    updateInputDescribedBy(emailInput, feedback, false);
12325    submitBtn.setAttribute("data-newsletter-unsubscribe-default-label", getButtonLabel(submitBtn));
12326
12327    emailInput.addEventListener("input", function () {
12328      root.classList.remove("is-success", "is-not-found", "is-submit-error", "is-already-unsubscribed", "is-error");
12329      clearFieldError(emailInput, feedback);
12330      resetSubmitState(submitBtn);
12331    });
12332
12333    emailInput.addEventListener("blur", function () {
12334      validateEmailField(emailInput, feedback, false);
12335    });
12336
12337    form.addEventListener("submit", function (event) {
12338      event.preventDefault();
12339
12340      root.classList.remove("is-success", "is-not-found", "is-submit-error", "is-already-unsubscribed", "is-error");
12341      resetSubmitState(submitBtn);
12342
12343      if (isHoneypotFilled(root)) {
12344        renderSuccess(root, form, emailInput, submitBtn, feedback, MESSAGES.success);
12345        return;
12346      }
12347
12348      if (!validateEmailField(emailInput, feedback, true)) {
12349        return;
12350      }
12351
12352      submitUnsubscribe(root, form, emailInput, submitBtn, feedback);
12353    });
12354  }
12355
12356  function getForm(root) {
12357    return root.querySelector("[data-newsletter-unsubscribe-form]");
12358  }
12359
12360  function getEmailInput(root) {
12361    return root.querySelector("[data-newsletter-unsubscribe-email]") || root.querySelector("input[type='email'][name='email']");
12362  }
12363
12364  function getSubmitButton(root) {
12365    return root.querySelector("[data-newsletter-unsubscribe-submit]") || root.querySelector("button[type='submit']");
12366  }
12367
12368  function getFeedbackElement(root) {
12369    return root.querySelector("[data-newsletter-unsubscribe-feedback]") || root.querySelector(".enisa-newsletter-unsubscribe__feedback");
12370  }
12371
12372  function safeFocus(element) {
12373    if (
12374      window.ENISA &&
12375      window.ENISA.utils &&
12376      typeof window.ENISA.utils.safeFocus === "function"
12377    ) {
12378      return window.ENISA.utils.safeFocus(element);
12379    }
12380
12381    if (!element || typeof element.focus !== "function") return false;
12382
12383    try {
12384      element.focus({ preventScroll: true });
12385    } catch (error) {
12386      element.focus();
12387    }
12388
12389    return true;
12390  }
12391
12392  function isHoneypotFilled(root) {
12393    var honeypot = root.querySelector("[data-newsletter-honeypot]");
12394    return !!(honeypot && honeypot.value && honeypot.value.trim().length > 0);
12395  }
12396
12397  function normalizeEmail(value) {
12398    return (value || "").replace(/\s+/g, "").trim().toLowerCase();
12399  }
12400
12401  function isValidEmail(value) {
12402    var email = normalizeEmail(value);
12403    return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(email);
12404  }
12405
12406  function escapeODataString(value) {
12407    return String(value || "").replace(/'/g, "''");
12408  }
12409
12410  function getWebApiTable(root) {
12411    var endpoint = (root.getAttribute("data-webapi-table") || DEFAULT_WEBAPI_TABLE).trim();
12412    return endpoint || DEFAULT_WEBAPI_TABLE;
12413  }
12414
12415  function getSource(root) {
12416    return (
12417      root.getAttribute("data-source") ||
12418      "newsletter-unsubscribe"
12419    );
12420  }
12421
12422  function getCurrentPageUrl() {
12423    return window.location.href || "";
12424  }
12425
12426  function getStatusValue(root) {
12427    var rawValue = root.getAttribute("data-status-value") || String(STATUS_UNSUBSCRIBED);
12428    var normalizedValue = String(rawValue).trim().replace(/\./g, "");
12429
12430    if (!/^-?\d+$/.test(normalizedValue)) {
12431      return STATUS_UNSUBSCRIBED;
12432    }
12433
12434    return Number(normalizedValue);
12435  }
12436
12437  function getButtonLabel(button) {
12438    if (!button) return "";
12439
12440    var label = button.querySelector(".enisa-btn__label");
12441    return (label ? label.textContent : button.textContent || "").trim();
12442  }
12443
12444  function setButtonLabel(button, label) {
12445    if (!button || !label) return;
12446
12447    var labelNode = button.querySelector(".enisa-btn__label");
12448
12449    if (labelNode) {
12450      labelNode.textContent = label;
12451      return;
12452    }
12453
12454    labelNode = document.createElement("span");
12455    labelNode.className = "enisa-btn__label";
12456    labelNode.textContent = label;
12457
12458    button.textContent = "";
12459    button.appendChild(labelNode);
12460  }
12461
12462  function resetSubmitState(submitBtn) {
12463    if (!submitBtn) return;
12464
12465    submitBtn.classList.remove("is-loading", "is-loaded", "is-done", "is-busy");
12466
12467    if (window.ENISA && window.ENISA.utils && typeof window.ENISA.utils.resetButtonProgress === "function") {
12468      window.ENISA.utils.resetButtonProgress(submitBtn);
12469    } else {
12470      submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
12471    }
12472
12473    submitBtn.disabled = false;
12474    submitBtn.setAttribute("aria-busy", "false");
12475
12476    var defaultLabel = submitBtn.getAttribute("data-newsletter-unsubscribe-default-label");
12477    if (defaultLabel) {
12478      setButtonLabel(submitBtn, defaultLabel);
12479    }
12480  }
12481
12482  function setBusy(root, submitBtn, feedback, isBusy) {
12483    root.classList.toggle("is-loading", isBusy);
12484
12485    if (submitBtn) {
12486      submitBtn.disabled = isBusy;
12487      submitBtn.classList.toggle("is-busy", isBusy);
12488      submitBtn.classList.remove("is-loading", "is-loaded", "is-done");
12489      submitBtn.setAttribute("aria-busy", isBusy ? "true" : "false");
12490
12491      if (isBusy) {
12492        submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
12493      } else {
12494        if (window.ENISA && window.ENISA.utils && typeof window.ENISA.utils.resetButtonProgress === "function") {
12495          window.ENISA.utils.resetButtonProgress(submitBtn);
12496        } else {
12497          submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
12498        }
12499      }
12500    }
12501
12502    if (isBusy) {
12503      setFeedback(feedback, MESSAGES.loading, "status");
12504    }
12505  }
12506
12507  function completeSubmitProgress(submitBtn) {
12508    if (!submitBtn) return;
12509
12510    submitBtn.disabled = false;
12511    submitBtn.classList.remove("is-loading", "is-loaded", "is-done", "is-busy");
12512
12513    if (window.ENISA && window.ENISA.utils && typeof window.ENISA.utils.resetButtonProgress === "function") {
12514      window.ENISA.utils.resetButtonProgress(submitBtn);
12515    } else {
12516      submitBtn.style.removeProperty("--enisa-btn-loader-overlay-w");
12517    }
12518
12519    setButtonLabel(submitBtn, SENT_SUBMIT_LABEL);
12520    submitBtn.setAttribute("aria-busy", "false");
12521  }
12522
12523  function setFeedback(feedback, message, mode) {
12524    if (!feedback) return;
12525
12526    feedback.textContent = message || "";
12527    feedback.hidden = !message;
12528    feedback.classList.remove("is-error", "is-success");
12529    feedback.removeAttribute("role");
12530
12531    if (mode === "error") {
12532      feedback.classList.add("is-error");
12533      feedback.setAttribute("role", "alert");
12534      return;
12535    }
12536
12537    if (mode === "status" || mode === "success") {
12538      feedback.setAttribute("role", "status");
12539    }
12540
12541    if (mode === "success") {
12542      feedback.classList.add("is-success");
12543    }
12544  }
12545
12546  function getFieldWrapper(input) {
12547    if (!input) return null;
12548    return input.closest(".enisa-newsletter-unsubscribe__form-row, .enisa-field-group");
12549  }
12550
12551  function updateInputDescribedBy(input, feedback, includeFeedback) {
12552    if (!input || !feedback || !feedback.id) return;
12553
12554    var ids = (input.getAttribute("aria-describedby") || "")
12555      .split(/\s+/)
12556      .filter(function (id) {
12557        return id && id !== feedback.id;
12558      });
12559
12560    if (includeFeedback) {
12561      ids.push(feedback.id);
12562    }
12563
12564    if (ids.length) {
12565      input.setAttribute("aria-describedby", ids.join(" "));
12566      return;
12567    }
12568
12569    input.removeAttribute("aria-describedby");
12570  }
12571
12572  function setFieldError(input, feedback, message, shouldFocus) {
12573    input.setAttribute("aria-invalid", "true");
12574    updateInputDescribedBy(input, feedback, true);
12575
12576    var root = input.closest(ROOT_SELECTOR);
12577    if (root) {
12578      root.classList.add("is-error");
12579    }
12580
12581    var field = getFieldWrapper(input);
12582    if (field) {
12583      field.classList.add("is-error");
12584    }
12585
12586    setFeedback(feedback, message, "error");
12587
12588    if (shouldFocus) {
12589      safeFocus(input);
12590    }
12591  }
12592
12593  function clearFieldError(input, feedback) {
12594    input.setAttribute("aria-invalid", "false");
12595    updateInputDescribedBy(input, feedback, false);
12596
12597    var root = input.closest(ROOT_SELECTOR);
12598    if (root) {
12599      root.classList.remove("is-error");
12600    }
12601
12602    var field = getFieldWrapper(input);
12603    if (field) {
12604      field.classList.remove("is-error");
12605    }
12606
12607    if (feedback && feedback.classList.contains("is-error")) {
12608      setFeedback(feedback, "", null);
12609    }
12610  }
12611
12612  function validateEmailField(input, feedback, shouldFocus) {
12613    var rawValue = input.value || "";
12614    var email = normalizeEmail(rawValue);
12615
12616    if (!email) {
12617      setFieldError(input, feedback, MESSAGES.required, shouldFocus);
12618      return false;
12619    }
12620
12621    if (!isValidEmail(email)) {
12622      setFieldError(input, feedback, MESSAGES.invalid, shouldFocus);
12623      return false;
12624    }
12625
12626    input.value = email;
12627    clearFieldError(input, feedback);
12628    return true;
12629  }
12630
12631  function getRequestVerificationToken() {
12632    if (window.shell && typeof window.shell.getTokenDeferred === "function") {
12633      return new Promise(function (resolve, reject) {
12634        window.shell.getTokenDeferred()
12635          .done(function (token) {
12636            if (!token) {
12637              reject(new Error("Request verification token not found"));
12638              return;
12639            }
12640
12641            resolve(token);
12642          })
12643          .fail(function () {
12644            reject(new Error("Unable to get Power Pages security token"));
12645          });
12646      });
12647    }
12648
12649    return fetch(TOKEN_ENDPOINT, {
12650      method: "GET",
12651      credentials: "same-origin",
12652      headers: {
12653        "Accept": "text/html"
12654      }
12655    })
12656      .then(function (response) {
12657        if (!response.ok) {
12658          throw createWebApiError(response.status, "Token request failed: " + response.status);
12659        }
12660
12661        return response.text();
12662      })
12663      .then(function (html) {
12664        var wrapper = document.createElement("div");
12665        wrapper.innerHTML = html;
12666
12667        var tokenInput = wrapper.querySelector("input[name='__RequestVerificationToken']");
12668        var token = tokenInput ? tokenInput.value : "";
12669
12670        if (!token) {
12671          throw new Error("Request verification token not found");
12672        }
12673
12674        return token;
12675      });
12676  }
12677
12678  function createWebApiError(status, responseText) {
12679    var error = new Error("Newsletter unsubscribe Web API error " + status + ": " + responseText);
12680    error.status = status;
12681    error.responseText = responseText || "";
12682    return error;
12683  }
12684
12685  function requestWebApi(url, options, token) {
12686    var method = options && options.method ? options.method : "GET";
12687    var payload = options && Object.prototype.hasOwnProperty.call(options, "payload")
12688      ? options.payload
12689      : null;
12690
12691    var headers = {
12692      "Accept": "application/json",
12693      "OData-MaxVersion": "4.0",
12694      "OData-Version": "4.0",
12695      "__RequestVerificationToken": token
12696    };
12697
12698    if (method !== "GET") {
12699      headers["Content-Type"] = "application/json; charset=utf-8";
12700    }
12701
12702    if (method === "PATCH") {
12703      headers["If-Match"] = "*";
12704    }
12705
12706    return fetch(url, {
12707      method: method,
12708      credentials: "same-origin",
12709      headers: headers,
12710      body: payload ? JSON.stringify(payload) : undefined
12711    })
12712      .then(function (response) {
12713        if (response.ok) {
12714          if (response.status === 204) {
12715            return null;
12716          }
12717
12718          return response.text().then(function (text) {
12719            return text ? JSON.parse(text) : null;
12720          });
12721        }
12722
12723        return response.text().then(function (text) {
12724          throw createWebApiError(response.status, text);
12725        });
12726      });
12727  }
12728
12729  function findExistingSubscription(endpoint, email, token) {
12730    var normalizedEmail = normalizeEmail(email);
12731    var filter = EMAIL_NORMALIZED_FIELD + " eq '" + escapeODataString(normalizedEmail) + "'";
12732    var select = [
12733      ID_FIELD,
12734      EMAIL_NORMALIZED_FIELD,
12735      STATUS_FIELD
12736    ].join(",");
12737
12738    var url = endpoint
12739      + "?$select=" + encodeURIComponent(select)
12740      + "&$filter=" + encodeURIComponent(filter)
12741      + "&$top=1";
12742
12743    return requestWebApi(url, { method: "GET" }, token)
12744      .then(function (response) {
12745        var records = response && response.value ? response.value : [];
12746        return records.length ? records[0] : null;
12747      });
12748  }
12749
12750  function buildUnsubscribePayload(root) {
12751    var payload = {};
12752
12753    payload[STATUS_FIELD] = getStatusValue(root);
12754    payload[SOURCE_FIELD] = getSource(root);
12755    payload[PAGE_URL_FIELD] = getCurrentPageUrl();
12756
12757    return payload;
12758  }
12759
12760  function unsubscribeSubscription(root, endpoint, recordId, token) {
12761    return requestWebApi(endpoint + "(" + recordId + ")", {
12762      method: "PATCH",
12763      payload: buildUnsubscribePayload(root)
12764    }, token);
12765  }
12766
12767  function isUnsubscribed(record, root) {
12768    return Number(record && record[STATUS_FIELD]) === getStatusValue(root);
12769  }
12770
12771  function submitUnsubscribe(root, form, emailInput, submitBtn, feedback) {
12772    var email = normalizeEmail(emailInput.value);
12773    var endpoint = getWebApiTable(root);
12774
12775    setBusy(root, submitBtn, feedback, true);
12776
12777    getRequestVerificationToken()
12778      .then(function (token) {
12779        return findExistingSubscription(endpoint, email, token)
12780          .then(function (existingRecord) {
12781            if (!existingRecord) {
12782              renderNotFound(root, emailInput, submitBtn, feedback);
12783              return null;
12784            }
12785
12786            if (isUnsubscribed(existingRecord, root)) {
12787              renderAlreadyUnsubscribed(root, form, emailInput, submitBtn, feedback);
12788              return null;
12789            }
12790
12791            return unsubscribeSubscription(
12792              root,
12793              endpoint,
12794              existingRecord[ID_FIELD],
12795              token
12796            ).then(function () {
12797              renderSuccess(root, form, emailInput, submitBtn, feedback, MESSAGES.success);
12798            });
12799          });
12800      })
12801      .catch(function (error) {
12802        if (window.console) {
12803          window.console.error("[ENISA newsletter unsubscribe]", error);
12804        }
12805
12806        renderSubmitError(root, emailInput, submitBtn, feedback);
12807      });
12808  }
12809
12810  function renderSuccess(root, form, emailInput, submitBtn, feedback, message) {
12811    setBusy(root, submitBtn, feedback, false);
12812
12813    root.classList.add("is-success");
12814    root.classList.remove("is-not-found", "is-submit-error", "is-already-unsubscribed", "is-error");
12815    setFeedback(feedback, message || MESSAGES.success, "success");
12816
12817    if (form && typeof form.reset === "function") {
12818      form.reset();
12819    }
12820
12821    if (emailInput) {
12822      emailInput.setAttribute("aria-invalid", "false");
12823
12824      var field = getFieldWrapper(emailInput);
12825      if (field) {
12826        field.classList.remove("is-error");
12827      }
12828    }
12829
12830    completeSubmitProgress(submitBtn);
12831  }
12832
12833  function renderAlreadyUnsubscribed(root, form, emailInput, submitBtn, feedback) {
12834    setBusy(root, submitBtn, feedback, false);
12835
12836    root.classList.add("is-success", "is-already-unsubscribed");
12837    root.classList.remove("is-not-found", "is-submit-error", "is-error");
12838    setFeedback(feedback, MESSAGES.alreadyUnsubscribed, "success");
12839
12840    if (form && typeof form.reset === "function") {
12841      form.reset();
12842    }
12843
12844    if (emailInput) {
12845      emailInput.setAttribute("aria-invalid", "false");
12846
12847      var field = getFieldWrapper(emailInput);
12848      if (field) {
12849        field.classList.remove("is-error");
12850      }
12851    }
12852
12853    completeSubmitProgress(submitBtn);
12854  }
12855
12856  function renderNotFound(root, emailInput, submitBtn, feedback) {
12857    setBusy(root, submitBtn, feedback, false);
12858    resetSubmitState(submitBtn);
12859
12860    root.classList.remove("is-success", "is-submit-error", "is-already-unsubscribed");
12861    root.classList.add("is-not-found");
12862    setFieldError(emailInput, feedback, MESSAGES.notFound, true);
12863  }
12864
12865  function renderSubmitError(root, emailInput, submitBtn, feedback) {
12866    setBusy(root, submitBtn, feedback, false);
12867    resetSubmitState(submitBtn);
12868
12869    root.classList.remove("is-success", "is-not-found", "is-already-unsubscribed");
12870    root.classList.add("is-submit-error");
12871    setFieldError(emailInput, feedback, MESSAGES.error, true);
12872  }
12873
12874  window.ENISA = window.ENISA || {};
12875  window.ENISA.newsletterUnsubscribe = {
12876    init: init
12877  };
12878
12879  if (document.readyState === "loading") {
12880    document.addEventListener("DOMContentLoaded", init);
12881  } else {
12882    init();
12883  }
12884})();
12885
12886/*
12887  ENISA | JS Module
12888  Modulo: 29-form-contact
12889  Descripcion: Gestion del formulario de contacto con Power Pages Web API, adjuntos como notas y validacion accesible.
12890*/
12891(function () {
12892  "use strict";
12893
12894  var ROOT_SELECTOR = "[data-cmp-contacto]";
12895  var INIT_ATTR = "data-contacto-init";
12896  var RECAPTCHA_SCRIPT_ID = "enisa-google-recaptcha-api";
12897  var RECAPTCHA_READY_EVENT = "enisa:recaptcha-ready";
12898
12899  var PRIMARY_NAME_FIELD = "enisa_name";
12900
12901  var CONTACT_FIELDS = {
12902    name: PRIMARY_NAME_FIELD,
12903    lastname: "enisa_lastname",
12904    email: "enisa_email",
12905    departmentKey: "enisa_departmentkey",
12906    departmentLabel: "enisa_departmentlabel",
12907    message: "enisa_message",
12908    privacyAccepted: "enisa_privacyaccepted",
12909    targetEmail: "enisa_targetemail",
12910  };
12911
12912  var DEFAULT_ERROR_MESSAGE =
12913    "No se ha podido enviar el formulario. Por favor, inténtalo de nuevo.";
12914  var DEFAULT_SUCCESS_MESSAGE =
12915    "Tu consulta se ha enviado correctamente. Revisa tu buzón de correo electrónico.";
12916  var DEFAULT_SUBMIT_LABEL = "Enviar consulta";
12917  var SUBMITTING_SUBMIT_LABEL = "Enviando";
12918  var SENT_SUBMIT_LABEL = "Enviado";
12919  var MAX_FILE_SIZE_FALLBACK = 10485760;
12920
12921  function getButtonLabel(button) {
12922    if (!button) return "";
12923
12924    var label = button.querySelector(".enisa-btn__label");
12925    return (label ? label.textContent : button.textContent || "").trim();
12926  }
12927
12928  function setButtonLabel(button, label) {
12929    if (!button || !label) return;
12930
12931    var labelNode = button.querySelector(".enisa-btn__label");
12932
12933    if (labelNode) {
12934      labelNode.textContent = label;
12935      return;
12936    }
12937
12938    labelNode = document.createElement("span");
12939    labelNode.className = "enisa-btn__label";
12940    labelNode.textContent = label;
12941
12942    button.textContent = "";
12943    button.appendChild(labelNode);
12944  }
12945
12946  function clearSubmitProgressTimer(button) {
12947    if (!button || !button.__enisaContactProgressTimer) return;
12948
12949    window.clearInterval(button.__enisaContactProgressTimer);
12950    button.__enisaContactProgressTimer = null;
12951  }
12952
12953  function resetSubmitProgress(button) {
12954    if (!button) return;
12955
12956    clearSubmitProgressTimer(button);
12957    button.style.removeProperty("--enisa-btn-loader-overlay-w");
12958  }
12959
12960  function startSubmitProgress(button) {
12961    var current = 8;
12962    var target = 86;
12963
12964    if (!button) return;
12965
12966    resetSubmitProgress(button);
12967    button.style.setProperty("--enisa-btn-loader-overlay-w", current + "%");
12968
12969    button.__enisaContactProgressTimer = window.setInterval(function () {
12970      current = Math.min(
12971        target,
12972        current + Math.max((target - current) * 0.18, 1.4),
12973      );
12974
12975      button.style.setProperty(
12976        "--enisa-btn-loader-overlay-w",
12977        current.toFixed(2) + "%",
12978      );
12979
12980      if (current >= target) {
12981        clearSubmitProgressTimer(button);
12982      }
12983    }, 160);
12984  }
12985
12986  function completeSubmitProgress(button) {
12987    if (!button) return;
12988
12989    clearSubmitProgressTimer(button);
12990    button.style.setProperty("--enisa-btn-loader-overlay-w", "100%");
12991  }
12992
12993  function setSubmitButtonState(button, state) {
12994    if (!button) return;
12995
12996    if (!button.hasAttribute("data-contact-submit-default-label")) {
12997      button.setAttribute(
12998        "data-contact-submit-default-label",
12999        getButtonLabel(button) || DEFAULT_SUBMIT_LABEL,
13000      );
13001    }
13002
13003    button.classList.add("enisa-btn--progress");
13004
13005    if (state === "submitting") {
13006      button.disabled = true;
13007      button.classList.add("is-loading");
13008      button.classList.remove("is-loaded", "is-done");
13009      button.setAttribute("aria-busy", "true");
13010      setButtonLabel(button, SUBMITTING_SUBMIT_LABEL);
13011      startSubmitProgress(button);
13012      return;
13013    }
13014
13015    if (state === "success") {
13016      button.disabled = true;
13017      button.classList.remove("is-loading");
13018      button.classList.add("is-loaded", "is-done");
13019      button.setAttribute("aria-busy", "false");
13020      setButtonLabel(button, SENT_SUBMIT_LABEL);
13021      completeSubmitProgress(button);
13022      return;
13023    }
13024
13025    button.disabled = false;
13026    button.classList.remove("is-loading", "is-loaded", "is-done");
13027    button.setAttribute("aria-busy", "false");
13028
13029    setButtonLabel(
13030      button,
13031      button.getAttribute("data-contact-submit-default-label") ||
13032        DEFAULT_SUBMIT_LABEL,
13033    );
13034
13035    resetSubmitProgress(button);
13036  }
13037
13038  function dispatchRecaptchaReady() {
13039    document.dispatchEvent(new CustomEvent(RECAPTCHA_READY_EVENT));
13040  }
13041
13042  function loadRecaptchaScript() {
13043    if (
13044      window.grecaptcha &&
13045      typeof window.grecaptcha.render === "function"
13046    ) {
13047      dispatchRecaptchaReady();
13048      return;
13049    }
13050
13051    if (document.getElementById(RECAPTCHA_SCRIPT_ID)) return;
13052
13053    window.enisaRecaptchaOnload = function () {
13054      dispatchRecaptchaReady();
13055    };
13056
13057    var script = document.createElement("script");
13058    script.id = RECAPTCHA_SCRIPT_ID;
13059    script.src =
13060      "https://www.google.com/recaptcha/api.js?onload=enisaRecaptchaOnload&render=explicit&hl=es";
13061    script.async = true;
13062    script.defer = true;
13063
13064    document.head.appendChild(script);
13065  }
13066
13067  function ready(callback) {
13068    if (document.readyState === "loading") {
13069      document.addEventListener("DOMContentLoaded", callback);
13070      return;
13071    }
13072
13073    callback();
13074  }
13075
13076  function debounce(fn, delay) {
13077    var timer = null;
13078
13079    return function () {
13080      var args = arguments;
13081      var ctx = this;
13082
13083      window.clearTimeout(timer);
13084
13085      timer = window.setTimeout(function () {
13086        fn.apply(ctx, args);
13087      }, delay || 150);
13088    };
13089  }
13090
13091  function safeFocus(element) {
13092    if (
13093      window.ENISA &&
13094      window.ENISA.utils &&
13095      typeof window.ENISA.utils.safeFocus === "function"
13096    ) {
13097      return window.ENISA.utils.safeFocus(element);
13098    }
13099
13100    if (!element || typeof element.focus !== "function") return false;
13101
13102    try {
13103      element.focus({ preventScroll: true });
13104    } catch (error) {
13105      element.focus();
13106    }
13107
13108    return true;
13109  }
13110
13111  function trimValue(value) {
13112    return String(value || "")
13113      .replace(/\s+/g, " ")
13114      .trim();
13115  }
13116
13117  function normalizeEmail(value) {
13118    return trimValue(value).toLowerCase();
13119  }
13120
13121  function isValidEmail(value) {
13122    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizeEmail(value));
13123  }
13124
13125  function decodeHtmlEntities(value) {
13126    if (!value || value.indexOf("&") === -1) return value || "";
13127
13128    var textarea = document.createElement("textarea");
13129    textarea.innerHTML = value;
13130
13131    return textarea.value;
13132  }
13133
13134  function parseJsonAttribute(value, fallback) {
13135    var raw = decodeHtmlEntities(value || "").trim();
13136
13137    if (!raw) return fallback;
13138
13139    try {
13140      return JSON.parse(raw);
13141    } catch (error) {
13142      return fallback;
13143    }
13144  }
13145
13146  function getSelectedOption(select) {
13147    if (!select || select.selectedIndex < 0) return null;
13148
13149    return select.options[select.selectedIndex] || null;
13150  }
13151
13152  function getSelectedOptionLabel(select) {
13153    var option = getSelectedOption(select);
13154
13155    return option
13156      ? trimValue(option.textContent || option.label || "")
13157      : "";
13158  }
13159
13160  function getSelectedOptionValue(select) {
13161    var option = getSelectedOption(select);
13162
13163    return option ? trimValue(option.value || "") : "";
13164  }
13165
13166  function getAllowedExtensions(input) {
13167    var raw = input
13168      ? input.getAttribute("data-allowed-extensions") || ""
13169      : "";
13170
13171    return raw
13172      .split(",")
13173      .map(function (item) {
13174        return trimValue(item)
13175          .replace(/^\./, "")
13176          .toLowerCase();
13177      })
13178      .filter(Boolean);
13179  }
13180
13181  function getFileExtension(fileName) {
13182    var parts = String(fileName || "").split(".");
13183
13184    if (parts.length < 2) return "";
13185
13186    return parts.pop().toLowerCase();
13187  }
13188
13189  function getMaxFileSize(input) {
13190    var value = input
13191      ? parseInt(input.getAttribute("data-max-size"), 10)
13192      : NaN;
13193
13194    return Number.isFinite(value) && value > 0
13195      ? value
13196      : MAX_FILE_SIZE_FALLBACK;
13197  }
13198
13199  function bytesToMb(bytes) {
13200    return Math.round((bytes / 1024 / 1024) * 10) / 10;
13201  }
13202
13203  function getPortalToken() {
13204    return new Promise(function (resolve, reject) {
13205      if (
13206        !window.shell ||
13207        typeof window.shell.getTokenDeferred !== "function"
13208      ) {
13209        reject(
13210          new Error("Power Pages token provider no disponible."),
13211        );
13212        return;
13213      }
13214
13215      window.shell
13216        .getTokenDeferred()
13217        .done(function (token) {
13218          resolve(token);
13219        })
13220        .fail(function () {
13221          reject(
13222            new Error(
13223              "No se ha podido obtener el token de seguridad.",
13224            ),
13225          );
13226        });
13227    });
13228  }
13229
13230  function validateLoginSession(data, textStatus, xhr, resolve) {
13231    if (typeof window.validateLoginSession === "function") {
13232      window.validateLoginSession(
13233        data,
13234        textStatus,
13235        xhr,
13236        resolve,
13237      );
13238
13239      return;
13240    }
13241
13242    resolve(data, textStatus, xhr);
13243  }
13244
13245  function webApiRequest(options) {
13246    return getPortalToken().then(function (token) {
13247      var headers = {
13248        Accept: "application/json",
13249        "Content-Type": "application/json; charset=utf-8",
13250        __RequestVerificationToken: token,
13251      };
13252
13253      Object.keys(options.headers || {}
13253).forEach(function (key) {
13254        headers[key] = options.headers[key];
13255      });
13256
13257      if (
13258        window.jQuery &&
13259        typeof window.jQuery.ajax === "function"
13260      ) {
13261        return new Promise(function (resolve, reject) {
13262          window.jQuery
13263            .ajax({
13264              type: options.method || "GET",
13265              url: options.url,
13266              data: options.body || null,
13267              headers: headers,
13268              contentType: headers["Content-Type"],
13269            })
13270            .done(function (data, textStatus, xhr) {
13271              validateLoginSession(
13272                data,
13273                textStatus,
13274                xhr,
13275                function () {
13276                  resolve({
13277                    data: data,
13278                    textStatus: textStatus,
13279                    xhr: xhr,
13280                    getResponseHeader: function (name) {
13281                      return xhr &&
13282                        typeof xhr.getResponseHeader ===
13283                          "function"
13284                        ? xhr.getResponseHeader(name)
13285                        : null;
13286                    },
13287                  });
13288                },
13289              );
13290            })
13291            .fail(function (xhr) {
13292              reject(createWebApiError(xhr));
13293            });
13294        });
13295      }
13296
13297      return window
13298        .fetch(options.url, {
13299          method: options.method || "GET",
13300          body: options.body || null,
13301          headers: headers,
13302          credentials: "same-origin",
13303        })
13304        .then(function (response) {
13305          if (!response.ok) {
13306            return response.text().then(function (text) {
13307              throw createFetchWebApiError(response, text);
13308            });
13309          }
13310
13311          return {
13312            response: response,
13313            getResponseHeader: function (name) {
13314              return response.headers.get(name);
13315            },
13316          };
13317        });
13318    });
13319  }
13320
13321  function createWebApiError(xhr) {
13322    var message = DEFAULT_ERROR_MESSAGE;
13323    var detail = "";
13324
13325    if (
13326      xhr &&
13327      xhr.responseJSON &&
13328      xhr.responseJSON.error
13329    ) {
13330      detail = xhr.responseJSON.error.message || "";
13331    } else if (xhr && xhr.responseText) {
13332      detail = xhr.responseText;
13333    }
13334
13335    if (detail) {
13336      message = detail;
13337    }
13338
13339    var error = new Error(message);
13340    error.xhr = xhr;
13341
13342    return error;
13343  }
13344
13345  function createFetchWebApiError(response, text) {
13346    var message = DEFAULT_ERROR_MESSAGE;
13347
13348    if (text) {
13349      try {
13350        var parsed = JSON.parse(text);
13351
13352        if (
13353          parsed &&
13354          parsed.error &&
13355          parsed.error.message
13356        ) {
13357          message = parsed.error.message;
13358        }
13359      } catch (error) {
13360        message = text;
13361      }
13362    }
13363
13364    var requestError = new Error(message);
13365    requestError.response = response;
13366    requestError.responseText = text;
13367
13368    return requestError;
13369  }
13370
13371  function extractGuidFromEntityHeader(headerValue) {
13372    var value = String(headerValue || "");
13373    var match = value.match(/\(([0-9a-fA-F-]{36})\)/);
13374
13375    if (match && match[1]) {
13376      return match[1];
13377    }
13378
13379    match = value.match(
13380      /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/,
13381    );
13382
13383    return match ? match[0] : "";
13384  }
13385
13386  function readFileAsBase64(file) {
13387    return new Promise(function (resolve, reject) {
13388      var reader = new FileReader();
13389
13390      reader.onload = function () {
13391        var result = String(reader.result || "");
13392        var commaIndex = result.indexOf(",");
13393
13394        resolve(
13395          commaIndex >= 0
13396            ? result.substring(commaIndex + 1)
13397            : result,
13398        );
13399      };
13400
13401      reader.onerror = function () {
13402        reject(
13403          new Error(
13404            "No se ha podido leer el archivo " +
13405              file.name +
13406              ".",
13407          ),
13408        );
13409      };
13410
13411      reader.readAsDataURL(file);
13412    });
13413  }
13414
13415  function ContactFormController(root) {
13416    this.root = root;
13417    this.form = root.querySelector("[data-contacto-form]");
13418    this.fields = {};
13419    this.isSubmitting = false;
13420    this.recaptchaWidgetId = null;
13421    this.departmentEmailMap = {};
13422  }
13423
13424  ContactFormController.prototype.init = function () {
13425    if (!this.form) return;
13426
13427    this.cacheFields();
13428
13429    this.departmentEmailMap = parseJsonAttribute(
13430      this.form.getAttribute("data-department-email-map"),
13431      {},
13432    );
13433
13434    this.initCustomSelects();
13435    this.initRecaptcha();
13436    this.bindEvents();
13437    this.updateFileList();
13438  };
13439
13440  ContactFormController.prototype.cacheFields = function () {
13441    var self = this;
13442
13443    this.form
13444      .querySelectorAll("[data-field]")
13445      .forEach(function (field) {
13446        var fieldName = field.getAttribute("data-field");
13447
13448        if (!fieldName) return;
13449
13450        self.fields[fieldName] = field;
13451      });
13452
13453    this.submitBtn = this.form.querySelector(
13454      "[data-submit-btn]",
13455    );
13456
13457    setSubmitButtonState(this.submitBtn, "idle");
13458
13459    this.loadingMsg = this.form.querySelector(
13460      "[data-loading]",
13461    );
13462
13463    this.successMsg = this.form.querySelector(
13464      "[data-success]",
13465    );
13466
13467    this.errorSummary = this.form.querySelector(
13468      "[data-error-summary], [data-error-form]",
13469    );
13470
13471    if (this.errorSummary) {
13472      this.errorSummary.setAttribute("role", "alert");
13473      this.errorSummary.setAttribute("tabindex", "-1");
13474    }
13475
13476    if (this.successMsg) {
13477      this.successMsg.setAttribute("role", "status");
13478      this.successMsg.setAttribute(
13479        "aria-live",
13480        "polite",
13481      );
13482      this.successMsg.setAttribute(
13483        "aria-atomic",
13484        "true",
13485      );
13486      this.successMsg.setAttribute("tabindex", "-1");
13487    }
13488
13489    this.fileList = this.form.querySelector(
13490      "[data-files-list]",
13491    );
13492
13493    this.fileClearBtn = this.form.querySelector(
13494      "[data-files-clear]",
13495    );
13496  };
13497
13498  ContactFormController.prototype.bindEvents = function () {
13499    var self = this;
13500
13501    Object.keys(this.fields).forEach(function (fieldName) {
13502      var field = self.fields[fieldName];
13503
13504      if (
13505        !field ||
13506        fieldName === "files" ||
13507        fieldName === "recaptcha"
13508      ) {
13509        return;
13510      }
13511
13512      field.addEventListener("blur", function () {
13513        self.validateField(fieldName);
13514      });
13515
13516      field.addEventListener("change", function () {
13517        self.validateField(fieldName);
13518      });
13519
13520      field.addEventListener(
13521        "input",
13522        debounce(function () {
13523          if (
13524            field.getAttribute("aria-invalid") ===
13525            "true"
13526          ) {
13527            self.validateField(fieldName);
13528          }
13529        }, 120),
13530      );
13531    });
13532
13533    if (this.fields.files) {
13534      this.fields.files.addEventListener(
13535        "change",
13536        function () {
13537          self.validateField("files");
13538          self.updateFileList();
13539        },
13540      );
13541
13542      var fileWrapper = this.form.querySelector(
13543        "[data-files-wrapper]",
13544      );
13545
13546      if (fileWrapper) {
13547        [
13548          "dragenter",
13549          "dragover",
13550          "dragleave",
13551          "drop",
13552        ].forEach(function (eventName) {
13553          fileWrapper.addEventListener(
13554            eventName,
13555            function (event) {
13556              self.handleFileDragDrop(
13557                event,
13558                fileWrapper,
13559              );
13560            },
13561          );
13562        });
13563      }
13564    }
13565
13566    if (
13567      this.fileClearBtn &&
13568      this.fields.files
13569    ) {
13570      this.fileClearBtn.addEventListener(
13571        "click",
13572        function () {
13573          self.fields.files.value = "";
13574          self.updateFileList();
13575          self.clearFieldError("files");
13576        },
13577      );
13578    }
13579
13580    if (this.fields.privacy) {
13581      this.fields.privacy.addEventListener(
13582        "keydown",
13583        function (event) {
13584          if (event.key !== "Enter") return;
13585
13586          event.preventDefault();
13587          event.stopImmediatePropagation();
13588          event.currentTarget.click();
13589        },
13590        true,
13591      );
13592    }
13593
13594    this.form.addEventListener(
13595      "submit",
13596      function (event) {
13597        event.preventDefault();
13598        self.handleSubmit();
13599      },
13600    );
13601  };
13602
13603  ContactFormController.prototype.initRecaptcha =
13604    function () {
13605      var recaptchaField = this.form.querySelector(
13606        "[data-recaptcha-field]",
13607      );
13608
13609      var widget = this.form.querySelector(
13610        "[data-recaptcha-widget]",
13611      );
13612
13613      var siteKey = this.form.getAttribute(
13614        "data-recaptcha-site-key",
13615      );
13616
13617      if (
13618        !recaptchaField ||
13619        !widget ||
13620        !siteKey
13621      ) {
13622        return;
13623      }
13624
13625      var self = this;
13626
13627      function render() {
13628        if (
13629          !window.grecaptcha ||
13630          typeof window.grecaptcha.render !== "function"
13631        ) {
13632          return false;
13633        }
13634
13635        if (
13636          widget.getAttribute(
13637            "data-recaptcha-rendered",
13638          ) === "true"
13639        ) {
13640          return true;
13641        }
13642
13643        self.recaptchaWidgetId =
13644          window.grecaptcha.render(widget, {
13645            sitekey: siteKey,
13646            callback: function (token) {
13647              if (self.fields.recaptcha) {
13648                self.fields.recaptcha.value =
13649                  token || "";
13650              }
13651
13652              self.clearFieldError("recaptcha");
13653            },
13654            "expired-callback": function () {
13655              if (self.fields.recaptcha) {
13656                self.fields.recaptcha.value = "";
13657              }
13658            },
13659          });
13660
13661        widget.setAttribute(
13662          "data-recaptcha-rendered",
13663          "true",
13664        );
13665
13666        return true;
13667      }
13668
13669      if (!render()) {
13670        document.addEventListener(
13671          RECAPTCHA_READY_EVENT,
13672          render,
13673          { once: true },
13674        );
13675
13676        loadRecaptchaScript();
13677
13678        var tries = 0;
13679
13680        var timer = window.setInterval(function () {
13681          tries += 1;
13682
13683          if (render() || tries >= 20) {
13684            window.clearInterval(timer);
13685          }
13686        }, 250);
13687      }
13688    };
13689
13690  ContactFormController.prototype.resetRecaptcha =
13691    function () {
13692      if (
13693        !window.grecaptcha ||
13694        this.recaptchaWidgetId === null
13695      ) {
13696        return;
13697      }
13698
13699      try {
13700        window.grecaptcha.reset(
13701          this.recaptchaWidgetId,
13702        );
13703      } catch (error) {}
13704
13705      if (this.fields.recaptcha) {
13706        this.fields.recaptcha.value = "";
13707      }
13708    };
13709
13710  ContactFormController.prototype.initCustomSelects =
13711    function () {
13712      var self = this;
13713
13714      var wrappers = this.form.querySelectorAll(
13715        "[data-enisa-select]",
13716      );
13717
13718      wrappers.forEach(function (wrapper) {
13719        if (
13720          wrapper.getAttribute(
13721            "data-enisa-select-init",
13722          ) === "true"
13723        ) {
13724          return;
13725        }
13726
13727        var select = wrapper.querySelector("select");
13728
13729        var trigger = wrapper.querySelector(
13730          ".enisa-select-control__trigger",
13731        );
13732
13733        var label = wrapper.querySelector(
13734          ".enisa-select-control__label",
13735        );
13736
13737        var menu = wrapper.querySelector(
13738          ".enisa-select-control__menu",
13739        );
13740
13741        if (
13742          !select ||
13743          !trigger ||
13744          !label ||
13745          !menu
13746        ) {
13747          return;
13748        }
13749
13750        wrapper.setAttribute(
13751          "data-enisa-select-init",
13752          "true",
13753        );
13754
13755        select.setAttribute("tabindex", "-1");
13756        select.setAttribute("aria-hidden", "true");
13757
13758        if (!label.id) {
13759          label.id =
13760            (select.id ||
13761              "enisa-contact-select") +
13762            "-value";
13763        }
13764
13765        var fieldLabel = select.id
13766          ? self.form.querySelector(
13767              "label[for='" +
13768                select.id.replace(/'/g, "\\'") +
13769                "']",
13770            )
13771          : null;
13772
13773        if (fieldLabel && !fieldLabel.id) {
13774          fieldLabel.id =
13775            select.id + "-label";
13776        }
13777
13778        var accessibleLabelId = fieldLabel
13779          ? fieldLabel.id
13780          : label.id;
13781
13782        trigger.setAttribute(
13783          "aria-labelledby",
13784          accessibleLabelId,
13785        );
13786
13787        trigger.setAttribute(
13788          "aria-haspopup",
13789          "listbox",
13790        );
13791
13792        trigger.setAttribute(
13793          "aria-expanded",
13794          "false",
13795        );
13796
13797        trigger.setAttribute(
13798          "aria-controls",
13799          menu.id ||
13800            select.id + "-listbox",
13801        );
13802
13803        if (
13804          select.getAttribute(
13805            "aria-describedby",
13806          )
13807        ) {
13808          trigger.setAttribute(
13809            "aria-describedby",
13810            select.getAttribute(
13811              "aria-describedby",
13812            ),
13813          );
13814        }
13815
13816        if (select.required) {
13817          trigger.setAttribute(
13818            "aria-required",
13819            "true",
13820          );
13821        } else {
13822          trigger.removeAttribute(
13823            "aria-required",
13824          );
13825        }
13826
13827        if (!menu.id) {
13828          menu.id =
13829            select.id + "-listbox";
13830        }
13831
13832        menu.setAttribute("role", "listbox");
13833
13834        menu.setAttribute(
13835          "aria-labelledby",
13836          accessibleLabelId,
13837        );
13838
13839        menu.hidden = true;
13840
13841        function syncLabel() {
13842          var selected =
13843            getSelectedOption(select);
13844
13845          var selectedText =
13846            selected && selected.value
13847              ? trimValue(
13848                  selected.textContent,
13849                )
13850              : "Selecciona una opción";
13851
13852          label.textContent = selectedText;
13853        }
13854
13855        function closeMenu() {
13856          wrapper.classList.remove("is-open");
13857
13858          trigger.setAttribute(
13859            "aria-expanded",
13860            "false",
13861          );
13862
13863          menu.hidden = true;
13864        }
13865
13866        function openMenu() {
13867          wrapper.classList.add("is-open");
13868
13869          trigger.setAttribute(
13870            "aria-expanded",
13871            "true",
13872          );
13873
13874          menu.hidden = false;
13875        }
13876
13877        function getOptions() {
13878          return Array.prototype.slice.call(
13879            menu.querySelectorAll(
13880              ".enisa-select-control__option",
13881            ),
13882          );
13883        }
13884
13885        function focusOption(index) {
13886          var options = getOptions();
13887
13888          if (!options.length) return;
13889
13890          var safeIndex = Math.max(
13891            0,
13892            Math.min(
13893              index,
13894              options.length - 1,
13895            ),
13896          );
13897
13898          safeFocus(options[safeIndex]);
13899        }
13900
13901        function getSelectedIndex() {
13902          var options = getOptions();
13903
13904          var selectedValue =
13905            getSelectedOptionValue(select);
13906
13907          for (
13908            var i = 0;
13909            i < options.length;
13910            i += 1
13911          ) {
13912            if (
13913              options[i].getAttribute(
13914                "data-value",
13915              ) === selectedValue
13916            ) {
13917              return i;
13918            }
13919          }
13920
13921          return 0;
13922        }
13923
13924        function buildMenu() {
13925          menu.innerHTML = "";
13926
13927          Array.prototype.forEach.call(
13928            select.options,
13929            function (option, index) {
13930              if (
13931                option.disabled &&
13932                !option.value
13933              ) {
13934                return;
13935              }
13936
13937              var item =
13938                document.createElement("div");
13939
13940              item.className =
13941                "enisa-select-control__option";
13942
13943              item.setAttribute(
13944                "role",
13945                "option",
13946              );
13947
13948              item.setAttribute(
13949                "data-value",
13950                option.value,
13951              );
13952
13953              item.setAttribute(
13954                "tabindex",
13955                "-1",
13956              );
13957
13958              item.setAttribute(
13959                "aria-selected",
13960                option.selected
13961                  ? "true"
13962                  : "false",
13963              );
13964
13965              item.textContent = trimValue(
13966                option.textContent,
13967              );
13968
13969              if (option.disabled) {
13970                item.setAttribute(
13971                  "aria-disabled",
13972                  "true",
13973                );
13974              }
13975
13976              item.addEventListener(
13977                "click",
13978                function () {
13979                  if (option.disabled) return;
13980
13981                  select.selectedIndex = index;
13982
13983                  select.dispatchEvent(
13984                    new Event("change", {
13985                      bubbles: true,
13986                    }),
13987                  );
13988
13989                  syncLabel();
13990                  buildMenu();
13991                  closeMenu();
13992                  safeFocus(trigger);
13993
13994                  self.validateField(
13995                    "department",
13996                  );
13997                },
13998              );
13999
14000              item.addEventListener(
14001                "keydown",
14002                function (event) {
14003                  var options = getOptions();
14004
14005                  var currentIndex =
14006                    options.indexOf(item);
14007
14008                  if (
14009                    event.key ===
14010                    "ArrowDown"
14011                  ) {
14012                    event.preventDefault();
14013
14014                    focusOption(
14015                      currentIndex + 1,
14016                    );
14017
14018                    return;
14019                  }
14020
14021                  if (
14022                    event.key === "ArrowUp"
14023                  ) {
14024                    event.preventDefault();
14025
14026                    focusOption(
14027                      currentIndex - 1,
14028                    );
14029
14030                    return;
14031                  }
14032
14033                  if (
14034                    event.key === "Home"
14035                  ) {
14036                    event.preventDefault();
14037                    focusOption(0);
14038                    return;
14039                  }
14040
14041                  if (
14042                    event.key === "End"
14043                  ) {
14044                    event.preventDefault();
14045
14046                    focusOption(
14047                      options.length - 1,
14048                    );
14049
14050                    return;
14051                  }
14052
14053                  if (
14054                    event.key === "Escape"
14055                  ) {
14056                    event.preventDefault();
14057                    closeMenu();
14058                    safeFocus(trigger);
14059                    return;
14060                  }
14061
14062                  if (
14063                    event.key === "Enter" ||
14064                    event.key === " "
14065                  ) {
14066                    event.preventDefault();
14067                    item.click();
14068                  }
14069                },
14070              );
14071
14072              menu.appendChild(item);
14073            },
14074          );
14075        }
14076
14077        trigger.addEventListener(
14078          "click",
14079          function () {
14080            if (
14081              wrapper.classList.contains(
14082                "is-open",
14083              )
14084            ) {
14085              closeMenu();
14086            } else {
14087              openMenu();
14088            }
14089          },
14090        );
14091
14092        trigger.addEventListener(
14093          "keydown",
14094          function (event) {
14095            if (
14096              event.key === "ArrowDown" ||
14097              event.key === "ArrowUp"
14098            ) {
14099              event.preventDefault();
14100              openMenu();
14101
14102              focusOption(
14103                getSelectedIndex(),
14104              );
14105
14106              return;
14107            }
14108
14109            if (
14110              event.key === "Enter" ||
14111              event.key === " "
14112            ) {
14113              event.preventDefault();
14114
14115              if (
14116                wrapper.classList.contains(
14117                  "is-open",
14118                )
14119              ) {
14120                closeMenu();
14121              } else {
14122                openMenu();
14123
14124                focusOption(
14125                  getSelectedIndex(),
14126                );
14127              }
14128
14129              return;
14130            }
14131
14132            if (event.key === "Escape") {
14133              closeMenu();
14134            }
14135          },
14136        );
14137
14138        document.addEventListener(
14139          "click",
14140          function (event) {
14141            if (
14142              wrapper.contains(event.target)
14143            ) {
14144              return;
14145            }
14146
14147            closeMenu();
14148          },
14149        );
14150
14151        select.addEventListener(
14152          "change",
14153          function () {
14154            syncLabel();
14155            buildMenu();
14156          },
14157        );
14158
14159        syncLabel();
14160        buildMenu();
14161      });
14162    };
14163
14164  ContactFormController.prototype.handleFileDragDrop =
14165    function (event, wrapper) {
14166      event.preventDefault();
14167      event.stopPropagation();
14168
14169      if (!this.fields.files) return;
14170
14171      if (
14172        event.type === "dragenter" ||
14173        event.type === "dragover"
14174      ) {
14175        wrapper.classList.add(
14176          "is-dragging",
14177        );
14178
14179        return;
14180      }
14181
14182      wrapper.classList.remove(
14183        "is-dragging",
14184      );
14185
14186      if (
14187        event.type !== "drop" ||
14188        !event.dataTransfer ||
14189        !event.dataTransfer.files
14190      ) {
14191        return;
14192      }
14193
14194      this.fields.files.files =
14195        event.dataTransfer.files;
14196
14197      this.validateField("files");
14198      this.updateFileList();
14199    };
14200
14201  ContactFormController.prototype.setFieldError =
14202    function (fieldName, message) {
14203      var field = this.fields[fieldName];
14204
14205      var errorNode = this.form.querySelector(
14206        "[data-error-" +
14207          fieldName +
14208          "]",
14209      );
14210
14211      if (field) {
14212        field.setAttribute(
14213          "aria-invalid",
14214          "true",
14215        );
14216      }
14217
14218      var selectRoot = field
14219        ? field.closest(
14220            ".enisa-select-control",
14221          )
14222        : null;
14223
14224      var selectTrigger = selectRoot
14225        ? selectRoot.querySelector(
14226            ".enisa-select-control__trigger",
14227          )
14228        : null;
14229
14230      if (selectTrigger) {
14231        selectTrigger.setAttribute(
14232          "aria-invalid",
14233          "true",
14234        );
14235      }
14236
14237      var fieldContainer = field
14238        ? field.closest(
14239            ".enisa-ayuda-contacto__field",
14240          ) ||
14241          field.closest("[data-field]")
14242        : null;
14243
14244      if (fieldContainer) {
14245        fieldContainer.classList.add(
14246          "has-error",
14247        );
14248      }
14249
14250      if (selectRoot) {
14251        selectRoot.classList.add(
14252          "is-error",
14253        );
14254      }
14255
14256      if (errorNode) {
14257        errorNode.textContent =
14258          message || "";
14259
14260        errorNode.hidden = !message;
14261      }
14262    };
14263
14264  ContactFormController.prototype.clearFieldError =
14265    function (fieldName) {
14266      var field = this.fields[fieldName];
14267
14268      var errorNode = this.form.querySelector(
14269        "[data-error-" +
14270          fieldName +
14271          "]",
14272      );
14273
14274      if (field) {
14275        field.setAttribute(
14276          "aria-invalid",
14277          "false",
14278        );
14279      }
14280
14281      var selectRoot = field
14282        ? field.closest(
14283            ".enisa-select-control",
14284          )
14285        : null;
14286
14287      var selectTrigger = selectRoot
14288        ? selectRoot.querySelector(
14289            ".enisa-select-control__trigger",
14290          )
14291        : null;
14292
14293      if (selectTrigger) {
14294        selectTrigger.setAttribute(
14295          "aria-invalid",
14296          "false",
14297        );
14298      }
14299
14300      var fieldContainer = field
14301        ? field.closest(
14302            ".enisa-ayuda-contacto__field",
14303          ) ||
14304          field.closest("[data-field]")
14305        : null;
14306
14307      if (fieldContainer) {
14308        fieldContainer.classList.remove(
14309          "has-error",
14310        );
14311      }
14312
14313      if (selectRoot) {
14314        selectRoot.classList.remove(
14315          "is-error",
14316        );
14317      }
14318
14319      if (errorNode) {
14320        errorNode.textContent = "";
14321        errorNode.hidden = true;
14322      }
14323    };
14324
14325  ContactFormController.prototype.validateField =
14326    function (fieldName) {
14327      var field = this.fields[fieldName];
14328
14329      if (!field) return true;
14330
14331      var value =
14332        field.type === "checkbox"
14333          ? field.checked
14334          : trimValue(field.value);
14335
14336      var message = "";
14337
14338      switch (fieldName) {
14339        case "name":
14340          if (!value) {
14341            message =
14342              "Introduce tu nombre.";
14343          }
14344          break;
14345
14346        case "lastname":
14347          if (!value) {
14348            message =
14349              "Introduce tus apellidos.";
14350          }
14351          break;
14352
14353        case "email":
14354          if (!value) {
14355            message =
14356              "Introduce tu correo electrónico.";
14357          } else if (!isValidEmail(value)) {
14358            message =
14359              "Introduce un correo electrónico válido.";
14360          }
14361          break;
14362
14363        case "department":
14364          if (!value) {
14365            message =
14366              "Selecciona un motivo.";
14367          }
14368          break;
14369
14370        case "message":
14371          if (!value) {
14372            message =
14373              "Escribe tu consulta.";
14374          }
14375          break;
14376
14377        case "privacy":
14378          if (!field.checked) {
14379            message =
14380              "Debes aceptar la política de privacidad.";
14381          }
14382          break;
14383
14384        case "files":
14385          message = this.validateFiles();
14386          break;
14387
14388        case "recaptcha":
14389          if (
14390            this.form.hasAttribute(
14391              "data-recaptcha-site-key",
14392            ) &&
14393            !trimValue(field.value)
14394          ) {
14395            message =
14396              "Completa la verificación anti-spam.";
14397          }
14398          break;
14399      }
14400
14401      if (message) {
14402        this.setFieldError(
14403          fieldName,
14404          message,
14405        );
14406
14407        return false;
14408      }
14409
14410      this.clearFieldError(fieldName);
14411
14412      return true;
14413    };
14414
14415  ContactFormController.prototype.validateFiles =
14416    function () {
14417      var fileInput = this.fields.files;
14418
14419      if (
14420        !fileInput ||
14421        !fileInput.files ||
14422        !fileInput.files.length
14423      ) {
14424        return "";
14425      }
14426
14427      var allowedExtensions =
14428        getAllowedExtensions(fileInput);
14429
14430      var maxSize =
14431        getMaxFileSize(fileInput);
14432
14433      for (
14434        var i = 0;
14435        i < fileInput.files.length;
14436        i += 1
14437      ) {
14438        var file = fileInput.files[i];
14439
14440        var extension =
14441          getFileExtension(file.name);
14442
14443        if (
14444          allowedExtensions.length &&
14445          allowedExtensions.indexOf(
14446            extension,
14447          ) === -1
14448        ) {
14449          return (
14450            "El archivo " +
14451            file.name +
14452            " no tiene un formato permitido."
14453          );
14454        }
14455
14456        if (file.size > maxSize) {
14457          return (
14458            "El archivo " +
14459            file.name +
14460            " supera el tamaño máximo de " +
14461            bytesToMb(maxSize) +
14462            " MB."
14463          );
14464        }
14465      }
14466
14467      return "";
14468    };
14469
14470  ContactFormController.prototype.validateForm =
14471    function () {
14472      var requiredFields = [
14473        "name",
14474        "lastname",
14475        "email",
14476        "department",
14477        "message",
14478        "privacy",
14479      ];
14480
14481      var isValid = true;
14482      var firstInvalid = null;
14483
14484      if (this.fields.files) {
14485        requiredFields.push("files");
14486      }
14487
14488      if (
14489        this.fields.recaptcha &&
14490        this.form.hasAttribute(
14491          "data-recaptcha-site-key",
14492        )
14493      ) {
14494        requiredFields.push("recaptcha");
14495      }
14496
14497      for (
14498        var i = 0;
14499        i < requiredFields.length;
14500        i += 1
14501      ) {
14502        var fieldName =
14503          requiredFields[i];
14504
14505        var fieldValid =
14506          this.validateField(fieldName);
14507
14508        if (
14509          !fieldValid &&
14510          !firstInvalid
14511        ) {
14512          firstInvalid =
14513            this.fields[fieldName];
14514        }
14515
14516        isValid =
14517          fieldValid && isValid;
14518      }
14519
14520      if (
14521        !isValid &&
14522        firstInvalid &&
14523        typeof firstInvalid.focus ===
14524          "function"
14525      ) {
14526        var selectRoot =
14527          firstInvalid.closest(
14528            ".enisa-select-control",
14529          );
14530
14531        var selectTrigger = selectRoot
14532          ? selectRoot.querySelector(
14533              ".enisa-select-control__trigger",
14534            )
14535          : null;
14536
14537        var focusTarget =
14538          selectTrigger || firstInvalid;
14539
14540        safeFocus(focusTarget);
14541      }
14542
14543      return isValid;
14544    };
14545
14546  ContactFormController.prototype.updateFileList =
14547    function () {
14548      var fileInput =
14549        this.fields.files;
14550
14551      var files =
14552        fileInput && fileInput.files
14553          ? Array.prototype.slice.call(
14554              fileInput.files,
14555            )
14556          : [];
14557
14558      var wrapper = this.form.querySelector(
14559        "[data-files-wrapper]",
14560      );
14561
14562      var fileText = wrapper
14563        ? wrapper.querySelector(
14564            ".enisa-ayuda-contacto__file-text",
14565          )
14566        : null;
14567
14568      var formats = wrapper
14569        ? wrapper.querySelector(
14570            ".enisa-ayuda-contacto__file-formats",
14571          )
14572        : null;
14573
14574      if (fileText) {
14575        if (files.length) {
14576          fileText.textContent =
14577            files.length +
14578            " archivo" +
14579            (files.length === 1
14580              ? ""
14581              : "s") +
14582            " seleccionado" +
14583            (files.length === 1
14584              ? ""
14585              : "s");
14586        } else {
14587          fileText.innerHTML =
14588            '<span class="enisa-ayuda-contacto__file-link">Subir archivos</span>';
14589        }
14590      }
14591
14592      if (formats) {
14593        formats.hidden =
14594          files.length > 0;
14595      }
14596
14597      if (this.fileClearBtn) {
14598        this.fileClearBtn.hidden =
14599          files.length === 0;
14600      }
14601
14602      if (!this.fileList) return;
14603
14604      this.fileList.innerHTML = "";
14605
14606      this.fileList.hidden =
14607        files.length === 0;
14608
14609      files.forEach(
14610        function (file) {
14611          var item =
14612            document.createElement("li");
14613
14614          item.className =
14615            "enisa-ayuda-contacto__file-item";
14616
14617          item.textContent =
14618            file.name +
14619            " (" +
14620            bytesToMb(file.size) +
14621            " MB)";
14622
14623          this.fileList.appendChild(
14624            item,
14625          );
14626        },
14627        this,
14628      );
14629    };
14630
14631  ContactFormController.prototype.getTargetEmail =
14632    function (departmentKey) {
14633      if (!departmentKey) return "";
14634
14635      var mapped =
14636        this.departmentEmailMap[
14637          departmentKey
14638        ];
14639
14640      if (mapped) {
14641        return trimValue(mapped);
14642      }
14643
14644      return "";
14645    };
14646
14647  ContactFormController.prototype.buildContactPayload =
14648    function () {
14649      var name = trimValue(
14650        this.fields.name &&
14651          this.fields.name.value,
14652      );
14653
14654      var lastname = trimValue(
14655        this.fields.lastname &&
14656          this.fields.lastname.value,
14657      );
14658
14659      var email = normalizeEmail(
14660        this.fields.email &&
14661          this.fields.email.value,
14662      );
14663
14664      var departmentKey =
14665        getSelectedOptionValue(
14666          this.fields.department,
14667        );
14668
14669      var departmentLabel =
14670        getSelectedOptionLabel(
14671          this.fields.department,
14672        );
14673
14674      var message = trimValue(
14675        this.fields.message &&
14676          this.fields.message.value,
14677      );
14678
14679      var targetEmail =
14680        this.getTargetEmail(
14681          departmentKey,
14682        );
14683
14684      var payload = {};
14685
14686      payload[CONTACT_FIELDS.name] =
14687        trimValue(
14688          name + " " + lastname,
14689        );
14690
14691      payload[CONTACT_FIELDS.lastname] =
14692        lastname;
14693
14694      payload[CONTACT_FIELDS.email] =
14695        email;
14696
14697      payload[
14698        CONTACT_FIELDS.departmentKey
14699      ] = departmentKey;
14700
14701      payload[
14702        CONTACT_FIELDS.departmentLabel
14703      ] = departmentLabel;
14704
14705      payload[CONTACT_FIELDS.message] =
14706        message;
14707
14708      payload[
14709        CONTACT_FIELDS.privacyAccepted
14710      ] = !!(
14711        this.fields.privacy &&
14712        this.fields.privacy.checked
14713      );
14714
14715      payload[
14716        CONTACT_FIELDS.targetEmail
14717      ] = targetEmail;
14718
14719      return payload;
14720    };
14721
14722  ContactFormController.prototype.createContact =
14723    function () {
14724      var entitySet = trimValue(
14725        this.form.getAttribute(
14726          "data-contact-entity-set",
14727        ),
14728      );
14729
14730      var payload =
14731        this.buildContactPayload();
14732
14733      if (!entitySet) {
14734        return Promise.reject(
14735          new Error(
14736            "No se ha configurado el entity set del formulario.",
14737          ),
14738        );
14739      }
14740
14741      return webApiRequest({
14742        method: "POST",
14743        url:
14744          "/_api/" +
14745          encodeURIComponent(entitySet),
14746        body: JSON.stringify(payload),
14747        headers: {
14748          Prefer: "return=representation",
14749        },
14750      }).then(function (result) {
14751        var entityIdHeader =
14752          result.getResponseHeader(
14753            "OData-EntityId",
14754          ) ||
14755          result.getResponseHeader(
14756            "odata-entityid",
14757          ) ||
14758          result.getResponseHeader(
14759            "Location",
14760          ) ||
14761          "";
14762
14763        var id =
14764          extractGuidFromEntityHeader(
14765            entityIdHeader,
14766          );
14767
14768        if (!id && result.data) {
14769          id =
14770            result.data
14771              .enisa_contactoid ||
14772            result.data
14773              .enisa_contactoId ||
14774            result.data.id ||
14775            "";
14776        }
14777
14778        return {
14779          id: id,
14780          payload: payload,
14781        };
14782      });
14783    };
14784
14785  ContactFormController.prototype.createAnnotation =
14786    function (contactId, file) {
14787      var annotationEntitySet =
14788        trimValue(
14789          this.form.getAttribute(
14790            "data-annotation-entity-set",
14791          ),
14792        ) || "annotations";
14793
14794      var contactEntitySet =
14795        trimValue(
14796          this.form.getAttribute(
14797            "data-contact-entity-set",
14798          ),
14799        );
14800
14801      var bindField =
14802        trimValue(
14803          this.form.getAttribute(
14804            "data-annotation-bind-field",
14805          ),
14806        ) ||
14807        "[email protected]";
14808
14809      if (
14810        !contactId ||
14811        !file ||
14812        !contactEntitySet
14813      ) {
14814        return Promise.resolve();
14815      }
14816
14817      return readFileAsBase64(
14818        file,
14819      ).then(function (base64) {
14820        var payload = {
14821          subject: file.name,
14822          filename: file.name,
14823          mimetype:
14824            file.type ||
14825            "application/octet-stream",
14826          documentbody: base64,
14827        };
14828
14829        payload[bindField] =
14830          "/" +
14831          contactEntitySet +
14832          "(" +
14833          contactId +
14834          ")";
14835
14836        return webApiRequest({
14837          method: "POST",
14838          url:
14839            "/_api/" +
14840            encodeURIComponent(
14841              annotationEntitySet,
14842            ),
14843          body: JSON.stringify(payload),
14844        });
14845      });
14846    };
14847
14848  ContactFormController.prototype.createAnnotations =
14849    function (contactId) {
14850      var fileInput =
14851        this.fields.files;
14852
14853      var files =
14854        fileInput && fileInput.files
14855          ? Array.prototype.slice.call(
14856              fileInput.files,
14857            )
14858          : [];
14859
14860      if (!files.length) {
14861        return Promise.resolve();
14862      }
14863
14864      var self = this;
14865
14866      return files.reduce(
14867        function (promise, file) {
14868          return promise.then(
14869            function () {
14870              return self.createAnnotation(
14871                contactId,
14872                file,
14873              );
14874            },
14875          );
14876        },
14877        Promise.resolve(),
14878      );
14879    };
14880
14881  ContactFormController.prototype.setSubmittingState =
14882    function (isSubmitting) {
14883      this.isSubmitting =
14884        isSubmitting;
14885
14886      if (this.submitBtn) {
14887        setSubmitButtonState(
14888          this.submitBtn,
14889          isSubmitting
14890            ? "submitting"
14891            : "idle",
14892        );
14893      }
14894
14895      if (this.loadingMsg) {
14896        this.loadingMsg.hidden =
14897          !isSubmitting;
14898      }
14899
14900      if (
14901        this.successMsg &&
14902        isSubmitting
14903      ) {
14904        this.successMsg.hidden = true;
14905      }
14906    };
14907
14908  ContactFormController.prototype.showError =
14909    function (message) {
14910      if (!this.errorSummary) return;
14911
14912      this.errorSummary.textContent =
14913        message ||
14914        DEFAULT_ERROR_MESSAGE;
14915
14916      this.errorSummary.hidden = false;
14917    };
14918
14919  ContactFormController.prototype.focusErrorSummary =
14920    function () {
14921      if (
14922        !this.errorSummary ||
14923        this.errorSummary.hidden
14924      ) {
14925        return;
14926      }
14927
14928      if (
14929        window.ENISA &&
14930        window.ENISA.utils &&
14931        typeof window.ENISA.utils
14932          .focusWithoutScroll ===
14933          "function"
14934      ) {
14935        window.ENISA.utils.focusWithoutScroll(
14936          this.errorSummary,
14937        );
14938
14939        return;
14940      }
14941
14942      safeFocus(this.errorSummary);
14943    };
14944
14945  ContactFormController.prototype.clearError =
14946    function () {
14947      if (!this.errorSummary) return;
14948
14949      this.errorSummary.textContent = "";
14950      this.errorSummary.hidden = true;
14951    };
14952
14953  ContactFormController.prototype.showSuccess =
14954    function () {
14955      if (!this.successMsg) return;
14956
14957      this.successMsg.textContent =
14958        DEFAULT_SUCCESS_MESSAGE;
14959
14960      this.successMsg.hidden = false;
14961
14962      setSubmitButtonState(
14963        this.submitBtn,
14964        "success",
14965      );
14966    };
14967
14968  ContactFormController.prototype.clearAllErrors =
14969    function () {
14970      var self = this;
14971
14972      Object.keys(this.fields).forEach(
14973        function (fieldName) {
14974          self.clearFieldError(
14975            fieldName,
14976          );
14977        },
14978      );
14979
14980      this.clearError();
14981    };
14982
14983  ContactFormController.prototype.resetForm =
14984    function () {
14985      this.form.reset();
14986
14987      this.form
14988        .querySelectorAll(
14989          "[data-enisa-select]",
14990        )
14991        .forEach(function (wrapper) {
14992          var select =
14993            wrapper.querySelector(
14994              "select",
14995            );
14996
14997          var label =
14998            wrapper.querySelector(
14999              ".enisa-select-control__label",
15000            );
15001
15002          if (select) {
15003            select.selectedIndex = 0;
15004          }
15005
15006          if (label) {
15007            label.textContent =
15008              "Selecciona una opción";
15009          }
15010        });
15011
15012      this.updateFileList();
15013      this.resetRecaptcha();
15014      this.clearAllErrors();
15015
15016      setSubmitButtonState(
15017        this.submitBtn,
15018        "idle",
15019      );
15020    };
15021
15022  ContactFormController.prototype.handleSubmit =
15023    function () {
15024      var self = this;
15025
15026      if (this.isSubmitting) return;
15027
15028      this.clearError();
15029
15030      if (!this.validateForm()) {
15031        this.showError(
15032          "Revisa los campos marcados antes de enviar el formulario.",
15033        );
15034
15035        return;
15036      }
15037
15038      this.setSubmittingState(true);
15039
15040      this.createContact()
15041        .then(function (contact) {
15042          return self.createAnnotations(
15043            contact.id,
15044          );
15045        })
15046        .then(function () {
15047          self.isSubmitting = false;
15048
15049          if (self.loadingMsg) {
15050            self.loadingMsg.hidden = true;
15051          }
15052
15053          self.showSuccess();
15054
15055          window.setTimeout(
15056            function () {
15057              self.resetForm();
15058
15059              if (self.successMsg) {
15060                self.successMsg.hidden =
15061                  true;
15062              }
15063
15064              if (self.submitBtn) {
15065                self.submitBtn.hidden =
15066                  false;
15067
15068                setSubmitButtonState(
15069                  self.submitBtn,
15070                  "idle",
15071                );
15072              }
15073            },
15074            3000,
15075          );
15076        })
15077        .catch(function (error) {
15078          self.setSubmittingState(false);
15079
15080          if (self.submitBtn) {
15081            self.submitBtn.hidden = false;
15082
15083            setSubmitButtonState(
15084              self.submitBtn,
15085              "idle",
15086            );
15087          }
15088
15089          self.showError(
15090            error && error.message
15091              ? error.message
15092              : DEFAULT_ERROR_MESSAGE,
15093          );
15094        });
15095    };
15096
15097  function initContactForms() {
15098    var roots =
15099      document.querySelectorAll(
15100        ROOT_SELECTOR,
15101      );
15102
15103    Array.prototype.forEach.call(
15104      roots,
15105      function (root) {
15106        if (
15107          root.getAttribute(
15108            INIT_ATTR,
15109          ) === "true"
15110        ) {
15111          return;
15112        }
15113
15114        root.setAttribute(
15115          INIT_ATTR,
15116          "true",
15117        );
15118
15119        var controller =
15120          new ContactFormController(root);
15121
15122        controller.init();
15123      },
15124    );
15125  }
15126
15127  ready(initContactForms);
15128
15129  if ("MutationObserver" in window) {
15130    var observerTimer = null;
15131
15132    var observer =
15133      new MutationObserver(
15134        function (mutations) {
15135          var hasContactForm =
15136            mutations.some(
15137              function (mutation) {
15138                return Array.prototype.some.call(
15139                  mutation.addedNodes,
15140                  function (node) {
15141                    if (
15142                      !node ||
15143                      node.nodeType !== 1
15144                    ) {
15145                      return false;
15146                    }
15147
15148                    return (
15149                      (node.matches &&
15150                        node.matches(
15151                          ROOT_SELECTOR,
15152                        )) ||
15153                      (node.querySelector &&
15154                        node.querySelector(
15155                          ROOT_SELECTOR,
15156                        ))
15157                    );
15158                  },
15159                );
15160              },
15161            );
15162
15163          if (!hasContactForm) return;
15164
15165          window.clearTimeout(
15166            observerTimer,
15167          );
15168
15169          observerTimer =
15170            window.setTimeout(
15171              initContactForms,
15172              150,
15173            );
15174        },
15175      );
15176
15177    observer.observe(document.body, {
15178      childList: true,
15179      subtree: true,
15180    });
15181  }
15182})();
15183
15184/*
15185  ENISA | JS Module
15186  Modulo: 30-eligibility-wizard
15187  Descripcion: Logica accesible del test/minitest de elegibilidad. No contiene logica del formulario de contacto.
15188*/
15189(function () {
15190  "use strict";
15191
15192  var ROOT_SELECTOR = ".enisa-wizard.enisa-wizard--minitest";
15193  var instanceCount = 0;
15194
15195  var DEFAULT_RESULT_COPY = {
15196    both: {
15197      title: "Tu empresa puede optar a financiación y certificación",
15198      text: "Según tus respuestas, tu pyme podría cumplir los requisitos para acceder a financiación y a la certificación como empresa emergente. Revisa los detalles y si quieres comenzar el proceso, regístrate.",
15199      type: "ok"
15200    },
15201    financingOnly: {
15202      title: "Tu empresa puede optar a financiación",
15203      text: "Según tus respuestas, tu pyme podría cumplir los requisitos para acceder a nuestros préstamos. Revisa los detalles y si quieres comenzar el proceso, regístrate.",
15204      type: "ok"
15205    },
15206    certificationOnly: {
15207      title: "Tu empresa puede certificarse como startup",
15208      text: "Según tus respuestas, tu pyme podría cumplir los requisitos para acceder a la certificación de empresa emergente. Revisa los detalles y si quieres comenzar el proceso, regístrate.",
15209      type: "ok"
15210    },
15211    none: {
15212      title: "Tu empresa no cumple con ningún requisito por ahora",
15213      text: "Según tus respuestas, tu empresa no encaja actualmente en nuestros criterios de financiación y certificación. Puedes revisar los requisitos por si quieres intentarlo de nuevo en otro momento.",
15214      type: "ko"
15215    }
15216  };
15217
15218  function ready(callback) {
15219    if (document.readyState === "loading") {
15220      document.addEventListener("DOMContentLoaded", callback);
15221      return;
15222    }
15223
15224    callback();
15225  }
15226
15227  function normalizeAnswerValue(value) {
15228    return String(value || "")
15229      .trim()
15230      .toLowerCase()
15231      .normalize("NFD")
15232      .replace(/[\u0300-\u036f]/g, "");
15233  }
15234
15235  function isYesValue(value) {
15236    var normalized = normalizeAnswerValue(value);
15237
15238    return (
15239      normalized === "si" ||
15240      normalized === "yes" ||
15241      normalized === "true" ||
15242      normalized === "1"
15243    );
15244  }
15245
15246  function decodeHtmlEntities(value) {
15247    if (!value || value.indexOf("&") === -1) return value || "";
15248
15249    var textarea = document.createElement("textarea");
15250    textarea.innerHTML = value;
15251    return textarea.value;
15252  }
15253
15254  function makeSafeId(value) {
15255    return String(value || "wizard")
15256      .toLowerCase()
15257      .replace(/[^a-z0-9\-_:.]+/g, "-")
15258      .replace(/^-+|-+$/g, "") || "wizard";
15259  }
15260
15261  function setId(element, id) {
15262    if (!element || !id) return "";
15263
15264    element.id = id;
15265
15266    return id;
15267  }
15268
15269  function focusElement(element) {
15270    if (!element || typeof element.focus !== "function") return;
15271
15272    window.requestAnimationFrame(function () {
15273      if (
15274        window.ENISA &&
15275        window.ENISA.utils &&
15276        typeof window.ENISA.utils.safeFocus === "function"
15277      ) {
15278        window.ENISA.utils.safeFocus(element);
15279        return;
15280      }
15281
15282      try {
15283        element.focus({ preventScroll: true });
15284      } catch (error) {
15285        element.focus();
15286      }
15287    });
15288  }
15289
15290  function parseJsonConfig(root) {
15291    var script =
15292      root.querySelector("#enisa-wizard-config") ||
15293      document.getElementById("enisa-wizard-config");
15294
15295    if (!script) return null;
15296
15297    var raw = script.textContent || "";
15298
15299    if (!raw.trim()) return null;
15300
15301    var decoded = decodeHtmlEntities(raw);
15302
15303    function tryParse(value) {
15304      try {
15305        return JSON.parse(value);
15306      } catch (error) {
15307        return null;
15308      }
15309    }
15310
15311    var parsed = tryParse(decoded);
15312
15313    if (parsed) return parsed;
15314
15315    var cleaned = decoded
15316      .replace(/^\uFEFF/, "")
15317      .replace(/[“”]/g, "\"")
15318      .replace(/[‘’]/g, "'")
15319      .trim();
15320
15321    if (cleaned.indexOf("<") !== -1 && cleaned.indexOf(">") !== -1) {
15322      var div = document.createElement("div");
15323
15324      div.innerHTML = cleaned;
15325      cleaned = (div.textContent || div.innerText || "").trim();
15326    }
15327
15328    return tryParse(cleaned);
15329  }
15330
15331  function looksLikeQuestion(item) {
15332    if (!item || typeof item !== "object") return false;
15333
15334    return !!(
15335      item.question ||
15336      item.title ||
15337      item.pregunta ||
15338      item.texto ||
15339      item.text
15340    );
15341  }
15342
15343  function findQuestionArray(config) {
15344    if (!config) return [];
15345
15346    if (Array.isArray(config)) return config;
15347
15348    var direct =
15349      config.questions ||
15350      config.items ||
15351      config.preguntas ||
15352      config.steps ||
15353      config.pasos;
15354
15355    if (Array.isArray(direct)) return direct;
15356
15357    if (direct && typeof direct === "object") {
15358      return Object.keys(direct)
15359        .sort(function (a, b) {
15360          var numA = parseInt(String(a).replace(/\D/g, ""), 10);
15361          var numB = parseInt(String(b).replace(/\D/g, ""), 10);
15362
15363          if (!isNaN(numA) && !isNaN(numB)) {
15364            return numA - numB;
15365          }
15366
15367          return String(a).localeCompare(String(b));
15368        })
15369        .map(function (key) {
15370          var item = direct[key] || {};
15371
15372          if (!item.id) {
15373            item.id = key;
15374          }
15375
15376          return item;
15377        });
15378    }
15379
15380    var keys = Object.keys(config);
15381
15382    for (var i = 0; i < keys.length; i += 1) {
15383      var value = config[keys[i]];
15384
15385      if (
15386        Array.isArray(value) &&
15387        value.length &&
15388        looksLikeQuestion(value[0])
15389      ) {
15390        return value;
15391      }
15392    }
15393
15394    for (var j = 0; j < keys.length; j += 1) {
15395      var nested = config[keys[j]];
15396
15397      if (!nested || typeof nested !== "object") continue;
15398
15399      var nestedFound = findQuestionArray(nested);
15400
15401      if (nestedFound.length) return nestedFound;
15402    }
15403
15404    return [];
15405  }
15406
15407  function normalizeOption(option, index) {
15408    if (typeof option === "string") {
15409      return {
15410        id: "opt-" + index,
15411        text: option,
15412        value: option
15413      };
15414    }
15415
15416    option = option || {};
15417
15418    return {
15419      id:
15420        option.id ||
15421        option.key ||
15422        option.value ||
15423        "opt-" + index,
15424      text:
15425        option.text ||
15426        option.label ||
15427        option.title ||
15428        option.texto ||
15429        option.respuesta ||
15430        "",
15431      value:
15432        option.value !== undefined
15433          ? option.value
15434          : option.id ||
15435            option.key ||
15436            option.text ||
15437            option.label ||
15438            option.texto ||
15439            option.respuesta ||
15440            ""
15441    };
15442  }
15443
15444  function normalizeQuestion(question, index) {
15445    question = question || {};
15446
15447    var rawOptions =
15448      question.options ||
15449      question.answers ||
15450      question.opciones ||
15451      question.respuestas ||
15452      [];
15453
15454    return {
15455      id: question.id || question.key || String(index + 1),
15456      kicker:
15457        question.kicker ||
15458        "Pregunta " + (index + 1) + " de {total}",
15459      title:
15460        question.question ||
15461        question.title ||
15462        question.pregunta ||
15463        question.text ||
15464        question.texto ||
15465        "",
15466      hint: question.hint || question.description || "",
15467      options: rawOptions.map(normalizeOption)
15468    };
15469  }
15470
15471  function setHintContent(target, hint) {
15472    if (!target) return;
15473
15474    var raw = typeof hint === "string" ? hint.trim() : "";
15475
15476    target.innerHTML = "";
15477
15478    if (!raw) {
15479      target.textContent = "";
15480      return;
15481    }
15482
15483    if (raw.indexOf("<") !== -1 && raw.indexOf(">") !== -1) {
15484      target.innerHTML = raw;
15485      return;
15486    }
15487
15488    var lines = raw
15489      .split(/\r?\n/)
15490      .map(function (line) {
15491        return line.trim();
15492      })
15493      .filter(Boolean);
15494
15495    if (!lines.length) {
15496      target.textContent = "";
15497      return;
15498    }
15499
15500    if (lines.length === 1) {
15501      target.textContent = lines[0];
15502      return;
15503    }
15504
15505    var intro = document.createElement("p");
15506
15507    intro.textContent = lines[0];
15508    target.appendChild(intro);
15509
15510    var list = document.createElement("ul");
15511
15512    lines.slice(1).forEach(function (line) {
15513      var item = document.createElement("li");
15514
15515      item.textContent = line.replace(/^[-*]\s*/, "");
15516      list.appendChild(item);
15517    });
15518
15519    target.appendChild(list);
15520  }
15521
15522  function getResultCopy(config, key) {
15523    var resultKeyMap = {
15524      both: "result_both",
15525      financingOnly: "result_ok_fin",
15526      certificationOnly: "result_ok_cer",
15527      none: "result_none"
15528    };
15529
15530    var fromConfig = null;
15531
15532    if (config && config.results) {
15533      fromConfig =
15534        config.results[key] ||
15535        config.results[resultKeyMap[key]] ||
15536        null;
15537    }
15538
15539    var base = DEFAULT_RESULT_COPY[key] || DEFAULT_RESULT_COPY.none;
15540
15541    if (!fromConfig) return base;
15542
15543    return {
15544      title:
15545        fromConfig.title ||
15546        fromConfig.heading ||
15547        base.title,
15548      text:
15549        fromConfig.text ||
15550        fromConfig.body ||
15551        base.text,
15552      type: fromConfig.type || base.type,
15553      cta1:
15554        fromConfig.cta1 ||
15555        fromConfig.primaryCta ||
15556        (
15557          fromConfig.cta1Label &&
15558          fromConfig.cta1Href
15559            ? {
15560                text: fromConfig.cta1Label,
15561                href: fromConfig.cta1Href,
15562                target: fromConfig.cta1Target
15563              }
15564            : null
15565        ),
15566      cta2:
15567        fromConfig.cta2 ||
15568        fromConfig.secondaryCta ||
15569        (
15570          fromConfig.cta2Label &&
15571          fromConfig.cta2Href
15572            ? {
15573                text: fromConfig.cta2Label,
15574                href: fromConfig.cta2Href,
15575                target: fromConfig.cta2Target
15576              }
15577            : null
15578        )
15579    };
15580  }
15581
15582  function setCta(link, cta) {
15583    if (!link) return;
15584
15585    if (!cta || !cta.text || !cta.href) {
15586      link.hidden = true;
15587      link.setAttribute("aria-disabled", "true");
15588      link.setAttribute("tabindex", "-1");
15589      link.removeAttribute("href");
15590      link.removeAttribute("target");
15591      link.removeAttribute("rel");
15592      link.textContent = "";
15593
15594      return;
15595    }
15596
15597    link.hidden = false;
15598    link.removeAttribute("aria-disabled");
15599    link.removeAttribute("tabindex");
15600    link.setAttribute("href", cta.href);
15601
15602    if (cta.target) {
15603      link.setAttribute("target", cta.target);
15604
15605      if (cta.target === "_blank") {
15606        link.setAttribute("rel", "noopener noreferrer");
15607      }
15608    } else {
15609      link.removeAttribute("target");
15610      link.removeAttribute("rel");
15611    }
15612
15613    link.textContent = cta.text;
15614  }
15615
15616  function WizardController(root) {
15617    this.root = root;
15618    this.config = parseJsonConfig(root) || {};
15619    this.instanceId = "enisa-wizard-" + (++instanceCount);
15620
15621    var rawQuestions = findQuestionArray(this.config);
15622
15623    this.questions = rawQuestions
15624      .map(normalizeQuestion)
15625      .filter(function (question) {
15626        return (
15627          question &&
15628          question.title &&
15629          question.options &&
15630          question.options.length
15631        );
15632      });
15633
15634    this.answers = [];
15635    this.currentIndex = 0;
15636
15637    this.mainTitle = root.querySelector(
15638      "[data-wizard-main-title]"
15639    );
15640    this.status = root.querySelector(
15641      "[data-wizard-status]"
15642    );
15643    this.progress = root.querySelector(
15644      "[data-wizard-progress-fill]"
15645    );
15646    this.progressWrap = root.querySelector(
15647      ".enisa-wizard__progress[role='progressbar']"
15648    );
15649    this.stage = root.querySelector(
15650      "[data-wizard-stage]"
15651    );
15652    this.kicker = root.querySelector(
15653      "[data-wizard-kicker]"
15654    );
15655    this.question = root.querySelector(
15656      "[data-wizard-question]"
15657    );
15658    this.hint = root.querySelector(
15659      "[data-wizard-hint]"
15660    );
15661    this.options = root.querySelector(
15662      "[data-wizard-options]"
15663    );
15664    this.backBtn = root.querySelector(
15665      "[data-wizard-back]"
15666    );
15667    this.nextBtn = root.querySelector(
15668      "[data-wizard-next]"
15669    );
15670    this.resultWrap = root.querySelector(
15671      "[data-wizard-result]"
15672    );
15673    this.resultIcon = root.querySelector(
15674      "[data-result-icon]"
15675    );
15676    this.resultTitle = root.querySelector(
15677      "[data-result-title]"
15678    );
15679    this.resultText = root.querySelector(
15680      "[data-result-text]"
15681    );
15682    this.resultCta1 = root.querySelector(
15683      "[data-result-cta-1]"
15684    );
15685    this.resultCta2 = root.querySelector(
15686      "[data-result-cta-2]"
15687    );
15688  }
15689
15690  WizardController.prototype.init = function () {
15691    if (
15692      !this.stage ||
15693      !this.options ||
15694      !this.nextBtn ||
15695      !this.backBtn ||
15696      !this.question ||
15697      !this.kicker
15698    ) {
15699      return;
15700    }
15701
15702    this.scopeStaticIds();
15703
15704    if (!this.questions.length) {
15705      this.question.textContent =
15706        "No se han podido cargar las preguntas.";
15707      this.kicker.textContent = "";
15708      this.options.innerHTML = "";
15709      this.nextBtn.disabled = true;
15710      this.announce(
15711        "No se han podido cargar las preguntas del test."
15712      );
15713
15714      return;
15715    }
15716
15717    if (this.questions.length < 5) {
15718      this.question.textContent =
15719        "No se han podido cargar todas las preguntas necesarias.";
15720      this.kicker.textContent = "";
15721      this.options.innerHTML = "";
15722      this.nextBtn.disabled = true;
15723      this.announce(
15724        "No se han podido cargar todas las preguntas necesarias del test."
15725      );
15726
15727      return;
15728    }
15729
15730    var self = this;
15731
15732    this.backBtn.addEventListener("click", function () {
15733      self.goBack();
15734    });
15735
15736    this.nextBtn.addEventListener("click", function () {
15737      self.goNext();
15738    });
15739
15740    this.options.addEventListener("change", function (event) {
15741      var input = event.target;
15742
15743      if (
15744        !input ||
15745        !input.classList ||
15746        !input.classList.contains(
15747          "enisa-wizard__option-input"
15748        )
15749      ) {
15750        return;
15751      }
15752
15753      self.selectOption(
15754        parseInt(
15755          input.getAttribute("data-option-index"),
15756          10
15757        ),
15758        true
15759      );
15760    });
15761
15762    this.renderQuestion(false);
15763  };
15764
15765  WizardController.prototype.scopeStaticIds = function () {
15766    var titleId = setId(
15767      this.mainTitle,
15768      this.instanceId + "-main-title"
15769    );
15770    var questionId = setId(
15771      this.question,
15772      this.instanceId + "-question"
15773    );
15774    var hintId = setId(
15775      this.hint,
15776      this.instanceId + "-hint"
15777    );
15778    var resultTitleId = setId(
15779      this.resultTitle,
15780      this.instanceId + "-result-title"
15781    );
15782
15783    if (titleId) {
15784      this.root.setAttribute(
15785        "aria-labelledby",
15786        titleId
15787      );
15788    }
15789
15790    if (questionId) {
15791      this.options.setAttribute(
15792        "aria-labelledby",
15793        questionId
15794      );
15795    }
15796
15797    if (hintId) {
15798      this.options.setAttribute(
15799        "aria-describedby",
15800        hintId
15801      );
15802    }
15803
15804    if (resultTitleId && this.resultWrap) {
15805      this.resultWrap.setAttribute(
15806        "aria-labelledby",
15807        resultTitleId
15808      );
15809    }
15810
15811    this.question.setAttribute("tabindex", "-1");
15812
15813    if (this.resultTitle) {
15814      this.resultTitle.setAttribute(
15815        "tabindex",
15816        "-1"
15817      );
15818    }
15819  };
15820
15821  WizardController.prototype.announce = function (message) {
15822    if (!this.status) return;
15823
15824    this.status.textContent = "";
15825
15826    var self = this;
15827
15828    window.setTimeout(function () {
15829      self.status.textContent = message || "";
15830    }, 20);
15831  };
15832
15833  WizardController.prototype.getProgress = function () {
15834    if (!this.questions.length) return 0;
15835
15836    return Math.round(
15837      (
15838        (this.currentIndex + 1) /
15839        this.questions.length
15840      ) * 100
15841    );
15842  };
15843
15844  WizardController.prototype.getProgressText = function () {
15845    return (
15846      "Pregunta " +
15847      (this.currentIndex + 1) +
15848      " de " +
15849      this.questions.length
15850    );
15851  };
15852
15853  WizardController.prototype.renderQuestion = function (
15854    moveFocus
15855  ) {
15856    var question = this.questions[this.currentIndex];
15857
15858    if (!question) return;
15859
15860    var total = this.questions.length;
15861    var selected = this.answers[this.currentIndex];
15862
15863    this.kicker.textContent = (
15864      question.kicker ||
15865      this.getProgressText()
15866    )
15867      .replace(
15868        "{index}",
15869        String(this.currentIndex + 1)
15870      )
15871      .replace(
15872        "{total}",
15873        String(total)
15874      );
15875
15876    this.question.textContent = question.title;
15877
15878    if (question.hint) {
15879      this.hint.hidden = false;
15880      setHintContent(this.hint, question.hint);
15881      this.options.setAttribute(
15882        "aria-describedby",
15883        this.hint.id
15884      );
15885    } else {
15886      this.hint.hidden = true;
15887      this.hint.textContent = "";
15888      this.options.removeAttribute(
15889        "aria-describedby"
15890      );
15891    }
15892
15893    this.renderOptions(question, selected);
15894    this.syncProgress();
15895    this.syncNav();
15896
15897    if (moveFocus) {
15898      this.announce(
15899        this.getProgressText() +
15900        ". " +
15901        question.title
15902      );
15903      focusElement(this.question);
15904    }
15905  };
15906
15907  WizardController.prototype.renderOptions = function (
15908    question,
15909    selected
15910  ) {
15911    var self = this;
15912    var groupName =
15913      this.instanceId +
15914      "-q-" +
15915      makeSafeId(
15916        question.id ||
15917        this.currentIndex
15918      );
15919
15920    this.options.innerHTML = "";
15921    this.options.setAttribute(
15922      "data-current-question",
15923      String(this.currentIndex)
15924    );
15925
15926    question.options.forEach(function (
15927      option,
15928      index
15929    ) {
15930      var optionId =
15931        self.instanceId +
15932        "-q-" +
15933        self.currentIndex +
15934        "-option-" +
15935        index;
15936
15937      var item = document.createElement("div");
15938      var input = document.createElement("input");
15939      var label = document.createElement("label");
15940      var text = document.createElement("span");
15941      var icon = document.createElement("span");
15942      var isSelected = selected === index;
15943
15944      item.className =
15945        "enisa-wizard__option-item";
15946
15947      input.type = "radio";
15948      input.className =
15949        "enisa-wizard__option-input";
15950      input.name = groupName;
15951      input.id = optionId;
15952      input.value = option.id;
15953      input.checked = isSelected;
15954      input.setAttribute(
15955        "data-option-index",
15956        String(index)
15957      );
15958
15959      label.className =
15960        "enisa-wizard__option";
15961      label.setAttribute("for", optionId);
15962      label.setAttribute(
15963        "data-option-index",
15964        String(index)
15965      );
15966
15967      if (isSelected) {
15968        item.classList.add("is-selected");
15969        label.classList.add("is-selected");
15970      }
15971
15972      text.className =
15973        "enisa-wizard__option-text";
15974      text.textContent = option.text;
15975
15976      icon.className =
15977        "enisa-wizard__option-icon";
15978      icon.setAttribute(
15979        "aria-hidden",
15980        "true"
15981      );
15982      icon.innerHTML =
15983        "<span class='enisa-icon enisa-icon--arrow-right'></span>";
15984
15985      label.appendChild(text);
15986      label.appendChild(icon);
15987      item.appendChild(input);
15988      item.appendChild(label);
15989      self.options.appendChild(item);
15990    });
15991  };
15992
15993  WizardController.prototype.selectOption = function (
15994    index,
15995    shouldAnnounce
15996  ) {
15997    var question = this.questions[this.currentIndex];
15998
15999    if (
16000      !question ||
16001      index < 0 ||
16002      index >= question.options.length
16003    ) {
16004      return;
16005    }
16006
16007    this.answers[this.currentIndex] = index;
16008    this.syncOptionSelection();
16009    this.syncNav();
16010
16011    if (shouldAnnounce) {
16012      this.announce(
16013        "Seleccionado: " +
16014        question.options[index].text +
16015        ". " +
16016        this.getProgressText() +
16017        "."
16018      );
16019    }
16020  };
16021
16022  WizardController.prototype.syncOptionSelection = function () {
16023    var selected = this.answers[this.currentIndex];
16024    var items = this.options.querySelectorAll(
16025      ".enisa-wizard__option-item"
16026    );
16027
16028    Array.prototype.forEach.call(
16029      items,
16030      function (item) {
16031        var input = item.querySelector(
16032          ".enisa-wizard__option-input"
16033        );
16034        var label = item.querySelector(
16035          ".enisa-wizard__option"
16036        );
16037        var index = input
16038          ? parseInt(
16039              input.getAttribute(
16040                "data-option-index"
16041              ),
16042              10
16043            )
16044          : -1;
16045        var isSelected = index === selected;
16046
16047        item.classList.toggle(
16048          "is-selected",
16049          isSelected
16050        );
16051
16052        if (label) {
16053          label.classList.toggle(
16054            "is-selected",
16055            isSelected
16056          );
16057        }
16058
16059        if (input) {
16060          input.checked = isSelected;
16061        }
16062      }
16063    );
16064  };
16065
16066  WizardController.prototype.syncProgress = function () {
16067    var percent = this.getProgress();
16068    var progressText = this.getProgressText();
16069
16070    if (this.progress) {
16071      this.progress.style.width =
16072        percent + "%";
16073    }
16074
16075    if (this.progressWrap) {
16076      this.progressWrap.setAttribute(
16077        "aria-valuenow",
16078        String(percent)
16079      );
16080      this.progressWrap.setAttribute(
16081        "aria-valuetext",
16082        progressText
16083      );
16084    }
16085  };
16086
16087  WizardController.prototype.syncNav = function () {
16088    var nextLabel =
16089      this.currentIndex ===
16090      this.questions.length - 1
16091        ? "Ver resultado"
16092        : "Continuar";
16093
16094    this.backBtn.disabled =
16095      this.currentIndex === 0;
16096
16097    this.nextBtn.disabled =
16098      this.answers[this.currentIndex] ===
16099      undefined;
16100
16101    this.nextBtn.textContent = nextLabel;
16102
16103    this.nextBtn.removeAttribute(
16104      "aria-label"
16105    );
16106  };
16107
16108  WizardController.prototype.goBack = function () {
16109    if (this.currentIndex === 0) return;
16110
16111    this.currentIndex -= 1;
16112    this.renderQuestion(true);
16113  };
16114
16115  WizardController.prototype.goNext = function () {
16116    if (
16117      this.answers[this.currentIndex] ===
16118      undefined
16119    ) {
16120      return;
16121    }
16122
16123    if (
16124      this.currentIndex >=
16125      this.questions.length - 1
16126    ) {
16127      this.showResult();
16128      return;
16129    }
16130
16131    this.currentIndex += 1;
16132    this.renderQuestion(true);
16133  };
16134
16135  WizardController.prototype.answerIsYes = function (
16136    questionIndex
16137  ) {
16138    var question =
16139      this.questions[questionIndex];
16140    var answerIndex =
16141      this.answers[questionIndex];
16142
16143    if (
16144      !question ||
16145      answerIndex === undefined ||
16146      !question.options[answerIndex]
16147    ) {
16148      return false;
16149    }
16150
16151    var option =
16152      question.options[answerIndex];
16153
16154    return (
16155      isYesValue(option.value) ||
16156      isYesValue(option.id) ||
16157      isYesValue(option.text)
16158    );
16159  };
16160
16161  WizardController.prototype.computeResultKey = function () {
16162    var q1Yes = this.answerIsYes(0);
16163    var q2Yes = this.answerIsYes(1);
16164    var q3Yes = this.answerIsYes(2);
16165    var q4Yes = this.answerIsYes(3);
16166    var q5Yes = this.answerIsYes(4);
16167
16168    var financiable =
16169      q1Yes &&
16170      q4Yes &&
16171      q5Yes &&
16172      !q3Yes;
16173
16174    var certificable =
16175      q1Yes &&
16176      q2Yes &&
16177      q5Yes;
16178
16179    if (financiable && certificable) {
16180      return "both";
16181    }
16182
16183    if (financiable) {
16184      return "financingOnly";
16185    }
16186
16187    if (certificable) {
16188      return "certificationOnly";
16189    }
16190
16191    return "none";
16192  };
16193
16194  WizardController.prototype.showResult = function () {
16195    var key = this.computeResultKey();
16196    var result = getResultCopy(
16197      this.config,
16198      key
16199    );
16200
16201    if (this.resultWrap) {
16202      this.resultWrap.hidden = false;
16203    }
16204
16205    if (this.resultIcon) {
16206      this.resultIcon.setAttribute(
16207        "data-type",
16208        result.type === "ko"
16209          ? "ko"
16210          : "ok"
16211      );
16212    }
16213
16214    if (this.resultTitle) {
16215      this.resultTitle.textContent =
16216        result.title;
16217    }
16218
16219    if (this.resultText) {
16220      this.resultText.textContent =
16221        result.text;
16222    }
16223
16224    setCta(
16225      this.resultCta1,
16226      result.cta1
16227    );
16228    setCta(
16229      this.resultCta2,
16230      result.cta2
16231    );
16232
16233    if (this.resultTitle) {
16234      if (
16235        window.ENISA &&
16236        window.ENISA.utils &&
16237        typeof window.ENISA.utils
16238          .focusWithoutScroll === "function"
16239      ) {
16240        window.ENISA.utils.focusWithoutScroll(
16241          this.resultTitle
16242        );
16243      } else {
16244        focusElement(this.resultTitle);
16245      }
16246    }
16247
16248    if (this.stage) {
16249      this.stage.hidden = true;
16250    }
16251
16252    if (this.progress) {
16253      this.progress.style.width = "100%";
16254    }
16255
16256    if (this.progressWrap) {
16257      this.progressWrap.setAttribute(
16258        "aria-valuenow",
16259        "100"
16260      );
16261      this.progressWrap.setAttribute(
16262        "aria-valuetext",
16263        "Test finalizado"
16264      );
16265    }
16266
16267    this.announce(
16268      "Resultado del test. " +
16269      result.title +
16270      ". " +
16271      result.text
16272    );
16273  };
16274
16275  function initEligibilityWizard() {
16276    var roots =
16277      document.querySelectorAll(
16278        ROOT_SELECTOR
16279      );
16280
16281    if (!roots.length) return;
16282
16283    Array.prototype.forEach.call(
16284      roots,
16285      function (root) {
16286        if (
16287          root.getAttribute(
16288            "data-wizard-init"
16289          ) === "true"
16290        ) {
16291          return;
16292        }
16293
16294        root.setAttribute(
16295          "data-wizard-init",
16296          "true"
16297        );
16298
16299        new WizardController(root).init();
16300      }
16301    );
16302  }
16303
16304  ready(initEligibilityWizard);
16305
16306  if ("MutationObserver" in window) {
16307    var observerTimer = null;
16308
16309    var observer = new MutationObserver(
16310      function (mutations) {
16311        var hasWizard = mutations.some(
16312          function (mutation) {
16313            return Array.prototype.some.call(
16314              mutation.addedNodes,
16315              function (node) {
16316                if (
16317                  !node ||
16318                  node.nodeType !== 1
16319                ) {
16320                  return false;
16321                }
16322
16323                return (
16324                  node.matches &&
16325                  node.matches(
16326                    ROOT_SELECTOR
16327                  )
16328                ) || (
16329                  node.querySelector &&
16330                  node.querySelector(
16331                    ROOT_SELECTOR
16332                  )
16333                );
16334              }
16335            );
16336          }
16337        );
16338
16339        if (!hasWizard) return;
16340
16341        window.clearTimeout(
16342          observerTimer
16343        );
16344
16345        observerTimer =
16346          window.setTimeout(
16347            initEligibilityWizard,
16348            150
16349          );
16350      }
16351    );
16352
16353    observer.observe(document.body, {
16354      childList: true,
16355      subtree: true
16356    });
16357  }
16358})();
16359
16360/*
16361  ENISA | JS Module
16362  Modulo: 31-accessibility-audit
16363  Descripcion: Normalizacion global de nombres accesibles, idioma y contenido dinamico.
16364*/
16365(function () {
16366  "use strict";
16367
16368  var INIT_ATTR = "data-enisa-a11y-init";
16369  var NEW_WINDOW_TEXT = "se abre en una nueva ventana";
16370  var NEW_WINDOW_HINT = " (" + NEW_WINDOW_TEXT + ")";
16371  var GENERIC_LABELS = {
16372    "abrir": true,
16373    "continuar": true,
16374    "descargar": true,
16375    "enviar": true,
16376    "leer mas": true,
16377    "leer más": true,
16378    "mas informacion": true,
16379    "mas información": true,
16380    "más informacion": true,
16381    "más información": true,
16382    "mostrar mas": true,
16383    "mostrar más": true,
16384    "ver mas": true,
16385    "ver más": true,
16386    "ver todos": true,
16387    "ver todas": true
16388  };
16389
16390  function toArray(list) {
16391    return Array.prototype.slice.call(list || []);
16392  }
16393
16394  function normalizeSpaces(value) {
16395    return String(value || "").replace(/\s+/g, " ").trim();
16396  }
16397
16398  function normalizeText(value) {
16399    return normalizeSpaces(value)
16400      .toLowerCase()
16401      .replace(/[áàäâ]/g, "a")
16402      .replace(/[éèëê]/g, "e")
16403      .replace(/[íìïî]/g, "i")
16404      .replace(/[óòöô]/g, "o")
16405      .replace(/[úùüû]/g, "u")
16406      .replace(/ñ/g, "n");
16407  }
16408
16409  function titleCase(value) {
16410    var text = normalizeSpaces(value);
16411    return text ? text.charAt(0).toUpperCase() + text.slice(1) : "";
16412  }
16413
16414  function ensureSpanishLanguage(scope) {
16415    var html = document.documentElement;
16416
16417    if (html && normalizeText(html.getAttribute("lang")) !== "es-es") {
16418      html.setAttribute("lang", "es-ES");
16419    }
16420
16421    if (document.body && !document.body.hasAttribute("lang")) {
16422      document.body.setAttribute("lang", "es-ES");
16423    }
16424
16425    toArray((scope || document).querySelectorAll("[data-force-spanish]")).forEac
16425h(function (node) {
16426      if (!node.hasAttribute("lang")) {
16427        node.setAttribute("lang", "es-ES");
16428      }
16429    });
16430  }
16431
16432  function mergeRel(link) {
16433    var values = normalizeSpaces(link.getAttribute("rel"))
16434      .toLowerCase()
16435      .split(" ")
16436      .filter(Boolean);
16437
16438    if (values.indexOf("noopener") === -1) values.push("noopener");
16439    if (values.indexOf("noreferrer") === -1) values.push("noreferrer");
16440
16441    link.setAttribute("rel", values.join(" "));
16442  }
16443
16444  function hasNewWindowText(value) {
16445    var text = normalizeText(value);
16446    return (
16447      text.indexOf("nueva ventana") !== -1 ||
16448      text.indexOf("nueva pestana") !== -1 ||
16449      text.indexOf("pestana nueva") !== -1
16450    );
16451  }
16452
16453  function normalizeNewWindowText(value) {
16454    return normalizeSpaces(value)
16455      .replace(/se abre en una nueva ventana/gi, NEW_WINDOW_TEXT)
16456      .replace(/se abre en una nueva pestaña/gi, NEW_WINDOW_TEXT)
16457      .replace(/abre en una nueva pestaña/gi, NEW_WINDOW_TEXT)
16458      .replace(/abre en nueva pestaña/gi, NEW_WINDOW_TEXT)
16459      .replace(/se abre en pestaña nueva/gi, NEW_WINDOW_TEXT)
16460      .replace(/abre en pestaña nueva/gi, NEW_WINDOW_TEXT)
16461      .replace(/se abre en una pestaña nueva/gi, NEW_WINDOW_TEXT);
16462  }
16463
16464  function getVisibleText(element) {
16465    if (!element) return "";
16466
16467    return normalizeSpaces(
16468      toArray(element.childNodes)
16469        .filter(function (node) {
16470          return node.nodeType === 3 ||
16471            (node.nodeType === 1 && node.getAttribute("aria-hidden") !== "true");
16472        })
16473        .map(function (node) {
16474          return node.textContent || "";
16475        })
16476        .join(" ")
16477    );
16478  }
16479
16480  function appendNewWindowHint(link) {
16481    var existing = link.querySelector("[data-new-tab-hint], [data-new-window-hint]");
16482
16483    if (existing) {
16484      existing.setAttribute("data-new-window-hint", "");
16485      existing.className = existing.className || "enisa-sr-only";
16486      existing.textContent = NEW_WINDOW_HINT;
16487      toArray(link.querySelectorAll("[data-new-tab-hint], [data-new-window-hint]")).forEach(function (hint) {
16488        if (hint !== existing) hint.parentNode.removeChild(hint);
16489      });
16490      return;
16491    }
16492
16493    var hint = document.createElement("span");
16494    hint.className = "enisa-sr-only";
16495    hint.setAttribute("data-new-window-hint", "");
16496    hint.textContent = NEW_WINDOW_HINT;
16497    link.appendChild(hint);
16498  }
16499
16500  function enhanceNewWindowLink(link) {
16501    var ariaLabel = normalizeSpaces(link.getAttribute("aria-label"));
16502    var title = normalizeSpaces(link.getAttribute("title"));
16503
16504    mergeRel(link);
16505
16506    if (ariaLabel) {
16507      ariaLabel = normalizeNewWindowText(ariaLabel);
16508      if (!hasNewWindowText(ariaLabel)) {
16509        ariaLabel += NEW_WINDOW_HINT;
16510      }
16511      link.setAttribute("aria-label", ariaLabel);
16512      toArray(link.querySelectorAll("[data-new-tab-hint], [data-new-window-hint]")).forEach(function (hint) {
16513        hint.parentNode.removeChild(hint);
16514      });
16515      return;
16516    }
16517
16518    if (title) {
16519      title = normalizeNewWindowText(title);
16520      if (!hasNewWindowText(title)) {
16521        title += NEW_WINDOW_HINT;
16522      }
16523      link.setAttribute("title", title);
16524    }
16525
16526    appendNewWindowHint(link);
16527  }
16528
16529  function getControlImageText(control) {
16530    var image = control.querySelector("img[alt], svg[aria-label], svg title");
16531
16532    if (!image) return "";
16533    if (image.tagName && image.tagName.toLowerCase() === "title") {
16534      return normalizeSpaces(image.textContent);
16535    }
16536
16537    return normalizeSpaces(image.getAttribute("alt") || image.getAttribute("aria-label"));
16538  }
16539
16540  function inferIconOnlyControlLabel(control) {
16541    var title = normalizeSpaces(control.getAttribute("title"));
16542    var className = normalizeText(control.className || "");
16543    var controlRef = normalizeText(control.getAttribute("aria-controls"));
16544    var type = normalizeText(control.getAttribute("type"));
16545    var imageText = getControlImageText(control);
16546
16547    if (title) return title;
16548    if (imageText) return imageText;
16549    if (className.indexOf("search") !== -1 || controlRef.indexOf("search") !== -1) return "Buscar";
16550    if (className.indexOf("clear") !== -1) return "Borrar";
16551    if (className.indexOf("close") !== -1) return "Cerrar";
16552    if (className.indexOf("menu") !== -1 || className.indexOf("toggle") !== -1) return "Abrir menú";
16553    if (className.indexOf("share") !== -1) return "Compartir";
16554    if (className.indexOf("download") !== -1) return "Descargar";
16555    if (className.indexOf("private") !== -1 || className.indexOf("user") !== -1) return "Área privada";
16556    if (className.indexOf("prev") !== -1 || className.indexOf("previous") !== -1) return "Anterior";
16557    if (className.indexOf("next") !== -1) return "Siguiente";
16558    if (type === "submit") return "Enviar";
16559
16560    return "";
16561  }
16562
16563  function ensureIconOnlyControlLabels(scope) {
16564    toArray((scope || document).querySelectorAll("a[href], button")).forEach(function (control) {
16565      var label = normalizeSpaces(control.getAttribute("aria-label"));
16566      var labelledBy = normalizeSpaces(control.getAttribute("aria-labelledby"));
16567      var visible = getVisibleText(control);
16568      var inferred;
16569
16570      if (label || labelledBy || visible) return;
16571
16572      inferred = inferIconOnlyControlLabel(control);
16573      if (!inferred) return;
16574
16575      if (control.matches("a[target='_blank']") && !hasNewWindowText(inferred)) {
16576        inferred += NEW_WINDOW_HINT;
16577      }
16578
16579      control.setAttribute("aria-label", normalizeSpaces(inferred));
16580    });
16581  }
16582
16583  function getClosestContext(control) {
16584    var root = control.closest(
16585      "article, li, section, form, [data-card-link], [data-news-card], [data-resource-item], [data-transparency-row], .enisa-news-card, .enisa-editorial-card, .enisa-cards-recursos__card, .enisa-recursos-detalle__item, .enisa-transparencia-documents__row"
16586    );
16587    var selectors = [
16588      "[data-accessible-title]",
16589      "[data-search-title]",
16590      ".enisa-news-card__title",
16591      ".enisa-editorial-latest-card__title",
16592      ".enisa-cards-recursos__card-title",
16593      ".enisa-recursos-detalle__title",
16594      ".enisa-transparencia-documents__cell:first-child",
16595      ".enisa-transparencia-documents-card__title",
16596      ".enisa-accordion__download-label",
16597      ".enisa-materiales__download-label",
16598      ".enisa-btn__label",
16599      "h1, h2, h3, h4, h5, h6"
16600    ];
16601    var node;
16602
16603    if (root) {
16604      for (var i = 0; i < selectors.length; i += 1) {
16605        node = root.querySelector(selectors[i]);
16606        if (node && node !== control) {
16607          var text = normalizeSpaces(node.getAttribute("data-accessible-title") || node.textContent);
16608          if (text && text !== getVisibleText(control)) return text;
16609        }
16610      }
16611    }
16612
16613    node = control.closest("section");
16614    if (node) {
16615      var labelledBy = node.getAttribute("aria-labelledby");
16616      var heading = labelledBy ? document.getElementById(labelledBy) : node.querySelector("h1, h2, h3");
16617      if (heading) return normalizeSpaces(heading.textContent);
16618    }
16619
16620    return "";
16621  }
16622
16623  function getDownloadContext(control) {
16624    var downloadLabel = control.querySelector(
16625      ".enisa-accordion__download-label, .enisa-materiales__download-label, .enisa-btn__label"
16626    );
16627    return normalizeSpaces(downloadLabel ? downloadLabel.textContent : "") || getClosestContext(control);
16628  }
16629
16630  function isGenericControl(control) {
16631    var text = normalizeText(getVisibleText(control));
16632    if (!text) return false;
16633    return GENERIC_LABELS[text] === true;
16634  }
16635
16636  function enhanceGenericControl(control) {
16637    if (!isGenericControl(control)) return;
16638    if (normalizeSpaces(control.getAttribute("aria-label"))) return;
16639
16640    var visible = normalizeSpaces(getVisibleText(control));
16641    var normalized = normalizeText(visible);
16642    var context = normalized === "descargar" ? getDownloadContext(control) : getClosestContext(control);
16643    var label = titleCase(visible);
16644
16645    if (context && normalizeText(context) !== normalizeText(visible)) {
16646      if (normalized.indexOf("descargar") === 0) {
16647        label = "Descargar " + context;
16648      } else if (normalized.indexOf("enviar") === 0) {
16649        label = "Enviar formulario" + (context ? " de " + context : "");
16650      } else {
16651        label = visible + ": " + context;
16652      }
16653    }
16654
16655    if (control.matches("a[target='_blank']") && !hasNewWindowText(label)) {
16656      label += NEW_WINDOW_HINT;
16657    }
16658
16659    control.setAttribute("aria-label", normalizeSpaces(label));
16660  }
16661
16662  function hasSvgAccessibleName(svg) {
16663    return !!(
16664      normalizeSpaces(svg.getAttribute("aria-label")) ||
16665      normalizeSpaces(svg.getAttribute("aria-labelledby")) ||
16666      svg.querySelector("title")
16667    );
16668  }
16669
16670  function silenceDecorativeIcons(scope) {
16671    toArray((scope || document).querySelectorAll("a img, button img, a svg, button svg, .enisa-icon, [class*='__icon'] svg, svg")).forEac
16671h(function (icon) {
16672      if (icon.closest("[data-icon-informative]")) return;
16673      if (icon.tagName && icon.tagName.toLowerCase() === "svg" && hasSvgAccessibleName(icon)) return;
16674
16675      icon.setAttribute("aria-hidden", "true");
16676
16677      if (icon.tagName && icon.tagName.toLowerCase() === "img") {
16678        icon.setAttribute("alt", "");
16679      }
16680
16681      if (icon.tagName && icon.tagName.toLowerCase() === "svg") {
16682        icon.setAttribute("focusable", "false");
16683      }
16684    });
16685  }
16686
16687  function ensureImageAlts(scope) {
16688    toArray((scope || document).querySelectorAll("img")).forEach(function (image) {
16689      var context = getClosestContext(image) || image.getAttribute("title") || "";
16690
16691      if (image.closest("a, button") || image.closest("[aria-hidden='true']")) {
16692        image.setAttribute("alt", "");
16693        image.setAttribute("aria-hidden", "true");
16694        return;
16695      }
16696
16697      if (image.hasAttribute("alt") && normalizeSpaces(image.getAttribute("alt")) === "") {
16698        image.setAttribute("aria-hidden", "true");
16699        return;
16700      }
16701
16702      if (image.hasAttribute("alt")) return;
16703
16704      image.setAttribute("alt", context ? context : "");
16705      if (!context) image.setAttribute("aria-hidden", "true");
16706    });
16707  }
16708
16709  function normalizeDuplicateCardLinks(scope) {
16710    toArray((scope || document).querySelectorAll("a[tabindex='-1'][href]")).forEach(function (link) {
16711      var card = link.closest("[data-card-link]");
16712      var primaryTarget = card ? card.querySelector("[data-card-link-target][href]") : null;
16713
16714      if (!link.matches("[class*='image-wrapper'], [class*='media-link'], [class*='image-link']")) return;
16715      if (!primaryTarget || primaryTarget === link) return;
16716
16717      link.removeAttribute("href");
16718      link.removeAttribute("aria-label");
16719      link.setAttribute("role", "presentation");
16720      toArray(link.querySelectorAll("img, svg")).forEach(function (media) {
16721        media.setAttribute("aria-hidden", "true");
16722        if (media.tagName && media.tagName.toLowerCase() === "img") media.setAttribute("alt", "");
16723        if (media.tagName && media.tagName.toLowerCase() === "svg") media.setAttribute("focusable", "false");
16724      });
16725    });
16726  }
16727
16728  function ensureLandmarkBasics(scope) {
16729    var root = scope && scope.querySelectorAll ? scope : document;
16730    var main = document.getElementById("main-content");
16731    var skip = document.querySelector(".enisa-skip-link[href^='#']");
16732
16733    if (!main) {
16734      main = document.querySelector("main.enisa-main, main.page-layout__main, main");
16735      if (main && !main.id) {
16736        main.id = "main-content";
16737      }
16738    }
16739
16740    if (main) {
16741      if (!main.hasAttribute("tabindex")) {
16742        main.setAttribute("tabindex", "-1");
16743      }
16744
16745      if (!main.hasAttribute("lang")) {
16746        main.setAttribute("lang", "es");
16747      }
16748    }
16749
16750    if (skip && main) {
16751      skip.setAttribute("href", "#main-content");
16752    }
16753
16754    toArray(root.querySelectorAll("[role='main']")).forEach(function (landmark) {
16755      if (landmark.tagName && landmark.tagName.toLowerCase() === "main") {
16756        landmark.removeAttribute("role");
16757      }
16758    });
16759  }
16760
16761  function isHiddenForAudit(element) {
16762    var style;
16763
16764    if (!element) return true;
16765    if (element.hidden || element.getAttribute("aria-hidden") === "true") return true;
16766    if (element.closest("[hidden], [aria-hidden='true'], [inert]")) return true;
16767
16768    if (window.getComputedStyle) {
16769      style = window.getComputedStyle(element);
16770      if (style.display === "none" || style.visibility === "hidden") return true;
16771    }
16772
16773    return false;
16774  }
16775
16776  function auditStructure(scope) {
16777    var root = scope && scope.querySelectorAll ? scope : document;
16778    var main = document.getElementById("main-content");
16779    var skip = document.querySelector(".enisa-skip-link");
16780    var header = document.querySelector("[data-header-root], .enisa-header, body > header");
16781    var footer = document.querySelector("footer, .enisa-footer");
16782    var landmarks = toArray(document.querySelectorAll("main, [role='main']")).map(function (landmark) {
16783      return {
16784        tag: landmark.tagName.toLowerCase(),
16785        id: landmark.id || "",
16786        className: typeof landmark.className === "string" ? landmark.className : "",
16787        hidden: isHiddenForAudit(landmark)
16788      };
16789    });
16790    var hiddenFocusable = toArray(root.querySelectorAll("a[href], button, input, select, textarea, summary, [tabindex]"))
16791      .filter(function (element) {
16792        return isHiddenForAudit(element) && element.tabIndex >= 0;
16793      })
16794      .slice(0, 25)
16795      .map(function (element) {
16796        return {
16797          tag: element.tagName.toLowerCase(),
16798          id: element.id || "",
16799          className: typeof element.className === "string" ? element.className : "",
16800          text: normalizeSpaces(element.textContent).slice(0, 80)
16801        };
16802      });
16803    var nestedInteractive = toArray(root.querySelectorAll("a[href], button, [role='button'], [role='link']"))
16804      .filter(function (control) {
16805        return !!control.querySelector("a[href], button, input, select, textarea, [role='button'], [role='link']");
16806      })
16807      .slice(0, 25)
16808      .map(function (control) {
16809        return {
16810          tag: control.tagName.toLowerCase(),
16811          id: control.id || "",
16812          className: typeof control.className === "string" ? control.className : "",
16813          text: normalizeSpaces(control.textContent).slice(0, 80)
16814        };
16815      });
16816
16817    return {
16818      visibleMainCount: landmarks.filter(function (item) { return !item.hidden; }).length,
16819      landmarks: landmarks,
16820      mainContentExists: !!main,
16821      mainHasProgrammaticFocus: !!(main && main.getAttribute("tabindex") === "-1"),
16822      skipExists: !!skip,
16823      skipHref: skip ? skip.getAttribute("href") : null,
16824      skipTargetsMain: !!(skip && main && skip.getAttribute("href") === "#main-content"),
16825      skipBeforeHeader: !!(skip && header && (skip.compareDocumentPosition(header) & Node.DOCUMENT_POSITION_FOLLOWING)),
16826      mainContainsHeader: !!(main && main.querySelector("[data-header-root], .enisa-header, header, nav")),
16827      mainContainsFooter: !!(main && footer && main.contains(footer)),
16828      bodyHasHeadLeak: !!(document.body && document.body.querySelector("meta, title, head, body")),
16829      hiddenFocusable: hiddenFocusable,
16830      nestedInteractive: nestedInteractive
16831    };
16832  }
16833
16834  function normalizeRedundantLabels(scope) {
16835    toArray((scope || document).querySelectorAll("a[aria-label], button[aria-label]")).forEach(function (control) {
16836      var label = normalizeSpaces(control.getAttribute("aria-label"));
16837      var visible = getVisibleText(control);
16838
16839      if (!label || !visible) return;
16840      if (hasNewWindowText(label) && !hasNewWindowText(visible)) return;
16841
16842      if (normalizeText(label) === normalizeText(visible)) {
16843        control.removeAttribute("aria-label");
16844      }
16845    });
16846
16847    toArray((scope || document).querySelectorAll("article[aria-label], li[aria-label], div[aria-label]")).forEach(function (container) {
16848      var role = normalizeSpaces(container.getAttribute("role"));
16849      var label = normalizeSpaces(container.getAttribute("aria-label"));
16850      var heading = container.querySelector("h1, h2, h3, h4, h5, h6");
16851
16852      if (role || !label || !heading) return;
16853      if (normalizeText(label) === normalizeText(heading.textContent)) {
16854        container.removeAttribute("aria-label");
16855      }
16856    });
16857  }
16858
16859  function normalizeAriaReferences(scope) {
16860    toArray((scope || document).querySelectorAll("[aria-labelledby], [aria-describedby]")).forEach(function (element) {
16861      ["aria-labelledby", "aria-describedby"].forEach(function (attr) {
16862        var raw = normalizeSpaces(element.getAttribute(attr));
16863        if (!raw) return;
16864
16865        var ids = raw.split(" ").filter(function (id) {
16866          var ref = document.getElementById(id);
16867          if (!ref) return false;
16868          if (attr === "aria-labelledby") return true;
16869          if (!ref.hidden) return true;
16870          return /(error|feedback|hint|status|help|legal)/i.test(id);
16871        });
16872
16873        if (ids.length) {
16874          element.setAttribute(attr, ids.join(" "));
16875        } else {
16876          element.removeAttribute(attr);
16877        }
16878      });
16879    });
16880  }
16881
16882  function normalizeLiveRegions(scope) {
16883    toArray((scope || document).querySelectorAll("[aria-live]")).forEach(function (node) {
16884      var role = normalizeSpaces(node.getAttribute("role")).toLowerCase();
16885
16886      if (role === "list" || role === "listbox") {
16887        node.removeAttribute("aria-live");
16888        node.removeAttribute("aria-atomic");
16889      }
16890    });
16891  }
16892
16893  function normalizeStaticTabPanelFocus(scope) {
16894    var root = scope && scope.querySelectorAll ? scope : document;
16895    var selector = "[role='tabpanel'][tabindex='0'], [data-resource-panel][tabindex='0'], [data-panel-index][tabindex='0']";
16896    var panels = [];
16897
16898    if (root.matches && root.matches(selector)) {
16899      panels.push(root);
16900    }
16901
16902    panels = panels.concat(toArray(root.querySelectorAll(selector)));
16903
16904    panels.forEach(function (panel) {
16905      panel.removeAttribute("tabindex");
16906    });
16907  }
16908
16909  function normalizeCarouselControl(button) {
16910    var label = normalizeSpaces(button.getAttribute("aria-label"));
16911    var group = button.closest("[aria-label]");
16912    var groupLabel = group ? normalizeSpaces(group.getAttribute("aria-label")) : "";
16913    var total = group ? group.querySelectorAll("button").length : 0;
16914    var index = toArray(group ? group.querySelectorAll("button") : []).indexOf(button) + 1;
16915
16916    if (!label) return;
16917
16918    if (/^tarjeta anterior$/i.test(label) && groupLabel) {
16919      button.setAttribute("aria-label", "Anterior elemento. " + groupLabel);
16920    } else if (/^tarjeta siguiente$/i.test(label) && groupLabel) {
16921      button.setAttribute("aria-label", "Siguiente elemento. " + groupLabel);
16922    } else if (/^pagina \d+$/i.test(normalizeText(label)) && total > 1) {
16923      button.setAttribute("aria-label", "Ir al elemento " + index + " de " + total);
16924    }
16925  }
16926
16927  function enhanceScope(scope) {
16928    var root = scope && scope.querySelectorAll ? scope : document;
16929    var links = [];
16930    var controls = [];
16931
16932    ensureSpanishLanguage(root);
16933    ensureIconOnlyControlLabels(root);
16934    silenceDecorativeIcons(root);
16935    ensureImageAlts(root);
16936    normalizeDuplicateCardLinks(root);
16937    ensureLandmarkBasics(root);
16938    normalizeRedundantLabels(root);
16939    normalizeAriaReferences(root);
16940    normalizeLiveRegions(root);
16941    normalizeStaticTabPanelFocus(root);
16942
16943    if (root.matches && root.matches("a[target='_blank']")) {
16944      links.push(root);
16945    }
16946
16947    links = links.concat(toArray(root.querySelectorAll("a[target='_blank']")));
16948    links.forEach(enhanceNewWindowLink);
16949
16950    if (root.matches && root.matches("a[href], button")) {
16951      controls.push(root);
16952    }
16953
16954    controls = controls.concat(toArray(root.querySelectorAll("a[href], button")));
16955    controls.forEach(function (control) {
16956      enhanceGenericControl(control);
16957      normalizeCarouselControl(control);
16958      control.setAttribute(INIT_ATTR, "true");
16959    });
16960  }
16961
16962  function init() {
16963    enhanceScope(document);
16964
16965    if (!("MutationObserver" in window) || !document.body) return;
16966
16967    var timer = null;
16968    var observer = new MutationObserver(function (mutations) {
16969      var nodes = [];
16970
16971      mutations.forEach(function (mutation) {
16972        toArray(mutation.addedNodes).forEach(function (node) {
16973          if (node && node.nodeType === 1) nodes.push(node);
16974        });
16975      });
16976
16977      if (!nodes.length) return;
16978
16979      window.clearTimeout(timer);
16980      timer = window.setTimeout(function () {
16981        nodes.forEach(enhanceScope);
16982      }, 80);
16983    });
16984
16985    observer.observe(document.body, {
16986      childList: true,
16987      subtree: true
16988    });
16989  }
16990
16991  if (document.readyState === "loading") {
16992    document.addEventListener("DOMContentLoaded", init);
16993  } else {
16994    init();
16995  }
16996
16997  window.ENISA = window.ENISA || {};
16998  window.ENISA.accessibility = {
16999    refresh: enhanceScope,
17000    audit: auditStructure
17001  };
17002})();
17003

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.