valtech-components 2.0.498 → 2.0.501

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 (49) hide show
  1. package/esm2022/lib/components/atoms/button/button.component.mjs +87 -48
  2. package/esm2022/lib/components/molecules/action-header/action-header.component.mjs +1 -1
  3. package/esm2022/lib/components/molecules/ad-slot/ad-slot.component.mjs +249 -0
  4. package/esm2022/lib/components/molecules/button-group/button-group.component.mjs +1 -1
  5. package/esm2022/lib/components/molecules/card/card.component.mjs +2 -2
  6. package/esm2022/lib/components/molecules/file-input/file-input.component.mjs +1 -1
  7. package/esm2022/lib/components/molecules/raffle-status-card/raffle-status-card.component.mjs +2 -2
  8. package/esm2022/lib/components/organisms/article/article.component.mjs +2 -2
  9. package/esm2022/lib/components/organisms/menu/menu.component.mjs +1 -1
  10. package/esm2022/lib/components/templates/page-template/page-template.component.mjs +1 -1
  11. package/esm2022/lib/services/ads/ads-consent.service.mjs +152 -0
  12. package/esm2022/lib/services/ads/ads-loader.service.mjs +160 -0
  13. package/esm2022/lib/services/ads/ads.service.mjs +449 -0
  14. package/esm2022/lib/services/ads/config.mjs +118 -0
  15. package/esm2022/lib/services/ads/index.mjs +14 -0
  16. package/esm2022/lib/services/ads/types.mjs +23 -0
  17. package/esm2022/lib/services/auth/auth.service.mjs +103 -6
  18. package/esm2022/lib/services/auth/index.mjs +4 -1
  19. package/esm2022/lib/services/auth/oauth-callback.component.mjs +141 -0
  20. package/esm2022/lib/services/auth/oauth.service.mjs +250 -0
  21. package/esm2022/lib/services/auth/types.mjs +1 -1
  22. package/esm2022/lib/services/firebase/analytics-error-handler.mjs +141 -0
  23. package/esm2022/lib/services/firebase/analytics-router-tracker.mjs +99 -0
  24. package/esm2022/lib/services/firebase/analytics.service.mjs +597 -0
  25. package/esm2022/lib/services/firebase/config.mjs +21 -2
  26. package/esm2022/lib/services/firebase/index.mjs +6 -1
  27. package/esm2022/public-api.mjs +6 -1
  28. package/fesm2022/valtech-components.mjs +2739 -239
  29. package/fesm2022/valtech-components.mjs.map +1 -1
  30. package/lib/components/atoms/button/button.component.d.ts +30 -6
  31. package/lib/components/molecules/ad-slot/ad-slot.component.d.ts +78 -0
  32. package/lib/components/organisms/article/article.component.d.ts +3 -3
  33. package/lib/services/ads/ads-consent.service.d.ts +59 -0
  34. package/lib/services/ads/ads-loader.service.d.ts +46 -0
  35. package/lib/services/ads/ads.service.d.ts +123 -0
  36. package/lib/services/ads/config.d.ts +69 -0
  37. package/lib/services/ads/index.d.ts +10 -0
  38. package/lib/services/ads/types.d.ts +163 -0
  39. package/lib/services/auth/auth.service.d.ts +56 -3
  40. package/lib/services/auth/index.d.ts +2 -0
  41. package/lib/services/auth/oauth-callback.component.d.ts +34 -0
  42. package/lib/services/auth/oauth.service.d.ts +90 -0
  43. package/lib/services/auth/types.d.ts +69 -0
  44. package/lib/services/firebase/analytics-error-handler.d.ts +54 -0
  45. package/lib/services/firebase/analytics-router-tracker.d.ts +51 -0
  46. package/lib/services/firebase/analytics.service.d.ts +256 -0
  47. package/lib/services/firebase/index.d.ts +4 -0
  48. package/package.json +1 -1
  49. package/public-api.d.ts +2 -0
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, Component, Input, Output, Injectable, HostListener, inject, Pipe, ChangeDetectionStrategy, ViewChild, signal, computed, makeEnvironmentProviders, APP_INITIALIZER, ChangeDetectorRef, ElementRef, InjectionToken, Inject, PLATFORM_ID, Optional, runInInjectionContext, effect } from '@angular/core';
2
+ import { EventEmitter, Component, Input, Output, Injectable, signal, makeEnvironmentProviders, APP_INITIALIZER, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, computed, ChangeDetectorRef, ElementRef, PLATFORM_ID, Inject, ErrorHandler, DestroyRef, InjectionToken, Optional, runInInjectionContext, effect, Injector } from '@angular/core';
3
3
  import * as i2$1 from '@ionic/angular/standalone';
4
4
  import { IonAvatar, IonCard, IonIcon, IonButton, IonSpinner, IonText, IonModal, IonHeader, IonToolbar, IonContent, IonButtons, IonTitle, IonProgressBar, IonSkeletonText, IonFab, IonFabButton, IonFabList, IonLabel, IonCardContent, IonCardHeader, IonCardTitle, IonCardSubtitle, IonCheckbox, IonTextarea, IonDatetime, IonDatetimeButton, IonInput, IonSelect, IonSelectOption, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonAccordion, IonAccordionGroup, IonItem, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonPopover, IonList, IonNote, ToastController as ToastController$1, IonCol, IonRow, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, AlertController } from '@ionic/angular/standalone';
5
5
  import * as i1 from '@angular/common';
@@ -13,7 +13,7 @@ import * as i1$2 from '@angular/platform-browser';
13
13
  import QRCodeStyling from 'qr-code-styling';
14
14
  import * as i1$3 from '@angular/forms';
15
15
  import { ReactiveFormsModule, FormsModule, FormControl, Validators } from '@angular/forms';
16
- import { BehaviorSubject, filter, map, distinctUntilChanged, Subject, throwError, firstValueFrom, of, from } from 'rxjs';
16
+ import { BehaviorSubject, filter, map, distinctUntilChanged, Subject, throwError, Observable, firstValueFrom, of, from } from 'rxjs';
17
17
  import * as i1$4 from 'ng-otp-input';
18
18
  import { NgOtpInputComponent, NgOtpInputModule } from 'ng-otp-input';
19
19
  import * as i2 from '@ionic/angular';
@@ -27,6 +27,7 @@ import 'prismjs/components/prism-typescript';
27
27
  import 'prismjs/components/prism-bash';
28
28
  import Swiper from 'swiper';
29
29
  import { Navigation, Pagination, EffectFade, EffectCube, EffectCoverflow, EffectFlip, Autoplay } from 'swiper/modules';
30
+ import { Analytics, logEvent, setUserId, setUserProperties, provideAnalytics, getAnalytics } from '@angular/fire/analytics';
30
31
  import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
31
32
  import * as i1$5 from '@angular/fire/auth';
32
33
  import { provideAuth, getAuth, connectAuthEmulator, authState, signInWithCustomToken, signOut } from '@angular/fire/auth';
@@ -35,7 +36,8 @@ import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexed
35
36
  import { provideMessaging, getMessaging, Messaging, getToken, deleteToken, onMessage } from '@angular/fire/messaging';
36
37
  import * as i1$7 from '@angular/fire/storage';
37
38
  import { provideStorage, getStorage, connectStorageEmulator, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
38
- import { catchError, switchMap, finalize, filter as filter$1, take, tap, map as map$1 } from 'rxjs/operators';
39
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
40
+ import { filter as filter$1, catchError, switchMap, finalize, take, tap, map as map$1 } from 'rxjs/operators';
39
41
  import * as i1$8 from '@angular/common/http';
40
42
  import { provideHttpClient, withInterceptors } from '@angular/common/http';
41
43
 
@@ -243,6 +245,154 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
243
245
  type: Output
244
246
  }] } });
245
247
 
248
+ /**
249
+ * Servicio para gestionar presets de componentes.
250
+ *
251
+ * Los presets permiten definir configuraciones reutilizables
252
+ * de componentes (tamaño, color, variante, etc.) que se pueden
253
+ * aplicar con un nombre semántico.
254
+ *
255
+ * @example
256
+ * // En un componente
257
+ * presets = inject(PresetService);
258
+ *
259
+ * // Obtener preset
260
+ * const buttonProps = this.presets.get<ButtonMetadata>('button', 'primary-action');
261
+ * // { size: 'large', color: 'primary', fill: 'solid' }
262
+ */
263
+ class PresetService {
264
+ constructor() {
265
+ this._presets = signal({});
266
+ }
267
+ /**
268
+ * Obtiene un preset específico para un componente
269
+ *
270
+ * @param component Tipo de componente (ej: 'button', 'card', 'input')
271
+ * @param presetName Nombre del preset (ej: 'primary-action', 'compact')
272
+ * @returns Propiedades del preset o objeto vacío si no existe
273
+ *
274
+ * @example
275
+ * // Obtener preset de botón
276
+ * const props = presets.get<ButtonMetadata>('button', 'primary-action');
277
+ *
278
+ * // Usar en componente
279
+ * <val-button [props]="props"></val-button>
280
+ */
281
+ get(component, presetName) {
282
+ const componentPresets = this._presets()[component];
283
+ if (!componentPresets) {
284
+ console.warn(`[presets] No presets registered for component: ${component}`);
285
+ return {};
286
+ }
287
+ const preset = componentPresets[presetName];
288
+ if (!preset) {
289
+ console.warn(`[presets] Preset '${presetName}' not found for component: ${component}`);
290
+ return {};
291
+ }
292
+ return preset;
293
+ }
294
+ /**
295
+ * Verifica si existe un preset
296
+ */
297
+ has(component, presetName) {
298
+ return !!this._presets()[component]?.[presetName];
299
+ }
300
+ /**
301
+ * Registra presets de la aplicación
302
+ *
303
+ * @param presets Configuración de presets
304
+ *
305
+ * @example
306
+ * presets.registerPresets({
307
+ * button: {
308
+ * 'primary-action': { size: 'large', color: 'primary' },
309
+ * },
310
+ * card: {
311
+ * 'feature': { variant: 'elevated' },
312
+ * }
313
+ * });
314
+ */
315
+ registerPresets(presets) {
316
+ this._presets.set(presets);
317
+ }
318
+ /**
319
+ * Agrega presets para un componente específico (merge con existentes)
320
+ */
321
+ registerComponentPresets(component, presets) {
322
+ this._presets.update((current) => ({
323
+ ...current,
324
+ [component]: {
325
+ ...current[component],
326
+ ...presets,
327
+ },
328
+ }));
329
+ }
330
+ /**
331
+ * Obtiene todos los nombres de presets para un componente
332
+ */
333
+ getPresetNames(component) {
334
+ return Object.keys(this._presets()[component] || {});
335
+ }
336
+ /**
337
+ * Obtiene todos los componentes con presets registrados
338
+ */
339
+ getRegisteredComponents() {
340
+ return Object.keys(this._presets());
341
+ }
342
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
343
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, providedIn: 'root' }); }
344
+ }
345
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, decorators: [{
346
+ type: Injectable,
347
+ args: [{ providedIn: 'root' }]
348
+ }] });
349
+
350
+ /**
351
+ * Configura el sistema de presets de Valtech Components.
352
+ *
353
+ * @param presets Configuración de presets por componente
354
+ * @returns Providers para agregar en app.config.ts
355
+ *
356
+ * @example
357
+ * // app.config.ts
358
+ * import { provideValtechPresets } from 'valtech-components';
359
+ *
360
+ * export const appConfig: ApplicationConfig = {
361
+ * providers: [
362
+ * provideValtechPresets({
363
+ * button: {
364
+ * 'primary-action': { size: 'large', color: 'primary', fill: 'solid' },
365
+ * 'secondary': { size: 'medium', color: 'secondary', fill: 'outline' },
366
+ * 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
367
+ * },
368
+ * card: {
369
+ * 'feature': { variant: 'elevated', padding: 'large' },
370
+ * 'compact': { variant: 'flat', padding: 'small' },
371
+ * },
372
+ * input: {
373
+ * 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
374
+ * }
375
+ * }),
376
+ * ]
377
+ * };
378
+ */
379
+ function provideValtechPresets(presets) {
380
+ return makeEnvironmentProviders([
381
+ {
382
+ provide: APP_INITIALIZER,
383
+ useFactory: (presetService) => {
384
+ return () => {
385
+ presetService.registerPresets(presets);
386
+ };
387
+ },
388
+ deps: [PresetService],
389
+ multi: true,
390
+ },
391
+ ]);
392
+ }
393
+
394
+ // Service
395
+
246
396
  const ENABLED = 'ENABLED';
247
397
  const DISABLED = 'DISABLED';
248
398
  const WORKING = 'WORKING';
@@ -477,7 +627,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
477
627
  /**
478
628
  * val-button
479
629
  *
480
- * A customizable button supporting icons, loading state, and navigation.
630
+ * A customizable button supporting icons, loading state, navigation, and presets.
631
+ *
632
+ * @example With preset (recommended with i18n):
633
+ * <val-button preset="primary-action" [props]="{ text: 'Submit' | t }"></val-button>
481
634
  *
482
635
  * @example Static text:
483
636
  * <val-button [props]="{
@@ -488,41 +641,74 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
488
641
  * icon: { name: 'save', slot: 'start' }
489
642
  * }" (onClick)="handler()"></val-button>
490
643
  *
491
- * @input props: ButtonMetadata - Configuration for the button (text, color, icon, state, etc.)
644
+ * @input preset: string - Name of preset to apply (e.g., 'primary-action', 'danger')
645
+ * @input props: ButtonMetadata - Configuration for the button (overrides preset values)
492
646
  * @output onClick - Emits when the button is clicked
493
647
  */
