@solvenza/cookies-compliance 1.2.1 → 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +960 -36
  2. package/bin/audit-cli.js +134 -13
  3. package/dist/audit.cjs +811 -0
  4. package/dist/audit.cjs.map +1 -0
  5. package/dist/audit.d.cts +60 -0
  6. package/dist/audit.d.ts +60 -0
  7. package/dist/audit.js +380 -0
  8. package/dist/audit.js.map +1 -0
  9. package/dist/chunk-C5QPXHTH.js +3557 -0
  10. package/dist/chunk-C5QPXHTH.js.map +1 -0
  11. package/dist/chunk-OHOHIAK6.js +442 -0
  12. package/dist/chunk-OHOHIAK6.js.map +1 -0
  13. package/dist/chunk-Q3FZFXQE.js +89 -0
  14. package/dist/chunk-UQU54ANZ.js +225 -0
  15. package/dist/chunk-UQU54ANZ.js.map +1 -0
  16. package/dist/chunk-W5MG56VI.js +269 -0
  17. package/dist/chunk-W5MG56VI.js.map +1 -0
  18. package/dist/consent.esm.cjs +2195 -84
  19. package/dist/consent.esm.cjs.map +1 -1
  20. package/dist/consent.esm.d.cts +173 -6
  21. package/dist/consent.esm.d.ts +173 -6
  22. package/dist/consent.esm.js +57 -23
  23. package/dist/consent.esm.js.map +1 -1
  24. package/dist/consent.min.js +437 -55
  25. package/dist/consent.min.js.map +1 -1
  26. package/dist/presets.cjs +495 -0
  27. package/dist/presets.cjs.map +1 -0
  28. package/dist/presets.d.cts +51 -0
  29. package/dist/presets.d.ts +51 -0
  30. package/dist/presets.js +16 -0
  31. package/dist/presets.js.map +1 -0
  32. package/dist/types--kyI7OOR.d.cts +314 -0
  33. package/dist/types--kyI7OOR.d.ts +314 -0
  34. package/dist/wrappers/angular.cjs +2341 -65
  35. package/dist/wrappers/angular.cjs.map +1 -1
  36. package/dist/wrappers/angular.d.cts +85 -5
  37. package/dist/wrappers/angular.d.ts +85 -5
  38. package/dist/wrappers/angular.js +217 -3
  39. package/dist/wrappers/angular.js.map +1 -1
  40. package/dist/wrappers/next.cjs +2353 -103
  41. package/dist/wrappers/next.cjs.map +1 -1
  42. package/dist/wrappers/next.d.cts +25 -1
  43. package/dist/wrappers/next.d.ts +25 -1
  44. package/dist/wrappers/next.js +30 -5
  45. package/dist/wrappers/next.js.map +1 -1
  46. package/dist/wrappers/nuxt.cjs +4171 -0
  47. package/dist/wrappers/nuxt.cjs.map +1 -0
  48. package/dist/wrappers/nuxt.d.cts +33 -0
  49. package/dist/wrappers/nuxt.d.ts +33 -0
  50. package/dist/wrappers/nuxt.js +51 -0
  51. package/dist/wrappers/nuxt.js.map +1 -0
  52. package/dist/wrappers/react.cjs +2336 -141
  53. package/dist/wrappers/react.cjs.map +1 -1
  54. package/dist/wrappers/react.d.cts +87 -1
  55. package/dist/wrappers/react.d.ts +87 -1
  56. package/dist/wrappers/react.js +21 -51
  57. package/dist/wrappers/react.js.map +1 -1
  58. package/dist/wrappers/vue.cjs +4163 -0
  59. package/dist/wrappers/vue.cjs.map +1 -0
  60. package/dist/wrappers/vue.d.cts +144 -0
  61. package/dist/wrappers/vue.d.ts +144 -0
  62. package/dist/wrappers/vue.js +28 -0
  63. package/dist/wrappers/vue.js.map +1 -0
  64. package/dist/wrappers/wordpress.js +1 -1
  65. package/package.json +26 -5
  66. package/schema.json +143 -4
  67. package/dist/chunk-DDAAVRWG.js +0 -25
  68. package/dist/chunk-FBRP4MSA.js +0 -1907
  69. package/dist/chunk-FBRP4MSA.js.map +0 -1
  70. package/dist/consent-engine-DOMyEKfk.d.cts +0 -193
  71. package/dist/consent-engine-DOMyEKfk.d.ts +0 -193
  72. /package/dist/{chunk-DDAAVRWG.js.map → chunk-Q3FZFXQE.js.map} +0 -0
@@ -20,6 +20,18 @@ var __spreadValues = (a, b) => {
20
20
  return a;
21
21
  };
