webs-sdk 0.19.62 → 0.19.64

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 (52) hide show
  1. package/README.md +490 -451
  2. package/dist/components/ARFilters/ARFilters.js +54 -54
  3. package/dist/components/ArtGenerator.js +141 -141
  4. package/dist/components/AvatarAI.js +14 -14
  5. package/dist/components/CookieConsent/constants.js +31 -31
  6. package/dist/components/CreativeFaceSwap.js +23 -23
  7. package/dist/components/GifsIa.js +76 -76
  8. package/dist/components/MemeGenerator.js +12 -12
  9. package/dist/components/PasswordReset/constants.js +33 -33
  10. package/dist/components/RecoveryPassword/constants.js +27 -27
  11. package/dist/components/SpinningWheel/SpinningWheel.js +7 -7
  12. package/dist/config.d.ts +3 -0
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +3 -0
  15. package/dist/config.js.map +1 -1
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/libraries/auth.d.ts.map +1 -1
  21. package/dist/libraries/auth.js +2 -2
  22. package/dist/libraries/auth.js.map +1 -1
  23. package/dist/libraries/clarity.d.ts +23 -0
  24. package/dist/libraries/clarity.d.ts.map +1 -0
  25. package/dist/libraries/clarity.js +110 -0
  26. package/dist/libraries/clarity.js.map +1 -0
  27. package/dist/libraries/index.d.ts +1 -0
  28. package/dist/libraries/index.d.ts.map +1 -1
  29. package/dist/libraries/index.js +3 -1
  30. package/dist/libraries/index.js.map +1 -1
  31. package/dist/libraries/networking.d.ts +4 -0
  32. package/dist/libraries/networking.d.ts.map +1 -1
  33. package/dist/libraries/networking.js +42 -6
  34. package/dist/libraries/networking.js.map +1 -1
  35. package/dist/libraries/session.d.ts.map +1 -1
  36. package/dist/libraries/session.js +2 -0
  37. package/dist/libraries/session.js.map +1 -1
  38. package/package.json +84 -83
  39. package/public/spin2win/assets/border-afristream.svg +26 -26
  40. package/public/spin2win/assets/border-efc.svg +23 -23
  41. package/public/spin2win/assets/border-streetreel.svg +23 -23
  42. package/public/spin2win/assets/border-totalgym.svg +27 -27
  43. package/public/spin2win/assets/border-womantoday.svg +26 -26
  44. package/public/spin2win/assets/indicator-afristream.svg +28 -28
  45. package/public/spin2win/assets/indicator-efc.svg +29 -29
  46. package/public/spin2win/assets/indicator-streetreel.svg +29 -29
  47. package/public/spin2win/assets/indicator-totalgym.svg +29 -29
  48. package/public/spin2win/fonts/switzer/README.md +22 -22
  49. package/dist/libraries/globals.d.ts +0 -17
  50. package/dist/libraries/globals.d.ts.map +0 -1
  51. package/dist/libraries/globals.js +0 -38
  52. package/dist/libraries/globals.js.map +0 -1
