valtech-components 2.0.448 → 2.0.450

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 (27) hide show
  1. package/esm2022/public-api.mjs +3 -2
  2. package/fesm2022/valtech-components.mjs +4 -2481
  3. package/fesm2022/valtech-components.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/public-api.d.ts +0 -1
  6. package/esm2022/lib/services/firebase/config.mjs +0 -108
  7. package/esm2022/lib/services/firebase/firebase.service.mjs +0 -285
  8. package/esm2022/lib/services/firebase/firestore-collection.mjs +0 -266
  9. package/esm2022/lib/services/firebase/firestore.service.mjs +0 -508
  10. package/esm2022/lib/services/firebase/index.mjs +0 -47
  11. package/esm2022/lib/services/firebase/messaging.service.mjs +0 -503
  12. package/esm2022/lib/services/firebase/shared-config.mjs +0 -138
  13. package/esm2022/lib/services/firebase/storage.service.mjs +0 -421
  14. package/esm2022/lib/services/firebase/types.mjs +0 -8
  15. package/esm2022/lib/services/firebase/utils/path-builder.mjs +0 -195
  16. package/esm2022/lib/services/firebase/utils/query-builder.mjs +0 -302
  17. package/lib/services/firebase/config.d.ts +0 -49
  18. package/lib/services/firebase/firebase.service.d.ts +0 -140
  19. package/lib/services/firebase/firestore-collection.d.ts +0 -195
  20. package/lib/services/firebase/firestore.service.d.ts +0 -303
  21. package/lib/services/firebase/index.d.ts +0 -40
  22. package/lib/services/firebase/messaging.service.d.ts +0 -254
  23. package/lib/services/firebase/shared-config.d.ts +0 -126
  24. package/lib/services/firebase/storage.service.d.ts +0 -204
  25. package/lib/services/firebase/types.d.ts +0 -281
  26. package/lib/services/firebase/utils/path-builder.d.ts +0 -132
  27. package/lib/services/firebase/utils/query-builder.d.ts +0 -210
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { EventEmitter, Component, Input, Output, Injectable, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, ElementRef, signal, computed, InjectionToken, makeEnvironmentProviders, PLATFORM_ID, NgZone } from '@angular/core';
2
+ import { EventEmitter, Component, Input, Output, Injectable, inject, HostListener, Pipe, ChangeDetectionStrategy, ViewChild, ChangeDetectorRef, ElementRef, signal, computed } 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';
6
- import { CommonModule, NgStyle, Location, NgFor, NgClass, isPlatformBrowser } from '@angular/common';
6
+ import { CommonModule, NgStyle, Location, NgFor, NgClass } from '@angular/common';
7
7
  import { addIcons } from 'ionicons';
8
8
  import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, list, grid, apps, menu, settings, home, search, person, helpCircle, informationCircle, documentText, notifications, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, checkmark, ellipse, starOutline, starHalf, heartHalf, checkmarkCircle, timeOutline, flag, trendingUp, trendingDown, remove, analytics, people, cash, cart, eye, chatbubbleOutline, thumbsUpOutline, thumbsUp, happyOutline, happy, sadOutline, sad, chevronUp, pin, pencil, callOutline, shuffleOutline, logoWhatsapp, paperPlaneOutline, mailOutline, trophyOutline, ticketOutline, giftOutline, personOutline, ellipsisVertical, closeCircle, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, documentOutline } from 'ionicons/icons';
9
9
  import * as i1$4 from '@angular/router';
@@ -13,7 +13,7 @@ import * as i1$1 from '@angular/platform-browser';
13
13
  import QRCodeStyling from 'qr-code-styling';
14
14
  import * as i1$2 from '@angular/forms';
15
15
  import { ReactiveFormsModule, FormsModule, FormControl, Validators } from '@angular/forms';
16
- import { BehaviorSubject, filter, map, distinctUntilChanged, Subject } from 'rxjs';
16
+ import { BehaviorSubject, filter } from 'rxjs';
17
17
  import * as i1$3 from 'ng-otp-input';
18
18
  import { NgOtpInputComponent, NgOtpInputModule } from 'ng-otp-input';
19
19
  import * as i2 from '@ionic/angular';
@@ -27,11 +27,6 @@ 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 { provideFirebaseApp, initializeApp } from '@angular/fire/app';
31
- import { provideAuth, getAuth, connectAuthEmulator, Auth, authState, signInWithCustomToken, signOut } from '@angular/fire/auth';
32
- import { provideFirestore, getFirestore, connectFirestoreEmulator, enableIndexedDbPersistence, Firestore, doc, getDoc, collection, query as query$1, getDocs, limit, docData, collectionData, serverTimestamp, addDoc, setDoc, updateDoc, deleteDoc, writeBatch, arrayUnion, arrayRemove, increment, where, orderBy, startAfter, startAt, endBefore, endAt, Timestamp } from '@angular/fire/firestore';
33
- import { provideMessaging, getMessaging, Messaging, getToken, deleteToken, onMessage } from '@angular/fire/messaging';
34
- import { provideStorage, getStorage, connectStorageEmulator, Storage, ref, uploadBytesResumable, getDownloadURL, getMetadata, deleteObject, listAll } from '@angular/fire/storage';
35
30
 
