valtech-components 2.0.838 → 2.0.840

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/lib/components/molecules/update-banner/types.mjs +4 -4
  2. package/esm2022/lib/components/molecules/update-banner/update-banner.component.mjs +5 -5
  3. package/esm2022/lib/components/organisms/change-password-modal/change-password-modal.component.mjs +19 -22
  4. package/esm2022/lib/components/organisms/mfa-modal/mfa-modal.component.mjs +5 -5
  5. package/esm2022/lib/config/company-footer.config.mjs +10 -3
  6. package/esm2022/lib/services/auth/auth.service.mjs +8 -3
  7. package/esm2022/lib/services/errors/index.mjs +9 -0
  8. package/esm2022/lib/services/errors/interpret-error.mjs +130 -0
  9. package/esm2022/lib/services/firebase/index.mjs +2 -2
  10. package/esm2022/lib/services/firebase/messaging.service.mjs +212 -3
  11. package/esm2022/lib/services/firebase/types.mjs +1 -1
  12. package/esm2022/lib/services/i18n/default-content.mjs +5 -1
  13. package/esm2022/lib/version.mjs +2 -2
  14. package/esm2022/public-api.mjs +6 -1
  15. package/fesm2022/valtech-components.mjs +397 -36
  16. package/fesm2022/valtech-components.mjs.map +1 -1
  17. package/lib/components/molecules/update-banner/types.d.ts +3 -3
  18. package/lib/components/organisms/change-password-modal/change-password-modal.component.d.ts +0 -1
  19. package/lib/config/company-footer.config.d.ts +4 -0
  20. package/lib/services/errors/index.d.ts +9 -0
  21. package/lib/services/errors/interpret-error.d.ts +63 -0
  22. package/lib/services/firebase/index.d.ts +1 -1
  23. package/lib/services/firebase/messaging.service.d.ts +110 -1
  24. package/lib/services/firebase/types.d.ts +30 -0
  25. package/lib/version.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/public-api.d.ts +1 -0
@@ -15,7 +15,7 @@ export declare const UPDATE_BANNER_I18N_NAMESPACE = "UpdateBanner";
15
15
  */
16
16
  export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
17
17
  readonly es: {
18
- readonly availableTitle: "Hay una versión nueva disponible";
18
+ readonly availableTitle: "Hay una versión nueva disponible";
19
19
  readonly availableMessage: "Actualiza para obtener las últimas mejoras.";
20
20
  readonly requiredTitle: "Debes actualizar para continuar";
21
21
  readonly requiredMessage: "Esta versión ya no es compatible. Actualiza para seguir.";
@@ -23,7 +23,7 @@ export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
23
23
  readonly dismissAction: "Cerrar";
24
24
  };