package/README.md CHANGED
@@ -1,451 +1,490 @@
1
- # webs-sdk
2
-
3
- SDK interno npm para portales Nexum. Encapsula toda la lógica común: autenticación, sesiones, comunicación con la API, tracking, internacionalización y componentes React reutilizables.
4
-
5
- **Versión actual:** 0.18.9
6
-
7
- ## Instalación
8
-
9
- ```bash
10
- npm install webs-sdk
11
- ```
12
-
13
- ## Uso básico
14
-
15
- ```typescript
16
- import WebsSDK from 'webs-sdk';
17
-
18
- // Inicializar sesión
19
- await WebsSDK.Session.init();
20
-
21
- // Petición HTTP
22
- const data = await WebsSDK.Networking.request('/api/endpoint', { param: 'value' });
23
-
24
- // Almacenamiento local
25
- WebsSDK.Storage.set('key', 'value');
26
- const value = WebsSDK.Storage.get('key');
27
- ```
28
-
29
- ## Librerías disponibles
30
-
31
- ### Networking
32
-
33
- ```typescript
34
- // Petición HTTP (inyecta sessionData automáticamente)
35
- await WebsSDK.Networking.request(url, data);
36
-
37
- // Eventos de tracking
38
- await WebsSDK.Networking.sendEvent('action', 'button_click', { button_id: 'submit' });
39
-
40
- // Suscripciones
41
- await WebsSDK.Networking.createSubscription(subscriptionData);
42
- const { success, subscription_active } = await WebsSDK.Networking.checkSubscription();
43
- ```
44
-
45
- ### Storage
46
-
47
- ```typescript
48
- WebsSDK.Storage.set('key', 'value');
49
- const value = WebsSDK.Storage.get('key');
50
-
51
- // Compresión de imágenes (0.8 ratio, 1024px max)
52
- const compressed = await WebsSDK.Storage.compressImage(imageFile, 2); // 2MB max
53
-
54
- // Creaciones (imágenes generadas por el usuario)
55
- await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'id', metadata);
56
- const creations = await WebsSDK.Storage.getCreations();
57
- await WebsSDK.Storage.deleteCreation('id');
58
- ```
59
-
60
- ### Session
61
-
62
- ```typescript
63
- await WebsSDK.Session.init();
64
-
65
- const sessionData = WebsSDK.Session.getSessionData();
66
- const userId = WebsSDK.Session.getUserID();
67
- const lang = WebsSDK.Session.getDeviceLanguage();
68
- const isSubscribed = WebsSDK.Session.getIsSubscribed();
69
- ```
70
-
71
- ### Utils
72
-
73
- ```typescript
74
- const id = WebsSDK.Utils.generateId();
75
- const isValidEmail = WebsSDK.Utils.isValidEmail('user@example.com');
76
- const isMobile = WebsSDK.Utils.isMobile();
77
- const deviceInfo = WebsSDK.Utils.getDeviceInfo();
78
- const truncated = WebsSDK.Utils.truncateText('Long text...', 10);
79
- ```
80
-
81
- ### MixPanel
82
-
83
- ```typescript
84
- await WebsSDK.MixPanel.initialize(token, false, false, false);
85
- await WebsSDK.MixPanel.trackEvent('page_view', { page: 'home' });
86
- await WebsSDK.MixPanel.identifyUser('user_123');
87
- await WebsSDK.MixPanel.setUserProperties({ plan: 'premium' });
88
- ```
89
-
90
- ### AuthManager
91
-
92
- ```typescript
93
- // Login estándar
94
- await WebsSDK.AuthManager.authUser({ login, password, website_id });
95
-
96
- // Login sin password (MSISDN)
97
- await WebsSDK.AuthManager.authOnlyUser({ login, website_id });
98
-
99
- // Login con OTP
100
- await WebsSDK.AuthManager.authOnlyUserOTP({ login, website_id, pincode });
101
-
102
- // Registro
103
- await WebsSDK.AuthManager.createUser({ login, password, website_id });
104
-
105
- // Auto-login post-pago (usado en el proxy de next_template)
106
- await WebsSDK.AuthManager.auto_login(cfg_sessionid, websiteData, requestUrl);
107
-
108
- // Baja/cancelación
109
- await WebsSDK.AuthManager.unsubscribeUser({ user_id, callback_url?, org? });
110
-
111
- // Gestión de perfil
112
- await WebsSDK.AuthManager.setUserMetadata(params);
113
- await WebsSDK.AuthManager.uploadProfilePhoto(params); // JPG/PNG/WEBP, max 5MB
114
- await WebsSDK.AuthManager.changePassword(params);
115
- ```
116
-
117
- ### Andromeda — Portales
118
-
119
- ```typescript
120
- // Metadata del portal (optimizado con cookie websiteDataFlag)
121
- await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('portal.com');
122
-
123
- // Configuración del producto (langs, login_type, logo, favicon, theme)
124
- await WebsSDK.Andromeda.getWebsiteConfig('portal-corp-web');
125
- ```
126
-
127
- ### ContentManager
128
-
129
- ```typescript
130
- // Contenido por preset
131
- await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'es' });
132
-
133
- // Colección filtrada por tags
134
- await WebsSDK.ContentManager.getCollection(['tag1', 'tag2'], 'es', false);
135
- ```
136
-
137
- ### User
138
-
139
- ```typescript
140
- await WebsSDK.User.checkSession(token); // Valida token (usado en middleware)
141
- await WebsSDK.User.getUserData(user_id, website_id);
142
- await WebsSDK.User.getCredits(user_id, website_id);
143
- ```
144
-
145
- ### CorporateAuthManager
146
-
147
- ```typescript
148
- // Server-side (middleware Next.js)
149
- const result = await WebsSDK.CorporateAuthManager.validateCorporateAuthServerSide(request);
150
- if (!result.authenticated) return result.redirectResponse;
151
-
152
- // Client-side
153
- await WebsSDK.CorporateAuthManager.checkCorporateLogin();
154
- await WebsSDK.CorporateAuthManager.validateUser(autoRedirect?);
155
- await WebsSDK.CorporateAuthManager.login(returnTo?);
156
- await WebsSDK.CorporateAuthManager.logout(redirectTo?);
157
- await WebsSDK.CorporateAuthManager.isAdmin(userData?);
158
- ```
159
-
160
- ### Legal
161
-
162
- ```typescript
163
- await WebsSDK.Legal.getLegalText('tos' | 'privacy' | 'faq', web_id, lang);
164
- ```
165
-
166
- ### Namespace `pre` — Pre-producción
167
-
168
- ```typescript
169
- // Lazy-loaded, apunta a bc1742-pre.gways.org
170
- await WebsSDK.pre.AuthManager.authUser(params);
171
- await WebsSDK.pre.Andromeda.getWebsiteMetadatabyHostname(hostname);
172
- ```
173
-
174
- ---
175
-
176
- ## Componentes React
177
-
178
- ### CookieConsent — Banner GDPR
179
-
180
- ```typescript
181
- import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
182
- import type { CookieConsentProps, CookieConsentTheme, CookieConsentPreferences } from 'webs-sdk';
183
-
184
- <CookieConsent
185
- locale="es" // en | es | fr | nl | ar | de | fi | ro | tr
186
- privacyPolicyUrl="/privacy"
187
- theme={{
188
- backgroundColor: '#1a1a2e',
189
- accentColor: '#6c63ff', // aplica a botón Accept, borde Reject, links y glow
190
- acceptButtonBg: 'linear-gradient(90deg, #6c63ff, #a855f7)', // soporta gradients CSS
191
- textColor: '#ffffff',
192
- }}
193
- onAccept={() => updateGAConsent('granted')}
194
- onReject={() => updateGAConsent('denied')}
195
- onSaveSettings={(prefs: CookieConsentPreferences) => handlePrefs(prefs)}
196
- />
197
- ```
198
-
199
- **Storage:**
200
- - Cookie `cookieConsent`: `accepted | rejected | custom` (1 año)
201
- - `localStorage.TRACKING_PERMISSION`: `granted | denied`
202
- - `localStorage.FUNCTIONAL_PERMISSION`: `granted | denied`
203
-
204
- ### Google Analytics — GA4
205
-
206
- ```typescript
207
- import { initGoogleAnalytics, updateGAConsent, trackGAEvent, trackGAPageView, isGALoaded } from 'webs-sdk';
208
-
209
- // Registra el GA ID. Carga el script SOLO si hay consentimiento previo.
210
- await initGoogleAnalytics('G-XXXXXXXXXX', { sendPageView: true, debug: false });
211
-
212
- // Llamar tras decisión del usuario en CookieConsent
213
- updateGAConsent('granted'); // carga GA si había pendingTrackingId
214
- updateGAConsent('denied'); // GA nunca se carga
215
-
216
- // Tracking
217
- trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
218
- trackGAPageView('/home', 'Inicio');
219
- isGALoaded(); // boolean
220
- ```
221
-
222
- > **GDPR estricto:** GA no usa Consent Mode v2. Si no hay consentimiento, el script `gtag.js` nunca se inyecta en el DOM.
223
-
224
- ### CDN Proxy
225
-
226
- ```typescript
227
- import { handleCDNRequest } from 'webs-sdk';
228
- import type { CDNOptions } from 'webs-sdk';
229
-
230
- // Usar en un route handler de Next.js (compatible con Edge Runtime)
231
- async function handleCDNRequest(
232
- pathname: string, // '/cdn/apariencias/33702/image.png'
233
- options?: {
234
- cdnBaseUrl?: string; // Default: 'https://dy822md8ge77v.cloudfront.net'
235
- cacheSeconds?: number; // Default: 300 (5 min)
236
- }
237
- ): Promise<Response | null> // null si pathname no empieza por /cdn/
238
- ```
239
-
240
- ### Componentes "For You" — Creación de contenido AI
241
-
242
- ```typescript
243
- import {
244
- AvatarAI, AvatarAIPage, AvatarAIForYouCard,
245
- CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
246
- MemeGenerator, MemeGeneratorPage, MemeGeneratorForYouCard,
247
- WallpapersName, WallpapersNamePage, WallpapersNameForYouCard,
248
- Wallpapers,
249
- Ringtone,
250
- } from 'webs-sdk';
251
- ```
252
-
253
- Todos aceptan `websiteId?: string` para tracking multi-portal.
254
-
255
- ### ARFilters — Filtros AR con DeepAR
256
-
257
- ```typescript
258
- import { ARFilters, ARFiltersForYouCard } from 'webs-sdk';
259
- import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';
260
-
261
- <ARFilters
262
- licenseKey="deepar-license-key"
263
- websiteId="portal-web-id"
264
- apiBaseUrl="https://api.fluver-ai.com"
265
- downloadProxyUrl="/cdn" // Proxy CORS para descarga de efectos
266
- filterTag="ar-filters-tag" // Obtiene filtros del CMS por tag
267
- // O bien: filters={[{ file: 'beauty', thumbUrl: '/thumb.jpg' }]}
268
- locale="es"
269
- texts={getARFiltersTexts('es')} // requerido
270
- onPhotoTaken={(url) => share(url)}
271
- onVideoRecorded={(url) => share(url)}
272
- />
273
- ```
274
-
275
- Soporta fullscreen en mobile (vendor-prefixed API para iOS Safari).
276
-
277
- ### SpinningWheel — Ruleta
278
-
279
- ```typescript
280
- import { SpinningWheel, wheelThemes } from 'webs-sdk';
281
-
282
- // Temas: vodacom | totalgym | womantoday | afristream | efc
283
- <SpinningWheel
284
- userId={userId}
285
- websiteId={websiteId}
286
- theme="vodacom"
287
- onResult={(segment) => handleWin(segment)}
288
- canSpin={true}
289
- alreadySpunToday={false}
290
- />
291
- ```
292
-
293
- ### Quiz y Stickers
294
-
295
- ```typescript
296
- import { Quiz, Stickers } from 'webs-sdk';
297
- ```
298
-
299
- ### Esports — Noticias, Vídeos y Partidas en Directo
300
-
301
- ```typescript
302
- import { EsportsNews, EsportsVideos, EsportsLive } from 'webs-sdk';
303
- ```
304
-
305
- #### EsportsNews
306
-
307
- ```typescript
308
- <EsportsNews
309
- config={{
310
- key: 'api-key',
311
- contents: [{ preset: 'esports_news', apiProviderId: 'id', category: [], tags: [], limit: 10 }]
312
- }}
313
- language="es"
314
- onNewsClick={(item) => router.push(`/news/${item.id}`)}
315
- theme={theme} // CSS classes para container, cards, badges, etc.
316
- categoryStyles={{ gaming: { badge: 'bg-blue-500', badgeText: 'text-white' } }}
317
- // Modo detalle (renderiza EsportsNewsDetail):
318
- newsId="article-id"
319
- newsData={item} // opcional si ya tienes los datos
320
- onBack={() => router.back()}
321
- />
322
- ```
323
-
324
- #### EsportsVideos
325
-
326
- ```typescript
327
- <EsportsVideos
328
- config={{ key: 'api-key', contents: [...] }}
329
- language="es"
330
- onVideoClick={(item) => openPlayer(item.video_url)}
331
- theme={theme} // incluye clases para duration overlay
332
- />
333
- ```
334
-
335
- #### EsportsLive
336
-
337
- ```typescript
338
- <EsportsLive
339
- language="es"
340
- timezone="Europe/Madrid"
341
- onLiveClick={(item) => openStream(item.streamUrl)}
342
- onMatchClick={(match) => openMatchDetail(match)}
343
- onGameClick={(gameId) => filterByGame(gameId)}
344
- website_id="portal-web-id"
345
- theme={theme}
346
- // Modo detalle de partida:
347
- matchId="serie-id"
348
- matchData={calendarMatch}
349
- onBack={() => setMatchId(null)}
350
- />
351
- ```
352
-
353
- ---
354
-
355
- ## Utilidades de validación de vídeo
356
-
357
- ```typescript
358
- import { validateVideoUrl, validateVideoBatch, clearValidationCache } from 'webs-sdk';
359
- import { useValidateVideoOnMount, useLazyVideoValidation } from 'webs-sdk';
360
-
361
- // Función pura (sin React)
362
- const state = await validateVideoUrl('https://stream.example.com/live.m3u8');
363
- // 'valid' | 'invalid' | 'unknown'
364
-
365
- // Batch con concurrencia 3
366
- const results = await validateVideoBatch([url1, url2, url3]);
367
-
368
- // React: validar al montar (evita renderizar streams rotos)
369
- const { isValid, isInvalid, isValidating } = useValidateVideoOnMount(videoUrl);
370
-
371
- // React: validación manual
372
- const { validate, isValid, isInvalid } = useLazyVideoValidation();
373
- await validate(videoUrl);
374
- ```
375
-
376
- Soporta HLS, DASH, MP4. Cache en memoria con TTL de 5 minutos.
377
-
378
- ---
379
-
380
- ## Estructura del proyecto
381
-
382
- ```
383
- src/
384
- ├── config.ts # Configuración central (URLs, endpoints, constantes)
385
- ├── index.ts # Barrel export principal
386
- ├── libraries/
387
- │ ├── networking.ts # HTTP + events
388
- │ ├── auth.ts # Autenticación, login, baja
389
- │ ├── session.ts # Sesiones y dispositivo
390
- │ ├── storage.ts # LocalStorage + compresión de imágenes
391
- │ ├── utils.ts # Utilidades generales
392
- │ ├── andromeda.ts # Metadata y config de portales
393
- │ ├── corp_auth.ts # Auth corporativa (JWT)
394
- │ ├── user.ts # Datos de usuario
395
- │ ├── mixpanel.ts # Analytics
396
- │ ├── audio.ts # Text-to-speech
397
- │ ├── calypso.ts # Event tracking interno
398
- │ ├── legal.ts # Textos legales
399
- │ ├── content.ts # Contenido dinámico + colecciones por tags
400
- │ ├── cdn.ts # CDN Proxy (Edge Runtime compatible)
401
- │ └── googleAnalytics.ts # GA4 con carga diferida por consentimiento
402
- ├── utils/
403
- │ ├── videoValidation.ts # Validación de URLs de streaming
404
- │ ├── useVideoValidation.ts # React hooks para validación
405
- │ └── timeUtils.ts # getTimeAgo — tiempo relativo i18n
406
- └── components/
407
- ├── AvatarAI.tsx
408
- ├── CreativeFaceSwap.tsx
409
- ├── Wallpapers.tsx
410
- ├── WallpapersName.tsx
411
- ├── Ringtone.tsx
412
- ├── MemeGenerator.tsx
413
- ├── Quiz.tsx
414
- ├── Stickers.tsx
415
- ├── VideoPlayer.tsx
416
- ├── ARFilters/
417
- ├── SpinningWheel/
418
- ├── CookieConsent/
419
- └── esports/
420
- ├── news/
421
- ├── videos/
422
- └── live/
423
- ```
424
-
425
- ## Desarrollo
426
-
427
- ```bash
428
- npm install
429
- npm run build # tsc → dist/
430
- npm run build:watch # watch mode
431
- npm run lint
432
- ```
433
-
434
- ## Publicación
435
-
436
- ```bash
437
- # Incluir [ROAD-361723] en el commit para auto-publish en CI
438
- git commit -m "feat: descripción [ROAD-361723]"
439
- git push
440
- ```
441
-
442
- Tras publicar, actualizar en los portales:
443
- ```bash
444
- npm install webs-sdk@<nueva-version>
445
- ```
446
-
447
- **Nota importante — `index.js` raíz:** es el barrel CommonJS mantenido manualmente. Cada export nuevo en `src/index.ts` debe añadirse también en `index.js`:
448
- ```js
449
- const { nuevaFuncion } = require('./dist/index.js');
450
- module.exports.nuevaFuncion = nuevaFuncion;
451
- ```
1
+ # webs-sdk
2
+
3
+ SDK interno npm para portales Nexum. Encapsula toda la lógica común: autenticación, sesiones, comunicación con la API, tracking, internacionalización y componentes React reutilizables.
4
+
5
+ **Versión actual:** 0.18.9
6
+
7
+ ## Instalación
8
+
9
+ ```bash
10
+ npm install webs-sdk
11
+ ```
12
+
13
+ ## Uso básico
14
+
15
+ ```typescript
16
+ import WebsSDK from 'webs-sdk';
17
+
18
+ // Inicializar sesión
19
+ await WebsSDK.Session.init();
20
+
21
+ // Petición HTTP
22
+ const data = await WebsSDK.Networking.request('/api/endpoint', { param: 'value' });
23
+
24
+ // Almacenamiento local
25
+ WebsSDK.Storage.set('key', 'value');
26
+ const value = WebsSDK.Storage.get('key');
27
+ ```
28
+
29
+ ## Librerías disponibles
30
+
31
+ ### Networking
32
+
33
+ ```typescript
34
+ // Petición HTTP (inyecta sessionData automáticamente)
35
+ await WebsSDK.Networking.request(url, data);
36
+
37
+ // Eventos de tracking
38
+ await WebsSDK.Networking.sendEvent('action', 'button_click', { button_id: 'submit' });
39
+
40
+ // Suscripciones
41
+ await WebsSDK.Networking.createSubscription(subscriptionData);
42
+ const { success, subscription_active } = await WebsSDK.Networking.checkSubscription();
43
+ ```
44
+
45
+ ### Storage
46
+
47
+ ```typescript
48
+ WebsSDK.Storage.set('key', 'value');
49
+ const value = WebsSDK.Storage.get('key');
50
+
51
+ // Compresión de imágenes (0.8 ratio, 1024px max)
52
+ const compressed = await WebsSDK.Storage.compressImage(imageFile, 2); // 2MB max
53
+
54
+ // Creaciones (imágenes generadas por el usuario)
55
+ await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'id', metadata);
56
+ const creations = await WebsSDK.Storage.getCreations();
57
+ await WebsSDK.Storage.deleteCreation('id');
58
+ ```
59
+
60
+ ### Session
61
+
62
+ ```typescript
63
+ await WebsSDK.Session.init();
64
+
65
+ const sessionData = WebsSDK.Session.getSessionData();
66
+ const userId = WebsSDK.Session.getUserID();
67
+ const lang = WebsSDK.Session.getDeviceLanguage();
68
+ const isSubscribed = WebsSDK.Session.getIsSubscribed();
69
+ ```
70
+
71
+ ### Utils
72
+
73
+ ```typescript
74
+ const id = WebsSDK.Utils.generateId();
75
+ const isValidEmail = WebsSDK.Utils.isValidEmail('user@example.com');
76
+ const isMobile = WebsSDK.Utils.isMobile();
77
+ const deviceInfo = WebsSDK.Utils.getDeviceInfo();
78
+ const truncated = WebsSDK.Utils.truncateText('Long text...', 10);
79
+ ```
80
+
81
+ ### MixPanel
82
+
83
+ ```typescript
84
+ await WebsSDK.MixPanel.initialize(token, false, false, false);
85
+ await WebsSDK.MixPanel.trackEvent('page_view', { page: 'home' });
86
+ await WebsSDK.MixPanel.identifyUser('user_123');
87
+ await WebsSDK.MixPanel.setUserProperties({ plan: 'premium' });
88
+ ```
89
+
90
+ ### Clarity — Microsoft Clarity
91
+
92
+ Heatmaps y session recordings. El `projectId` de cada portal viaja en su `/config`
93
+ (`product_config.tracking_id`). Cómo se entrega a Clarity depende del entorno:
94
+
95
+ - **App móvil**: arranca solo. `Session.init()` → `Networking.executeInit()` lee
96
+ `product_config.tracking_id` e inicializa Clarity, identificando con `getUserID()`.
97
+ - **Portal web**: `executeInit()` no corre (el config llega por el proxy /
98
+ `WebsiteProvider` del portal). El portal debe inicializar Clarity con un
99
+ `ClarityInitializer` (espejo del `MixpanelInitializer`) que tome el `tracking_id`
100
+ del `WebsiteProvider` y llame a `initialize()`.
101
+
102
+ ```typescript
103
+ // Init con opciones
104
+ await WebsSDK.Clarity.initialize(projectId, {
105
+ devMode: false, // true → omite la carga (desarrollo)
106
+ requireConsent: true, // true → espera TRACKING_PERMISSION==='granted' (con banner)
107
+ // false → arranca sin esperar consentimiento (sin banner)
108
+ });
109
+
110
+ // Consentimiento de cookies (desde el banner)
111
+ await WebsSDK.Clarity.updateConsent(true); // false para rechazar
112
+
113
+ // Identify: automático en Session.init() con getUserID(); manual opcional
114
+ WebsSDK.Clarity.identify(userId);
115
+ WebsSDK.Clarity.setTag('plan', 'premium');
116
+ WebsSDK.Clarity.trackEvent('checkout_started');
117
+ ```
118
+
119
+ - **Consentimiento:** con `requireConsent: true` respeta `TRACKING_PERMISSION`
120
+ (igual que GA4): si no está `'granted'`, no carga; queda pendiente y arranca al
121
+ llamar `updateConsent(true)`. Con `requireConsent: false` (tenants sin banner)
122
+ arranca directo.
123
+ - **Identify diferido:** no importa el orden de init vs `Session.init()` ni cuándo
124
+ llegue el consentimiento — el `identify(getUserID())` se aplica en cuanto Clarity
125
+ está listo.
126
+ - **Dev mode:** con `devMode: true` no carga (no ensucia Clarity con sesiones de
127
+ desarrollo). El auto-init móvil usa `DEBUG_MODE` para esto.
128
+
129
+ ### AuthManager
130
+
131
+ ```typescript
132
+ // Login estándar
133
+ await WebsSDK.AuthManager.authUser({ login, password, website_id });
134
+
135
+ // Login sin password (MSISDN)
136
+ await WebsSDK.AuthManager.authOnlyUser({ login, website_id });
137
+
138
+ // Login con OTP
139
+ await WebsSDK.AuthManager.authOnlyUserOTP({ login, website_id, pincode });
140
+
141
+ // Registro
142
+ await WebsSDK.AuthManager.createUser({ login, password, website_id });
143
+
144
+ // Auto-login post-pago (usado en el proxy de next_template)
145
+ await WebsSDK.AuthManager.auto_login(cfg_sessionid, websiteData, requestUrl);
146
+
147
+ // Baja/cancelación
148
+ await WebsSDK.AuthManager.unsubscribeUser({ user_id, callback_url?, org? });
149
+
150
+ // Gestión de perfil
151
+ await WebsSDK.AuthManager.setUserMetadata(params);
152
+ await WebsSDK.AuthManager.uploadProfilePhoto(params); // JPG/PNG/WEBP, max 5MB
153
+ await WebsSDK.AuthManager.changePassword(params);
154
+ ```
155
+
156
+ ### Andromeda — Portales
157
+
158
+ ```typescript
159
+ // Metadata del portal (optimizado con cookie websiteDataFlag)
160
+ await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('portal.com');
161
+
162
+ // Configuración del producto (langs, login_type, logo, favicon, theme)
163
+ await WebsSDK.Andromeda.getWebsiteConfig('portal-corp-web');
164
+ ```
165
+
166
+ ### ContentManager
167
+
168
+ ```typescript
169
+ // Contenido por preset
170
+ await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'es' });
171
+
172
+ // Colección filtrada por tags
173
+ await WebsSDK.ContentManager.getCollection(['tag1', 'tag2'], 'es', false);
174
+ ```
175
+
176
+ ### User
177
+
178
+ ```typescript
179
+ await WebsSDK.User.checkSession(token); // Valida token (usado en middleware)
180
+ await WebsSDK.User.getUserData(user_id, website_id);
181
+ await WebsSDK.User.getCredits(user_id, website_id);
182
+ ```
183
+
184
+ ### CorporateAuthManager
185
+
186
+ ```typescript
187
+ // Server-side (middleware Next.js)
188
+ const result = await WebsSDK.CorporateAuthManager.validateCorporateAuthServerSide(request);
189
+ if (!result.authenticated) return result.redirectResponse;
190
+
191
+ // Client-side
192
+ await WebsSDK.CorporateAuthManager.checkCorporateLogin();
193
+ await WebsSDK.CorporateAuthManager.validateUser(autoRedirect?);
194
+ await WebsSDK.CorporateAuthManager.login(returnTo?);
195
+ await WebsSDK.CorporateAuthManager.logout(redirectTo?);
196
+ await WebsSDK.CorporateAuthManager.isAdmin(userData?);
197
+ ```
198
+
199
+ ### Legal
200
+
201
+ ```typescript
202
+ await WebsSDK.Legal.getLegalText('tos' | 'privacy' | 'faq', web_id, lang);
203
+ ```
204
+
205
+ ### Namespace `pre` — Pre-producción
206
+
207
+ ```typescript
208
+ // Lazy-loaded, apunta a bc1742-pre.gways.org
209
+ await WebsSDK.pre.AuthManager.authUser(params);
210
+ await WebsSDK.pre.Andromeda.getWebsiteMetadatabyHostname(hostname);
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Componentes React
216
+
217
+ ### CookieConsent — Banner GDPR
218
+
219
+ ```typescript
220
+ import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
221
+ import type { CookieConsentProps, CookieConsentTheme, CookieConsentPreferences } from 'webs-sdk';
222
+
223
+ <CookieConsent
224
+ locale="es" // en | es | fr | nl | ar | de | fi | ro | tr
225
+ privacyPolicyUrl="/privacy"
226
+ theme={{
227
+ backgroundColor: '#1a1a2e',
228
+ accentColor: '#6c63ff', // aplica a botón Accept, borde Reject, links y glow
229
+ acceptButtonBg: 'linear-gradient(90deg, #6c63ff, #a855f7)', // soporta gradients CSS
230
+ textColor: '#ffffff',
231
+ }}
232
+ onAccept={() => updateGAConsent('granted')}
233
+ onReject={() => updateGAConsent('denied')}
234
+ onSaveSettings={(prefs: CookieConsentPreferences) => handlePrefs(prefs)}
235
+ />
236
+ ```
237
+
238
+ **Storage:**
239
+ - Cookie `cookieConsent`: `accepted | rejected | custom` (1 año)
240
+ - `localStorage.TRACKING_PERMISSION`: `granted | denied`
241
+ - `localStorage.FUNCTIONAL_PERMISSION`: `granted | denied`
242
+
243
+ ### Google Analytics — GA4
244
+
245
+ ```typescript
246
+ import { initGoogleAnalytics, updateGAConsent, trackGAEvent, trackGAPageView, isGALoaded } from 'webs-sdk';
247
+
248
+ // Registra el GA ID. Carga el script SOLO si hay consentimiento previo.
249
+ await initGoogleAnalytics('G-XXXXXXXXXX', { sendPageView: true, debug: false });
250
+
251
+ // Llamar tras decisión del usuario en CookieConsent
252
+ updateGAConsent('granted'); // carga GA si había pendingTrackingId
253
+ updateGAConsent('denied'); // GA nunca se carga
254
+
255
+ // Tracking
256
+ trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
257
+ trackGAPageView('/home', 'Inicio');
258
+ isGALoaded(); // boolean
259
+ ```
260
+
261
+ > **GDPR estricto:** GA no usa Consent Mode v2. Si no hay consentimiento, el script `gtag.js` nunca se inyecta en el DOM.
262
+
263
+ ### CDN Proxy
264
+
265
+ ```typescript
266
+ import { handleCDNRequest } from 'webs-sdk';
267
+ import type { CDNOptions } from 'webs-sdk';
268
+
269
+ // Usar en un route handler de Next.js (compatible con Edge Runtime)
270
+ async function handleCDNRequest(
271
+ pathname: string, // '/cdn/apariencias/33702/image.png'
272
+ options?: {
273
+ cdnBaseUrl?: string; // Default: 'https://dy822md8ge77v.cloudfront.net'
274
+ cacheSeconds?: number; // Default: 300 (5 min)
275
+ }
276
+ ): Promise<Response | null> // null si pathname no empieza por /cdn/
277
+ ```
278
+
279
+ ### Componentes "For You" — Creación de contenido AI
280
+
281
+ ```typescript
282
+ import {
283
+ AvatarAI, AvatarAIPage, AvatarAIForYouCard,
284
+ CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
285
+ MemeGenerator, MemeGeneratorPage, MemeGeneratorForYouCard,
286
+ WallpapersName, WallpapersNamePage, WallpapersNameForYouCard,
287
+ Wallpapers,
288
+ Ringtone,
289
+ } from 'webs-sdk';
290
+ ```
291
+
292
+ Todos aceptan `websiteId?: string` para tracking multi-portal.
293
+
294
+ ### ARFilters — Filtros AR con DeepAR
295
+
296
+ ```typescript
297
+ import { ARFilters, ARFiltersForYouCard } from 'webs-sdk';
298
+ import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';
299
+
300
+ <ARFilters
301
+ licenseKey="deepar-license-key"
302
+ websiteId="portal-web-id"
303
+ apiBaseUrl="https://api.fluver-ai.com"
304
+ downloadProxyUrl="/cdn" // Proxy CORS para descarga de efectos
305
+ filterTag="ar-filters-tag" // Obtiene filtros del CMS por tag
306
+ // O bien: filters={[{ file: 'beauty', thumbUrl: '/thumb.jpg' }]}
307
+ locale="es"
308
+ texts={getARFiltersTexts('es')} // requerido
309
+ onPhotoTaken={(url) => share(url)}
310
+ onVideoRecorded={(url) => share(url)}
311
+ />
312
+ ```
313
+
314
+ Soporta fullscreen en mobile (vendor-prefixed API para iOS Safari).
315
+
316
+ ### SpinningWheel — Ruleta
317
+
318
+ ```typescript
319
+ import { SpinningWheel, wheelThemes } from 'webs-sdk';
320
+
321
+ // Temas: vodacom | totalgym | womantoday | afristream | efc
322
+ <SpinningWheel
323
+ userId={userId}
324
+ websiteId={websiteId}
325
+ theme="vodacom"
326
+ onResult={(segment) => handleWin(segment)}
327
+ canSpin={true}
328
+ alreadySpunToday={false}
329
+ />
330
+ ```
331
+
332
+ ### Quiz y Stickers
333
+
334
+ ```typescript
335
+ import { Quiz, Stickers } from 'webs-sdk';
336
+ ```
337
+
338
+ ### Esports — Noticias, Vídeos y Partidas en Directo
339
+
340
+ ```typescript
341
+ import { EsportsNews, EsportsVideos, EsportsLive } from 'webs-sdk';
342
+ ```
343
+
344
+ #### EsportsNews
345
+
346
+ ```typescript
347
+ <EsportsNews
348
+ config={{
349
+ key: 'api-key',
350
+ contents: [{ preset: 'esports_news', apiProviderId: 'id', category: [], tags: [], limit: 10 }]
351
+ }}
352
+ language="es"
353
+ onNewsClick={(item) => router.push(`/news/${item.id}`)}
354
+ theme={theme} // CSS classes para container, cards, badges, etc.
355
+ categoryStyles={{ gaming: { badge: 'bg-blue-500', badgeText: 'text-white' } }}
356
+ // Modo detalle (renderiza EsportsNewsDetail):
357
+ newsId="article-id"
358
+ newsData={item} // opcional si ya tienes los datos
359
+ onBack={() => router.back()}
360
+ />
361
+ ```
362
+
363
+ #### EsportsVideos
364
+
365
+ ```typescript
366
+ <EsportsVideos
367
+ config={{ key: 'api-key', contents: [...] }}
368
+ language="es"
369
+ onVideoClick={(item) => openPlayer(item.video_url)}
370
+ theme={theme} // incluye clases para duration overlay
371
+ />
372
+ ```
373
+
374
+ #### EsportsLive
375
+
376
+ ```typescript
377
+ <EsportsLive
378
+ language="es"
379
+ timezone="Europe/Madrid"
380
+ onLiveClick={(item) => openStream(item.streamUrl)}
381
+ onMatchClick={(match) => openMatchDetail(match)}
382
+ onGameClick={(gameId) => filterByGame(gameId)}
383
+ website_id="portal-web-id"
384
+ theme={theme}
385
+ // Modo detalle de partida:
386
+ matchId="serie-id"
387
+ matchData={calendarMatch}
388
+ onBack={() => setMatchId(null)}
389
+ />
390
+ ```
391
+
392
+ ---
393
+
394
+ ## Utilidades de validación de vídeo
395
+
396
+ ```typescript
397
+ import { validateVideoUrl, validateVideoBatch, clearValidationCache } from 'webs-sdk';
398
+ import { useValidateVideoOnMount, useLazyVideoValidation } from 'webs-sdk';
399
+
400
+ // Función pura (sin React)
401
+ const state = await validateVideoUrl('https://stream.example.com/live.m3u8');
402
+ // 'valid' | 'invalid' | 'unknown'
403
+
404
+ // Batch con concurrencia 3
405
+ const results = await validateVideoBatch([url1, url2, url3]);
406
+
407
+ // React: validar al montar (evita renderizar streams rotos)
408
+ const { isValid, isInvalid, isValidating } = useValidateVideoOnMount(videoUrl);
409
+
410
+ // React: validación manual
411
+ const { validate, isValid, isInvalid } = useLazyVideoValidation();
412
+ await validate(videoUrl);
413
+ ```
414
+
415
+ Soporta HLS, DASH, MP4. Cache en memoria con TTL de 5 minutos.
416
+
417
+ ---
418
+
419
+ ## Estructura del proyecto
420
+
421
+ ```
422
+ src/
423
+ ├── config.ts # Configuración central (URLs, endpoints, constantes)
424
+ ├── index.ts # Barrel export principal
425
+ ├── libraries/
426
+ │ ├── networking.ts # HTTP + events
427
+ │ ├── auth.ts # Autenticación, login, baja
428
+ │ ├── session.ts # Sesiones y dispositivo
429
+ │ ├── storage.ts # LocalStorage + compresión de imágenes
430
+ │ ├── utils.ts # Utilidades generales
431
+ │ ├── andromeda.ts # Metadata y config de portales
432
+ │ ├── corp_auth.ts # Auth corporativa (JWT)
433
+ │ ├── user.ts # Datos de usuario
434
+ │ ├── mixpanel.ts # Analytics
435
+ │ ├── audio.ts # Text-to-speech
436
+ │ ├── calypso.ts # Event tracking interno
437
+ │ ├── legal.ts # Textos legales
438
+ │ ├── content.ts # Contenido dinámico + colecciones por tags
439
+ │ ├── cdn.ts # CDN Proxy (Edge Runtime compatible)
440
+ │ └── googleAnalytics.ts # GA4 con carga diferida por consentimiento
441
+ ├── utils/
442
+ │ ├── videoValidation.ts # Validación de URLs de streaming
443
+ │ ├── useVideoValidation.ts # React hooks para validación
444
+ │ └── timeUtils.ts # getTimeAgo — tiempo relativo i18n
445
+ └── components/
446
+ ├── AvatarAI.tsx
447
+ ├── CreativeFaceSwap.tsx
448
+ ├── Wallpapers.tsx
449
+ ├── WallpapersName.tsx
450
+ ├── Ringtone.tsx
451
+ ├── MemeGenerator.tsx
452
+ ├── Quiz.tsx
453
+ ├── Stickers.tsx
454
+ ├── VideoPlayer.tsx
455
+ ├── ARFilters/
456
+ ├── SpinningWheel/
457
+ ├── CookieConsent/
458
+ └── esports/
459
+ ├── news/
460
+ ├── videos/
461
+ └── live/
462
+ ```
463
+
464
+ ## Desarrollo
465
+
466
+ ```bash
467
+ npm install
468
+ npm run build # tsc → dist/
469
+ npm run build:watch # watch mode
470
+ npm run lint
471
+ ```
472
+
473
+ ## Publicación
474
+
475
+ ```bash
476
+ # Incluir [ROAD-361723] en el commit para auto-publish en CI
477
+ git commit -m "feat: descripción [ROAD-361723]"
478
+ git push
479
+ ```
480
+
481
+ Tras publicar, actualizar en los portales:
482
+ ```bash
483
+ npm install webs-sdk@<nueva-version>
484
+ ```
485
+
486
+ **Nota importante — `index.js` raíz:** es el barrel CommonJS mantenido manualmente. Cada export nuevo en `src/index.ts` debe añadirse también en `index.js`:
487
+ ```js
488
+ const { nuevaFuncion } = require('./dist/index.js');
489
+ module.exports.nuevaFuncion = nuevaFuncion;
490
+ ```