# webs-sdk

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.

**Versión actual:** 0.18.9

## Instalación

```bash
npm install webs-sdk
```

## Uso básico

```typescript
import WebsSDK from 'webs-sdk';

// Inicializar sesión
await WebsSDK.Session.init();

// Petición HTTP
const data = await WebsSDK.Networking.request('/api/endpoint', { param: 'value' });

// Almacenamiento local
WebsSDK.Storage.set('key', 'value');
const value = WebsSDK.Storage.get('key');
```

## Librerías disponibles

### Networking

```typescript
// Petición HTTP (inyecta sessionData automáticamente)
await WebsSDK.Networking.request(url, data);

// Eventos de tracking
await WebsSDK.Networking.sendEvent('action', 'button_click', { button_id: 'submit' });

// Suscripciones
await WebsSDK.Networking.createSubscription(subscriptionData);
const { success, subscription_active } = await WebsSDK.Networking.checkSubscription();
```

### Storage

```typescript
WebsSDK.Storage.set('key', 'value');
const value = WebsSDK.Storage.get('key');

// Compresión de imágenes (0.8 ratio, 1024px max)
const compressed = await WebsSDK.Storage.compressImage(imageFile, 2); // 2MB max

// Creaciones (imágenes generadas por el usuario)
await WebsSDK.Storage.handleDownloadImageToCreations(base64, 'id', metadata);
const creations = await WebsSDK.Storage.getCreations();
await WebsSDK.Storage.deleteCreation('id');
```

### Session

```typescript
await WebsSDK.Session.init();

const sessionData = WebsSDK.Session.getSessionData();
const userId = WebsSDK.Session.getUserID();
const lang = WebsSDK.Session.getDeviceLanguage();
const isSubscribed = WebsSDK.Session.getIsSubscribed();
```

### Utils

```typescript
const id = WebsSDK.Utils.generateId();
const isValidEmail = WebsSDK.Utils.isValidEmail('user@example.com');
const isMobile = WebsSDK.Utils.isMobile();
const deviceInfo = WebsSDK.Utils.getDeviceInfo();
const truncated = WebsSDK.Utils.truncateText('Long text...', 10);
```

### MixPanel

```typescript
await WebsSDK.MixPanel.initialize(token, false, false, false);
await WebsSDK.MixPanel.trackEvent('page_view', { page: 'home' });
await WebsSDK.MixPanel.identifyUser('user_123');
await WebsSDK.MixPanel.setUserProperties({ plan: 'premium' });
```

### AuthManager

```typescript
// Login estándar
await WebsSDK.AuthManager.authUser({ login, password, website_id });

// Login sin password (MSISDN)
await WebsSDK.AuthManager.authOnlyUser({ login, website_id });

// Login con OTP
await WebsSDK.AuthManager.authOnlyUserOTP({ login, website_id, pincode });

// Registro
await WebsSDK.AuthManager.createUser({ login, password, website_id });

// Auto-login post-pago (usado en el proxy de next_template)
await WebsSDK.AuthManager.auto_login(cfg_sessionid, websiteData, requestUrl);

// Baja/cancelación
await WebsSDK.AuthManager.unsubscribeUser({ user_id, callback_url?, org? });

// Gestión de perfil
await WebsSDK.AuthManager.setUserMetadata(params);
await WebsSDK.AuthManager.uploadProfilePhoto(params); // JPG/PNG/WEBP, max 5MB
await WebsSDK.AuthManager.changePassword(params);
```

### Andromeda — Portales

```typescript
// Metadata del portal (optimizado con cookie websiteDataFlag)
await WebsSDK.Andromeda.getWebsiteMetadatabyHostname('portal.com');

// Configuración del producto (langs, login_type, logo, favicon, theme)
await WebsSDK.Andromeda.getWebsiteConfig('portal-corp-web');
```

### ContentManager

```typescript
// Contenido por preset
await WebsSDK.ContentManager.getContentByPreset('homepage', { lang: 'es' });

// Colección filtrada por tags
await WebsSDK.ContentManager.getCollection(['tag1', 'tag2'], 'es', false);
```

### User

```typescript
await WebsSDK.User.checkSession(token);            // Valida token (usado en middleware)
await WebsSDK.User.getUserData(user_id, website_id);
await WebsSDK.User.getCredits(user_id, website_id);
```

### CorporateAuthManager

```typescript
// Server-side (middleware Next.js)
const result = await WebsSDK.CorporateAuthManager.validateCorporateAuthServerSide(request);
if (!result.authenticated) return result.redirectResponse;

// Client-side
await WebsSDK.CorporateAuthManager.checkCorporateLogin();
await WebsSDK.CorporateAuthManager.validateUser(autoRedirect?);
await WebsSDK.CorporateAuthManager.login(returnTo?);
await WebsSDK.CorporateAuthManager.logout(redirectTo?);
await WebsSDK.CorporateAuthManager.isAdmin(userData?);
```

### Legal