494
648
  class ButtonComponent {
495
- constructor(download, icon, navigation) {
649
+ constructor(download, _icon, navigation) {
496
650
  this.download = download;
497
651
  this.navigation = navigation;
498
652
  this.states = ComponentStates;
653
+ this.presets = inject(PresetService);
499
654
  /**
500
655
  * The text to display on the button.
501
656
  */
502
657
  this.displayText = '';
658
+ /**
659
+ * Button configuration. Values here override preset values.
660
+ * When using presets, only partial props are needed (preset provides defaults).
661
+ */
662
+ this.props = {};
663
+ /**
664
+ * Resolved props after merging preset + explicit props.
665
+ * Preset values are overridden by explicit props.
666
+ */
667
+ this.resolvedProps = {};
503
668
  /**
504
669
  * Event emitted when the button is clicked.
505
670
  */
506
671
  this.onClick = new EventEmitter();
507
672
  }
508
673
  ngOnInit() {
674
+ this.resolveProps();
509
675
  this.setupDisplayText();
510
676
  }
677
+ ngOnChanges(changes) {
678
+ if (changes['preset'] || changes['props']) {
679
+ this.resolveProps();
680
+ this.setupDisplayText();
681
+ }
682
+ }
683
+ /**
684
+ * Merge preset configuration with explicit props.
685
+ * Explicit props take precedence over preset values.
686
+ */
687
+ resolveProps() {
688
+ const presetProps = this.preset
689
+ ? this.presets.get('button', this.preset)
690
+ : {};
691
+ // Merge: preset defaults < explicit props
692
+ this.resolvedProps = {
693
+ ...presetProps,
694
+ ...this.props,
695
+ };
696
+ }
511
697
  /**
512
- * Set up the text content based on the props configuration.
698
+ * Set up the text content based on the resolved props configuration.
513
699
  */
514
700
  setupDisplayText() {
515
- if (this.props.text) {
516
- if (this.props.contentInterpolation) {
517
- this.displayText = this.interpolateContent(this.props.text, this.props.contentInterpolation);
701
+ if (this.resolvedProps.text) {
702
+ if (this.resolvedProps.contentInterpolation) {
703
+ this.displayText = this.interpolateContent(this.resolvedProps.text, this.resolvedProps.contentInterpolation);
518
704
  }
519
705
  else {
520
- this.displayText = this.props.text;
706
+ this.displayText = this.resolvedProps.text;
521
707
  }
522
708
  }
523
- else if (this.props.contentFallback) {
709
+ else if (this.resolvedProps.contentFallback) {
524
710
  // Backwards compatibility: use fallback if text is not provided
525
- this.displayText = this.props.contentFallback;
711
+ this.displayText = this.resolvedProps.contentFallback;
526
712
  }
527
713
  else {
528
714
  this.displayText = '';
@@ -538,38 +724,38 @@ class ButtonComponent {
538
724
  });
539
725
  }
540
726
  clickHandler() {
541
- if (this.props.state === this.states.DISABLED) {
727
+ if (this.resolvedProps.state === this.states.DISABLED) {
542
728
  return;
543
729
  }
544
- if (this.props.actionType === ActionType.APP_NAVIGATION) {
545
- this.navigation.navigateByUrl(this.props.link);
730
+ if (this.resolvedProps.actionType === ActionType.APP_NAVIGATION) {
731
+ this.navigation.navigateByUrl(this.resolvedProps.link);
546
732
  }
547
- if (this.props.download) {
548
- this.download.downloadLinkFromBrowser(this.props.download);
733
+ if (this.resolvedProps.download) {
734
+ this.download.downloadLinkFromBrowser(this.resolvedProps.download);
549
735
  }
550
- if (this.props.handler) {
551
- this.props.handler(this.props.ref);
736
+ if (this.resolvedProps.handler) {
737
+ this.resolvedProps.handler(this.resolvedProps.ref);
552
738
  }
553
- this.onClick.emit(this.props.token);
739
+ this.onClick.emit(this.resolvedProps.token);
554
740
  }
555
741
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonComponent, deps: [{ token: DownloadService }, { token: IconService }, { token: NavigationService }], target: i0.ɵɵFactoryTarget.Component }); }
556
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { props: "props" }, outputs: { onClick: "onClick" }, ngImport: i0, template: `
742
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: ButtonComponent, isStandalone: true, selector: "val-button", inputs: { preset: "preset", props: "props" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: `
557
743
  <ion-button
558
- [type]="props.type"
559
- [color]="props.color"
560
- [expand]="props.expand"
561
- [fill]="props.fill"
562
- [size]="props.size"
563
- [href]="props.href"
564
- [target]="props.target"
565
- [shape]="props.shape"
744
+ [type]="resolvedProps.type"
745
+ [color]="resolvedProps.color"
746
+ [expand]="resolvedProps.expand"
747
+ [fill]="resolvedProps.fill"
748
+ [size]="resolvedProps.size"
749
+ [href]="resolvedProps.href"
750
+ [target]="resolvedProps.target"
751
+ [shape]="resolvedProps.shape"
566
752
  (click)="clickHandler()"
567
- [disabled]="props.state === states.DISABLED"
568
- [ngClass]="props.size ? [props.size] : []"
753
+ [disabled]="resolvedProps.state === states.DISABLED"
754
+ [ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
569
755
  >
570
- <ion-icon *ngIf="props.icon" [slot]="props.icon.slot" [name]="props.icon.name"></ion-icon>
571
- <ion-spinner *ngIf="props.state === states.WORKING" name="circular"></ion-spinner>
572
- <ion-text *ngIf="props.state !== states.WORKING">{{ displayText }}</ion-text>
756
+ <ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
757
+ <ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
758
+ <ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
573
759
  </ion-button>
574
760
  `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: IonText, selector: "ion-text", inputs: ["color", "mode"] }] }); }
575
761
  }
@@ -577,24 +763,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
577
763
  type: Component,
578
764
  args: [{ selector: 'val-button', standalone: true, imports: [CommonModule, IonButton, IonIcon, IonSpinner, IonText], template: `
579
765
  <ion-button
580
- [type]="props.type"
581
- [color]="props.color"
582
- [expand]="props.expand"
583
- [fill]="props.fill"
584
- [size]="props.size"
585
- [href]="props.href"
586
- [target]="props.target"
587
- [shape]="props.shape"
766
+ [type]="resolvedProps.type"
767
+ [color]="resolvedProps.color"
768
+ [expand]="resolvedProps.expand"
769
+ [fill]="resolvedProps.fill"
770
+ [size]="resolvedProps.size"
771
+ [href]="resolvedProps.href"
772
+ [target]="resolvedProps.target"
773
+ [shape]="resolvedProps.shape"
588
774
  (click)="clickHandler()"
589
- [disabled]="props.state === states.DISABLED"
590
- [ngClass]="props.size ? [props.size] : []"
775
+ [disabled]="resolvedProps.state === states.DISABLED"
776
+ [ngClass]="resolvedProps.size ? [resolvedProps.size] : []"
591
777
  >
592
- <ion-icon *ngIf="props.icon" [slot]="props.icon.slot" [name]="props.icon.name"></ion-icon>
593
- <ion-spinner *ngIf="props.state === states.WORKING" name="circular"></ion-spinner>
594
- <ion-text *ngIf="props.state !== states.WORKING">{{ displayText }}</ion-text>
778
+ <ion-icon *ngIf="resolvedProps.icon" [slot]="resolvedProps.icon.slot" [name]="resolvedProps.icon.name"></ion-icon>
779
+ <ion-spinner *ngIf="resolvedProps.state === states.WORKING" name="circular"></ion-spinner>
780
+ <ion-text *ngIf="resolvedProps.state !== states.WORKING">{{ displayText }}</ion-text>
595
781
  </ion-button>
596
782
  `, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-button{font-family:var(--ion-default-font),Arial,sans-serif;min-width:6.25rem;margin:0}ion-button.small{--padding-bottom: 12px;--padding-end: 14px;--padding-start: 14px;--padding-top: 12px}ion-button.button-clear{padding:0;margin:0}\n"] }]
597
- }], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: { props: [{
783
+ }], ctorParameters: () => [{ type: DownloadService }, { type: IconService }, { type: NavigationService }], propDecorators: { preset: [{
784
+ type: Input
785
+ }], props: [{
598
786
  type: Input
599
787
  }], onClick: [{
600
788
  type: Output
@@ -4189,7 +4377,7 @@ class ButtonGroupComponent {
4189
4377
  [ngStyle]="{ width: props.buttons.length === 1 ? '100%' : 'auto' }"
4190
4378
  ></val-button>
4191
4379
  </div>
4192
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
4380
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}val-button{display:inline-block;margin-top:.25rem;margin-right:.25rem}.group-container{width:100%}.group-container.left{text-align:left}.group-container.center{text-align:center}.group-container.right{text-align:right}.group-container.column{display:flex;flex-direction:column-reverse}.group-container.spaced{display:flex;justify-content:space-between}.group-container.leftocenter{text-align:left}@media (min-width: 768px){.group-container.leftocenter{text-align:center}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
4193
4381
  }
4194
4382
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ButtonGroupComponent, decorators: [{
4195
4383
  type: Component,
@@ -4409,7 +4597,7 @@ class CardComponent {
4409
4597
  </ng-container>
4410
4598
  </ion-buttons>
4411
4599
  </ion-card>
4412
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4600
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}ion-card.tapable{transition:transform .3s ease,box-shadow .3s ease}ion-card.tapable:hover{transform:scale(1.01);box-shadow:.1875rem .625rem .5rem #1219541a}.tapable{cursor:pointer}.checker{display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-header{padding:10px;display:flex;flex-direction:row;justify-content:space-between;align-items:center}.complex-content{padding-left:10px;padding-top:4px;padding-bottom:10px}.complex{border-radius:16px}img{border-radius:24px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: IonCardSubtitle, selector: "ion-card-subtitle", inputs: ["color", "mode"] }, { kind: "component", type: IonCheckbox, selector: "ion-checkbox", inputs: ["checked", "color", "disabled", "errorText", "helperText", "indeterminate", "justify", "labelPlacement", "mode", "name", "value"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }] }); }
4413
4601
  }
4414
4602
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CardComponent, decorators: [{
4415
4603
  type: Component,
@@ -4977,7 +5165,7 @@ class FileInputComponent {
4977
5165
  </div>
4978
5166
  <val-button [props]="contrastButton" (onClick)="fileInput.click()"></val-button>
4979
5167
  </div>
4980
- `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
5168
+ `, isInline: true, styles: [":root{--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}@media (prefers-color-scheme: dark){:root{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}.section{margin-top:1rem}.input{margin:.5rem 0}@media (min-width: 768px){.input{margin:.75rem 0}}.file-container{margin-top:.25rem}.name-container{display:flex;flex-direction:row;align-items:center}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
4981
5169
  }
4982
5170
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FileInputComponent, decorators: [{
4983
5171
  type: Component,
@@ -7833,7 +8021,7 @@ class ActionHeaderComponent {
7833
8021
  <val-display [props]="props.title" />
7834
8022
  <val-button [props]="props.action" />
7835
8023
  </section>
7836
- `, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
8024
+ `, isInline: true, styles: [".header-content-container{width:100%;display:flex;align-items:center;justify-content:space-between}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
7837
8025
  }
7838
8026
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ActionHeaderComponent, decorators: [{
7839
8027
  type: Component,
@@ -13626,7 +13814,7 @@ class RaffleStatusCardComponent {
13626
13814
  }
13627
13815
  </div>
13628
13816
  </article>
13629
- `, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
13817
+ `, isInline: true, styles: [":host{display:block}.raffle-card{display:flex;flex-direction:column;background:var(--ion-color-light);border-radius:12px;overflow:hidden;box-shadow:0 2px 8px #00000014;transition:transform .2s,box-shadow .2s}.raffle-card:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.card-image{position:relative;aspect-ratio:16/9;overflow:hidden}.card-image val-image{width:100%;height:100%}.card-image val-image ::ng-deep img{width:100%;height:100%;object-fit:cover}.status-badge{position:absolute;top:12px;left:12px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;padding:4px 10px;border-radius:4px}.status-badge.inline{position:static;margin-bottom:8px}.countdown-overlay{position:absolute;bottom:0;left:0;right:0;padding:8px 12px;background:linear-gradient(transparent,#000000b3)}.card-content{display:flex;flex-direction:column;gap:12px;padding:16px}.card-header{display:flex;flex-direction:column;gap:4px}.card-title{font-size:18px;font-weight:600;color:var(--ion-color-dark);margin:0;line-height:1.3}.card-description{font-size:14px;color:var(--ion-color-medium-shade);margin:0;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.beneficiary-info{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--ion-color-medium-shade)}.beneficiary-info ion-icon{font-size:16px;color:var(--ion-color-danger)}.beneficiary-info .beneficiary-image{width:24px;height:24px;border-radius:50%;overflow:hidden}.prize-preview{padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.prize-header{display:flex;align-items:center;gap:4px;font-size:11px;text-transform:uppercase;letter-spacing:.5px;color:var(--ion-color-medium);margin-bottom:4px}.prize-header ion-icon{font-size:14px;color:var(--ion-color-warning)}.prize-name{font-size:15px;font-weight:600;color:var(--ion-color-dark)}.prize-value{font-size:18px;font-weight:700;color:var(--ion-color-success);margin-top:4px}.progress-section{display:flex;flex-direction:column;gap:6px}.progress-header{display:flex;justify-content:space-between;align-items:center}.progress-label{font-size:12px;color:var(--ion-color-medium-shade)}.progress-value{font-size:14px;font-weight:600;color:var(--ion-color-dark)}ion-progress-bar{height:8px;border-radius:4px;--background: var(--ion-color-light-shade)}.ticket-stats{display:flex;align-items:center;justify-content:center;gap:16px;padding:12px;background:var(--ion-color-light-shade);border-radius:8px}.stat{display:flex;align-items:center;gap:6px;font-size:13px}.stat ion-icon{font-size:18px;color:var(--ion-color-primary)}.stat-value{font-weight:700;color:var(--ion-color-dark);font-size:16px}.stat-label{color:var(--ion-color-medium-shade)}.stat-divider{width:1px;height:24px;background:var(--ion-color-medium-tint)}.price-section{display:flex;justify-content:center;padding:8px 0}.dates-section{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--ion-color-medium-shade)}.date-item{display:flex;align-items:center;gap:6px}.date-item ion-icon{font-size:16px;color:var(--ion-color-primary)}.card-actions{display:flex;gap:8px;margin-top:4px}.card-actions val-button{flex:1}.variant-featured{border:2px solid var(--ion-color-warning)}.variant-featured .card-image{aspect-ratio:2/1}.variant-featured .card-title{font-size:22px}.variant-compact .card-content{padding:12px;gap:8px}.variant-compact .card-title{font-size:15px}.variant-compact .card-description{font-size:13px;-webkit-line-clamp:1}.variant-compact .ticket-stats,.variant-compact .dates-section{display:none}.variant-horizontal{flex-direction:row}.variant-horizontal .card-image{width:40%;min-width:150px;aspect-ratio:1}.variant-horizontal .card-content{flex:1;justify-content:center}.size-small .card-content{padding:12px;gap:8px}.size-small .card-title{font-size:15px}.size-small .card-description{font-size:12px}.size-small .ticket-stats{padding:8px}.size-small .stat-value{font-size:14px}.size-large .card-content{padding:24px;gap:16px}.size-large .card-title{font-size:24px}.size-large .card-description{font-size:16px}.size-large .prize-name{font-size:18px}.size-large .prize-value{font-size:22px}.status-upcoming{opacity:.9}.status-sold_out .card-image:after{content:\"\";position:absolute;inset:0;background:#0000004d}.status-completed .card-actions{opacity:.5;pointer-events:none}.status-cancelled{opacity:.6}.status-cancelled .card-image:after{content:\"\";position:absolute;inset:0;background:#0006}@media (max-width: 480px){.variant-horizontal{flex-direction:column}.variant-horizontal .card-image{width:100%;aspect-ratio:16/9}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }, { kind: "component", type: IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: CountdownComponent, selector: "val-countdown", inputs: ["props"], outputs: ["complete", "tick"] }, { kind: "component", type: PriceTagComponent, selector: "val-price-tag", inputs: ["props"] }] }); }
13630
13818
  }
13631
13819
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RaffleStatusCardComponent, decorators: [{
13632
13820
  type: Component,
@@ -15976,7 +16164,7 @@ class ArticleComponent {
15976
16164
  </ng-container>
15977
16165
  </div>
15978
16166
  </article>
15979
- `, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
16167
+ `, isInline: true, styles: [".val-article{width:100%;margin:0 auto;line-height:1.6;color:var(--ion-color-dark, #1f2937);display:block;position:static}.val-article--centered{margin:0 auto}.val-article--light{background-color:var(--ion-color-light, #ffffff);color:var(--ion-color-dark, #1f2937)}.val-article--dark{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article__element{position:relative}.val-article__element:last-child{margin-bottom:0!important}.val-article__spacing-top--none{margin-top:0!important}.val-article__spacing-top--small{margin-top:.5rem!important}.val-article__spacing-top--medium{margin-top:1rem!important}.val-article__spacing-top--large{margin-top:1.5rem!important}.val-article__spacing-top--xlarge{margin-top:2.5rem!important}.val-article__spacing-bottom--none{margin-bottom:0!important}.val-article__spacing-bottom--small{margin-bottom:.5rem!important}.val-article__spacing-bottom--medium{margin-bottom:1rem!important}.val-article__spacing-bottom--large{margin-bottom:1.5rem!important}.val-article__spacing-bottom--xlarge{margin-bottom:2.5rem!important}.val-article__spacing-horizontal--none{margin-left:0!important;margin-right:0!important}.val-article__spacing-horizontal--small{margin-left:.5rem!important;margin-right:.5rem!important}.val-article__spacing-horizontal--medium{margin-left:1rem!important;margin-right:1rem!important}.val-article__spacing-horizontal--large{margin-left:1.5rem!important;margin-right:1.5rem!important}.val-article__spacing-horizontal--xlarge{margin-left:2.5rem!important;margin-right:2.5rem!important}.val-article__quote{border-left:4px solid #0969da;padding:1rem;background-color:var(--ion-color-light-shade, #f8f9fa);border-radius:0 8px 8px 0;font-style:italic}.val-article__quote-content{margin-bottom:.5rem;font-size:1.1em}.val-article__quote-author{font-size:.9em;color:var(--ion-color-medium, #6c757d);font-style:normal;font-weight:500;text-align:right}.val-article__quote-source{font-weight:400;opacity:.8}.val-article__highlight{padding:1rem;background-color:var(--ion-color-warning-tint, #fff3cd);border:1px solid var(--ion-color-warning, #ffc107);border-radius:4px}.val-article__highlight--rounded{border-radius:12px}.val-article__code{border-radius:8px;overflow:hidden;border:1px solid #e1e5e9}.val-article__code-language{background-color:var(--ion-color-medium-tint, #f8f9fa);padding:.5rem 1rem;font-size:.875em;font-weight:500;color:var(--ion-color-dark, #495057);border-bottom:1px solid #e1e5e9;text-transform:uppercase;letter-spacing:.5px}.val-article__code-content{background-color:#f6f8fa;color:#24292e;padding:1rem;margin:0;overflow-x:auto;font-family:SFMono-Regular,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:.875em;line-height:1.5}.val-article__code-content code{background:none;padding:0;font-size:inherit;color:inherit}.val-article__code-content--dark{background-color:#161b22;color:#f0f6fc}.val-article__list{padding-left:1.5rem}.val-article__list--ordered{list-style-type:decimal}.val-article__list-item{margin-bottom:.5rem;line-height:1.6;position:relative}.val-article__list-item:last-child{margin-bottom:0}.val-article__list-check{color:var(--ion-color-success, #28a745);font-weight:700;margin-right:.5rem;position:absolute;left:-1.5rem}.val-article .val-article__list:has(.val-article__list-check){list-style:none;padding-left:1rem}.val-article__button-container--left{text-align:left}.val-article__button-container--center{text-align:center}.val-article__button-container--right{text-align:right}.val-article__separator{text-align:center}.val-article__separator-line{border:none;height:1px;background-color:#d0d7de;margin:0}.val-article__separator-line--thin{height:1px}.val-article__separator-line--thick{height:3px}.val-article__separator-dots{color:#d0d7de;font-size:1.5em;letter-spacing:.5em;-webkit-user-select:none;user-select:none}.val-article__separator-space{height:0}.val-article__image{text-align:center}.val-article__image--left{text-align:left}.val-article__image--center{text-align:center}.val-article__image--right{text-align:right}.val-article__image-content{max-width:100%;height:auto;border-radius:4px;box-shadow:0 2px 8px #0000001a;transition:transform .2s ease}.val-article__image-content:hover{transform:scale(1.02)}.val-article__image-content--rounded{border-radius:12px}.val-article__image-caption{margin-top:.5rem;font-size:.875em;color:var(--ion-color-medium, #6c757d);font-style:italic;text-align:center}.val-article__video{text-align:center}.val-article__video-content{max-width:100%;height:auto;border-radius:8px;box-shadow:0 4px 12px #00000026}.val-article__video-title{margin-top:.5rem;font-size:.9em;color:var(--ion-color-medium, #6c757d);font-weight:500}.val-article__custom *{max-width:100%}@media (max-width: 768px){.val-article__code-content{font-size:.8em;padding:.5rem}.val-article__image-content:hover{transform:none}.val-article__quote,.val-article__highlight{padding:.5rem}}@media (prefers-color-scheme: dark){.val-article--auto{background-color:var(--ion-color-dark, #1f2937);color:var(--ion-color-light, #ffffff)}.val-article--auto .val-article__quote{background-color:#ffffff0d}.val-article--auto .val-article__highlight{background-color:#ffc1071a;border-color:var(--ion-color-warning-shade, #e0a800)}.val-article--auto .val-article__code-content{background-color:#161b22;color:#f0f6fc}}.val-article__element{animation:fadeInUp .3s ease-out}@keyframes fadeInUp{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@media (prefers-contrast: high){.val-article__separator-line{background-color:currentColor;opacity:.5}.val-article__quote{border-left-width:6px}}@media (prefers-reduced-motion: reduce){.val-article .val-article__element{animation:none}.val-article__image-content{transition:none}.val-article__image-content:hover{transform:none}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: CodeDisplayComponent, selector: "val-code-display", inputs: ["props"] }, { kind: "component", type: NotesBoxComponent, selector: "val-notes-box", inputs: ["props"] }, { kind: "component", type: PlainCodeBoxComponent, selector: "val-plain-code-box", inputs: ["props"] }] }); }
15980
16168
  }
15981
16169
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ArticleComponent, decorators: [{
15982
16170
  type: Component,
@@ -19912,7 +20100,7 @@ class MenuComponent {
19912
20100
  />
19913
20101
  }
19914
20102
  </ion-menu>
19915
- `, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
20103
+ `, isInline: true, styles: ["ion-menu{--min-width: 75%}@media (min-width: 768px){ion-menu{--min-width: 40%}}ion-menu ion-header{background:var(--ion-color-light)}ion-menu ion-content{--background: var(--ion-color-light)}.menu-close-button{display:block;margin:1rem .5rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: IonMenu, selector: "ion-menu", inputs: ["contentId", "disabled", "maxEdgeStart", "menuId", "side", "swipeGesture", "type"] }, { kind: "component", type: IonButtons, selector: "ion-buttons", inputs: ["collapse"] }, { kind: "component", type: IonMenuToggle, selector: "ion-menu-toggle", inputs: ["autoHide", "menu"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonContent, selector: "ion-content", inputs: ["color", "fixedSlotPlacement", "forceOverscroll", "fullscreen", "scrollEvents", "scrollX", "scrollY"] }, { kind: "component", type: ImageComponent, selector: "val-image", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
19916
20104
  }
19917
20105
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MenuComponent, decorators: [{
19918
20106
  type: Component,
@@ -20393,7 +20581,7 @@ class PageTemplateComponent {
20393
20581
  </ion-row>
20394
20582
  }
20395
20583
  </ion-grid>
20396
- `, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["props"], outputs: ["onClick"] }] }); }
20584
+ `, isInline: true, styles: [".page-title{padding:0;font-size:1.6rem;font-weight:800}@media (min-width: 768px){.page-title{font-size:2rem}}.description-row{margin-bottom:16px}.description-container{margin-top:1rem}.back-row{margin-bottom:16px}.back-button{display:block;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonHeader, selector: "ion-header", inputs: ["collapse", "mode", "translucent"] }, { kind: "component", type: IonToolbar, selector: "ion-toolbar", inputs: ["color", "mode"] }, { kind: "component", type: IonTitle, selector: "ion-title", inputs: ["color", "size"] }, { kind: "component", type: ExpandableTextComponent, selector: "val-expandable-text", inputs: ["props"] }, { kind: "component", type: IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: IonRow, selector: "ion-row" }, { kind: "component", type: IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
20397
20585
  }
20398
20586
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PageTemplateComponent, decorators: [{
20399
20587
  type: Component,
@@ -21482,99 +21670,942 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
21482
21670
  */
21483
21671
 
21484
21672
  /**
21485
- * Firebase Configuration
21673
+ * Analytics Service (Firebase GA4)
21486
21674
  *
21487
- * Configuración e inicialización de Firebase para aplicaciones Angular.
21488
- * Usa provideValtechFirebase() en el bootstrap de tu aplicación.
21489
- */
21490
- /**
21491
- * Token de inyección para la configuración de Firebase.
21492
- * Usado internamente por los servicios de Firebase.
21675
+ * Servicio para tracking de eventos, page views y errores con Firebase Analytics.
21676
+ * Integra con el sistema de auth para user properties y respeta consent mode GDPR.
21493
21677
  */
21494
- const VALTECH_FIREBASE_CONFIG = new InjectionToken('ValtechFirebaseConfig');
21678
+ /** Key por defecto para persistir consent en localStorage */
21679
+ const DEFAULT_CONSENT_STORAGE_KEY = 'analytics_consent';
21680
+ /** Máximo de eventos en historial de debug */
21681
+ const MAX_DEBUG_HISTORY = 100;
21495
21682
  /**
21496
- * Provee Firebase a la aplicación Angular.
21683
+ * Servicio de Firebase Analytics (GA4).
21497
21684
  *
21498
- * @param config - Configuración de Firebase
21499
- * @returns EnvironmentProviders para usar en bootstrapApplication
21685
+ * Proporciona tracking de eventos, page views, errores y métricas de performance.
21686
+ * Soporta GDPR Consent Mode y modo debug para desarrollo.
21500
21687
  *
21501
21688
  * @example
21502
21689
  * ```typescript
21503
- * // main.ts
21504
- * import { bootstrapApplication } from '@angular/platform-browser';
21505
- * import { provideValtechFirebase } from 'valtech-components';
21506
- * import { environment } from './environments/environment';
21507
- *
21508
- * bootstrapApplication(AppComponent, {
21509
- * providers: [
21510
- * provideValtechFirebase({
21511
- * firebase: environment.firebase,
21512
- * persistence: true,
21513
- * emulator: environment.useEmulators ? {
21514
- * firestore: { host: 'localhost', port: 8080 },
21515
- * auth: { host: 'localhost', port: 9099 },
21516
- * storage: { host: 'localhost', port: 9199 },
21517
- * } : undefined,
21518
- * }),
21519
- * ],
21520
- * });
21690
+ * @Component({...})
21691
+ * export class ProductComponent {
21692
+ * private analytics = inject(AnalyticsService);
21693
+ *
21694
+ * onPurchase(product: Product) {
21695
+ * this.analytics.logEvent('purchase', {
21696
+ * transaction_id: order.id,
21697
+ * value: order.total,
21698
+ * currency: 'EUR'
21699
+ * });
21700
+ * }
21701
+ * }
21521
21702
  * ```
21522
21703
  */
21523
- function provideValtechFirebase(config) {
21524
- // Construir array de providers base
21525
- const providers = [
21526
- // Guardar configuración para uso en servicios
21527
- { provide: VALTECH_FIREBASE_CONFIG, useValue: config },
21528
- // Inicializar Firebase App
21529
- provideFirebaseApp(() => initializeApp(config.firebase)),
21530
- // Firestore con soporte para emuladores y persistencia
21531
- provideFirestore(() => {
21532
- const firestore = getFirestore();
21533
- // Conectar a emulador si está configurado
21534
- if (config.emulator?.firestore) {
21535
- connectFirestoreEmulator(firestore, config.emulator.firestore.host, config.emulator.firestore.port);
21536
- }
21537
- // Habilitar persistencia offline si está configurada
21538
- if (config.persistence) {
21539
- enableIndexedDbPersistence(firestore).catch((err) => {
21540
- if (err.code === 'failed-precondition') {
21541
- console.warn('[ValtechFirebase] Persistencia no disponible: múltiples pestañas abiertas');
21542
- }
21543
- else if (err.code === 'unimplemented') {
21544
- console.warn('[ValtechFirebase] Persistencia no soportada en este navegador');
21545
- }
21546
- });
21547
- }
21548
- return firestore;
21549
- }),
21550
- // Auth con soporte para emulador
21551
- provideAuth(() => {
21552
- const auth = getAuth();
21553
- // Conectar a emulador si está configurado
21554
- if (config.emulator?.auth) {
21555
- connectAuthEmulator(auth, `http://${config.emulator.auth.host}:${config.emulator.auth.port}`, { disableWarnings: true });
21556
- }
21557
- return auth;
21558
- }),
21559
- // Storage con soporte para emulador
21560
- provideStorage(() => {
21561
- const storage = getStorage();
21562
- // Conectar a emulador si está configurado
21563
- if (config.emulator?.storage) {
21564
- connectStorageEmulator(storage, config.emulator.storage.host, config.emulator.storage.port);
21565
- }
21566
- return storage;
21567
- }),
21568
- ];
21569
- // Messaging (FCM) - solo si está explícitamente habilitado
21570
- // Requiere Service Worker configurado, puede congelar la app si no está disponible
21571
- if (config.enableMessaging) {
21572
- // Pre-registrar SW custom antes de que Firebase lo intente
21573
- if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
21574
- navigator.serviceWorker.register('/firebase-messaging-sw.js').catch(console.error);
21704
+ class AnalyticsService {
21705
+ constructor(injector, config, platformId) {
21706
+ this.injector = injector;
21707
+ this.config = config;
21708
+ this.platformId = platformId;
21709
+ // ===========================================================================
21710
+ // ESTADO (Signals)
21711
+ // ===========================================================================
21712
+ this._consentState = signal({
21713
+ settings: {},
21714
+ updatedAt: null,
21715
+ hasDecided: false,
21716
+ });
21717
+ this._isDebugMode = signal(false);
21718
+ this._debugHistory = signal([]);
21719
+ /** Estado de consentimiento actual (readonly) */
21720
+ this.consentState = this._consentState.asReadonly();
21721
+ /** Indica si está en modo debug */
21722
+ this.isDebugMode = this._isDebugMode.asReadonly();
21723
+ /** Indica si analytics está habilitado y funcionando */
21724
+ this.isEnabled = computed(() => {
21725
+ return this.isAnalyticsSupported() && this._consentState().settings.analytics === true;
21726
+ });
21727
+ this.analyticsConfig = config.analyticsConfig ?? {};
21728
+ this.consentStorageKey =
21729
+ this.analyticsConfig.consentStorageKey ?? DEFAULT_CONSENT_STORAGE_KEY;
21730
+ this.eventPrefix = this.analyticsConfig.eventPrefix ?? '';
21731
+ this.samplingRate = this.analyticsConfig.samplingRate ?? 1.0;
21732
+ this.initializeAnalytics();
21733
+ }
21734
+ // ===========================================================================
21735
+ // INICIALIZACIÓN
21736
+ // ===========================================================================
21737
+ /**
21738
+ * Inicializa el servicio de analytics
21739
+ */
21740
+ initializeAnalytics() {
21741
+ if (!isPlatformBrowser(this.platformId)) {
21742
+ return;
21743
+ }
21744
+ // Cargar consent desde localStorage
21745
+ this.loadConsentFromStorage();
21746
+ // Configurar debug mode
21747
+ const debugMode = this.analyticsConfig.debugMode ?? false;
21748
+ this._isDebugMode.set(debugMode);
21749
+ // Aplicar consent inicial a gtag
21750
+ this.applyConsentToGtag(this._consentState().settings);
21751
+ // Setear user properties por defecto
21752
+ if (this.analyticsConfig.defaultUserProperties) {
21753
+ this.setUserProperties(this.analyticsConfig.defaultUserProperties);
21754
+ }
21755
+ if (debugMode) {
21756
+ console.log('[Analytics] Inicializado en modo debug - eventos NO se envían a Firebase');
21757
+ }
21758
+ }
21759
+ /**
21760
+ * Obtiene la instancia de Analytics de forma perezosa.
21761
+ * Esto evita el error de APP_INITIALIZER de AngularFire.
21762
+ */
21763
+ getAnalyticsInstance() {
21764
+ if (!this.config.enableAnalytics) {
21765
+ return null;
21766
+ }
21767
+ try {
21768
+ return this.injector.get(Analytics, null);
21769
+ }
21770
+ catch {
21771
+ return null;
21772
+ }
21773
+ }
21774
+ /**
21775
+ * Verifica si Analytics está soportado
21776
+ */
21777
+ isAnalyticsSupported() {
21778
+ if (!isPlatformBrowser(this.platformId)) {
21779
+ return false;
21780
+ }
21781
+ if (!this.config.enableAnalytics) {
21782
+ return false;
21783
+ }
21784
+ if (!this.config.firebase.measurementId) {
21785
+ console.warn('[Analytics] measurementId no configurado en firebase config');
21786
+ return false;
21787
+ }
21788
+ return true;
21789
+ }
21790
+ /**
21791
+ * Verifica si debe enviar el evento (sampling)
21792
+ */
21793
+ shouldSample() {
21794
+ if (this.samplingRate >= 1.0) {
21795
+ return true;
21796
+ }
21797
+ return Math.random() < this.samplingRate;
21798
+ }
21799
+ // ===========================================================================
21800
+ // PAGE VIEWS
21801
+ // ===========================================================================
21802
+ /**
21803
+ * Registra un page view.
21804
+ * Normalmente se usa automáticamente via AnalyticsRouterTracker.
21805
+ *
21806
+ * @param pagePath - Ruta de la página (ej: '/products/123')
21807
+ * @param pageTitle - Título de la página (opcional)
21808
+ */
21809
+ logPageView(pagePath, pageTitle) {
21810
+ this.logEvent('page_view', {
21811
+ page_path: pagePath,
21812
+ page_title: pageTitle ?? document?.title,
21813
+ page_location: window?.location?.href,
21814
+ });
21815
+ }
21816
+ /**
21817
+ * Registra un screen view (para apps tipo SPA).
21818
+ *
21819
+ * @param screenName - Nombre del screen
21820
+ * @param screenClass - Clase del screen (opcional)
21821
+ */
21822
+ logScreenView(screenName, screenClass) {
21823
+ this.logEvent('screen_view', {
21824
+ screen_name: screenName,
21825
+ screen_class: screenClass,
21826
+ });
21827
+ }
21828
+ // ===========================================================================
21829
+ // EVENTOS
21830
+ // ===========================================================================
21831
+ /**
21832
+ * Registra un evento tipado GA4.
21833
+ *
21834
+ * @param eventName - Nombre del evento (tipado)
21835
+ * @param params - Parámetros del evento (tipados según el nombre)
21836
+ *
21837
+ * @example
21838
+ * ```typescript
21839
+ * // Evento tipado con autocompletado
21840
+ * analytics.logEvent('add_to_cart', {
21841
+ * item_id: '123',
21842
+ * item_name: 'Producto',
21843
+ * value: 99.99,
21844
+ * currency: 'EUR'
21845
+ * });
21846
+ * ```
21847
+ */
21848
+ logEvent(eventName, params) {
21849
+ this.trackEvent(eventName, params);
21850
+ }
21851
+ /**
21852
+ * Registra un evento custom con parámetros libres.
21853
+ * Usar cuando el evento no está en el catálogo tipado.
21854
+ *
21855
+ * @param eventName - Nombre del evento custom
21856
+ * @param params - Parámetros libres
21857
+ */
21858
+ logCustomEvent(eventName, params) {
21859
+ const prefixedName = this.eventPrefix + eventName;
21860
+ this.trackEvent(prefixedName, params);
21861
+ }
21862
+ /**
21863
+ * Lógica común para enviar eventos
21864
+ */
21865
+ trackEvent(eventName, params) {
21866
+ // Verificar consent
21867
+ if (!this._consentState().settings.analytics) {
21868
+ this.addToDebugHistory('event', eventName, params, false);
21869
+ return;
21870
+ }
21871
+ // Aplicar sampling
21872
+ if (!this.shouldSample()) {
21873
+ return;
21874
+ }
21875
+ // Debug mode: solo log, no enviar
21876
+ if (this._isDebugMode()) {
21877
+ this.addToDebugHistory('event', eventName, params, false);
21878
+ console.log(`[Analytics] Event: ${eventName}`, params);
21879
+ return;
21880
+ }
21881
+ // Enviar a Firebase
21882
+ const analytics = this.getAnalyticsInstance();
21883
+ if (analytics) {
21884
+ try {
21885
+ logEvent(analytics, eventName, params);
21886
+ this.addToDebugHistory('event', eventName, params, true);
21887
+ }
21888
+ catch (error) {
21889
+ console.error('[Analytics] Error enviando evento:', error);
21890
+ }
21891
+ }
21892
+ }
21893
+ // ===========================================================================
21894
+ // ECOMMERCE
21895
+ // ===========================================================================
21896
+ /**
21897
+ * Registra vista de item
21898
+ */
21899
+ logViewItem(item) {
21900
+ this.logEvent('view_item', {
21901
+ item_id: item.item_id,
21902
+ item_name: item.item_name,
21903
+ value: item.price,
21904
+ currency: item.currency,
21905
+ });
21906
+ }
21907
+ /**
21908
+ * Registra agregar al carrito
21909
+ */
21910
+ logAddToCart(item, quantity = 1) {
21911
+ this.logEvent('add_to_cart', {
21912
+ item_id: item.item_id,
21913
+ item_name: item.item_name,
21914
+ value: (item.price ?? 0) * quantity,
21915
+ currency: item.currency,
21916
+ quantity,
21917
+ });
21918
+ }
21919
+ /**
21920
+ * Registra inicio de checkout
21921
+ */
21922
+ logBeginCheckout(items, value, currency = 'EUR') {
21923
+ this.logEvent('begin_checkout', {
21924
+ value,
21925
+ currency,
21926
+ items,
21927
+ });
21928
+ }
21929
+ /**
21930
+ * Registra compra completada
21931
+ */
21932
+ logPurchase(transactionId, items, value, currency = 'EUR') {
21933
+ this.logEvent('purchase', {
21934
+ transaction_id: transactionId,
21935
+ value,
21936
+ currency,
21937
+ items,
21938
+ });
21939
+ }
21940
+ // ===========================================================================
21941
+ // USER PROPERTIES
21942
+ // ===========================================================================
21943
+ /**
21944
+ * Setea el userId para asociar eventos con el usuario.
21945
+ * Llamado automáticamente si enableAuthIntegration=true.
21946
+ *
21947
+ * @param userId - ID del usuario o null para limpiar
21948
+ */
21949
+ setUserId(userId) {
21950
+ if (!this.isAnalyticsSupported()) {
21951
+ return;
21952
+ }
21953
+ if (this._isDebugMode()) {
21954
+ console.log(`[Analytics] Set userId: ${userId}`);
21955
+ this.addToDebugHistory('user_property', 'user_id', { userId }, false);
21956
+ return;
21957
+ }
21958
+ const analytics = this.getAnalyticsInstance();
21959
+ if (analytics) {
21960
+ try {
21961
+ setUserId(analytics, userId);
21962
+ }
21963
+ catch (error) {
21964
+ console.error('[Analytics] Error seteando userId:', error);
21965
+ }
21966
+ }
21967
+ }
21968
+ /**
21969
+ * Setea propiedades del usuario para segmentación.
21970
+ *
21971
+ * @param properties - Propiedades key-value
21972
+ *
21973
+ * @example
21974
+ * ```typescript
21975
+ * analytics.setUserProperties({
21976
+ * subscription_tier: 'premium',
21977
+ * preferred_language: 'es'
21978
+ * });
21979
+ * ```
21980
+ */
21981
+ setUserProperties(properties) {
21982
+ if (!this.isAnalyticsSupported()) {
21983
+ return;
21984
+ }
21985
+ if (this._isDebugMode()) {
21986
+ console.log('[Analytics] Set user properties:', properties);
21987
+ this.addToDebugHistory('user_property', 'properties', properties, false);
21988
+ return;
21989
+ }
21990
+ const analytics = this.getAnalyticsInstance();
21991
+ if (analytics) {
21992
+ try {
21993
+ // Convertir a Record<string, string> para Firebase
21994
+ const stringProps = {};
21995
+ for (const [key, value] of Object.entries(properties)) {
21996
+ if (value !== undefined) {
21997
+ stringProps[key] = String(value);
21998
+ }
21999
+ }
22000
+ setUserProperties(analytics, stringProps);
22001
+ }
22002
+ catch (error) {
22003
+ console.error('[Analytics] Error seteando user properties:', error);
22004
+ }
22005
+ }
22006
+ }
22007
+ /**
22008
+ * Setea la organización activa (multi-tenant).
22009
+ * Llamado automáticamente si enableAuthIntegration=true.
22010
+ *
22011
+ * @param orgId - ID de la organización o null
22012
+ */
22013
+ setActiveOrganization(orgId) {
22014
+ if (orgId) {
22015
+ this.setUserProperties({ active_organization: orgId });
22016
+ }
22017
+ }
22018
+ // ===========================================================================
22019
+ // ERROR TRACKING
22020
+ // ===========================================================================
22021
+ /**
22022
+ * Registra un error para tracking.
22023
+ * Integra automáticamente con Angular ErrorHandler si enableErrorTracking=true.
22024
+ *
22025
+ * @param error - Error o mensaje de error
22026
+ * @param context - Contexto adicional
22027
+ *
22028
+ * @example
22029
+ * ```typescript
22030
+ * try {
22031
+ * await riskyOperation();
22032
+ * } catch (error) {
22033
+ * analytics.logError(error, { context: 'checkout_flow' });
22034
+ * }
22035
+ * ```
22036
+ */
22037
+ logError(error, context) {
22038
+ const errorMessage = error instanceof Error ? error.message : error;
22039
+ const errorStack = error instanceof Error ? error.stack : undefined;
22040
+ const errorType = error instanceof Error ? error.name : 'Error';
22041
+ this.logEvent('error_occurred', {
22042
+ error_type: errorType,
22043
+ error_message: errorMessage.substring(0, 100), // Limitar longitud
22044
+ error_stack: errorStack?.substring(0, 500),
22045
+ context: context ? JSON.stringify(context) : undefined,
22046
+ });
22047
+ }
22048
+ /**
22049
+ * Registra un error fatal (crash-level).
22050
+ */
22051
+ logFatalError(error, context) {
22052
+ this.logError(error, { ...context, severity: 'fatal' });
22053
+ }
22054
+ // ===========================================================================
22055
+ // CONSENT MODE (GDPR)
22056
+ // ===========================================================================
22057
+ /**
22058
+ * Actualiza el estado de consentimiento del usuario.
22059
+ * Afecta qué datos se recolectan y envían.
22060
+ *
22061
+ * @param consent - Settings de consentimiento
22062
+ *
22063
+ * @example
22064
+ * ```typescript
22065
+ * // Usuario acepta todo
22066
+ * analytics.updateConsent({ analytics: true, advertising: true });
22067
+ *
22068
+ * // Usuario rechaza publicidad
22069
+ * analytics.updateConsent({ analytics: true, advertising: false });
22070
+ * ```
22071
+ */
22072
+ updateConsent(consent) {
22073
+ const newState = {
22074
+ settings: { ...this._consentState().settings, ...consent },
22075
+ updatedAt: new Date(),
22076
+ hasDecided: true,
22077
+ };
22078
+ this._consentState.set(newState);
22079
+ this.saveConsentToStorage(newState);
22080
+ this.applyConsentToGtag(newState.settings);
22081
+ this.addToDebugHistory('consent', 'update', consent, false);
22082
+ }
22083
+ /**
22084
+ * Deniega todo consentimiento.
22085
+ */
22086
+ denyAllConsent() {
22087
+ this.updateConsent({
22088
+ analytics: false,
22089
+ advertising: false,
22090
+ functionality: false,
22091
+ security: true, // Siempre permitido
22092
+ });
22093
+ }
22094
+ /**
22095
+ * Acepta todo consentimiento.
22096
+ */
22097
+ grantAllConsent() {
22098
+ this.updateConsent({
22099
+ analytics: true,
22100
+ advertising: true,
22101
+ functionality: true,
22102
+ security: true,
22103
+ });
22104
+ }
22105
+ /**
22106
+ * Obtiene el estado actual de consentimiento.
22107
+ */
22108
+ getConsentState() {
22109
+ return this._consentState();
22110
+ }
22111
+ /**
22112
+ * Aplica consent settings a gtag (GA4 Consent Mode v2)
22113
+ */
22114
+ applyConsentToGtag(settings) {
22115
+ if (!isPlatformBrowser(this.platformId)) {
22116
+ return;
22117
+ }
22118
+ const gtag = window.gtag;
22119
+ if (typeof gtag !== 'function') {
22120
+ return;
22121
+ }
22122
+ try {
22123
+ gtag('consent', 'update', {
22124
+ analytics_storage: settings.analytics ? 'granted' : 'denied',
22125
+ ad_storage: settings.advertising ? 'granted' : 'denied',
22126
+ ad_user_data: settings.advertising ? 'granted' : 'denied',
22127
+ ad_personalization: settings.advertising ? 'granted' : 'denied',
22128
+ functionality_storage: settings.functionality ? 'granted' : 'denied',
22129
+ security_storage: settings.security !== false ? 'granted' : 'denied',
22130
+ });
22131
+ }
22132
+ catch (error) {
22133
+ console.warn('[Analytics] Error aplicando consent a gtag:', error);
22134
+ }
22135
+ }
22136
+ /**
22137
+ * Carga consent desde localStorage
22138
+ */
22139
+ loadConsentFromStorage() {
22140
+ if (!isPlatformBrowser(this.platformId)) {
22141
+ return;
22142
+ }
22143
+ try {
22144
+ const stored = localStorage.getItem(this.consentStorageKey);
22145
+ if (stored) {
22146
+ const parsed = JSON.parse(stored);
22147
+ this._consentState.set({
22148
+ ...parsed,
22149
+ updatedAt: parsed.updatedAt ? new Date(parsed.updatedAt) : null,
22150
+ });
22151
+ }
22152
+ else if (this.analyticsConfig.defaultConsent) {
22153
+ // Aplicar consent por defecto si no hay guardado
22154
+ this._consentState.set({
22155
+ settings: this.analyticsConfig.defaultConsent,
22156
+ updatedAt: null,
22157
+ hasDecided: false,
22158
+ });
22159
+ }
22160
+ }
22161
+ catch (error) {
22162
+ console.warn('[Analytics] Error cargando consent:', error);
22163
+ }
22164
+ }
22165
+ /**
22166
+ * Guarda consent en localStorage
22167
+ */
22168
+ saveConsentToStorage(state) {
22169
+ if (!isPlatformBrowser(this.platformId)) {
22170
+ return;
22171
+ }
22172
+ try {
22173
+ localStorage.setItem(this.consentStorageKey, JSON.stringify(state));
22174
+ }
22175
+ catch (error) {
22176
+ console.warn('[Analytics] Error guardando consent:', error);
22177
+ }
22178
+ }
22179
+ // ===========================================================================
22180
+ // TIMING / PERFORMANCE
22181
+ // ===========================================================================
22182
+ /**
22183
+ * Registra una métrica de timing/performance.
22184
+ *
22185
+ * @param name - Nombre de la métrica
22186
+ * @param valueMs - Valor en milisegundos
22187
+ * @param params - Parámetros adicionales
22188
+ *
22189
+ * @example
22190
+ * ```typescript
22191
+ * const start = performance.now();
22192
+ * await loadData();
22193
+ * analytics.logTiming('data_load', performance.now() - start, {
22194
+ * category: 'api',
22195
+ * endpoint: '/products'
22196
+ * });
22197
+ * ```
22198
+ */
22199
+ logTiming(name, valueMs, params) {
22200
+ this.logEvent('performance_metric', {
22201
+ metric_name: name,
22202
+ value: Math.round(valueMs),
22203
+ unit: 'ms',
22204
+ ...params,
22205
+ });
22206
+ }
22207
+ // ===========================================================================
22208
+ // DEBUG
22209
+ // ===========================================================================
22210
+ /**
22211
+ * Habilita/deshabilita modo debug.
22212
+ * En debug: logea a consola, no envía a Firebase.
22213
+ */
22214
+ setDebugMode(enabled) {
22215
+ this._isDebugMode.set(enabled);
22216
+ console.log(`[Analytics] Debug mode: ${enabled ? 'ON' : 'OFF'}`);
22217
+ }
22218
+ /**
22219
+ * Obtiene historial de eventos (solo en debug mode).
22220
+ * Útil para testing y desarrollo.
22221
+ */
22222
+ getDebugHistory() {
22223
+ return this._debugHistory();
22224
+ }
22225
+ /**
22226
+ * Limpia historial de debug.
22227
+ */
22228
+ clearDebugHistory() {
22229
+ this._debugHistory.set([]);
22230
+ }
22231
+ /**
22232
+ * Agrega evento al historial de debug
22233
+ */
22234
+ addToDebugHistory(type, name, params, sent = false) {
22235
+ if (!this._isDebugMode()) {
22236
+ return;
22237
+ }
22238
+ const event = {
22239
+ timestamp: new Date(),
22240
+ type,
22241
+ name,
22242
+ params,
22243
+ sent,
22244
+ };
22245
+ this._debugHistory.update((history) => {
22246
+ const newHistory = [event, ...history];
22247
+ return newHistory.slice(0, MAX_DEBUG_HISTORY);
22248
+ });
22249
+ }
22250
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, deps: [{ token: i0.Injector }, { token: VALTECH_FIREBASE_CONFIG }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
22251
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, providedIn: 'root' }); }
22252
+ }
22253
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsService, decorators: [{
22254
+ type: Injectable,
22255
+ args: [{ providedIn: 'root' }]
22256
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
22257
+ type: Inject,
22258
+ args: [VALTECH_FIREBASE_CONFIG]
22259
+ }] }, { type: Object, decorators: [{
22260
+ type: Inject,
22261
+ args: [PLATFORM_ID]
22262
+ }] }] });
22263
+
22264
+ var analytics_service = /*#__PURE__*/Object.freeze({
22265
+ __proto__: null,
22266
+ AnalyticsService: AnalyticsService
22267
+ });
22268
+
22269
+ /**
22270
+ * Analytics Error Handler
22271
+ *
22272
+ * ErrorHandler personalizado que envía errores no capturados a Firebase Analytics.
22273
+ * Se activa si enableErrorTracking=true en analyticsConfig.
22274
+ */
22275
+ /**
22276
+ * ErrorHandler que trackea errores en Firebase Analytics.
22277
+ *
22278
+ * Captura errores no manejados de la aplicación y los envía a GA4
22279
+ * como eventos 'error_occurred'. También delega al ErrorHandler
22280
+ * default para mantener el comportamiento de console.error.
22281
+ *
22282
+ * @example
22283
+ * ```typescript
22284
+ * // Se activa automáticamente si enableErrorTracking=true
22285
+ * provideValtechFirebase({
22286
+ * firebase: environment.firebase,
22287
+ * enableAnalytics: true,
22288
+ * analyticsConfig: {
22289
+ * enableErrorTracking: true,
22290
+ * },
22291
+ * });
22292
+ * ```
22293
+ */
22294
+ class AnalyticsErrorHandler {
22295
+ constructor() {
22296
+ this.analytics = inject(AnalyticsService);
22297
+ this.defaultHandler = new ErrorHandler();
22298
+ }
22299
+ /**
22300
+ * Maneja un error no capturado.
22301
+ * Envía el error a Analytics y luego al handler default.
22302
+ */
22303
+ handleError(error) {
22304
+ // Enviar a Analytics
22305
+ try {
22306
+ this.trackError(error);
22307
+ }
22308
+ catch (trackingError) {
22309
+ // No fallar si el tracking falla
22310
+ console.warn('[AnalyticsErrorHandler] Error tracking failed:', trackingError);
22311
+ }
22312
+ // Delegar al handler default (console.error)
22313
+ this.defaultHandler.handleError(error);
22314
+ }
22315
+ /**
22316
+ * Trackea el error en Analytics
22317
+ */
22318
+ trackError(error) {
22319
+ // Extraer información del error
22320
+ const errorInfo = this.extractErrorInfo(error);
22321
+ this.analytics.logError(errorInfo.error, {
22322
+ source: 'uncaught',
22323
+ url: this.getCurrentUrl(),
22324
+ ...errorInfo.context,
22325
+ });
22326
+ }
22327
+ /**
22328
+ * Extrae información útil del error
22329
+ */
22330
+ extractErrorInfo(error) {
22331
+ const context = {};
22332
+ // Error estándar
22333
+ if (error instanceof Error) {
22334
+ // Detectar errores de chunk loading (lazy loading)
22335
+ if (error.message.includes('Loading chunk')) {
22336
+ context['error_category'] = 'chunk_loading';
22337
+ }
22338
+ // Detectar errores de red
22339
+ if (error.message.includes('NetworkError') || error.message.includes('Failed to fetch')) {
22340
+ context['error_category'] = 'network';
22341
+ }
22342
+ return { error, context };
22343
+ }
22344
+ // ErrorEvent (ej: errores de script)
22345
+ if (typeof ErrorEvent !== 'undefined' && error instanceof ErrorEvent) {
22346
+ return {
22347
+ error: new Error(error.message || 'Script error'),
22348
+ context: {
22349
+ filename: error.filename || 'unknown',
22350
+ lineno: String(error.lineno || 0),
22351
+ colno: String(error.colno || 0),
22352
+ },
22353
+ };
22354
+ }
22355
+ // PromiseRejection
22356
+ if (this.isPromiseRejection(error)) {
22357
+ const reason = error.reason;
22358
+ if (reason instanceof Error) {
22359
+ return {
22360
+ error: reason,
22361
+ context: { error_category: 'unhandled_promise' },
22362
+ };
22363
+ }
22364
+ return {
22365
+ error: new Error(String(reason) || 'Unhandled promise rejection'),
22366
+ context: { error_category: 'unhandled_promise' },
22367
+ };
22368
+ }
22369
+ // Objeto con message
22370
+ if (error && typeof error === 'object' && 'message' in error) {
22371
+ return {
22372
+ error: new Error(String(error.message)),
22373
+ context,
22374
+ };
22375
+ }
22376
+ // Fallback: convertir a string
22377
+ return {
22378
+ error: new Error(String(error) || 'Unknown error'),
22379
+ context,
22380
+ };
22381
+ }
22382
+ /**
22383
+ * Verifica si es un PromiseRejectionEvent
22384
+ */
22385
+ isPromiseRejection(error) {
22386
+ return (typeof PromiseRejectionEvent !== 'undefined' &&
22387
+ error instanceof PromiseRejectionEvent);
22388
+ }
22389
+ /**
22390
+ * Obtiene la URL actual de forma segura
22391
+ */
22392
+ getCurrentUrl() {
22393
+ try {
22394
+ return window?.location?.href || 'unknown';
22395
+ }
22396
+ catch {
22397
+ return 'unknown';
22398
+ }
22399
+ }
22400
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22401
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler }); }
22402
+ }
22403
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsErrorHandler, decorators: [{
22404
+ type: Injectable
22405
+ }] });
22406
+
22407
+ /**
22408
+ * Analytics Router Tracker
22409
+ *
22410
+ * Servicio que trackea automáticamente page views cuando el usuario navega.
22411
+ * Se activa automáticamente si enablePageViewTracking=true en analyticsConfig.
22412
+ */
22413
+ /**
22414
+ * Tracker automático de page views via Router.
22415
+ *
22416
+ * Este servicio escucha eventos de navegación del Router y registra
22417
+ * page views automáticamente en Firebase Analytics.
22418
+ *
22419
+ * Se excluyen rutas configuradas en `analyticsConfig.excludeRoutes`.
22420
+ *
22421
+ * @example
22422
+ * ```typescript
22423
+ * // Se activa automáticamente si enablePageViewTracking=true
22424
+ * provideValtechFirebase({
22425
+ * firebase: environment.firebase,
22426
+ * enableAnalytics: true,
22427
+ * analyticsConfig: {
22428
+ * enablePageViewTracking: true,
22429
+ * excludeRoutes: ['/admin/*', '/debug/*'],
22430
+ * },
22431
+ * });
22432
+ * ```
22433
+ */
22434
+ class AnalyticsRouterTracker {
22435
+ constructor(config) {
22436
+ this.config = config;
22437
+ this.analytics = inject(AnalyticsService);
22438
+ this.router = inject(Router);
22439
+ this.destroyRef = inject(DestroyRef);
22440
+ const analyticsConfig = config.analyticsConfig ?? {};
22441
+ this.enabled = analyticsConfig.enablePageViewTracking !== false;
22442
+ this.excludePatterns = this.compileExcludePatterns(analyticsConfig.excludeRoutes ?? []);
22443
+ if (this.enabled && config.enableAnalytics) {
22444
+ this.startTracking();
22445
+ }
22446
+ }
22447
+ /**
22448
+ * Inicia el tracking de navegación
22449
+ */
22450
+ startTracking() {
22451
+ this.router.events
22452
+ .pipe(filter$1((event) => event instanceof NavigationEnd), filter$1((event) => !this.isExcluded(event.urlAfterRedirects)), takeUntilDestroyed(this.destroyRef))
22453
+ .subscribe((event) => {
22454
+ this.analytics.logPageView(event.urlAfterRedirects);
22455
+ });
22456
+ }
22457
+ /**
22458
+ * Compila patrones de exclusión a RegExp
22459
+ */
22460
+ compileExcludePatterns(patterns) {
22461
+ return patterns.map((pattern) => {
22462
+ // Convertir glob pattern a regex
22463
+ // Ej: '/admin/*' -> /^\/admin\/.*$/
22464
+ const regexPattern = pattern
22465
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escapar caracteres especiales
22466
+ .replace(/\*/g, '.*'); // Convertir * a .*
22467
+ return new RegExp(`^${regexPattern}$`);
22468
+ });
22469
+ }
22470
+ /**
22471
+ * Verifica si una URL debe ser excluida del tracking
22472
+ */
22473
+ isExcluded(url) {
22474
+ // Remover query params para la comparación
22475
+ const path = url.split('?')[0];
22476
+ return this.excludePatterns.some((pattern) => pattern.test(path));
22477
+ }
22478
+ /**
22479
+ * Registra un page view manualmente.
22480
+ * Útil para casos donde necesitas trackear manualmente.
22481
+ */
22482
+ trackPageView(path, title) {
22483
+ if (this.isExcluded(path)) {
22484
+ return;
22485
+ }
22486
+ this.analytics.logPageView(path, title);
22487
+ }
22488
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, deps: [{ token: VALTECH_FIREBASE_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
22489
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, providedIn: 'root' }); }
22490
+ }
22491
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AnalyticsRouterTracker, decorators: [{
22492
+ type: Injectable,
22493
+ args: [{ providedIn: 'root' }]
22494
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
22495
+ type: Inject,
22496
+ args: [VALTECH_FIREBASE_CONFIG]
22497
+ }] }] });
22498
+
22499
+ /**
22500
+ * Firebase Configuration
22501
+ *
22502
+ * Configuración e inicialización de Firebase para aplicaciones Angular.
22503
+ * Usa provideValtechFirebase() en el bootstrap de tu aplicación.
22504
+ */
22505
+ /**
22506
+ * Token de inyección para la configuración de Firebase.
22507
+ * Usado internamente por los servicios de Firebase.
22508
+ */
22509
+ const VALTECH_FIREBASE_CONFIG = new InjectionToken('ValtechFirebaseConfig');
22510
+ /**
22511
+ * Provee Firebase a la aplicación Angular.
22512
+ *
22513
+ * @param config - Configuración de Firebase
22514
+ * @returns EnvironmentProviders para usar en bootstrapApplication
22515
+ *
22516
+ * @example
22517
+ * ```typescript
22518
+ * // main.ts
22519
+ * import { bootstrapApplication } from '@angular/platform-browser';
22520
+ * import { provideValtechFirebase } from 'valtech-components';
22521
+ * import { environment } from './environments/environment';
22522
+ *
22523
+ * bootstrapApplication(AppComponent, {
22524
+ * providers: [
22525
+ * provideValtechFirebase({
22526
+ * firebase: environment.firebase,
22527
+ * persistence: true,
22528
+ * emulator: environment.useEmulators ? {
22529
+ * firestore: { host: 'localhost', port: 8080 },
22530
+ * auth: { host: 'localhost', port: 9099 },
22531
+ * storage: { host: 'localhost', port: 9199 },
22532
+ * } : undefined,
22533
+ * }),
22534
+ * ],
22535
+ * });
22536
+ * ```
22537
+ */
22538
+ function provideValtechFirebase(config) {
22539
+ // Construir array de providers base
22540
+ const providers = [
22541
+ // Guardar configuración para uso en servicios
22542
+ { provide: VALTECH_FIREBASE_CONFIG, useValue: config },
22543
+ // Inicializar Firebase App
22544
+ provideFirebaseApp(() => initializeApp(config.firebase)),
22545
+ // Firestore con soporte para emuladores y persistencia
22546
+ provideFirestore(() => {
22547
+ const firestore = getFirestore();
22548
+ // Conectar a emulador si está configurado
22549
+ if (config.emulator?.firestore) {
22550
+ connectFirestoreEmulator(firestore, config.emulator.firestore.host, config.emulator.firestore.port);
22551
+ }
22552
+ // Habilitar persistencia offline si está configurada
22553
+ if (config.persistence) {
22554
+ enableIndexedDbPersistence(firestore).catch((err) => {
22555
+ if (err.code === 'failed-precondition') {
22556
+ console.warn('[ValtechFirebase] Persistencia no disponible: múltiples pestañas abiertas');
22557
+ }
22558
+ else if (err.code === 'unimplemented') {
22559
+ console.warn('[ValtechFirebase] Persistencia no soportada en este navegador');
22560
+ }
22561
+ });
22562
+ }
22563
+ return firestore;
22564
+ }),
22565
+ // Auth con soporte para emulador
22566
+ provideAuth(() => {
22567
+ const auth = getAuth();
22568
+ // Conectar a emulador si está configurado
22569
+ if (config.emulator?.auth) {
22570
+ connectAuthEmulator(auth, `http://${config.emulator.auth.host}:${config.emulator.auth.port}`, { disableWarnings: true });
22571
+ }
22572
+ return auth;
22573
+ }),
22574
+ // Storage con soporte para emulador
22575
+ provideStorage(() => {
22576
+ const storage = getStorage();
22577
+ // Conectar a emulador si está configurado
22578
+ if (config.emulator?.storage) {
22579
+ connectStorageEmulator(storage, config.emulator.storage.host, config.emulator.storage.port);
22580
+ }
22581
+ return storage;
22582
+ }),
22583
+ ];
22584
+ // Messaging (FCM) - solo si está explícitamente habilitado
22585
+ // Requiere Service Worker configurado, puede congelar la app si no está disponible
22586
+ if (config.enableMessaging) {
22587
+ // Pre-registrar SW custom antes de que Firebase lo intente
22588
+ if (typeof navigator !== 'undefined' && 'serviceWorker' in navigator) {
22589
+ navigator.serviceWorker.register('/firebase-messaging-sw.js').catch(console.error);
21575
22590
  }
21576
22591
  providers.push(provideMessaging(() => getMessaging()));
21577
22592
  }
22593
+ // Analytics (GA4) - solo si está explícitamente habilitado
22594
+ // Requiere measurementId en firebase config
22595
+ if (config.enableAnalytics && config.firebase.measurementId) {
22596
+ providers.push(provideAnalytics(() => getAnalytics()));
22597
+ // Router tracker para page views automáticos (por defecto habilitado)
22598
+ if (config.analyticsConfig?.enablePageViewTracking !== false) {
22599
+ providers.push(AnalyticsRouterTracker);
22600
+ }
22601
+ // Error handler para tracking automático de errores
22602
+ if (config.analyticsConfig?.enableErrorTracking) {
22603
+ providers.push({
22604
+ provide: ErrorHandler,
22605
+ useClass: AnalyticsErrorHandler,
22606
+ });
22607
+ }
22608
+ }
21578
22609
  return makeEnvironmentProviders(providers);
21579
22610
  }