36
31
  /**
37
32
  * val-avatar
@@ -21133,2478 +21128,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
21133
21128
  }]
21134
21129
  }], ctorParameters: () => [{ type: i2$1.ModalController }] });
21135
21130
 
21136
- /**
21137
- * Firebase Types
21138
- *
21139
- * Tipos e interfaces para la integración de Firebase en valtech-components.
21140
- * Todos los modelos de Firestore deben extender FirestoreDocument.
21141
- */
21142
-
21143
- /**
21144
- * Firebase Configuration
21145
- *
21146
- * Configuración e inicialización de Firebase para aplicaciones Angular.
21147
- * Usa provideValtechFirebase() en el bootstrap de tu aplicación.
21148
- */
21149
- /**
21150
- * Token de inyección para la configuración de Firebase.
21151
- * Usado internamente por los servicios de Firebase.
21152
- */
21153
- const VALTECH_FIREBASE_CONFIG = new InjectionToken('ValtechFirebaseConfig');
21154
- /**
21155
- * Provee Firebase a la aplicación Angular.
21156
- *
21157
- * @param config - Configuración de Firebase
21158
- * @returns EnvironmentProviders para usar en bootstrapApplication
21159
- *
21160
- * @example
21161
- * ```typescript
21162
- * // main.ts
21163
- * import { bootstrapApplication } from '@angular/platform-browser';
21164
- * import { provideValtechFirebase } from 'valtech-components';
21165
- * import { environment } from './environments/environment';
21166
- *
21167
- * bootstrapApplication(AppComponent, {
21168
- * providers: [
21169
- * provideValtechFirebase({
21170
- * firebase: environment.firebase,
21171
- * persistence: true,
21172
- * emulator: environment.useEmulators ? {
21173
- * firestore: { host: 'localhost', port: 8080 },
21174
- * auth: { host: 'localhost', port: 9099 },
21175
- * storage: { host: 'localhost', port: 9199 },
21176
- * } : undefined,
21177
- * }),
21178
- * ],
21179
- * });
21180
- * ```
21181
- */
21182
- function provideValtechFirebase(config) {
21183
- // Construir array de providers base
21184
- const providers = [
21185
- // Guardar configuración para uso en servicios
21186
- { provide: VALTECH_FIREBASE_CONFIG, useValue: config },
21187
- // Inicializar Firebase App
21188
- provideFirebaseApp(() => initializeApp(config.firebase)),
21189
- // Firestore con soporte para emuladores y persistencia
21190
- provideFirestore(() => {
21191
- const firestore = getFirestore();
21192
- // Conectar a emulador si está configurado
21193
- if (config.emulator?.firestore) {
21194
- connectFirestoreEmulator(firestore, config.emulator.firestore.host, config.emulator.firestore.port);
21195
- }
21196
- // Habilitar persistencia offline si está configurada
21197
- if (config.persistence) {
21198
- enableIndexedDbPersistence(firestore).catch((err) => {
21199
- if (err.code === 'failed-precondition') {
21200
- console.warn('[ValtechFirebase] Persistencia no disponible: múltiples pestañas abiertas');
21201
- }
21202
- else if (err.code === 'unimplemented') {
21203
- console.warn('[ValtechFirebase] Persistencia no soportada en este navegador');
21204
- }
21205
- });
21206
- }
21207
- return firestore;
21208
- }),
21209
- // Auth con soporte para emulador
21210
- provideAuth(() => {
21211
- const auth = getAuth();
21212
- // Conectar a emulador si está configurado
21213
- if (config.emulator?.auth) {
21214
- connectAuthEmulator(auth, `http://${config.emulator.auth.host}:${config.emulator.auth.port}`, { disableWarnings: true });
21215
- }
21216
- return auth;
21217
- }),
21218
- // Storage con soporte para emulador
21219
- provideStorage(() => {
21220
- const storage = getStorage();
21221
- // Conectar a emulador si está configurado
21222
- if (config.emulator?.storage) {
21223
- connectStorageEmulator(storage, config.emulator.storage.host, config.emulator.storage.port);
21224
- }
21225
- return storage;
21226
- }),
21227
- ];
21228
- // Messaging (FCM) - solo si está explícitamente habilitado
21229
- // Requiere Service Worker configurado, puede congelar la app si no está disponible
21230
- if (config.enableMessaging) {
21231
- providers.push(provideMessaging(() => getMessaging()));
21232
- }
21233
- return makeEnvironmentProviders(providers);
21234
- }
21235
- /**
21236
- * Verifica si los emuladores están configurados.
21237
- *
21238
- * @param config - Configuración de Firebase
21239
- * @returns true si hay al menos un emulador configurado
21240
- */
21241
- function hasEmulators(config) {
21242
- return !!(config.emulator?.firestore || config.emulator?.auth || config.emulator?.storage);
21243
- }
21244
-
21245
- /**
21246
- * Firebase Shared Configuration
21247
- *
21248
- * Configuración base de Firebase compartida entre todas las apps del monorepo.
21249
- * Los secrets (apiKey, appId) se inyectan en build time via environment.
21250
- */
21251
- const APP_IDS = {
21252
- DEMO: 'demo',
21253
- SHOWCASE: 'showcase',
21254
- ADMIN_PORTAL: 'admin-portal',
21255
- APP: 'app',
21256
- };
21257
- // ============================================================================
21258
- // FIREBASE PROJECT IDS
21259
- // ============================================================================
21260
- /**
21261
- * IDs de los proyectos Firebase por ambiente.
21262
- * Deben coincidir con los proyectos creados en Firebase Console.
21263
- */
21264
- const FIREBASE_PROJECTS = {
21265
- development: 'myvaltech-dev',
21266
- production: 'myvaltech-prod',
21267
- };
21268
- // ============================================================================
21269
- // EMULATOR CONFIGURATION (shared across all apps)
21270
- // ============================================================================
21271
- /**
21272
- * Configuración de emuladores compartida.
21273
- * Todos los puertos deben coincidir con frontend/firebase/firebase.json
21274
- */
21275
- const SHARED_EMULATOR_CONFIG = {
21276
- firestore: { host: 'localhost', port: 9080 },
21277
- storage: { host: 'localhost', port: 9199 },
21278
- auth: { host: 'localhost', port: 9099 },
21279
- };
21280
- // ============================================================================
21281
- // STORAGE PATH BUILDERS
21282
- // ============================================================================
21283
- /**
21284
- * Genera paths de Storage con namespace por app.
21285
- *
21286
- * @example
21287
- * // Path específico de la app
21288
- * storagePaths.forApp('showcase', 'uploads', 'image.jpg')
21289
- * // => 'showcase/uploads/image.jpg'
21290
- *
21291
- * // Path compartido
21292
- * storagePaths.shared.profilePhoto('user123', 'avatar.jpg')
21293
- * // => 'profile-photos/user123/avatar.jpg'
21294
- */
21295
- const storagePaths = {
21296
- /** Carpeta específica de la app: {appId}/{...paths} */
21297
- forApp: (appId, ...paths) => [appId, ...paths].join('/'),
21298
- /** Carpetas compartidas (sin namespace) */
21299
- shared: {
21300
- /** Foto de perfil de usuario */
21301
- profilePhoto: (userId, fileName) => `profile-photos/${userId}/${fileName}`,
21302
- /** Archivos públicos accesibles sin autenticación */
21303
- public: (...paths) => `public/${paths.join('/')}`,
21304
- },
21305
- /** Carpetas de desarrollo (acceso libre en emuladores) */
21306
- demo: (...paths) => `demo/${paths.join('/')}`,
21307
- };
21308
- // ============================================================================
21309
- // FIRESTORE COLLECTION BUILDERS
21310
- // ============================================================================
21311
- /**
21312
- * Genera paths de colecciones con namespace por app.
21313
- *
21314
- * IMPORTANTE: La estructura es /apps/{appId}/{collection}/{docId}
21315
- * Firestore requiere número impar de segmentos para paths de colección.
21316
- *
21317
- * @example
21318
- * // Colección específica de la app
21319
- * collections.forApp('showcase', 'items')
21320
- * // => 'apps/showcase/items'
21321
- *
21322
- * // Colección de desarrollo
21323
- * collections.forApp('demo', 'items')
21324
- * // => 'apps/demo/items'
21325
- *
21326
- * // Colección compartida
21327
- * collections.shared.users
21328
- * // => 'users'
21329
- */
21330
- const collections = {
21331
- /** Colección específica de la app: apps/{appId}/{collection} */
21332
- forApp: (appId, collectionName) => `apps/${appId}/${collectionName}`,
21333
- /** Colecciones compartidas (sin namespace, nivel raíz) */
21334
- shared: {
21335
- /** Usuarios del sistema */
21336
- users: 'users',
21337
- /** Perfiles públicos */
21338
- profiles: 'profiles',
21339
- /** Notificaciones de usuarios */
21340
- notifications: 'notifications',
21341
- },
21342
- };
21343
- /**
21344
- * Crea la configuración completa de Firebase desde variables de entorno.
21345
- * Usa esto en el environment.ts de cada app.
21346
- *
21347
- * @example
21348
- * // environment.ts
21349
- * export const environment = {
21350
- * firebase: createFirebaseConfig(
21351
- * {
21352
- * apiKey: 'AIza...',
21353
- * authDomain: 'myvaltech-dev.firebaseapp.com',
21354
- * projectId: 'myvaltech-dev',
21355
- * storageBucket: 'myvaltech-dev.appspot.com',
21356
- * messagingSenderId: '123456789',
21357
- * appId: '1:123456789:web:abc123',
21358
- * },
21359
- * { useEmulators: true, persistence: true }
21360
- * ),
21361
- * };
21362
- */
21363
- function createFirebaseConfig(envConfig, options = {}) {
21364
- const { useEmulators = false, persistence = true, enableMessaging = false, messagingVapidKey } = options;
21365
- return {
21366
- firebase: envConfig,
21367
- persistence,
21368
- enableMessaging,
21369
- messagingVapidKey,
21370
- emulator: useEmulators ? SHARED_EMULATOR_CONFIG : undefined,
21371
- };
21372
- }
21373
- // ============================================================================
21374
- // UTILITY: Check if running in emulator mode
21375
- // ============================================================================
21376
- /**
21377
- * Verifica si la configuración tiene emuladores habilitados
21378
- */
21379
- function isEmulatorMode(config) {
21380
- return config.emulator !== undefined;
21381
- }
21382
-
21383
- /**
21384
- * Firebase Service
21385
- *
21386
- * Servicio principal para la autenticación con Firebase usando Custom Tokens.
21387
- * Permite que usuarios autenticados con tu backend (Cognito, etc.) accedan
21388
- * a servicios de Firebase (Firestore, Storage, FCM) de manera segura.
21389
- */
21390
- /**
21391
- * Servicio de autenticación de Firebase.
21392
- *
21393
- * Este servicio NO maneja el login de usuarios directamente.
21394
- * En su lugar, trabaja con Custom Tokens generados por tu backend.
21395
- *
21396
- * @example
21397
- * ```typescript
21398
- * // Después de autenticarte con tu backend (ej: Cognito)
21399
- * @Component({...})
21400
- * export class LoginComponent {
21401
- * private authService = inject(AuthService); // Tu servicio de auth
21402
- * private firebase = inject(FirebaseService); // Este servicio
21403
- *
21404
- * async login(email: string, password: string) {
21405
- * // 1. Autenticar con tu backend
21406
- * const response = await this.authService.login(email, password);
21407
- *
21408
- * // 2. El backend devuelve un Firebase Custom Token
21409
- * if (response.firebaseToken) {
21410
- * await this.firebase.signInWithCustomToken(response.firebaseToken);
21411
- * }
21412
- *
21413
- * // Ahora el usuario puede acceder a Firestore, Storage, etc.
21414
- * }
21415
- *
21416
- * async logout() {
21417
- * await this.authService.logout();
21418
- * await this.firebase.signOut();
21419
- * }
21420
- * }
21421
- * ```
21422
- */
21423
- class FirebaseService {
21424
- constructor() {
21425
- this.auth = inject(Auth);
21426
- this.config = inject(VALTECH_FIREBASE_CONFIG);
21427
- /** Estado interno de la sesión */
21428
- this.sessionState = new BehaviorSubject({
21429
- user: null,
21430
- isAuthenticated: false,
21431
- isLoading: true,
21432
- error: null,
21433
- });
21434
- /** Estado actual de la sesión como Observable */
21435
- this.state$ = this.sessionState.asObservable();
21436
- /** Usuario actual de Firebase como Observable */
21437
- this.user$ = authState(this.auth).pipe(map((user) => (user ? this.mapUser(user) : null)), distinctUntilChanged((a, b) => a?.uid === b?.uid));
21438
- /** Indica si el usuario está autenticado en Firebase */
21439
- this.isAuthenticated$ = this.user$.pipe(map((user) => !!user), distinctUntilChanged());
21440
- // Escuchar cambios en el estado de autenticación
21441
- authState(this.auth).subscribe({
21442
- next: (user) => {
21443
- this.sessionState.next({
21444
- user: user ? this.mapUser(user) : null,
21445
- isAuthenticated: !!user,
21446
- isLoading: false,
21447
- error: null,
21448
- });
21449
- },
21450
- error: (error) => {
21451
- this.sessionState.next({
21452
- user: null,
21453
- isAuthenticated: false,
21454
- isLoading: false,
21455
- error,
21456
- });
21457
- },
21458
- });
21459
- }
21460
- // ===========================================================================
21461
- // AUTENTICACIÓN
21462
- // ===========================================================================
21463
- /**
21464
- * Autentica al usuario con un Custom Token generado por el backend.
21465
- *
21466
- * @param token - Firebase Custom Token generado por tu backend
21467
- * @returns UserCredential con la información del usuario
21468
- * @throws Error si el token es inválido o expiró
21469
- *
21470
- * @example
21471
- * ```typescript
21472
- * // Después de login exitoso con tu backend
21473
- * const { firebaseToken } = await backendAuth.login(email, password);
21474
- * await firebaseService.signInWithCustomToken(firebaseToken);
21475
- * ```
21476
- */
21477
- async signInWithCustomToken(token) {
21478
- try {
21479
- const credential = await signInWithCustomToken(this.auth, token);
21480
- return credential;
21481
- }
21482
- catch (error) {
21483
- const message = this.getErrorMessage(error);
21484
- throw new Error(message);
21485
- }
21486
- }
21487
- /**
21488
- * Cierra la sesión de Firebase.
21489
- * Llamar junto con el logout de tu sistema de autenticación principal.
21490
- *
21491
- * @example
21492
- * ```typescript
21493
- * async logout() {
21494
- * await this.backendAuth.logout(); // Tu auth
21495
- * await this.firebase.signOut(); // Firebase
21496
- * }
21497
- * ```
21498
- */
21499
- async signOut() {
21500
- try {
21501
- await signOut(this.auth);
21502
- }
21503
- catch (error) {
21504
- const message = this.getErrorMessage(error);
21505
- throw new Error(message);
21506
- }
21507
- }
21508
- // ===========================================================================
21509
- // GETTERS SÍNCRONOS
21510
- // ===========================================================================
21511
- /**
21512
- * Obtiene el usuario actual de Firebase (síncrono).
21513
- * Retorna null si no hay usuario autenticado.
21514
- */
21515
- get currentUser() {
21516
- const user = this.auth.currentUser;
21517
- return user ? this.mapUser(user) : null;
21518
- }
21519
- /**
21520
- * Obtiene el UID del usuario actual.
21521
- * Retorna null si no hay usuario autenticado.
21522
- */
21523
- get uid() {
21524
- return this.auth.currentUser?.uid ?? null;
21525
- }
21526
- /**
21527
- * Indica si hay un usuario autenticado actualmente.
21528
- */
21529
- get isAuthenticated() {
21530
- return !!this.auth.currentUser;
21531
- }
21532
- // ===========================================================================
21533
- // TOKENS
21534
- // ===========================================================================
21535
- /**
21536
- * Obtiene el ID Token de Firebase para el usuario actual.
21537
- * Útil para validar el usuario en tu backend.
21538
- *
21539
- * @param forceRefresh - Si true, fuerza la renovación del token
21540
- * @returns ID Token o null si no hay usuario
21541
- */
21542
- async getIdToken(forceRefresh = false) {
21543
- const user = this.auth.currentUser;
21544
- if (!user)
21545
- return null;
21546
- try {
21547
- return await user.getIdToken(forceRefresh);
21548
- }
21549
- catch {
21550
- return null;
21551
- }
21552
- }
21553
- /**
21554
- * Obtiene los claims personalizados del token del usuario.
21555
- * Los claims son establecidos por tu backend al crear el Custom Token.
21556
- *
21557
- * @returns Objeto con los claims o vacío si no hay usuario
21558
- */
21559
- async getClaims() {
21560
- const user = this.auth.currentUser;
21561
- if (!user)
21562
- return {};
21563
- try {
21564
- const result = await user.getIdTokenResult();
21565
- return result.claims;
21566
- }
21567
- catch {
21568
- return {};
21569
- }
21570
- }
21571
- /**
21572
- * Verifica si el usuario tiene un rol específico.
21573
- * El rol debe estar definido en los claims del Custom Token.
21574
- *
21575
- * @param role - Nombre del rol a verificar
21576
- * @returns true si el usuario tiene el rol
21577
- */
21578
- async hasRole(role) {
21579
- const claims = await this.getClaims();
21580
- return claims['role'] === role || (Array.isArray(claims['roles']) && claims['roles'].includes(role));
21581
- }
21582
- // ===========================================================================
21583
- // UTILIDADES
21584
- // ===========================================================================
21585
- /**
21586
- * Espera a que el estado de autenticación esté determinado.
21587
- * Útil en guards o al inicializar la app.
21588
- *
21589
- * @returns Usuario actual o null
21590
- */
21591
- waitForAuth() {
21592
- return new Promise((resolve) => {
21593
- const subscription = this.state$.subscribe((state) => {
21594
- if (!state.isLoading) {
21595
- subscription.unsubscribe();
21596
- resolve(state.user);
21597
- }
21598
- });
21599
- });
21600
- }
21601
- /**
21602
- * Obtiene la configuración actual de Firebase.
21603
- */
21604
- getConfig() {
21605
- return this.config;
21606
- }
21607
- /**
21608
- * Indica si los emuladores están habilitados.
21609
- */
21610
- isUsingEmulators() {
21611
- return !!(this.config.emulator?.firestore || this.config.emulator?.auth || this.config.emulator?.storage);
21612
- }
21613
- // ===========================================================================
21614
- // MÉTODOS PRIVADOS
21615
- // ===========================================================================
21616
- /**
21617
- * Mapea un User de Firebase a nuestra interface FirebaseUser
21618
- */
21619
- mapUser(user) {
21620
- return {
21621
- uid: user.uid,
21622
- email: user.email,
21623
- displayName: user.displayName,
21624
- photoURL: user.photoURL,
21625
- emailVerified: user.emailVerified,
21626
- isAnonymous: user.isAnonymous,
21627
- providerId: user.providerId,
21628
- };
21629
- }
21630
- /**
21631
- * Convierte errores de Firebase a mensajes en español
21632
- */
21633
- getErrorMessage(error) {
21634
- if (error instanceof Error) {
21635
- const code = error.code;
21636
- switch (code) {
21637
- case 'auth/invalid-custom-token':
21638
- return 'Token de autenticación inválido';
21639
- case 'auth/custom-token-mismatch':
21640
- return 'El token no corresponde a este proyecto';
21641
- case 'auth/network-request-failed':
21642
- return 'Error de conexión. Verifica tu conexión a internet';
21643
- case 'auth/too-many-requests':
21644
- return 'Demasiados intentos. Intenta de nuevo más tarde';
21645
- case 'auth/user-disabled':
21646
- return 'Esta cuenta ha sido deshabilitada';
21647
- case 'auth/user-not-found':
21648
- return 'Usuario no encontrado';
21649
- default:
21650
- return error.message || 'Error de autenticación desconocido';
21651
- }
21652
- }
21653
- return 'Error de autenticación desconocido';
21654
- }
21655
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
21656
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, providedIn: 'root' }); }
21657
- }
21658
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirebaseService, decorators: [{
21659
- type: Injectable,
21660
- args: [{ providedIn: 'root' }]
21661
- }], ctorParameters: () => [] });
21662
-
21663
- /**
21664
- * Path Builder
21665
- *
21666
- * Utilidades para construir rutas de Firestore con templates.
21667
- * Soporta rutas multi-nivel y anidadas.
21668
- */
21669
- /**
21670
- * Construye una ruta de Firestore reemplazando placeholders.
21671
- *
21672
- * @param template - Template con placeholders en formato {param}
21673
- * @param params - Objeto con los valores a reemplazar
21674
- * @returns Ruta construida
21675
- * @throws Error si faltan parámetros requeridos
21676
- *
21677
- * @example
21678
- * ```typescript
21679
- * // Ruta simple
21680
- * buildPath('users/{userId}', { userId: 'abc123' });
21681
- * // => 'users/abc123'
21682
- *
21683
- * // Ruta anidada
21684
- * buildPath('users/{userId}/documents/{docId}', {
21685
- * userId: 'abc123',
21686
- * docId: 'doc456'
21687
- * });
21688
- * // => 'users/abc123/documents/doc456'
21689
- *
21690
- * // Múltiples niveles
21691
- * buildPath('orgs/{orgId}/teams/{teamId}/members/{memberId}', {
21692
- * orgId: 'org1',
21693
- * teamId: 'team2',
21694
- * memberId: 'member3'
21695
- * });
21696
- * // => 'orgs/org1/teams/team2/members/member3'
21697
- * ```
21698
- */
21699
- function buildPath(template, params) {
21700
- let result = template;
21701
- // Encontrar todos los placeholders
21702
- const placeholders = template.match(/\{([^}]+)\}/g);
21703
- if (!placeholders) {
21704
- return template;
21705
- }
21706
- for (const placeholder of placeholders) {
21707
- const key = placeholder.slice(1, -1); // Remover { y }
21708
- const value = params[key];
21709
- if (value === undefined || value === null) {
21710
- throw new Error(`Parámetro requerido '${key}' no proporcionado para la ruta: ${template}`);
21711
- }
21712
- if (typeof value !== 'string' || value.trim() === '') {
21713
- throw new Error(`El parámetro '${key}' debe ser un string no vacío`);
21714
- }
21715
- // Validar que no contenga caracteres inválidos para Firestore
21716
- if (value.includes('/')) {
21717
- throw new Error(`El parámetro '${key}' no puede contener '/'`);
21718
- }
21719
- result = result.replace(placeholder, value);
21720
- }
21721
- return result;
21722
- }
21723
- /**
21724
- * Extrae los nombres de los parámetros de un template de ruta.
21725
- *
21726
- * @param template - Template de ruta
21727
- * @returns Array con los nombres de los parámetros
21728
- *
21729
- * @example
21730
- * ```typescript
21731
- * extractParams('users/{userId}/documents/{docId}');
21732
- * // => ['userId', 'docId']
21733
- * ```
21734
- */
21735
- function extractPathParams(template) {
21736
- const matches = template.match(/\{([^}]+)\}/g);
21737
- if (!matches)
21738
- return [];
21739
- return matches.map((m) => m.slice(1, -1));
21740
- }
21741
- /**
21742
- * Valida que una ruta de Firestore sea válida.
21743
- *
21744
- * @param path - Ruta a validar
21745
- * @returns true si la ruta es válida
21746
- *
21747
- * @example
21748
- * ```typescript
21749
- * isValidPath('users/abc123'); // true
21750
- * isValidPath('users/abc123/documents'); // true
21751
- * isValidPath('users//documents'); // false (segmento vacío)
21752
- * isValidPath(''); // false (vacío)
21753
- * ```
21754
- */
21755
- function isValidPath(path) {
21756
- if (!path || path.trim() === '')
21757
- return false;
21758
- const segments = path.split('/');
21759
- // No puede tener segmentos vacíos
21760
- if (segments.some((s) => s.trim() === ''))
21761
- return false;
21762
- // No puede empezar o terminar con /
21763
- if (path.startsWith('/') || path.endsWith('/'))
21764
- return false;
21765
- return true;
21766
- }
21767
- /**
21768
- * Obtiene la ruta de la colección padre de un documento.
21769
- *
21770
- * @param documentPath - Ruta completa del documento
21771
- * @returns Ruta de la colección padre
21772
- *
21773
- * @example
21774
- * ```typescript
21775
- * getCollectionPath('users/abc123');
21776
- * // => 'users'
21777
- *
21778
- * getCollectionPath('users/abc123/documents/doc456');
21779
- * // => 'users/abc123/documents'
21780
- * ```
21781
- */
21782
- function getCollectionPath(documentPath) {
21783
- const segments = documentPath.split('/');
21784
- if (segments.length < 2) {
21785
- throw new Error(`Ruta de documento inválida: ${documentPath}`);
21786
- }
21787
- return segments.slice(0, -1).join('/');
21788
- }
21789
- /**
21790
- * Obtiene el ID del documento de una ruta.
21791
- *
21792
- * @param documentPath - Ruta completa del documento
21793
- * @returns ID del documento
21794
- *
21795
- * @example
21796
- * ```typescript
21797
- * getDocumentId('users/abc123');
21798
- * // => 'abc123'
21799
- *
21800
- * getDocumentId('users/abc123/documents/doc456');
21801
- * // => 'doc456'
21802
- * ```
21803
- */
21804
- function getDocumentId(documentPath) {
21805
- const segments = documentPath.split('/');
21806
- if (segments.length < 2 || segments.length % 2 !== 0) {
21807
- throw new Error(`Ruta de documento inválida: ${documentPath}`);
21808
- }
21809
- return segments[segments.length - 1];
21810
- }
21811
- /**
21812
- * Verifica si una ruta apunta a un documento (número par de segmentos).
21813
- *
21814
- * @param path - Ruta a verificar
21815
- * @returns true si es una ruta de documento
21816
- *
21817
- * @example
21818
- * ```typescript
21819
- * isDocumentPath('users/abc123'); // true
21820
- * isDocumentPath('users'); // false (colección)
21821
- * isDocumentPath('users/abc123/documents'); // false (colección)
21822
- * ```
21823
- */
21824
- function isDocumentPath(path) {
21825
- const segments = path.split('/').filter((s) => s.trim() !== '');
21826
- return segments.length > 0 && segments.length % 2 === 0;
21827
- }
21828
- /**
21829
- * Verifica si una ruta apunta a una colección (número impar de segmentos).
21830
- *
21831
- * @param path - Ruta a verificar
21832
- * @returns true si es una ruta de colección
21833
- */
21834
- function isCollectionPath(path) {
21835
- const segments = path.split('/').filter((s) => s.trim() !== '');
21836
- return segments.length > 0 && segments.length % 2 !== 0;
21837
- }
21838
- /**
21839
- * Combina una ruta base con segmentos adicionales.
21840
- *
21841
- * @param basePath - Ruta base
21842
- * @param segments - Segmentos adicionales
21843
- * @returns Ruta combinada
21844
- *
21845
- * @example
21846
- * ```typescript
21847
- * joinPath('users', 'abc123', 'documents');
21848
- * // => 'users/abc123/documents'
21849
- * ```
21850
- */
21851
- function joinPath(...segments) {
21852
- return segments
21853
- .filter((s) => s && s.trim() !== '')
21854
- .map((s) => s.replace(/^\/+|\/+$/g, '')) // Remover / al inicio y final
21855
- .join('/');
21856
- }
21857
-
21858
- /**
21859
- * Firestore Service
21860
- *
21861
- * Servicio genérico para operaciones CRUD en Firestore.
21862
- * Soporta lecturas one-time, subscripciones real-time, paginación y queries complejas.
21863
- */
21864
- /**
21865
- * Servicio para operaciones CRUD en Firestore.
21866
- *
21867
- * @example
21868
- * ```typescript
21869
- * interface User extends FirestoreDocument {
21870
- * name: string;
21871
- * email: string;
21872
- * role: 'admin' | 'user';
21873
- * }
21874
- *
21875
- * @Component({...})
21876
- * export class UsersComponent {
21877
- * private firestore = inject(FirestoreService);
21878
- *
21879
- * // Lectura one-time
21880
- * async loadUser(id: string) {
21881
- * const user = await this.firestore.getDoc<User>('users', id);
21882
- * }
21883
- *
21884
- * // Subscripción real-time
21885
- * users$ = this.firestore.collectionChanges<User>('users', {
21886
- * where: [{ field: 'role', operator: '==', value: 'admin' }],
21887
- * orderBy: [{ field: 'name', direction: 'asc' }]
21888
- * });
21889
- *
21890
- * // Crear documento
21891
- * async createUser(data: Omit<User, 'id'>) {
21892
- * const user = await this.firestore.addDoc<User>('users', data);
21893
- * }
21894
- * }
21895
- * ```
21896
- */
21897
- class FirestoreService {
21898
- constructor() {
21899
- this.firestore = inject(Firestore);
21900
- }
21901
- // ===========================================================================
21902
- // LECTURAS ONE-TIME (Promise)
21903
- // ===========================================================================
21904
- /**
21905
- * Obtiene un documento por ID (lectura única).
21906
- *
21907
- * @param collectionPath - Ruta de la colección
21908
- * @param docId - ID del documento
21909
- * @returns Documento o null si no existe
21910
- *
21911
- * @example
21912
- * ```typescript
21913
- * const user = await firestoreService.getDoc<User>('users', 'abc123');
21914
- * if (user) {
21915
- * console.log(user.name);
21916
- * }
21917
- * ```
21918
- */
21919
- async getDoc(collectionPath, docId) {
21920
- const docRef = doc(this.firestore, collectionPath, docId);
21921
- const snapshot = await getDoc(docRef);
21922
- if (!snapshot.exists()) {
21923
- return null;
21924
- }
21925
- return this.mapDocument(snapshot);
21926
- }
21927
- /**
21928
- * Obtiene múltiples documentos con opciones de query.
21929
- *
21930
- * @param collectionPath - Ruta de la colección
21931
- * @param options - Opciones de query (where, orderBy, limit)
21932
- * @returns Array de documentos
21933
- *
21934
- * @example
21935
- * ```typescript
21936
- * // Todos los usuarios activos ordenados por nombre
21937
- * const users = await firestoreService.getDocs<User>('users', {
21938
- * where: [{ field: 'active', operator: '==', value: true }],
21939
- * orderBy: [{ field: 'name', direction: 'asc' }],
21940
- * limit: 50
21941
- * });
21942
- * ```
21943
- */
21944
- async getDocs(collectionPath, options) {
21945
- const collectionRef = collection(this.firestore, collectionPath);
21946
- const constraints = this.buildQueryConstraints(options);
21947
- const q = query$1(collectionRef, ...constraints);
21948
- const snapshot = await getDocs(q);
21949
- return snapshot.docs.map((doc) => this.mapDocument(doc));
21950
- }
21951
- /**
21952
- * Obtiene documentos con paginación basada en cursores.
21953
- *
21954
- * @param collectionPath - Ruta de la colección
21955
- * @param options - Opciones de query (debe incluir limit)
21956
- * @returns Resultado paginado con cursor para la siguiente página
21957
- *
21958
- * @example
21959
- * ```typescript
21960
- * // Primera página
21961
- * const page1 = await firestoreService.getPaginated<User>('users', {
21962
- * orderBy: [{ field: 'createdAt', direction: 'desc' }],
21963
- * limit: 10
21964
- * });
21965
- *
21966
- * // Siguiente página
21967
- * if (page1.hasMore) {
21968
- * const page2 = await firestoreService.getPaginated<User>('users', {
21969
- * orderBy: [{ field: 'createdAt', direction: 'desc' }],
21970
- * limit: 10,
21971
- * startAfter: page1.lastDoc
21972
- * });
21973
- * }
21974
- * ```
21975
- */
21976
- async getPaginated(collectionPath, options) {
21977
- const collectionRef = collection(this.firestore, collectionPath);
21978
- const constraints = this.buildQueryConstraints(options);
21979
- // Pedir uno más para saber si hay más páginas
21980
- const q = query$1(collectionRef, ...constraints, limit(options.limit + 1));
21981
- const snapshot = await getDocs(q);
21982
- const docs = snapshot.docs;
21983
- const hasMore = docs.length > options.limit;
21984
- // Si hay más, remover el documento extra
21985
- const resultDocs = hasMore ? docs.slice(0, -1) : docs;
21986
- const lastDoc = resultDocs.length > 0 ? resultDocs[resultDocs.length - 1] : null;
21987
- return {
21988
- data: resultDocs.map((doc) => this.mapDocument(doc)),
21989
- hasMore,
21990
- lastDoc,
21991
- };
21992
- }
21993
- /**
21994
- * Verifica si un documento existe.
21995
- *
21996
- * @param collectionPath - Ruta de la colección
21997
- * @param docId - ID del documento
21998
- * @returns true si el documento existe
21999
- */
22000
- async exists(collectionPath, docId) {
22001
- const docRef = doc(this.firestore, collectionPath, docId);
22002
- const snapshot = await getDoc(docRef);
22003
- return snapshot.exists();
22004
- }
22005
- // ===========================================================================
22006
- // SUBSCRIPCIONES REAL-TIME (Observable)
22007
- // ===========================================================================
22008
- /**
22009
- * Suscribe a cambios de un documento (real-time).
22010
- *
22011
- * @param collectionPath - Ruta de la colección
22012
- * @param docId - ID del documento
22013
- * @returns Observable que emite cuando el documento cambia
22014
- *
22015
- * @example
22016
- * ```typescript
22017
- * // En el componente
22018
- * user$ = this.firestoreService.docChanges<User>('users', this.userId);
22019
- *
22020
- * // En el template
22021
- * @if (user$ | async; as user) {
22022
- * <p>{{ user.name }}</p>
22023
- * }
22024
- * ```
22025
- */
22026
- docChanges(collectionPath, docId) {
22027
- const docRef = doc(this.firestore, collectionPath, docId);
22028
- return docData(docRef, { idField: 'id' }).pipe(map((data) => {
22029
- if (!data)
22030
- return null;
22031
- return this.convertTimestamps(data);
22032
- }));
22033
- }
22034
- /**
22035
- * Suscribe a cambios de una colección (real-time).
22036
- *
22037
- * @param collectionPath - Ruta de la colección
22038
- * @param options - Opciones de query
22039
- * @returns Observable que emite cuando la colección cambia
22040
- *
22041
- * @example
22042
- * ```typescript
22043
- * // Usuarios activos en tiempo real
22044
- * activeUsers$ = this.firestoreService.collectionChanges<User>('users', {
22045
- * where: [{ field: 'status', operator: '==', value: 'online' }]
22046
- * });
22047
- * ```
22048
- */
22049
- collectionChanges(collectionPath, options) {
22050
- const collectionRef = collection(this.firestore, collectionPath);
22051
- const constraints = this.buildQueryConstraints(options);
22052
- const q = query$1(collectionRef, ...constraints);
22053
- return collectionData(q, { idField: 'id' }).pipe(map((docs) => docs.map((doc) => this.convertTimestamps(doc))));
22054
- }
22055
- // ===========================================================================
22056
- // ESCRITURA
22057
- // ===========================================================================
22058
- /**
22059
- * Agrega un documento con ID auto-generado.
22060
- *
22061
- * @param collectionPath - Ruta de la colección
22062
- * @param data - Datos del documento (sin id, createdAt, updatedAt)
22063
- * @returns Documento creado con su ID
22064
- *
22065
- * @example
22066
- * ```typescript
22067
- * const newUser = await firestoreService.addDoc<User>('users', {
22068
- * name: 'John Doe',
22069
- * email: 'john@example.com',
22070
- * role: 'user'
22071
- * });
22072
- * console.log('Created user with ID:', newUser.id);
22073
- * ```
22074
- */
22075
- async addDoc(collectionPath, data) {
22076
- const collectionRef = collection(this.firestore, collectionPath);
22077
- const timestamp = serverTimestamp();
22078
- const docData = {
22079
- ...data,
22080
- createdAt: timestamp,
22081
- updatedAt: timestamp,
22082
- };
22083
- const docRef = await addDoc(collectionRef, docData);
22084
- // Obtener el documento creado para retornarlo con timestamps resueltos
22085
- const snapshot = await getDoc(docRef);
22086
- return this.mapDocument(snapshot);
22087
- }
22088
- /**
22089
- * Crea o sobrescribe un documento con ID específico.
22090
- *
22091
- * @param collectionPath - Ruta de la colección
22092
- * @param docId - ID del documento
22093
- * @param data - Datos del documento
22094
- * @param options - Opciones (merge: true para merge en lugar de sobrescribir)
22095
- *
22096
- * @example
22097
- * ```typescript
22098
- * // Sobrescribir completamente
22099
- * await firestoreService.setDoc<User>('users', 'user123', userData);
22100
- *
22101
- * // Merge con datos existentes
22102
- * await firestoreService.setDoc<User>('users', 'user123', { name: 'New Name' }, { merge: true });
22103
- * ```
22104
- */
22105
- async setDoc(collectionPath, docId, data, options) {
22106
- const docRef = doc(this.firestore, collectionPath, docId);
22107
- const timestamp = serverTimestamp();
22108
- const docData = {
22109
- ...data,
22110
- updatedAt: timestamp,
22111
- ...(options?.merge ? {} : { createdAt: timestamp }),
22112
- };
22113
- await setDoc(docRef, docData, { merge: options?.merge ?? false });
22114
- }
22115
- /**
22116
- * Actualiza campos específicos de un documento.
22117
- *
22118
- * @param collectionPath - Ruta de la colección
22119
- * @param docId - ID del documento
22120
- * @param data - Campos a actualizar
22121
- *
22122
- * @example
22123
- * ```typescript
22124
- * await firestoreService.updateDoc<User>('users', 'user123', {
22125
- * name: 'Updated Name',
22126
- * lastLogin: new Date()
22127
- * });
22128
- * ```
22129
- */
22130
- async updateDoc(collectionPath, docId, data) {
22131
- const docRef = doc(this.firestore, collectionPath, docId);
22132
- await updateDoc(docRef, {
22133
- ...data,
22134
- updatedAt: serverTimestamp(),
22135
- });
22136
- }
22137
- /**
22138
- * Elimina un documento.
22139
- *
22140
- * @param collectionPath - Ruta de la colección
22141
- * @param docId - ID del documento
22142
- *
22143
- * @example
22144
- * ```typescript
22145
- * await firestoreService.deleteDoc('users', 'user123');
22146
- * ```
22147
- */
22148
- async deleteDoc(collectionPath, docId) {
22149
- const docRef = doc(this.firestore, collectionPath, docId);
22150
- await deleteDoc(docRef);
22151
- }
22152
- // ===========================================================================
22153
- // OPERACIONES EN LOTE
22154
- // ===========================================================================
22155
- /**
22156
- * Ejecuta múltiples operaciones de escritura de forma atómica.
22157
- *
22158
- * @param operations - Función que recibe el batch y agrega operaciones
22159
- *
22160
- * @example
22161
- * ```typescript
22162
- * await firestoreService.batch((batch) => {
22163
- * batch.set('users/user1', { name: 'User 1' });
22164
- * batch.update('users/user2', { status: 'inactive' });
22165
- * batch.delete('users/user3');
22166
- * });
22167
- * ```
22168
- */
22169
- async batch(operations) {
22170
- const batch = writeBatch(this.firestore);
22171
- const batchApi = {
22172
- set: (path, data) => {
22173
- const [collectionPath, docId] = this.splitPath(path);
22174
- const docRef = doc(this.firestore, collectionPath, docId);
22175
- batch.set(docRef, {
22176
- ...data,
22177
- createdAt: serverTimestamp(),
22178
- updatedAt: serverTimestamp(),
22179
- });
22180
- },
22181
- update: (path, data) => {
22182
- const [collectionPath, docId] = this.splitPath(path);
22183
- const docRef = doc(this.firestore, collectionPath, docId);
22184
- batch.update(docRef, {
22185
- ...data,
22186
- updatedAt: serverTimestamp(),
22187
- });
22188
- },
22189
- delete: (path) => {
22190
- const [collectionPath, docId] = this.splitPath(path);
22191
- const docRef = doc(this.firestore, collectionPath, docId);
22192
- batch.delete(docRef);
22193
- },
22194
- };
22195
- operations(batchApi);
22196
- await batch.commit();
22197
- }
22198
- // ===========================================================================
22199
- // UTILIDADES
22200
- // ===========================================================================
22201
- /**
22202
- * Construye una ruta a partir de un template.
22203
- *
22204
- * @param template - Template con placeholders {param}
22205
- * @param params - Valores para los placeholders
22206
- * @returns Ruta construida
22207
- *
22208
- * @example
22209
- * ```typescript
22210
- * const path = firestoreService.buildPath('users/{userId}/documents/{docId}', {
22211
- * userId: 'user123',
22212
- * docId: 'doc456'
22213
- * });
22214
- * // => 'users/user123/documents/doc456'
22215
- * ```
22216
- */
22217
- buildPath(template, params) {
22218
- return buildPath(template, params);
22219
- }
22220
- /**
22221
- * Genera un ID único para un documento (sin crearlo).
22222
- *
22223
- * @param collectionPath - Ruta de la colección
22224
- * @returns ID único generado por Firestore
22225
- */
22226
- generateId(collectionPath) {
22227
- const collectionRef = collection(this.firestore, collectionPath);
22228
- return doc(collectionRef).id;
22229
- }
22230
- /**
22231
- * Retorna un valor de timestamp del servidor.
22232
- * Usar en campos de fecha para que Firestore asigne el timestamp.
22233
- */
22234
- serverTimestamp() {
22235
- return serverTimestamp();
22236
- }
22237
- /**
22238
- * Retorna un valor para agregar elementos a un array.
22239
- *
22240
- * @example
22241
- * ```typescript
22242
- * await firestoreService.updateDoc('users', 'user123', {
22243
- * tags: firestoreService.arrayUnion('new-tag')
22244
- * });
22245
- * ```
22246
- */
22247
- arrayUnion(...elements) {
22248
- return arrayUnion(...elements);
22249
- }
22250
- /**
22251
- * Retorna un valor para remover elementos de un array.
22252
- */
22253
- arrayRemove(...elements) {
22254
- return arrayRemove(...elements);
22255
- }
22256
- /**
22257
- * Retorna un valor para incrementar un campo numérico.
22258
- *
22259
- * @example
22260
- * ```typescript
22261
- * await firestoreService.updateDoc('users', 'user123', {
22262
- * loginCount: firestoreService.increment(1)
22263
- * });
22264
- * ```
22265
- */
22266
- increment(n) {
22267
- return increment(n);
22268
- }
22269
- // ===========================================================================
22270
- // MÉTODOS PRIVADOS
22271
- // ===========================================================================
22272
- /**
22273
- * Construye los QueryConstraints a partir de QueryOptions
22274
- */
22275
- buildQueryConstraints(options) {
22276
- const constraints = [];
22277
- if (!options)
22278
- return constraints;
22279
- // Where clauses
22280
- if (options.where) {
22281
- for (const clause of options.where) {
22282
- constraints.push(where(clause.field, clause.operator, clause.value));
22283
- }
22284
- }
22285
- // OrderBy clauses
22286
- if (options.orderBy) {
22287
- for (const clause of options.orderBy) {
22288
- constraints.push(orderBy(clause.field, clause.direction));
22289
- }
22290
- }
22291
- // Cursors para paginación
22292
- if (options.startAfter) {
22293
- constraints.push(startAfter(options.startAfter));
22294
- }
22295
- if (options.startAt) {
22296
- constraints.push(startAt(options.startAt));
22297
- }
22298
- if (options.endBefore) {
22299
- constraints.push(endBefore(options.endBefore));
22300
- }
22301
- if (options.endAt) {
22302
- constraints.push(endAt(options.endAt));
22303
- }
22304
- // Limit (se agrega al final)
22305
- if (options.limit) {
22306
- constraints.push(limit(options.limit));
22307
- }
22308
- return constraints;
22309
- }
22310
- /**
22311
- * Mapea un DocumentSnapshot a nuestro tipo
22312
- */
22313
- mapDocument(snapshot) {
22314
- const data = snapshot.data();
22315
- if (!data) {
22316
- throw new Error('Documento no tiene datos');
22317
- }
22318
- return {
22319
- id: snapshot.id,
22320
- ...this.convertTimestamps(data),
22321
- };
22322
- }
22323
- /**
22324
- * Convierte Timestamps de Firestore a Date de JavaScript
22325
- */
22326
- convertTimestamps(data) {
22327
- const result = {};
22328
- for (const [key, value] of Object.entries(data)) {
22329
- if (value instanceof Timestamp) {
22330
- result[key] = value.toDate();
22331
- }
22332
- else if (value && typeof value === 'object' && !Array.isArray(value)) {
22333
- result[key] = this.convertTimestamps(value);
22334
- }
22335
- else {
22336
- result[key] = value;
22337
- }
22338
- }
22339
- return result;
22340
- }
22341
- /**
22342
- * Divide una ruta de documento en colección e ID
22343
- */
22344
- splitPath(path) {
22345
- const segments = path.split('/');
22346
- if (segments.length < 2 || segments.length % 2 !== 0) {
22347
- throw new Error(`Ruta de documento inválida: ${path}`);
22348
- }
22349
- const docId = segments.pop();
22350
- const collectionPath = segments.join('/');
22351
- return [collectionPath, docId];
22352
- }
22353
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirestoreService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
22354
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirestoreService, providedIn: 'root' }); }
22355
- }
22356
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: FirestoreService, decorators: [{
22357
- type: Injectable,
22358
- args: [{ providedIn: 'root' }]
22359
- }] });
22360
-
22361
- /**
22362
- * Query Builder
22363
- *
22364
- * Builder fluido para construir queries de Firestore de manera legible.
22365
- * Alternativa más expresiva a pasar objetos QueryOptions directamente.
22366
- */
22367
- /**
22368
- * Builder fluido para queries de Firestore.
22369
- *
22370
- * @example
22371
- * ```typescript
22372
- * // Construir query con builder
22373
- * const options = new QueryBuilder()
22374
- * .where('status', '==', 'active')
22375
- * .where('age', '>=', 18)
22376
- * .orderBy('createdAt', 'desc')
22377
- * .limit(10)
22378
- * .build();
22379
- *
22380
- * // Usar con FirestoreService
22381
- * const users = await firestoreService.getDocs<User>('users', options);
22382
- *
22383
- * // O con método estático
22384
- * const options2 = QueryBuilder.create()
22385
- * .where('category', '==', 'electronics')
22386
- * .orderBy('price', 'asc')
22387
- * .build();
22388
- * ```
22389
- */
22390
- class QueryBuilder {
22391
- constructor() {
22392
- this.whereConditions = [];
22393
- this.orderByConditions = [];
22394
- }
22395
- /**
22396
- * Crea una nueva instancia del builder (método estático alternativo).
22397
- */
22398
- static create() {
22399
- return new QueryBuilder();
22400
- }
22401
- /**
22402
- * Agrega una condición where.
22403
- *
22404
- * @param field - Campo a filtrar
22405
- * @param operator - Operador de comparación
22406
- * @param value - Valor a comparar
22407
- *
22408
- * @example
22409
- * ```typescript
22410
- * builder.where('status', '==', 'active')
22411
- * builder.where('price', '>=', 100)
22412
- * builder.where('tags', 'array-contains', 'featured')
22413
- * builder.where('category', 'in', ['electronics', 'books'])
22414
- * ```
22415
- */
22416
- where(field, operator, value) {
22417
- this.whereConditions.push({ field, operator, value });
22418
- return this;
22419
- }
22420
- /**
22421
- * Shortcut para where con operador '=='.
22422
- *
22423
- * @example
22424
- * ```typescript
22425
- * builder.whereEquals('status', 'active')
22426
- * // equivalente a: builder.where('status', '==', 'active')
22427
- * ```
22428
- */
22429
- whereEquals(field, value) {
22430
- return this.where(field, '==', value);
22431
- }
22432
- /**
22433
- * Shortcut para where con operador '!='.
22434
- */
22435
- whereNotEquals(field, value) {
22436
- return this.where(field, '!=', value);
22437
- }
22438
- /**
22439
- * Shortcut para where con operador '>'.
22440
- */
22441
- whereGreaterThan(field, value) {
22442
- return this.where(field, '>', value);
22443
- }
22444
- /**
22445
- * Shortcut para where con operador '>='.
22446
- */
22447
- whereGreaterOrEqual(field, value) {
22448
- return this.where(field, '>=', value);
22449
- }
22450
- /**
22451
- * Shortcut para where con operador '<'.
22452
- */
22453
- whereLessThan(field, value) {
22454
- return this.where(field, '<', value);
22455
- }
22456
- /**
22457
- * Shortcut para where con operador '<='.
22458
- */
22459
- whereLessOrEqual(field, value) {
22460
- return this.where(field, '<=', value);
22461
- }
22462
- /**
22463
- * Shortcut para where con operador 'array-contains'.
22464
- *
22465
- * @example
22466
- * ```typescript
22467
- * builder.whereArrayContains('tags', 'featured')
22468
- * ```
22469
- */
22470
- whereArrayContains(field, value) {
22471
- return this.where(field, 'array-contains', value);
22472
- }
22473
- /**
22474
- * Shortcut para where con operador 'array-contains-any'.
22475
- *
22476
- * @example
22477
- * ```typescript
22478
- * builder.whereArrayContainsAny('tags', ['featured', 'new'])
22479
- * ```
22480
- */
22481
- whereArrayContainsAny(field, values) {
22482
- return this.where(field, 'array-contains-any', values);
22483
- }
22484
- /**
22485
- * Shortcut para where con operador 'in'.
22486
- *
22487
- * @example
22488
- * ```typescript
22489
- * builder.whereIn('status', ['active', 'pending'])
22490
- * ```
22491
- */
22492
- whereIn(field, values) {
22493
- return this.where(field, 'in', values);
22494
- }
22495
- /**
22496
- * Shortcut para where con operador 'not-in'.
22497
- */
22498
- whereNotIn(field, values) {
22499
- return this.where(field, 'not-in', values);
22500
- }
22501
- /**
22502
- * Agrega ordenamiento por un campo.
22503
- *
22504
- * @param field - Campo por el cual ordenar
22505
- * @param direction - Dirección: 'asc' o 'desc' (default: 'asc')
22506
- *
22507
- * @example
22508
- * ```typescript
22509
- * builder.orderBy('createdAt', 'desc')
22510
- * builder.orderBy('name') // asc por defecto
22511
- * ```
22512
- */
22513
- orderBy(field, direction = 'asc') {
22514
- this.orderByConditions.push({ field, direction });
22515
- return this;
22516
- }
22517
- /**
22518
- * Shortcut para orderBy descendente.
22519
- */
22520
- orderByDesc(field) {
22521
- return this.orderBy(field, 'desc');
22522
- }
22523
- /**
22524
- * Shortcut para orderBy ascendente.
22525
- */
22526
- orderByAsc(field) {
22527
- return this.orderBy(field, 'asc');
22528
- }
22529
- /**
22530
- * Limita el número de resultados.
22531
- *
22532
- * @param count - Número máximo de documentos
22533
- *
22534
- * @example
22535
- * ```typescript
22536
- * builder.limit(10)
22537
- * ```
22538
- */
22539
- limit(count) {
22540
- if (count <= 0) {
22541
- throw new Error('El límite debe ser mayor a 0');
22542
- }
22543
- this.limitValue = count;
22544
- return this;
22545
- }
22546
- /**
22547
- * Cursor para paginación: empezar después de un documento.
22548
- *
22549
- * @param cursor - Documento o snapshot desde donde continuar
22550
- *
22551
- * @example
22552
- * ```typescript
22553
- * // Primera página
22554
- * const page1 = await service.getPaginated('users', builder.limit(10).build());
22555
- *
22556
- * // Siguiente página
22557
- * const page2 = await service.getPaginated('users',
22558
- * builder.startAfter(page1.lastDoc).limit(10).build()
22559
- * );
22560
- * ```
22561
- */
22562
- startAfter(cursor) {
22563
- this.startAfterValue = cursor;
22564
- return this;
22565
- }
22566
- /**
22567
- * Cursor para paginación: empezar en un documento.
22568
- */
22569
- startAt(cursor) {
22570
- this.startAtValue = cursor;
22571
- return this;
22572
- }
22573
- /**
22574
- * Cursor para paginación: terminar antes de un documento.
22575
- */
22576
- endBefore(cursor) {
22577
- this.endBeforeValue = cursor;
22578
- return this;
22579
- }
22580
- /**
22581
- * Cursor para paginación: terminar en un documento.
22582
- */
22583
- endAt(cursor) {
22584
- this.endAtValue = cursor;
22585
- return this;
22586
- }
22587
- /**
22588
- * Construye el objeto QueryOptions.
22589
- *
22590
- * @returns QueryOptions para usar con FirestoreService
22591
- */
22592
- build() {
22593
- const options = {};
22594
- if (this.whereConditions.length > 0) {
22595
- options.where = [...this.whereConditions];
22596
- }
22597
- if (this.orderByConditions.length > 0) {
22598
- options.orderBy = [...this.orderByConditions];
22599
- }
22600
- if (this.limitValue !== undefined) {
22601
- options.limit = this.limitValue;
22602
- }
22603
- if (this.startAfterValue !== undefined) {
22604
- options.startAfter = this.startAfterValue;
22605
- }
22606
- if (this.startAtValue !== undefined) {
22607
- options.startAt = this.startAtValue;
22608
- }
22609
- if (this.endBeforeValue !== undefined) {
22610
- options.endBefore = this.endBeforeValue;
22611
- }
22612
- if (this.endAtValue !== undefined) {
22613
- options.endAt = this.endAtValue;
22614
- }
22615
- return options;
22616
- }
22617
- /**
22618
- * Resetea el builder para reutilización.
22619
- */
22620
- reset() {
22621
- this.whereConditions = [];
22622
- this.orderByConditions = [];
22623
- this.limitValue = undefined;
22624
- this.startAfterValue = undefined;
22625
- this.startAtValue = undefined;
22626
- this.endBeforeValue = undefined;
22627
- this.endAtValue = undefined;
22628
- return this;
22629
- }
22630
- /**
22631
- * Clona el builder actual.
22632
- */
22633
- clone() {
22634
- const cloned = new QueryBuilder();
22635
- cloned.whereConditions = [...this.whereConditions];
22636
- cloned.orderByConditions = [...this.orderByConditions];
22637
- cloned.limitValue = this.limitValue;
22638
- cloned.startAfterValue = this.startAfterValue;
22639
- cloned.startAtValue = this.startAtValue;
22640
- cloned.endBeforeValue = this.endBeforeValue;
22641
- cloned.endAtValue = this.endAtValue;
22642
- return cloned;
22643
- }
22644
- }
22645
- /**
22646
- * Función helper para crear un QueryBuilder.
22647
- *
22648
- * @example
22649
- * ```typescript
22650
- * import { query } from 'valtech-components';
22651
- *
22652
- * const options = query()
22653
- * .where('status', '==', 'active')
22654
- * .orderBy('createdAt', 'desc')
22655
- * .limit(10)
22656
- * .build();
22657
- * ```
22658
- */
22659
- function query() {
22660
- return new QueryBuilder();
22661
- }
22662
-
22663
- /**
22664
- * Storage Service
22665
- *
22666
- * Servicio para operaciones de Firebase Storage.
22667
- * Soporta upload con tracking de progreso, download y gestión de archivos.
22668
- */
22669
- /**
22670
- * Servicio para Firebase Storage.
22671
- *
22672
- * @example
22673
- * ```typescript
22674
- * @Component({...})
22675
- * export class FileUploadComponent {
22676
- * private storage = inject(StorageService);
22677
- *
22678
- * uploadProgress = signal<number>(0);
22679
- * downloadUrl = signal<string | null>(null);
22680
- *
22681
- * async onFileSelected(event: Event) {
22682
- * const file = (event.target as HTMLInputElement).files?.[0];
22683
- * if (!file) return;
22684
- *
22685
- * // Upload con progreso
22686
- * this.storage.upload(`uploads/${file.name}`, file).subscribe({
22687
- * next: (progress) => this.uploadProgress.set(progress.percentage),
22688
- * complete: async () => {
22689
- * const url = await this.storage.getDownloadUrl(`uploads/${file.name}`);
22690
- * this.downloadUrl.set(url);
22691
- * }
22692
- * });
22693
- * }
22694
- * }
22695
- * ```
22696
- */
22697
- class StorageService {
22698
- constructor() {
22699
- this.storage = inject(Storage);
22700
- }
22701
- // ===========================================================================
22702
- // UPLOAD
22703
- // ===========================================================================
22704
- /**
22705
- * Sube un archivo con tracking de progreso.
22706
- *
22707
- * @param path - Ruta en Storage donde guardar el archivo
22708
- * @param file - Archivo a subir (File o Blob)
22709
- * @param metadata - Metadata opcional (contentType, customMetadata)
22710
- * @returns Observable que emite el progreso y completa cuando termina
22711
- *
22712
- * @example
22713
- * ```typescript
22714
- * // Upload básico
22715
- * storage.upload('images/photo.jpg', file).subscribe({
22716
- * next: (progress) => console.log(`${progress.percentage}%`),
22717
- * complete: () => console.log('Upload completado')
22718
- * });
22719
- *
22720
- * // Con metadata
22721
- * storage.upload('docs/report.pdf', file, {
22722
- * contentType: 'application/pdf',
22723
- * customMetadata: { uploadedBy: 'user123' }
22724
- * }).subscribe(...);
22725
- * ```
22726
- */
22727
- upload(path, file, metadata) {
22728
- const storageRef = ref(this.storage, path);
22729
- const uploadMetadata = {
22730
- contentType: metadata?.contentType || (file instanceof File ? file.type : undefined),
22731
- customMetadata: metadata?.customMetadata,
22732
- cacheControl: metadata?.cacheControl,
22733
- };
22734
- const task = uploadBytesResumable(storageRef, file, uploadMetadata);
22735
- const progress$ = new BehaviorSubject({
22736
- bytesTransferred: 0,
22737
- totalBytes: file.size,
22738
- percentage: 0,
22739
- state: 'running',
22740
- });
22741
- task.on('state_changed', (snapshot) => {
22742
- progress$.next({
22743
- bytesTransferred: snapshot.bytesTransferred,
22744
- totalBytes: snapshot.totalBytes,
22745
- percentage: Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100),
22746
- state: this.mapTaskState(snapshot.state),
22747
- });
22748
- }, (error) => {
22749
- progress$.next({
22750
- bytesTransferred: 0,
22751
- totalBytes: file.size,
22752
- percentage: 0,
22753
- state: 'error',
22754
- });
22755
- progress$.error(this.getErrorMessage(error));
22756
- }, () => {
22757
- progress$.next({
22758
- bytesTransferred: file.size,
22759
- totalBytes: file.size,
22760
- percentage: 100,
22761
- state: 'success',
22762
- });
22763
- progress$.complete();
22764
- });
22765
- return progress$.asObservable();
22766
- }
22767
- /**
22768
- * Sube un archivo y retorna la URL de descarga al completar.
22769
- *
22770
- * @param path - Ruta en Storage
22771
- * @param file - Archivo a subir
22772
- * @param metadata - Metadata opcional
22773
- * @returns Resultado del upload con URL de descarga
22774
- *
22775
- * @example
22776
- * ```typescript
22777
- * const result = await storage.uploadAndGetUrl('avatars/user123.jpg', file);
22778
- * console.log('URL:', result.downloadUrl);
22779
- * ```
22780
- */
22781
- async uploadAndGetUrl(path, file, metadata) {
22782
- return new Promise((resolve, reject) => {
22783
- this.upload(path, file, metadata).subscribe({
22784
- complete: async () => {
22785
- try {
22786
- const storageRef = ref(this.storage, path);
22787
- const downloadUrl = await getDownloadURL(storageRef);
22788
- const storedMetadata = await getMetadata(storageRef);
22789
- resolve({
22790
- downloadUrl,
22791
- fullPath: storedMetadata.fullPath,
22792
- name: storedMetadata.name,
22793
- size: storedMetadata.size,
22794
- contentType: storedMetadata.contentType || 'application/octet-stream',
22795
- metadata: storedMetadata.customMetadata || {},
22796
- });
22797
- }
22798
- catch (error) {
22799
- reject(this.getErrorMessage(error));
22800
- }
22801
- },
22802
- error: (error) => reject(error),
22803
- });
22804
- });
22805
- }
22806
- /**
22807
- * Sube un archivo desde una Data URL (base64).
22808
- *
22809
- * @param path - Ruta en Storage
22810
- * @param dataUrl - Data URL (ej: 'data:image/png;base64,...')
22811
- * @param metadata - Metadata opcional
22812
- * @returns Resultado del upload
22813
- *
22814
- * @example
22815
- * ```typescript
22816
- * // Desde canvas
22817
- * const dataUrl = canvas.toDataURL('image/png');
22818
- * const result = await storage.uploadFromDataUrl('images/drawing.png', dataUrl);
22819
- * ```
22820
- */
22821
- async uploadFromDataUrl(path, dataUrl, metadata) {
22822
- // Extraer content type y datos base64
22823
- const matches = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
22824
- if (!matches) {
22825
- throw new Error('Data URL inválida');
22826
- }
22827
- const contentType = matches[1];
22828
- const base64Data = matches[2];
22829
- // Convertir base64 a Blob
22830
- const byteCharacters = atob(base64Data);
22831
- const byteNumbers = new Array(byteCharacters.length);
22832
- for (let i = 0; i < byteCharacters.length; i++) {
22833
- byteNumbers[i] = byteCharacters.charCodeAt(i);
22834
- }
22835
- const byteArray = new Uint8Array(byteNumbers);
22836
- const blob = new Blob([byteArray], { type: contentType });
22837
- return this.uploadAndGetUrl(path, blob, {
22838
- contentType,
22839
- ...metadata,
22840
- });
22841
- }
22842
- // ===========================================================================
22843
- // DOWNLOAD
22844
- // ===========================================================================
22845
- /**
22846
- * Obtiene la URL de descarga de un archivo.
22847
- *
22848
- * @param path - Ruta del archivo en Storage
22849
- * @returns URL de descarga
22850
- *
22851
- * @example
22852
- * ```typescript
22853
- * const url = await storage.getDownloadUrl('images/photo.jpg');
22854
- * // Usar en <img [src]="url">
22855
- * ```
22856
- */
22857
- async getDownloadUrl(path) {
22858
- try {
22859
- const storageRef = ref(this.storage, path);
22860
- return await getDownloadURL(storageRef);
22861
- }
22862
- catch (error) {
22863
- throw new Error(this.getErrorMessage(error));
22864
- }
22865
- }
22866
- /**
22867
- * Obtiene la metadata de un archivo.
22868
- *
22869
- * @param path - Ruta del archivo
22870
- * @returns Metadata del archivo
22871
- */
22872
- async getMetadata(path) {
22873
- try {
22874
- const storageRef = ref(this.storage, path);
22875
- const metadata = await getMetadata(storageRef);
22876
- return {
22877
- contentType: metadata.contentType,
22878
- customMetadata: metadata.customMetadata,
22879
- cacheControl: metadata.cacheControl,
22880
- size: metadata.size,
22881
- name: metadata.name,
22882
- };
22883
- }
22884
- catch (error) {
22885
- throw new Error(this.getErrorMessage(error));
22886
- }
22887
- }
22888
- // ===========================================================================
22889
- // DELETE
22890
- // ===========================================================================
22891
- /**
22892
- * Elimina un archivo.
22893
- *
22894
- * @param path - Ruta del archivo a eliminar
22895
- *
22896
- * @example
22897
- * ```typescript
22898
- * await storage.delete('images/old-photo.jpg');
22899
- * ```
22900
- */
22901
- async delete(path) {
22902
- try {
22903
- const storageRef = ref(this.storage, path);
22904
- await deleteObject(storageRef);
22905
- }
22906
- catch (error) {
22907
- throw new Error(this.getErrorMessage(error));
22908
- }
22909
- }
22910
- /**
22911
- * Elimina múltiples archivos.
22912
- *
22913
- * @param paths - Array de rutas a eliminar
22914
- *
22915
- * @example
22916
- * ```typescript
22917
- * await storage.deleteMultiple([
22918
- * 'images/photo1.jpg',
22919
- * 'images/photo2.jpg'
22920
- * ]);
22921
- * ```
22922
- */
22923
- async deleteMultiple(paths) {
22924
- await Promise.all(paths.map((path) => this.delete(path)));
22925
- }
22926
- // ===========================================================================
22927
- // LIST
22928
- // ===========================================================================
22929
- /**
22930
- * Lista archivos en un directorio.
22931
- *
22932
- * @param path - Ruta del directorio
22933
- * @returns Lista de rutas de archivos
22934
- *
22935
- * @example
22936
- * ```typescript
22937
- * const result = await storage.list('images/');
22938
- * console.log(result.items); // ['images/photo1.jpg', 'images/photo2.jpg']
22939
- * ```
22940
- */
22941
- async list(path) {
22942
- try {
22943
- const storageRef = ref(this.storage, path);
22944
- const result = await listAll(storageRef);
22945
- return {
22946
- items: result.items.map((item) => item.fullPath),
22947
- nextPageToken: undefined, // listAll no soporta paginación
22948
- };
22949
- }
22950
- catch (error) {
22951
- throw new Error(this.getErrorMessage(error));
22952
- }
22953
- }
22954
- // ===========================================================================
22955
- // UTILIDADES
22956
- // ===========================================================================
22957
- /**
22958
- * Genera un nombre de archivo único con timestamp.
22959
- *
22960
- * @param originalName - Nombre original del archivo
22961
- * @param prefix - Prefijo opcional
22962
- * @returns Nombre único
22963
- *
22964
- * @example
22965
- * ```typescript
22966
- * const uniqueName = storage.generateFileName('photo.jpg', 'user123');
22967
- * // => 'user123_1703091234567_photo.jpg'
22968
- * ```
22969
- */
22970
- generateFileName(originalName, prefix) {
22971
- const timestamp = Date.now();
22972
- const sanitizedName = originalName.replace(/[^a-zA-Z0-9.-]/g, '_');
22973
- if (prefix) {
22974
- return `${prefix}_${timestamp}_${sanitizedName}`;
22975
- }
22976
- return `${timestamp}_${sanitizedName}`;
22977
- }
22978
- /**
22979
- * Genera una ruta única para un archivo.
22980
- *
22981
- * @param directory - Directorio base
22982
- * @param originalName - Nombre original
22983
- * @param prefix - Prefijo opcional
22984
- * @returns Ruta completa única
22985
- *
22986
- * @example
22987
- * ```typescript
22988
- * const path = storage.generatePath('uploads', 'photo.jpg', 'user123');
22989
- * // => 'uploads/user123_1703091234567_photo.jpg'
22990
- * ```
22991
- */
22992
- generatePath(directory, originalName, prefix) {
22993
- const fileName = this.generateFileName(originalName, prefix);
22994
- const cleanDir = directory.replace(/\/+$/, ''); // Remover / final
22995
- return `${cleanDir}/${fileName}`;
22996
- }
22997
- /**
22998
- * Obtiene la extensión de un archivo.
22999
- *
23000
- * @param filename - Nombre del archivo
23001
- * @returns Extensión (sin el punto)
23002
- */
23003
- getExtension(filename) {
23004
- const parts = filename.split('.');
23005
- return parts.length > 1 ? parts.pop().toLowerCase() : '';
23006
- }
23007
- /**
23008
- * Verifica si un archivo es una imagen basándose en su extensión.
23009
- */
23010
- isImage(filename) {
23011
- const ext = this.getExtension(filename);
23012
- return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp'].includes(ext);
23013
- }
23014
- /**
23015
- * Verifica si un archivo es un documento.
23016
- */
23017
- isDocument(filename) {
23018
- const ext = this.getExtension(filename);
23019
- return ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt'].includes(ext);
23020
- }
23021
- // ===========================================================================
23022
- // MÉTODOS PRIVADOS
23023
- // ===========================================================================
23024
- /**
23025
- * Mapea el estado de la tarea de upload
23026
- */
23027
- mapTaskState(state) {
23028
- switch (state) {
23029
- case 'running':
23030
- return 'running';
23031
- case 'paused':
23032
- return 'paused';
23033
- case 'success':
23034
- return 'success';
23035
- case 'canceled':
23036
- return 'canceled';
23037
- case 'error':
23038
- return 'error';
23039
- default:
23040
- return 'running';
23041
- }
23042
- }
23043
- /**
23044
- * Convierte errores de Storage a mensajes en español
23045
- */
23046
- getErrorMessage(error) {
23047
- if (error instanceof Error) {
23048
- const code = error.code;
23049
- switch (code) {
23050
- case 'storage/object-not-found':
23051
- return 'El archivo no existe';
23052
- case 'storage/unauthorized':
23053
- return 'No tienes permiso para acceder a este archivo';
23054
- case 'storage/canceled':
23055
- return 'La operación fue cancelada';
23056
- case 'storage/quota-exceeded':
23057
- return 'Se ha excedido la cuota de almacenamiento';
23058
- case 'storage/invalid-checksum':
23059
- return 'El archivo está corrupto';
23060
- case 'storage/retry-limit-exceeded':
23061
- return 'Error de conexión. Intenta de nuevo';
23062
- case 'storage/invalid-url':
23063
- return 'URL de archivo inválida';
23064
- case 'storage/invalid-argument':
23065
- return 'Argumento inválido';
23066
- default:
23067
- return error.message || 'Error de almacenamiento desconocido';
23068
- }
23069
- }
23070
- return 'Error de almacenamiento desconocido';
23071
- }
23072
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
23073
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StorageService, providedIn: 'root' }); }
23074
- }
23075
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StorageService, decorators: [{
23076
- type: Injectable,
23077
- args: [{ providedIn: 'root' }]
23078
- }] });
23079
-
23080
- /**
23081
- * Messaging Service (FCM)
23082
- *
23083
- * Servicio para Firebase Cloud Messaging (Push Notifications).
23084
- * Permite solicitar permisos, obtener tokens, escuchar mensajes y manejar
23085
- * navegación (deep linking) cuando el usuario toca una notificación.
23086
- */
23087
- /**
23088
- * Servicio para Firebase Cloud Messaging (FCM).
23089
- *
23090
- * Permite recibir notificaciones push en la aplicación web.
23091
- * Requiere VAPID key configurada en ValtechFirebaseConfig.
23092
- *
23093
- * @example
23094
- * ```typescript
23095
- * @Component({...})
23096
- * export class NotificationComponent {
23097
- * private messaging = inject(MessagingService);
23098
- *
23099
- * token = signal<string | null>(null);
23100
- *
23101
- * async enableNotifications() {
23102
- * // Solicitar permiso y obtener token
23103
- * const token = await this.messaging.requestPermission();
23104
- *
23105
- * if (token) {
23106
- * this.token.set(token);
23107
- * // Enviar token a tu backend para almacenarlo
23108
- * await this.backend.registerDeviceToken(token);
23109
- * }
23110
- * }
23111
- *
23112
- * // Escuchar mensajes en foreground
23113
- * messages$ = this.messaging.onMessage();
23114
- * }
23115
- * ```
23116
- */
23117
- class MessagingService {
23118
- constructor() {
23119
- this.messaging = inject(Messaging, { optional: true });
23120
- this.config = inject(VALTECH_FIREBASE_CONFIG);
23121
- this.platformId = inject(PLATFORM_ID);
23122
- this.ngZone = inject(NgZone);
23123
- this.messageSubject = new Subject();
23124
- this.notificationClickSubject = new Subject();
23125
- this.stateSubject = new BehaviorSubject({
23126
- token: null,
23127
- permission: 'default',
23128
- isSupported: false,
23129
- });
23130
- this.initializeMessaging();
23131
- }
23132
- // ===========================================================================
23133
- // INICIALIZACIÓN
23134
- // ===========================================================================
23135
- /**
23136
- * Inicializa el servicio de messaging
23137
- */
23138
- async initializeMessaging() {
23139
- if (!isPlatformBrowser(this.platformId)) {
23140
- return;
23141
- }
23142
- const supported = await this.checkSupport();
23143
- const permission = this.getPermissionState();
23144
- this.stateSubject.next({
23145
- ...this.stateSubject.value,
23146
- isSupported: supported,
23147
- permission,
23148
- });
23149
- // Si ya tiene permiso, configurar listeners
23150
- if (supported && permission === 'granted') {
23151
- this.setupMessageListener();
23152
- }
23153
- // Escuchar mensajes del Service Worker (clicks en notificaciones background)
23154
- this.setupServiceWorkerListener();
23155
- }
23156
- /**
23157
- * Configura listener para mensajes del Service Worker.
23158
- * Recibe eventos cuando el usuario hace click en una notificación background.
23159
- */
23160
- setupServiceWorkerListener() {
23161
- if (!isPlatformBrowser(this.platformId) || !('serviceWorker' in navigator)) {
23162
- return;
23163
- }
23164
- navigator.serviceWorker.addEventListener('message', (event) => {
23165
- // Verificar que es un mensaje de notificación click
23166
- if (event.data?.type === 'NOTIFICATION_CLICK') {
23167
- this.ngZone.run(() => {
23168
- const notification = event.data.notification;
23169
- const action = this.extractActionFromData(notification.data);
23170
- this.notificationClickSubject.next({
23171
- notification,
23172
- action,
23173
- timestamp: new Date(),
23174
- });
23175
- });
23176
- }
23177
- });
23178
- }
23179
- /**
23180
- * Verifica si FCM está soportado en el navegador actual
23181
- */
23182
- async checkSupport() {
23183
- if (!isPlatformBrowser(this.platformId)) {
23184
- return false;
23185
- }
23186
- // Verificar APIs necesarias
23187
- if (!('Notification' in window)) {
23188
- return false;
23189
- }
23190
- if (!('serviceWorker' in navigator)) {
23191
- return false;
23192
- }
23193
- // Verificar que messaging esté disponible
23194
- if (!this.messaging) {
23195
- return false;
23196
- }
23197
- return true;
23198
- }
23199
- // ===========================================================================
23200
- // PERMISOS Y TOKEN
23201
- // ===========================================================================
23202
- /**
23203
- * Solicita permiso de notificaciones y obtiene el token FCM.
23204
- *
23205
- * @returns Token FCM si se otorgó permiso, null si se denegó
23206
- *
23207
- * @example
23208
- * ```typescript
23209
- * const token = await messaging.requestPermission();
23210
- * if (token) {
23211
- * console.log('Token FCM:', token);
23212
- * // Enviar a backend
23213
- * } else {
23214
- * console.log('Permiso denegado o no soportado');
23215
- * }
23216
- * ```
23217
- */
23218
- async requestPermission() {
23219
- if (!await this.isSupported()) {
23220
- console.warn('FCM no está soportado en este navegador');
23221
- return null;
23222
- }
23223
- try {
23224
- // Solicitar permiso de notificaciones
23225
- const permission = await Notification.requestPermission();
23226
- this.stateSubject.next({
23227
- ...this.stateSubject.value,
23228
- permission: permission,
23229
- });
23230
- if (permission !== 'granted') {
23231
- console.warn('Permiso de notificaciones denegado');
23232
- return null;
23233
- }
23234
- // Obtener token FCM
23235
- const token = await this.getToken();
23236
- if (token) {
23237
- // Configurar listener de mensajes
23238
- this.setupMessageListener();
23239
- }
23240
- return token;
23241
- }
23242
- catch (error) {
23243
- console.error('Error solicitando permiso de notificaciones:', error);
23244
- return null;
23245
- }
23246
- }
23247
- /**
23248
- * Obtiene el token FCM actual (sin solicitar permiso).
23249
- *
23250
- * @returns Token FCM si está disponible, null si no
23251
- *
23252
- * @example
23253
- * ```typescript
23254
- * const token = await messaging.getToken();
23255
- * ```
23256
- */
23257
- async getToken() {
23258
- if (!this.messaging) {
23259
- return null;
23260
- }
23261
- const vapidKey = this.config.messagingVapidKey;
23262
- if (!vapidKey) {
23263
- console.warn('VAPID key no configurada. FCM no funcionará.');
23264
- return null;
23265
- }
23266
- try {
23267
- const token = await getToken(this.messaging, { vapidKey });
23268
- this.stateSubject.next({
23269
- ...this.stateSubject.value,
23270
- token,
23271
- });
23272
- return token;
23273
- }
23274
- catch (error) {
23275
- console.error('Error obteniendo token FCM:', error);
23276
- return null;
23277
- }
23278
- }
23279
- /**
23280
- * Elimina el token FCM actual (unsubscribe de notificaciones).
23281
- *
23282
- * @example
23283
- * ```typescript
23284
- * await messaging.deleteToken();
23285
- * console.log('Token eliminado, no recibirá más notificaciones');
23286
- * ```
23287
- */
23288
- async deleteToken() {
23289
- if (!this.messaging) {
23290
- return;
23291
- }
23292
- try {
23293
- await deleteToken(this.messaging);
23294
- this.stateSubject.next({
23295
- ...this.stateSubject.value,
23296
- token: null,
23297
- });
23298
- // Limpiar listener de mensajes
23299
- if (this.unsubscribeOnMessage) {
23300
- this.unsubscribeOnMessage();
23301
- this.unsubscribeOnMessage = undefined;
23302
- }
23303
- }
23304
- catch (error) {
23305
- console.error('Error eliminando token FCM:', error);
23306
- throw new Error('No se pudo eliminar el token de notificaciones');
23307
- }
23308
- }
23309
- // ===========================================================================
23310
- // MENSAJES
23311
- // ===========================================================================
23312
- /**
23313
- * Observable de mensajes recibidos en foreground.
23314
- *
23315
- * IMPORTANTE: Los mensajes en background son manejados por el Service Worker.
23316
- *
23317
- * @returns Observable que emite cuando llega un mensaje en foreground
23318
- *
23319
- * @example
23320
- * ```typescript
23321
- * messaging.onMessage().subscribe(payload => {
23322
- * console.log('Mensaje recibido:', payload);
23323
- * // Mostrar notificación custom o actualizar UI
23324
- * });
23325
- * ```
23326
- */
23327
- onMessage() {
23328
- return this.messageSubject.asObservable();
23329
- }
23330
- /**
23331
- * Configura el listener de mensajes en foreground
23332
- */
23333
- setupMessageListener() {
23334
- if (!this.messaging || this.unsubscribeOnMessage) {
23335
- return;
23336
- }
23337
- this.unsubscribeOnMessage = onMessage(this.messaging, (payload) => {
23338
- const notification = {
23339
- title: payload.notification?.title,
23340
- body: payload.notification?.body,
23341
- image: payload.notification?.image,
23342
- data: payload.data,
23343
- messageId: payload.messageId,
23344
- };
23345
- this.messageSubject.next(notification);
23346
- });
23347
- }
23348
- // ===========================================================================
23349
- // ESTADO Y UTILIDADES
23350
- // ===========================================================================
23351
- /**
23352
- * Obtiene el estado actual del permiso de notificaciones.
23353
- *
23354
- * @returns 'granted' | 'denied' | 'default'
23355
- *
23356
- * @example
23357
- * ```typescript
23358
- * const permission = messaging.getPermissionState();
23359
- * if (permission === 'granted') {
23360
- * // Ya tiene permiso
23361
- * } else if (permission === 'default') {
23362
- * // Puede solicitar permiso
23363
- * } else {
23364
- * // Denegado, debe habilitar manualmente
23365
- * }
23366
- * ```
23367
- */
23368
- getPermissionState() {
23369
- if (!isPlatformBrowser(this.platformId)) {
23370
- return 'default';
23371
- }
23372
- if (!('Notification' in window)) {
23373
- return 'denied';
23374
- }
23375
- return Notification.permission;
23376
- }
23377
- /**
23378
- * Verifica si FCM está soportado en el navegador actual.
23379
- *
23380
- * @returns true si FCM está soportado
23381
- *
23382
- * @example
23383
- * ```typescript
23384
- * if (await messaging.isSupported()) {
23385
- * // Puede usar notificaciones push
23386
- * } else {
23387
- * // Navegador no soporta o no tiene Service Worker
23388
- * }
23389
- * ```
23390
- */
23391
- async isSupported() {
23392
- return this.checkSupport();
23393
- }
23394
- /**
23395
- * Obtiene el token actual sin hacer request.
23396
- *
23397
- * @returns Token almacenado o null
23398
- */
23399
- get currentToken() {
23400
- return this.stateSubject.value.token;
23401
- }
23402
- /**
23403
- * Observable del estado completo del servicio de messaging.
23404
- */
23405
- get state$() {
23406
- return this.stateSubject.asObservable();
23407
- }
23408
- /**
23409
- * Verifica si el usuario ya otorgó permiso de notificaciones.
23410
- */
23411
- get hasPermission() {
23412
- return this.stateSubject.value.permission === 'granted';
23413
- }
23414
- // ===========================================================================
23415
- // DEEP LINKING / NAVEGACIÓN
23416
- // ===========================================================================
23417
- /**
23418
- * Observable de clicks en notificaciones.
23419
- *
23420
- * Emite cuando el usuario hace click en una notificación (foreground o background).
23421
- * Usa este observable para navegar a la página correspondiente.
23422
- *
23423
- * @returns Observable que emite NotificationClickEvent
23424
- *
23425
- * @example
23426
- * ```typescript
23427
- * @Component({...})
23428
- * export class AppComponent {
23429
- * private messaging = inject(MessagingService);
23430
- * private router = inject(Router);
23431
- *
23432
- * constructor() {
23433
- * this.messaging.onNotificationClick().subscribe(event => {
23434
- * if (event.action.route) {
23435
- * this.router.navigate([event.action.route], {
23436
- * queryParams: event.action.queryParams
23437
- * });
23438
- * }
23439
- * });
23440
- * }
23441
- * }
23442
- * ```
23443
- */
23444
- onNotificationClick() {
23445
- return this.notificationClickSubject.asObservable();
23446
- }
23447
- /**
23448
- * Extrae la acción de navegación de los datos de una notificación.
23449
- *
23450
- * Busca campos específicos en el payload de datos:
23451
- * - `route`: Ruta interna de la app (ej: '/orders/123')
23452
- * - `url`: URL externa (ej: 'https://example.com')
23453
- * - `action_type`: Tipo de acción personalizada
23454
- * - Campos con prefijo `action_`: Datos adicionales
23455
- *
23456
- * @param data - Datos del payload de la notificación
23457
- * @returns Acción de navegación extraída
23458
- *
23459
- * @example
23460
- * ```typescript
23461
- * // Payload desde el backend:
23462
- * // { route: '/orders/123', action_type: 'view_order', action_orderId: '123' }
23463
- *
23464
- * const action = messaging.extractActionFromData(notification.data);
23465
- * // { route: '/orders/123', actionType: 'view_order', actionData: { orderId: '123' } }
23466
- * ```
23467
- */
23468
- extractActionFromData(data) {
23469
- if (!data) {
23470
- return {};
23471
- }
23472
- const action = {};
23473
- // Ruta interna
23474
- if (data['route']) {
23475
- action.route = data['route'];
23476
- }
23477
- // URL externa
23478
- if (data['url']) {
23479
- action.url = data['url'];
23480
- }
23481
- // Tipo de acción
23482
- if (data['action_type']) {
23483
- action.actionType = data['action_type'];
23484
- }
23485
- // Query params (puede venir como JSON string)
23486
- if (data['query_params']) {
23487
- try {
23488
- action.queryParams = JSON.parse(data['query_params']);
23489
- }
23490
- catch {
23491
- // Si no es JSON válido, intentar parsear como key=value
23492
- action.queryParams = this.parseQueryString(data['query_params']);
23493
- }
23494
- }
23495
- // Datos adicionales con prefijo action_
23496
- const actionData = {};
23497
- for (const [key, value] of Object.entries(data)) {
23498
- if (key.startsWith('action_') && key !== 'action_type') {
23499
- const cleanKey = key.replace('action_', '');
23500
- // Intentar parsear JSON si es posible
23501
- try {
23502
- actionData[cleanKey] = JSON.parse(value);
23503
- }
23504
- catch {
23505
- actionData[cleanKey] = value;
23506
- }
23507
- }
23508
- }
23509
- if (Object.keys(actionData).length > 0) {
23510
- action.actionData = actionData;
23511
- }
23512
- return action;
23513
- }
23514
- /**
23515
- * Emite manualmente un evento de click en notificación.
23516
- *
23517
- * Útil para manejar clicks en notificaciones foreground donde
23518
- * la app decide mostrar un banner custom.
23519
- *
23520
- * @param notification - Payload de la notificación
23521
- *
23522
- * @example
23523
- * ```typescript
23524
- * messaging.onMessage().subscribe(notification => {
23525
- * // Mostrar banner custom
23526
- * this.showBanner(notification, () => {
23527
- * // Usuario hizo click en el banner
23528
- * messaging.handleNotificationClick(notification);
23529
- * });
23530
- * });
23531
- * ```
23532
- */
23533
- handleNotificationClick(notification) {
23534
- const action = this.extractActionFromData(notification.data);
23535
- this.notificationClickSubject.next({
23536
- notification,
23537
- action,
23538
- timestamp: new Date(),
23539
- });
23540
- }
23541
- /**
23542
- * Verifica si una notificación tiene acción de navegación.
23543
- *
23544
- * @param data - Datos del payload
23545
- * @returns true si tiene route o url
23546
- */
23547
- hasNavigationAction(data) {
23548
- if (!data)
23549
- return false;
23550
- return !!(data['route'] || data['url']);
23551
- }
23552
- /**
23553
- * Parsea un query string en un objeto.
23554
- */
23555
- parseQueryString(queryString) {
23556
- const params = {};
23557
- if (!queryString)
23558
- return params;
23559
- // Remover ? inicial si existe
23560
- const cleanQuery = queryString.startsWith('?') ? queryString.slice(1) : queryString;
23561
- for (const pair of cleanQuery.split('&')) {
23562
- const [key, value] = pair.split('=');
23563
- if (key) {
23564
- params[decodeURIComponent(key)] = decodeURIComponent(value || '');
23565
- }
23566
- }
23567
- return params;
23568
- }
23569
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessagingService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
23570
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessagingService, providedIn: 'root' }); }
23571
- }
23572
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: MessagingService, decorators: [{
23573
- type: Injectable,
23574
- args: [{ providedIn: 'root' }]
23575
- }], ctorParameters: () => [] });
23576
-
23577
- /**
23578
- * Firebase Services
23579
- *
23580
- * Servicios reutilizables para integración con Firebase.
23581
- *
23582
- * @example
23583
- * ```typescript
23584
- * // En main.ts
23585
- * import { provideValtechFirebase } from 'valtech-components';
23586
- *
23587
- * bootstrapApplication(AppComponent, {
23588
- * providers: [
23589
- * provideValtechFirebase({
23590
- * firebase: environment.firebase,
23591
- * persistence: true,
23592
- * }),
23593
- * ],
23594
- * });
23595
- *
23596
- * // En componentes
23597
- * import { FirebaseService, FirestoreService } from 'valtech-components';
23598
- *
23599
- * @Component({...})
23600
- * export class MyComponent {
23601
- * private firebase = inject(FirebaseService);
23602
- * private firestore = inject(FirestoreService);
23603
- * }
23604
- * ```
23605
- */
23606
- // Tipos
23607
-
23608
21131
  /*
23609
21132
  * Public API Surface of valtech-components
23610
21133
  */