```typescript
await WebsSDK.Legal.getLegalText('tos' | 'privacy' | 'faq', web_id, lang);
```

### Namespace `pre` — Pre-producción

```typescript
// Lazy-loaded, apunta a bc1742-pre.gways.org
await WebsSDK.pre.AuthManager.authUser(params);
await WebsSDK.pre.Andromeda.getWebsiteMetadatabyHostname(hostname);
```

---

## Componentes React

### CookieConsent — Banner GDPR

```typescript
import { CookieConsent, getCookieConsentTexts } from 'webs-sdk';
import type { CookieConsentProps, CookieConsentTheme, CookieConsentPreferences } from 'webs-sdk';

<CookieConsent
  locale="es"                        // en | es | fr | nl | ar | de | fi | ro | tr
  privacyPolicyUrl="/privacy"
  theme={{
    backgroundColor: '#1a1a2e',
    accentColor: '#6c63ff',           // aplica a botón Accept, borde Reject, links y glow
    acceptButtonBg: 'linear-gradient(90deg, #6c63ff, #a855f7)', // soporta gradients CSS
    textColor: '#ffffff',
  }}
  onAccept={() => updateGAConsent('granted')}
  onReject={() => updateGAConsent('denied')}
  onSaveSettings={(prefs: CookieConsentPreferences) => handlePrefs(prefs)}
/>
```

**Storage:**
- Cookie `cookieConsent`: `accepted | rejected | custom` (1 año)
- `localStorage.TRACKING_PERMISSION`: `granted | denied`
- `localStorage.FUNCTIONAL_PERMISSION`: `granted | denied`

### Google Analytics — GA4

```typescript
import { initGoogleAnalytics, updateGAConsent, trackGAEvent, trackGAPageView, isGALoaded } from 'webs-sdk';

// Registra el GA ID. Carga el script SOLO si hay consentimiento previo.
await initGoogleAnalytics('G-XXXXXXXXXX', { sendPageView: true, debug: false });

// Llamar tras decisión del usuario en CookieConsent
updateGAConsent('granted');  // carga GA si había pendingTrackingId
updateGAConsent('denied');   // GA nunca se carga

// Tracking
trackGAEvent('purchase', { value: 9.99, currency: 'EUR' });
trackGAPageView('/home', 'Inicio');
isGALoaded(); // boolean
```

> **GDPR estricto:** GA no usa Consent Mode v2. Si no hay consentimiento, el script `gtag.js` nunca se inyecta en el DOM.

### CDN Proxy

```typescript
import { handleCDNRequest } from 'webs-sdk';
import type { CDNOptions } from 'webs-sdk';

// Usar en un route handler de Next.js (compatible con Edge Runtime)
async function handleCDNRequest(
  pathname: string,        // '/cdn/apariencias/33702/image.png'
  options?: {
    cdnBaseUrl?: string;   // Default: 'https://dy822md8ge77v.cloudfront.net'
    cacheSeconds?: number; // Default: 300 (5 min)
  }
): Promise<Response | null>  // null si pathname no empieza por /cdn/
```

### Componentes "For You" — Creación de contenido AI

```typescript
import {
  AvatarAI, AvatarAIPage, AvatarAIForYouCard,
  CreativeFaceSwap, CreativeFaceSwapPage, CreativeFaceSwapForYouCard,
  MemeGenerator, MemeGeneratorPage, MemeGeneratorForYouCard,
  WallpapersName, WallpapersNamePage, WallpapersNameForYouCard,
  Wallpapers,
  Ringtone,
} from 'webs-sdk';
```

Todos aceptan `websiteId?: string` para tracking multi-portal.

### ARFilters — Filtros AR con DeepAR

```typescript
import { ARFilters, ARFiltersForYouCard } from 'webs-sdk';
import type { DeepARProps, DeepARFilter, DeepARTexts } from 'webs-sdk';

<ARFilters
  licenseKey="deepar-license-key"
  websiteId="portal-web-id"
  apiBaseUrl="https://api.fluver-ai.com"
  downloadProxyUrl="/cdn"            // Proxy CORS para descarga de efectos
  filterTag="ar-filters-tag"         // Obtiene filtros del CMS por tag
  // O bien: filters={[{ file: 'beauty', thumbUrl: '/thumb.jpg' }]}
  locale="es"
  texts={getARFiltersTexts('es')}    // requerido
  onPhotoTaken={(url) => share(url)}
  onVideoRecorded={(url) => share(url)}
/>
```

Soporta fullscreen en mobile (vendor-prefixed API para iOS Safari).

### SpinningWheel — Ruleta

```typescript
import { SpinningWheel, wheelThemes } from 'webs-sdk';

// Temas: vodacom | totalgym | womantoday | afristream | efc
<SpinningWheel
  userId={userId}
  websiteId={websiteId}
  theme="vodacom"
  onResult={(segment) => handleWin(segment)}
  canSpin={true}
  alreadySpunToday={false}
/>
```