21580
22611
  /**
@@ -25255,6 +26286,250 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
25255
26286
  args: [VALTECH_AUTH_CONFIG]
25256
26287
  }] }] });
25257
26288
 
26289
+ /**
26290
+ * Servicio de OAuth para login social.
26291
+ *
26292
+ * Implementa flujo OAuth server-side con popup:
26293
+ * 1. Frontend abre popup hacia backend
26294
+ * 2. Backend redirige a provider (Google, Apple, Microsoft)
26295
+ * 3. Usuario autoriza
26296
+ * 4. Backend intercambia code, genera JWT, redirige con tokens
26297
+ * 5. Popup envía tokens a ventana padre via postMessage
26298
+ *
26299
+ * @example
26300
+ * ```typescript
26301
+ * import { OAuthService, AuthService } from 'valtech-components';
26302
+ *
26303
+ * @Component({...})
26304
+ * export class LoginComponent {
26305
+ * private oauth = inject(OAuthService);
26306
+ * private auth = inject(AuthService);
26307
+ *
26308
+ * async loginWithGoogle() {
26309
+ * this.oauth.startFlow('google').subscribe({
26310
+ * next: (result) => {
26311
+ * // Tokens recibidos, guardar en auth state
26312
+ * this.auth.handleOAuthSuccess(result);
26313
+ * this.router.navigate(['/']);
26314
+ * },
26315
+ * error: (error) => {
26316
+ * console.error('OAuth failed:', error);
26317
+ * }
26318
+ * });
26319
+ * }
26320
+ * }
26321
+ * ```
26322
+ */
26323
+ class OAuthService {
26324
+ constructor(config, http, ngZone) {
26325
+ this.config = config;
26326
+ this.http = http;
26327
+ this.ngZone = ngZone;
26328
+ this.popup = null;
26329
+ this.messageHandler = null;
26330
+ this.checkClosedInterval = null;
26331
+ }
26332
+ /**
26333
+ * Inicia flujo OAuth en popup.
26334
+ * Retorna Observable que emite cuando el usuario completa el flujo.
26335
+ *
26336
+ * @param provider - Proveedor OAuth ('google', 'apple', 'microsoft')
26337
+ * @returns Observable que emite OAuthResult o error
26338
+ */
26339
+ startFlow(provider) {
26340
+ return new Observable(observer => {
26341
+ // Construir URL de inicio
26342
+ const redirectUri = `${window.location.origin}/auth/oauth/callback`;
26343
+ const startUrl = `${this.config.apiUrl}/v2/auth/oauth/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}`;
26344
+ // Abrir popup centrado
26345
+ const width = 500;
26346
+ const height = 600;
26347
+ const left = window.screenX + (window.outerWidth - width) / 2;
26348
+ const top = window.screenY + (window.outerHeight - height) / 2;
26349
+ const features = `width=${width},height=${height},left=${left},top=${top},popup=yes`;
26350
+ this.popup = window.open(startUrl, 'oauth', features);
26351
+ if (!this.popup) {
26352
+ observer.error({
26353
+ code: 'POPUP_BLOCKED',
26354
+ message: 'El navegador bloqueó la ventana emergente. Por favor, permite popups para este sitio.',
26355
+ });
26356
+ return () => { };
26357
+ }
26358
+ // Escuchar mensajes del popup
26359
+ this.messageHandler = (event) => {
26360
+ // Validar origen
26361
+ if (event.origin !== window.location.origin) {
26362
+ return;
26363
+ }
26364
+ // Validar tipo de mensaje
26365
+ const data = event.data;
26366
+ if (data?.type !== 'oauth-callback') {
26367
+ return;
26368
+ }
26369
+ // Limpiar
26370
+ this.cleanup();
26371
+ // Emitir resultado dentro de NgZone para trigger change detection
26372
+ this.ngZone.run(() => {
26373
+ if (data.error) {
26374
+ observer.error(data.error);
26375
+ }
26376
+ else if (data.tokens) {
26377
+ observer.next(data.tokens);
26378
+ observer.complete();
26379
+ }
26380
+ else {
26381
+ observer.error({
26382
+ code: 'INVALID_RESPONSE',
26383
+ message: 'Respuesta inválida del servidor de autenticación',
26384
+ });
26385
+ }
26386
+ });
26387
+ };
26388
+ window.addEventListener('message', this.messageHandler);
26389
+ // Verificar si popup se cierra manualmente
26390
+ this.checkClosedInterval = setInterval(() => {
26391
+ if (this.popup?.closed) {
26392
+ this.cleanup();
26393
+ this.ngZone.run(() => {
26394
+ observer.error({
26395
+ code: 'POPUP_CLOSED',
26396
+ message: 'Se cerró la ventana de autenticación',
26397
+ });
26398
+ });
26399
+ }
26400
+ }, 500);
26401
+ // Cleanup cuando el observable se destruye
26402
+ return () => this.cleanup();
26403
+ });
26404
+ }
26405
+ /**
26406
+ * Inicia flujo de linking para vincular un proveedor adicional.
26407
+ * Requiere que el usuario esté autenticado.
26408
+ *
26409
+ * @param provider - Proveedor OAuth a vincular
26410
+ * @returns Observable que emite cuando se completa el linking
26411
+ */
26412
+ startLinkFlow(provider) {
26413
+ return new Observable(observer => {
26414
+ const redirectUri = `${window.location.origin}/auth/oauth/callback`;
26415
+ const startUrl = `${this.config.apiUrl}/v2/auth/oauth/link/${provider}/start?redirect_uri=${encodeURIComponent(redirectUri)}`;
26416
+ const width = 500;
26417
+ const height = 600;
26418
+ const left = window.screenX + (window.outerWidth - width) / 2;
26419
+ const top = window.screenY + (window.outerHeight - height) / 2;
26420
+ const features = `width=${width},height=${height},left=${left},top=${top},popup=yes`;
26421
+ this.popup = window.open(startUrl, 'oauth-link', features);
26422
+ if (!this.popup) {
26423
+ observer.error({
26424
+ code: 'POPUP_BLOCKED',
26425
+ message: 'El navegador bloqueó la ventana emergente',
26426
+ });
26427
+ return () => { }; // cleanup function
26428
+ }
26429
+ this.messageHandler = (event) => {
26430
+ if (event.origin !== window.location.origin)
26431
+ return;
26432
+ const data = event.data;
26433
+ if (data?.type !== 'oauth-callback')
26434
+ return;
26435
+ this.cleanup();
26436
+ this.ngZone.run(() => {
26437
+ if (data.error) {
26438
+ observer.error(data.error);
26439
+ }
26440
+ else {
26441
+ observer.next(data.tokens || {});
26442
+ observer.complete();
26443
+ }
26444
+ });
26445
+ };
26446
+ window.addEventListener('message', this.messageHandler);
26447
+ this.checkClosedInterval = setInterval(() => {
26448
+ if (this.popup?.closed) {
26449
+ this.cleanup();
26450
+ this.ngZone.run(() => {
26451
+ observer.error({
26452
+ code: 'POPUP_CLOSED',
26453
+ message: 'Se cerró la ventana de autenticación',
26454
+ });
26455
+ });
26456
+ }
26457
+ }, 500);
26458
+ return () => this.cleanup();
26459
+ });
26460
+ }
26461
+ /**
26462
+ * Obtiene los proveedores OAuth vinculados al usuario.
26463
+ */
26464
+ getLinkedProviders() {
26465
+ return this.http
26466
+ .get(`${this.config.apiUrl}/v2/auth/oauth/providers`)
26467
+ .pipe(catchError(error => throwError(() => ({
26468
+ code: error.error?.code || 'FETCH_ERROR',
26469
+ message: error.error?.message || 'Error al obtener proveedores vinculados',
26470
+ }))));
26471
+ }
26472
+ /**
26473
+ * Desvincula un proveedor OAuth.
26474
+ */
26475
+ unlinkProvider(provider) {
26476
+ return this.http
26477
+ .post(`${this.config.apiUrl}/v2/auth/oauth/unlink`, { provider })
26478
+ .pipe(catchError(error => throwError(() => ({
26479
+ code: error.error?.code || 'UNLINK_ERROR',
26480
+ message: error.error?.message || 'Error al desvincular proveedor',
26481
+ }))));
26482
+ }
26483
+ /**
26484
+ * Establece contraseña para usuarios que solo tienen OAuth.
26485
+ */
26486
+ setPassword(password) {
26487
+ return this.http
26488
+ .post(`${this.config.apiUrl}/v2/auth/oauth/set-password`, { password })
26489
+ .pipe(catchError(error => throwError(() => ({
26490
+ code: error.error?.code || 'SET_PASSWORD_ERROR',
26491
+ message: error.error?.message || 'Error al establecer contraseña',
26492
+ }))));
26493
+ }
26494
+ /**
26495
+ * Verifica si el usuario tiene contraseña establecida.
26496
+ */
26497
+ hasPassword() {
26498
+ return this.http
26499
+ .get(`${this.config.apiUrl}/v2/auth/oauth/has-password`)
26500
+ .pipe(catchError(error => throwError(() => ({
26501
+ code: error.error?.code || 'CHECK_PASSWORD_ERROR',
26502
+ message: error.error?.message || 'Error al verificar contraseña',
26503
+ }))));
26504
+ }
26505
+ /**
26506
+ * Limpia recursos del popup.
26507
+ */
26508
+ cleanup() {
26509
+ if (this.messageHandler) {
26510
+ window.removeEventListener('message', this.messageHandler);
26511
+ this.messageHandler = null;
26512
+ }
26513
+ if (this.checkClosedInterval) {
26514
+ clearInterval(this.checkClosedInterval);
26515
+ this.checkClosedInterval = null;
26516
+ }
26517
+ if (this.popup && !this.popup.closed) {
26518
+ this.popup.close();
26519
+ }
26520
+ this.popup = null;
26521
+ }
26522
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); }
26523
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, providedIn: 'root' }); }
26524
+ }
26525
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthService, decorators: [{
26526
+ type: Injectable,
26527
+ args: [{ providedIn: 'root' }]
26528
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
26529
+ type: Inject,
26530
+ args: [VALTECH_AUTH_CONFIG]
26531
+ }] }, { type: i1$8.HttpClient }, { type: i0.NgZone }] });
26532
+
25258
26533
  /**
25259
26534
  * Servicio principal de autenticación.
25260
26535
  *
@@ -25278,7 +26553,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
25278
26553
  * ```
25279
26554
  */