22
22
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
+ var __objRest = (source, exclude) => {
24
+ var target = {};
25
+ for (var prop in source)
26
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
27
+ target[prop] = source[prop];
28
+ if (source != null && __getOwnPropSymbols)
29
+ for (var prop of __getOwnPropSymbols(source)) {
30
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
31
+ target[prop] = source[prop];
32
+ }
33
+ return target;
34
+ };
23
35
  var __export = (target, all) => {
24
36
  for (var name in all)
25
37
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -37,12 +49,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
37
49
  // src/index.ts
38
50
  var src_exports = {};
39
51
  __export(src_exports, {
52
+ BUILTIN_TRANSLATIONS: () => BUILTIN_TRANSLATIONS,
40
53
  ConfigValidationError: () => ConfigValidationError,
41
54
  Consent: () => Consent,
42
55
  ConsentConfigBuilder: () => ConsentConfigBuilder,
43
56
  ConsentEngine: () => ConsentEngine,
44
57
  CookieStorageProvider: () => CookieStorageProvider,
45
58
  CookieStore: () => CookieStore,
59
+ FloatingBadge: () => FloatingBadge,
46
60
  GoogleConsentAdapter: () => GoogleConsentAdapter,
47
61
  I18nEngine: () => I18nEngine,
48
62
  IframeGate: () => IframeGate,
@@ -53,13 +67,22 @@ __export(src_exports, {
53
67
  PolicyGenerator: () => PolicyGenerator,
54
68
  ResourceGate: () => ResourceGate,
55
69
  ResourceScanner: () => ResourceScanner,
70
+ SERVICE_PRESETS: () => SERVICE_PRESETS,
56
71
  ScriptGate: () => ScriptGate,
57
72
  ScriptResourceBlocker: () => ScriptResourceBlocker,
73
+ StorageCleaner: () => StorageCleaner,
58
74
  StorageFactory: () => StorageFactory,
75
+ applyGpcChoices: () => applyGpcChoices,
59
76
  computeReceiptSignature: () => computeReceiptSignature,
60
77
  createReceipt: () => createReceipt,
78
+ defineServices: () => defineServices,
79
+ detectGpcSignal: () => detectGpcSignal,
80
+ getPreset: () => getPreset,
81
+ hasPreset: () => hasPreset,
61
82
  isReceiptExpired: () => isReceiptExpired,
62
83
  parseReceipt: () => parseReceipt,
84
+ resolveConfigPresets: () => resolveConfigPresets,
85
+ resolveGpcConfig: () => resolveGpcConfig,
63
86
  sanitizeHtml: () => sanitizeHtml,
64
87
  validateConfig: () => validateConfig,
65
88
  verifyReceiptIntegrity: () => verifyReceiptIntegrity
@@ -108,14 +131,15 @@ function validateConfig(config) {
108
131
  }
109
132
  if (config.services && typeof config.services === "object") {
110
133
  for (const [serviceId, serviceConfig] of Object.entries(config.services)) {
111
- if (!serviceConfig.category) {
134
+ const category = serviceConfig.category;
135
+ if (!category) {
112
136
  throw new ConfigValidationError(
113
137
  `Service '${serviceId}' missing 'category' reference.`
114
138
  );
115
139
  }
116
- if (!config.categories[serviceConfig.category]) {
140
+ if (!config.categories[category]) {
117
141
  throw new ConfigValidationError(
118
- `Service '${serviceId}' references non-existent category '${serviceConfig.category}'.`
142
+ `Service '${serviceId}' references non-existent category '${category}'.`
119
143
  );
120
144
  }
121
145
  }
@@ -134,17 +158,21 @@ var StateManager = class {
134
158
  };
135
159
  this.config = null;
136
160
  }
137
- init(config, choices, receipt) {
138
- var _a;
161
+ init(config, choices, receipt, gpc) {
162
+ var _a, _b;
139
163
  this.config = config;
140
164
  this.state = {
141
165
  initialized: true,
142
166
  policyVersion: config.policyVersion,
143
167
  locale: ((_a = config.locale) == null ? void 0 : _a.default) || "es",
144
168
  receipt,
145
- choices
169
+ choices,
170
+ gpc: (_b = gpc != null ? gpc : receipt == null ? void 0 : receipt.gpc) != null ? _b : false
146
171
  };
147
172
  }
173
+ setLocale(locale) {
174
+ this.state.locale = locale;
175
+ }
148
176
  getConfig() {
149
177
  return this.config;
150
178
  }
@@ -163,7 +191,7 @@ var StateManager = class {
163
191
  hasService(serviceId) {
164
192
  if (!this.config || !this.config.services) return false;
165
193
  const service = this.config.services[serviceId];
166
- if (!service) return false;
194
+ if (!service || !service.category) return false;
167
195
  return this.hasCategory(service.category);
168
196
  }
169
197
  updateChoices(receipt) {
@@ -556,6 +584,178 @@ var MemoryStore = {
556
584
  clear: () => new MemoryStorageProvider().clear()
557
585
  };
558
586
 
587
+ // src/storage/storage-cleaner.ts
588
+ var StorageCleaner = class {
589
+ /**
590
+ * Evaluates if a given storage key or cookie name matches a pattern.
591
+ * Supports:
592
+ * - Wildcard glob: "*", "_ga*", "ph_*_posthog", "*session*"
593
+ * - Exact string match (case-insensitive)
594
+ */
595
+ static matchesPattern(key, pattern) {
596
+ if (!key || !pattern) return false;
597
+ if (pattern === "*" || key === pattern) return true;
598
+ if (pattern.includes("*")) {
599
+ const escaped = pattern.split("*").map((segment) => segment.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&")).join(".*");
600
+ try {
601
+ const regex = new RegExp(`^${escaped}$`, "i");
602
+ return regex.test(key);
603
+ } catch (e) {
604
+ return false;
605
+ }
606
+ }
607
+ return key.toLowerCase() === pattern.toLowerCase();
608
+ }
609
+ /**
610
+ * Purges keys from window.localStorage that match any of the provided patterns.
611
+ * Safe in SSR, sandboxed iframes, and private browsing modes.
612
+ */
613
+ static purgeLocalStorage(patterns) {
614
+ if (typeof window === "undefined" || !patterns || patterns.length === 0) return [];
615
+ try {
616
+ if (typeof window.localStorage === "undefined") return [];
617
+ const keysToRemove = [];
618
+ const len = window.localStorage.length;
619
+ for (let i = 0; i < len; i++) {
620
+ const key = window.localStorage.key(i);
621
+ if (key && patterns.some((p) => this.matchesPattern(key, p))) {
622
+ keysToRemove.push(key);
623
+ }
624
+ }
625
+ for (const key of keysToRemove) {
626
+ window.localStorage.removeItem(key);
627
+ }
628
+ return keysToRemove;
629
+ } catch (e) {
630
+ return [];
631
+ }
632
+ }
633
+ /**
634
+ * Purges keys from window.sessionStorage that match any of the provided patterns.
635
+ * Safe in SSR, sandboxed iframes, and private browsing modes.
636
+ */
637
+ static purgeSessionStorage(patterns) {
638
+ if (typeof window === "undefined" || !patterns || patterns.length === 0) return [];
639
+ try {
640
+ if (typeof window.sessionStorage === "undefined") return [];
641
+ const keysToRemove = [];
642
+ const len = window.sessionStorage.length;
643
+ for (let i = 0; i < len; i++) {
644
+ const key = window.sessionStorage.key(i);
645
+ if (key && patterns.some((p) => this.matchesPattern(key, p))) {
646
+ keysToRemove.push(key);
647
+ }
648
+ }
649
+ for (const key of keysToRemove) {
650
+ window.sessionStorage.removeItem(key);
651
+ }
652
+ return keysToRemove;
653
+ } catch (e) {
654
+ return [];
655
+ }
656
+ }
657
+ /**
658
+ * Purges cookies declared for services, supporting wildcard glob names (e.g. "_ga_*").
659
+ */
660
+ static purgeCookies(cookieDeclarations, defaultPath = "/", defaultDomain) {
661
+ if (typeof document === "undefined" || !cookieDeclarations || cookieDeclarations.length === 0) {
662
+ return [];
663
+ }
664
+ const removedCookies = [];
665
+ const allExistingCookies = this.getAllCookieNames();
666
+ for (const cookieItem of cookieDeclarations) {
667
+ const targetDomain = cookieItem.domain || defaultDomain;
668
+ if (cookieItem.name.includes("*")) {
669
+ for (const existingName of allExistingCookies) {
670
+ if (this.matchesPattern(existingName, cookieItem.name)) {
671
+ CookieStore.remove(existingName, defaultPath, targetDomain);
672
+ if (!removedCookies.includes(existingName)) {
673
+ removedCookies.push(existingName);
674
+ }
675
+ }
676
+ }
677
+ } else {
678
+ CookieStore.remove(cookieItem.name, defaultPath, targetDomain);
679
+ if (!removedCookies.includes(cookieItem.name)) {
680
+ removedCookies.push(cookieItem.name);
681
+ }
682
+ }
683
+ }
684
+ return removedCookies;
685
+ }
686
+ static getAllCookieNames() {
687
+ if (typeof document === "undefined") return [];
688
+ try {
689
+ return document.cookie.split(";").map((c) => c.trim().split("=")[0]).filter((n) => !!n);
690
+ } catch (e) {
691
+ return [];
692
+ }
693
+ }
694
+ /**
695
+ * Purges all cookies, localStorage, and sessionStorage keys associated with a category.
696
+ */
697
+ static purgeCategory(config, category) {
698
+ var _a, _b, _c;
699
+ const report = {
700
+ category,
701
+ purgedCookies: [],
702
+ purgedLocalStorage: [],
703
+ purgedSessionStorage: []
704
+ };
705
+ if (!config) return report;
706
+ const path = ((_a = config.storage) == null ? void 0 : _a.path) || "/";
707
+ const defaultDomain = (_b = config.storage) == null ? void 0 : _b.domain;
708
+ const cookiesToPurge = [];
709
+ const localStoragePatterns = /* @__PURE__ */ new Set();
710
+ const sessionStoragePatterns = /* @__PURE__ */ new Set();
711
+ const catConfig = (_c = config.categories) == null ? void 0 : _c[category];
712
+ if (catConfig) {
713
+ if (catConfig.storageKeys) {
714
+ catConfig.storageKeys.forEach((k) => {
715
+ localStoragePatterns.add(k);
716
+ sessionStoragePatterns.add(k);
717
+ });
718
+ }
719
+ if (catConfig.localStorage) {
720
+ catConfig.localStorage.forEach((k) => localStoragePatterns.add(k));
721
+ }
722
+ if (catConfig.sessionStorage) {
723
+ catConfig.sessionStorage.forEach((k) => sessionStoragePatterns.add(k));
724
+ }
725
+ }
726
+ if (config.services) {
727
+ for (const service of Object.values(config.services)) {
728
+ if (service.category === category) {
729
+ if (service.cookies) {
730
+ for (const cookieItem of service.cookies) {
731
+ cookiesToPurge.push({
732
+ name: cookieItem.name,
733
+ domain: cookieItem.domain || defaultDomain
734
+ });
735
+ }
736
+ }
737
+ if (service.storageKeys) {
738
+ service.storageKeys.forEach((k) => {
739
+ localStoragePatterns.add(k);
740
+ sessionStoragePatterns.add(k);
741
+ });
742
+ }
743
+ if (service.localStorage) {
744
+ service.localStorage.forEach((k) => localStoragePatterns.add(k));
745
+ }
746
+ if (service.sessionStorage) {
747
+ service.sessionStorage.forEach((k) => sessionStoragePatterns.add(k));
748
+ }
749
+ }
750
+ }
751
+ }
752
+ report.purgedCookies = this.purgeCookies(cookiesToPurge, path, defaultDomain);
753
+ report.purgedLocalStorage = this.purgeLocalStorage(Array.from(localStoragePatterns));
754
+ report.purgedSessionStorage = this.purgeSessionStorage(Array.from(sessionStoragePatterns));
755
+ return report;
756
+ }
757
+ };
758
+
559
759
  // src/blocker/script-gate.ts
560
760
  var ScriptResourceBlocker = class {
561
761
  constructor() {
@@ -1320,6 +1520,152 @@ function injectStyles(nonce) {
1320
1520
  flex-direction: row;
1321
1521
  }
1322
1522
  }
1523
+
1524
+ /* Floating Revocation & Preference Badge */
1525
+ .consent-floating-badge {
1526
+ position: fixed;
1527
+ z-index: 2147483640;
1528
+ font-family: var(--consent-font);
1529
+ opacity: 0;
1530
+ transform: scale(0.85);
1531
+ pointer-events: none;
1532
+ transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1), transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
1533
+ }
1534
+
1535
+ .consent-floating-badge.is-visible {
1536
+ opacity: 1;
1537
+ transform: scale(1);
1538
+ pointer-events: auto;
1539
+ }
1540
+
1541
+ .consent-floating-badge--bottom-left {
1542
+ bottom: 1.25rem;
1543
+ left: 1.25rem;
1544
+ }
1545
+
1546
+ .consent-floating-badge--bottom-right {
1547
+ bottom: 1.25rem;
1548
+ right: 1.25rem;
1549
+ }
1550
+
1551
+ .consent-floating-badge--top-left {
1552
+ top: 1.25rem;
1553
+ left: 1.25rem;
1554
+ }
1555
+
1556
+ .consent-floating-badge--top-right {
1557
+ top: 1.25rem;
1558
+ right: 1.25rem;
1559
+ }
1560
+
1561
+ .consent-floating-badge-inner {
1562
+ position: relative;
1563
+ display: inline-flex;
1564
+ align-items: center;
1565
+ }
1566
+
1567
+ .consent-floating-badge-btn {
1568
+ display: inline-flex;
1569
+ align-items: center;
1570
+ justify-content: center;
1571
+ gap: 0.5rem;
1572
+ width: 46px;
1573
+ height: 46px;
1574
+ min-width: 46px;
1575
+ border-radius: 9999px;
1576
+ background: var(--consent-bg);
1577
+ color: var(--consent-fg);
1578
+ border: 1px solid var(--consent-border);
1579
+ box-shadow: 0 10px 25px -5px rgba(15, 23, 42, 0.15), 0 4px 6px -2px rgba(15, 23, 42, 0.05);
1580
+ cursor: pointer;
1581
+ padding: 0;
1582
+ margin: 0;
1583
+ transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, border-color 0.2s ease, color 0.2s ease;
1584
+ outline: none;
1585
+ }
1586
+
1587
+ .consent-floating-badge-btn.has-label {
1588
+ width: auto;
1589
+ padding: 0 1rem;
1590
+ }
1591
+
1592
+ .consent-floating-badge-btn:hover {
1593
+ transform: translateY(-2px);
1594
+ box-shadow: 0 14px 28px -6px rgba(15, 23, 42, 0.2), 0 6px 10px -3px rgba(15, 23, 42, 0.08);
1595
+ border-color: var(--consent-accent);
1596
+ color: var(--consent-accent);
1597
+ }
1598
+
1599
+ .consent-floating-badge-btn:active {
1600
+ transform: translateY(0) scale(0.96);
1601
+ }
1602
+
1603
+ .consent-floating-badge-btn:focus-visible {
1604
+ box-shadow: 0 0 0 3px var(--consent-focus-ring), 0 10px 25px -5px rgba(15, 23, 42, 0.15);
1605
+ border-color: var(--consent-accent);
1606
+ }
1607
+
1608
+ .consent-floating-badge-icon {
1609
+ display: inline-flex;
1610
+ align-items: center;
1611
+ justify-content: center;
1612
+ width: 22px;
1613
+ height: 22px;
1614
+ }
1615
+
1616
+ .consent-floating-badge-text {
1617
+ font-size: 0.85rem;
1618
+ font-weight: 600;
1619
+ white-space: nowrap;
1620
+ }
1621
+
1622
+ .consent-floating-badge-tooltip {
1623
+ position: absolute;
1624
+ white-space: nowrap;
1625
+ pointer-events: none;
1626
+ opacity: 0;
1627
+ visibility: hidden;
1628
+ background: var(--consent-primary);
1629
+ color: var(--consent-primary-fg);
1630
+ font-size: 0.76rem;
1631
+ font-weight: 500;
1632
+ padding: 0.4rem 0.75rem;
1633
+ border-radius: 8px;
1634
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
1635
+ transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
1636
+ z-index: 2147483641;
1637
+ }
1638
+
1639
+ .consent-floating-badge--bottom-left .consent-floating-badge-tooltip {
1640
+ bottom: calc(100% + 8px);
1641
+ left: 0;
1642
+ transform: translateY(4px);
1643
+ }
1644
+
1645
+ .consent-floating-badge--bottom-right .consent-floating-badge-tooltip {
1646
+ bottom: calc(100% + 8px);
1647
+ right: 0;
1648
+ transform: translateY(4px);
1649
+ }
1650
+
1651
+ .consent-floating-badge--top-left .consent-floating-badge-tooltip {
1652
+ top: calc(100% + 8px);
1653
+ left: 0;
1654
+ transform: translateY(-4px);
1655
+ }
1656
+
1657
+ .consent-floating-badge--top-right .consent-floating-badge-tooltip {
1658
+ top: calc(100% + 8px);
1659
+ right: 0;
1660
+ transform: translateY(-4px);
1661
+ }
1662
+
1663
+ .consent-floating-badge-inner:hover .consent-floating-badge-tooltip,
1664
+ .consent-floating-badge-btn:focus-visible + .consent-floating-badge-tooltip {
1665
+ opacity: 1;
1666
+ visibility: visible;
1667
+ transform: translateY(0);
1668
+ }
1323
1669
  `;
1324
1670
  document.head.appendChild(styleEl);
1325
1671
  }
@@ -1342,6 +1688,9 @@ var ConsentBanner = class {
1342
1688
  const acceptText = sanitizeHtml(bannerConfig.accept || "Aceptar todas");
1343
1689
  const rejectText = sanitizeHtml(bannerConfig.reject || "Rechazar todas");
1344
1690
  const configureText = sanitizeHtml(bannerConfig.configure || "Configurar");
1691
+ const cookiesPolicyText = sanitizeHtml(bannerConfig.cookiesPolicy || "Pol\xEDtica de cookies");
1692
+ const privacyPolicyText = sanitizeHtml(bannerConfig.privacyPolicy || "Pol\xEDtica de privacidad");
1693
+ const bannerAriaLabel = sanitizeHtml(bannerConfig.ariaLabel || "Gesti\xF3n de consentimiento de privacidad");
1345
1694
  const privacyUrl = sanitizeUrl(((_c = config.policy) == null ? void 0 : _c.privacyUrl) || "/politica-privacidad");
1346
1695
  const cookiesUrl = sanitizeUrl(((_d = config.policy) == null ? void 0 : _d.cookiesUrl) || "/politica-cookies");
1347
1696
  const wrapper = document.createElement("div");
@@ -1349,7 +1698,7 @@ var ConsentBanner = class {
1349
1698
  wrapper.setAttribute("role", "region");
1350
1699
  wrapper.setAttribute(
1351
1700
  "aria-label",
1352
- "Gesti\xF3n de consentimiento de privacidad"
1701
+ bannerAriaLabel
1353
1702
  );
1354
1703
  wrapper.innerHTML = `
1355
1704
  <div class="consent-banner-container">
@@ -1364,8 +1713,8 @@ var ConsentBanner = class {
1364
1713
  </h2>
1365
1714
  <p class="consent-banner-desc">${descText}</p>
1366
1715
  <div class="consent-banner-links">
1367
- <a href="${cookiesUrl}" target="_blank" rel="noopener">Pol\xEDtica de cookies</a>
1368
- <a href="${privacyUrl}" target="_blank" rel="noopener">Pol\xEDtica de privacidad</a>
1716
+ <a href="${cookiesUrl}" target="_blank" rel="noopener">${cookiesPolicyText}</a>
1717
+ <a href="${privacyUrl}" target="_blank" rel="noopener">${privacyPolicyText}</a>
1369
1718
  </div>
1370
1719
  </div>
1371
1720
  <div class="consent-banner-actions">
@@ -1395,6 +1744,9 @@ var ConsentBanner = class {
1395
1744
  document.body.appendChild(wrapper);
1396
1745
  this.element = wrapper;
1397
1746
  }
1747
+ getIsVisible() {
1748
+ return this.element !== null;
1749
+ }
1398
1750
  remove() {
1399
1751
  if (this.element && this.element.parentNode) {
1400
1752
  this.element.parentNode.removeChild(this.element);
@@ -1417,9 +1769,19 @@ var PreferencesModal = class {
1417
1769
  this.lastFocusedElement = document.activeElement;
1418
1770
  const prefConfig = ((_b = config.ui) == null ? void 0 : _b.preferences) || {};
1419
1771
  const modalTitle = sanitizeHtml(prefConfig.title || "Preferencias de privacidad");
1772
+ const modalSubtitle = sanitizeHtml(
1773
+ prefConfig.subtitle || "Gestiona tus permisos de almacenamiento por finalidad (LSSI art. 22.2 & RGPD)."
1774
+ );
1420
1775
  const saveText = sanitizeHtml(prefConfig.save || "Guardar selecci\xF3n");
1421
1776
  const acceptAllText = sanitizeHtml(prefConfig.acceptAll || "Permitir todas");
1422
1777
  const rejectAllText = sanitizeHtml(prefConfig.rejectAll || "Rechazar opcionales");
1778
+ const closeLabel = sanitizeHtml(prefConfig.closeLabel || "Cerrar ventana");
1779
+ const requiredBadge = sanitizeHtml(prefConfig.requiredBadge || "Requerida");
1780
+ const optionalBadge = sanitizeHtml(prefConfig.optionalBadge || "Opcional");
1781
+ const servicesLabel = sanitizeHtml(prefConfig.servicesLabel || "Servicios incluidos");
1782
+ const viewServices = sanitizeHtml(prefConfig.viewServices || "Ver servicios");
1783
+ const hideServices = sanitizeHtml(prefConfig.hideServices || "Ocultar servicios");
1784
+ const thirdParty = sanitizeHtml(prefConfig.thirdParty || "Terceros");
1423
1785
  const backdrop = document.createElement("div");
1424
1786
  backdrop.className = "consent-dialog-backdrop";
1425
1787
  const dialog = document.createElement("div");
@@ -1433,10 +1795,10 @@ var PreferencesModal = class {
1433
1795
  <div>
1434
1796
  <h2 class="consent-dialog-title" id="consent-dialog-title-id">${modalTitle}</h2>
1435
1797
  <p style="margin: 0.2rem 0 0 0; font-size: 0.84rem; color: var(--consent-muted); font-weight: 400;">
1436
- Gestiona tus permisos de almacenamiento por finalidad (LSSI art. 22.2 &amp; RGPD).
1798
+ ${modalSubtitle}
1437
1799
  </p>
1438
1800
  </div>
1439
- <button type="button" class="consent-dialog-close" aria-label="Cerrar ventana">
1801
+ <button type="button" class="consent-dialog-close" aria-label="${closeLabel}">
1440
1802
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
1441
1803
  <line x1="18" y1="6" x2="6" y2="18"></line>
1442
1804
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -1457,14 +1819,14 @@ var PreferencesModal = class {
1457
1819
  servicesHtml = `
1458
1820
  <div class="consent-category-services" id="cat-services-${catId}" style="display: none; margin-top: 0.75rem; padding-top: 0.6rem; border-top: 1px dashed var(--consent-divider); font-size: 0.82rem;">
1459
1821
  <div style="font-weight: 600; color: var(--consent-muted); margin-bottom: 0.4rem; font-size: 0.75rem; letter-spacing: 0.02em;">
1460
- Servicios incluidos (${associatedServices.length})
1822
+ ${servicesLabel} (${associatedServices.length})
1461
1823
  </div>
1462
1824
  <div style="display: flex; flex-direction: column; gap: 0.3rem;">
1463
1825
  ${associatedServices.map(
1464
1826
  ([srvId, srv]) => `
1465
1827
  <div style="display: flex; justify-content: space-between; align-items: center; padding: 0.2rem 0;">
1466
1828
  <span style="font-weight: 500;">${sanitizeHtml(srv.label || srvId)}</span>
1467
- <span style="color: var(--consent-muted); font-size: 0.78rem;">${sanitizeHtml(srv.provider || "Terceros")}</span>
1829
+ <span style="color: var(--consent-muted); font-size: 0.78rem;">${sanitizeHtml(srv.provider || thirdParty)}</span>
1468
1830
  </div>
1469
1831
  `
1470
1832
  ).join("")}
@@ -1477,11 +1839,11 @@ var PreferencesModal = class {
1477
1839
  <div style="flex: 1; padding-right: 1rem;">
1478
1840
  <div style="display: flex; align-items: center; gap: 0.4rem;">
1479
1841
  <span class="consent-category-name">${sanitizeHtml(catConfig.label)}</span>
1480
- ${isRequired ? '<span class="consent-badge consent-badge-required">Requerida</span>' : '<span class="consent-badge consent-badge-optional">Opcional</span>'}
1842
+ ${isRequired ? `<span class="consent-badge consent-badge-required">${requiredBadge}</span>` : `<span class="consent-badge consent-badge-optional">${optionalBadge}</span>`}
1481
1843
  </div>
1482
1844
  <p class="consent-category-desc">${sanitizeHtml(catConfig.description)}</p>
1483
1845
  ${associatedServices.length > 0 ? `<button type="button" class="consent-toggle-services-btn" data-target="cat-services-${catId}">
1484
- Ver servicios (${associatedServices.length}) \u25BE
1846
+ ${viewServices} (${associatedServices.length}) \u25BE
1485
1847
  </button>` : ""}
1486
1848
  </div>
1487
1849
  <label class="consent-toggle">
@@ -1510,7 +1872,7 @@ var PreferencesModal = class {
1510
1872
  if (targetEl) {
1511
1873
  const isHidden = targetEl.style.display === "none";
1512
1874
  targetEl.style.display = isHidden ? "block" : "none";
1513
- toggleServicesBtn.textContent = isHidden ? `Ocultar servicios (${associatedServices.length}) \u25B4` : `Ver servicios (${associatedServices.length}) \u25BE`;
1875
+ toggleServicesBtn.textContent = isHidden ? `${hideServices} (${associatedServices.length}) \u25B4` : `${viewServices} (${associatedServices.length}) \u25BE`;
1514
1876
  }
1515
1877
  }
1516
1878
  });
@@ -1540,6 +1902,12 @@ var PreferencesModal = class {
1540
1902
  handlers.onClose();
1541
1903
  this.close();
1542
1904
  });
1905
+ backdrop.addEventListener("click", (e) => {
1906
+ if (e.target === backdrop) {
1907
+ handlers.onClose();
1908
+ this.close();
1909
+ }
1910
+ });
1543
1911
  (_e = footer.querySelector("#consent-pref-reject")) == null ? void 0 : _e.addEventListener("click", () => {
1544
1912
  handlers.onRejectAll();
1545
1913
  this.close();
@@ -1584,6 +1952,9 @@ var PreferencesModal = class {
1584
1952
  const firstFocusable = dialog.querySelector("button");
1585
1953
  firstFocusable == null ? void 0 : firstFocusable.focus();
1586
1954
  }
1955
+ getIsOpen() {
1956
+ return this.backdrop !== null;
1957
+ }
1587
1958
  close() {
1588
1959
  if (this._keyListener) {
1589
1960
  document.removeEventListener("keydown", this._keyListener);
@@ -1600,6 +1971,109 @@ var PreferencesModal = class {
1600
1971
  }
1601
1972
  };
1602
1973
 
1974
+ // src/ui/floating-badge.ts
1975
+ var FloatingBadge = class {
1976
+ constructor() {
1977
+ this.element = null;
1978
+ this.isVisible = false;
1979
+ }
1980
+ getIconSvg(icon) {
1981
+ switch (icon) {
1982
+ case "shield":
1983
+ return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1984
+ <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
1985
+ <path d="m9 12 2 2 4-4"/>
1986
+ </svg>`;
1987
+ case "settings":
1988
+ return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1989
+ <circle cx="12" cy="12" r="3"/>
1990
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
1991
+ </svg>`;
1992
+ case "cookie":
1993
+ default:
1994
+ return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1995
+ <path d="M12 2a10 10 0 1 0 10 10 4 4 0 0 1-5-5 4 4 0 0 1-5-5"/>
1996
+ <path d="M8.5 8.5v.01"/>
1997
+ <path d="M16 15.5v.01"/>
1998
+ <path d="M12 12v.01"/>
1999
+ <path d="M11 17v.01"/>
2000
+ <path d="M7 13v.01"/>
2001
+ </svg>`;
2002
+ }
2003
+ }
2004
+ render(config, handlers) {
2005
+ var _a, _b, _c;
2006
+ if (typeof document === "undefined") return;
2007
+ this.remove();
2008
+ injectStyles((_a = config.csp) == null ? void 0 : _a.nonce);
2009
+ const badgeConfig = typeof ((_b = config.ui) == null ? void 0 : _b.floatingBadge) === "object" && ((_c = config.ui) == null ? void 0 : _c.floatingBadge) !== null ? config.ui.floatingBadge : {};
2010
+ const position = badgeConfig.position || "bottom-left";
2011
+ const labelText = badgeConfig.label ? sanitizeHtml(badgeConfig.label) : "";
2012
+ const ariaLabel = badgeConfig.ariaLabel ? sanitizeHtml(badgeConfig.ariaLabel) : labelText || "Configuraci\xF3n y revocaci\xF3n de cookies";
2013
+ const tooltipText = badgeConfig.tooltip ? sanitizeHtml(badgeConfig.tooltip) : labelText || "Configurar o declinar cookies";
2014
+ const showLabel = badgeConfig.showLabel === true && !!labelText;
2015
+ const iconSvg = this.getIconSvg(badgeConfig.icon);
2016
+ const container = document.createElement("div");
2017
+ container.className = `consent-floating-badge consent-floating-badge--${position}`;
2018
+ container.setAttribute("role", "complementary");
2019
+ container.setAttribute("aria-label", ariaLabel);
2020
+ container.innerHTML = `
2021
+ <div class="consent-floating-badge-inner">
2022
+ <button
2023
+ type="button"
2024
+ class="consent-floating-badge-btn ${showLabel ? "has-label" : ""}"
2025
+ id="consent-floating-trigger"
2026
+ data-consent-open
2027
+ aria-label="${ariaLabel}"
2028
+ >
2029
+ <span class="consent-floating-badge-icon" aria-hidden="true">
2030
+ ${iconSvg}
2031
+ </span>
2032
+ ${showLabel ? `<span class="consent-floating-badge-text">${labelText}</span>` : ""}
2033
+ </button>
2034
+ <div class="consent-floating-badge-tooltip" role="tooltip" aria-hidden="true">
2035
+ ${tooltipText}
2036
+ </div>
2037
+ </div>
2038
+ `;
2039
+ const button = container.querySelector("#consent-floating-trigger");
2040
+ button == null ? void 0 : button.addEventListener("click", (e) => {
2041
+ e.preventDefault();
2042
+ handlers.onClick();
2043
+ });
2044
+ document.body.appendChild(container);
2045
+ this.element = container;
2046
+ }
2047
+ show() {
2048
+ if (this.element) {
2049
+ this.element.classList.add("is-visible");
2050
+ this.isVisible = true;
2051
+ }
2052
+ }
2053
+ hide() {
2054
+ if (this.element) {
2055
+ this.element.classList.remove("is-visible");
2056
+ this.isVisible = false;
2057
+ }
2058
+ }
2059
+ getIsVisible() {
2060
+ return this.isVisible;
2061
+ }
2062
+ hasElement() {
2063
+ return this.element !== null;
2064
+ }
2065
+ getElement() {
2066
+ return this.element;
2067
+ }
2068
+ remove() {
2069
+ if (this.element && this.element.parentNode) {
2070
+ this.element.parentNode.removeChild(this.element);
2071
+ this.element = null;
2072
+ this.isVisible = false;
2073
+ }
2074
+ }
2075
+ };
2076
+
1603
2077
  // src/diagnostics/resource-scanner.ts
1604
2078
  var ResourceScanner = class _ResourceScanner {
1605
2079
  static runDiagnostic(_config, isConsentGiven) {
@@ -1626,42 +2100,194 @@ var ResourceScanner = class _ResourceScanner {
1626
2100
 
1627
2101
  // src/ui/policy-generator.ts
1628
2102
  var PolicyGenerator = class {
1629
- static renderTable(config) {
2103
+ /**
2104
+ * Extract all cookie and storage key details across categories and services (including presets).
2105
+ */
2106
+ static extractCookieRows(config) {
2107
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
2108
+ const rows = [];
2109
+ if (config.services) {
2110
+ for (const [srvId, srv] of Object.entries(config.services)) {
2111
+ const cat = config.categories[srv.category || "necessary"] || { label: srv.category || "General" };
2112
+ const providerName = srv.provider || srv.label || srvId;
2113
+ if (srv.cookies && srv.cookies.length > 0) {
2114
+ for (const c of srv.cookies) {
2115
+ rows.push({
2116
+ name: c.name,
2117
+ type: "Cookie",
2118
+ provider: providerName,
2119
+ purpose: c.purpose || srv.description || cat.description || "Funcionalidad del servicio",
2120
+ duration: c.duration || "Persistente",
2121
+ categoryLabel: cat.label,
2122
+ policyUrl: srv.policyUrl
2123
+ });
2124
+ }
2125
+ }
2126
+ if (srv.storageKeys && srv.storageKeys.length > 0) {
2127
+ for (const k of srv.storageKeys) {
2128
+ rows.push({
2129
+ name: k,
2130
+ type: "localStorage",
2131
+ provider: providerName,
2132
+ purpose: srv.description || cat.description || "Almacenamiento de estado y preferencias",
2133
+ duration: "Persistente",
2134
+ categoryLabel: cat.label,
2135
+ policyUrl: srv.policyUrl
2136
+ });
2137
+ }
2138
+ }
2139
+ if (srv.localStorage && srv.localStorage.length > 0) {
2140
+ for (const k of srv.localStorage) {
2141
+ rows.push({
2142
+ name: k,
2143
+ type: "localStorage",
2144
+ provider: providerName,
2145
+ purpose: srv.description || cat.description || "Almacenamiento web local",
2146
+ duration: "Persistente",
2147
+ categoryLabel: cat.label,
2148
+ policyUrl: srv.policyUrl
2149
+ });
2150
+ }
2151
+ }
2152
+ if (srv.sessionStorage && srv.sessionStorage.length > 0) {
2153
+ for (const k of srv.sessionStorage) {
2154
+ rows.push({
2155
+ name: k,
2156
+ type: "sessionStorage",
2157
+ provider: providerName,
2158
+ purpose: srv.description || cat.description || "Almacenamiento temporal de sesi\xF3n",
2159
+ duration: "Sesi\xF3n",
2160
+ categoryLabel: cat.label,
2161
+ policyUrl: srv.policyUrl
2162
+ });
2163
+ }
2164
+ }
2165
+ if (!((_a = srv.cookies) == null ? void 0 : _a.length) && !((_b = srv.storageKeys) == null ? void 0 : _b.length) && !((_c = srv.localStorage) == null ? void 0 : _c.length) && !((_d = srv.sessionStorage) == null ? void 0 : _d.length)) {
2166
+ rows.push({
2167
+ name: srvId,
2168
+ type: "Cookie",
2169
+ provider: providerName,
2170
+ purpose: srv.description || cat.description || "Servicio integrado",
2171
+ duration: "Variable",
2172
+ categoryLabel: cat.label,
2173
+ policyUrl: srv.policyUrl
2174
+ });
2175
+ }
2176
+ }
2177
+ }
2178
+ if (config.categories) {
2179
+ for (const [, cat] of Object.entries(config.categories)) {
2180
+ if (cat.storageKeys) {
2181
+ for (const k of cat.storageKeys) {
2182
+ if (!rows.some((r) => r.name === k)) {
2183
+ rows.push({
2184
+ name: k,
2185
+ type: "localStorage",
2186
+ provider: "Propia / Sitio Web",
2187
+ purpose: cat.description,
2188
+ duration: "Persistente",
2189
+ categoryLabel: cat.label
2190
+ });
2191
+ }
2192
+ }
2193
+ }
2194
+ if (cat.localStorage) {
2195
+ for (const k of cat.localStorage) {
2196
+ if (!rows.some((r) => r.name === k)) {
2197
+ rows.push({
2198
+ name: k,
2199
+ type: "localStorage",
2200
+ provider: "Propia / Sitio Web",
2201
+ purpose: cat.description,
2202
+ duration: "Persistente",
2203
+ categoryLabel: cat.label
2204
+ });
2205
+ }
2206
+ }
2207
+ }
2208
+ if (cat.sessionStorage) {
2209
+ for (const k of cat.sessionStorage) {
2210
+ if (!rows.some((r) => r.name === k)) {
2211
+ rows.push({
2212
+ name: k,
2213
+ type: "sessionStorage",
2214
+ provider: "Propia / Sitio Web",
2215
+ purpose: cat.description,
2216
+ duration: "Sesi\xF3n",
2217
+ categoryLabel: cat.label
2218
+ });
2219
+ }
2220
+ }
2221
+ }
2222
+ }
2223
+ }
2224
+ const consentCookieName = ((_e = config.storage) == null ? void 0 : _e.name) || "site_consent";
2225
+ if (!rows.some((r) => r.name === consentCookieName)) {
2226
+ rows.unshift({
2227
+ name: consentCookieName,
2228
+ type: ((_f = config.storage) == null ? void 0 : _f.type) === "memory" ? "localStorage" : "Cookie",
2229
+ provider: "Propia (Solvenza Cookies Compliance)",
2230
+ purpose: "Guarda las preferencias y recibo firmado de consentimiento del usuario.",
2231
+ duration: `${(_h = (_g = config.consent) == null ? void 0 : _g.maxAgeDays) != null ? _h : 365} d\xEDas`,
2232
+ categoryLabel: ((_j = (_i = config.categories) == null ? void 0 : _i.necessary) == null ? void 0 : _j.label) || "Necesarias"
2233
+ });
2234
+ }
2235
+ return rows;
2236
+ }
2237
+ /**
2238
+ * Render stylized table of cookies and storage keys.
2239
+ */
2240
+ static renderTable(config, options = {}) {
2241
+ const isDark = options.theme === "dark";
2242
+ const bgHeader = isDark ? "#1e293b" : "#f8fafc";
2243
+ const borderCol = isDark ? "#334155" : "#e2e8f0";
2244
+ const textCol = isDark ? "#f8fafc" : "#0f172a";
2245
+ const mutedCol = isDark ? "#94a3b8" : "#64748b";
2246
+ const rowAltBg = isDark ? "#0f172a" : "#ffffff";
2247
+ const rowBg = isDark ? "#1e293b" : "#f8fafc";
1630
2248
  let html = `
1631
- <div class="consent-policy-table-wrapper" style="margin: 1.5rem 0; font-family: system-ui, sans-serif;">
1632
- <table style="width: 100%; border-collapse: collapse; text-align: left; border: 1px solid #e2e8f0;">
2249
+ <div class="solvenza-policy-table-wrapper" style="overflow-x: auto; margin: 1.5rem 0; border: 1px solid ${borderCol}; border-radius: 10px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); font-family: system-ui, -apple-system, sans-serif;">
2250
+ <table style="width: 100%; border-collapse: collapse; text-align: left; font-size: 0.88rem; color: ${textCol};">
1633
2251
  <thead>
1634
- <tr style="background: #f8fafc; border-bottom: 2px solid #cbd5e1;">
1635
- <th style="padding: 0.75rem; border: 1px solid #e2e8f0;">Categor\xEDa / Servicio</th>
1636
- <th style="padding: 0.75rem; border: 1px solid #e2e8f0;">Proveedor</th>
1637
- <th style="padding: 0.75rem; border: 1px solid #e2e8f0;">Finalidad</th>
1638
- <th style="padding: 0.75rem; border: 1px solid #e2e8f0;">Requerida</th>
2252
+ <tr style="background: ${bgHeader}; border-bottom: 2px solid ${borderCol};">
2253
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Nombre / Clave</th>
2254
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Tipo</th>
2255
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Categor\xEDa</th>
2256
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Proveedor</th>
2257
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Finalidad</th>
2258
+ <th style="padding: 0.85rem 1rem; font-weight: 700;">Duraci\xF3n</th>
1639
2259
  </tr>
1640
2260
  </thead>
1641
2261
  <tbody>
1642
2262
  `;
1643
- for (const [catId, cat] of Object.entries(config.categories)) {
1644
- const isReq = cat.required ? "S\xED (T\xE9cnica)" : "No (Opcional)";
2263
+ const rows = this.extractCookieRows(config);
2264
+ if (rows.length === 0) {
1645
2265
  html += `
1646
- <tr style="background: #ffffff; font-weight: 600;">
1647
- <td style="padding: 0.75rem; border: 1px solid #e2e8f0;" colspan="3">${sanitizeHtml(cat.label)}</td>
1648
- <td style="padding: 0.75rem; border: 1px solid #e2e8f0;">${isReq}</td>
2266
+ <tr>
2267
+ <td colspan="6" style="padding: 1.5rem; text-align: center; color: ${mutedCol};">
2268
+ No se han registrado cookies ni elementos de almacenamiento en la configuraci\xF3n.
2269
+ </td>
1649
2270
  </tr>
1650
2271
  `;
1651
- if (config.services) {
1652
- for (const [srvId, srv] of Object.entries(config.services)) {
1653
- if (srv.category === catId) {
1654
- html += `
1655
- <tr style="background: #f8fafc; font-size: 0.9rem;">
1656
- <td style="padding: 0.5rem 0.75rem 0.5rem 1.5rem; border: 1px solid #e2e8f0;">\u21B3 ${sanitizeHtml(srv.label || srvId)}</td>
1657
- <td style="padding: 0.5rem 0.75rem; border: 1px solid #e2e8f0;">${sanitizeHtml(srv.provider || "-")}</td>
1658
- <td style="padding: 0.5rem 0.75rem; border: 1px solid #e2e8f0;">${sanitizeHtml(cat.description || "-")}</td>
1659
- <td style="padding: 0.5rem 0.75rem; border: 1px solid #e2e8f0;">${isReq}</td>
1660
- </tr>
1661
- `;
1662
- }
1663
- }
1664
- }
2272
+ } else {
2273
+ rows.forEach((r, idx) => {
2274
+ const bg = idx % 2 === 0 ? rowAltBg : rowBg;
2275
+ const providerHtml = r.policyUrl ? `<a href="${sanitizeHtml(r.policyUrl)}" target="_blank" rel="noopener noreferrer" style="color: #2563eb; text-decoration: underline;">${sanitizeHtml(r.provider)} \u2197</a>` : sanitizeHtml(r.provider);
2276
+ html += `
2277
+ <tr style="background: ${bg}; border-bottom: 1px solid ${borderCol};">
2278
+ <td style="padding: 0.75rem 1rem; font-family: monospace; font-weight: 600; color: #2563eb;">${sanitizeHtml(r.name)}</td>
2279
+ <td style="padding: 0.75rem 1rem;">
2280
+ <span style="display: inline-block; padding: 0.15rem 0.5rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; background: ${r.type === "Cookie" ? "#e0e7ff" : "#fef3c7"}; color: ${r.type === "Cookie" ? "#3730a3" : "#92400e"};">
2281
+ ${r.type}
2282
+ </span>
2283
+ </td>
2284
+ <td style="padding: 0.75rem 1rem; font-weight: 500;">${sanitizeHtml(r.categoryLabel)}</td>
2285
+ <td style="padding: 0.75rem 1rem;">${providerHtml}</td>
2286
+ <td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(r.purpose)}</td>
2287
+ <td style="padding: 0.75rem 1rem; white-space: nowrap;">${sanitizeHtml(r.duration)}</td>
2288
+ </tr>
2289
+ `;
2290
+ });
1665
2291
  }
1666
2292
  html += `
1667
2293
  </tbody>
@@ -1670,8 +2296,1164 @@ var PolicyGenerator = class {
1670
2296
  `;
1671
2297
  return html;
1672
2298
  }
2299
+ /**
2300
+ * Render complete legal Cookie Policy document adapted to LSSI art. 22.2 and AEPD 2024.
2301
+ */
2302
+ static renderCookiePolicy(config, options = {}) {
2303
+ var _a, _b, _c;
2304
+ if (options.view === "table-only") {
2305
+ return this.renderTable(config, options);
2306
+ }
2307
+ const companyName = ((_a = config.legalEntity) == null ? void 0 : _a.tradeName) || ((_b = config.legalEntity) == null ? void 0 : _b.name) || "el Titular del Sitio Web";
2308
+ const lastUpdated = ((_c = config.legalNotice) == null ? void 0 : _c.lastUpdated) || config.policyVersion || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2309
+ const isDark = options.theme === "dark";
2310
+ const textCol = isDark ? "#f8fafc" : "#0f172a";
2311
+ const mutedCol = isDark ? "#94a3b8" : "#475569";
2312
+ const cardBg = isDark ? "#1e293b" : "#ffffff";
2313
+ const borderCol = isDark ? "#334155" : "#e2e8f0";
2314
+ const tableHtml = this.renderTable(config, options);
2315
+ let html = `
2316
+ <article class="solvenza-cookie-policy-document ${options.className || ""}" style="font-family: system-ui, -apple-system, sans-serif; color: ${textCol}; line-height: 1.7; max-width: 900px; margin: 0 auto; padding: 1rem 0;">
2317
+ <header style="margin-bottom: 2rem; border-bottom: 1px solid ${borderCol}; padding-bottom: 1.5rem;">
2318
+ <h1 style="font-size: 1.85rem; font-weight: 800; margin: 0 0 0.5rem 0; color: ${textCol};">Pol\xEDtica de Cookies</h1>
2319
+ <p style="margin: 0; font-size: 0.9rem; color: ${mutedCol};">
2320
+ Conforme al art\xEDculo 22.2 de la Ley 34/2002 (LSSI-CE), el RGPD (UE 2016/679) y la Gu\xEDa sobre el uso de cookies de la AEPD.
2321
+ <br><em>\xDAltima actualizaci\xF3n: ${sanitizeHtml(lastUpdated)}</em>
2322
+ </p>
2323
+ </header>
2324
+
2325
+ <section style="margin-bottom: 2rem;">
2326
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">1. \xBFQu\xE9 son las cookies y tecnolog\xEDas de almacenamiento local?</h2>
2327
+ <p style="color: ${mutedCol};">
2328
+ Este sitio web, titularidad de <strong>${sanitizeHtml(companyName)}</strong>, utiliza cookies y tecnolog\xEDas de almacenamiento similares (tales como <code>localStorage</code>, <code>sessionStorage</code>, p\xEDxeles de seguimiento y etiquetas) para garantizar el funcionamiento t\xE9cnico del sitio, optimizar la experiencia de navegaci\xF3n, medir el uso de la web y, en su caso, mostrar contenidos personalizados o multimedia.
2329
+ </p>
2330
+ <p style="color: ${mutedCol};">
2331
+ Una <strong>cookie</strong> es un peque\xF1o fichero de texto que se descarga y almacena en el navegador del usuario al acceder a determinadas p\xE1ginas web. Permite a una p\xE1gina web, entre otras cosas, recordar las preferencias de navegaci\xF3n, almacenar y recuperar informaci\xF3n sobre los h\xE1bitos de visita y reconocer al usuario en visitas posteriores.
2332
+ </p>
2333
+ </section>
2334
+
2335
+ <section style="margin-bottom: 2rem;">
2336
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">2. Tipos de cookies y finalidades utilizadas</h2>
2337
+ <p style="color: ${mutedCol};">
2338
+ En funci\xF3n de su finalidad, titularidad y plazo de permanencia, en este sitio web se utilizan las siguientes categor\xEDas:
2339
+ </p>
2340
+ <ul style="color: ${mutedCol}; padding-left: 1.5rem; margin-bottom: 1.5rem;">
2341
+ `;
2342
+ for (const [, cat] of Object.entries(config.categories)) {
2343
+ const isReq = cat.required === true;
2344
+ html += `
2345
+ <li style="margin-bottom: 0.6rem;">
2346
+ <strong>${sanitizeHtml(cat.label)}</strong> (${isReq ? "T\xE9cnicas / Exentas de consentimiento" : "Opcionales / Sujetas a consentimiento"}):
2347
+ ${sanitizeHtml(cat.description)}
2348
+ </li>
2349
+ `;
2350
+ }
2351
+ html += `
2352
+ </ul>
2353
+
2354
+ <h3 style="font-size: 1.1rem; font-weight: 700; color: ${textCol}; margin-top: 1.5rem;">Inventario detallado de cookies y almacenamiento web</h3>
2355
+ <p style="color: ${mutedCol}; font-size: 0.9rem;">
2356
+ A continuaci\xF3n se detallan de forma transparente todas las cookies y claves de almacenamiento registradas en la aplicaci\xF3n:
2357
+ </p>
2358
+ ${tableHtml}
2359
+ </section>
2360
+
2361
+ <section style="margin-bottom: 2rem; background: ${cardBg}; border: 1px solid ${borderCol}; border-radius: 12px; padding: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.03);">
2362
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol}; margin-top: 0;">3. Gesti\xF3n, configuraci\xF3n y revocaci\xF3n del consentimiento</h2>
2363
+ <p style="color: ${mutedCol};">
2364
+ De acuerdo con las directrices de la Agencia Espa\xF1ola de Protecci\xF3n de Datos (AEPD), retirar o modificar el consentimiento debe ser tan f\xE1cil como otorgarlo. Puedes modificar tus preferencias o revocar el consentimiento en cualquier momento:
2365
+ </p>
2366
+ <div style="display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1.25rem;">
2367
+ <button
2368
+ type="button"
2369
+ data-consent-open
2370
+ onclick="if(window.Consent) window.Consent.openPreferences()"
2371
+ style="background: #0f172a; color: #ffffff; border: none; padding: 0.65rem 1.3rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem; cursor: pointer; display: inline-flex; align-items: center; gap: 0.5rem;"
2372
+ >
2373
+ \u2699\uFE0F Abrir panel de preferencias de cookies
2374
+ </button>
2375
+ <button
2376
+ type="button"
2377
+ onclick="if(window.Consent) window.Consent.withdraw()"
2378
+ style="background: #ffffff; color: #dc2626; border: 1px solid #fca5a5; padding: 0.65rem 1.3rem; border-radius: 8px; font-weight: 600; font-size: 0.9rem; cursor: pointer; display: inline-flex; align-items: center; gap: 0.5rem;"
2379
+ >
2380
+ \u{1F504} Revocar consentimiento y purgar datos
2381
+ </button>
2382
+ </div>
2383
+ </section>
2384
+
2385
+ <section style="margin-bottom: 2rem;">
2386
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">4. C\xF3mo deshabilitar o eliminar las cookies en los navegadores</h2>
2387
+ <p style="color: ${mutedCol};">
2388
+ El usuario puede permitir, bloquear o eliminar las cookies instaladas en su equipo mediante la configuraci\xF3n de las opciones de su navegador web:
2389
+ </p>
2390
+ <ul style="color: #2563eb; padding-left: 1.5rem;">
2391
+ <li style="margin-bottom: 0.4rem;"><a href="https://support.google.com/chrome/answer/95647" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">Google Chrome</a></li>
2392
+ <li style="margin-bottom: 0.4rem;"><a href="https://support.mozilla.org/es/kb/habilitar-y-deshabilitar-cookies-sitios-web-rastrear-preferencias" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">Mozilla Firefox</a></li>
2393
+ <li style="margin-bottom: 0.4rem;"><a href="https://support.apple.com/es-es/guide/safari/sfri11471/mac" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">Apple Safari</a></li>
2394
+ <li style="margin-bottom: 0.4rem;"><a href="https://support.microsoft.com/es-es/microsoft-edge/eliminar-las-cookies-en-microsoft-edge-63947406-40ac-c3b8-57b9-2a946a29ae09" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">Microsoft Edge</a></li>
2395
+ <li style="margin-bottom: 0.4rem;"><a href="https://help.opera.com/en/latest/web-preferences/#cookies" target="_blank" rel="noopener noreferrer" style="color: inherit; text-decoration: underline;">Opera Browser</a></li>
2396
+ </ul>
2397
+ </section>
2398
+
2399
+ <section style="margin-bottom: 2rem;">
2400
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">5. Reconocimiento de Global Privacy Control (GPC)</h2>
2401
+ <p style="color: ${mutedCol};">
2402
+ Este sitio web est\xE1 adaptado al est\xE1ndar <strong>Global Privacy Control (GPC)</strong> y a la cabecera <code>Do Not Track (DNT)</code>. Si tu navegador emite una se\xF1al universal de no seguimiento, nuestro sistema desactivar\xE1 autom\xE1ticamente todas las cookies no necesarias sin requerir interacci\xF3n manual.
2403
+ </p>
2404
+ </section>
2405
+
2406
+ <section style="margin-bottom: 2rem;">
2407
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">6. Transferencias internacionales de datos</h2>
2408
+ <p style="color: ${mutedCol};">
2409
+ Determinadas cookies de terceros (tales como Google Analytics o Meta) pueden implicar la transferencia internacional de datos a servidores ubicados en Estados Unidos u otros pa\xEDses fuera del Espacio Econ\xF3mico Europeo (EEE). Dichas transferencias se encuentran amparadas bajo el Marco de Privacidad de Datos UE-EE.UU. (Data Privacy Framework) o Cl\xE1usulas Contractuales Tipo aprobadas por la Comisi\xF3n Europea.
2410
+ </p>
2411
+ </section>
2412
+ </article>
2413
+ `;
2414
+ return html;
2415
+ }
2416
+ /**
2417
+ * Render complete legal notice (Aviso Legal) conforming to LSSI-CE Art. 10.
2418
+ */
2419
+ static renderLegalNotice(config, options = {}) {
2420
+ var _a, _b, _c;
2421
+ const entity = config.legalEntity || {
2422
+ name: "[Raz\xF3n Social del Titular]",
2423
+ taxId: "[NIF / CIF]",
2424
+ address: "[Domicilio Social]",
2425
+ email: "[Email de Contacto]"
2426
+ };
2427
+ const law = ((_a = config.legalNotice) == null ? void 0 : _a.applicableLaw) || "Legislaci\xF3n espa\xF1ola (LSSI-CE, LOPDGDD) y Reglamento General de Protecci\xF3n de Datos (RGPD UE 2016/679)";
2428
+ const jurisdiction = ((_b = config.legalNotice) == null ? void 0 : _b.jurisdiction) || "Juzgados y Tribunales competentes conforme a la normativa de consumidores y usuarios";
2429
+ const lastUpdated = ((_c = config.legalNotice) == null ? void 0 : _c.lastUpdated) || config.policyVersion || (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2430
+ const isDark = options.theme === "dark";
2431
+ const textCol = isDark ? "#f8fafc" : "#0f172a";
2432
+ const mutedCol = isDark ? "#94a3b8" : "#475569";
2433
+ const borderCol = isDark ? "#334155" : "#e2e8f0";
2434
+ return `
2435
+ <article class="solvenza-legal-notice-document ${options.className || ""}" style="font-family: system-ui, -apple-system, sans-serif; color: ${textCol}; line-height: 1.7; max-width: 900px; margin: 0 auto; padding: 1rem 0;">
2436
+ <header style="margin-bottom: 2rem; border-bottom: 1px solid ${borderCol}; padding-bottom: 1.5rem;">
2437
+ <h1 style="font-size: 1.85rem; font-weight: 800; margin: 0 0 0.5rem 0; color: ${textCol};">Aviso Legal</h1>
2438
+ <p style="margin: 0; font-size: 0.9rem; color: ${mutedCol};">
2439
+ En cumplimiento del art\xEDculo 10 de la Ley 34/2002, de 11 de julio, de Servicios de la Sociedad de la Informaci\xF3n y de Comercio Electr\xF3nico (LSSI-CE).
2440
+ <br><em>\xDAltima actualizaci\xF3n: ${sanitizeHtml(lastUpdated)}</em>
2441
+ </p>
2442
+ </header>
2443
+
2444
+ <section style="margin-bottom: 2rem;">
2445
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">1. Datos identificativos del titular</h2>
2446
+ <table style="width: 100%; border-collapse: collapse; margin-top: 1rem; border: 1px solid ${borderCol};">
2447
+ <tbody>
2448
+ <tr style="border-bottom: 1px solid ${borderCol};">
2449
+ <td style="padding: 0.75rem 1rem; font-weight: 600; width: 30%;">Raz\xF3n Social:</td>
2450
+ <td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.name)}</td>
2451
+ </tr>
2452
+ ${entity.tradeName ? `<tr style="border-bottom: 1px solid ${borderCol};"><td style="padding: 0.75rem 1rem; font-weight: 600;">Nombre Comercial:</td><td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.tradeName)}</td></tr>` : ""}
2453
+ <tr style="border-bottom: 1px solid ${borderCol};">
2454
+ <td style="padding: 0.75rem 1rem; font-weight: 600;">NIF / CIF:</td>
2455
+ <td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.taxId || "-")}</td>
2456
+ </tr>
2457
+ <tr style="border-bottom: 1px solid ${borderCol};">
2458
+ <td style="padding: 0.75rem 1rem; font-weight: 600;">Domicilio Social:</td>
2459
+ <td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.address || "-")}</td>
2460
+ </tr>
2461
+ <tr style="border-bottom: 1px solid ${borderCol};">
2462
+ <td style="padding: 0.75rem 1rem; font-weight: 600;">Correo Electr\xF3nico:</td>
2463
+ <td style="padding: 0.75rem 1rem; color: ${mutedCol};"><a href="mailto:${sanitizeHtml(entity.email || "")}" style="color: #2563eb;">${sanitizeHtml(entity.email || "-")}</a></td>
2464
+ </tr>
2465
+ ${entity.phone ? `<tr style="border-bottom: 1px solid ${borderCol};"><td style="padding: 0.75rem 1rem; font-weight: 600;">Tel\xE9fono:</td><td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.phone)}</td></tr>` : ""}
2466
+ ${entity.registryData ? `<tr style="border-bottom: 1px solid ${borderCol};"><td style="padding: 0.75rem 1rem; font-weight: 600;">Datos Registrales:</td><td style="padding: 0.75rem 1rem; color: ${mutedCol};">${sanitizeHtml(entity.registryData)}</td></tr>` : ""}
2467
+ ${entity.dpoEmail ? `<tr><td style="padding: 0.75rem 1rem; font-weight: 600;">Delegado de Protecci\xF3n de Datos (DPO):</td><td style="padding: 0.75rem 1rem; color: ${mutedCol};"><a href="mailto:${sanitizeHtml(entity.dpoEmail)}" style="color: #2563eb;">${sanitizeHtml(entity.dpoEmail)}</a></td></tr>` : ""}
2468
+ </tbody>
2469
+ </table>
2470
+ </section>
2471
+
2472
+ <section style="margin-bottom: 2rem;">
2473
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">2. Condiciones generales de uso</h2>
2474
+ <p style="color: ${mutedCol};">
2475
+ El acceso y/o uso de este sitio web atribuye la condici\xF3n de usuario, que acepta, desde dicho acceso y/o uso, las presentes condiciones generales. El usuario se compromete a hacer un uso adecuado de los contenidos y servicios que el titular ofrece a trav\xE9s de su sitio web y a no emplearlos para incurrir en actividades il\xEDcitas o contrarias a la buena fe y al orden p\xFAblico.
2476
+ </p>
2477
+ </section>
2478
+
2479
+ <section style="margin-bottom: 2rem;">
2480
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">3. Propiedad intelectual e industrial</h2>
2481
+ <p style="color: ${mutedCol};">
2482
+ Todos los derechos de propiedad intelectual e industrial sobre el dise\xF1o, marcas, logotipos, textos, c\xF3digo fuente e ilustraciones de este sitio web corresponden al titular o a sus leg\xEDtimos licenciantes. Queda expresamente prohibida la reproducci\xF3n, distribuci\xF3n o comunicaci\xF3n p\xFAblica de la totalidad o parte de los contenidos sin autorizaci\xF3n previa y por escrito.
2483
+ </p>
2484
+ </section>
2485
+
2486
+ <section style="margin-bottom: 2rem;">
2487
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">4. Exclusi\xF3n de garant\xEDas y responsabilidad</h2>
2488
+ <p style="color: ${mutedCol};">
2489
+ El titular no se hace responsable, en ning\xFAn caso, de los da\xF1os y perjuicios de cualquier naturaleza que pudieran ocasionar errores u omisiones en los contenidos, falta de disponibilidad del portal o la transmisi\xF3n de virus o programas maliciosos, a pesar de haber adoptado todas las medidas tecnol\xF3gicas necesarias para evitarlo.
2490
+ </p>
2491
+ </section>
2492
+
2493
+ <section style="margin-bottom: 2rem;">
2494
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">5. Legislaci\xF3n aplicable y jurisdicci\xF3n</h2>
2495
+ <p style="color: ${mutedCol};">
2496
+ Las relaciones entre el titular y el usuario se regir\xE1n por la <strong>${sanitizeHtml(law)}</strong>. Para la resoluci\xF3n de cualquier controversia, las partes se someten a los <strong>${sanitizeHtml(jurisdiction)}</strong>, sin perjuicio de los fueros imperativos legales aplicables.
2497
+ </p>
2498
+ </section>
2499
+ </article>
2500
+ `;
2501
+ }
2502
+ /**
2503
+ * Render Privacy Policy document (Política de Privacidad RGPD).
2504
+ */
2505
+ static renderPrivacyPolicy(config, options = {}) {
2506
+ const entity = config.legalEntity || {
2507
+ name: "[Raz\xF3n Social del Responsable]",
2508
+ taxId: "[NIF / CIF]",
2509
+ address: "[Domicilio]",
2510
+ email: "privacidad@solvenza.es"
2511
+ };
2512
+ const isDark = options.theme === "dark";
2513
+ const textCol = isDark ? "#f8fafc" : "#0f172a";
2514
+ const mutedCol = isDark ? "#94a3b8" : "#475569";
2515
+ const borderCol = isDark ? "#334155" : "#e2e8f0";
2516
+ return `
2517
+ <article class="solvenza-privacy-policy-document ${options.className || ""}" style="font-family: system-ui, -apple-system, sans-serif; color: ${textCol}; line-height: 1.7; max-width: 900px; margin: 0 auto; padding: 1rem 0;">
2518
+ <header style="margin-bottom: 2rem; border-bottom: 1px solid ${borderCol}; padding-bottom: 1.5rem;">
2519
+ <h1 style="font-size: 1.85rem; font-weight: 800; margin: 0 0 0.5rem 0; color: ${textCol};">Pol\xEDtica de Privacidad y Protecci\xF3n de Datos</h1>
2520
+ <p style="margin: 0; font-size: 0.9rem; color: ${mutedCol};">
2521
+ Conforme al Reglamento General de Protecci\xF3n de Datos (RGPD UE 2016/679) y la Ley Org\xE1nica 3/2018 (LOPDGDD).
2522
+ </p>
2523
+ </header>
2524
+
2525
+ <section style="margin-bottom: 2rem;">
2526
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">1. Responsable del tratamiento</h2>
2527
+ <p style="color: ${mutedCol};">
2528
+ <strong>Identidad:</strong> ${sanitizeHtml(entity.name)}<br>
2529
+ <strong>NIF / CIF:</strong> ${sanitizeHtml(entity.taxId || "-")}<br>
2530
+ <strong>Direcci\xF3n:</strong> ${sanitizeHtml(entity.address || "-")}<br>
2531
+ <strong>Correo electr\xF3nico:</strong> <a href="mailto:${sanitizeHtml(entity.email || "")}" style="color: #2563eb;">${sanitizeHtml(entity.email || "-")}</a><br>
2532
+ ${entity.dpoEmail ? `<strong>Contacto DPO:</strong> <a href="mailto:${sanitizeHtml(entity.dpoEmail)}" style="color: #2563eb;">${sanitizeHtml(entity.dpoEmail)}</a>` : ""}
2533
+ </p>
2534
+ </section>
2535
+
2536
+ <section style="margin-bottom: 2rem;">
2537
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">2. Finalidad del tratamiento y base jur\xEDdica</h2>
2538
+ <p style="color: ${mutedCol};">
2539
+ Tratamos los datos facilitados por los usuarios con las finalidades de gestionar sus solicitudes, prestar los servicios contratados y, en su caso, analizar el rendimiento de la web y remitir comunicaciones comerciales sobre la base de su <strong>consentimiento expl\xEDcito</strong> (art. 6.1.a RGPD) o en la <strong>ejecuci\xF3n contractual</strong> (art. 6.1.b RGPD).
2540
+ </p>
2541
+ </section>
2542
+
2543
+ <section style="margin-bottom: 2rem;">
2544
+ <h2 style="font-size: 1.3rem; font-weight: 700; color: ${textCol};">3. Derechos del interesado</h2>
2545
+ <p style="color: ${mutedCol};">
2546
+ Cualquier persona tiene derecho a obtener confirmaci\xF3n sobre si estamos tratando datos personales que le conciernen. Los interesados tienen derecho a acceder a sus datos personales, solicitar la rectificaci\xF3n de los datos inexactos o, en su caso, solicitar su supresi\xF3n cuando los datos ya no sean necesarios para los fines que fueron recogidos.
2547
+ </p>
2548
+ <p style="color: ${mutedCol};">
2549
+ Puedes ejercer tus derechos de Acceso, Rectificaci\xF3n, Supresi\xF3n, Limitaci\xF3n, Portabilidad y Oposici\xF3n enviando un correo a <a href="mailto:${sanitizeHtml(entity.email || "")}" style="color: #2563eb;">${sanitizeHtml(entity.email || "")}</a>. Asimismo, puedes presentar una reclamaci\xF3n ante la Agencia Espa\xF1ola de Protecci\xF3n de Datos (<a href="https://www.aepd.es" target="_blank" rel="noopener noreferrer" style="color: #2563eb;">www.aepd.es</a>).
2550
+ </p>
2551
+ </section>
2552
+ </article>
2553
+ `;
2554
+ }
1673
2555
  };
1674
2556
 
2557
+ // src/i18n/engine.ts
2558
+ var BUILTIN_TRANSLATIONS = {
2559
+ es: {
2560
+ ui: {
2561
+ banner: {
2562
+ title: "Tu privacidad, bajo tu control",
2563
+ description: "Usamos tecnolog\xEDas necesarias para el funcionamiento del sitio. Con tu permiso, tambi\xE9n podemos utilizar anal\xEDtica y marketing.",
2564
+ accept: "Aceptar todas",
2565
+ reject: "Rechazar todas",
2566
+ configure: "Configurar",
2567
+ cookiesPolicy: "Pol\xEDtica de cookies",
2568
+ privacyPolicy: "Pol\xEDtica de privacidad",
2569
+ ariaLabel: "Gesti\xF3n de consentimiento de privacidad"
2570
+ },
2571
+ preferences: {
2572
+ title: "Preferencias de privacidad",
2573
+ subtitle: "Gestiona tus permisos de almacenamiento por finalidad (LSSI art. 22.2 & RGPD).",
2574
+ save: "Guardar selecci\xF3n",
2575
+ acceptAll: "Permitir todas",
2576
+ rejectAll: "Rechazar opcionales",
2577
+ closeLabel: "Cerrar ventana",
2578
+ requiredBadge: "Requerida",
2579
+ optionalBadge: "Opcional",
2580
+ servicesLabel: "Servicios incluidos",
2581
+ viewServices: "Ver servicios",
2582
+ hideServices: "Ocultar servicios",
2583
+ thirdParty: "Terceros"
2584
+ },
2585
+ floatingBadge: {
2586
+ label: "Cookies",
2587
+ ariaLabel: "Configuraci\xF3n y revocaci\xF3n de cookies",
2588
+ tooltip: "Configurar o declinar cookies"
2589
+ }
2590
+ },
2591
+ categories: {
2592
+ necessary: {
2593
+ label: "Cookies T\xE9cnicas y Necesarias",
2594
+ description: "Imprescindibles para que el sitio web funcione y no pueden ser desactivadas."
2595
+ },
2596
+ analytics: {
2597
+ label: "Medici\xF3n y Rendimiento",
2598
+ description: "Nos permiten analizar las visitas y fuentes de tr\xE1fico para optimizar el sitio."
2599
+ },
2600
+ marketing: {
2601
+ label: "Publicidad Personalizada",
2602
+ description: "Utilizadas para mostrar anuncios relevantes seg\xFAn tus intereses y navegaci\xF3n."
2603
+ },
2604
+ preferences: {
2605
+ label: "Preferencias y Personalizaci\xF3n",
2606
+ description: "Permiten recordar informaci\xF3n que cambia el aspecto o comportamiento del sitio."
2607
+ }
2608
+ }
2609
+ },
2610
+ en: {
2611
+ ui: {
2612
+ banner: {
2613
+ title: "Your privacy, under your control",
2614
+ description: "We use essential technologies for our website to function. With your consent, we may also use analytics and marketing technologies.",
2615
+ accept: "Accept all",
2616
+ reject: "Reject all",
2617
+ configure: "Customize",
2618
+ cookiesPolicy: "Cookie policy",
2619
+ privacyPolicy: "Privacy policy",
2620
+ ariaLabel: "Privacy consent management"
2621
+ },
2622
+ preferences: {
2623
+ title: "Privacy Preferences",
2624
+ subtitle: "Manage your storage permissions by purpose (GDPR & ePrivacy compliant).",
2625
+ save: "Save preferences",
2626
+ acceptAll: "Allow all",
2627
+ rejectAll: "Reject optional",
2628
+ closeLabel: "Close dialog",
2629
+ requiredBadge: "Required",
2630
+ optionalBadge: "Optional",
2631
+ servicesLabel: "Included services",
2632
+ viewServices: "View services",
2633
+ hideServices: "Hide services",
2634
+ thirdParty: "Third-party"
2635
+ },
2636
+ floatingBadge: {
2637
+ label: "Cookies",
2638
+ ariaLabel: "Cookie preferences and revocation",
2639
+ tooltip: "Customize or decline cookies"
2640
+ }
2641
+ },
2642
+ categories: {
2643
+ necessary: {
2644
+ label: "Strictly Necessary Cookies",
2645
+ description: "Essential for the website to function properly and cannot be deactivated."
2646
+ },
2647
+ analytics: {
2648
+ label: "Performance & Analytics",
2649
+ description: "Help us understand visitor behavior and traffic sources to optimize performance."
2650
+ },
2651
+ marketing: {
2652
+ label: "Targeted Advertising",
2653
+ description: "Used to deliver relevant ads and track campaign effectiveness across websites."
2654
+ },
2655
+ preferences: {
2656
+ label: "Preferences & Personalization",
2657
+ description: "Enable the website to remember user choices like language and layout settings."
2658
+ }
2659
+ }
2660
+ },
2661
+ ca: {
2662
+ ui: {
2663
+ banner: {
2664
+ title: "La teva privacitat, sota el teu control",
2665
+ description: "Utilitzem tecnologies necess\xE0ries per al funcionament del lloc. Amb el teu perm\xEDs, tamb\xE9 podem utilitzar anal\xEDtica i m\xE0rqueting.",
2666
+ accept: "Acceptar-les totes",
2667
+ reject: "Rebutjar-les totes",
2668
+ configure: "Configurar",
2669
+ cookiesPolicy: "Pol\xEDtica de cookies",
2670
+ privacyPolicy: "Pol\xEDtica de privacitat",
2671
+ ariaLabel: "Gesti\xF3 de consentiment de privacitat"
2672
+ },
2673
+ preferences: {
2674
+ title: "Prefer\xE8ncies de privacitat",
2675
+ subtitle: "Gestiona els teus permisos d'emmagatzematge per finalitat (LSSI art. 22.2 & RGPD).",
2676
+ save: "Desar selecci\xF3",
2677
+ acceptAll: "Permetre-les totes",
2678
+ rejectAll: "Rebutjar opcionals",
2679
+ closeLabel: "Tancar finestra",
2680
+ requiredBadge: "Requerida",
2681
+ optionalBadge: "Opcional",
2682
+ servicesLabel: "Serveis inclosos",
2683
+ viewServices: "Veure serveis",
2684
+ hideServices: "Amagar serveis",
2685
+ thirdParty: "Tercers"
2686
+ },
2687
+ floatingBadge: {
2688
+ label: "Cookies",
2689
+ ariaLabel: "Configuraci\xF3 i revocaci\xF3 de cookies",
2690
+ tooltip: "Configurar o declinar cookies"
2691
+ }
2692
+ },
2693
+ categories: {
2694
+ necessary: {
2695
+ label: "Cookies T\xE8cniques i Necess\xE0ries",
2696
+ description: "Imprescindibles perqu\xE8 el lloc web funcioni correctament i no es poden desactivar."
2697
+ },
2698
+ analytics: {
2699
+ label: "Mesurament i Rendiment",
2700
+ description: "Ens permeten analitzar les visites i fonts de tr\xE0nsit per optimitzar el lloc."
2701
+ },
2702
+ marketing: {
2703
+ label: "Publicidad Personalitzada",
2704
+ description: "Utilitzades per mostrar anuncis rellevants segons els teus interessos i navegaci\xF3."
2705
+ },
2706
+ preferences: {
2707
+ label: "Prefer\xE8ncies i Personalitzaci\xF3",
2708
+ description: "Permeten recordar informaci\xF3 que canvia l'aspecte o comportament del lloc."
2709
+ }
2710
+ }
2711
+ },
2712
+ eu: {
2713
+ ui: {
2714
+ banner: {
2715
+ title: "Zure pribatutasuna, zure kontrolpean",
2716
+ description: "Webguneak funtzionatzeko beharrezkoak diren teknologiak erabiltzen ditugu. Zure baimenarekin, analitika eta marketina ere erabil ditzakegu.",
2717
+ accept: "Onartu guztiak",
2718
+ reject: "Baztertu guztiak",
2719
+ configure: "Konfiguratu",
2720
+ cookiesPolicy: "Cookie politika",
2721
+ privacyPolicy: "Pribatutasun politika",
2722
+ ariaLabel: "Pribatutasun-baimenen kudeaketa"
2723
+ },
2724
+ preferences: {
2725
+ title: "Pribatutasun-hobespenak",
2726
+ subtitle: "Kudeatu zure biltegiratze-baimenak helburuaren arabera (RGPD).",
2727
+ save: "Gorde hautapena",
2728
+ acceptAll: "Onartu guztiak",
2729
+ rejectAll: "Baztertu aukerakoak",
2730
+ closeLabel: "Itxi leihoa",
2731
+ requiredBadge: "Beharrezkoa",
2732
+ optionalBadge: "Aukerakoa",
2733
+ servicesLabel: "Barne dauden zerbitzuak",
2734
+ viewServices: "Ikusi zerbitzuak",
2735
+ hideServices: "Ezkutatu zerbitzuak",
2736
+ thirdParty: "Hirugarrenak"
2737
+ },
2738
+ floatingBadge: {
2739
+ label: "Cookieak",
2740
+ ariaLabel: "Cookien ezarpenak eta ezeztapena",
2741
+ tooltip: "Konfiguratu edo baztertu cookieak"
2742
+ }
2743
+ }
2744
+ },
2745
+ gl: {
2746
+ ui: {
2747
+ banner: {
2748
+ title: "A t\xFAa privacidade, baixo o teu control",
2749
+ description: "Empregamos tecnolox\xEDas necesarias para o funcionamento do sitio. Co teu permiso, tam\xE9n podemos utilizar anal\xEDtica e m\xE1rketing.",
2750
+ accept: "Aceptar todas",
2751
+ reject: "Rexeitar todas",
2752
+ configure: "Configurar",
2753
+ cookiesPolicy: "Pol\xEDtica de cookies",
2754
+ privacyPolicy: "Pol\xEDtica de privacidade",
2755
+ ariaLabel: "Xesti\xF3n de consentimento de privacidade"
2756
+ },
2757
+ preferences: {
2758
+ title: "Preferencias de privacidade",
2759
+ subtitle: "Xestiona os teus permisos de almacenamento por finalidade (LSSI art. 22.2 & RGPD).",
2760
+ save: "Gardar selecci\xF3n",
2761
+ acceptAll: "Permitir todas",
2762
+ rejectAll: "Rexeitar opcionais",
2763
+ closeLabel: "Pechar xanela",
2764
+ requiredBadge: "Requirida",
2765
+ optionalBadge: "Opcional",
2766
+ servicesLabel: "Servizos inclu\xEDdos",
2767
+ viewServices: "Ver servizos",
2768
+ hideServices: "Ocultar servizos",
2769
+ thirdParty: "Terceiros"
2770
+ },
2771
+ floatingBadge: {
2772
+ label: "Cookies",
2773
+ ariaLabel: "Configuraci\xF3n e revocaci\xF3n de cookies",
2774
+ tooltip: "Configurar ou declinar cookies"
2775
+ }
2776
+ }
2777
+ }
2778
+ };
2779
+ var I18nEngine = class {
2780
+ constructor(defaultLocale = "es") {
2781
+ this.locale = "es";
2782
+ this.locale = defaultLocale;
2783
+ }
2784
+ setLocale(locale) {
2785
+ this.locale = locale.toLowerCase();
2786
+ }
2787
+ getLocale() {
2788
+ return this.locale;
2789
+ }
2790
+ detectBrowserLocale(supportedLocales) {
2791
+ if (typeof navigator !== "undefined" && navigator.language) {
2792
+ const detected = navigator.language.split("-")[0].toLowerCase();
2793
+ if (supportedLocales && supportedLocales.length > 0) {
2794
+ const normalized = supportedLocales.map((l) => l.toLowerCase());
2795
+ if (normalized.includes(detected)) {
2796
+ return detected;
2797
+ }
2798
+ return normalized[0];
2799
+ }
2800
+ return detected;
2801
+ }
2802
+ return "es";
2803
+ }
2804
+ isLocaleSupported(locale, config) {
2805
+ var _a, _b;
2806
+ const clean = locale.toLowerCase();
2807
+ if (((_a = config == null ? void 0 : config.locale) == null ? void 0 : _a.supported) && config.locale.supported.length > 0) {
2808
+ return config.locale.supported.map((s) => s.toLowerCase()).includes(clean);
2809
+ }
2810
+ if ((config == null ? void 0 : config.translations) && config.translations[clean]) {
2811
+ return true;
2812
+ }
2813
+ if (BUILTIN_TRANSLATIONS[clean]) {
2814
+ return true;
2815
+ }
2816
+ return clean === (((_b = config == null ? void 0 : config.locale) == null ? void 0 : _b.default) || "es").toLowerCase();
2817
+ }
2818
+ detectParentLocale(config) {
2819
+ var _a, _b, _c, _d, _e, _f;
2820
+ const supported = (_a = config == null ? void 0 : config.locale) == null ? void 0 : _a.supported;
2821
+ const defaultLocale = (((_b = config == null ? void 0 : config.locale) == null ? void 0 : _b.default) || "es").toLowerCase();
2822
+ if (((_c = config == null ? void 0 : config.locale) == null ? void 0 : _c.syncHtmlLang) !== false && typeof document !== "undefined" && document.documentElement) {
2823
+ const htmlLang = document.documentElement.lang;
2824
+ if (htmlLang && htmlLang.trim().length > 0) {
2825
+ const clean = htmlLang.split("-")[0].toLowerCase().trim();
2826
+ if (this.isLocaleSupported(clean, config)) {
2827
+ return clean;
2828
+ }
2829
+ }
2830
+ }
2831
+ if (((_d = config == null ? void 0 : config.locale) == null ? void 0 : _d.syncUrl) !== false && typeof window !== "undefined" && window.location) {
2832
+ const pathSegments = window.location.pathname.split("/").filter(Boolean);
2833
+ if (pathSegments.length > 0) {
2834
+ const potentialLang = pathSegments[0].toLowerCase();
2835
+ if (this.isLocaleSupported(potentialLang, config)) {
2836
+ return potentialLang;
2837
+ }
2838
+ }
2839
+ try {
2840
+ const urlParams = new URLSearchParams(window.location.search);
2841
+ const queryLang = (_e = urlParams.get("lang") || urlParams.get("locale")) == null ? void 0 : _e.toLowerCase();
2842
+ if (queryLang && this.isLocaleSupported(queryLang, config)) {
2843
+ return queryLang;
2844
+ }
2845
+ } catch (e) {
2846
+ }
2847
+ }
2848
+ if ((_f = config == null ? void 0 : config.locale) == null ? void 0 : _f.autoDetect) {
2849
+ return this.detectBrowserLocale(supported);
2850
+ }
2851
+ return defaultLocale;
2852
+ }
2853
+ startParentSync(config, onLocaleChange) {
2854
+ var _a, _b;
2855
+ const cleanups = [];
2856
+ if (((_a = config.locale) == null ? void 0 : _a.syncHtmlLang) !== false && typeof document !== "undefined" && typeof MutationObserver !== "undefined" && document.documentElement) {
2857
+ const observer = new MutationObserver((mutations) => {
2858
+ for (const mutation of mutations) {
2859
+ if (mutation.type === "attributes" && mutation.attributeName === "lang") {
2860
+ const rawLang = document.documentElement.lang;
2861
+ if (rawLang && rawLang.trim().length > 0) {
2862
+ const clean = rawLang.split("-")[0].toLowerCase().trim();
2863
+ if (this.isLocaleSupported(clean, config) && clean !== this.locale) {
2864
+ onLocaleChange(clean);
2865
+ }
2866
+ }
2867
+ }
2868
+ }
2869
+ });
2870
+ observer.observe(document.documentElement, {
2871
+ attributes: true,
2872
+ attributeFilter: ["lang"]
2873
+ });
2874
+ cleanups.push(() => observer.disconnect());
2875
+ }
2876
+ if (((_b = config.locale) == null ? void 0 : _b.autoDetect) && typeof window !== "undefined") {
2877
+ const handleLangChange = () => {
2878
+ var _a2;
2879
+ const detected = this.detectBrowserLocale((_a2 = config.locale) == null ? void 0 : _a2.supported);
2880
+ if (detected !== this.locale) {
2881
+ onLocaleChange(detected);
2882
+ }
2883
+ };
2884
+ window.addEventListener("languagechange", handleLangChange);
2885
+ cleanups.push(() => window.removeEventListener("languagechange", handleLangChange));
2886
+ }
2887
+ return () => {
2888
+ cleanups.forEach((cleanup) => cleanup());
2889
+ };
2890
+ }
2891
+ resolveConfig(config, targetLocale) {
2892
+ var _a, _b;
2893
+ const baseLocale = (((_a = config.locale) == null ? void 0 : _a.default) || "es").toLowerCase();
2894
+ const locale = (targetLocale || this.locale || baseLocale).toLowerCase();
2895
+ const resolved = JSON.parse(JSON.stringify(config));
2896
+ const builtin = BUILTIN_TRANSLATIONS[locale];
2897
+ if (builtin) {
2898
+ const isBaseLocale = locale === baseLocale;
2899
+ this.applyTranslation(resolved, builtin, !isBaseLocale);
2900
+ }
2901
+ const custom = (_b = config.translations) == null ? void 0 : _b[locale];
2902
+ if (custom) {
2903
+ this.applyTranslation(resolved, custom, true);
2904
+ }
2905
+ return resolved;
2906
+ }
2907
+ applyTranslation(target, translation, overwriteExisting) {
2908
+ if (translation.policy) {
2909
+ if (!target.policy) target.policy = {};
2910
+ if (translation.policy.privacyUrl && (overwriteExisting || !target.policy.privacyUrl)) {
2911
+ target.policy.privacyUrl = translation.policy.privacyUrl;
2912
+ }
2913
+ if (translation.policy.cookiesUrl && (overwriteExisting || !target.policy.cookiesUrl)) {
2914
+ target.policy.cookiesUrl = translation.policy.cookiesUrl;
2915
+ }
2916
+ }
2917
+ if (translation.ui) {
2918
+ if (!target.ui) target.ui = {};
2919
+ if (translation.ui.banner) {
2920
+ if (!target.ui.banner) target.ui.banner = {};
2921
+ for (const [key, value] of Object.entries(this.filterDefined(translation.ui.banner))) {
2922
+ if (overwriteExisting || !target.ui.banner[key]) {
2923
+ target.ui.banner[key] = value;
2924
+ }
2925
+ }
2926
+ }
2927
+ if (translation.ui.preferences) {
2928
+ if (!target.ui.preferences) target.ui.preferences = {};
2929
+ for (const [key, value] of Object.entries(this.filterDefined(translation.ui.preferences))) {
2930
+ if (overwriteExisting || !target.ui.preferences[key]) {
2931
+ target.ui.preferences[key] = value;
2932
+ }
2933
+ }
2934
+ }
2935
+ if (translation.ui.floatingBadge) {
2936
+ if (typeof target.ui.floatingBadge === "object" && target.ui.floatingBadge !== null) {
2937
+ for (const [key, value] of Object.entries(this.filterDefined(translation.ui.floatingBadge))) {
2938
+ if (overwriteExisting || !target.ui.floatingBadge[key]) {
2939
+ target.ui.floatingBadge[key] = value;
2940
+ }
2941
+ }
2942
+ } else if (target.ui.floatingBadge === true) {
2943
+ target.ui.floatingBadge = __spreadValues({
2944
+ enabled: true,
2945
+ position: "bottom-left",
2946
+ icon: "cookie"
2947
+ }, this.filterDefined(translation.ui.floatingBadge));
2948
+ }
2949
+ }
2950
+ }
2951
+ if (translation.categories && target.categories) {
2952
+ for (const [catId, catTrans] of Object.entries(translation.categories)) {
2953
+ if (target.categories[catId]) {
2954
+ if (catTrans.label && (overwriteExisting || !target.categories[catId].label)) {
2955
+ target.categories[catId].label = catTrans.label;
2956
+ }
2957
+ if (catTrans.description && (overwriteExisting || !target.categories[catId].description)) {
2958
+ target.categories[catId].description = catTrans.description;
2959
+ }
2960
+ }
2961
+ }
2962
+ }
2963
+ if (translation.services && target.services) {
2964
+ for (const [srvId, srvTrans] of Object.entries(translation.services)) {
2965
+ if (target.services[srvId]) {
2966
+ if (srvTrans.label && (overwriteExisting || !target.services[srvId].label)) {
2967
+ target.services[srvId].label = srvTrans.label;
2968
+ }
2969
+ if (srvTrans.provider && (overwriteExisting || !target.services[srvId].provider)) {
2970
+ target.services[srvId].provider = srvTrans.provider;
2971
+ }
2972
+ }
2973
+ }
2974
+ }
2975
+ }
2976
+ filterDefined(obj) {
2977
+ const result = {};
2978
+ for (const [key, value] of Object.entries(obj)) {
2979
+ if (value !== void 0 && value !== null) {
2980
+ result[key] = value;
2981
+ }
2982
+ }
2983
+ return result;
2984
+ }
2985
+ };
2986
+
2987
+ // src/presets/presets-catalog.ts
2988
+ var SERVICE_PRESETS = {
2989
+ // --- ANALYTICS ---
2990
+ ga4: {
2991
+ id: "ga4",
2992
+ category: "analytics",
2993
+ label: "Google Analytics 4",
2994
+ provider: "Google LLC",
2995
+ description: "Medici\xF3n de visitas, p\xE1ginas vistas, eventos y comportamiento de navegaci\xF3n del usuario.",
2996
+ policyUrl: "https://policies.google.com/privacy",
2997
+ cookies: [
2998
+ { name: "_ga", duration: "2 a\xF1os", purpose: "Distinguir a los usuarios \xFAnicos en el sitio web." },
2999
+ { name: "_ga_*", duration: "2 a\xF1os", purpose: "Mantener el estado de la sesi\xF3n de Google Analytics 4." },
3000
+ { name: "_gid", duration: "24 horas", purpose: "Distinguir a los usuarios durante un \xFAnico d\xEDa." },
3001
+ { name: "_gat*", duration: "1 minuto", purpose: "Limitar el porcentaje de solicitudes a Google Analytics." }
3002
+ ],
3003
+ storageKeys: ["_ga_*", "google_analytics_*"],
3004
+ localStorage: ["_ga_*", "google_analytics_*"]
3005
+ },
3006
+ google_analytics: {
3007
+ id: "google_analytics",
3008
+ category: "analytics",
3009
+ label: "Google Analytics (Universal / GA4)",
3010
+ provider: "Google LLC",
3011
+ description: "Medici\xF3n de visitas y estad\xEDsticas an\xF3nimas de tr\xE1fico web.",
3012
+ policyUrl: "https://policies.google.com/privacy",
3013
+ cookies: [
3014
+ { name: "_ga", duration: "2 a\xF1os", purpose: "Distinguir usuarios." },
3015
+ { name: "_ga_*", duration: "2 a\xF1os", purpose: "Estado de la sesi\xF3n GA4." },
3016
+ { name: "_gid", duration: "24 horas", purpose: "Identificador diario." }
3017
+ ],
3018
+ storageKeys: ["_ga_*"]
3019
+ },
3020
+ posthog: {
3021
+ id: "posthog",
3022
+ category: "analytics",
3023
+ label: "PostHog Analytics",
3024
+ provider: "PostHog, Inc.",
3025
+ description: "Anal\xEDtica de producto, registro de sesiones, mapas de ruta y embudos de interacci\xF3n.",
3026
+ policyUrl: "https://posthog.com/privacy",
3027
+ cookies: [
3028
+ { name: "ph_*_posthog", duration: "1 a\xF1o", purpose: "Persistencia de usuario e identificaci\xF3n de sesiones en PostHog." }
3029
+ ],
3030
+ storageKeys: ["ph_*_posthog"],
3031
+ localStorage: ["ph_*_posthog"],
3032
+ sessionStorage: ["ph_*_posthog"]
3033
+ },
3034
+ hotjar: {
3035
+ id: "hotjar",
3036
+ category: "analytics",
3037
+ label: "Hotjar Heatmaps & Feedback",
3038
+ provider: "Hotjar Ltd.",
3039
+ description: "Mapas de calor de clics, desplazamientos, grabaciones de comportamiento y encuestas.",
3040
+ policyUrl: "https://www.hotjar.com/legal/policies/privacy/",
3041
+ cookies: [
3042
+ { name: "_hjSession*", duration: "30 minutos", purpose: "Mantener los datos de la sesi\xF3n actual del usuario en Hotjar." },
3043
+ { name: "_hjSessionUser*", duration: "1 a\xF1o", purpose: "Mantener el ID de usuario an\xF3nimo en el navegador." },
3044
+ { name: "_hjIncludedIn*", duration: "Sesi\xF3n", purpose: "Determinar si el usuario est\xE1 incluido en el muestreo de datos." },
3045
+ { name: "_hjAbsoluteCanvasUrl", duration: "Sesi\xF3n", purpose: "Almacenar la URL can\xF3nica de visualizaci\xF3n." },
3046
+ { name: "_hjTLDTest", duration: "Sesi\xF3n", purpose: "Verificar el nivel de dominio para cookies." }
3047
+ ],
3048
+ storageKeys: ["_hj*", "hj*"],
3049
+ localStorage: ["_hj*"],
3050
+ sessionStorage: ["_hj*"]
3051
+ },
3052
+ clarity: {
3053
+ id: "clarity",
3054
+ category: "analytics",
3055
+ label: "Microsoft Clarity",
3056
+ provider: "Microsoft Corporation",
3057
+ description: "Grabaci\xF3n de navegaci\xF3n y mapas t\xE9rmicos de interacci\xF3n sin datos personales.",
3058
+ policyUrl: "https://privacy.microsoft.com/privacystatement",
3059
+ cookies: [
3060
+ { name: "_clck", duration: "1 a\xF1o", purpose: "Persistir el ID de usuario Clarity y sus preferencias de p\xE1gina." },
3061
+ { name: "_clsk", duration: "24 horas", purpose: "Conectar m\xFAltiples visitas de p\xE1gina en una \xFAnica sesi\xF3n." },
3062
+ { name: "CLID", duration: "1 a\xF1o", purpose: "Identificador de navegador para Clarity." },
3063
+ { name: "MUID", duration: "1 a\xF1o", purpose: "Identificador de usuario \xFAnico de Microsoft." }
3064
+ ],
3065
+ storageKeys: ["_cl*"],
3066
+ localStorage: ["_cl*"]
3067
+ },
3068
+ matomo: {
3069
+ id: "matomo",
3070
+ category: "analytics",
3071
+ label: "Matomo Analytics",
3072
+ provider: "InnoCraft Ltd.",
3073
+ description: "Plataforma de anal\xEDtica web local-first y centrada en la privacidad.",
3074
+ policyUrl: "https://matomo.org/privacy-policy/",
3075
+ cookies: [
3076
+ { name: "_pk_id*", duration: "13 meses", purpose: "Almacenar datos de usuario \xFAnicos como el ID de visitante." },
3077
+ { name: "_pk_ses*", duration: "30 minutos", purpose: "Almacenar datos temporales de la sesi\xF3n de navegaci\xF3n." },
3078
+ { name: "_pk_ref*", duration: "6 meses", purpose: "Almacenar la informaci\xF3n de atribuci\xF3n de procedencia." }
3079
+ ],
3080
+ storageKeys: ["_pk_*"],
3081
+ localStorage: ["_pk_*"]
3082
+ },
3083
+ plausible: {
3084
+ id: "plausible",
3085
+ category: "analytics",
3086
+ label: "Plausible Analytics",
3087
+ provider: "Plausible Insights O\xDC",
3088
+ description: "Anal\xEDtica web ligera y respetuosa con la privacidad, 100% libre de cookies.",
3089
+ policyUrl: "https://plausible.io/data-policy",
3090
+ cookies: [],
3091
+ storageKeys: []
3092
+ },
3093
+ // --- MARKETING & ADVERTISING ---
3094
+ meta_pixel: {
3095
+ id: "meta_pixel",
3096
+ category: "marketing",
3097
+ label: "Meta Pixel (Facebook)",
3098
+ provider: "Meta Platforms Ireland Ltd.",
3099
+ description: "Medici\xF3n de conversiones, personalizaci\xF3n y optimizaci\xF3n de campa\xF1as publicitarias en Facebook e Instagram.",
3100
+ policyUrl: "https://www.facebook.com/privacy/policy",
3101
+ cookies: [
3102
+ { name: "_fbp", duration: "90 d\xEDas", purpose: "Almacenar y rastrear visitas en los sitios web para segmentaci\xF3n publicitaria." },
3103
+ { name: "_fbc", duration: "90 d\xEDas", purpose: "Guardar el \xFAltimo clic en un anuncio de Facebook (par\xE1metro fbclid)." },
3104
+ { name: "fr", duration: "90 d\xEDas", purpose: "Cookie de publicidad comportamental de Facebook." },
3105
+ { name: "tr", duration: "Sesi\xF3n", purpose: "P\xEDxel de seguimiento de eventos en tiempo real." },
3106
+ { name: "datr", duration: "2 a\xF1os", purpose: "Identificaci\xF3n del navegador web para seguridad y anal\xEDtica publicitaria." }
3107
+ ],
3108
+ storageKeys: ["_fbp*"],
3109
+ localStorage: ["_fbp*"]
3110
+ },
3111
+ facebook_pixel: {
3112
+ id: "facebook_pixel",
3113
+ category: "marketing",
3114
+ label: "Meta Pixel (Facebook)",
3115
+ provider: "Meta Platforms Ireland Ltd.",
3116
+ description: "Medici\xF3n de conversiones y retargeting en Meta/Facebook.",
3117
+ policyUrl: "https://www.facebook.com/privacy/policy",
3118
+ cookies: [
3119
+ { name: "_fbp", duration: "90 d\xEDas", purpose: "Rastreo de conversiones y atribuci\xF3n de campa\xF1as." },
3120
+ { name: "_fbc", duration: "90 d\xEDas", purpose: "Identificador de clic de anuncio." }
3121
+ ],
3122
+ storageKeys: ["_fbp*"]
3123
+ },
3124
+ google_ads: {
3125
+ id: "google_ads",
3126
+ category: "marketing",
3127
+ label: "Google Ads & Remarketing",
3128
+ provider: "Google LLC",
3129
+ description: "Medici\xF3n de conversiones publicitarias y campa\xF1as de retargeting de Google Ads.",
3130
+ policyUrl: "https://policies.google.com/technologies/ads",
3131
+ cookies: [
3132
+ { name: "_gcl_au", duration: "90 d\xEDas", purpose: "Medici\xF3n de conversiones publicitarias de Google AdSense y Ads." },
3133
+ { name: "_gcl_aw", duration: "90 d\xEDas", purpose: "Conversiones procedentes de clics en anuncios de Google Ads." },
3134
+ { name: "IDE", duration: "1 a\xF1o", purpose: "Publicidad dirigida y medici\xF3n de rendimiento de DoubleClick." },
3135
+ { name: "DSID", duration: "2 semanas", purpose: "Identificar usuario conectado en sitios web ajenos a Google." },
3136
+ { name: "RUL", duration: "1 a\xF1o", purpose: "Audiencias de remarketing de Google Ads." }
3137
+ ],
3138
+ storageKeys: ["_gcl_*"]
3139
+ },
3140
+ tiktok_pixel: {
3141
+ id: "tiktok_pixel",
3142
+ category: "marketing",
3143
+ label: "TikTok Pixel",
3144
+ provider: "TikTok Technology Limited",
3145
+ description: "Medici\xF3n de rendimiento y segmentaci\xF3n de campa\xF1as publicitarias en TikTok.",
3146
+ policyUrl: "https://www.tiktok.com/legal/privacy-policy-eea",
3147
+ cookies: [
3148
+ { name: "_tt_enable_cookie", duration: "13 meses", purpose: "Habilitar la medici\xF3n de conversiones del p\xEDxel de TikTok." },
3149
+ { name: "_ttp", duration: "13 meses", purpose: "Medir y mejorar el rendimiento de las campa\xF1as publicitarias en TikTok." }
3150
+ ],
3151
+ storageKeys: ["tt_*", "_ttp*"]
3152
+ },
3153
+ linkedin_insight: {
3154
+ id: "linkedin_insight",
3155
+ category: "marketing",
3156
+ label: "LinkedIn Insight Tag",
3157
+ provider: "LinkedIn Ireland Unlimited Company",
3158
+ description: "Informes de conversiones y atribuci\xF3n de campa\xF1as profesionales B2B en LinkedIn.",
3159
+ policyUrl: "https://www.linkedin.com/legal/privacy-policy",
3160
+ cookies: [
3161
+ { name: "li_sugr", duration: "90 d\xEDas", purpose: "Identificador probabil\xEDstico de navegador en LinkedIn." },
3162
+ { name: "bcookie", duration: "1 a\xF1o", purpose: "Identificador de sesi\xF3n de navegador para servicios de LinkedIn." },
3163
+ { name: "lidc", duration: "24 horas", purpose: "Enrutamiento de centros de datos para LinkedIn." },
3164
+ { name: "UserMatchHistory", duration: "30 d\xEDas", purpose: "Sincronizaci\xF3n de identificadores de publicidad de LinkedIn." }
3165
+ ]
3166
+ },
3167
+ twitter_pixel: {
3168
+ id: "twitter_pixel",
3169
+ category: "marketing",
3170
+ label: "X / Twitter Ads Pixel",
3171
+ provider: "X Corp.",
3172
+ description: "Medici\xF3n de conversiones e interacci\xF3n con campa\xF1as publicitarias en X (Twitter).",
3173
+ policyUrl: "https://twitter.com/privacy",
3174
+ cookies: [
3175
+ { name: "personalization_id", duration: "2 a\xF1os", purpose: "Personalizaci\xF3n de publicidad y medici\xF3n de eventos en X." },
3176
+ { name: "muc_ads", duration: "2 a\xF1os", purpose: "Seguimiento de conversiones publicitarias en X." }
3177
+ ]
3178
+ },
3179
+ hubspot: {
3180
+ id: "hubspot",
3181
+ category: "marketing",
3182
+ label: "HubSpot CRM & Tracking",
3183
+ provider: "HubSpot, Inc.",
3184
+ description: "Anal\xEDtica de leads, formularios inteligentes y seguimiento de clientes potenciales.",
3185
+ policyUrl: "https://legal.hubspot.com/privacy-policy",
3186
+ cookies: [
3187
+ { name: "__hstc", duration: "6 meses", purpose: "Rastreo de visitantes \xFAnicos, sesiones y marcas de tiempo." },
3188
+ { name: "hubspotutk", duration: "6 meses", purpose: "Rastreo de identidad de visitante pasado a formularios." },
3189
+ { name: "__hssc", duration: "30 minutos", purpose: "Rastreo de sesiones activas en HubSpot." },
3190
+ { name: "__hssrc", duration: "Sesi\xF3n", purpose: "Determinar si el usuario ha reiniciado su navegador." }
3191
+ ],
3192
+ storageKeys: ["__hs*", "hubspot*"]
3193
+ },
3194
+ // --- EMBEDDED MEDIA ---
3195
+ youtube: {
3196
+ id: "youtube",
3197
+ category: "marketing",
3198
+ label: "YouTube Video Player",
3199
+ provider: "Google LLC",
3200
+ description: "Reproducci\xF3n de contenidos de v\xEDdeo integrados y almacenamiento de preferencias del reproductor.",
3201
+ policyUrl: "https://policies.google.com/privacy",
3202
+ cookies: [
3203
+ { name: "VISITOR_INFO1_LIVE", duration: "6 meses", purpose: "Estimar el ancho de banda del usuario en p\xE1ginas con v\xEDdeos de YouTube." },
3204
+ { name: "YSC", duration: "Sesi\xF3n", purpose: "Registrar estad\xEDsticas de visualizaciones de v\xEDdeo de YouTube." },
3205
+ { name: "PREF", duration: "8 meses", purpose: "Almacenar preferencias de configuraci\xF3n del reproductor." },
3206
+ { name: "GPS", duration: "30 minutos", purpose: "Rastrear ubicaci\xF3n en dispositivos m\xF3viles." }
3207
+ ],
3208
+ storageKeys: ["yt-*", "yt-remote-*"],
3209
+ localStorage: ["yt-*", "yt-remote-*"]
3210
+ },
3211
+ vimeo: {
3212
+ id: "vimeo",
3213
+ category: "marketing",
3214
+ label: "Vimeo Video Player",
3215
+ provider: "Vimeo, Inc.",
3216
+ description: "Reproducci\xF3n de v\xEDdeos interactivos alojados en Vimeo y estad\xEDsticas de visualizaci\xF3n.",
3217
+ policyUrl: "https://vimeo.com/privacy",
3218
+ cookies: [
3219
+ { name: "vuid", duration: "2 a\xF1os", purpose: "Almacenar el historial de reproducciones de v\xEDdeo del usuario en Vimeo." },
3220
+ { name: "player", duration: "1 a\xF1o", purpose: "Guardar las preferencias de volumen y resoluci\xF3n del reproductor." }
3221
+ ],
3222
+ storageKeys: ["vimeo*"]
3223
+ },
3224
+ spotify: {
3225
+ id: "spotify",
3226
+ category: "marketing",
3227
+ label: "Spotify Player",
3228
+ provider: "Spotify AB",
3229
+ description: "Reproducci\xF3n embebida de canciones, podcasts y listas de reproducci\xF3n de Spotify.",
3230
+ policyUrl: "https://www.spotify.com/legal/privacy-policy/",
3231
+ cookies: [
3232
+ { name: "sp_t", duration: "1 a\xF1o", purpose: "Identificador de usuario \xFAnico de Spotify para contenido embebido." },
3233
+ { name: "sp_m", duration: "1 a\xF1o", purpose: "Preferencias de reproducci\xF3n y cookies de sesi\xF3n." }
3234
+ ]
3235
+ },
3236
+ // --- CHAT & CUSTOMER SUPPORT ---
3237
+ intercom: {
3238
+ id: "intercom",
3239
+ category: "marketing",
3240
+ label: "Intercom Messenger",
3241
+ provider: "Intercom R&D Unlimited Company",
3242
+ description: "Widget de mensajer\xEDa, soporte al cliente en tiempo real y asistencia guiada.",
3243
+ policyUrl: "https://www.intercom.com/legal/privacy",
3244
+ cookies: [
3245
+ { name: "intercom-id-*", duration: "9 meses", purpose: "Identificador an\xF3nimo de visitante para conversaciones en Intercom." },
3246
+ { name: "intercom-session-*", duration: "7 d\xEDas", purpose: "Persistir la sesi\xF3n de chat activa." },
3247
+ { name: "intercom-device-id-*", duration: "9 meses", purpose: "Identificador del dispositivo del visitante." }
3248
+ ],
3249
+ storageKeys: ["intercom*"]
3250
+ },
3251
+ crisp: {
3252
+ id: "crisp",
3253
+ category: "marketing",
3254
+ label: "Crisp Live Chat",
3255
+ provider: "Crisp IM SARL",
3256
+ description: "Chat en vivo de soporte y mensajer\xEDa multicanal para visitantes.",
3257
+ policyUrl: "https://crisp.chat/privacy",
3258
+ cookies: [
3259
+ { name: "crisp-client/*", duration: "6 meses", purpose: "Identificador de sesi\xF3n de chat en Crisp." }
3260
+ ],
3261
+ storageKeys: ["crisp-client*"]
3262
+ },
3263
+ // --- NECESSARY / TECHNICAL / SECURITY ---
3264
+ gtm: {
3265
+ id: "gtm",
3266
+ category: "necessary",
3267
+ label: "Google Tag Manager",
3268
+ provider: "Google LLC",
3269
+ description: "Contenedor t\xE9cnico para la inyecci\xF3n y gesti\xF3n centralizada de scripts del sitio web.",
3270
+ policyUrl: "https://policies.google.com/privacy",
3271
+ cookies: [
3272
+ { name: "_gtm_*", duration: "Sesi\xF3n", purpose: "Depuraci\xF3n t\xE9cnica de contenedores de Google Tag Manager." }
3273
+ ]
3274
+ },
3275
+ google_recaptcha: {
3276
+ id: "google_recaptcha",
3277
+ category: "necessary",
3278
+ label: "Google reCAPTCHA",
3279
+ provider: "Google LLC",
3280
+ description: "Protecci\xF3n contra bots automatizados, ataques de fuerza bruta y spam en formularios.",
3281
+ policyUrl: "https://policies.google.com/privacy",
3282
+ cookies: [
3283
+ { name: "_GRECAPTCHA", duration: "6 meses", purpose: "Evaluaci\xF3n de riesgo de bots para protecci\xF3n de formularios." },
3284
+ { name: "rc::a", duration: "Persistente", purpose: "Distinguir entre humanos y bots automatizados." },
3285
+ { name: "rc::b", duration: "Sesi\xF3n", purpose: "Distinguir entre humanos y bots automatizados." },
3286
+ { name: "rc::c", duration: "Sesi\xF3n", purpose: "Distinguir entre humanos y bots automatizados." }
3287
+ ]
3288
+ },
3289
+ cloudflare: {
3290
+ id: "cloudflare",
3291
+ category: "necessary",
3292
+ label: "Cloudflare Security & Turnstile",
3293
+ provider: "Cloudflare, Inc.",
3294
+ description: "Mitigaci\xF3n de ataques DDoS, balanceo de carga CDN y validaci\xF3n de seguridad Turnstile.",
3295
+ policyUrl: "https://www.cloudflare.com/privacypolicy/",
3296
+ cookies: [
3297
+ { name: "__cf_bm", duration: "30 minutos", purpose: "Gesti\xF3n de bots y mitigaci\xF3n de tr\xE1fico malicioso en Cloudflare." },
3298
+ { name: "cf_clearance", duration: "1 a\xF1o", purpose: "Autorizaci\xF3n de paso tras superar desaf\xEDo de seguridad Turnstile/Cloudflare." }
3299
+ ]
3300
+ },
3301
+ stripe: {
3302
+ id: "stripe",
3303
+ category: "necessary",
3304
+ label: "Stripe Payments & Fraud Prevention",
3305
+ provider: "Stripe, Inc.",
3306
+ description: "Procesamiento seguro de transacciones bancarias, tarjetas y prevenci\xF3n de fraude financiero.",
3307
+ policyUrl: "https://stripe.com/privacy",
3308
+ cookies: [
3309
+ { name: "__stripe_mid", duration: "1 a\xF1o", purpose: "Prevenci\xF3n de fraude y autenticaci\xF3n de pagos en Stripe." },
3310
+ { name: "__stripe_sid", duration: "30 minutos", purpose: "Identificador de sesi\xF3n de transacci\xF3n en Stripe." },
3311
+ { name: "m", duration: "2 a\xF1os", purpose: "Detecci\xF3n de fraude financiero en la pasarela de pagos Stripe." }
3312
+ ],
3313
+ storageKeys: ["__stripe_*"],
3314
+ localStorage: ["__stripe_*"]
3315
+ },
3316
+ paypal: {
3317
+ id: "paypal",
3318
+ category: "necessary",
3319
+ label: "PayPal Checkout",
3320
+ provider: "PayPal (Europe) S.\xE0 r.l. et Cie, S.C.A.",
3321
+ description: "Procesamiento de pagos y pasarela de cobro segura de PayPal.",
3322
+ policyUrl: "https://www.paypal.com/webapps/mpp/ua/privacy-full",
3323
+ cookies: [
3324
+ { name: "ts", duration: "3 a\xF1os", purpose: "Gesti\xF3n segura de pagos y prevenci\xF3n de fraude en PayPal." },
3325
+ { name: "ts_c", duration: "3 a\xF1os", purpose: "Autenticaci\xF3n segura de usuario en la pasarela PayPal." }
3326
+ ]
3327
+ }
3328
+ };
3329
+
3330
+ // src/presets/index.ts
3331
+ function hasPreset(presetId) {
3332
+ return Object.prototype.hasOwnProperty.call(SERVICE_PRESETS, presetId);
3333
+ }
3334
+ function getPreset(presetId, overrides) {
3335
+ const preset = SERVICE_PRESETS[presetId];
3336
+ if (!preset) {
3337
+ throw new Error(
3338
+ `[ConsentSDK] Unknown service preset: '${presetId}'. Available presets: ${Object.keys(
3339
+ SERVICE_PRESETS
3340
+ ).join(", ")}`
3341
+ );
3342
+ }
3343
+ const base = {
3344
+ category: preset.category,
3345
+ label: preset.label,
3346
+ provider: preset.provider,
3347
+ policyUrl: preset.policyUrl,
3348
+ description: preset.description,
3349
+ cookies: preset.cookies ? [...preset.cookies] : void 0,
3350
+ storageKeys: preset.storageKeys ? [...preset.storageKeys] : void 0,
3351
+ localStorage: preset.localStorage ? [...preset.localStorage] : void 0,
3352
+ sessionStorage: preset.sessionStorage ? [...preset.sessionStorage] : void 0
3353
+ };
3354
+ if (!overrides) {
3355
+ return base;
3356
+ }
3357
+ return __spreadProps(__spreadValues(__spreadValues({}, base), overrides), {
3358
+ cookies: overrides.cookies || base.cookies,
3359
+ storageKeys: overrides.storageKeys || base.storageKeys,
3360
+ localStorage: overrides.localStorage || base.localStorage,
3361
+ sessionStorage: overrides.sessionStorage || base.sessionStorage
3362
+ });
3363
+ }
3364
+ function defineServices(definitions) {
3365
+ const result = {};
3366
+ for (const [key, def] of Object.entries(definitions)) {
3367
+ if (typeof def === "string") {
3368
+ result[key] = getPreset(def);
3369
+ } else if (def && typeof def === "object") {
3370
+ if (def.preset) {
3371
+ const _a = def, { preset } = _a, overrides = __objRest(_a, ["preset"]);
3372
+ result[key] = getPreset(preset, overrides);
3373
+ } else if (hasPreset(key)) {
3374
+ result[key] = getPreset(key, def);
3375
+ } else {
3376
+ result[key] = def;
3377
+ }
3378
+ }
3379
+ }
3380
+ return result;
3381
+ }
3382
+ function resolveConfigPresets(config) {
3383
+ if (!config || !config.services || typeof config.services !== "object") {
3384
+ return config;
3385
+ }
3386
+ const hydratedServices = {};
3387
+ for (const [serviceKey, serviceDef] of Object.entries(config.services)) {
3388
+ if (!serviceDef || typeof serviceDef !== "object") {
3389
+ hydratedServices[serviceKey] = serviceDef;
3390
+ continue;
3391
+ }
3392
+ const presetName = serviceDef.preset || (hasPreset(serviceKey) && !serviceDef.category ? serviceKey : void 0);
3393
+ if (presetName && hasPreset(presetName)) {
3394
+ const preset = SERVICE_PRESETS[presetName];
3395
+ hydratedServices[serviceKey] = __spreadValues({
3396
+ category: serviceDef.category || preset.category,
3397
+ label: serviceDef.label || preset.label,
3398
+ provider: serviceDef.provider || preset.provider,
3399
+ policyUrl: serviceDef.policyUrl || preset.policyUrl,
3400
+ description: serviceDef.description || preset.description,
3401
+ cookies: serviceDef.cookies || (preset.cookies ? [...preset.cookies] : void 0),
3402
+ storageKeys: serviceDef.storageKeys || (preset.storageKeys ? [...preset.storageKeys] : void 0),
3403
+ localStorage: serviceDef.localStorage || (preset.localStorage ? [...preset.localStorage] : void 0),
3404
+ sessionStorage: serviceDef.sessionStorage || (preset.sessionStorage ? [...preset.sessionStorage] : void 0)
3405
+ }, serviceDef);
3406
+ } else {
3407
+ hydratedServices[serviceKey] = serviceDef;
3408
+ }
3409
+ }
3410
+ return __spreadProps(__spreadValues({}, config), {
3411
+ services: hydratedServices
3412
+ });
3413
+ }
3414
+
3415
+ // src/core/gpc.ts
3416
+ function detectGpcSignal() {
3417
+ var _a, _b, _c, _d;
3418
+ if (typeof window === "undefined" && typeof navigator === "undefined" && typeof globalThis === "undefined") {
3419
+ return false;
3420
+ }
3421
+ const nav = typeof navigator !== "undefined" ? navigator : null;
3422
+ const win = typeof window !== "undefined" ? window : null;
3423
+ const glob = typeof globalThis !== "undefined" ? globalThis : null;
3424
+ if ((nav == null ? void 0 : nav.globalPrivacyControl) === true || (win == null ? void 0 : win.globalPrivacyControl) === true || ((_a = win == null ? void 0 : win.navigator) == null ? void 0 : _a.globalPrivacyControl) === true || ((_b = glob == null ? void 0 : glob.navigator) == null ? void 0 : _b.globalPrivacyControl) === true || (glob == null ? void 0 : glob.globalPrivacyControl) === true) {
3425
+ return true;
3426
+ }
3427
+ if ((nav == null ? void 0 : nav.doNotTrack) === "1" || (win == null ? void 0 : win.doNotTrack) === "1" || ((_c = win == null ? void 0 : win.navigator) == null ? void 0 : _c.doNotTrack) === "1" || ((_d = glob == null ? void 0 : glob.navigator) == null ? void 0 : _d.doNotTrack) === "1" || (glob == null ? void 0 : glob.doNotTrack) === "1" || (nav == null ? void 0 : nav.msDoNotTrack) === "1" || (win == null ? void 0 : win.external) && typeof win.external.msTrackingProtectionEnabled === "function" && win.external.msTrackingProtectionEnabled()) {
3428
+ return true;
3429
+ }
3430
+ return false;
3431
+ }
3432
+ function resolveGpcConfig(config) {
3433
+ const gpc = config == null ? void 0 : config.gpc;
3434
+ return {
3435
+ enabled: (gpc == null ? void 0 : gpc.enabled) !== false,
3436
+ respectSignal: (gpc == null ? void 0 : gpc.respectSignal) !== false,
3437
+ mode: (gpc == null ? void 0 : gpc.mode) || "auto-reject",
3438
+ categories: (gpc == null ? void 0 : gpc.categories) || [],
3439
+ noticeText: (gpc == null ? void 0 : gpc.noticeText) || "Se\xF1al de Privacidad Global (GPC) detectada: cookies no esenciales desactivadas."
3440
+ };
3441
+ }
3442
+ function applyGpcChoices(config, baseChoices) {
3443
+ const result = __spreadValues({}, baseChoices || {});
3444
+ const gpcOptions = resolveGpcConfig(config);
3445
+ for (const [catId, catConfig] of Object.entries(config.categories)) {
3446
+ if (catConfig.required || catId === "necessary") {
3447
+ result[catId] = true;
3448
+ } else {
3449
+ if (gpcOptions.categories.length === 0 || gpcOptions.categories.includes(catId)) {
3450
+ result[catId] = false;
3451
+ }
3452
+ }
3453
+ }
3454
+ return result;
3455
+ }
3456
+
1675
3457
  // src/core/consent-engine.ts
1676
3458
  var ConsentEngine = class {
1677
3459
  constructor() {
@@ -1680,6 +3462,9 @@ var ConsentEngine = class {
1680
3462
  this.blockerRegistry = new BlockerRegistry();
1681
3463
  this.banner = new ConsentBanner();
1682
3464
  this.preferencesModal = new PreferencesModal();
3465
+ this.floatingBadge = new FloatingBadge();
3466
+ this.i18n = new I18nEngine();
3467
+ this.stopLocaleSync = null;
1683
3468
  this.initPromise = null;
1684
3469
  this.resolveReady = null;
1685
3470
  this.readyPromise = new Promise((resolve) => {
@@ -1689,7 +3474,7 @@ var ConsentEngine = class {
1689
3474
  async init(configInput) {
1690
3475
  if (this.initPromise) return this.initPromise;
1691
3476
  this.initPromise = (async () => {
1692
- var _a, _b, _c;
3477
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
1693
3478
  let config;
1694
3479
  if (typeof configInput === "string") {
1695
3480
  const response = await fetch(configInput);
@@ -1702,16 +3487,72 @@ var ConsentEngine = class {
1702
3487
  } else {
1703
3488
  config = configInput;
1704
3489
  }
3490
+ config = resolveConfigPresets(config);
1705
3491
  validateConfig(config);
1706
- const cookieName = ((_a = config.storage) == null ? void 0 : _a.name) || "site_consent";
1707
- const rawReceipt = ((_b = config.storage) == null ? void 0 : _b.type) === "memory" ? MemoryStore.get(cookieName) : CookieStore.get(cookieName);
3492
+ const initialLocale = this.i18n.detectParentLocale(config);
3493
+ this.i18n.setLocale(initialLocale);
3494
+ (_a = this.stopLocaleSync) == null ? void 0 : _a.call(this);
3495
+ this.stopLocaleSync = this.i18n.startParentSync(config, (newLocale) => {
3496
+ this.setLocale(newLocale);
3497
+ });
3498
+ const cookieName = ((_b = config.storage) == null ? void 0 : _b.name) || "site_consent";
3499
+ const rawReceipt = ((_c = config.storage) == null ? void 0 : _c.type) === "memory" ? MemoryStore.get(cookieName) : CookieStore.get(cookieName);
1708
3500
  const savedReceipt = rawReceipt ? parseReceipt(rawReceipt) : null;
1709
3501
  const evalResult = evaluatePolicy(config, savedReceipt);
1710
- const activeReceipt = evalResult.isValid ? savedReceipt : null;
1711
- this.stateManager.init(config, evalResult.choices, activeReceipt);
3502
+ const isGpcDetected = detectGpcSignal();
3503
+ const gpcConfig = resolveGpcConfig(config);
3504
+ const isGpcActive = isGpcDetected && gpcConfig.enabled && gpcConfig.respectSignal;
3505
+ let activeReceipt = evalResult.isValid ? savedReceipt : null;
3506
+ let activeChoices = evalResult.choices;
3507
+ let autoAppliedGpc = false;
3508
+ if (!evalResult.isValid && isGpcActive) {
3509
+ if (gpcConfig.mode !== "notice-only") {
3510
+ activeChoices = applyGpcChoices(config);
3511
+ activeReceipt = createReceipt(
3512
+ config.policyVersion,
3513
+ activeChoices,
3514
+ "gpc",
3515
+ null,
3516
+ (_d = config.security) == null ? void 0 : _d.secretKey
3517
+ );
3518
+ activeReceipt.gpc = true;
3519
+ autoAppliedGpc = true;
3520
+ const receiptJson = JSON.stringify(activeReceipt);
3521
+ if (((_e = config.storage) == null ? void 0 : _e.type) === "memory") {
3522
+ MemoryStore.set(cookieName, receiptJson);
3523
+ } else {
3524
+ CookieStore.set(cookieName, receiptJson, {
3525
+ path: ((_f = config.storage) == null ? void 0 : _f.path) || "/",
3526
+ maxAgeDays: (_h = (_g = config.consent) == null ? void 0 : _g.maxAgeDays) != null ? _h : 365,
3527
+ sameSite: ((_i = config.storage) == null ? void 0 : _i.sameSite) || "Lax",
3528
+ secure: (_j = config.storage) == null ? void 0 : _j.secure,
3529
+ domain: (_k = config.storage) == null ? void 0 : _k.domain
3530
+ });
3531
+ }
3532
+ for (const [catId, allowed] of Object.entries(activeChoices)) {
3533
+ if (!allowed && catId !== "necessary") {
3534
+ StorageCleaner.purgeCategory(config, catId);
3535
+ }
3536
+ }
3537
+ }
3538
+ }
3539
+ this.stateManager.init(config, activeChoices, activeReceipt, isGpcDetected);
3540
+ this.stateManager.setLocale(initialLocale);
3541
+ if (isGpcDetected) {
3542
+ this.eventBus.emit("gpc:detected", {
3543
+ signal: true,
3544
+ autoApplied: autoAppliedGpc,
3545
+ choices: activeChoices
3546
+ });
3547
+ this.dispatchDomEvent("solvenza:gpc", {
3548
+ signal: true,
3549
+ autoApplied: autoAppliedGpc,
3550
+ choices: activeChoices
3551
+ });
3552
+ }
1712
3553
  GoogleConsentAdapter.initDefault();
1713
- if (evalResult.isValid) {
1714
- GoogleConsentAdapter.update(evalResult.choices);
3554
+ if (evalResult.isValid || autoAppliedGpc) {
3555
+ GoogleConsentAdapter.update(activeChoices);
1715
3556
  this.dispatchDomEvent("solvenza:restored", { state: this.getConsent() });
1716
3557
  }
1717
3558
  this.blockerRegistry.init(
@@ -1729,9 +3570,19 @@ var ConsentEngine = class {
1729
3570
  }
1730
3571
  );
1731
3572
  this.setupGlobalRevocationTrigger();
1732
- (_c = this.resolveReady) == null ? void 0 : _c.call(this);
3573
+ const resolvedConfig = this.getResolvedConfig() || config;
3574
+ const badgeConfig = this.resolveFloatingBadgeConfig(resolvedConfig);
3575
+ if (badgeConfig.enabled) {
3576
+ this.floatingBadge.render(resolvedConfig, {
3577
+ onClick: () => this.openPreferences()
3578
+ });
3579
+ if (evalResult.isValid || autoAppliedGpc || badgeConfig.visibility === "always") {
3580
+ this.showFloatingBadge();
3581
+ }
3582
+ }
3583
+ (_l = this.resolveReady) == null ? void 0 : _l.call(this);
1733
3584
  this.eventBus.emit("ready", { state: this.getConsent() });
1734
- if (!evalResult.isValid) {
3585
+ if (!evalResult.isValid && !autoAppliedGpc) {
1735
3586
  this.showBanner();
1736
3587
  }
1737
3588
  })();
@@ -1783,13 +3634,24 @@ var ConsentEngine = class {
1783
3634
  this.saveChoices(sanitizedChoices, "preferences");
1784
3635
  }
1785
3636
  openPreferences() {
1786
- const config = this.stateManager.getConfig();
3637
+ const config = this.getResolvedConfig();
1787
3638
  if (!config) return;
3639
+ this.hideFloatingBadge();
1788
3640
  this.preferencesModal.render(config, this.stateManager.getChoices(), {
1789
- onSave: (choices) => this.setPreferences(choices),
1790
- onAcceptAll: () => this.acceptAll(),
1791
- onRejectAll: () => this.rejectAll(),
3641
+ onSave: (choices) => {
3642
+ this.setPreferences(choices);
3643
+ this.restoreFloatingBadgeIfNeeded();
3644
+ },
3645
+ onAcceptAll: () => {
3646
+ this.acceptAll();
3647
+ this.restoreFloatingBadgeIfNeeded();
3648
+ },
3649
+ onRejectAll: () => {
3650
+ this.rejectAll();
3651
+ this.restoreFloatingBadgeIfNeeded();
3652
+ },
1792
3653
  onClose: () => {
3654
+ this.restoreFloatingBadgeIfNeeded();
1793
3655
  this.eventBus.emit("preferences:closed", void 0);
1794
3656
  }
1795
3657
  });
@@ -1797,6 +3659,82 @@ var ConsentEngine = class {
1797
3659
  }
1798
3660
  closePreferences() {
1799
3661
  this.preferencesModal.close();
3662
+ this.restoreFloatingBadgeIfNeeded();
3663
+ this.eventBus.emit("preferences:closed", void 0);
3664
+ }
3665
+ getLocale() {
3666
+ return this.i18n.getLocale();
3667
+ }
3668
+ setLocale(locale) {
3669
+ const previousLocale = this.i18n.getLocale();
3670
+ if (locale.toLowerCase() === previousLocale.toLowerCase()) return;
3671
+ this.i18n.setLocale(locale);
3672
+ this.stateManager.setLocale(locale);
3673
+ const config = this.getResolvedConfig();
3674
+ if (!config) return;
3675
+ if (this.banner.getIsVisible()) {
3676
+ this.banner.render(config, {
3677
+ onAcceptAll: () => this.acceptAll(),
3678
+ onRejectAll: () => this.rejectAll(),
3679
+ onConfigure: () => this.openPreferences()
3680
+ });
3681
+ }
3682
+ if (this.preferencesModal.getIsOpen()) {
3683
+ this.preferencesModal.render(config, this.stateManager.getChoices(), {
3684
+ onSave: (choices) => {
3685
+ this.setPreferences(choices);
3686
+ this.restoreFloatingBadgeIfNeeded();
3687
+ },
3688
+ onAcceptAll: () => {
3689
+ this.acceptAll();
3690
+ this.restoreFloatingBadgeIfNeeded();
3691
+ },
3692
+ onRejectAll: () => {
3693
+ this.rejectAll();
3694
+ this.restoreFloatingBadgeIfNeeded();
3695
+ },
3696
+ onClose: () => {
3697
+ this.restoreFloatingBadgeIfNeeded();
3698
+ this.eventBus.emit("preferences:closed", void 0);
3699
+ }
3700
+ });
3701
+ }
3702
+ const badgeConfig = this.resolveFloatingBadgeConfig(config);
3703
+ if (badgeConfig.enabled) {
3704
+ const wasVisible = this.floatingBadge.getIsVisible();
3705
+ this.floatingBadge.render(config, {
3706
+ onClick: () => this.openPreferences()
3707
+ });
3708
+ if (wasVisible) {
3709
+ this.floatingBadge.show();
3710
+ }
3711
+ }
3712
+ this.eventBus.emit("locale:changed", { locale, previousLocale });
3713
+ this.dispatchDomEvent("solvenza:locale:changed", { locale, previousLocale });
3714
+ }
3715
+ /**
3716
+ * Synchronize active locale with parent application i18n state.
3717
+ */
3718
+ syncLocale(locale) {
3719
+ this.setLocale(locale);
3720
+ }
3721
+ /**
3722
+ * Check if Global Privacy Control (GPC) or Do Not Track (DNT) signal is active.
3723
+ */
3724
+ isGpcActive() {
3725
+ return detectGpcSignal();
3726
+ }
3727
+ restoreFloatingBadgeIfNeeded() {
3728
+ const config = this.getResolvedConfig();
3729
+ if (!config) return;
3730
+ const badgeConfig = this.resolveFloatingBadgeConfig(config);
3731
+ if (!badgeConfig.enabled) return;
3732
+ this.showFloatingBadge();
3733
+ }
3734
+ getResolvedConfig() {
3735
+ const rawConfig = this.stateManager.getConfig();
3736
+ if (!rawConfig) return null;
3737
+ return this.i18n.resolveConfig(rawConfig, this.getLocale());
1800
3738
  }
1801
3739
  withdraw() {
1802
3740
  var _a, _b, _c, _d;
@@ -1811,15 +3749,76 @@ var ConsentEngine = class {
1811
3749
  }
1812
3750
  for (const catId of Object.keys(config.categories)) {
1813
3751
  if (catId !== "necessary") {
1814
- CookieStore.clearServiceCookies(config, catId);
3752
+ this.purgeCategory(catId);
1815
3753
  }
1816
3754
  }
1817
3755
  this.stateManager.clearChoices();
1818
3756
  GoogleConsentAdapter.update(this.stateManager.getChoices());
1819
3757
  this.eventBus.emit("consent:withdrawn", { previousChoices });
1820
3758
  this.dispatchDomEvent("solvenza:updated", { choices: this.stateManager.getChoices() });
3759
+ const badgeConfig = this.resolveFloatingBadgeConfig(this.getResolvedConfig() || config);
3760
+ if (badgeConfig.enabled && badgeConfig.visibility !== "always") {
3761
+ this.hideFloatingBadge();
3762
+ }
1821
3763
  this.showBanner();
1822
3764
  }
3765
+ purgeCategory(category) {
3766
+ const config = this.stateManager.getConfig();
3767
+ if (!config) {
3768
+ return {
3769
+ category,
3770
+ purgedCookies: [],
3771
+ purgedLocalStorage: [],
3772
+ purgedSessionStorage: []
3773
+ };
3774
+ }
3775
+ const report = StorageCleaner.purgeCategory(config, category);
3776
+ this.eventBus.emit("storage:purged", { category, report });
3777
+ this.dispatchDomEvent("solvenza:storage:purged", { category, report });
3778
+ return report;
3779
+ }
3780
+ purgeStorage(categoryOrService) {
3781
+ var _a, _b;
3782
+ const config = this.stateManager.getConfig();
3783
+ if (!config) return [];
3784
+ if (categoryOrService) {
3785
+ if ((_a = config.categories) == null ? void 0 : _a[categoryOrService]) {
3786
+ return [this.purgeCategory(categoryOrService)];
3787
+ }
3788
+ const service = (_b = config.services) == null ? void 0 : _b[categoryOrService];
3789
+ if (service == null ? void 0 : service.category) {
3790
+ return [this.purgeCategory(service.category)];
3791
+ }
3792
+ }
3793
+ const reports = [];
3794
+ const currentChoices = this.stateManager.getChoices();
3795
+ for (const [catId, allowed] of Object.entries(currentChoices)) {
3796
+ if (!allowed && catId !== "necessary") {
3797
+ reports.push(this.purgeCategory(catId));
3798
+ }
3799
+ }
3800
+ return reports;
3801
+ }
3802
+ showFloatingBadge() {
3803
+ const config = this.getResolvedConfig();
3804
+ if (!config) return;
3805
+ const badgeConfig = this.resolveFloatingBadgeConfig(config);
3806
+ if (!badgeConfig.enabled) return;
3807
+ const el = this.floatingBadge.getElement();
3808
+ if (!el || !el.parentNode) {
3809
+ this.floatingBadge.render(config, {
3810
+ onClick: () => this.openPreferences()
3811
+ });
3812
+ }
3813
+ this.floatingBadge.show();
3814
+ this.eventBus.emit("floating-badge:shown", void 0);
3815
+ this.dispatchDomEvent("solvenza:badge:shown", void 0);
3816
+ }
3817
+ hideFloatingBadge() {
3818
+ this.floatingBadge.hide();
3819
+ this.eventBus.emit("floating-badge:hidden", void 0);
3820
+ this.dispatchDomEvent("solvenza:badge:hidden", void 0);
3821
+ }
1823
3822
  when(categoryOrService, callback) {
1824
3823
  return CustomServiceAdapter.createWhen(
1825
3824
  this.eventBus,
@@ -1841,15 +3840,56 @@ var ConsentEngine = class {
1841
3840
  const isGiven = !!this.getReceipt();
1842
3841
  return ResourceScanner.runDiagnostic(config, isGiven);
1843
3842
  }
1844
- mountPolicy(targetContainer) {
1845
- const config = this.stateManager.getConfig();
3843
+ mountPolicy(targetContainer, options) {
3844
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
3845
+ if (!config) {
3846
+ throw new Error("[ConsentSDK] SDK not initialized.");
3847
+ }
3848
+ const container = typeof targetContainer === "string" ? document.querySelector(targetContainer) : targetContainer;
3849
+ if (container) {
3850
+ container.innerHTML = PolicyGenerator.renderCookiePolicy(config, options);
3851
+ }
3852
+ }
3853
+ renderPolicyHtml(options) {
3854
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
3855
+ if (!config) {
3856
+ throw new Error("[ConsentSDK] SDK not initialized.");
3857
+ }
3858
+ return PolicyGenerator.renderCookiePolicy(config, options);
3859
+ }
3860
+ mountLegalNotice(targetContainer, options) {
3861
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
3862
+ if (!config) {
3863
+ throw new Error("[ConsentSDK] SDK not initialized.");
3864
+ }
3865
+ const container = typeof targetContainer === "string" ? document.querySelector(targetContainer) : targetContainer;
3866
+ if (container) {
3867
+ container.innerHTML = PolicyGenerator.renderLegalNotice(config, options);
3868
+ }
3869
+ }
3870
+ renderLegalNoticeHtml(options) {
3871
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
3872
+ if (!config) {
3873
+ throw new Error("[ConsentSDK] SDK not initialized.");
3874
+ }
3875
+ return PolicyGenerator.renderLegalNotice(config, options);
3876
+ }
3877
+ mountPrivacyPolicy(targetContainer, options) {
3878
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
1846
3879
  if (!config) {
1847
3880
  throw new Error("[ConsentSDK] SDK not initialized.");
1848
3881
  }
1849
3882
  const container = typeof targetContainer === "string" ? document.querySelector(targetContainer) : targetContainer;
1850
3883
  if (container) {
1851
- container.innerHTML = PolicyGenerator.renderTable(config);
3884
+ container.innerHTML = PolicyGenerator.renderPrivacyPolicy(config, options);
3885
+ }
3886
+ }
3887
+ renderPrivacyPolicyHtml(options) {
3888
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
3889
+ if (!config) {
3890
+ throw new Error("[ConsentSDK] SDK not initialized.");
1852
3891
  }
3892
+ return PolicyGenerator.renderPrivacyPolicy(config, options);
1853
3893
  }
1854
3894
  saveChoices(choices, source) {
1855
3895
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
@@ -1862,10 +3902,13 @@ var ConsentEngine = class {
1862
3902
  this.stateManager.getReceipt(),
1863
3903
  (_a = config.security) == null ? void 0 : _a.secretKey
1864
3904
  );
3905
+ if (this.isGpcActive()) {
3906
+ receipt.gpc = true;
3907
+ }
1865
3908
  this.stateManager.updateChoices(receipt);
1866
3909
  for (const [catId, isAllowed] of Object.entries(choices)) {
1867
3910
  if (!isAllowed) {
1868
- CookieStore.clearServiceCookies(config, catId);
3911
+ this.purgeCategory(catId);
1869
3912
  }
1870
3913
  }
1871
3914
  const cookieName = ((_b = config.storage) == null ? void 0 : _b.name) || "site_consent";
@@ -1898,12 +3941,21 @@ var ConsentEngine = class {
1898
3941
  }
1899
3942
  }
1900
3943
  this.banner.remove();
3944
+ const resolvedConfig = this.getResolvedConfig() || config;
3945
+ const badgeConfig = this.resolveFloatingBadgeConfig(resolvedConfig);
3946
+ if (badgeConfig.enabled) {
3947
+ this.showFloatingBadge();
3948
+ }
1901
3949
  this.eventBus.emit("consent:changed", { choices, receipt });
1902
3950
  this.dispatchDomEvent("solvenza:updated", { choices, receipt });
1903
3951
  }
1904
3952
  showBanner() {
1905
- const config = this.stateManager.getConfig();
3953
+ const config = this.getResolvedConfig() || this.stateManager.getConfig();
1906
3954
  if (!config) return;
3955
+ const badgeConfig = this.resolveFloatingBadgeConfig(config);
3956
+ if (badgeConfig.enabled && badgeConfig.visibility !== "always") {
3957
+ this.hideFloatingBadge();
3958
+ }
1907
3959
  this.banner.render(config, {
1908
3960
  onAcceptAll: () => this.acceptAll(),
1909
3961
  onRejectAll: () => this.rejectAll(),
@@ -1927,6 +3979,45 @@ var ConsentEngine = class {
1927
3979
  document.addEventListener("solvenza:preferences", () => {
1928
3980
  this.openPreferences();
1929
3981
  });
3982
+ document.addEventListener("solvenza:badge:show", () => {
3983
+ this.showFloatingBadge();
3984
+ });
3985
+ document.addEventListener("solvenza:badge:hide", () => {
3986
+ this.hideFloatingBadge();
3987
+ });
3988
+ document.addEventListener("solvenza:locale", (e) => {
3989
+ var _a;
3990
+ if ((_a = e == null ? void 0 : e.detail) == null ? void 0 : _a.locale) {
3991
+ this.setLocale(e.detail.locale);
3992
+ }
3993
+ });
3994
+ }
3995
+ resolveFloatingBadgeConfig(config) {
3996
+ var _a;
3997
+ const raw = (_a = config == null ? void 0 : config.ui) == null ? void 0 : _a.floatingBadge;
3998
+ if (raw === false) {
3999
+ return { enabled: false };
4000
+ }
4001
+ if (raw === true || raw === void 0) {
4002
+ return {
4003
+ enabled: true,
4004
+ position: "bottom-left",
4005
+ icon: "cookie",
4006
+ visibility: "after-consent"
4007
+ };
4008
+ }
4009
+ if (typeof raw === "object" && raw !== null) {
4010
+ return {
4011
+ enabled: raw.enabled !== false,
4012
+ position: raw.position || "bottom-left",
4013
+ label: raw.label,
4014
+ ariaLabel: raw.ariaLabel,
4015
+ showLabel: raw.showLabel,
4016
+ icon: raw.icon || "cookie",
4017
+ visibility: raw.visibility || "after-consent"
4018
+ };
4019
+ }
4020
+ return { enabled: true, position: "bottom-left", icon: "cookie", visibility: "after-consent" };
1930
4021
  }
1931
4022
  dispatchDomEvent(name, detail) {
1932
4023
  if (typeof document === "undefined") return;
@@ -1964,17 +4055,44 @@ var ConsentConfigBuilder = class {
1964
4055
  this.config.policyVersion = version;
1965
4056
  return this;
1966
4057
  }
4058
+ setLegalEntity(entity) {
4059
+ this.config.legalEntity = entity;
4060
+ return this;
4061
+ }
4062
+ setLegalNotice(notice) {
4063
+ this.config.legalNotice = notice;
4064
+ return this;
4065
+ }
1967
4066
  setPolicyUrls(privacyUrl, cookiesUrl) {
1968
4067
  this.config.policy = { privacyUrl, cookiesUrl };
1969
4068
  return this;
1970
4069
  }
1971
- setLocale(defaultLocale, autoDetect = true) {
4070
+ setLocale(defaultLocale, autoDetect = true, supported) {
1972
4071
  this.config.locale = {
1973
4072
  default: defaultLocale,
1974
- autoDetect
4073
+ autoDetect,
4074
+ supported
1975
4075
  };
1976
4076
  return this;
1977
4077
  }
4078
+ setTranslations(translations) {
4079
+ this.config.translations = translations;
4080
+ return this;
4081
+ }
4082
+ addTranslation(locale, translation) {
4083
+ if (!this.config.translations) {
4084
+ this.config.translations = {};
4085
+ }
4086
+ this.config.translations[locale.toLowerCase()] = translation;
4087
+ return this;
4088
+ }
4089
+ setFloatingBadge(badgeConfig) {
4090
+ if (!this.config.ui) {
4091
+ this.config.ui = {};
4092
+ }
4093
+ this.config.ui.floatingBadge = badgeConfig;
4094
+ return this;
4095
+ }
1978
4096
  addCategory(id, category) {
1979
4097
  if (!this.config.categories) {
1980
4098
  this.config.categories = {};
@@ -2021,25 +4139,6 @@ var StorageFactory = class {
2021
4139
  }
2022
4140
  };
2023
4141
 
2024
- // src/i18n/engine.ts
2025
- var I18nEngine = class {
2026
- constructor() {
2027
- this.locale = "es";
2028
- }
2029
- setLocale(locale) {
2030
- this.locale = locale;
2031
- }
2032
- getLocale() {
2033
- return this.locale;
2034
- }
2035
- detectBrowserLocale() {
2036
- if (typeof navigator !== "undefined" && navigator.language) {
2037
- return navigator.language.split("-")[0].toLowerCase();
2038
- }
2039
- return "es";
2040
- }
2041
- };
2042
-
2043
4142
  // src/index.ts
2044
4143
  if (typeof window !== "undefined") {
2045
4144
  window.Consent = Consent;
@@ -2049,6 +4148,7 @@ if (typeof window !== "undefined") {
2049
4148
  ConsentConfigBuilder,
2050
4149
  validateConfig,
2051
4150
  PolicyGenerator,
4151
+ FloatingBadge,
2052
4152
  ResourceScanner
2053
4153
  };
2054
4154
  }
@@ -2070,12 +4170,14 @@ if (typeof document !== "undefined") {
2070
4170
  }
2071
4171
  // Annotate the CommonJS export names for ESM import in node:
2072
4172
  0 && (module.exports = {
4173
+ BUILTIN_TRANSLATIONS,
2073
4174
  ConfigValidationError,
2074
4175
  Consent,
2075
4176
  ConsentConfigBuilder,
2076
4177
  ConsentEngine,
2077
4178
  CookieStorageProvider,
2078
4179
  CookieStore,
4180
+ FloatingBadge,
2079
4181
  GoogleConsentAdapter,
2080
4182
  I18nEngine,
2081
4183
  IframeGate,
@@ -2086,13 +4188,22 @@ if (typeof document !== "undefined") {
2086
4188
  PolicyGenerator,
2087
4189
  ResourceGate,
2088
4190
  ResourceScanner,
4191
+ SERVICE_PRESETS,
2089
4192
  ScriptGate,
2090
4193
  ScriptResourceBlocker,
4194
+ StorageCleaner,
2091
4195
  StorageFactory,
4196
+ applyGpcChoices,
2092
4197
  computeReceiptSignature,
2093
4198
  createReceipt,
4199
+ defineServices,
4200
+ detectGpcSignal,
4201
+ getPreset,
4202
+ hasPreset,
2094
4203
  isReceiptExpired,
2095
4204
  parseReceipt,
4205
+ resolveConfigPresets,
4206
+ resolveGpcConfig,
2096
4207
  sanitizeHtml,
2097
4208
  validateConfig,
2098
4209
  verifyReceiptIntegrity