@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.
- package/README.md +960 -36
- package/bin/audit-cli.js +134 -13
- package/dist/audit.cjs +811 -0
- package/dist/audit.cjs.map +1 -0
- package/dist/audit.d.cts +60 -0
- package/dist/audit.d.ts +60 -0
- package/dist/audit.js +380 -0
- package/dist/audit.js.map +1 -0
- package/dist/chunk-C5QPXHTH.js +3557 -0
- package/dist/chunk-C5QPXHTH.js.map +1 -0
- package/dist/chunk-OHOHIAK6.js +442 -0
- package/dist/chunk-OHOHIAK6.js.map +1 -0
- package/dist/chunk-Q3FZFXQE.js +89 -0
- package/dist/chunk-UQU54ANZ.js +225 -0
- package/dist/chunk-UQU54ANZ.js.map +1 -0
- package/dist/chunk-W5MG56VI.js +269 -0
- package/dist/chunk-W5MG56VI.js.map +1 -0
- package/dist/consent.esm.cjs +2195 -84
- package/dist/consent.esm.cjs.map +1 -1
- package/dist/consent.esm.d.cts +173 -6
- package/dist/consent.esm.d.ts +173 -6
- package/dist/consent.esm.js +57 -23
- package/dist/consent.esm.js.map +1 -1
- package/dist/consent.min.js +437 -55
- package/dist/consent.min.js.map +1 -1
- package/dist/presets.cjs +495 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +51 -0
- package/dist/presets.d.ts +51 -0
- package/dist/presets.js +16 -0
- package/dist/presets.js.map +1 -0
- package/dist/types--kyI7OOR.d.cts +314 -0
- package/dist/types--kyI7OOR.d.ts +314 -0
- package/dist/wrappers/angular.cjs +2341 -65
- package/dist/wrappers/angular.cjs.map +1 -1
- package/dist/wrappers/angular.d.cts +85 -5
- package/dist/wrappers/angular.d.ts +85 -5
- package/dist/wrappers/angular.js +217 -3
- package/dist/wrappers/angular.js.map +1 -1
- package/dist/wrappers/next.cjs +2353 -103
- package/dist/wrappers/next.cjs.map +1 -1
- package/dist/wrappers/next.d.cts +25 -1
- package/dist/wrappers/next.d.ts +25 -1
- package/dist/wrappers/next.js +30 -5
- package/dist/wrappers/next.js.map +1 -1
- package/dist/wrappers/nuxt.cjs +4171 -0
- package/dist/wrappers/nuxt.cjs.map +1 -0
- package/dist/wrappers/nuxt.d.cts +33 -0
- package/dist/wrappers/nuxt.d.ts +33 -0
- package/dist/wrappers/nuxt.js +51 -0
- package/dist/wrappers/nuxt.js.map +1 -0
- package/dist/wrappers/react.cjs +2336 -141
- package/dist/wrappers/react.cjs.map +1 -1
- package/dist/wrappers/react.d.cts +87 -1
- package/dist/wrappers/react.d.ts +87 -1
- package/dist/wrappers/react.js +21 -51
- package/dist/wrappers/react.js.map +1 -1
- package/dist/wrappers/vue.cjs +4163 -0
- package/dist/wrappers/vue.cjs.map +1 -0
- package/dist/wrappers/vue.d.cts +144 -0
- package/dist/wrappers/vue.d.ts +144 -0
- package/dist/wrappers/vue.js +28 -0
- package/dist/wrappers/vue.js.map +1 -0
- package/dist/wrappers/wordpress.js +1 -1
- package/package.json +26 -5
- package/schema.json +143 -4
- package/dist/chunk-DDAAVRWG.js +0 -25
- package/dist/chunk-FBRP4MSA.js +0 -1907
- package/dist/chunk-FBRP4MSA.js.map +0 -1
- package/dist/consent-engine-DOMyEKfk.d.cts +0 -193
- package/dist/consent-engine-DOMyEKfk.d.ts +0 -193
- /package/dist/{chunk-DDAAVRWG.js.map → chunk-Q3FZFXQE.js.map} +0 -0
package/README.md
CHANGED
|
@@ -30,7 +30,10 @@ npm install @solvenza/cookies-compliance
|
|
|
30
30
|
### CDN (1 sola línea)
|
|
31
31
|
|
|
32
32
|
```html
|
|
33
|
-
<script
|
|
33
|
+
<script
|
|
34
|
+
src="https://cdn.jsdelivr.net/npm/@solvenza/cookies-compliance@1/dist/consent.min.js"
|
|
35
|
+
data-config="/consent.json"
|
|
36
|
+
></script>
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
---
|
|
@@ -56,6 +59,14 @@ Ejemplo de `consent.json`:
|
|
|
56
59
|
"privacyUrl": "/politica-privacidad",
|
|
57
60
|
"cookiesUrl": "/politica-cookies"
|
|
58
61
|
},
|
|
62
|
+
"ui": {
|
|
63
|
+
"floatingBadge": {
|
|
64
|
+
"enabled": true,
|
|
65
|
+
"position": "bottom-left",
|
|
66
|
+
"icon": "cookie",
|
|
67
|
+
"label": "Cookies"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
59
70
|
"categories": {
|
|
60
71
|
"necessary": {
|
|
61
72
|
"required": true,
|
|
@@ -74,74 +85,584 @@ Ejemplo de `consent.json`:
|
|
|
74
85
|
}
|
|
75
86
|
},
|
|
76
87
|
"services": {
|
|
77
|
-
"ga4": {
|
|
78
|
-
|
|
88
|
+
"ga4": {
|
|
89
|
+
"category": "analytics",
|
|
90
|
+
"label": "Google Analytics 4",
|
|
91
|
+
"provider": "Google"
|
|
92
|
+
},
|
|
93
|
+
"youtube": {
|
|
94
|
+
"category": "marketing",
|
|
95
|
+
"label": "YouTube Embed",
|
|
96
|
+
"provider": "Google"
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Botón Flotante de Revocación Permanente (AEPD / RGPD)
|
|
105
|
+
|
|
106
|
+
De acuerdo con la **Guía de la AEPD sobre el uso de cookies**, el usuario debe tener a su disposición un mecanismo permanente y fácilmente accesible para modificar sus preferencias o **declinar/revocar el consentimiento en cualquier momento**.
|
|
107
|
+
|
|
108
|
+
Puedes activarlo directamente en `consent.json`:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"ui": {
|
|
113
|
+
"floatingBadge": {
|
|
114
|
+
"enabled": true,
|
|
115
|
+
"position": "bottom-left",
|
|
116
|
+
"icon": "cookie",
|
|
117
|
+
"label": "Cookies",
|
|
118
|
+
"showLabel": false,
|
|
119
|
+
"visibility": "after-consent"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
O de forma abreviada:
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"ui": {
|
|
130
|
+
"floatingBadge": true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Opciones de configuración:
|
|
136
|
+
|
|
137
|
+
| Parámetro | Tipo | Por defecto | Descripción |
|
|
138
|
+
| ------------ | -------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------- |
|
|
139
|
+
| `enabled` | `boolean` | `true` (si se declara objeto) | Activa o desactiva el widget flotante |
|
|
140
|
+
| `position` | `"bottom-left"` \| `"bottom-right"` \| `"top-left"` \| `"top-right"` | `"bottom-left"` | Esquina de anclaje en pantalla |
|
|
141
|
+
| `icon` | `"cookie"` \| `"shield"` \| `"settings"` | `"cookie"` | Icono SVG estilizado |
|
|
142
|
+
| `label` | `string` | `"Cookies"` | Texto accesible y etiqueta en píldora |
|
|
143
|
+
| `tooltip` | `string` | `"Configurar o declinar cookies"` | Texto descriptivo emergente al pasar el cursor (hover) |
|
|
144
|
+
| `showLabel` | `boolean` | `false` | Muestra la etiqueta de texto junto al icono en forma de píldora |
|
|
145
|
+
| `visibility` | `"after-consent"` \| `"always"` | `"after-consent"` | Muestra el botón tras cerrar el banner o en todo momento |
|
|
146
|
+
|
|
147
|
+
> **Ciclo de vida automático con el modal**: Para garantizar una experiencia de usuario impecable y sin elementos superpuestos, el botón flotante se oculta mientras el modal de preferencias está abierto. **En cuanto el modal se cierra** (mediante el botón 'X', clic en el backdrop, tecla Escape o al guardar la selección), **el botón flotante reaparece de inmediato**.
|
|
148
|
+
|
|
149
|
+
### API Programática:
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// Mostrar u ocultar manualmente
|
|
153
|
+
Consent.showFloatingBadge();
|
|
154
|
+
Consent.hideFloatingBadge();
|
|
155
|
+
|
|
156
|
+
// Eventos
|
|
157
|
+
Consent.on("floating-badge:shown", () => console.log("Badge visible"));
|
|
158
|
+
Consent.on("floating-badge:hidden", () => console.log("Badge oculto"));
|
|
159
|
+
|
|
160
|
+
// Eventos nativos DOM (Zero-code)
|
|
161
|
+
document.dispatchEvent(new Event("solvenza:badge:show"));
|
|
162
|
+
document.dispatchEvent(new Event("solvenza:badge:hide"));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Internacionalización y Sincronización Multilingüe (i18n)
|
|
168
|
+
|
|
169
|
+
El sistema de internacionalización (`I18nEngine`) está diseñado bajo el principio de **cero boilerplate** y **sincronización transparente con la aplicación padre**:
|
|
170
|
+
|
|
171
|
+
> [!NOTE]
|
|
172
|
+
> **Sin selectores invasivos en el banner**: La librería **no** incluye selectores ni desplegables de idioma dentro del banner ni del modal de preferencias. El idioma de navegación es responsabilidad exclusiva de la aplicación anfitriona (padre). El banner y el modal se adaptan y sincronizan automáticamente al idioma de la web en tiempo real.
|
|
173
|
+
|
|
174
|
+
### Mecanismos de Sincronización Automática (Zero Boilerplate)
|
|
175
|
+
|
|
176
|
+
1. **Observador Reactivo del HTML (`<html lang="...">`)**:
|
|
177
|
+
- La librería observa dinámicamente mediante `MutationObserver` el atributo `lang` en `document.documentElement`.
|
|
178
|
+
- Cuando tu aplicación padre cambia de idioma (ej. `document.documentElement.lang = "en"`), el banner, modal y botón flotante se traducen y renderizan de inmediato **sin requerir reinicialización ni código de pegamento**.
|
|
179
|
+
2. **Detección Automática por Ruta y Query Params**:
|
|
180
|
+
- Detecta prefijos en la URL (como `/en/...` o `/ca/...`) o parámetros de consulta (`?lang=en`, `?locale=en`).
|
|
181
|
+
3. **Sincronización Directa de Estado (`Consent.syncLocale`)**:
|
|
182
|
+
- Función declarativa para enlazar el estado de traducción de tu aplicación (`react-i18next`, `next-intl`, `@ngx-translate`, etc.).
|
|
183
|
+
|
|
184
|
+
### Idiomas Integrados por Defecto
|
|
185
|
+
|
|
186
|
+
El SDK incluye diccionarios oficiales para la normativa española y europea sin necesidad de configurar textos:
|
|
187
|
+
|
|
188
|
+
- **Español (`es`)** [Por defecto]
|
|
189
|
+
- **Inglés (`en`)**
|
|
190
|
+
- **Catalán (`ca`)**
|
|
191
|
+
- **Euskera (`eu`)**
|
|
192
|
+
- **Gallego (`gl`)**
|
|
193
|
+
|
|
194
|
+
### Configuración Declarativa en `consent.json`:
|
|
195
|
+
|
|
196
|
+
```json
|
|
197
|
+
{
|
|
198
|
+
"locale": {
|
|
199
|
+
"default": "es",
|
|
200
|
+
"syncHtmlLang": true,
|
|
201
|
+
"syncUrl": true,
|
|
202
|
+
"autoDetect": true,
|
|
203
|
+
"supported": ["es", "en", "ca", "eu", "gl"]
|
|
204
|
+
},
|
|
205
|
+
"translations": {
|
|
206
|
+
"en": {
|
|
207
|
+
"policy": {
|
|
208
|
+
"privacyUrl": "/en/privacy-policy",
|
|
209
|
+
"cookiesUrl": "/en/cookie-policy"
|
|
210
|
+
},
|
|
211
|
+
"ui": {
|
|
212
|
+
"banner": {
|
|
213
|
+
"title": "Your privacy, your choice",
|
|
214
|
+
"accept": "Accept all",
|
|
215
|
+
"reject": "Reject all",
|
|
216
|
+
"configure": "Configure"
|
|
217
|
+
},
|
|
218
|
+
"preferences": {
|
|
219
|
+
"title": "Privacy Preferences",
|
|
220
|
+
"save": "Save preferences"
|
|
221
|
+
},
|
|
222
|
+
"floatingBadge": {
|
|
223
|
+
"label": "Cookies",
|
|
224
|
+
"tooltip": "Configure or decline cookies"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"categories": {
|
|
228
|
+
"analytics": {
|
|
229
|
+
"label": "Usage Analytics",
|
|
230
|
+
"description": "Allows aggregated performance measurement."
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Sincronización Programática:
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
// 1. Sincronizar el idioma desde la app padre
|
|
242
|
+
Consent.syncLocale("en");
|
|
243
|
+
|
|
244
|
+
// 2. Obtener idioma activo
|
|
245
|
+
console.log(Consent.getLocale()); // "en"
|
|
246
|
+
|
|
247
|
+
// 3. Escuchar cambios de idioma
|
|
248
|
+
Consent.on("locale:changed", ({ locale, previousLocale }) => {
|
|
249
|
+
console.log(`Idioma cambiado de ${previousLocale} a ${locale}`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// 4. Conmutación mediante CustomEvent del DOM (Zero-code / Microfrontends)
|
|
253
|
+
document.dispatchEvent(
|
|
254
|
+
new CustomEvent("solvenza:locale", { detail: { locale: "en" } }),
|
|
255
|
+
);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Configuración con `ConsentConfigBuilder` (TypeScript):
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
const config = new ConsentConfigBuilder("1.0.0")
|
|
262
|
+
.setLocale("es", true, ["es", "en", "ca"])
|
|
263
|
+
.addTranslation("en", {
|
|
264
|
+
ui: {
|
|
265
|
+
banner: { title: "Your privacy, your choice" },
|
|
266
|
+
floatingBadge: { tooltip: "Cookie Settings" },
|
|
267
|
+
},
|
|
268
|
+
categories: {
|
|
269
|
+
analytics: { label: "Analytics", description: "Aggregated telemetry." },
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
.build();
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Purga Automática de Web Storage (localStorage, sessionStorage y Cookies)
|
|
278
|
+
|
|
279
|
+
A partir de la versión **1.5.3**, cuando el usuario desmarca una categoría en el panel de preferencias o revoca su elección (`Consent.withdraw()`), el SDK no solo elimina las cookies en `document.cookie`, sino que también **purga de forma reactiva las claves guardadas en `localStorage` y `sessionStorage`** por bibliotecas de analítica o marketing (Google Analytics 4, PostHog, Mixpanel, Hotjar, etc.).
|
|
280
|
+
|
|
281
|
+
### Soporte de Comodines (Glob Wildcards)
|
|
282
|
+
|
|
283
|
+
Puedes declarar patrones exactos o con comodín `*`:
|
|
284
|
+
|
|
285
|
+
- `_ga*`: coincide con `_ga`, `_gid`, `_ga_G123456`, etc.
|
|
286
|
+
- `ph_*_posthog`: coincide con identificadores dinámicos de PostHog.
|
|
287
|
+
- `*session*`: coincide con cualquier clave que contenga `session`.
|
|
288
|
+
|
|
289
|
+
### Configuración en `consent.json`:
|
|
290
|
+
|
|
291
|
+
```json
|
|
292
|
+
{
|
|
293
|
+
"categories": {
|
|
294
|
+
"analytics": {
|
|
295
|
+
"label": "Analítica",
|
|
296
|
+
"description": "Medición agregada del tráfico.",
|
|
297
|
+
"storageKeys": ["_ga*", "_gid*"]
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
"services": {
|
|
301
|
+
"ga4": {
|
|
302
|
+
"category": "analytics",
|
|
303
|
+
"label": "Google Analytics 4",
|
|
304
|
+
"cookies": [{ "name": "_ga" }, { "name": "_ga_*" }, { "name": "_gid" }],
|
|
305
|
+
"storageKeys": ["_ga*", "_gid*"]
|
|
306
|
+
},
|
|
307
|
+
"posthog": {
|
|
308
|
+
"category": "analytics",
|
|
309
|
+
"label": "PostHog",
|
|
310
|
+
"localStorage": ["ph_*_posthog"],
|
|
311
|
+
"sessionStorage": ["ph_*_posthog"]
|
|
312
|
+
}
|
|
79
313
|
}
|
|
80
314
|
}
|
|
81
315
|
```
|
|
82
316
|
|
|
317
|
+
### Opciones de purga disponibles:
|
|
318
|
+
|
|
319
|
+
| Campo | Nivel | Descripción |
|
|
320
|
+
| ---------------- | -------------------- | --------------------------------------------------------------------------- |
|
|
321
|
+
| `storageKeys` | Categoría o Servicio | Claves o patrones a purgar tanto de `localStorage` como de `sessionStorage` |
|
|
322
|
+
| `localStorage` | Categoría o Servicio | Claves o patrones a purgar exclusivamente de `localStorage` |
|
|
323
|
+
| `sessionStorage` | Categoría o Servicio | Claves o patrones a purgar exclusivamente de `sessionStorage` |
|
|
324
|
+
| `cookies[].name` | Servicio | Nombres de cookies (soporta comodines como `_ga_*`) |
|
|
325
|
+
|
|
326
|
+
### API Programática:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import { Consent, StorageCleaner } from "@solvenza/cookies-compliance";
|
|
330
|
+
|
|
331
|
+
// 1. Purgar manualmente el almacenamiento de una categoría
|
|
332
|
+
const report = Consent.purgeCategory("analytics");
|
|
333
|
+
console.log(report.purgedCookies); // ["_ga", "_ga_G123456"]
|
|
334
|
+
console.log(report.purgedLocalStorage); // ["_ga", "ph_client_posthog"]
|
|
335
|
+
console.log(report.purgedSessionStorage); // ["temp_analytics_session"]
|
|
336
|
+
|
|
337
|
+
// 2. Escuchar eventos de purga
|
|
338
|
+
Consent.on("storage:purged", ({ category, report }) => {
|
|
339
|
+
console.log(`Almacenamiento purgado para la categoría ${category}:`, report);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// 3. Utilidad independiente
|
|
343
|
+
StorageCleaner.purgeLocalStorage(["_ga*", "temp_*"]);
|
|
344
|
+
```
|
|
345
|
+
|
|
83
346
|
---
|
|
84
347
|
|
|
85
|
-
##
|
|
348
|
+
## Presets de Servicios Comunes (GA4, Meta, Hotjar, etc.)
|
|
349
|
+
|
|
350
|
+
El SDK incluye un catálogo oficial de más de 20 presets predefinidos con todos los metadatos necesarios (proveedor legal, categorías por defecto, patrones de cookies con duración y propósito, claves de Web Storage y políticas oficiales):
|
|
351
|
+
|
|
352
|
+
### Catálogo de Servicios Soportados:
|
|
353
|
+
|
|
354
|
+
| Categoría | Presets Disponibles |
|
|
355
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
356
|
+
| **Analítica** | `ga4`, `google_analytics`, `posthog`, `hotjar`, `clarity`, `matomo`, `plausible` |
|
|
357
|
+
| **Marketing & Ads** | `meta_pixel`, `facebook_pixel`, `google_ads`, `tiktok_pixel`, `linkedin_insight`, `twitter_pixel`, `hubspot` |
|
|
358
|
+
| **Media Embebida** | `youtube`, `vimeo`, `spotify` |
|
|
359
|
+
| **Chat & Soporte** | `intercom`, `crisp` |
|
|
360
|
+
| **Técnicas / Seguridad / Pagos** | `gtm`, `google_recaptcha`, `cloudflare`, `stripe`, `paypal` |
|
|
361
|
+
|
|
362
|
+
### Uso Declarativo en `consent.json`:
|
|
363
|
+
|
|
364
|
+
Basta con especificar `"preset": "<nombre>"` para que el SDK hidrate automáticamente cookies, almacenamiento y proveedor:
|
|
365
|
+
|
|
366
|
+
```json
|
|
367
|
+
{
|
|
368
|
+
"schemaVersion": 1,
|
|
369
|
+
"policyVersion": "2026-09-30",
|
|
370
|
+
"categories": {
|
|
371
|
+
"necessary": {
|
|
372
|
+
"required": true,
|
|
373
|
+
"label": "Necesarias",
|
|
374
|
+
"description": "Cookies técnicas de seguridad y pago."
|
|
375
|
+
},
|
|
376
|
+
"analytics": {
|
|
377
|
+
"required": false,
|
|
378
|
+
"label": "Analítica",
|
|
379
|
+
"description": "Medición del tráfico y uso."
|
|
380
|
+
},
|
|
381
|
+
"marketing": {
|
|
382
|
+
"required": false,
|
|
383
|
+
"label": "Marketing",
|
|
384
|
+
"description": "Publicidad y contenido embebido."
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"services": {
|
|
388
|
+
"ga4": { "preset": "ga4" },
|
|
389
|
+
"facebook": { "preset": "meta_pixel" },
|
|
390
|
+
"hotjar": { "preset": "hotjar" },
|
|
391
|
+
"youtube": { "preset": "youtube" },
|
|
392
|
+
"stripe": { "preset": "stripe" }
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Uso Programático en TypeScript / JavaScript:
|
|
398
|
+
|
|
399
|
+
```ts
|
|
400
|
+
import {
|
|
401
|
+
Consent,
|
|
402
|
+
getPreset,
|
|
403
|
+
defineServices,
|
|
404
|
+
SERVICE_PRESETS,
|
|
405
|
+
} from "@solvenza/cookies-compliance";
|
|
406
|
+
// o importación directa:
|
|
407
|
+
// import { getPreset, defineServices } from "@solvenza/cookies-compliance/presets";
|
|
408
|
+
|
|
409
|
+
// 1. Obtener preset individual con personalizaciones
|
|
410
|
+
const customGA4 = getPreset("ga4", {
|
|
411
|
+
label: "Google Analytics (Región UE)",
|
|
412
|
+
provider: "Google Ireland Ltd.",
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// 2. Definir múltiples servicios de forma concisa y tipada
|
|
416
|
+
const services = defineServices({
|
|
417
|
+
ga4: "ga4",
|
|
418
|
+
meta: "meta_pixel",
|
|
419
|
+
hotjar: { preset: "hotjar", label: "Mapas de Calor UX" },
|
|
420
|
+
customApi: { category: "necessary", label: "API Interna de Autenticación" },
|
|
421
|
+
});
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
## Soporte Global Privacy Control (GPC) & Do Not Track (DNT)
|
|
427
|
+
|
|
428
|
+
El SDK detecta automáticamente la señal del estándar W3C **Global Privacy Control (`navigator.globalPrivacyControl === true`)** y **Do Not Track (`navigator.doNotTrack === "1"`)** enviada por navegadores como Firefox, Brave, DuckDuckGo o extensiones de privacidad.
|
|
429
|
+
|
|
430
|
+
### Comportamiento y Cumplimiento:
|
|
431
|
+
|
|
432
|
+
1. **Aplicación automática**: Si el usuario no ha expresado un consentimiento explícito previo y tiene GPC activado, el motor desactiva automáticamente las cookies y almacenamiento no esenciales.
|
|
433
|
+
2. **Generación de Comprobante Legal**: Se emite un recibo firmado con `source: "gpc"` y `gpc: true`.
|
|
434
|
+
3. **Purga Automática**: Se limpian de inmediato las cookies y Web Storage (`localStorage` / `sessionStorage`) de terceros.
|
|
435
|
+
4. **Sincronización con Google Consent Mode**: Se actualiza el modo de consentimiento de Google como `denied` para analítica y publicidad.
|
|
436
|
+
|
|
437
|
+
### Configuración en `consent.json`:
|
|
438
|
+
|
|
439
|
+
```json
|
|
440
|
+
{
|
|
441
|
+
"schemaVersion": 1,
|
|
442
|
+
"policyVersion": "2026-09-30",
|
|
443
|
+
"gpc": {
|
|
444
|
+
"enabled": true,
|
|
445
|
+
"respectSignal": true,
|
|
446
|
+
"mode": "auto-reject",
|
|
447
|
+
"categories": ["analytics", "marketing"]
|
|
448
|
+
},
|
|
449
|
+
"categories": {
|
|
450
|
+
"necessary": {
|
|
451
|
+
"required": true,
|
|
452
|
+
"label": "Necesarias",
|
|
453
|
+
"description": "Técnicas"
|
|
454
|
+
},
|
|
455
|
+
"analytics": {
|
|
456
|
+
"required": false,
|
|
457
|
+
"label": "Analítica",
|
|
458
|
+
"description": "Métricas"
|
|
459
|
+
},
|
|
460
|
+
"marketing": {
|
|
461
|
+
"required": false,
|
|
462
|
+
"label": "Marketing",
|
|
463
|
+
"description": "Publicidad"
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### API Programática:
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
import { Consent } from "@solvenza/cookies-compliance";
|
|
473
|
+
|
|
474
|
+
// Consultar si la señal GPC está activa en el navegador
|
|
475
|
+
if (Consent.isGpcActive()) {
|
|
476
|
+
console.log("Navegador con señal GPC / DNT activa.");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Escuchar evento de detección de señal
|
|
480
|
+
Consent.on("gpc:detected", ({ signal, autoApplied, choices }) => {
|
|
481
|
+
console.log("Señal de privacidad procesada:", {
|
|
482
|
+
signal,
|
|
483
|
+
autoApplied,
|
|
484
|
+
choices,
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
En React / Next:
|
|
490
|
+
|
|
491
|
+
```tsx
|
|
492
|
+
import { useGpc } from "@solvenza/cookies-compliance/react";
|
|
493
|
+
|
|
494
|
+
export function PrivacyNotice() {
|
|
495
|
+
const isGpc = useGpc();
|
|
496
|
+
return isGpc ? (
|
|
497
|
+
<p>Señal de privacidad Global Privacy Control detectada.</p>
|
|
498
|
+
) : null;
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
En Vue 3 / Nuxt 3:
|
|
503
|
+
|
|
504
|
+
```vue
|
|
505
|
+
<script setup>
|
|
506
|
+
import { useGpc } from "@solvenza/cookies-compliance/vue";
|
|
507
|
+
const isGpc = useGpc();
|
|
508
|
+
</script>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
---
|
|
86
512
|
|
|
87
513
|
### Vanilla HTML5
|
|
88
514
|
|
|
89
|
-
|
|
515
|
+
Incrusta el script compilado y tu configuración. El botón flotante de revocación se activará automáticamente según lo definido en `consent.json`:
|
|
90
516
|
|
|
91
517
|
```html
|
|
92
|
-
<!--
|
|
93
|
-
<script
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
518
|
+
<!-- 1. Carga automática con script compilado y configuración JSON -->
|
|
519
|
+
<script src="./consent.min.js" data-config="./consent.json"></script>
|
|
520
|
+
|
|
521
|
+
<!-- 2. Scripts y recursos bloqueados previamente -->
|
|
522
|
+
<script
|
|
523
|
+
type="text/plain"
|
|
524
|
+
data-consent="analytics"
|
|
525
|
+
data-service="ga4"
|
|
526
|
+
data-src="https://www.googletagmanager.com/gtag/js?id=G-DEMO123"
|
|
527
|
+
></script>
|
|
99
528
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
data-
|
|
103
|
-
data-
|
|
104
|
-
|
|
105
|
-
|
|
529
|
+
<iframe
|
|
530
|
+
data-consent="marketing"
|
|
531
|
+
data-service="youtube"
|
|
532
|
+
data-src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
533
|
+
width="560"
|
|
534
|
+
height="315"
|
|
535
|
+
>
|
|
106
536
|
</iframe>
|
|
107
537
|
|
|
108
|
-
<!-- Botón de revocación
|
|
109
|
-
<button type="button" data-consent-open>
|
|
538
|
+
<!-- 3. Botón de revocación opcional en footer (adicional al badge flotante permanente) -->
|
|
539
|
+
<button type="button" data-consent-open>Gestionar cookies</button>
|
|
110
540
|
```
|
|
111
541
|
|
|
112
542
|
### React 18+ / Vite
|
|
113
543
|
|
|
544
|
+
Puedes sincronizar el idioma de la aplicación (ej. procedente de `react-i18next` o de tu estado) sin boilerplate utilizando el hook `useSyncConsentLocale`:
|
|
545
|
+
|
|
114
546
|
```tsx
|
|
115
|
-
import { useEffect } from "react";
|
|
116
|
-
import { Consent } from "@solvenza/cookies-compliance";
|
|
117
|
-
import {
|
|
547
|
+
import { useState, useEffect } from "react";
|
|
548
|
+
import { Consent, ConsentConfigBuilder } from "@solvenza/cookies-compliance";
|
|
549
|
+
import {
|
|
550
|
+
useConsent,
|
|
551
|
+
useSyncConsentLocale,
|
|
552
|
+
ConsentGate,
|
|
553
|
+
} from "@solvenza/cookies-compliance/react";
|
|
118
554
|
|
|
119
555
|
export function App() {
|
|
556
|
+
const [appLang, setAppLang] = useState("es");
|
|
120
557
|
const isAnalyticsAllowed = useConsent("analytics");
|
|
121
558
|
|
|
559
|
+
// Sincronización automática de 1 línea con el estado de tu app
|
|
560
|
+
useSyncConsentLocale(appLang);
|
|
561
|
+
|
|
122
562
|
useEffect(() => {
|
|
123
|
-
|
|
563
|
+
// Inicialización declarativa con FloatingBadge activado
|
|
564
|
+
const config = new ConsentConfigBuilder("2026-08-23")
|
|
565
|
+
.setPolicyUrls("/politica-privacidad", "/politica-cookies")
|
|
566
|
+
.setLocale("es", true, ["es", "en", "ca"])
|
|
567
|
+
.setFloatingBadge({
|
|
568
|
+
enabled: true,
|
|
569
|
+
position: "bottom-left",
|
|
570
|
+
icon: "cookie",
|
|
571
|
+
label: "Cookies",
|
|
572
|
+
})
|
|
573
|
+
.addCategory("analytics", {
|
|
574
|
+
required: false,
|
|
575
|
+
label: "Analítica de uso",
|
|
576
|
+
description: "Permite medir de forma agregada el uso de la web.",
|
|
577
|
+
})
|
|
578
|
+
.addCategory("marketing", {
|
|
579
|
+
required: false,
|
|
580
|
+
label: "Marketing y Vídeo",
|
|
581
|
+
description: "Permite reproducir vídeos y contenido interactivo.",
|
|
582
|
+
})
|
|
583
|
+
.build();
|
|
584
|
+
|
|
585
|
+
void Consent.init(config);
|
|
124
586
|
}, []);
|
|
125
587
|
|
|
126
588
|
return (
|
|
127
589
|
<div>
|
|
590
|
+
<header>
|
|
591
|
+
<button onClick={() => setAppLang("es")}>ES</button>
|
|
592
|
+
<button onClick={() => setAppLang("en")}>EN</button>
|
|
593
|
+
</header>
|
|
128
594
|
<p>Analítica: {isAnalyticsAllowed ? "Activa" : "Bloqueada"}</p>
|
|
129
|
-
|
|
595
|
+
|
|
596
|
+
{/* Componente Declarativo ConsentGate */}
|
|
597
|
+
<ConsentGate
|
|
598
|
+
category="marketing"
|
|
599
|
+
fallback={
|
|
600
|
+
<div className="cookie-blocked-placeholder">
|
|
601
|
+
<p>
|
|
602
|
+
El reproductor de vídeo requiere permiso de cookies de marketing.
|
|
603
|
+
</p>
|
|
604
|
+
<button onClick={() => Consent.openPreferences()}>
|
|
605
|
+
Ajustes de Cookies
|
|
606
|
+
</button>
|
|
607
|
+
</div>
|
|
608
|
+
}
|
|
609
|
+
>
|
|
610
|
+
<iframe
|
|
611
|
+
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
612
|
+
width="560"
|
|
613
|
+
height="315"
|
|
614
|
+
/>
|
|
615
|
+
</ConsentGate>
|
|
130
616
|
</div>
|
|
131
617
|
);
|
|
132
618
|
}
|
|
133
619
|
```
|
|
134
620
|
|
|
135
|
-
### Next.js (App Router)
|
|
621
|
+
### Next.js (App Router & Pages)
|
|
622
|
+
|
|
623
|
+
Importa hooks y el componente `<ConsentGate>` directamente desde `@solvenza/cookies-compliance/next`:
|
|
136
624
|
|
|
137
625
|
```tsx
|
|
138
|
-
// app/
|
|
626
|
+
// app/components/VideoPlayer.tsx
|
|
627
|
+
"use client";
|
|
628
|
+
import { ConsentGate, useConsent } from "@solvenza/cookies-compliance/next";
|
|
629
|
+
|
|
630
|
+
export function VideoPlayer() {
|
|
631
|
+
return (
|
|
632
|
+
<ConsentGate
|
|
633
|
+
category="marketing"
|
|
634
|
+
fallback={({ openPreferences }) => (
|
|
635
|
+
<div className="banner-blocked">
|
|
636
|
+
<p>Vídeo bloqueado por privacidad.</p>
|
|
637
|
+
<button onClick={openPreferences}>
|
|
638
|
+
Aceptar cookies de marketing
|
|
639
|
+
</button>
|
|
640
|
+
</div>
|
|
641
|
+
)}
|
|
642
|
+
>
|
|
643
|
+
<iframe src="https://www.youtube.com/embed/..." />
|
|
644
|
+
</ConsentGate>
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
Configura el SDK en el `RootLayout` con `strategy="beforeInteractive"`:
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
// app/[locale]/layout.tsx
|
|
139
653
|
import Script from "next/script";
|
|
140
654
|
|
|
141
|
-
export default function RootLayout({
|
|
655
|
+
export default function RootLayout({
|
|
656
|
+
children,
|
|
657
|
+
params: { locale },
|
|
658
|
+
}: {
|
|
659
|
+
children: React.ReactNode;
|
|
660
|
+
params: { locale: string };
|
|
661
|
+
}) {
|
|
142
662
|
return (
|
|
143
|
-
<html lang=
|
|
663
|
+
<html lang={locale}>
|
|
144
664
|
<head>
|
|
665
|
+
{/* Sincronización automática con el atributo lang sin necesidad de boilerplate */}
|
|
145
666
|
<Script
|
|
146
667
|
src="/vendor/consent.min.js"
|
|
147
668
|
data-config="/consent.json"
|
|
@@ -156,9 +677,15 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
156
677
|
|
|
157
678
|
### Angular 20 Standalone
|
|
158
679
|
|
|
680
|
+
Carga la configuración con `provideAppInitializer` inyectando `ConsentService`. Puedes sincronizar el idioma en cualquier momento con `consentService.syncLocale(locale)` (ej. conectado a `@ngx-translate` o `Transloco`):
|
|
681
|
+
|
|
159
682
|
```typescript
|
|
160
683
|
// app.config.ts
|
|
161
|
-
import {
|
|
684
|
+
import {
|
|
685
|
+
ApplicationConfig,
|
|
686
|
+
provideAppInitializer,
|
|
687
|
+
inject,
|
|
688
|
+
} from "@angular/core";
|
|
162
689
|
import { ConsentService } from "@solvenza/cookies-compliance/angular";
|
|
163
690
|
|
|
164
691
|
export const appConfig: ApplicationConfig = {
|
|
@@ -166,12 +693,196 @@ export const appConfig: ApplicationConfig = {
|
|
|
166
693
|
ConsentService,
|
|
167
694
|
provideAppInitializer(async () => {
|
|
168
695
|
const consentService = inject(ConsentService);
|
|
169
|
-
await consentService.init(
|
|
696
|
+
await consentService.init({
|
|
697
|
+
schemaVersion: 1,
|
|
698
|
+
policyVersion: "2026-08-23",
|
|
699
|
+
locale: { default: "es", autoDetect: true },
|
|
700
|
+
policy: {
|
|
701
|
+
privacyUrl: "/politica-privacidad",
|
|
702
|
+
cookiesUrl: "/politica-cookies",
|
|
703
|
+
},
|
|
704
|
+
ui: {
|
|
705
|
+
floatingBadge: {
|
|
706
|
+
enabled: true,
|
|
707
|
+
position: "bottom-left",
|
|
708
|
+
icon: "cookie",
|
|
709
|
+
label: "Cookies",
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
categories: {
|
|
713
|
+
necessary: {
|
|
714
|
+
required: true,
|
|
715
|
+
label: "Necesarias",
|
|
716
|
+
description: "Imprescindibles.",
|
|
717
|
+
},
|
|
718
|
+
analytics: {
|
|
719
|
+
required: false,
|
|
720
|
+
label: "Analítica",
|
|
721
|
+
description: "Medición agregada.",
|
|
722
|
+
},
|
|
723
|
+
marketing: {
|
|
724
|
+
required: false,
|
|
725
|
+
label: "Marketing",
|
|
726
|
+
description: "Vídeo y contenido interactivo.",
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
});
|
|
170
730
|
}),
|
|
171
731
|
],
|
|
172
732
|
};
|
|
173
733
|
```
|
|
174
734
|
|
|
735
|
+
Uso de la directiva estructural `*consentGate` en componentes standalone de Angular:
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// video-player.component.ts
|
|
739
|
+
import { Component } from "@angular/core";
|
|
740
|
+
import {
|
|
741
|
+
ConsentGateDirective,
|
|
742
|
+
ConsentService,
|
|
743
|
+
} from "@solvenza/cookies-compliance/angular";
|
|
744
|
+
|
|
745
|
+
@Component({
|
|
746
|
+
selector: "app-video-player",
|
|
747
|
+
standalone: true,
|
|
748
|
+
imports: [ConsentGateDirective],
|
|
749
|
+
template: `
|
|
750
|
+
<!-- Renderizado condicional reactivo -->
|
|
751
|
+
<div *consentGate="'marketing'; else videoBlocked">
|
|
752
|
+
<iframe
|
|
753
|
+
src="https://www.youtube.com/embed/dQw4w9WgXcQ"
|
|
754
|
+
width="560"
|
|
755
|
+
height="315"
|
|
756
|
+
></iframe>
|
|
757
|
+
</div>
|
|
758
|
+
|
|
759
|
+
<ng-template #videoBlocked>
|
|
760
|
+
<div class="video-placeholder">
|
|
761
|
+
<p>Vídeo bloqueado. Requiere consentimiento de marketing.</p>
|
|
762
|
+
<button (click)="openCookies()">Configurar cookies</button>
|
|
763
|
+
</div>
|
|
764
|
+
</ng-template>
|
|
765
|
+
`,
|
|
766
|
+
})
|
|
767
|
+
export class VideoPlayerComponent {
|
|
768
|
+
constructor(private consentService: ConsentService) {}
|
|
769
|
+
|
|
770
|
+
openCookies() {
|
|
771
|
+
this.consentService.openPreferences();
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Vue 3 (Composition API & Componente ConsentGate)
|
|
777
|
+
|
|
778
|
+
Usa los composables reactivos y el componente declarativo `<ConsentGate>`:
|
|
779
|
+
|
|
780
|
+
```vue
|
|
781
|
+
<script setup lang="ts">
|
|
782
|
+
import {
|
|
783
|
+
useConsent,
|
|
784
|
+
useSyncConsentLocale,
|
|
785
|
+
ConsentGate,
|
|
786
|
+
} from "@solvenza/cookies-compliance/vue";
|
|
787
|
+
import { useI18n } from "vue-i18n";
|
|
788
|
+
|
|
789
|
+
const { locale } = useI18n();
|
|
790
|
+
// Sincronización automática de idioma con vue-i18n
|
|
791
|
+
useSyncConsentLocale(locale);
|
|
792
|
+
|
|
793
|
+
const isAnalyticsAllowed = useConsent("analytics");
|
|
794
|
+
</script>
|
|
795
|
+
|
|
796
|
+
<template>
|
|
797
|
+
<div>
|
|
798
|
+
<p>
|
|
799
|
+
Estado de analítica: {{ isAnalyticsAllowed ? "Permitida" : "Bloqueada" }}
|
|
800
|
+
</p>
|
|
801
|
+
|
|
802
|
+
<!-- Renderizado declarativo condicional -->
|
|
803
|
+
<ConsentGate category="marketing">
|
|
804
|
+
<template #default>
|
|
805
|
+
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
|
806
|
+
</template>
|
|
807
|
+
<template #fallback>
|
|
808
|
+
<div class="blocked-card">
|
|
809
|
+
<p>
|
|
810
|
+
Vídeo bloqueado por privacidad. Acepta cookies de Marketing para
|
|
811
|
+
reproducirlo.
|
|
812
|
+
</p>
|
|
813
|
+
</div>
|
|
814
|
+
</template>
|
|
815
|
+
</ConsentGate>
|
|
816
|
+
</div>
|
|
817
|
+
</template>
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
Plugin global en `main.ts`:
|
|
821
|
+
|
|
822
|
+
```ts
|
|
823
|
+
import { createApp } from "vue";
|
|
824
|
+
import { createConsentPlugin } from "@solvenza/cookies-compliance/vue";
|
|
825
|
+
import App from "./App.vue";
|
|
826
|
+
|
|
827
|
+
const app = createApp(App);
|
|
828
|
+
app.use(createConsentPlugin("/consent.json"));
|
|
829
|
+
app.mount("#app");
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
### Nuxt 3 (SSR & Universal)
|
|
833
|
+
|
|
834
|
+
Crea un plugin cliente en `plugins/consent.client.ts`:
|
|
835
|
+
|
|
836
|
+
```ts
|
|
837
|
+
// plugins/consent.client.ts
|
|
838
|
+
import { defineNuxtConsentPlugin } from "@solvenza/cookies-compliance/nuxt";
|
|
839
|
+
|
|
840
|
+
export default defineNuxtPlugin(defineNuxtConsentPlugin("/consent.json"));
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
Y usa los composables y `<ConsentGate>` directamente en cualquier página o componente:
|
|
844
|
+
|
|
845
|
+
```vue
|
|
846
|
+
<!-- pages/index.vue -->
|
|
847
|
+
<script setup lang="ts">
|
|
848
|
+
import { useConsent, ConsentGate } from "@solvenza/cookies-compliance/nuxt";
|
|
849
|
+
|
|
850
|
+
const isAnalyticsAllowed = useConsent("analytics");
|
|
851
|
+
</script>
|
|
852
|
+
|
|
853
|
+
<template>
|
|
854
|
+
<main>
|
|
855
|
+
<h1>Mi aplicación Nuxt 3</h1>
|
|
856
|
+
<ConsentGate category="marketing">
|
|
857
|
+
<template #default>
|
|
858
|
+
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ" />
|
|
859
|
+
</template>
|
|
860
|
+
<template #fallback>
|
|
861
|
+
<p>Vídeo bloqueado. Por favor, autoriza la categoría de marketing.</p>
|
|
862
|
+
</template>
|
|
863
|
+
</ConsentGate>
|
|
864
|
+
</main>
|
|
865
|
+
</template>
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
### WordPress
|
|
869
|
+
|
|
870
|
+
```php
|
|
871
|
+
// functions.php o plugin personalizado
|
|
872
|
+
function enqueue_solvenza_cookies() {
|
|
873
|
+
wp_enqueue_script(
|
|
874
|
+
"solvenza-cookies",
|
|
875
|
+
get_template_directory_uri() . "/vendor/consent.min.js",
|
|
876
|
+
array(),
|
|
877
|
+
"1.5.3",
|
|
878
|
+
false // En <head> para cumplir LSSI antes de scripts de analítica
|
|
879
|
+
);
|
|
880
|
+
// Asocia la configuración JSON con el badge flotante habilitado
|
|
881
|
+
wp_script_add_data("solvenza-cookies", "data-config", get_template_directory_uri() . "/consent.json");
|
|
882
|
+
}
|
|
883
|
+
add_action("wp_enqueue_scripts", "enqueue_solvenza_cookies");
|
|
884
|
+
```
|
|
885
|
+
|
|
175
886
|
---
|
|
176
887
|
|
|
177
888
|
## API JavaScript
|
|
@@ -201,22 +912,231 @@ const unsubscribe = Consent.on("consent:changed", ({ choices, receipt }) => {
|
|
|
201
912
|
console.log("Nuevo consentimiento:", choices);
|
|
202
913
|
});
|
|
203
914
|
|
|
204
|
-
// Generar
|
|
205
|
-
Consent.mountPolicy("#contenedor-politica");
|
|
915
|
+
// Generar documentos legales y tablas dinámicamente en Vanilla JS
|
|
916
|
+
Consent.mountPolicy("#contenedor-politica", { view: "full" });
|
|
917
|
+
Consent.mountLegalNotice("#contenedor-aviso-legal");
|
|
918
|
+
Consent.mountPrivacyPolicy("#contenedor-privacidad");
|
|
919
|
+
|
|
920
|
+
// O renderizar como string HTML para SSR
|
|
921
|
+
const htmlPolitica = Consent.renderPolicyHtml({ view: "full" });
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
---
|
|
925
|
+
|
|
926
|
+
## Componentes de Textos Legales y Tablas de Cookies
|
|
927
|
+
|
|
928
|
+
La librería incluye componentes declarativos y utilidades para generar automáticamente la **Política de Cookies**, el **Aviso Legal (LSSI-CE art. 10)** y la **Política de Privacidad (RGPD)** totalmente adaptados a la configuración y servicios declarados.
|
|
929
|
+
|
|
930
|
+
### 1. Configuración de datos del Titular (`legalEntity` y `legalNotice`)
|
|
931
|
+
|
|
932
|
+
En `consent.json`:
|
|
933
|
+
|
|
934
|
+
```json
|
|
935
|
+
{
|
|
936
|
+
"schemaVersion": 1,
|
|
937
|
+
"policyVersion": "2026-08-23",
|
|
938
|
+
"legalEntity": {
|
|
939
|
+
"name": "Mi Empresa S.L.",
|
|
940
|
+
"tradeName": "Mi Marca",
|
|
941
|
+
"taxId": "B-12345678",
|
|
942
|
+
"address": "Calle Principal 123, 28001 Madrid, España",
|
|
943
|
+
"email": "privacidad@mi-sitio.com",
|
|
944
|
+
"phone": "+34 910 000 000",
|
|
945
|
+
"registryData": "Registro Mercantil de Madrid, Tomo 123, Folio 45",
|
|
946
|
+
"dpoEmail": "dpo@mi-sitio.com"
|
|
947
|
+
},
|
|
948
|
+
"legalNotice": {
|
|
949
|
+
"applicableLaw": "Legislación española (LSSI-CE, LOPDGDD) y RGPD",
|
|
950
|
+
"jurisdiction": "Juzgados y Tribunales de Madrid"
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
```
|
|
954
|
+
|
|
955
|
+
O con `ConsentConfigBuilder`:
|
|
956
|
+
|
|
957
|
+
```typescript
|
|
958
|
+
const config = new ConsentConfigBuilder("2026-08-23")
|
|
959
|
+
.setLegalEntity({
|
|
960
|
+
name: "Mi Empresa S.L.",
|
|
961
|
+
tradeName: "Mi Marca",
|
|
962
|
+
taxId: "B-12345678",
|
|
963
|
+
address: "Calle Principal 123, Madrid",
|
|
964
|
+
email: "privacidad@mi-sitio.com",
|
|
965
|
+
})
|
|
966
|
+
.setLegalNotice({
|
|
967
|
+
jurisdiction: "Juzgados y Tribunales de Madrid",
|
|
968
|
+
})
|
|
969
|
+
.build();
|
|
206
970
|
```
|
|
207
971
|
|
|
208
972
|
---
|
|
209
973
|
|
|
210
|
-
|
|
974
|
+
### 2. Uso en React & Next.js
|
|
975
|
+
|
|
976
|
+
```tsx
|
|
977
|
+
import {
|
|
978
|
+
CookiePolicy,
|
|
979
|
+
LegalNotice,
|
|
980
|
+
PrivacyPolicy,
|
|
981
|
+
} from "@solvenza/cookies-compliance/react";
|
|
982
|
+
// O desde "@solvenza/cookies-compliance/next"
|
|
983
|
+
|
|
984
|
+
export function CookiePolicyPage() {
|
|
985
|
+
return (
|
|
986
|
+
<div>
|
|
987
|
+
{/* 1. Documento completo de Política de Cookies (con botones AEPD y guías de navegador) */}
|
|
988
|
+
<CookiePolicy view="full" />
|
|
211
989
|
|
|
212
|
-
|
|
990
|
+
{/* 2. O solo la tabla interactiva de cookies registradas */}
|
|
991
|
+
<CookiePolicy view="table-only" />
|
|
992
|
+
|
|
993
|
+
{/* 3. Aviso Legal LSSI-CE */}
|
|
994
|
+
<LegalNotice />
|
|
995
|
+
|
|
996
|
+
{/* 4. Política de Privacidad RGPD */}
|
|
997
|
+
<PrivacyPolicy />
|
|
998
|
+
</div>
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
### 3. Uso en Vue 3 & Nuxt 3
|
|
1006
|
+
|
|
1007
|
+
```vue
|
|
1008
|
+
<script setup lang="ts">
|
|
1009
|
+
import {
|
|
1010
|
+
CookiePolicy,
|
|
1011
|
+
LegalNotice,
|
|
1012
|
+
PrivacyPolicy,
|
|
1013
|
+
} from "@solvenza/cookies-compliance/vue";
|
|
1014
|
+
// O desde "@solvenza/cookies-compliance/nuxt"
|
|
1015
|
+
</script>
|
|
1016
|
+
|
|
1017
|
+
<template>
|
|
1018
|
+
<main>
|
|
1019
|
+
<!-- Documento completo con revocación AEPD interactiva -->
|
|
1020
|
+
<CookiePolicy view="full" />
|
|
1021
|
+
|
|
1022
|
+
<!-- Solo inventario de cookies -->
|
|
1023
|
+
<CookiePolicy view="table-only" />
|
|
1024
|
+
|
|
1025
|
+
<!-- Aviso Legal LSSI-CE -->
|
|
1026
|
+
<LegalNotice />
|
|
1027
|
+
</main>
|
|
1028
|
+
</template>
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
---
|
|
1032
|
+
|
|
1033
|
+
### 4. Uso en Angular 17+ / 20
|
|
1034
|
+
|
|
1035
|
+
```html
|
|
1036
|
+
<!-- app.component.html -->
|
|
1037
|
+
<solvenza-cookie-policy [view]="'full'"></solvenza-cookie-policy>
|
|
1038
|
+
<solvenza-cookie-policy [view]="'table-only'"></solvenza-cookie-policy>
|
|
1039
|
+
<solvenza-legal-notice></solvenza-legal-notice>
|
|
1040
|
+
<solvenza-privacy-policy></solvenza-privacy-policy>
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
---
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
## GitHub Action de Auditoría LSSI & AEPD
|
|
1048
|
+
|
|
1049
|
+
Automatiza la validación continua de cumplimiento legal (LSSI art. 22.2, directrices AEPD 2024 y RGPD) en tus flujos de CI/CD de GitHub Actions antes de desplegar a producción.
|
|
1050
|
+
|
|
1051
|
+
### Uso rápido en `.github/workflows/lssi-audit.yml`:
|
|
1052
|
+
|
|
1053
|
+
```yaml
|
|
1054
|
+
name: "Auditoría LSSI & AEPD"
|
|
1055
|
+
|
|
1056
|
+
on:
|
|
1057
|
+
push:
|
|
1058
|
+
branches: [main, develop]
|
|
1059
|
+
pull_request:
|
|
1060
|
+
branches: [main]
|
|
1061
|
+
|
|
1062
|
+
jobs:
|
|
1063
|
+
lssi-compliance:
|
|
1064
|
+
name: "Verificación de Cumplimiento LSSI"
|
|
1065
|
+
runs-on: ubuntu-latest
|
|
1066
|
+
steps:
|
|
1067
|
+
- name: Checkout del código
|
|
1068
|
+
uses: actions/checkout@v4
|
|
1069
|
+
|
|
1070
|
+
- name: Ejecutar Auditoría LSSI & AEPD
|
|
1071
|
+
uses: solvenza/cookies-compliance@v1
|
|
1072
|
+
with:
|
|
1073
|
+
config-path: "./consent.json"
|
|
1074
|
+
src-dir: "./src"
|
|
1075
|
+
fail-on-error: "true"
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
### Entradas (Inputs) de la GitHub Action:
|
|
1079
|
+
|
|
1080
|
+
| Input | Descripción | Valor por defecto |
|
|
1081
|
+
| --------------- | ------------------------------------------------------------------------------------- | ----------------- |
|
|
1082
|
+
| `config-path` | Ruta relativa al archivo `consent.json` | `./consent.json` |
|
|
1083
|
+
| `src-dir` | Directorio con archivos fuente a escanear (`.html`, `.jsx`, `.tsx`, `.vue`, etc.) | `./` |
|
|
1084
|
+
| `url` | URL en vivo a auditar (ej: `https://staging.mi-sitio.com`) | _(Opcional)_ |
|
|
1085
|
+
| `fail-on-error` | Bloquear el pipeline con código de salida no nulo si se detectan violaciones críticas | `true` |
|
|
1086
|
+
|
|
1087
|
+
### Resumen automático en el Step Summary de GitHub:
|
|
1088
|
+
|
|
1089
|
+
La GitHub Action genera anotaciones directas en las líneas de código (`::error::` / `::warning::`) y añade una tabla resumen visual en el **Job Step Summary** con la puntuación de 0 a 100, estado de cumplimiento y recomendaciones de subsanación.
|
|
1090
|
+
|
|
1091
|
+
---
|
|
1092
|
+
|
|
1093
|
+
## Herramienta CLI de auditoría LSSI
|
|
1094
|
+
|
|
1095
|
+
Puedes ejecutar el motor de auditoría de forma manual o en tus scripts de `package.json`:
|
|
213
1096
|
|
|
214
1097
|
```bash
|
|
215
|
-
|
|
1098
|
+
# Auditar configuración y código fuente local
|
|
1099
|
+
npx @solvenza/cookies-compliance consent-audit --config ./consent.json --src ./src --fail-on-error
|
|
1100
|
+
|
|
1101
|
+
# Auditar una URL remota en vivo
|
|
1102
|
+
npx @solvenza/cookies-compliance consent-audit --url https://mi-sitio.com
|
|
1103
|
+
|
|
1104
|
+
# Generar reporte estructurado en formato JSON
|
|
1105
|
+
npx @solvenza/cookies-compliance consent-audit --config ./consent.json --src ./src --json
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
### Uso programático en Node.js / TypeScript:
|
|
1109
|
+
|
|
1110
|
+
```typescript
|
|
1111
|
+
import { LssiAuditor } from "@solvenza/cookies-compliance/audit";
|
|
1112
|
+
|
|
1113
|
+
const report = await LssiAuditor.run({
|
|
1114
|
+
configPath: "./consent.json",
|
|
1115
|
+
srcDir: "./src",
|
|
1116
|
+
url: "https://staging.mi-sitio.com",
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
console.log(`Puntuación: ${report.score}/100 - Cumple: ${report.compliant}`);
|
|
216
1120
|
```
|
|
217
1121
|
|
|
218
1122
|
---
|
|
219
1123
|
|
|
1124
|
+
## Ejemplos de integración por framework
|
|
1125
|
+
|
|
1126
|
+
El repositorio incluye ejemplos interactivos listos para ejecutar:
|
|
1127
|
+
|
|
1128
|
+
| Directorio | Framework / Stack | Características clave |
|
|
1129
|
+
| --------------------------------------------------- | ---------------------- | --------------------------------------------------------------------------------------- |
|
|
1130
|
+
| [`examples/vanilla-html/`](./examples/vanilla-html) | Vanilla HTML5 / JS | Inclusión vía `<script data-config="...">`, iframe gate nativo y CSS personalizado |
|
|
1131
|
+
| [`examples/react-app/`](./examples/react-app) | React 18+ & Vite | `useConsent`, `useSyncConsentLocale`, `<ConsentGate>` y tabla de política dinámica |
|
|
1132
|
+
| [`examples/next-app/`](./examples/next-app) | Next.js (App Router) | Script con `strategy="beforeInteractive"`, componentes de cliente y SSR |
|
|
1133
|
+
| [`examples/vue-app/`](./examples/vue-app) | Vue 3 & Vite | Composables reactivos `useConsent`, `useGpc`, `useSyncConsentLocale` y `<ConsentGate>` |
|
|
1134
|
+
| [`examples/nuxt-app/`](./examples/nuxt-app) | Nuxt 3 | Módulo / plugin SSR universal `@solvenza/cookies-compliance/nuxt` |
|
|
1135
|
+
| [`examples/angular-app/`](./examples/angular-app) | Angular 17+ Standalone | `ConsentService`, directiva estructural `*consentGate` y `provideAppInitializer` |
|
|
1136
|
+
| [`playground/`](./playground) | Sandbox Interactivo | Panel de pruebas con simulador de presets, purga de Web Storage e inspección de recibos |
|
|
1137
|
+
|
|
1138
|
+
---
|
|
1139
|
+
|
|
220
1140
|
## Entorno de desarrollo local
|
|
221
1141
|
|
|
222
1142
|
1. Clona el repositorio:
|
|
@@ -237,6 +1157,10 @@ npx @solvenza/cookies-compliance consent-audit https://mi-sitio.com
|
|
|
237
1157
|
```bash
|
|
238
1158
|
npm run playground
|
|
239
1159
|
```
|
|
1160
|
+
5. Inicia el ejemplo de Vue 3:
|
|
1161
|
+
```bash
|
|
1162
|
+
cd examples/vue-app && npm install && npm run dev
|
|
1163
|
+
```
|
|
240
1164
|
|
|
241
1165
|
---
|
|
242
1166
|
|