25280
26555
  class AuthService {
25281
- constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, messagingService, i18nService) {
26556
+ constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, oauthService, messagingService, i18nService) {
25282
26557
  this.config = config;
25283
26558
  this.http = http;
25284
26559
  this.router = router;
@@ -25287,6 +26562,7 @@ class AuthService {
25287
26562
  this.storageService = storageService;
25288
26563
  this.syncService = syncService;
25289
26564
  this.firebaseService = firebaseService;
26565
+ this.oauthService = oauthService;
25290
26566
  this.messagingService = messagingService;
25291
26567
  this.i18nService = i18nService;
25292
26568
  // Timer para refresh proactivo
@@ -25400,6 +26676,101 @@ class AuthService {
25400
26676
  }
25401
26677
  }), catchError(error => this.handleAuthError(error)));
25402
26678
  }
26679
+ // =============================================
26680
+ // OAUTH (Login social)
26681
+ // =============================================
26682
+ /**
26683
+ * Inicia sesión con OAuth (Google, Apple, Microsoft).
26684
+ * Abre un popup para el flujo de autenticación.
26685
+ *
26686
+ * @param provider - Proveedor OAuth ('google', 'apple', 'microsoft')
26687
+ * @returns Observable que emite SigninResponse cuando se completa
26688
+ *
26689
+ * @example
26690
+ * ```typescript
26691
+ * this.auth.signinWithOAuth('google').subscribe({
26692
+ * next: () => this.router.navigate(['/']),
26693
+ * error: (err) => console.error('OAuth failed:', err)
26694
+ * });
26695
+ * ```
26696
+ */
26697
+ signinWithOAuth(provider) {
26698
+ this.stateService.clearError();
26699
+ return this.oauthService.startFlow(provider).pipe(tap(result => {
26700
+ // Convertir OAuthResult a SigninResponse compatible
26701
+ const response = {
26702
+ operationId: 'oauth',
26703
+ accessToken: result.accessToken,
26704
+ refreshToken: result.refreshToken,
26705
+ firebaseToken: result.firebaseToken,
26706
+ expiresIn: result.expiresIn,
26707
+ roles: result.roles,
26708
+ permissions: result.permissions,
26709
+ };
26710
+ this.handleSuccessfulAuth(response);
26711
+ }), map$1(result => ({
26712
+ operationId: 'oauth',
26713
+ accessToken: result.accessToken,
26714
+ refreshToken: result.refreshToken,
26715
+ firebaseToken: result.firebaseToken,
26716
+ expiresIn: result.expiresIn,
26717
+ roles: result.roles,
26718
+ permissions: result.permissions,
26719
+ })), catchError(error => {
26720
+ const authError = {
26721
+ code: error.code || 'OAUTH_ERROR',
26722
+ message: error.message || 'Error de autenticación OAuth',
26723
+ };
26724
+ this.stateService.setError(authError);
26725
+ return throwError(() => authError);
26726
+ }));
26727
+ }
26728
+ /**
26729
+ * Vincula un proveedor OAuth adicional a la cuenta actual.
26730
+ * Requiere que el usuario esté autenticado.
26731
+ *
26732
+ * @param provider - Proveedor OAuth a vincular
26733
+ */
26734
+ linkOAuthProvider(provider) {
26735
+ return this.oauthService.startLinkFlow(provider).pipe(map$1(() => ({ success: true })), catchError(error => {
26736
+ const authError = {
26737
+ code: error.code || 'LINK_ERROR',
26738
+ message: error.message || 'Error al vincular proveedor',
26739
+ };
26740
+ this.stateService.setError(authError);
26741
+ return throwError(() => authError);
26742
+ }));
26743
+ }
26744
+ /**
26745
+ * Obtiene los proveedores OAuth vinculados al usuario.
26746
+ */
26747
+ getLinkedProviders() {
26748
+ return this.oauthService.getLinkedProviders();
26749
+ }
26750
+ /**
26751
+ * Desvincula un proveedor OAuth de la cuenta.
26752
+ *
26753
+ * @param provider - Proveedor a desvincular
26754
+ */
26755
+ unlinkOAuthProvider(provider) {
26756
+ return this.oauthService.unlinkProvider(provider);
26757
+ }
26758
+ /**
26759
+ * Establece contraseña para usuarios que solo tienen OAuth.
26760
+ * Permite que usuarios OAuth-only puedan también usar email/password.
26761
+ *
26762
+ * @param password - Nueva contraseña
26763
+ */
26764
+ setPasswordForOAuthUser(password) {
26765
+ return this.oauthService.setPassword(password);
26766
+ }
26767
+ /**
26768
+ * Verifica si el usuario tiene contraseña establecida.
26769
+ * Útil para mostrar/ocultar opciones de cambio de contraseña.
26770
+ */
26771
+ checkHasPassword() {
26772
+ return this.oauthService.hasPassword();
26773
+ }
25403
26774
  /**
25404
26775
  * Registra un nuevo usuario.
25405
26776
  * El usuario queda en estado PENDING hasta verificar su email.
@@ -26093,7 +27464,7 @@ class AuthService {
26093
27464
  }
26094
27465
  return { platform: 'web', browser, os };
26095
27466
  }
26096
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
27467
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: OAuthService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
26097
27468
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, providedIn: 'root' }); }
26098
27469
  }
26099
27470
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, decorators: [{
@@ -26102,7 +27473,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
26102
27473
  }], ctorParameters: () => [{ type: undefined, decorators: [{
26103
27474
  type: Inject,
26104
27475
  args: [VALTECH_AUTH_CONFIG]
26105
- }] }, { type: i1$8.HttpClient }, { type: i1$1.Router }, { type: AuthStateService }, { type: TokenService }, { type: AuthStorageService }, { type: AuthSyncService }, { type: FirebaseService }, { type: MessagingService, decorators: [{
27476
+ }] }, { type: i1$8.HttpClient }, { type: i1$1.Router }, { type: AuthStateService }, { type: TokenService }, { type: AuthStorageService }, { type: AuthSyncService }, { type: FirebaseService }, { type: OAuthService }, { type: MessagingService, decorators: [{
26106
27477
  type: Optional
26107
27478
  }] }, { type: I18nService, decorators: [{
26108
27479
  type: Optional
@@ -26301,6 +27672,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
26301
27672
  type: Optional
26302
27673
  }] }] });
26303
27674
 
27675
+ /**
27676
+ * Analytics Types
27677
+ *
27678
+ * Tipos e interfaces para el servicio de Firebase Analytics (GA4).
27679
+ */
27680
+
26304
27681
  /**
26305
27682
  * Firebase Services
26306
27683
  *
@@ -26725,6 +28102,144 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
26725
28102
  args: [VALTECH_AUTH_CONFIG]
26726
28103
  }] }, { type: i1$8.HttpClient }] });
26727
28104
 
28105
+ /**
28106
+ * Componente de callback para OAuth.
28107
+ *
28108
+ * Este componente procesa la respuesta del servidor OAuth y envía
28109
+ * los tokens a la ventana padre via postMessage.
28110
+ *
28111
+ * Debe agregarse a las rutas de la aplicación:
28112
+ * ```typescript
28113
+ * // app.routes.ts
28114
+ * import { OAuthCallbackComponent } from 'valtech-components';
28115
+ *
28116
+ * export const routes: Routes = [
28117
+ * { path: 'auth/oauth/callback', component: OAuthCallbackComponent },
28118
+ * // ... otras rutas
28119
+ * ];
28120
+ * ```
28121
+ *
28122
+ * El backend redirige a esta ruta con los tokens en query params:
28123
+ * `/auth/oauth/callback?access_token=xxx&refresh_token=xxx&expires_in=900`
28124
+ *
28125
+ * O con error:
28126
+ * `/auth/oauth/callback?error=INVALID_CODE&error_description=...`
28127
+ */
28128
+ class OAuthCallbackComponent {
28129
+ constructor() {
28130
+ this.message = 'Procesando autenticación...';
28131
+ }
28132
+ ngOnInit() {
28133
+ this.processCallback();
28134
+ }
28135
+ processCallback() {
28136
+ const params = new URLSearchParams(window.location.search);
28137
+ // Verificar si hay error
28138
+ const error = params.get('error');
28139
+ if (error) {
28140
+ this.sendToParent({
28141
+ type: 'oauth-callback',
28142
+ error: {
28143
+ code: error,
28144
+ message: params.get('error_description') || 'Error de autenticación',
28145
+ },
28146
+ });
28147
+ this.message = 'Error de autenticación';
28148
+ this.closeAfterDelay();
28149
+ return;
28150
+ }
28151
+ // Extraer tokens
28152
+ const accessToken = params.get('access_token');
28153
+ const refreshToken = params.get('refresh_token');
28154
+ const expiresIn = params.get('expires_in');
28155
+ const firebaseToken = params.get('firebase_token');
28156
+ if (!accessToken || !refreshToken) {
28157
+ this.sendToParent({
28158
+ type: 'oauth-callback',
28159
+ error: {
28160
+ code: 'MISSING_TOKENS',
28161
+ message: 'No se recibieron los tokens de autenticación',
28162
+ },
28163
+ });
28164
+ this.message = 'Error: tokens no recibidos';
28165
+ this.closeAfterDelay();
28166
+ return;
28167
+ }
28168
+ // Extraer roles y permisos (pueden venir como JSON en base64)
28169
+ let roles;
28170
+ let permissions;
28171
+ const rolesParam = params.get('roles');
28172
+ const permissionsParam = params.get('permissions');
28173
+ if (rolesParam) {
28174
+ try {
28175
+ roles = JSON.parse(atob(rolesParam));
28176
+ }
28177
+ catch {
28178
+ roles = rolesParam.split(',');
28179
+ }
28180
+ }
28181
+ if (permissionsParam) {
28182
+ try {
28183
+ permissions = JSON.parse(atob(permissionsParam));
28184
+ }
28185
+ catch {
28186
+ permissions = permissionsParam.split(',');
28187
+ }
28188
+ }
28189
+ // Enviar tokens a la ventana padre
28190
+ const result = {
28191
+ accessToken,
28192
+ refreshToken,
28193
+ expiresIn: expiresIn ? parseInt(expiresIn, 10) : 900,
28194
+ firebaseToken: firebaseToken || undefined,
28195
+ roles,
28196
+ permissions,
28197
+ isNewUser: params.get('is_new_user') === 'true',
28198
+ linked: params.get('linked') === 'true',
28199
+ };
28200
+ this.sendToParent({
28201
+ type: 'oauth-callback',
28202
+ tokens: result,
28203
+ });
28204
+ this.message = 'Autenticación exitosa';
28205
+ this.closeAfterDelay();
28206
+ }
28207
+ sendToParent(data) {
28208
+ if (window.opener) {
28209
+ // Enviar al opener (ventana que abrió el popup)
28210
+ window.opener.postMessage(data, window.location.origin);
28211
+ }
28212
+ else if (window.parent !== window) {
28213
+ // Enviar al parent (si estamos en iframe)
28214
+ window.parent.postMessage(data, window.location.origin);
28215
+ }
28216
+ }
28217
+ closeAfterDelay() {
28218
+ // Dar tiempo para que el mensaje se envíe antes de cerrar
28219
+ setTimeout(() => {
28220
+ if (window.opener) {
28221
+ window.close();
28222
+ }
28223
+ }, 500);
28224
+ }
28225
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthCallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
28226
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.14", type: OAuthCallbackComponent, isStandalone: true, selector: "val-oauth-callback", ngImport: i0, template: `
28227
+ <div class="oauth-callback">
28228
+ <div class="oauth-callback__spinner"></div>
28229
+ <p class="oauth-callback__text">{{ message }}</p>
28230
+ </div>
28231
+ `, isInline: true, styles: [".oauth-callback{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.oauth-callback__spinner{width:40px;height:40px;border:3px solid #f3f3f3;border-top:3px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:16px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.oauth-callback__text{color:#666;font-size:14px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] }); }
28232
+ }
28233
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OAuthCallbackComponent, decorators: [{
28234
+ type: Component,
28235
+ args: [{ selector: 'val-oauth-callback', standalone: true, imports: [CommonModule], template: `
28236
+ <div class="oauth-callback">
28237
+ <div class="oauth-callback__spinner"></div>
28238
+ <p class="oauth-callback__text">{{ message }}</p>
28239
+ </div>
28240
+ `, styles: [".oauth-callback{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.oauth-callback__spinner{width:40px;height:40px;border:3px solid #f3f3f3;border-top:3px solid #3498db;border-radius:50%;animation:spin 1s linear infinite;margin-bottom:16px}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.oauth-callback__text{color:#666;font-size:14px}\n"] }]
28241
+ }] });
28242
+
26728
28243
  /**
26729
28244
  * Valtech Auth Service
26730
28245
  *
@@ -26798,152 +28313,1137 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
26798
28313
  // Tipos
26799
28314
 
26800
28315
  /**
26801
- * Servicio para gestionar presets de componentes.
28316
+ * Ads Types
26802
28317
  *
26803
- * Los presets permiten definir configuraciones reutilizables
26804
- * de componentes (tamaño, color, variante, etc.) que se pueden
26805
- * aplicar con un nombre semántico.
28318
+ * Tipos e interfaces para el servicio de Google Ad Manager.
28319
+ * Usa Google Publisher Tags (GPT) para renderizar ads.
28320
+ */
28321
+ /**
28322
+ * Mapeo de tamanos predefinidos a dimensiones.
28323
+ */
28324
+ const AD_SIZE_MAP = {
28325
+ leaderboard: [728, 90],
28326
+ billboard: [970, 250],
28327
+ 'medium-rectangle': [300, 250],
28328
+ 'large-rectangle': [336, 280],
28329
+ 'half-page': [300, 600],
28330
+ skyscraper: [160, 600],
28331
+ 'mobile-banner': [320, 50],
28332
+ 'mobile-leaderboard': [320, 100],
28333
+ fluid: 'fluid',
28334
+ native: 'fluid',
28335
+ custom: 'fluid',
28336
+ };
28337
+
28338
+ /**
28339
+ * Ads Consent Service
26806
28340
  *
26807
- * @example
26808
- * // En un componente
26809
- * presets = inject(PresetService);
28341
+ * Integra el servicio de Ads con el Consent Mode v2 existente en AnalyticsService.
28342
+ * Proporciona estado de consent especifico para ads (advertising).
28343
+ */
28344
+ /**
28345
+ * Servicio que maneja el consent para ads.
26810
28346
  *
26811
- * // Obtener preset
26812
- * const buttonProps = this.presets.get<ButtonMetadata>('button', 'primary-action');
26813
- * // { size: 'large', color: 'primary', fill: 'solid' }
28347
+ * Se integra con AnalyticsService para reutilizar el estado de consent GDPR.
28348
+ * Si AnalyticsService no esta disponible, usa valores por defecto conservadores.
26814
28349
  */
26815
- class PresetService {
28350
+ class AdsConsentService {
26816
28351
  constructor() {
26817
- this._presets = signal({});
28352
+ this.platformId = inject(PLATFORM_ID);
28353
+ this.injector = inject(Injector);
28354
+ // Cache del estado de consent
28355
+ this._consentState = signal({
28356
+ adStorage: false,
28357
+ adPersonalization: false,
28358
+ adUserData: false,
28359
+ });
28360
+ /** Estado de consent para ads (readonly) */
28361
+ this.adsConsentState = this._consentState.asReadonly();
28362
+ /** Indica si se pueden mostrar ads (al menos consent basico) */
28363
+ this.canShowAds = computed(() => {
28364
+ // Siempre permitimos mostrar ads (pueden ser no personalizados)
28365
+ // El control real esta en canPersonalize
28366
+ return true;
28367
+ });
28368
+ /** Indica si se pueden mostrar ads personalizados */
28369
+ this.canPersonalize = computed(() => {
28370
+ const state = this._consentState();
28371
+ return state.adStorage && state.adPersonalization && state.adUserData;
28372
+ });
28373
+ this.analyticsService = null;
28374
+ this.consentSyncInterval = null;
28375
+ if (isPlatformBrowser(this.platformId)) {
28376
+ this.initializeConsentSync();
28377
+ }
26818
28378
  }
26819
28379
  /**
26820
- * Obtiene un preset específico para un componente
28380
+ * Inicializa la sincronizacion con AnalyticsService.
28381
+ */
28382
+ initializeConsentSync() {
28383
+ // Intentar obtener AnalyticsService de forma lazy
28384
+ // Usamos setTimeout para dar tiempo a que se inicialice
28385
+ setTimeout(() => {
28386
+ this.syncWithAnalyticsConsent();
28387
+ }, 100);
28388
+ // Re-sincronizar periodicamente por si el usuario cambia consent
28389
+ this.consentSyncInterval = setInterval(() => {
28390
+ this.syncWithAnalyticsConsent();
28391
+ }, 5000);
28392
+ }
28393
+ /**
28394
+ * Sincroniza el estado de consent desde AnalyticsService.
28395
+ */
28396
+ async syncWithAnalyticsConsent() {
28397
+ try {
28398
+ // Importar AnalyticsService dinamicamente
28399
+ const { AnalyticsService } = await Promise.resolve().then(function () { return analytics_service; });
28400
+ if (!this.analyticsService) {
28401
+ this.analyticsService = this.injector.get(AnalyticsService, null);
28402
+ }
28403
+ if (this.analyticsService) {
28404
+ // AnalyticsService expone consentState como signal
28405
+ const consentState = this.analyticsService.consentState();
28406
+ this.updateFromAnalyticsConsent(consentState.settings);
28407
+ }
28408
+ }
28409
+ catch {
28410
+ // AnalyticsService no disponible - usar defaults
28411
+ this.setDefaultConsent();
28412
+ }
28413
+ }
28414
+ /**
28415
+ * Actualiza el estado de consent desde AnalyticsService.
28416
+ */
28417
+ updateFromAnalyticsConsent(settings) {
28418
+ const hasAdvertisingConsent = settings['advertising'] === true;
28419
+ this._consentState.set({
28420
+ adStorage: hasAdvertisingConsent,
28421
+ adPersonalization: hasAdvertisingConsent,
28422
+ adUserData: hasAdvertisingConsent,
28423
+ });
28424
+ }
28425
+ /**
28426
+ * Establece consent por defecto (sin datos personales).
28427
+ */
28428
+ setDefaultConsent() {
28429
+ this._consentState.set({
28430
+ adStorage: false,
28431
+ adPersonalization: false,
28432
+ adUserData: false,
28433
+ });
28434
+ }
28435
+ /**
28436
+ * Actualiza manualmente el consent para ads.
28437
+ * Usar cuando el usuario actualiza sus preferencias directamente.
26821
28438
  *
26822
- * @param component Tipo de componente (ej: 'button', 'card', 'input')
26823
- * @param presetName Nombre del preset (ej: 'primary-action', 'compact')
26824
- * @returns Propiedades del preset o objeto vacío si no existe
28439
+ * @param consent - Nuevo estado de consent parcial
28440
+ */
28441
+ updateConsent(consent) {
28442
+ this._consentState.update((current) => ({
28443
+ ...current,
28444
+ ...consent,
28445
+ }));
28446
+ // Notificar a GPT si ya esta cargado
28447
+ this.applyConsentToGPT();
28448
+ }
28449
+ /**
28450
+ * Aplica el estado de consent actual a GPT.
28451
+ */
28452
+ applyConsentToGPT() {
28453
+ if (!isPlatformBrowser(this.platformId))
28454
+ return;
28455
+ const googletag = window.googletag;
28456
+ if (!googletag)
28457
+ return;
28458
+ googletag.cmd.push(() => {
28459
+ const canPersonalize = this.canPersonalize();
28460
+ // 0 = personalized, 1 = non-personalized
28461
+ googletag.pubads().setRequestNonPersonalizedAds(canPersonalize ? 0 : 1);
28462
+ });
28463
+ }
28464
+ /**
28465
+ * Obtiene el estado actual de consent.
28466
+ */
28467
+ getConsentState() {
28468
+ return this._consentState();
28469
+ }
28470
+ /**
28471
+ * Destruye el servicio y limpia recursos.
28472
+ */
28473
+ destroy() {
28474
+ if (this.consentSyncInterval) {
28475
+ clearInterval(this.consentSyncInterval);
28476
+ this.consentSyncInterval = null;
28477
+ }
28478
+ }
28479
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
28480
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, providedIn: 'root' }); }
28481
+ }
28482
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsConsentService, decorators: [{
28483
+ type: Injectable,
28484
+ args: [{ providedIn: 'root' }]
28485
+ }], ctorParameters: () => [] });
28486
+
28487
+ /**
28488
+ * Ads Loader Service
28489
+ *
28490
+ * Maneja la carga lazy del script GPT (Google Publisher Tag).
28491
+ * Solo carga el script cuando se necesita el primer ad.
28492
+ */
28493
+ /** URL del script GPT */
28494
+ const GPT_SCRIPT_URL = 'https://securepubads.g.doubleclick.net/tag/js/gpt.js';
28495
+ /**
28496
+ * Servicio para cargar el script de Google Publisher Tags.
28497
+ *
28498
+ * Implementa lazy loading: el script solo se carga cuando
28499
+ * se solicita renderizar el primer ad slot.
28500
+ */
28501
+ class AdsLoaderService {
28502
+ constructor(config, platformId, consentService) {
28503
+ this.config = config;
28504
+ this.platformId = platformId;
28505
+ this.consentService = consentService;
28506
+ this._isLoading = signal(false);
28507
+ this._isLoaded = signal(false);
28508
+ this._error = signal(null);
28509
+ /** Indica si el script esta cargando */
28510
+ this.isLoading = this._isLoading.asReadonly();
28511
+ /** Indica si el script esta cargado */
28512
+ this.isLoaded = this._isLoaded.asReadonly();
28513
+ /** Error de carga (si existe) */
28514
+ this.error = this._error.asReadonly();
28515
+ this.loadPromise = null;
28516
+ }
28517
+ /**
28518
+ * Carga el script GPT de forma lazy.
28519
+ * Retorna la instancia de googletag o null si falla.
26825
28520
  *
26826
- * @example
26827
- * // Obtener preset de botón
26828
- * const props = presets.get<ButtonMetadata>('button', 'primary-action');
28521
+ * @returns Promise con googletag o null
28522
+ */
28523
+ loadGPT() {
28524
+ // SSR check
28525
+ if (!isPlatformBrowser(this.platformId)) {
28526
+ return Promise.resolve(null);
28527
+ }
28528
+ // Ya cargado
28529
+ if (this._isLoaded() && window.googletag) {
28530
+ return Promise.resolve(window.googletag);
28531
+ }
28532
+ // Ya hay una carga en progreso
28533
+ if (this.loadPromise) {
28534
+ return this.loadPromise;
28535
+ }
28536
+ this._isLoading.set(true);
28537
+ this._error.set(null);
28538
+ this.loadPromise = new Promise((resolve) => {
28539
+ // Inicializar cmd queue
28540
+ window.googletag = window.googletag || { cmd: [] };
28541
+ const googletag = window.googletag;
28542
+ // Crear script
28543
+ const script = document.createElement('script');
28544
+ script.async = true;
28545
+ script.src = GPT_SCRIPT_URL;
28546
+ script.onload = () => {
28547
+ googletag.cmd.push(() => {
28548
+ this.configureGPT(googletag);
28549
+ this._isLoaded.set(true);
28550
+ this._isLoading.set(false);
28551
+ if (this.config.debugMode) {
28552
+ console.log('[ValtechAds] Script GPT cargado y configurado');
28553
+ }
28554
+ resolve(googletag);
28555
+ });
28556
+ };
28557
+ script.onerror = (error) => {
28558
+ console.error('[ValtechAds] Error cargando GPT:', error);
28559
+ this._error.set(new Error('Error cargando Google Publisher Tag'));
28560
+ this._isLoading.set(false);
28561
+ this.loadPromise = null;
28562
+ resolve(null);
28563
+ };
28564
+ // Insertar script
28565
+ const firstScript = document.getElementsByTagName('script')[0];
28566
+ firstScript.parentNode?.insertBefore(script, firstScript);
28567
+ });
28568
+ return this.loadPromise;
28569
+ }
28570
+ /**
28571
+ * Configura GPT con las opciones de la aplicacion.
28572
+ */
28573
+ configureGPT(googletag) {
28574
+ const pubads = googletag.pubads();
28575
+ // Single Request Architecture para mejor performance
28576
+ pubads.enableSingleRequest();
28577
+ // Colapsar divs vacios antes de fetch
28578
+ pubads.collapseEmptyDivs(true);
28579
+ // Lazy loading nativo de GPT
28580
+ if (this.config.lazyLoad && this.config.lazyLoadConfig) {
28581
+ pubads.enableLazyLoad({
28582
+ fetchMarginPercent: this.config.lazyLoadConfig.fetchMarginPercent,
28583
+ renderMarginPercent: this.config.lazyLoadConfig.renderMarginPercent,
28584
+ mobileScaling: 2.0,
28585
+ });
28586
+ }
28587
+ // Targeting global
28588
+ if (this.config.globalTargeting) {
28589
+ for (const [key, value] of Object.entries(this.config.globalTargeting)) {
28590
+ pubads.setTargeting(key, value);
28591
+ }
28592
+ }
28593
+ // Non-personalized ads segun consent
28594
+ if (!this.consentService.canPersonalize()) {
28595
+ pubads.setRequestNonPersonalizedAds(1);
28596
+ }
28597
+ // Event listeners
28598
+ pubads.addEventListener('slotRenderEnded', (event) => {
28599
+ const slotId = event.slot.getSlotElementId();
28600
+ if (this.config.debugMode) {
28601
+ console.log(`[ValtechAds] Slot ${slotId} rendered, empty: ${event.isEmpty}`);
28602
+ }
28603
+ });
28604
+ // Debug mode
28605
+ if (this.config.debugMode) {
28606
+ console.log('[ValtechAds] GPT configurado:', {
28607
+ lazyLoad: this.config.lazyLoad,
28608
+ personalized: this.consentService.canPersonalize(),
28609
+ targeting: this.config.globalTargeting,
28610
+ });
28611
+ }
28612
+ }
28613
+ /**
28614
+ * Verifica si el script GPT esta disponible.
28615
+ */
28616
+ isGPTAvailable() {
28617
+ return isPlatformBrowser(this.platformId) && !!window.googletag && this._isLoaded();
28618
+ }
28619
+ /**
28620
+ * Obtiene la instancia de googletag si esta cargada.
28621
+ */
28622
+ getGPT() {
28623
+ if (this.isGPTAvailable()) {
28624
+ return window.googletag;
28625
+ }
28626
+ return null;
28627
+ }
28628
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, deps: [{ token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
28629
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, providedIn: 'root' }); }
28630
+ }
28631
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsLoaderService, decorators: [{
28632
+ type: Injectable,
28633
+ args: [{ providedIn: 'root' }]
28634
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
28635
+ type: Inject,
28636
+ args: [VALTECH_ADS_CONFIG]
28637
+ }] }, { type: Object, decorators: [{
28638
+ type: Inject,
28639
+ args: [PLATFORM_ID]
28640
+ }] }, { type: AdsConsentService }] });
28641
+
28642
+ /**
28643
+ * Ads Service
28644
+ *
28645
+ * Servicio principal para Google Ad Manager (GPT).
28646
+ * Integra con el sistema de consent existente y respeta usuarios premium.
28647
+ */
28648
+ /**
28649
+ * Servicio principal de Google Ad Manager.
28650
+ *
28651
+ * Maneja la creacion, destruccion y refresh de ad slots.
28652
+ * Integra automaticamente con consent mode y detecta usuarios premium.
28653
+ *
28654
+ * @example
28655
+ * ```typescript
28656
+ * @Component({...})
28657
+ * export class MyComponent {
28658
+ * private ads = inject(AdsService);
28659
+ *
28660
+ * // Verificar si mostrar ads
28661
+ * showAds = this.ads.isEnabled;
28662
+ *
28663
+ * // Verificar si usuario es premium
28664
+ * isPremium = this.ads.isPremiumUser;
28665
+ * }
28666
+ * ```
28667
+ */
28668
+ class AdsService {
28669
+ constructor(injector, config, platformId, router, loaderService, consentService) {
28670
+ this.injector = injector;
28671
+ this.config = config;
28672
+ this.platformId = platformId;
28673
+ this.router = router;
28674
+ this.loaderService = loaderService;
28675
+ this.consentService = consentService;
28676
+ // ===========================================================================
28677
+ // ESTADO (Signals)
28678
+ // ===========================================================================
28679
+ this._isInitialized = signal(false);
28680
+ this._isEnabled = signal(false);
28681
+ this._isPremiumUser = signal(false);
28682
+ this._isDebugMode = signal(false);
28683
+ this._slots = signal(new Map());
28684
+ this._slotStates = signal(new Map());
28685
+ this._events = signal([]);
28686
+ /** Indica si el servicio esta inicializado */
28687
+ this.isInitialized = this._isInitialized.asReadonly();
28688
+ /** Indica si los ads estan habilitados (consent + no premium) */
28689
+ this.isEnabled = computed(() => {
28690
+ return (this._isInitialized() &&
28691
+ this._isEnabled() &&
28692
+ !this._isPremiumUser() &&
28693
+ this.consentService.canShowAds());
28694
+ });
28695
+ /** Indica si el usuario es premium (no ve ads) */
28696
+ this.isPremiumUser = this._isPremiumUser.asReadonly();
28697
+ /** Indica si esta en modo debug */
28698
+ this.isDebugMode = this._isDebugMode.asReadonly();
28699
+ /** Estado de consent para ads */
28700
+ this.consentState = this.consentService.adsConsentState;
28701
+ /** Eventos de ads (historial) */
28702
+ this.events = this._events.asReadonly();
28703
+ // ===========================================================================
28704
+ // INTERNOS
28705
+ // ===========================================================================
28706
+ this.activeSlots = new Map();
28707
+ this.routerSubscription = null;
28708
+ this.refreshTimerId = null;
28709
+ this._isDebugMode.set(config.debugMode ?? false);
28710
+ }
28711
+ // ===========================================================================
28712
+ // INICIALIZACION
28713
+ // ===========================================================================
28714
+ /**
28715
+ * Inicializa el servicio de ads.
28716
+ * Llamado automaticamente por APP_INITIALIZER.
28717
+ * NO carga el script GPT hasta que se necesite el primer ad.
28718
+ */
28719
+ async initialize() {
28720
+ if (!isPlatformBrowser(this.platformId)) {
28721
+ return;
28722
+ }
28723
+ // Verificar si usuario es premium
28724
+ if (this.config.isPremiumUser) {
28725
+ try {
28726
+ this._isPremiumUser.set(this.config.isPremiumUser());
28727
+ }
28728
+ catch {
28729
+ this._isPremiumUser.set(false);
28730
+ }
28731
+ }
28732
+ // Si es premium, no inicializar ads
28733
+ if (this._isPremiumUser()) {
28734
+ if (this._isDebugMode()) {
28735
+ console.log('[ValtechAds] Usuario premium detectado - ads deshabilitados');
28736
+ }
28737
+ this._isInitialized.set(true);
28738
+ return;
28739
+ }
28740
+ // Verificar consent basico
28741
+ if (!this.consentService.canShowAds() && !this.config.showNonPersonalizedAds) {
28742
+ if (this._isDebugMode()) {
28743
+ console.log('[ValtechAds] Sin consent para ads');
28744
+ }
28745
+ this._isInitialized.set(true);
28746
+ return;
28747
+ }
28748
+ // Suscribirse a cambios de ruta para verificar exclusiones
28749
+ this.setupRouteListener();
28750
+ // Configurar auto-refresh si esta habilitado
28751
+ if (this.config.autoRefreshInterval && this.config.autoRefreshInterval > 0) {
28752
+ this.setupAutoRefresh();
28753
+ }
28754
+ this._isEnabled.set(true);
28755
+ this._isInitialized.set(true);
28756
+ if (this._isDebugMode()) {
28757
+ console.log('[ValtechAds] Inicializado en modo debug', {
28758
+ networkId: this.config.networkId,
28759
+ lazyLoad: this.config.lazyLoad,
28760
+ excludeRoutes: this.config.excludeRoutes,
28761
+ });
28762
+ }
28763
+ }
28764
+ /**
28765
+ * Carga el script GPT de forma lazy.
28766
+ * Llamado automaticamente cuando se renderiza el primer ad slot.
28767
+ */
28768
+ async ensureGPTLoaded() {
28769
+ if (!this.isEnabled()) {
28770
+ return null;
28771
+ }
28772
+ return this.loaderService.loadGPT();
28773
+ }
28774
+ // ===========================================================================
28775
+ // GESTION DE SLOTS
28776
+ // ===========================================================================
28777
+ /**
28778
+ * Define y muestra un ad slot.
28779
+ * Llamado internamente por el componente AdSlot.
26829
28780
  *
26830
- * // Usar en componente
26831
- * <val-button [props]="props"></val-button>
28781
+ * @param config - Configuracion del slot
28782
+ * @returns ID del slot o null si no se puede mostrar
26832
28783
  */
26833
- get(component, presetName) {
26834
- const componentPresets = this._presets()[component];
26835
- if (!componentPresets) {
26836
- console.warn(`[presets] No presets registered for component: ${component}`);
26837
- return {};
28784
+ async defineSlot(config) {
28785
+ if (!this.isEnabled()) {
28786
+ this.updateSlotState(config.slotId, 'hidden');
28787
+ return null;
26838
28788
  }
26839
- const preset = componentPresets[presetName];
26840
- if (!preset) {
26841
- console.warn(`[presets] Preset '${presetName}' not found for component: ${component}`);
26842
- return {};
28789
+ // Verificar si la ruta actual esta excluida
28790
+ if (this.isRouteExcluded()) {
28791
+ this.updateSlotState(config.slotId, 'hidden');
28792
+ return null;
26843
28793
  }
26844
- return preset;
28794
+ this.updateSlotState(config.slotId, 'loading');
28795
+ const googletag = await this.ensureGPTLoaded();
28796
+ if (!googletag) {
28797
+ this.updateSlotState(config.slotId, 'error');
28798
+ return null;
28799
+ }
28800
+ return new Promise((resolve) => {
28801
+ googletag.cmd.push(() => {
28802
+ try {
28803
+ const adUnitPath = `${this.config.networkId}${config.adUnitPath}`;
28804
+ const sizes = this.resolveSizes(config.size);
28805
+ const slot = googletag.defineSlot(adUnitPath, sizes, config.slotId);
28806
+ if (!slot) {
28807
+ this.updateSlotState(config.slotId, 'error');
28808
+ this.emitEvent({ type: 'error', slotId: config.slotId, error: new Error('No se pudo crear el slot') });
28809
+ resolve(null);
28810
+ return;
28811
+ }
28812
+ // Agregar al servicio pubads
28813
+ slot.addService(googletag.pubads());
28814
+ // Configurar size mapping responsivo
28815
+ if (config.sizeMapping && config.sizeMapping.length > 0) {
28816
+ const mapping = this.buildSizeMapping(googletag, config.sizeMapping);
28817
+ slot.defineSizeMapping(mapping);
28818
+ }
28819
+ // Configurar targeting
28820
+ this.applyTargeting(slot, config.targeting);
28821
+ // Colapsar si esta vacio
28822
+ if (config.collapseEmpty ?? this.config.defaultSlotConfig?.collapseEmpty) {
28823
+ slot.setCollapseEmptyDiv(true, true);
28824
+ }
28825
+ // Guardar referencia
28826
+ this.activeSlots.set(config.slotId, slot);
28827
+ this._slots.update((slots) => new Map(slots).set(config.slotId, slot));
28828
+ // Mostrar el ad
28829
+ googletag.enableServices();
28830
+ googletag.display(config.slotId);
28831
+ this.updateSlotState(config.slotId, 'rendered');
28832
+ this.emitEvent({ type: 'loaded', slotId: config.slotId, isEmpty: false });
28833
+ if (this._isDebugMode()) {
28834
+ console.log(`[ValtechAds] Slot definido: ${config.slotId}`, {
28835
+ adUnitPath,
28836
+ sizes: config.size,
28837
+ });
28838
+ }
28839
+ resolve(config.slotId);
28840
+ }
28841
+ catch (error) {
28842
+ console.error('[ValtechAds] Error definiendo slot:', error);
28843
+ this.updateSlotState(config.slotId, 'error');
28844
+ this.emitEvent({ type: 'error', slotId: config.slotId, error: error });
28845
+ resolve(null);
28846
+ }
28847
+ });
28848
+ });
26845
28849
  }
26846
28850
  /**
26847
- * Verifica si existe un preset
28851
+ * Destruye un slot y libera recursos.
28852
+ *
28853
+ * @param slotId - ID del slot a destruir
26848
28854
  */
26849
- has(component, presetName) {
26850
- return !!this._presets()[component]?.[presetName];
28855
+ destroySlot(slotId) {
28856
+ const slot = this.activeSlots.get(slotId);
28857
+ if (!slot)
28858
+ return;
28859
+ const googletag = window.googletag;
28860
+ if (googletag) {
28861
+ googletag.cmd.push(() => {
28862
+ googletag.destroySlots([slot]);
28863
+ });
28864
+ }
28865
+ this.activeSlots.delete(slotId);
28866
+ this._slots.update((slots) => {
28867
+ const newSlots = new Map(slots);
28868
+ newSlots.delete(slotId);
28869
+ return newSlots;
28870
+ });
28871
+ this._slotStates.update((states) => {
28872
+ const newStates = new Map(states);
28873
+ newStates.delete(slotId);
28874
+ return newStates;
28875
+ });
28876
+ if (this._isDebugMode()) {
28877
+ console.log(`[ValtechAds] Slot destruido: ${slotId}`);
28878
+ }
26851
28879
  }
26852
28880
  /**
26853
- * Registra presets de la aplicación
28881
+ * Refresca un slot especifico o todos los slots activos.
26854
28882
  *
26855
- * @param presets Configuración de presets
26856
- *
26857
- * @example
26858
- * presets.registerPresets({
26859
- * button: {
26860
- * 'primary-action': { size: 'large', color: 'primary' },
26861
- * },
26862
- * card: {
26863
- * 'feature': { variant: 'elevated' },
26864
- * }
26865
- * });
28883
+ * @param slotIds - IDs de slots a refrescar (undefined = todos)
26866
28884
  */
26867
- registerPresets(presets) {
26868
- this._presets.set(presets);
28885
+ refreshSlots(slotIds) {
28886
+ if (!this.isEnabled())
28887
+ return;
28888
+ const googletag = window.googletag;
28889
+ if (!googletag)
28890
+ return;
28891
+ googletag.cmd.push(() => {
28892
+ if (slotIds && slotIds.length > 0) {
28893
+ const slots = slotIds.map((id) => this.activeSlots.get(id)).filter((s) => !!s);
28894
+ if (slots.length > 0) {
28895
+ googletag.pubads().refresh(slots);
28896
+ if (this._isDebugMode()) {
28897
+ console.log(`[ValtechAds] Slots refrescados: ${slotIds.join(', ')}`);
28898
+ }
28899
+ }
28900
+ }
28901
+ else {
28902
+ googletag.pubads().refresh();
28903
+ if (this._isDebugMode()) {
28904
+ console.log('[ValtechAds] Todos los slots refrescados');
28905
+ }
28906
+ }
28907
+ });
26869
28908
  }
26870
28909
  /**
26871
- * Agrega presets para un componente específico (merge con existentes)
28910
+ * Obtiene el estado de un slot.
28911
+ *
28912
+ * @param slotId - ID del slot
28913
+ * @returns Estado actual del slot
26872
28914
  */
26873
- registerComponentPresets(component, presets) {
26874
- this._presets.update((current) => ({
26875
- ...current,
26876
- [component]: {
26877
- ...current[component],
26878
- ...presets,
26879
- },
26880
- }));
28915
+ getSlotState(slotId) {
28916
+ return this._slotStates().get(slotId) ?? 'idle';
26881
28917
  }
28918
+ // ===========================================================================
28919
+ // PREMIUM USER
28920
+ // ===========================================================================
26882
28921
  /**
26883
- * Obtiene todos los nombres de presets para un componente
26884
- */
26885
- getPresetNames(component) {
26886
- return Object.keys(this._presets()[component] || {});
28922
+ * Actualiza el estado premium del usuario.
28923
+ * Llamar cuando cambie el estado de suscripcion.
28924
+ *
28925
+ * @param isPremium - Nuevo estado premium
28926
+ */
28927
+ updatePremiumStatus(isPremium) {
28928
+ const wasEnabled = this.isEnabled();
28929
+ this._isPremiumUser.set(isPremium);
28930
+ if (isPremium && wasEnabled) {
28931
+ // Destruir todos los slots activos
28932
+ this.destroyAllSlots();
28933
+ if (this._isDebugMode()) {
28934
+ console.log('[ValtechAds] Usuario ahora es premium - ads deshabilitados');
28935
+ }
28936
+ }
28937
+ else if (!isPremium && !wasEnabled && this._isInitialized()) {
28938
+ if (this._isDebugMode()) {
28939
+ console.log('[ValtechAds] Usuario ya no es premium - ads habilitados');
28940
+ }
28941
+ }
26887
28942
  }
26888
28943
  /**
26889
- * Obtiene todos los componentes con presets registrados
28944
+ * Destruye todos los slots activos.
26890
28945
  */
26891
- getRegisteredComponents() {
26892
- return Object.keys(this._presets());
28946
+ destroyAllSlots() {
28947
+ const googletag = window.googletag;
28948
+ if (googletag) {
28949
+ googletag.cmd.push(() => {
28950
+ googletag.destroySlots();
28951
+ });
28952
+ }
28953
+ this.activeSlots.clear();
28954
+ this._slots.set(new Map());
28955
+ this._slotStates.set(new Map());
28956
+ if (this._isDebugMode()) {
28957
+ console.log('[ValtechAds] Todos los slots destruidos');
28958
+ }
26893
28959
  }
26894
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
26895
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, providedIn: 'root' }); }
28960
+ // ===========================================================================
28961
+ // PRIVATE METHODS
28962
+ // ===========================================================================
28963
+ resolveSizes(size) {
28964
+ if (typeof size === 'string') {
28965
+ // Single preset size
28966
+ const mapped = AD_SIZE_MAP[size];
28967
+ return mapped === 'fluid' ? 'fluid' : [mapped];
28968
+ }
28969
+ if (Array.isArray(size)) {
28970
+ if (typeof size[0] === 'string') {
28971
+ // Array of preset sizes
28972
+ return size.map((s) => {
28973
+ const mapped = AD_SIZE_MAP[s];
28974
+ return mapped === 'fluid' ? 'fluid' : mapped;
28975
+ });
28976
+ }
28977
+ // Direct size array
28978
+ return size;
28979
+ }
28980
+ return 'fluid';
28981
+ }
28982
+ buildSizeMapping(googletag, mapping) {
28983
+ const builder = googletag.sizeMapping();
28984
+ // Ordenar por viewport descendente
28985
+ const sorted = [...mapping].sort((a, b) => b.viewportWidth - a.viewportWidth);
28986
+ for (const m of sorted) {
28987
+ builder.addSize([m.viewportWidth, 0], m.sizes);
28988
+ }
28989
+ return builder.build();
28990
+ }
28991
+ applyTargeting(slot, targeting) {
28992
+ // Aplicar targeting global
28993
+ if (this.config.globalTargeting) {
28994
+ for (const [key, value] of Object.entries(this.config.globalTargeting)) {
28995
+ slot.setTargeting(key, value);
28996
+ }
28997
+ }
28998
+ // Aplicar targeting del slot
28999
+ if (targeting) {
29000
+ for (const [key, value] of Object.entries(targeting)) {
29001
+ slot.setTargeting(key, value);
29002
+ }
29003
+ }
29004
+ // Agregar targeting de debug si esta habilitado
29005
+ if (this._isDebugMode()) {
29006
+ slot.setTargeting('test', 'true');
29007
+ }
29008
+ }
29009
+ updateSlotState(slotId, state) {
29010
+ this._slotStates.update((states) => new Map(states).set(slotId, state));
29011
+ }
29012
+ emitEvent(event) {
29013
+ const fullEvent = { ...event, timestamp: new Date() };
29014
+ this._events.update((events) => [...events.slice(-99), fullEvent]);
29015
+ // Callbacks de configuracion
29016
+ if (event.type === 'loaded' || event.type === 'empty') {
29017
+ this.config.onAdLoaded?.(event.slotId, event.isEmpty ?? false);
29018
+ }
29019
+ else if (event.type === 'error' && event.error) {
29020
+ this.config.onAdError?.(event.slotId, event.error);
29021
+ }
29022
+ }
29023
+ setupRouteListener() {
29024
+ if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
29025
+ return;
29026
+ }
29027
+ this.routerSubscription = this.router.events
29028
+ .pipe(filter$1((event) => event instanceof NavigationEnd))
29029
+ .subscribe((event) => {
29030
+ const shouldHide = this.isRouteExcluded(event.urlAfterRedirects);
29031
+ if (shouldHide) {
29032
+ this.destroyAllSlots();
29033
+ }
29034
+ });
29035
+ }
29036
+ setupAutoRefresh() {
29037
+ const intervalMs = (this.config.autoRefreshInterval ?? 0) * 1000;
29038
+ if (intervalMs <= 0)
29039
+ return;
29040
+ this.refreshTimerId = setInterval(() => {
29041
+ if (this.isEnabled() && this.activeSlots.size > 0) {
29042
+ this.refreshSlots();
29043
+ }
29044
+ }, intervalMs);
29045
+ }
29046
+ isRouteExcluded(url) {
29047
+ if (!this.config.excludeRoutes || this.config.excludeRoutes.length === 0) {
29048
+ return false;
29049
+ }
29050
+ const currentUrl = url ?? this.router.url;
29051
+ return this.config.excludeRoutes.some((pattern) => {
29052
+ const regex = new RegExp(pattern);
29053
+ return regex.test(currentUrl);
29054
+ });
29055
+ }
29056
+ // ===========================================================================
29057
+ // LIFECYCLE
29058
+ // ===========================================================================
29059
+ ngOnDestroy() {
29060
+ this.routerSubscription?.unsubscribe();
29061
+ if (this.refreshTimerId) {
29062
+ clearInterval(this.refreshTimerId);
29063
+ }
29064
+ this.destroyAllSlots();
29065
+ this.consentService.destroy();
29066
+ }
29067
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, deps: [{ token: i0.Injector }, { token: VALTECH_ADS_CONFIG }, { token: PLATFORM_ID }, { token: i1$1.Router }, { token: AdsLoaderService }, { token: AdsConsentService }], target: i0.ɵɵFactoryTarget.Injectable }); }
29068
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, providedIn: 'root' }); }
26896
29069
  }
26897
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PresetService, decorators: [{
29070
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdsService, decorators: [{
26898
29071
  type: Injectable,
26899
29072
  args: [{ providedIn: 'root' }]
26900
- }] });
29073
+ }], ctorParameters: () => [{ type: i0.Injector }, { type: undefined, decorators: [{
29074
+ type: Inject,
29075
+ args: [VALTECH_ADS_CONFIG]
29076
+ }] }, { type: Object, decorators: [{
29077
+ type: Inject,
29078
+ args: [PLATFORM_ID]
29079
+ }] }, { type: i1$1.Router }, { type: AdsLoaderService }, { type: AdsConsentService }] });
26901
29080
 
26902
29081
  /**
26903
- * Configura el sistema de presets de Valtech Components.
29082
+ * Ads Configuration
26904
29083
  *
26905
- * @param presets Configuración de presets por componente
26906
- * @returns Providers para agregar en app.config.ts
29084
+ * Configuracion e inicializacion de Google Ad Manager para Angular.
29085
+ * Usa provideValtechAds() en el bootstrap de tu aplicacion.
29086
+ */
29087
+ /**
29088
+ * Token de inyeccion para la configuracion de Ads.
29089
+ * Usado internamente por los servicios de ads.
29090
+ */
29091
+ const VALTECH_ADS_CONFIG = new InjectionToken('ValtechAdsConfig');
29092
+ /**
29093
+ * Configuracion por defecto de lazy loading.
29094
+ */
29095
+ const DEFAULT_LAZY_LOAD_CONFIG = {
29096
+ rootMargin: '200px',
29097
+ threshold: 0,
29098
+ fetchMarginPercent: 200,
29099
+ renderMarginPercent: 100,
29100
+ };
29101
+ /**
29102
+ * Configuracion por defecto del servicio de ads.
29103
+ */
29104
+ const DEFAULT_ADS_CONFIG = {
29105
+ debugMode: false,
29106
+ lazyLoad: true,
29107
+ lazyLoadConfig: DEFAULT_LAZY_LOAD_CONFIG,
29108
+ enablePersonalization: true,
29109
+ showNonPersonalizedAds: true,
29110
+ autoRefreshInterval: 0, // Deshabilitado por defecto
29111
+ defaultSlotConfig: {
29112
+ collapseEmpty: true,
29113
+ showSkeleton: true,
29114
+ minHeight: '90px',
29115
+ },
29116
+ };
29117
+ /**
29118
+ * Factory para inicializar AdsService.
29119
+ * Se ejecuta durante el bootstrap de la aplicacion.
29120
+ */
29121
+ function initializeAds(adsService) {
29122
+ return () => adsService.initialize();
29123
+ }
29124
+ /**
29125
+ * Provee el servicio de Ads a la aplicacion Angular.
29126
+ *
29127
+ * @param config - Configuracion de Google Ad Manager
29128
+ * @returns EnvironmentProviders para usar en bootstrapApplication
26907
29129
  *
26908
29130
  * @example
26909
- * // app.config.ts
26910
- * import { provideValtechPresets } from 'valtech-components';
29131
+ * ```typescript
29132
+ * // main.ts
29133
+ * import { bootstrapApplication } from '@angular/platform-browser';
29134
+ * import { provideValtechAds, provideValtechFirebase } from 'valtech-components';
29135
+ * import { environment } from './environments/environment';
26911
29136
  *
26912
- * export const appConfig: ApplicationConfig = {
29137
+ * bootstrapApplication(AppComponent, {
26913
29138
  * providers: [
26914
- * provideValtechPresets({
26915
- * button: {
26916
- * 'primary-action': { size: 'large', color: 'primary', fill: 'solid' },
26917
- * 'secondary': { size: 'medium', color: 'secondary', fill: 'outline' },
26918
- * 'danger': { size: 'medium', color: 'danger', fill: 'solid' },
26919
- * },
26920
- * card: {
26921
- * 'feature': { variant: 'elevated', padding: 'large' },
26922
- * 'compact': { variant: 'flat', padding: 'small' },
26923
- * },
26924
- * input: {
26925
- * 'form-field': { size: 'medium', fill: 'outline', labelPosition: 'floating' },
26926
- * }
29139
+ * // Firebase primero (para consent mode)
29140
+ * provideValtechFirebase({
29141
+ * firebase: environment.firebase,
29142
+ * enableAnalytics: true,
26927
29143
  * }),
26928
- * ]
26929
- * };
29144
+ *
29145
+ * // Luego Ads
29146
+ * provideValtechAds({
29147
+ * networkId: '/12345678',
29148
+ * debugMode: !environment.production,
29149
+ * isPremiumUser: () => inject(AuthService).hasRole('premium'),
29150
+ * }),
29151
+ * ],
29152
+ * });
29153
+ * ```
29154
+ *
29155
+ * @example Con targeting global y rutas excluidas
29156
+ * ```typescript
29157
+ * provideValtechAds({
29158
+ * networkId: '/12345678',
29159
+ * globalTargeting: {
29160
+ * site: 'myvaltech',
29161
+ * section: 'app',
29162
+ * },
29163
+ * excludeRoutes: [
29164
+ * '^/checkout',
29165
+ * '^/payment',
29166
+ * '^/premium',
29167
+ * ],
29168
+ * })
29169
+ * ```
26930
29170
  */
26931
- function provideValtechPresets(presets) {
29171
+ function provideValtechAds(config) {
29172
+ // Merge config con defaults
29173
+ const mergedConfig = {
29174
+ ...DEFAULT_ADS_CONFIG,
29175
+ ...config,
29176
+ lazyLoadConfig: {
29177
+ ...DEFAULT_LAZY_LOAD_CONFIG,
29178
+ ...config.lazyLoadConfig,
29179
+ },
29180
+ defaultSlotConfig: {
29181
+ ...DEFAULT_ADS_CONFIG.defaultSlotConfig,
29182
+ ...config.defaultSlotConfig,
29183
+ },
29184
+ };
26932
29185
  return makeEnvironmentProviders([
29186
+ { provide: VALTECH_ADS_CONFIG, useValue: mergedConfig },
29187
+ // Inicializar AdsService al arrancar (pero NO cargar script GPT aun)
26933
29188
  {
26934
29189
  provide: APP_INITIALIZER,
26935
- useFactory: (presetService) => {
26936
- return () => {
26937
- presetService.registerPresets(presets);
26938
- };
26939
- },
26940
- deps: [PresetService],
29190
+ useFactory: initializeAds,
29191
+ deps: [AdsService],
26941
29192
  multi: true,
26942
29193
  },
26943
29194
  ]);
26944
29195
  }
26945
29196
 
26946
- // Service
29197
+ /**
29198
+ * Ads Module
29199
+ *
29200
+ * Exports publicos para el servicio de Google Ad Manager.
29201
+ */
29202
+ // Configuration
29203
+
29204
+ /**
29205
+ * Ad Slot Component
29206
+ *
29207
+ * Componente standalone para mostrar ads de Google Ad Manager.
29208
+ * Se integra automaticamente con el servicio de Ads y respeta consent + premium.
29209
+ *
29210
+ * @example
29211
+ * ```html
29212
+ * <!-- Banner basico -->
29213
+ * <val-ad-slot
29214
+ * slotId="homepage-top"
29215
+ * adUnitPath="/homepage/top"
29216
+ * size="leaderboard"
29217
+ * />
29218
+ *
29219
+ * <!-- Con responsivo -->
29220
+ * <val-ad-slot
29221
+ * slotId="sidebar-ad"
29222
+ * adUnitPath="/sidebar"
29223
+ * [size]="['medium-rectangle', 'half-page']"
29224
+ * [sizeMapping]="[
29225
+ * { viewportWidth: 1024, sizes: [[300, 600]] },
29226
+ * { viewportWidth: 768, sizes: [[300, 250]] },
29227
+ * { viewportWidth: 0, sizes: [[320, 50]] }
29228
+ * ]"
29229
+ * />
29230
+ *
29231
+ * <!-- Native ad -->
29232
+ * <val-ad-slot
29233
+ * slotId="article-native"
29234
+ * adUnitPath="/article/native"
29235
+ * size="native"
29236
+ * [isNative]="true"
29237
+ * />
29238
+ * ```
29239
+ */
29240
+ class AdSlotComponent {
29241
+ constructor() {
29242
+ this.adsService = inject(AdsService);
29243
+ this.platformId = inject(PLATFORM_ID);
29244
+ /** Tamano del ad */
29245
+ this.size = 'medium-rectangle';
29246
+ /** Colapsar si vacio */
29247
+ this.collapseEmpty = true;
29248
+ /** Es native ad */
29249
+ this.isNative = false;
29250
+ /** CSS class adicional */
29251
+ this.cssClass = '';
29252
+ /** Altura minima */
29253
+ this.minHeight = '90px';
29254
+ /** Mostrar skeleton */
29255
+ this.showSkeleton = true;
29256
+ // ===========================================================================
29257
+ // ESTADO
29258
+ // ===========================================================================
29259
+ this._state = signal('idle');
29260
+ this.state = this._state.asReadonly();
29261
+ /** Indica si el componente debe renderizarse */
29262
+ this.shouldRender = computed(() => {
29263
+ // No renderizar en SSR
29264
+ if (!isPlatformBrowser(this.platformId)) {
29265
+ return false;
29266
+ }
29267
+ // No renderizar si usuario es premium
29268
+ if (this.adsService.isPremiumUser()) {
29269
+ return false;
29270
+ }
29271
+ // No renderizar si ads estan deshabilitados y aun no inicializado
29272
+ if (!this.adsService.isInitialized()) {
29273
+ return false;
29274
+ }
29275
+ return true;
29276
+ });
29277
+ /** Ancho del skeleton */
29278
+ this.skeletonWidth = computed(() => {
29279
+ const firstSize = this.getFirstSize();
29280
+ return firstSize ? `${firstSize[0]}px` : '100%';
29281
+ });
29282
+ /** Altura del skeleton */
29283
+ this.skeletonHeight = computed(() => {
29284
+ const firstSize = this.getFirstSize();
29285
+ return firstSize ? `${firstSize[1]}px` : this.minHeight;
29286
+ });
29287
+ }
29288
+ // ===========================================================================
29289
+ // LIFECYCLE
29290
+ // ===========================================================================
29291
+ async ngOnInit() {
29292
+ if (!this.shouldRender()) {
29293
+ return;
29294
+ }
29295
+ await this.initializeSlot();
29296
+ }
29297
+ ngOnDestroy() {
29298
+ if (isPlatformBrowser(this.platformId)) {
29299
+ this.adsService.destroySlot(this.slotId);
29300
+ }
29301
+ }
29302
+ // ===========================================================================
29303
+ // PRIVATE
29304
+ // ===========================================================================
29305
+ async initializeSlot() {
29306
+ this._state.set('loading');
29307
+ const config = {
29308
+ slotId: this.slotId,
29309
+ adUnitPath: this.adUnitPath,
29310
+ size: this.size,
29311
+ sizeMapping: this.sizeMapping,
29312
+ targeting: this.targeting,
29313
+ collapseEmpty: this.collapseEmpty,
29314
+ isNative: this.isNative,
29315
+ cssClass: this.cssClass,
29316
+ minHeight: this.minHeight,
29317
+ showSkeleton: this.showSkeleton,
29318
+ };
29319
+ const result = await this.adsService.defineSlot(config);
29320
+ if (result) {
29321
+ this._state.set('rendered');
29322
+ }
29323
+ else {
29324
+ this._state.set(this.adsService.getSlotState(this.slotId));
29325
+ }
29326
+ }
29327
+ getFirstSize() {
29328
+ if (typeof this.size === 'string') {
29329
+ const mapped = AD_SIZE_MAP[this.size];
29330
+ return mapped === 'fluid' ? null : mapped;
29331
+ }
29332
+ if (Array.isArray(this.size)) {
29333
+ if (typeof this.size[0] === 'number') {
29334
+ return this.size;
29335
+ }
29336
+ if (typeof this.size[0] === 'string') {
29337
+ const first = AD_SIZE_MAP[this.size[0]];
29338
+ return first === 'fluid' ? null : first;
29339
+ }
29340
+ if (Array.isArray(this.size[0])) {
29341
+ return this.size[0];
29342
+ }
29343
+ }
29344
+ return null;
29345
+ }
29346
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
29347
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: AdSlotComponent, isStandalone: true, selector: "val-ad-slot", inputs: { slotId: "slotId", adUnitPath: "adUnitPath", size: "size", sizeMapping: "sizeMapping", targeting: "targeting", collapseEmpty: "collapseEmpty", isNative: "isNative", cssClass: "cssClass", minHeight: "minHeight", showSkeleton: "showSkeleton" }, ngImport: i0, template: `
29348
+ @if (shouldRender()) {
29349
+ <div
29350
+ class="val-ad-slot"
29351
+ [class.val-ad-slot--loading]="state() === 'loading'"
29352
+ [class.val-ad-slot--rendered]="state() === 'rendered'"
29353
+ [class.val-ad-slot--empty]="state() === 'empty'"
29354
+ [class.val-ad-slot--hidden]="state() === 'hidden'"
29355
+ [class.val-ad-slot--native]="isNative"
29356
+ [class]="cssClass"
29357
+ [style.min-height]="minHeight"
29358
+ >
29359
+ <!-- Skeleton mientras carga -->
29360
+ @if (showSkeleton && state() === 'loading') {
29361
+ <div
29362
+ class="val-ad-slot__skeleton"
29363
+ [style.width]="skeletonWidth()"
29364
+ [style.height]="skeletonHeight()"
29365
+ ></div>
29366
+ }
29367
+
29368
+ <!-- Container del ad -->
29369
+ <div
29370
+ [id]="slotId"
29371
+ class="val-ad-slot__container"
29372
+ [class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
29373
+ ></div>
29374
+
29375
+ <!-- Debug info -->
29376
+ @if (adsService.isDebugMode()) {
29377
+ <div class="val-ad-slot__debug">
29378
+ <small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
29379
+ </div>
29380
+ }
29381
+ </div>
29382
+ }
29383
+ `, isInline: true, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
29384
+ }
29385
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AdSlotComponent, decorators: [{
29386
+ type: Component,
29387
+ args: [{ selector: 'val-ad-slot', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
29388
+ @if (shouldRender()) {
29389
+ <div
29390
+ class="val-ad-slot"
29391
+ [class.val-ad-slot--loading]="state() === 'loading'"
29392
+ [class.val-ad-slot--rendered]="state() === 'rendered'"
29393
+ [class.val-ad-slot--empty]="state() === 'empty'"
29394
+ [class.val-ad-slot--hidden]="state() === 'hidden'"
29395
+ [class.val-ad-slot--native]="isNative"
29396
+ [class]="cssClass"
29397
+ [style.min-height]="minHeight"
29398
+ >
29399
+ <!-- Skeleton mientras carga -->
29400
+ @if (showSkeleton && state() === 'loading') {
29401
+ <div
29402
+ class="val-ad-slot__skeleton"
29403
+ [style.width]="skeletonWidth()"
29404
+ [style.height]="skeletonHeight()"
29405
+ ></div>
29406
+ }
29407
+
29408
+ <!-- Container del ad -->
29409
+ <div
29410
+ [id]="slotId"
29411
+ class="val-ad-slot__container"
29412
+ [class.val-ad-slot__container--hidden]="state() === 'loading' && showSkeleton"
29413
+ ></div>
29414
+
29415
+ <!-- Debug info -->
29416
+ @if (adsService.isDebugMode()) {
29417
+ <div class="val-ad-slot__debug">
29418
+ <small>{{ slotId }} | {{ adUnitPath }} | {{ state() }}</small>
29419
+ </div>
29420
+ }
29421
+ </div>
29422
+ }
29423
+ `, styles: [".val-ad-slot{position:relative;display:flex;justify-content:center;align-items:center;overflow:hidden}.val-ad-slot--loading{background-color:var(--ion-color-light, #f4f4f4)}.val-ad-slot--empty,.val-ad-slot--hidden{display:none!important}.val-ad-slot--native{width:100%}.val-ad-slot__skeleton{background:linear-gradient(90deg,var(--ion-color-light, #f4f4f4) 25%,var(--ion-color-light-shade, #e0e0e0) 50%,var(--ion-color-light, #f4f4f4) 75%);background-size:200% 100%;animation:skeleton-loading 1.5s infinite;border-radius:4px}.val-ad-slot__container--hidden{visibility:hidden;position:absolute}.val-ad-slot__debug{position:absolute;bottom:0;left:0;background:#000000b3;color:#fff;padding:2px 6px;font-size:10px;z-index:1000;font-family:monospace}@keyframes skeleton-loading{0%{background-position:200% 0}to{background-position:-200% 0}}\n"] }]
29424
+ }], propDecorators: { slotId: [{
29425
+ type: Input,
29426
+ args: [{ required: true }]
29427
+ }], adUnitPath: [{
29428
+ type: Input,
29429
+ args: [{ required: true }]
29430
+ }], size: [{
29431
+ type: Input
29432
+ }], sizeMapping: [{
29433
+ type: Input
29434
+ }], targeting: [{
29435
+ type: Input
29436
+ }], collapseEmpty: [{
29437
+ type: Input
29438
+ }], isNative: [{
29439
+ type: Input
29440
+ }], cssClass: [{
29441
+ type: Input
29442
+ }], minHeight: [{
29443
+ type: Input
29444
+ }], showSkeleton: [{
29445
+ type: Input
29446
+ }] } });
26947
29447
 