### Quiz y Stickers

```typescript
import { Quiz, Stickers } from 'webs-sdk';
```

### Esports — Noticias, Vídeos y Partidas en Directo

```typescript
import { EsportsNews, EsportsVideos, EsportsLive } from 'webs-sdk';
```

#### EsportsNews

```typescript
<EsportsNews
  config={{
    key: 'api-key',
    contents: [{ preset: 'esports_news', apiProviderId: 'id', category: [], tags: [], limit: 10 }]
  }}
  language="es"
  onNewsClick={(item) => router.push(`/news/${item.id}`)}
  theme={theme}                    // CSS classes para container, cards, badges, etc.
  categoryStyles={{ gaming: { badge: 'bg-blue-500', badgeText: 'text-white' } }}
  // Modo detalle (renderiza EsportsNewsDetail):
  newsId="article-id"
  newsData={item}                  // opcional si ya tienes los datos
  onBack={() => router.back()}
/>
```

#### EsportsVideos

```typescript
<EsportsVideos
  config={{ key: 'api-key', contents: [...] }}
  language="es"
  onVideoClick={(item) => openPlayer(item.video_url)}
  theme={theme}                    // incluye clases para duration overlay
/>
```

#### EsportsLive

```typescript
<EsportsLive
  language="es"
  timezone="Europe/Madrid"
  onLiveClick={(item) => openStream(item.streamUrl)}
  onMatchClick={(match) => openMatchDetail(match)}
  onGameClick={(gameId) => filterByGame(gameId)}
  website_id="portal-web-id"
  theme={theme}
  // Modo detalle de partida:
  matchId="serie-id"
  matchData={calendarMatch}
  onBack={() => setMatchId(null)}
/>
```

---

## Utilidades de validación de vídeo

```typescript
import { validateVideoUrl, validateVideoBatch, clearValidationCache } from 'webs-sdk';
import { useValidateVideoOnMount, useLazyVideoValidation } from 'webs-sdk';

// Función pura (sin React)
const state = await validateVideoUrl('https://stream.example.com/live.m3u8');
// 'valid' | 'invalid' | 'unknown'

// Batch con concurrencia 3
const results = await validateVideoBatch([url1, url2, url3]);

// React: validar al montar (evita renderizar streams rotos)
const { isValid, isInvalid, isValidating } = useValidateVideoOnMount(videoUrl);

// React: validación manual
const { validate, isValid, isInvalid } = useLazyVideoValidation();
await validate(videoUrl);
```

Soporta HLS, DASH, MP4. Cache en memoria con TTL de 5 minutos.

---

## Estructura del proyecto

```
src/
├── config.ts              # Configuración central (URLs, endpoints, constantes)
├── index.ts               # Barrel export principal
├── libraries/
│   ├── networking.ts      # HTTP + events
│   ├── auth.ts            # Autenticación, login, baja
│   ├── session.ts         # Sesiones y dispositivo
│   ├── storage.ts         # LocalStorage + compresión de imágenes
│   ├── utils.ts           # Utilidades generales
│   ├── andromeda.ts       # Metadata y config de portales
│   ├── corp_auth.ts       # Auth corporativa (JWT)
│   ├── user.ts            # Datos de usuario
│   ├── mixpanel.ts        # Analytics
│   ├── audio.ts           # Text-to-speech
│   ├── calypso.ts         # Event tracking interno
│   ├── legal.ts           # Textos legales
│   ├── content.ts         # Contenido dinámico + colecciones por tags
│   ├── cdn.ts             # CDN Proxy (Edge Runtime compatible)
│   └── googleAnalytics.ts # GA4 con carga diferida por consentimiento
├── utils/
│   ├── videoValidation.ts  # Validación de URLs de streaming
│   ├── useVideoValidation.ts # React hooks para validación
│   └── timeUtils.ts        # getTimeAgo — tiempo relativo i18n
└── components/
    ├── AvatarAI.tsx
    ├── CreativeFaceSwap.tsx
    ├── Wallpapers.tsx
    ├── WallpapersName.tsx
    ├── Ringtone.tsx
    ├── MemeGenerator.tsx
    ├── Quiz.tsx
    ├── Stickers.tsx
    ├── VideoPlayer.tsx
    ├── ARFilters/
    ├── SpinningWheel/
    ├── CookieConsent/
    └── esports/
        ├── news/
        ├── videos/
        └── live/
```

## Desarrollo

```bash
npm install
npm run build          # tsc → dist/
npm run build:watch    # watch mode
npm run lint
```

## Publicación

```bash
# Incluir [ROAD-361723] en el commit para auto-publish en CI
git commit -m "feat: descripción [ROAD-361723]"
git push
```

Tras publicar, actualizar en los portales:
```bash
npm install webs-sdk@<nueva-version>
```

**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`:
```js
const { nuevaFuncion } = require('./dist/index.js');
module.exports.nuevaFuncion = nuevaFuncion;
```