@@ -23613,5 +21136,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
23613
21136
  * Generated bundle index. Do not edit.
23614
21137
  */
23615
21138
 
23616
- export { APP_IDS, ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, ArticleBuilder, ArticleComponent, 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_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, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FIREBASE_PROJECTS, FabComponent, FileInputComponent, FirebaseService, FirestoreService, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, MessagingService, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, 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, SHARED_EMULATOR_CONFIG, 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, 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, ToolbarActionType, ToolbarComponent, VALTECH_FIREBASE_CONFIG, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, buildPath, collections, createFirebaseConfig, createGlowCardProps, createNumberFromToField, createTitleProps, extractPathParams, getCollectionPath, getDocumentId, goToTop, hasEmulators, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, provideValtechFirebase, query, replaceSpecialChars, resolveColor, resolveInputDefaultValue, storagePaths };
21139
+ export { ARTICLE_SPACING, AccordionComponent, ActionHeaderComponent, ActionType, AlertBoxComponent, ArticleBuilder, ArticleComponent, 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_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, DisplayComponent, DividerComponent, DownloadService, EmailInputComponent, ExpandableTextComponent, FabComponent, FileInputComponent, FooterComponent, FooterLinksComponent, FormComponent, FormFooterComponent, FunHeaderComponent, GlowCardComponent, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, Icon, IconComponent, IconService, ImageComponent, InAppBrowserService, InfoComponent, InputType, ItemListComponent, LanguageSelectorComponent, LayeredCardComponent, LayoutComponent, LinkComponent, LinkProcessorService, LinksAccordionComponent, LinksCakeComponent, LocalStorageService, LocaleService, MODAL_SIZES, MOTION, MenuComponent, ModalService, MultiSelectSearchComponent, NavigationService, NoContentComponent, NotesBoxComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PLATFORM_CONFIGS, PageContentComponent, PageTemplateComponent, PageWrapperComponent, PaginationComponent, ParticipantCardComponent, PasswordInputComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, 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, 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, ShareButtonsComponent, SimpleComponent, SkeletonComponent, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, StatsCardComponent, StepperComponent, SwipeCarouselComponent, TabbedContentComponent, TabsComponent, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TicketGridComponent, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, ToolbarActionType, ToolbarComponent, WinnerDisplayComponent, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, createGlowCardProps, createNumberFromToField, createTitleProps, goToTop, isAtEnd, maxLength, replaceSpecialChars, resolveColor, resolveInputDefaultValue };
23617
21140
  //# sourceMappingURL=valtech-components.mjs.map