26948
29448
  /*
26949
29449
  * Public API Surface of valtech-components
@@ -26953,5 +29453,5 @@ function provideValtechPresets(presets) {
26953
29453
  * Generated bundle index. Do not edit.
26954
29454
  */
26955
29455
 
26956
- export { ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
29456
+ export { AD_SIZE_MAP, ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsConsentService, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, ArticleBuilder, ArticleComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, BannerComponent, BaseDefault, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, CheckInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContentLoaderComponent, CountdownComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CANCEL_BUTTON, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_EMPTY_STATE, DEFAULT_LAZY_LOAD_CONFIG, DEFAULT_LEGEND_LABELS, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAYMENT_STATUS_COLORS, DEFAULT_PAYMENT_STATUS_LABELS, DEFAULT_PLATFORMS, DEFAULT_STATUS_COLORS, DEFAULT_STATUS_LABELS, DEFAULT_WINNER_LABELS, DataTableComponent, DateInputComponent, DateRangeInputComponent, DeviceService, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAuthCallbackComponent, OAuthService, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, RadioInputComponent, RaffleStatusCardComponent, RangeInputComponent, RatingComponent, RecapCardComponent, RightsFooterComponent, SKELETON_PRESETS, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SegmentControlComponent, SelectSearchComponent, SessionService, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TranslatePipe, TypedCollection, VALTECH_ADS_CONFIG, VALTECH_AUTH_CONFIG, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, guestGuard, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, permissionGuard, permissionGuardFromRoute, provideValtechAds, provideValtechAuth, provideValtechAuthInterceptor, provideValtechFirebase, provideValtechI18n, provideValtechPresets, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, roleGuard, storagePaths, superAdminGuard };
26957
29457
  //# sourceMappingURL=valtech-components.mjs.map