25
25
  readonly en: {
26
- readonly availableTitle: "A new version is available";
26
+ readonly availableTitle: "A new version is available";
27
27
  readonly availableMessage: "Update to get the latest improvements.";
28
28
  readonly requiredTitle: "You must update to continue";
29
29
  readonly requiredMessage: "This version is no longer supported. Please update.";
@@ -31,7 +31,7 @@ export declare const UPDATE_BANNER_DEFAULT_CONTENT: {
31
31
  readonly dismissAction: "Close";
32
32
  };
33
33
  readonly pt: {
34
- readonly availableTitle: "Há uma nova versão disponível";
34
+ readonly availableTitle: "Há uma nova versão disponível";
35
35
  readonly availableMessage: "Atualize para obter as últimas melhorias.";
36
36
  readonly requiredTitle: "Você precisa atualizar para continuar";
37
37
  readonly requiredMessage: "Esta versão não é mais compatível. Atualize para seguir.";
@@ -49,7 +49,6 @@ export declare class ChangePasswordModalComponent {
49
49
  /** Modo actual — `loading` mientras se consulta `checkHasPassword()`. */
50
50
  readonly mode: import("@angular/core").Signal<PasswordModalMode>;
51
51
  private readonly _formState;
52
- constructor();
53
52
  /** Traduce una clave del namespace `_auth`. */
54
53
  t(key: string): string;
55
54
  readonly formProps: import("@angular/core").Signal<FormMetadata>;
@@ -64,6 +64,8 @@ export declare const VALTECH_FOOTER_I18N: {
64
64
  aboutUs: string;
65
65
  privacyPolicy: string;
66
66
  termsConditions: string;
67
+ cookiesPolicy: string;
68
+ legalNotice: string;
67
69
  contactSupport: string;
68
70
  faq: string;
69
71
  feedback: string;
@@ -75,6 +77,8 @@ export declare const VALTECH_FOOTER_I18N: {
75
77
  aboutUs: string;
76
78
  privacyPolicy: string;
77
79
  termsConditions: string;
80
+ cookiesPolicy: string;
81
+ legalNotice: string;
78
82
  contactSupport: string;
79
83
  faq: string;
80
84
  feedback: string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Error interpretation helpers.
3
+ *
4
+ * `interpretError` normaliza cualquier error (HttpErrorResponse crudo,
5
+ * AuthError aplanado, Error de JS, o un valor arbitrario) a un
6
+ * `InterpretedError` con forma estable. Función pura, sin Angular DI.
7
+ */
8
+ export { interpretError } from './interpret-error';
9
+ export type { InterpretedError } from './interpret-error';
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Error interpretation helper for the Valtech factory.
3
+ *
4
+ * Todos los frontends del factory consumen la misma API (backend Go) y la misma
5
+ * librería. La lógica de interpretar errores debe vivir una sola vez, acá.
6
+ *
7
+ * El backend Go (`apperrors`) SIEMPRE devuelve errores como JSON:
8
+ * { "code": string, "message": string, "operationId": string }
9
+ * donde `message` ya viene en español y es user-friendly.
10
+ *
11
+ * En Angular un error HTTP llega como `HttpErrorResponse` (body en `.error`).
12
+ * Un fallo de red es un `HttpErrorResponse` con `status === 0`.
13
+ *
14
+ * Además `AuthService.handleAuthError` aplana el `HttpErrorResponse` a un
15
+ * `AuthError { code, message }` (code/message al nivel superior). Por eso este
16
+ * helper acepta AMBAS formas — el crudo y el aplanado.
17
+ */
18
+ /**
19
+ * Resultado normalizado de cualquier error. Garantiza una forma estable y
20
+ * predecible para que las webapps no tengan que hacer narrowing manual.
21
+ */
22
+ export interface InterpretedError {
23
+ /** Código del backend, o el sentinel `'NETWORK'` / `'UNKNOWN'`. */
24
+ code: string;
25
+ /** Mensaje del backend (español, user-friendly) o un genérico en español. */
26
+ message: string;
27
+ /** `operationId` del backend, si vino. Útil para soporte / tracing. */
28
+ operationId?: string;
29
+ /** HTTP status, si aplica (`0` para fallos de red). */
30
+ status?: number;
31
+ /** `true` si fue un fallo de red (status 0 / sin respuesta). */
32
+ isNetwork: boolean;
33
+ }
34
+ /**
35
+ * Normaliza CUALQUIER error a un `InterpretedError`.
36
+ *
37
+ * Función pura — sin dependencias de Angular DI, testeable y usable desde
38
+ * cualquier lado (componentes, servicios, interceptores, scripts).
39
+ *
40
+ * Nunca lanza: siempre devuelve un `InterpretedError` válido.
41
+ *
42
+ * Casos cubiertos:
43
+ * - `HttpErrorResponse` con `status === 0` (o sin respuesta) → fallo de red.
44
+ * - `HttpErrorResponse` con body `{ code, message, operationId }` del backend.
45
+ * - `AuthError` aplanado `{ code, message }` (code/message top-level).
46
+ * - `Error` plano de JS → `code: 'UNKNOWN'`, `message: err.message`.
47
+ * - Cualquier otra cosa (string, null, undefined, objeto raro) → genérico.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * try {
52
+ * await firstValueFrom(this.http.get(url));
53
+ * } catch (err) {
54
+ * const e = interpretError(err);
55
+ * if (e.isNetwork) {
56
+ * this.toast.show({ message: e.message, color: 'dark' });
57
+ * } else {
58
+ * this.errorCode.set(e.code);
59
+ * }
60
+ * }
61
+ * ```
62
+ */
63
+ export declare function interpretError(err: unknown): InterpretedError;
@@ -36,7 +36,7 @@ export { CollectionOptions, FirestoreCollection, FirestoreCollectionFactory, Sub
36
36
  export { QueryBuilder, query } from './utils/query-builder';
37
37
  export { buildPath, extractPathParams, getCollectionPath, getDocumentId, isCollectionPath, isDocumentPath, isValidPath, joinPath, } from './utils/path-builder';
38
38
  export { StorageService } from './storage.service';
39
- export { MessagingService } from './messaging.service';
39
+ export { MessagingService, type EnablePushOptions, type RegisterDeviceFn, } from './messaging.service';
40
40
  export { NotificationDocument, NotificationsService } from './notifications.service';
41
41
  export { AnalyticsService } from './analytics.service';
42
42
  export { AnalyticsRouterTracker } from './analytics-router-tracker';
@@ -7,8 +7,28 @@
7
7
  */
8
8
  import { Injector, NgZone } from '@angular/core';
9
9
  import { Observable } from 'rxjs';
10
- import { NotificationAction, NotificationClickEvent, NotificationPayload, NotificationPermission, ValtechFirebaseConfig } from './types';
10
+ import { EnablePushResult, NotificationAction, NotificationClickEvent, NotificationPayload, NotificationPermission, ValtechFirebaseConfig } from './types';
11
11
  import * as i0 from "@angular/core";
12
+ /**
13
+ * Callback opcional de registro de device para `MessagingService.enable()`.
14
+ *
15
+ * El registro del device vive en `AuthService` (necesita el JWT + el endpoint
16
+ * del backend). `MessagingService` NO puede depender de `AuthService` sin
17
+ * crear un ciclo (AuthService → MessagingService). Por eso el caller le pasa
18
+ * el paso de registro como callback. Recibe el token FCM y devuelve si el
19
+ * device quedó registrado.
20
+ */
21
+ export type RegisterDeviceFn = (token: string) => Promise<boolean>;
22
+ /**
23
+ * Opciones de `MessagingService.enable()`.
24
+ */
25
+ export interface EnablePushOptions {
26
+ /**
27
+ * Paso opcional de registro de device en el backend. Si se omite, `enable()`
28
+ * resuelve con `status: 'enabled'` apenas obtiene el token (sin registrar).
29
+ */
30
+ registerDevice?: RegisterDeviceFn;
31
+ }
12
32
  /**
13
33
  * Estado interno del servicio de messaging
14
34
  */
@@ -69,6 +89,31 @@ export declare class MessagingService {
69
89
  * Es un *optimistic hint* — la verdad la confirma el siguiente `getToken()`.
70
90
  */
71
91
  private readonly TOKEN_STORAGE_KEY;
92
+ /**
93
+ * Timeout (ms) para `navigator.serviceWorker.ready` dentro de `getToken()`.
94
+ *
95
+ * En un cold load el SW puede no estar activado aún y `serviceWorker.ready`
96
+ * no resolver nunca. Pasado este tiempo, `getToken()` rechaza limpio en lugar
97
+ * de colgarse indefinidamente.
98
+ */
99
+ private readonly SW_READY_TIMEOUT_MS;
100
+ /**
101
+ * Timeout (ms) del watchdog de `enable()` antes de auto-recargar la página.
102
+ *
103
+ * El flujo de activación a veces se cuelga ANTES de `getToken()` — caso
104
+ * típico: `Notification.requestPermission()` no muestra el popup del SO en un
105
+ * cold load. El timeout de `getToken()` (SW_READY_TIMEOUT_MS) no cubre eso;
106
+ * por eso `enable()` envuelve el flujo completo en este watchdog.
107
+ *
108
+ * Un flujo exitoso real llega a token+device en ~4s; 15s es holgado y no
109
+ * atrapa un éxito lento.
110
+ */
111
+ private readonly ENABLE_WATCHDOG_MS;
112
+ /**
113
+ * Key de sessionStorage — garantiza un solo auto-reload por sesión. Si el
114
+ * flujo se cuelga una 2ª vez tras el reload, NO se recarga de nuevo (anti-loop).
115
+ */
116
+ private readonly AUTORELOAD_FLAG;
72
117
  constructor(injector: Injector, config: ValtechFirebaseConfig, platformId: Object, ngZone: NgZone);
73
118
  /**
74
119
  * Obtiene la instancia de Messaging via Firebase SDK directo (NO AngularFire DI).
@@ -122,6 +167,56 @@ export declare class MessagingService {
122
167
  * ```
123
168
  */
124
169
  requestPermission(): Promise<string | null>;
170
+ /**
171
+ * Flujo completo de activación de push, robusto, cross-app.
172
+ *
173
+ * Orquesta: pedir permiso → SW ready → obtener token FCM → (opcional)
174
+ * registrar el device en backend. Es el punto de orquestación único que cada
175
+ * app del factory consume — la lógica de robustez vive aquí, no en cada página.
176
+ *
177
+ * **Watchdog de auto-reload.** El flujo a veces se cuelga ANTES de `getToken()`
178
+ * (ej. `Notification.requestPermission()` que no muestra el popup en un cold
179
+ * load) — el timeout interno de `getToken()` no cubre ese caso. Por eso
180
+ * `enable()` envuelve el flujo entero en un watchdog: si no alcanza un estado
181
+ * terminal en `ENABLE_WATCHDOG_MS` (15s):
182
+ * - 1ª vez en la sesión → marca un flag en `sessionStorage` y hace
183
+ * `window.location.reload()` (un fresh load suele tener el SW activo).
184
+ * Resuelve con `{ status: 'timeout', reloaded: true }`.
185
+ * - ya se auto-recargó antes → NO recarga (anti-loop): limpia el flag y
186
+ * resuelve con `{ status: 'timeout', reloaded: false }` para que la app
187
+ * muestre un error.
188
+ *
189
+ * NO hace throw — siempre resuelve con un `EnablePushResult` descriptivo que
190
+ * la página consumidora inspecciona para decidir qué toast mostrar.
191
+ *
192
+ * @param options.registerDevice Callback opcional que registra el device en
193
+ * el backend (vive en `AuthService` — se pasa como callback para evitar el
194
+ * ciclo de DI AuthService ↔ MessagingService).
195
+ *
196
+ * @example
197
+ * ```typescript
198
+ * const result = await messaging.enable({
199
+ * registerDevice: (token) => auth.registerDevice(token).then(r => r.registered),
200
+ * });
201
+ * if (result.status === 'enabled') {
202
+ * // result.token disponible — push activo
203
+ * } else if (result.status === 'timeout' && !result.reloaded) {
204
+ * // mostrar error: el flujo se colgó y ya se consumió el auto-reload
205
+ * }
206
+ * ```
207
+ */
208
+ enable(options?: EnablePushOptions): Promise<EnablePushResult>;
209
+ /**
210
+ * Flujo real de activación, sin el watchdog (lo envuelve `enable()`).
211
+ * Resuelve siempre con un `EnablePushResult` — no hace throw.
212
+ */
213
+ private runEnableFlow;
214
+ /** True si ya se auto-recargó una vez en esta sesión. */
215
+ private hasAutoReloaded;
216
+ /** Marca que se consumió el auto-reload de esta sesión. */
217
+ private markAutoReloaded;
218
+ /** Limpia el flag anti-loop — tras un éxito o un fallo definitivo. */
219
+ private clearAutoReloadFlag;
125
220
  /**
126
221
  * Obtiene el token FCM actual (sin solicitar permiso).
127
222
  *
@@ -142,6 +237,20 @@ export declare class MessagingService {
142
237
  * SW en iOS PWA. Solo registramos como fallback si todavía no existe.
143
238
  */
144
239
  private resolveServiceWorkerRegistration;
240
+ /**
241
+ * Espera a que el Service Worker quede activo (`navigator.serviceWorker.ready`)
242
+ * pero con un timeout duro.
243
+ *
244
+ * `navigator.serviceWorker.ready` resuelve cuando hay un SW *activo*. En un
245
+ * cold load (primera visita, SW recién registrado, iOS PWA recién abierta) el
246
+ * SW puede quedar en estado `installing`/`waiting` y `ready` no resolver nunca
247
+ * → `getToken()` se cuelga. El `Promise.race` contra un `setTimeout` garantiza
248
+ * que esta espera termina: o gana `ready` (caso normal) o gana el timeout y
249
+ * lanzamos un error claro para que `getToken()` rechace en vez de colgarse.
250
+ *
251
+ * El timer se limpia en ambas ramas para no dejar timers colgando.
252
+ */
253
+ private waitForServiceWorkerReady;
145
254
  /**
146
255
  * Persiste el token FCM en localStorage (o lo limpia si es null/empty).
147
256
  */
@@ -346,6 +346,36 @@ export interface NotificationAction {
346
346
  /** Datos adicionales para la acción */
347
347
  actionData?: Record<string, unknown>;
348
348
  }
349
+ /**
350
+ * Resultado de `MessagingService.enable()`.
351
+ *
352
+ * Tipo discriminado descriptivo (NO se hace throw): la página consumidora lo
353
+ * inspecciona para decidir qué toast/UX mostrar.
354
+ *
355
+ * `status`:
356
+ * - `enabled` — permiso otorgado + token FCM obtenido (`token` presente).
357
+ * - `denied` — el usuario denegó el permiso del navegador.
358
+ * - `unsupported` — el navegador no soporta FCM (incl. iOS sin standalone).
359
+ * - `error` — el flujo falló por otra causa (ver `reason`).
360
+ * - `timeout` — el flujo se colgó >15s. Si `reloaded` es true, la página
361
+ * está por recargarse (auto-reload, 1ª vez en la sesión); si
362
+ * es false, ya se consumió el auto-reload y la página debe
363
+ * mostrar un error sin recargar.
364
+ */
365
+ export interface EnablePushResult {
366
+ /** Resultado del flujo de activación de push. */
367
+ status: 'enabled' | 'denied' | 'unsupported' | 'error' | 'timeout';
368
+ /** Token FCM — presente solo cuando `status === 'enabled'`. */
369
+ token?: string;
370
+ /** Detalle legible de la causa — presente en `error` / `timeout`. */
371
+ reason?: string;
372
+ /**
373
+ * Solo para `status === 'timeout'`: true si se disparó el auto-reload
374
+ * (la página está por recargarse). False si el auto-reload ya se consumió
375
+ * esta sesión y la app debe mostrar un error en lugar de recargar.
376
+ */
377
+ reloaded?: boolean;
378
+ }
349
379
  /**
350
380
  * Evento de click en una notificación
351
381
  */
package/lib/version.d.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Current version of valtech-components.
3
3
  * This is automatically updated during the publish process.
4
4
  */
5
- export declare const VERSION = "2.0.838";
5
+ export declare const VERSION = "2.0.840";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "valtech-components",
3
- "version": "2.0.838",
3
+ "version": "2.0.840",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "valtech-firebase-config": "./src/lib/services/firebase/scripts/generate-sw-config.js"
package/public-api.d.ts CHANGED
@@ -255,6 +255,7 @@ export * from './lib/services/navigation';
255
255
  export * from './lib/services/theme.service';
256
256
  export * from './lib/services/toast.service';
257
257
  export * from './lib/services/types';
258
+ export * from './lib/services/errors';
258
259
  export * from './lib/services/confirmation-dialog/confirmation-dialog.service';
259
260
  export * from './lib/services/confirmation-dialog/types';
260
261
  export * from './lib/services/qr-generator/qr-generator.service';