valtech-components 2.0.992 → 2.0.993

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.
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { inject, InjectionToken, makeEnvironmentProviders, APP_INITIALIZER, ErrorHandler, Injectable, DestroyRef, Inject, signal, computed, PLATFORM_ID, isDevMode, Optional, runInInjectionContext, effect, Pipe, TemplateRef, ViewContainerRef, input, Directive, EventEmitter, Component, Input, Output, ChangeDetectionStrategy, HostBinding, HostListener, NgZone, ViewChild, ChangeDetectorRef, output, ContentChild, ElementRef, viewChild, untracked, Injector, isSignal, ViewEncapsulation } from '@angular/core';
3
- import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, shareReplay, race, timer, forkJoin } from 'rxjs';
3
+ import { BehaviorSubject, throwError, Subject, map, distinctUntilChanged, filter as filter$1, take as take$1, firstValueFrom, of, from, EMPTY, Observable, interval, debounceTime, switchMap as switchMap$1, catchError as catchError$1, takeUntil, isObservable, race, timer, shareReplay, forkJoin } from 'rxjs';
4
4
  import { catchError, switchMap, finalize, filter, take, map as map$1, tap, distinctUntilChanged as distinctUntilChanged$1, retry, timeout, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs/operators';
5
5
  import * as i1$3 from '@angular/common/http';
6
6
  import { provideHttpClient, withInterceptors, HttpClient, HttpErrorResponse } from '@angular/common/http';
@@ -25,7 +25,7 @@ import { getApps, getApp, initializeApp as initializeApp$1 } from 'firebase/app'
25
25
  import * as i2$1 from '@ionic/angular/standalone';
26
26
  import { AlertController, 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, IonInput, IonSelect, IonSelectOption, IonPopover, IonList, IonItem, IonRadioGroup, IonRadio, IonRange, IonSearchbar, IonSegment, IonSegmentButton, IonToggle, IonRippleEffect, IonAccordion, IonAccordionGroup, IonTabBar, IonTabButton, IonBadge, IonBreadcrumb, IonBreadcrumbs, IonChip, IonNote, IonDatetimeButton, ToastController as ToastController$1, IonCol, IonRow, IonRefresher, IonRefresherContent, IonMenuButton, IonFooter, IonListHeader, IonInfiniteScroll, IonInfiniteScrollContent, IonGrid, MenuController, IonMenu, IonMenuToggle, IonSplitPane } from '@ionic/angular/standalone';
27
27
  import { addIcons } from 'ionicons';
28
- import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, 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, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, menuOutline } from 'ionicons/icons';
28
+ import { addOutline, addCircleOutline, alertOutline, alertCircleOutline, arrowBackOutline, arrowForwardOutline, arrowDownOutline, settings, settingsOutline, checkmarkCircleOutline, ellipsisHorizontalOutline, notifications, notificationsOutline, openOutline, closeOutline, chatbubblesOutline, shareOutline, heart, heartOutline, home, homeOutline, eyeOffOutline, eyeOutline, scanOutline, chevronDownOutline, chevronForwardOutline, checkmarkOutline, clipboardOutline, copyOutline, filterOutline, locationOutline, calendarOutline, businessOutline, logoTwitter, logoInstagram, logoLinkedin, logoYoutube, logoTiktok, logoFacebook, logoGoogle, createOutline, trashOutline, playOutline, peopleOutline, phonePortraitOutline, refreshOutline, documentTextOutline, lockClosedOutline, informationCircleOutline, logoNpm, removeOutline, optionsOutline, personOutline, shieldCheckmarkOutline, keyOutline, desktopOutline, logOutOutline, cloudDownloadOutline, cloudOutline, gitBranchOutline, layersOutline, serverOutline, warningOutline, bulbOutline, lockOpenOutline, diamondOutline, add, close, share, create, trash, star, camera, mic, send, downloadOutline, chevronDown, language, globeOutline, checkmark, list, grid, apps, menu, search, person, helpCircle, informationCircle, documentText, mail, calendar, folder, chevronForward, ellipsisHorizontal, chevronBack, playBack, playForward, 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, logoWhatsapp, paperPlaneOutline, mailOutline, chevronDownCircleOutline, closeCircle, alertCircle, logoApple, logoMicrosoft, linkOutline, unlinkOutline, cloudOfflineOutline, documentOutline, attachOutline, cameraOutline, closeCircleOutline, imageOutline, chevronBackOutline, sendOutline, chatbubbleEllipsesOutline, swapVerticalOutline, chevronUpOutline, searchOutline, cartOutline, chatbubble, compass, compassOutline, gridOutline, listOutline, folderOutline, documents, documentsOutline, statsChart, statsChartOutline, laptopOutline, swapHorizontalOutline, alarmOutline, chatboxEllipsesOutline, checkmarkDoneOutline, cogOutline, notificationsOffOutline, menuOutline } from 'ionicons/icons';
29
29
  import { Browser } from '@capacitor/browser';
30
30
  import * as i1$6 from '@angular/platform-browser';
31
31
  import { DomSanitizer, Meta, Title } from '@angular/platform-browser';
@@ -54,7 +54,7 @@ import 'prismjs/components/prism-json';
54
54
  * Current version of valtech-components.
55
55
  * This is automatically updated during the publish process.
56
56
  */
57
- const VERSION = '2.0.992';
57
+ const VERSION = '2.0.993';
58
58
 
59
59
  // Control de estado de refresco (singleton a nivel de módulo)
60
60
  let isRefreshing = false;
@@ -21657,7 +21657,7 @@ class CommentComponent {
21657
21657
  </div>
21658
21658
  }
21659
21659
  </div>
21660
- `, isInline: true, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}:host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(.6875rem,.8125rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(.6875rem,.75rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(.6875rem,.6875rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(.6875rem,.625rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(.6875rem,.5625rem)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
21660
+ `, isInline: true, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"], dependencies: [{ kind: "component", type: CommentComponent, selector: "val-comment", inputs: ["props"], outputs: ["authorClick", "reactionClick", "actionClick", "menuItemClick", "loadMoreClick", "collapseToggle"] }, { kind: "ngmodule", type: CommonModule }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "form", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: IonPopover, selector: "ion-popover" }, { kind: "component", type: IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: IonItem, selector: "ion-item", inputs: ["button", "color", "detail", "detailIcon", "disabled", "download", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: AvatarComponent, selector: "val-avatar", inputs: ["preset", "props"], outputs: ["onClick"] }] }); }
21661
21661
  }
21662
21662
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: CommentComponent, decorators: [{
21663
21663
  type: Component,
@@ -21847,7 +21847,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
21847
21847
  </div>
21848
21848
  }
21849
21849
  </div>
21850
- `, styles: ["@charset \"UTF-8\";:root{--val-container-sm: 540px;--val-container-md: 720px;--val-container-lg: 880px;--val-container-xl: 1100px;--val-container-padding: 16px;--ion-color-primary: #7026df;--ion-color-primary-rgb: 112, 38, 223;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #6321c4;--ion-color-primary-tint: #7e3ce2;--ion-color-secondary: #e2ccff;--ion-color-secondary-rgb: 226, 204, 255;--ion-color-secondary-contrast: #000000;--ion-color-secondary-contrast-rgb: 0, 0, 0;--ion-color-secondary-shade: #c7b4e0;--ion-color-secondary-tint: #e5d1ff;--ion-color-texti: #354c69;--ion-color-texti-rgb: 53, 76, 105;--ion-color-texti-contrast: #ffffff;--ion-color-texti-contrast-rgb: 255, 255, 255;--ion-color-texti-shade: #2f435c;--ion-color-texti-tint: #495e78;--ion-color-darki: #090f1b;--ion-color-darki-rgb: 9, 15, 27;--ion-color-darki-contrast: #ffffff;--ion-color-darki-contrast-rgb: 255, 255, 255;--ion-color-darki-shade: #080d18;--ion-color-darki-tint: #222732;--ion-color-medium: #9e9e9e;--ion-color-medium-rgb: 158, 158, 158;--ion-color-medium-contrast: #000000;--ion-color-medium-contrast-rgb: 0, 0, 0;--ion-color-medium-shade: #8b8b8b;--ion-color-medium-tint: #a8a8a8;--swiper-pagination-color: var(--ion-color-primary);--swiper-navigation-color: var(--ion-color-primary);--swiper-pagination-bullet-inactive-color: var(--ion-color-medium)}body.dark,html.ion-palette-dark,body[data-theme=dark]{--ion-color-texti: #8fc1ff;--ion-color-texti-rgb: 143, 193, 255;--ion-color-texti-contrast: #000000;--ion-color-texti-contrast-rgb: 0, 0, 0;--ion-color-texti-shade: #7eaae0;--ion-color-texti-tint: #9ac7ff;--ion-color-darki: #ffffff;--ion-color-darki-rgb: 255, 255, 255;--ion-color-darki-contrast: #000000;--ion-color-darki-contrast-rgb: 0, 0, 0;--ion-color-darki-shade: #e0e0e0;--ion-color-darki-tint: #ffffff;--ion-color-primary: #8f49f8;--ion-color-primary-rgb: 143, 73, 248;--ion-color-primary-contrast: #ffffff;--ion-color-primary-contrast-rgb: 255, 255, 255;--ion-color-primary-shade: #7e40da;--ion-color-primary-tint: #9a5bf9}.ion-color-texti{--ion-color-base: var(--ion-color-texti);--ion-color-base-rgb: var(--ion-color-texti-rgb);--ion-color-contrast: var(--ion-color-texti-contrast);--ion-color-contrast-rgb: var(--ion-color-texti-contrast-rgb);--ion-color-shade: var(--ion-color-texti-shade);--ion-color-tint: var(--ion-color-texti-tint)}.ion-color-darki{--ion-color-base: var(--ion-color-darki);--ion-color-base-rgb: var(--ion-color-darki-rgb);--ion-color-contrast: var(--ion-color-darki-contrast);--ion-color-contrast-rgb: var(--ion-color-darki-contrast-rgb);--ion-color-shade: var(--ion-color-darki-shade);--ion-color-tint: var(--ion-color-darki-tint)}:host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(.6875rem,.8125rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(.6875rem,.75rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(.6875rem,.6875rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(.6875rem,.625rem)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(.6875rem,.5625rem)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"] }]
21850
+ `, styles: [":host{display:block}.comment{--indent-size: 24px;--max-indent: 72px;padding:12px 0}.comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.08);border-radius:8px;padding:12px;margin:-4px}.comment.pinned{border-left:3px solid var(--ion-color-primary);padding-left:12px}.comment.collapsed{opacity:.7}.pinned-indicator{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--ion-color-primary);margin-bottom:8px;padding-left:44px}.pinned-indicator ion-icon{font-size:.875rem}.comment-main{display:flex;gap:12px}.comment-avatar{flex-shrink:0;cursor:pointer}.comment-avatar val-avatar{--size: 36px}.comment-avatar .avatar-placeholder{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--ion-color-primary),var(--ion-color-secondary));display:flex;align-items:center;justify-content:center;color:#fff;font-size:.875rem;font-weight:600}.comment-body{flex:1;min-width:0}.comment-header{display:flex;align-items:center;flex-wrap:wrap;gap:6px;margin-bottom:4px}.author-info{display:flex;align-items:center;gap:4px;cursor:pointer}.author-info:hover .author-name{text-decoration:underline}.author-name{font-weight:600;font-size:.875rem;color:var(--ion-text-color)}.verified-badge{color:var(--ion-color-primary);font-size:1rem}.author-username{font-size:.8125rem;color:var(--ion-color-medium)}.comment-meta{display:flex;align-items:center;gap:6px;margin-left:auto}.timestamp{font-size:.75rem;color:var(--ion-color-medium)}.edited-label{font-size:.6875rem;color:var(--ion-color-medium);font-style:italic}.more-button{--padding-start: 4px;--padding-end: 4px;margin:0;height:28px}.more-button ion-icon{font-size:1.125rem;color:var(--ion-color-medium)}.menu-divider{height:1px;background-color:var(--ion-color-light);margin:4px 0}.comment-content{font-size:.875rem;line-height:1.5;color:var(--ion-text-color);word-wrap:break-word;overflow-wrap:break-word}.comment-content :global(.comment-link){color:var(--ion-color-primary);text-decoration:none}.comment-content :global(.comment-link):hover{text-decoration:underline}.comment-content :global(.comment-mention){color:var(--ion-color-primary);font-weight:500;cursor:pointer}.comment-content :global(.comment-mention):hover{text-decoration:underline}.comment-content :global(.comment-hashtag){color:var(--ion-color-primary);cursor:pointer}.comment-content :global(.comment-hashtag):hover{text-decoration:underline}.comment-reactions{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}.reaction-button{display:flex;align-items:center;gap:4px;background:none;border:none;padding:4px 8px;border-radius:16px;cursor:pointer;font-size:.8125rem;color:var(--reaction-color, var(--ion-color-medium));transition:all .2s ease;background-color:transparent}.reaction-button:hover{background-color:rgba(var(--ion-color-medium-rgb),.1)}.reaction-button.active{background-color:rgba(var(--ion-color-primary-rgb),.1)}.reaction-button.active ion-icon{transform:scale(1.1)}.reaction-button ion-icon{font-size:1.125rem;transition:transform .2s ease}.reaction-button .reaction-count{font-weight:500}.comment-actions{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;margin-left:-8px}.comment-actions ion-button{--padding-start: 8px;--padding-end: 8px;font-size:.8125rem;height:32px;text-transform:none;font-weight:500}.expand-button,.collapse-button,.load-more-button,.view-replies-button{display:flex;align-items:center;gap:6px;background:none;border:none;padding:8px 12px;cursor:pointer;font-size:.8125rem;color:var(--ion-color-primary);border-radius:8px;transition:background-color .2s}.expand-button:hover,.collapse-button:hover,.load-more-button:hover,.view-replies-button:hover{background-color:rgba(var(--ion-color-primary-rgb),.1)}.expand-button ion-icon,.collapse-button ion-icon,.load-more-button ion-icon,.view-replies-button ion-icon{font-size:1rem}.expand-button{margin-top:4px;color:var(--ion-color-medium)}.comment-children{margin-top:12px;padding-left:min(var(--indent-size),var(--max-indent));border-left:2px solid var(--ion-color-light-shade)}.comment-children val-comment{display:block}.comment-children val-comment:first-of-type .comment{padding-top:0}.load-more-button,.view-replies-button{margin-top:8px;margin-left:0}ion-popover{--width: 200px}ion-popover ion-list{padding:4px 0}ion-popover ion-item{--min-height: 40px;--padding-start: 12px;font-size:.875rem}ion-popover ion-item ion-icon{font-size:1.125rem;margin-right:8px}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 2px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 2px);height:max(28px,36px - 2px);font-size:max(11px,14px - 1px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 4px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 4px);height:max(28px,36px - 4px);font-size:max(11px,14px - 2px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 6px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 6px);height:max(28px,36px - 6px);font-size:max(11px,14px - 3px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 8px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 8px);height:max(28px,36px - 8px);font-size:max(11px,14px - 4px)}:host-context(val-comment) .comment-avatar val-avatar{--size: max(28px, 36px - 10px)}:host-context(val-comment) .comment-avatar .avatar-placeholder{width:max(28px,36px - 10px);height:max(28px,36px - 10px);font-size:max(11px,14px - 5px)}:host-context(.dark) .comment.highlighted,:host-context(.ion-palette-dark) .comment.highlighted,:host-context(html.ion-palette-dark) .comment.highlighted,:host-context(body.dark) .comment.highlighted,:host-context([data-theme=dark]) .comment.highlighted{background-color:rgba(var(--ion-color-primary-rgb),.12)}:host-context(.dark) .avatar-placeholder,:host-context(.ion-palette-dark) .avatar-placeholder,:host-context(html.ion-palette-dark) .avatar-placeholder,:host-context(body.dark) .avatar-placeholder,:host-context([data-theme=dark]) .avatar-placeholder{background:linear-gradient(135deg,var(--ion-color-primary-shade),var(--ion-color-secondary-shade))}\n"] }]
21851
21851
  }], propDecorators: { props: [{
21852
21852
  type: Input
21853
21853
  }], authorClick: [{
@@ -41737,7 +41737,7 @@ const PROFILE_VIEW_I18N = {
41737
41737
  const HANDLE_PATTERN = /^[a-z_][a-z0-9_]*$/;
41738
41738
  const HANDLE_MIN = 3;
41739
41739
  const HANDLE_MAX = 20;
41740
- const DEFAULT_NAMESPACE$c = 'Settings.Profile';
41740
+ const DEFAULT_NAMESPACE$d = 'Settings.Profile';
41741
41741
  /**
41742
41742
  * `val-profile-view` — vista Perfil full-feature autocontenida (organism).
41743
41743
  *
@@ -41787,7 +41787,7 @@ class ProfileViewComponent {
41787
41787
  showPhone: merged.showPhone ?? true,
41788
41788
  showShareCta: merged.showShareCta ?? true,
41789
41789
  profileBaseUrl: merged.profileBaseUrl ?? '',
41790
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$c,
41790
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$d,
41791
41791
  onSaved: merged.onSaved,
41792
41792
  onAvatarUploaded: merged.onAvatarUploaded,
41793
41793
  };
@@ -42263,12 +42263,17 @@ class ProfileViewComponent {
42263
42263
 
42264
42264
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
42265
42265
 
42266
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
42267
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
42266
+ @if (resolvedConfig().showShareCta && p.handle) {
42267
+ <section class="settings-section share-cta-section">
42268
+ <val-cta-card [props]="shareCta()" (onAction)="shareOpen.set(true)" />
42269
+ </section>
42270
+ }
42268
42271
  }
42269
42272
  }
42270
42273
  </div>
42271
- `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }] }); }
42274
+
42275
+ <val-share-profile-modal [isOpen]="shareOpen()" [props]="shareModal()" (dismissed)="shareOpen.set(false)" />
42276
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: AvatarUploadComponent, selector: "val-avatar-upload", inputs: ["props"], outputs: ["uploaded", "error", "uploadStart"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }, { kind: "component", type: FormComponent, selector: "val-form", inputs: ["props"], outputs: ["onSubmit", "onInvalid", "onSelectChange"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: CtaCardComponent, selector: "val-cta-card", inputs: ["props"], outputs: ["onAction"] }, { kind: "component", type: ShareProfileModalComponent, selector: "val-share-profile-modal", inputs: ["isOpen", "props"], outputs: ["dismissed", "shared"] }] }); }
42272
42277
  }
42273
42278
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ProfileViewComponent, decorators: [{
42274
42279
  type: Component,
@@ -42344,11 +42349,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
42344
42349
 
42345
42350
  <val-form #form [props]="formMeta" (onSubmit)="onSubmit($event)" />
42346
42351
 
42347
- <!-- Card de compartir + modal QR removidos temporalmente (se retoman luego).
42348
- Wiring intacto: shareCta(), shareModal(), shareOpen, showShareCta. -->
42352
+ @if (resolvedConfig().showShareCta && p.handle) {
42353
+ <section class="settings-section share-cta-section">
42354
+ <val-cta-card [props]="shareCta()" (onAction)="shareOpen.set(true)" />
42355
+ </section>
42356
+ }
42349
42357
  }
42350
42358
  }
42351
42359
  </div>
42360
+
42361
+ <val-share-profile-modal [isOpen]="shareOpen()" [props]="shareModal()" (dismissed)="shareOpen.set(false)" />
42352
42362
  `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.page-header h1{font-size:22px;font-weight:700;margin:0 0 4px}.hint{font-size:13px;opacity:.7;margin:0 0 12px}.settings-section{padding:16px 0}.settings-section+.settings-section{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.avatar-section{display:flex;align-items:center;gap:16px}.avatar-meta{display:flex;flex-direction:column;gap:2px}\n"] }]
42353
42363
  }], ctorParameters: () => [], propDecorators: { config: [{
42354
42364
  type: Input
@@ -42479,15 +42489,6 @@ class PreferencesService {
42479
42489
  this._updateInFlight = false;
42480
42490
  const destroyRef = inject(DestroyRef);
42481
42491
  destroyRef.onDestroy(() => this.unbind());
42482
- // Seed `_fontSize` con el valor que FontSizeService ya restauró desde
42483
- // localStorage en su constructor (providedIn root → ya corrió al inyectarlo
42484
- // acá). Sin esto, el signal arranca en 'medium' y el effect de abajo —que
42485
- // dispara al marcarse `synced`— forzaría 'medium' pisando el tamaño
42486
- // restaurado cuando el snapshot Firestore NO trae `fontSize` (user sin ese
42487
- // campo en el mirror). Bug: cambias el tamaño, refrescas y se pierde.
42488
- if (this.fontSizeService) {
42489
- this._fontSize.set(this.fontSizeService.size());
42490
- }
42491
42492
  // Auto-bind al user actual. effect() corre en injection context (constructor OK).
42492
42493
  // allowSignalWrites: bindToUser/unbind escriben `_synced` (señal interna sin
42493
42494
  // dependencias del propio effect) → seguro y necesario.
@@ -42702,7 +42703,7 @@ const PREFERENCES_VIEW_I18N = {
42702
42703
  },
42703
42704
  };
42704
42705
 
42705
- const DEFAULT_NAMESPACE$b = 'Settings.General';
42706
+ const DEFAULT_NAMESPACE$c = 'Settings.General';
42706
42707
  const DEFAULT_LANGUAGES = ['es', 'en'];
42707
42708
  /**
42708
42709
  * `val-preferences-view` — vista de preferencias de UI cross-app (tema +
@@ -42739,7 +42740,7 @@ class PreferencesViewComponent {
42739
42740
  showLanguage: merged.showLanguage ?? true,
42740
42741
  showFontSize: merged.showFontSize ?? true,
42741
42742
  supportedLanguages: merged.supportedLanguages ?? DEFAULT_LANGUAGES,
42742
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
42743
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$c,
42743
42744
  onThemeChanged: merged.onThemeChanged,
42744
42745
  onLanguageChanged: merged.onLanguageChanged,
42745
42746
  onFontSizeChanged: merged.onFontSizeChanged,
@@ -44551,7 +44552,7 @@ const SECURITY_VIEW_I18N = {
44551
44552
  };
44552
44553
 
44553
44554
  addIcons({ laptopOutline, phonePortraitOutline });
44554
- const DEFAULT_NAMESPACE$a = 'Settings.Security';
44555
+ const DEFAULT_NAMESPACE$b = 'Settings.Security';
44555
44556
  /**
44556
44557
  * `val-security-view` — vista Seguridad full-feature autocontenida (organism).
44557
44558
  *
@@ -44600,7 +44601,7 @@ class SecurityViewComponent {
44600
44601
  showChangeEmail: merged.showChangeEmail ?? true,
44601
44602
  showMfa: merged.showMfa ?? true,
44602
44603
  showPolicies: merged.showPolicies ?? true,
44603
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$a,
44604
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$b,
44604
44605
  handleDeepLinks: merged.handleDeepLinks ?? true,
44605
44606
  homeRoute: merged.homeRoute ?? '/app/home',
44606
44607
  onMfaCompleted: merged.onMfaCompleted,
@@ -45506,7 +45507,7 @@ const CREATE_ORG_MODAL_I18N = {
45506
45507
  },
45507
45508
  };
45508
45509
 
45509
- const DEFAULT_NAMESPACE$9 = 'CreateOrgModal';
45510
+ const DEFAULT_NAMESPACE$a = 'CreateOrgModal';
45510
45511
  /**
45511
45512
  * `val-create-org-modal` — modal de creación de organización (organism
45512
45513
  * compartido). Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por
@@ -45532,10 +45533,10 @@ class CreateOrgModalComponent {
45532
45533
  this.toast = inject(ToastService);
45533
45534
  this.errors = inject(ValtechErrorService);
45534
45535
  /** Namespace i18n con que la vista resuelve sus textos. */
45535
- this.i18nNamespace = DEFAULT_NAMESPACE$9;
45536
+ this.i18nNamespace = DEFAULT_NAMESPACE$a;
45536
45537
  this._busy = signal(false);
45537
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
45538
- this.i18n.registerContent(DEFAULT_NAMESPACE$9, CREATE_ORG_MODAL_I18N);
45538
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$a)) {
45539
+ this.i18n.registerContent(DEFAULT_NAMESPACE$a, CREATE_ORG_MODAL_I18N);
45539
45540
  }
45540
45541
  this.formMeta = signal(this.buildFormMeta());
45541
45542
  effect(() => {
@@ -45771,7 +45772,7 @@ const DELETE_ACCOUNT_MODAL_I18N = {
45771
45772
  },
45772
45773
  };
45773
45774
 
45774
- const DEFAULT_NAMESPACE$8 = 'Settings.DeleteAccount';
45775
+ const DEFAULT_NAMESPACE$9 = 'Settings.DeleteAccount';
45775
45776
  /**
45776
45777
  * `val-delete-account-modal` — modal de eliminación de cuenta (organism,
45777
45778
  * cuenta-específico). Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -45797,7 +45798,7 @@ class DeleteAccountModalComponent {
45797
45798
  this.i18n = inject(I18nService);
45798
45799
  this.toast = inject(ToastService);
45799
45800
  /** Namespace i18n con que la vista resuelve sus textos. */
45800
- this.i18nNamespace = DEFAULT_NAMESPACE$8;
45801
+ this.i18nNamespace = DEFAULT_NAMESPACE$9;
45801
45802
  this.hasPassword = signal(null);
45802
45803
  this.codeSent = signal(false);
45803
45804
  this.formState = signal(ComponentStates.ENABLED);
@@ -45864,8 +45865,8 @@ class DeleteAccountModalComponent {
45864
45865
  ],
45865
45866
  actions: this.submitBtn('delete-code-submit', 'confirmCta'),
45866
45867
  }));
45867
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
45868
- this.i18n.registerContent(DEFAULT_NAMESPACE$8, DELETE_ACCOUNT_MODAL_I18N);
45868
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$9)) {
45869
+ this.i18n.registerContent(DEFAULT_NAMESPACE$9, DELETE_ACCOUNT_MODAL_I18N);
45869
45870
  }
45870
45871
  }
45871
45872
  ngOnInit() {
@@ -46066,7 +46067,7 @@ const ORG_INFO_SHEET_I18N = {
46066
46067
  },
46067
46068
  };
46068
46069
 
46069
- const DEFAULT_NAMESPACE$7 = 'OrgInfoSheet';
46070
+ const DEFAULT_NAMESPACE$8 = 'OrgInfoSheet';
46070
46071
  /**
46071
46072
  * `val-org-info-sheet` — sheet informativo "¿Qué son las organizaciones?".
46072
46073
  * Extraído del `OrgInfoSheetComponent` inline de `AccountPage` bajo el proceso de
@@ -46111,15 +46112,15 @@ class OrgInfoSheetComponent {
46111
46112
  text: this.closeProps.text || this.resolvedCloseLabel(),
46112
46113
  }));
46113
46114
  addIcons({ peopleOutline, swapHorizontalOutline, mailOutline });
46114
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
46115
- this.i18n.registerContent(DEFAULT_NAMESPACE$7, ORG_INFO_SHEET_I18N);
46115
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$8)) {
46116
+ this.i18n.registerContent(DEFAULT_NAMESPACE$8, ORG_INFO_SHEET_I18N);
46116
46117
  }
46117
46118
  }
46118
46119
  dismiss() {
46119
46120
  this._modalRef?.dismiss(null, 'cancel');
46120
46121
  }
46121
46122
  tt(key) {
46122
- return this.i18n.t(key, DEFAULT_NAMESPACE$7);
46123
+ return this.i18n.t(key, DEFAULT_NAMESPACE$8);
46123
46124
  }
46124
46125
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: OrgInfoSheetComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
46125
46126
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: OrgInfoSheetComponent, isStandalone: true, selector: "val-org-info-sheet", inputs: { title: "title", subtitle: "subtitle", body: "body", closeLabel: "closeLabel", closeProps: "closeProps", _modalRef: "_modalRef" }, ngImport: i0, template: `
@@ -46286,7 +46287,7 @@ const SWITCH_ORG_MODAL_I18N = {
46286
46287
  },
46287
46288
  };
46288
46289
 
46289
- const DEFAULT_NAMESPACE$6 = 'Settings.SwitchOrg';
46290
+ const DEFAULT_NAMESPACE$7 = 'Settings.SwitchOrg';
46290
46291
  addIcons({ businessOutline, checkmarkCircleOutline });
46291
46292
  /**
46292
46293
  * `val-switch-org-modal` — modal de cambio de organización activa (organism
@@ -46312,7 +46313,7 @@ class SwitchOrgModalComponent {
46312
46313
  this.orgSwitch = inject(OrgSwitchService);
46313
46314
  this.errors = inject(ValtechErrorService);
46314
46315
  /** Namespace i18n con que la vista resuelve sus textos. */
46315
- this.i18nNamespace = DEFAULT_NAMESPACE$6;
46316
+ this.i18nNamespace = DEFAULT_NAMESPACE$7;
46316
46317
  this.orgs = signal([]);
46317
46318
  this.loading = signal(false);
46318
46319
  this.query = signal('');
@@ -46328,8 +46329,8 @@ class SwitchOrgModalComponent {
46328
46329
  return list;
46329
46330
  return list.filter(o => o.name.toLowerCase().includes(q));
46330
46331
  });
46331
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$6)) {
46332
- this.i18n.registerContent(DEFAULT_NAMESPACE$6, SWITCH_ORG_MODAL_I18N);
46332
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$7)) {
46333
+ this.i18n.registerContent(DEFAULT_NAMESPACE$7, SWITCH_ORG_MODAL_I18N);
46333
46334
  }
46334
46335
  this.loadOrgs();
46335
46336
  }
@@ -46647,7 +46648,7 @@ const ACCOUNT_VIEW_I18N = {
46647
46648
  };
46648
46649
 
46649
46650
  addIcons({ peopleOutline, businessOutline, chevronForwardOutline, swapHorizontalOutline, mailOutline });
46650
- const DEFAULT_NAMESPACE$5 = 'Settings.Account';
46651
+ const DEFAULT_NAMESPACE$6 = 'Settings.Account';
46651
46652
  const DEFAULT_MANAGE_ORG_ROUTE = '/app/settings/organization';
46652
46653
  /**
46653
46654
  * `val-account-view` — vista Cuenta full-feature autocontenida (organism).
@@ -46688,7 +46689,7 @@ class AccountViewComponent {
46688
46689
  showNewOrgCta: merged.showNewOrgCta ?? true,
46689
46690
  showLogout: merged.showLogout ?? true,
46690
46691
  showDeleteAccount: merged.showDeleteAccount ?? true,
46691
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$5,
46692
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$6,
46692
46693
  manageOrgRoute: merged.manageOrgRoute ?? DEFAULT_MANAGE_ORG_ROUTE,
46693
46694
  onOrgSwitched: merged.onOrgSwitched,
46694
46695
  onOrgCreated: merged.onOrgCreated,
@@ -47413,7 +47414,7 @@ const EDIT_ORG_MODAL_I18N = {
47413
47414
  },
47414
47415
  };
47415
47416
 
47416
- const DEFAULT_NAMESPACE$4 = 'EditOrgModal';
47417
+ const DEFAULT_NAMESPACE$5 = 'EditOrgModal';
47417
47418
  /**
47418
47419
  * `val-edit-org-modal` — modal de edición de organización (nombre + descripción).
47419
47420
  * Promovido desde `showcase` bajo el proceso de ADR-021. Reusado por la vista de
@@ -47440,10 +47441,10 @@ class EditOrgModalComponent {
47440
47441
  /** Organización a editar (pasada por `componentProps`). */
47441
47442
  this.org = null;
47442
47443
  /** Namespace i18n con que la vista resuelve sus textos. */
47443
- this.i18nNamespace = DEFAULT_NAMESPACE$4;
47444
+ this.i18nNamespace = DEFAULT_NAMESPACE$5;
47444
47445
  this.saving = signal(false);
47445
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
47446
- this.i18n.registerContent(DEFAULT_NAMESPACE$4, EDIT_ORG_MODAL_I18N);
47446
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$5)) {
47447
+ this.i18n.registerContent(DEFAULT_NAMESPACE$5, EDIT_ORG_MODAL_I18N);
47447
47448
  }
47448
47449
  this.formMeta = signal(this.buildFormMeta());
47449
47450
  effect(() => {
@@ -47681,7 +47682,7 @@ const INVITE_MEMBER_MODAL_I18N = {
47681
47682
  },
47682
47683
  };
47683
47684
 
47684
- const DEFAULT_NAMESPACE$3 = 'InviteModal';
47685
+ const DEFAULT_NAMESPACE$4 = 'InviteModal';
47685
47686
  addIcons({ personOutline, mailOutline, closeOutline, checkmarkCircleOutline });
47686
47687
  /**
47687
47688
  * `val-invite-member-modal` — invitación de miembros a una organización.
@@ -47712,7 +47713,7 @@ class InviteMemberModalComponent {
47712
47713
  /** Ids de miembros ya existentes (excluidos de los resultados). */
47713
47714
  this.existingMemberIds = [];
47714
47715
  /** Namespace i18n con que la vista resuelve sus textos. */
47715
- this.i18nNamespace = DEFAULT_NAMESPACE$3;
47716
+ this.i18nNamespace = DEFAULT_NAMESPACE$4;
47716
47717
  this.query = signal('');
47717
47718
  this.searching = signal(false);
47718
47719
  this.searchResults = signal([]);
@@ -47759,8 +47760,8 @@ class InviteMemberModalComponent {
47759
47760
  state: this.sending() ? ComponentStates.WORKING : ComponentStates.ENABLED,
47760
47761
  }));
47761
47762
  this.searchSubject = new Subject();
47762
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
47763
- this.i18n.registerContent(DEFAULT_NAMESPACE$3, INVITE_MEMBER_MODAL_I18N);
47763
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$4)) {
47764
+ this.i18n.registerContent(DEFAULT_NAMESPACE$4, INVITE_MEMBER_MODAL_I18N);
47764
47765
  }
47765
47766
  this.loadRoles();
47766
47767
  this.roleSub = this.roleControl.valueChanges.subscribe(v => {
@@ -48249,7 +48250,7 @@ const MEMBER_DETAIL_MODAL_I18N = {
48249
48250
  },
48250
48251
  };
48251
48252
 
48252
- const DEFAULT_NAMESPACE$2 = 'Settings.MemberDetail';
48253
+ const DEFAULT_NAMESPACE$3 = 'Settings.MemberDetail';
48253
48254
  /**
48254
48255
  * `val-member-detail-modal` — detalle de miembro de organización: perfil, rol y
48255
48256
  * permisos. Promovido desde `showcase` bajo el proceso de ADR-021.
@@ -48286,7 +48287,7 @@ class MemberDetailModalComponent {
48286
48287
  { id: 'admin', name: 'admin' },
48287
48288
  ];
48288
48289
  /** Namespace i18n con que la vista resuelve sus textos. */
48289
- this.i18nNamespace = DEFAULT_NAMESPACE$2;
48290
+ this.i18nNamespace = DEFAULT_NAMESPACE$3;
48290
48291
  this.working = signal(false);
48291
48292
  this.currentRole = signal('');
48292
48293
  this.roleControl = new FormControl('');
@@ -48326,8 +48327,8 @@ class MemberDetailModalComponent {
48326
48327
  type: 'button',
48327
48328
  state: this.working() ? ComponentStates.WORKING : ComponentStates.ENABLED,
48328
48329
  }));
48329
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
48330
- this.i18n.registerContent(DEFAULT_NAMESPACE$2, MEMBER_DETAIL_MODAL_I18N);
48330
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$3)) {
48331
+ this.i18n.registerContent(DEFAULT_NAMESPACE$3, MEMBER_DETAIL_MODAL_I18N);
48331
48332
  }
48332
48333
  }
48333
48334
  ngOnInit() {
@@ -48664,7 +48665,7 @@ const TRANSFER_OWNERSHIP_MODAL_I18N = {
48664
48665
  },
48665
48666
  };
48666
48667
 
48667
- const DEFAULT_NAMESPACE$1 = 'Settings.TransferModal';
48668
+ const DEFAULT_NAMESPACE$2 = 'Settings.TransferModal';
48668
48669
  /**
48669
48670
  * `val-transfer-ownership-modal` — selector de miembro para transferir la
48670
48671
  * propiedad de la organización. Promovido desde `showcase` bajo el proceso de
@@ -48688,7 +48689,7 @@ class TransferOwnershipModalComponent {
48688
48689
  /** Lista de miembros transferibles (todos excepto el owner actual). */
48689
48690
  this.members = [];
48690
48691
  /** Namespace i18n con que la vista resuelve sus textos. */
48691
- this.i18nNamespace = DEFAULT_NAMESPACE$1;
48692
+ this.i18nNamespace = DEFAULT_NAMESPACE$2;
48692
48693
  this.query = signal('');
48693
48694
  this.selectedId = signal('');
48694
48695
  this.filtered = computed(() => {
@@ -48700,8 +48701,8 @@ class TransferOwnershipModalComponent {
48700
48701
  m.userId.toLowerCase().includes(q));
48701
48702
  });
48702
48703
  this.noResultsLabel = computed(() => `${this.t('noResults')} "${this.query()}"`);
48703
- if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$1)) {
48704
- this.i18n.registerContent(DEFAULT_NAMESPACE$1, TRANSFER_OWNERSHIP_MODAL_I18N);
48704
+ if (!this.i18n.hasNamespace(DEFAULT_NAMESPACE$2)) {
48705
+ this.i18n.registerContent(DEFAULT_NAMESPACE$2, TRANSFER_OWNERSHIP_MODAL_I18N);
48705
48706
  }
48706
48707
  }
48707
48708
  ngOnInit() {
@@ -49034,7 +49035,7 @@ const ORGANIZATION_VIEW_I18N = {
49034
49035
  },
49035
49036
  };
49036
49037
 
49037
- const DEFAULT_NAMESPACE = 'Settings.Organization';
49038
+ const DEFAULT_NAMESPACE$1 = 'Settings.Organization';
49038
49039
  const DEFAULT_VIEW_PERMISSIONS_ROUTE = '/app/settings/permissions';
49039
49040
  const DEFAULT_LEAVE_REDIRECT_ROUTE = '/app/settings/account';
49040
49041
  /**
@@ -49091,7 +49092,7 @@ class OrganizationViewComponent {
49091
49092
  showInvite: merged.showInvite ?? true,
49092
49093
  showTransferOwnership: merged.showTransferOwnership ?? true,
49093
49094
  showLeave: merged.showLeave ?? true,
49094
- i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
49095
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE$1,
49095
49096
  viewPermissionsRoute: merged.viewPermissionsRoute ?? DEFAULT_VIEW_PERMISSIONS_ROUTE,
49096
49097
  leaveRedirectRoute: merged.leaveRedirectRoute ?? DEFAULT_LEAVE_REDIRECT_ROUTE,
49097
49098
  onOrgEdited: merged.onOrgEdited,
@@ -50098,6 +50099,942 @@ function provideValtechOrganizationRoutes(opts) {
50098
50099
  ];
50099
50100
  }
50100
50101
 
50102
+ /**
50103
+ * Refreshable Stream
50104
+ *
50105
+ * Helper estándar para vistas que consumen datos de Firestore. Soporta DOS
50106
+ * modos — el caller decide cuál vía la `factory`:
50107
+ *
50108
+ * - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`.
50109
+ * Carga una vez y completa. Más barato — sin listener vivo.
50110
+ * - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza.
50111
+ * Solo donde importa (inbox de notificaciones, badges).
50112
+ *
50113
+ * En ambos modos la primera suscripción se gatea por `firebaseAuthReady`
50114
+ * (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta
50115
+ * antes de que `request.auth` esté disponible en las reglas de Firestore, lo
50116
+ * que cierra la ventana de `permission-denied` en cold start de PWA.
50117
+ *
50118
+ * El `retry` con backoff corto es solo red de seguridad para reconexiones
50119
+ * transitorias (no para cubrir cold start — eso lo cubre el gate de auth).
50120
+ */
50121
+ /**
50122
+ * Crea un stream refrescable a partir de una factory de Observable.
50123
+ *
50124
+ * DEBE llamarse en un injection context (field initializer o constructor) —
50125
+ * usa `toSignal`/`toObservable`/`inject` internamente.
50126
+ *
50127
+ * @param factory - Función que produce el Observable a consumir. El caller
50128
+ * decide el modo: `() => from(svc.getAllOnce())` (one-shot) o
50129
+ * `() => svc.getAll()` (real-time).
50130
+ * @param options - Configuración opcional (fallback, retry, gate).
50131
+ *
50132
+ * @example
50133
+ * ```typescript
50134
+ * // one-shot (recomendado por defecto)
50135
+ * private readonly stream = createRefreshableStream(
50136
+ * () => from(this.svc.getAllOnce()),
50137
+ * );
50138
+ *
50139
+ * // real-time (inbox)
50140
+ * private readonly stream = createRefreshableStream(
50141
+ * () => this.notifs.getAll(50),
50142
+ * );
50143
+ *
50144
+ * readonly items = this.stream.data;
50145
+ * readonly loading = this.stream.loading;
50146
+ *
50147
+ * ionViewWillEnter() {
50148
+ * this.pageRefresh.register(() => this.stream.reload());
50149
+ * }
50150
+ * ```
50151
+ */
50152
+ function createRefreshableStream(factory, options) {
50153
+ const injector = options?.injector ?? inject(Injector);
50154
+ const fallback = (options?.fallback ?? null);
50155
+ const retryCount = options?.retryCount ?? 4;
50156
+ const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
50157
+ const gateOnFirebaseAuth = options?.gateOnFirebaseAuth ?? true;
50158
+ const authGateTimeoutMs = options?.authGateTimeoutMs ?? 20000;
50159
+ const firstEmitTimeoutMs = options?.firstEmitTimeoutMs ?? 20000;
50160
+ // FirebaseService es opcional — sin él (apps sin Firebase) no se gatea.
50161
+ const firebase = gateOnFirebaseAuth ? injector.get(FirebaseService, null) : null;
50162
+ const _reload = signal(0);
50163
+ const _loading = signal(true);
50164
+ const _error = signal(false);
50165
+ // Gate de Firebase Auth: el stream no abre la primera suscripción hasta que
50166
+ // la sesión esté confirmada. `race` con un `timer` de seguridad — si la
50167
+ // sesión NUNCA se establece (token inválido, signInWithFirebase falló), el
50168
+ // gate igual se libera tras `authGateTimeoutMs` y la factory procede: si no
50169
+ // hay permiso terminará en estado `error`, jamás en skeleton infinito.
50170
+ // Tag los dos inputs del `race` para distinguir en logs cuál ganó: la sesión
50171
+ // de Firebase Auth (`auth-ready`) o el timeout de seguridad (`timeout`).
50172
+ const authGate$ = firebase
50173
+ ? race(firebase.firebaseAuthReady$.pipe(map$1(() => 'auth-ready')), timer(authGateTimeoutMs).pipe(map$1(() => 'timeout'))).pipe(take(1), tap(winner => {
50174
+ if (winner === 'timeout') {
50175
+ console.warn(`[RefreshStream] auth gate resolved by TIMEOUT after ${authGateTimeoutMs}ms ` +
50176
+ '(Firebase Auth session never confirmed)');
50177
+ }
50178
+ else {
50179
+ console.log('[RefreshStream] auth gate resolved by firebaseAuthReady$ (session ready)');
50180
+ }
50181
+ }))
50182
+ : of(true);
50183
+ const data = toSignal(toObservable(_reload, { injector }).pipe(tap(() => {
50184
+ _loading.set(true);
50185
+ _error.set(false);
50186
+ }),
50187
+ // Esperar a Firebase Auth listo antes de cada (re)suscripción. Como
50188
+ // `firebaseAuthReady$` completa tras la primera emisión, las reloads
50189
+ // posteriores resuelven el gate de inmediato (auth ya está listo).
50190
+ switchMap(() => authGate$.pipe(switchMap(() => factory().pipe(tap(() => {
50191
+ _loading.set(false);
50192
+ _error.set(false);
50193
+ console.log('[RefreshStream] factory emitted first value — loading=false');
50194
+ }),
50195
+ // Backoff corto exponencial acotado — solo red de seguridad para
50196
+ // reconexiones transitorias, NO para cubrir cold start.
50197
+ retry({
50198
+ count: retryCount,
50199
+ delay: (_err, attempt) => timer(Math.min(retryBaseDelayMs * 2 ** (attempt - 1), 4000)),
50200
+ }),
50201
+ // Techo absoluto para la PRIMERA emisión. Si la factory no emite
50202
+ // ni valor ni error (servicio nunca inicializado → `EMPTY` →
50203
+ // silencio), `timeout` dispara un error → `catchError` → estado
50204
+ // `error`. Nunca skeleton perpetuo. Va DESPUÉS de `retry` para
50205
+ // que cuente el tiempo total, no por intento.
50206
+ timeout({ first: firstEmitTimeoutMs }), catchError(err => {
50207
+ _loading.set(false);
50208
+ _error.set(true);
50209
+ console.error(`[RefreshStream] factory failed/timed out → error state: ${err instanceof Error ? err.message : String(err)}`);
50210
+ return of(fallback);
50211
+ })))))), { initialValue: null, injector });
50212
+ return {
50213
+ data,
50214
+ loading: _loading.asReadonly(),
50215
+ error: _error.asReadonly(),
50216
+ reload: () => _reload.update(n => n + 1),
50217
+ };
50218
+ }
50219
+
50220
+ /**
50221
+ * Defaults i18n (es/en) embebidos en `val-notifications-view`. Auto-registrados
50222
+ * en el constructor del componente si el consumer no proveyó el namespace
50223
+ * (`Notifications` por default). SOLO incluye las keys que la vista usa.
50224
+ *
50225
+ * Portado del override `Notifications` (showcase) bajo el proceso de ADR-021.
50226
+ */
50227
+ const NOTIFICATIONS_VIEW_I18N = {
50228
+ es: {
50229
+ pageTitle: 'Notificaciones',
50230
+ pageDescription: 'Tu bandeja de mensajes y actualizaciones',
50231
+ groupToday: 'Hoy',
50232
+ groupThisMonth: 'Este mes',
50233
+ groupEarlier: 'Anteriores',
50234
+ empty: 'Aún no tienes notificaciones',
50235
+ emptyHint: 'Activa las notificaciones para recibir actualizaciones, alertas y novedades importantes.',
50236
+ emptyCta: 'Ir a configuración',
50237
+ loadErrorTitle: 'No pudimos cargar tus notificaciones',
50238
+ loadErrorHint: 'Revisa tu conexión e inténtalo de nuevo. Tus notificaciones siguen guardadas.',
50239
+ offlineTitle: 'Sin conexión',
50240
+ offlineHint: 'No hay acceso a la red. Reconéctate e intenta de nuevo.',
50241
+ retryCta: 'Reintentar',
50242
+ pushOffTitle: 'No recibirás avisos',
50243
+ pushOffHint: 'Puedes leer tus mensajes aquí, pero las notificaciones push están desactivadas. Actívalas para recibir avisos en tu dispositivo.',
50244
+ pushOffCta: 'Activar notificaciones',
50245
+ loading: 'Cargando…',
50246
+ loadMore: 'Cargar más',
50247
+ seeMore: 'Ver más',
50248
+ seeLess: 'Ver menos',
50249
+ open: 'Ver',
50250
+ markAll: 'Marcar todas como leídas',
50251
+ unread: 'No leída',
50252
+ crossApp: 'Desde {app}',
50253
+ timeNow: 'ahora',
50254
+ timeMinutes: 'hace {n} min',
50255
+ timeHours: 'hace {n} h',
50256
+ timeDays: 'hace {n} d',
50257
+ markedAllSuccess: 'Notificaciones marcadas como leídas.',
50258
+ markedAllError: 'No se pudo marcar todas como leídas.',
50259
+ openError: 'No se pudo abrir la notificación.',
50260
+ },
50261
+ en: {
50262
+ pageTitle: 'Notifications',
50263
+ pageDescription: 'Your inbox of messages and updates',
50264
+ groupToday: 'Today',
50265
+ groupThisMonth: 'This month',
50266
+ groupEarlier: 'Earlier',
50267
+ empty: 'No notifications yet',
50268
+ emptyHint: 'Enable notifications to receive updates, alerts and important news.',
50269
+ emptyCta: 'Go to settings',
50270
+ loadErrorTitle: "We couldn't load your notifications",
50271
+ loadErrorHint: 'Check your connection and try again. Your notifications are still saved.',
50272
+ offlineTitle: 'No connection',
50273
+ offlineHint: 'No network access. Reconnect and try again.',
50274
+ retryCta: 'Retry',
50275
+ pushOffTitle: "You won't get alerts",
50276
+ pushOffHint: 'You can read your messages here, but push notifications are off. Turn them on to get alerts on your device.',
50277
+ pushOffCta: 'Enable notifications',
50278
+ loading: 'Loading…',
50279
+ loadMore: 'Load more',
50280
+ seeMore: 'See more',
50281
+ seeLess: 'See less',
50282
+ open: 'Open',
50283
+ markAll: 'Mark all as read',
50284
+ unread: 'Unread',
50285
+ crossApp: 'From {app}',
50286
+ timeNow: 'now',
50287
+ timeMinutes: '{n} min ago',
50288
+ timeHours: '{n} h ago',
50289
+ timeDays: '{n} d ago',
50290
+ markedAllSuccess: 'Notifications marked as read.',
50291
+ markedAllError: 'Could not mark all as read.',
50292
+ openError: 'Could not open notification.',
50293
+ },
50294
+ };
50295
+
50296
+ addIcons({
50297
+ alarmOutline,
50298
+ chatboxEllipsesOutline,
50299
+ chatbubbleOutline,
50300
+ checkmarkCircleOutline,
50301
+ checkmarkDoneOutline,
50302
+ cloudOfflineOutline,
50303
+ cogOutline,
50304
+ documentTextOutline,
50305
+ notificationsOffOutline,
50306
+ notificationsOutline,
50307
+ openOutline,
50308
+ personOutline,
50309
+ });
50310
+ const DEFAULT_NAMESPACE = 'Notifications';
50311
+ const DEFAULT_SETTINGS_PATH = '/app/settings/notifications';
50312
+ const DEFAULT_PAGE_SIZE = 10;
50313
+ const DEFAULT_MAX_LOAD = 50;
50314
+ /**
50315
+ * `val-notifications-view` — inbox de notificaciones full-feature autocontenido
50316
+ * (organism). Promovido desde `showcase` bajo el proceso de ADR-021.
50317
+ *
50318
+ * Separado de `/app/settings/notifications` (que controla permiso FCM + master
50319
+ * toggle). Aquí el user lee/abre/marca-leídas sus mensajes.
50320
+ *
50321
+ * - **Stream real-time** vía `NotificationsService.getAll(maxLoad)` envuelto en
50322
+ * `createRefreshableStream` (gateado por `firebaseAuthReady`, retry corto,
50323
+ * `firstEmitTimeoutMs: 15000`). Estados: loading (skeleton), error
50324
+ * (`val-empty-state` + reintentar), empty (`val-empty-state` + CTA), data
50325
+ * (lista agrupada Hoy / Este mes / Anteriores).
50326
+ * - **Abrir** una notif → `NotificationActionService.open()` (markRead +
50327
+ * switch-org + navegar/handoff cross-app). Hook `onNotificationOpen` opcional.
50328
+ * - **Marcar todas como leídas** (gateado por `enableMarkAll`).
50329
+ * - **CTA push-off** cuando el permiso del navegador no está concedido (gateado
50330
+ * por `showPushOffCta`); navega a `settingsNotificationsPath`.
50331
+ * - **Paginación soft** — slice local (`pageSize`, +`pageSize` por click) sobre el
50332
+ * cache del stream; el tope de fetch es `maxLoad`.
50333
+ * - **i18n auto-registrado** es/en bajo `i18nNamespace` (default `Notifications`).
50334
+ *
50335
+ * NO renderiza ion-content — vive dentro de val-page-wrapper. `Router` y
50336
+ * `ActivatedRoute` se inyectan `{ optional: true }` → funciona sin router
50337
+ * (embebida / Storybook). El forward-compat icon resolver lee `notif.payload.icon`
50338
+ * si existe (`{ type, value, color? }`), cae a un mapa por `notif.type`, luego a
50339
+ * `notifications-outline`.
50340
+ */
50341
+ class NotificationsViewComponent {
50342
+ /** Namespace i18n resuelto (capturado para llamadas no-reactivas). */
50343
+ get ns() {
50344
+ return this.resolvedConfig().i18nNamespace;
50345
+ }
50346
+ /** Paso de paginación soft (revela +N por "cargar más"). */
50347
+ get pageStep() {
50348
+ return this.resolvedConfig().pageSize;
50349
+ }
50350
+ constructor() {
50351
+ this.nav = inject(NavigationService);
50352
+ this.i18n = inject(I18nService);
50353
+ this.notifs = inject(NotificationsService);
50354
+ this.action = inject(NotificationActionService);
50355
+ this.toast = inject(ToastService);
50356
+ this.errors = inject(ValtechErrorService);
50357
+ this.auth = inject(AuthService);
50358
+ this.pageRefresh = inject(PageRefreshService);
50359
+ // `ActivatedRoute` opcional → la vista funciona embebida / en Storybook sin
50360
+ // router (solo lo usa para leer el route data de config como fallback). La
50361
+ // navegación va por `NavigationService` (wrapper opcional sobre `Router`).
50362
+ this.route = inject(ActivatedRoute, { optional: true });
50363
+ this.resolvedConfig = computed(() => {
50364
+ const fromRoute = (this.route?.snapshot.data['notificationsConfig'] ?? {});
50365
+ const merged = { ...fromRoute, ...(this.config ?? {}) };
50366
+ return {
50367
+ showPushOffCta: merged.showPushOffCta ?? true,
50368
+ enableMarkAll: merged.enableMarkAll ?? true,
50369
+ settingsNotificationsPath: merged.settingsNotificationsPath ?? DEFAULT_SETTINGS_PATH,
50370
+ i18nNamespace: merged.i18nNamespace ?? DEFAULT_NAMESPACE,
50371
+ pageSize: merged.pageSize ?? DEFAULT_PAGE_SIZE,
50372
+ maxLoad: merged.maxLoad ?? DEFAULT_MAX_LOAD,
50373
+ onNotificationOpen: merged.onNotificationOpen,
50374
+ };
50375
+ });
50376
+ this._expanded = signal(new Set());
50377
+ this._pageSize = signal(this.resolvedConfig().pageSize);
50378
+ this._bulkBusy = signal(false);
50379
+ this._loadingMore = signal(false);
50380
+ /**
50381
+ * Push activado = permiso del navegador en `granted`. Si NO está activado,
50382
+ * el user puede leer el inbox (Firestore) pero NO recibirá push en su
50383
+ * dispositivo → mostramos una card que lo invita a ir a configuración.
50384
+ * Se evalúa en cada montaje de la vista.
50385
+ */
50386
+ this.pushEnabled = signal(this.auth.getNotificationPermissionState() === 'granted');
50387
+ /**
50388
+ * Stream firestore real-time vía el helper estándar `createRefreshableStream`
50389
+ * (modo real-time: factory devuelve un listener vivo `notifs.getAll(maxLoad)`).
50390
+ *
50391
+ * El helper gatea la primera suscripción por `firebaseAuthReady`, aplica un
50392
+ * `retry` corto con backoff y, tras agotar reintentos, emite el fallback (`[]`)
50393
+ * y marca `error` — estado de error en UI, no skeleton infinito. `reload()`
50394
+ * re-suscribe el stream completo (lo usa el pull-to-refresh).
50395
+ */
50396
+ this._stream = createRefreshableStream(() => this.notifs.getAll(this.resolvedConfig().maxLoad), {
50397
+ fallback: [],
50398
+ // `notifs.getAll()` YA gatea internamente por `firebaseAuthReady`. Si el
50399
+ // stream también gateara, serían DOS esperas de auth apiladas.
50400
+ gateOnFirebaseAuth: false,
50401
+ // Cold start de PWA en iOS puede pasar de 8s en redes lentas; con 15s
50402
+ // evitamos el falso estado de error.
50403
+ firstEmitTimeoutMs: 15000,
50404
+ });
50405
+ /** True si el stream falló tras agotar reintentos → muestra estado de error. */
50406
+ this.streamError = this._stream.error;
50407
+ this.loading = computed(() => this._stream.data() === null);
50408
+ /**
50409
+ * Notifs reales mostradas. Filtramos docs sin título Y sin body — son ruido
50410
+ * (típicamente sentinel/system writes pre-schema con title/body opcionales).
50411
+ */
50412
+ this.allNotifs = computed(() => (this._stream.data() ?? []).filter(n => !!(n.title && n.title.trim()) || !!(n.body && n.body.trim())));
50413
+ this.hasMore = computed(() => this.allNotifs().length > this._pageSize());
50414
+ this.showMarkAll = computed(() => this.resolvedConfig().enableMarkAll && !this.loading() && this.allNotifs().some(n => !n.isRead));
50415
+ /** Sub-conjunto paginado, agrupado por período. */
50416
+ this.groups = computed(() => {
50417
+ const items = this.allNotifs().slice(0, this._pageSize());
50418
+ const now = new Date();
50419
+ const buckets = {
50420
+ today: [],
50421
+ thisMonth: [],
50422
+ earlier: [],
50423
+ };
50424
+ for (const n of items) {
50425
+ buckets[this.bucketOf(n, now)].push(n);
50426
+ }
50427
+ const out = [];
50428
+ if (buckets.today.length) {
50429
+ out.push({ key: 'today', label: this.tt('groupToday'), items: buckets.today });
50430
+ }
50431
+ if (buckets.thisMonth.length) {
50432
+ out.push({
50433
+ key: 'thisMonth',
50434
+ label: this.tt('groupThisMonth'),
50435
+ items: buckets.thisMonth,
50436
+ });
50437
+ }
50438
+ if (buckets.earlier.length) {
50439
+ out.push({ key: 'earlier', label: this.tt('groupEarlier'), items: buckets.earlier });
50440
+ }
50441
+ return out;
50442
+ });
50443
+ this.pageTitle = computed(() => this.tt('pageTitle'));
50444
+ this.pageDescription = computed(() => this.tt('pageDescription'));
50445
+ // CTA "activa las notificaciones push" — visible cuando pushEnabled() es false.
50446
+ this.ctaTitle = computed(() => this.tt('pushOffTitle'));
50447
+ this.ctaHint = computed(() => this.tt('pushOffHint'));
50448
+ this.ctaButtonProps = computed(() => ({
50449
+ text: this.tt('pushOffCta'),
50450
+ color: 'dark',
50451
+ fill: 'outline',
50452
+ size: 'small',
50453
+ shape: 'round',
50454
+ type: 'button',
50455
+ }));
50456
+ /**
50457
+ * Estado de error de carga: convierte el error del stream en
50458
+ * `EmptyStateMetadata` via el helper canónico `createErrorStateProps`.
50459
+ * El helper decide variante (`offline` vs `error`) por `isNetwork`.
50460
+ * Null cuando el stream no ha fallado.
50461
+ */
50462
+ this.errorState = computed(() => {
50463
+ this.i18n.lang();
50464
+ const err = this.streamError();
50465
+ if (!err)
50466
+ return null;
50467
+ return createErrorStateProps(err, {
50468
+ title: {
50469
+ offline: this.tt('offlineTitle'),
50470
+ error: this.tt('loadErrorTitle'),
50471
+ },
50472
+ description: {
50473
+ offline: this.tt('offlineHint'),
50474
+ error: interpretError(err).message || this.tt('loadErrorHint'),
50475
+ },
50476
+ retryLabel: this.tt('retryCta'),
50477
+ onRetry: () => this._stream.reload(),
50478
+ retrying: this.loading(),
50479
+ });
50480
+ });
50481
+ /**
50482
+ * Estado vacío (lista real = 0 notificaciones, sin error de carga).
50483
+ * Se construye manualmente con `variant: 'empty'` — sin helper de error.
50484
+ */
50485
+ this.emptyStateProps = computed(() => {
50486
+ this.i18n.lang();
50487
+ return {
50488
+ variant: 'empty',
50489
+ icon: 'notifications-off-outline',
50490
+ title: this.tt('empty'),
50491
+ description: this.tt('emptyHint'),
50492
+ action: {
50493
+ label: this.tt('emptyCta'),
50494
+ handler: () => this.onGoToSettings(),
50495
+ },
50496
+ };
50497
+ });
50498
+ this.seeMore = computed(() => this.tt('seeMore'));
50499
+ this.seeLess = computed(() => this.tt('seeLess'));
50500
+ this.markAllButtonProps = computed(() => ({
50501
+ text: this.tt('markAll'),
50502
+ color: 'dark',
50503
+ fill: 'outline',
50504
+ size: 'small',
50505
+ shape: 'round',
50506
+ type: 'button',
50507
+ icon: { name: 'checkmark-done-outline', slot: 'start' },
50508
+ state: this._bulkBusy() ? ComponentStates.WORKING : ComponentStates.ENABLED,
50509
+ }));
50510
+ this.loadMoreButtonProps = computed(() => ({
50511
+ text: this.tt('loadMore'),
50512
+ color: 'dark',
50513
+ fill: 'clear',
50514
+ size: 'default',
50515
+ shape: 'round',
50516
+ type: 'button',
50517
+ state: this._loadingMore() ? ComponentStates.WORKING : ComponentStates.ENABLED,
50518
+ }));
50519
+ const ns = this.ns;
50520
+ if (!this.i18n.hasNamespace(ns)) {
50521
+ this.i18n.registerContent(ns, NOTIFICATIONS_VIEW_I18N);
50522
+ }
50523
+ this.nav.setBackHeader('pageTitle', ns, { withMenu: true });
50524
+ // Re-aplicar header en cambio de idioma.
50525
+ effect(() => {
50526
+ this.i18n.lang();
50527
+ this.nav.setBackHeader('pageTitle', this.ns, { withMenu: true });
50528
+ }, { allowSignalWrites: true });
50529
+ }
50530
+ /**
50531
+ * Registra el pull-to-refresh estándar. Usamos `ngOnInit`/`ngOnDestroy`
50532
+ * (NO `ionViewWillEnter`) porque `val-page-wrapper` usa `<router-outlet>`
50533
+ * plano — los hooks `ionView*` solo disparan dentro de `<ion-router-outlet>`.
50534
+ */
50535
+ ngOnInit() {
50536
+ this.pageRefresh.register(() => this.reload());
50537
+ }
50538
+ /** Quita el handler al destruir la vista — el refresher desaparece. */
50539
+ ngOnDestroy() {
50540
+ this.pageRefresh.unregister();
50541
+ }
50542
+ /**
50543
+ * Refresh manual: re-suscribe el stream Firestore (listener nuevo) vía el
50544
+ * helper. Resuelve tras un margen breve — el stream es real-time y re-emite
50545
+ * en milisegundos; el delay sólo mantiene el spinner visible lo justo.
50546
+ */
50547
+ async reload() {
50548
+ this._stream.reload();
50549
+ await new Promise(r => setTimeout(r, 700));
50550
+ }
50551
+ // ---------------------------------------------------------------------------
50552
+ // Per-item helpers (puros — usables desde template).
50553
+ // ---------------------------------------------------------------------------
50554
+ openButtonProps() {
50555
+ return {
50556
+ text: this.tt('open'),
50557
+ color: 'dark',
50558
+ fill: 'clear',
50559
+ size: 'small',
50560
+ shape: 'round',
50561
+ type: 'button',
50562
+ icon: { name: 'open-outline', slot: 'end' },
50563
+ };
50564
+ }
50565
+ isExpanded(id) {
50566
+ return this._expanded().has(id);
50567
+ }
50568
+ toggleExpand(id) {
50569
+ const next = new Set(this._expanded());
50570
+ if (next.has(id))
50571
+ next.delete(id);
50572
+ else
50573
+ next.add(id);
50574
+ this._expanded.set(next);
50575
+ }
50576
+ isLong(body) {
50577
+ if (!body)
50578
+ return false;
50579
+ return body.length > 110 || body.includes('\n');
50580
+ }
50581
+ /**
50582
+ * Icon resolver con orden de fallback:
50583
+ * 1. notif.payload.icon (forward-compat, sin breaking change en schema)
50584
+ * 2. mapa por notif.type
50585
+ * 3. default notifications-outline
50586
+ */
50587
+ resolveIcon(n) {
50588
+ const hint = n.payload?.['icon'];
50589
+ if (hint?.value) {
50590
+ if (hint.type === 'image' || hint.type === 'avatar') {
50591
+ return { kind: hint.type, src: hint.value };
50592
+ }
50593
+ // Ignoramos hint.color — todos los iconos van en dark por convención UX.
50594
+ return { kind: 'ionicon', name: hint.value, color: 'dark' };
50595
+ }
50596
+ const t = n.type;
50597
+ const name = (() => {
50598
+ switch (t) {
50599
+ case 'approval':
50600
+ return 'checkmark-circle-outline';
50601
+ case 'chat':
50602
+ case 'chatbot':
50603
+ return 'chatbubble-outline';
50604
+ case 'comment':
50605
+ return 'chatbox-ellipses-outline';
50606
+ case 'reminder':
50607
+ return 'alarm-outline';
50608
+ case 'system':
50609
+ return 'cog-outline';
50610
+ case 'user':
50611
+ case 'mention':
50612
+ return 'person-outline';
50613
+ case 'document':
50614
+ return 'document-text-outline';
50615
+ default:
50616
+ return 'notifications-outline';
50617
+ }
50618
+ })();
50619
+ return { kind: 'ionicon', name, color: 'dark' };
50620
+ }
50621
+ ioniconName(n) {
50622
+ const r = this.resolveIcon(n);
50623
+ return r.kind === 'ionicon' ? r.name : '';
50624
+ }
50625
+ ioniconColor(n) {
50626
+ const r = this.resolveIcon(n);
50627
+ return r.kind === 'ionicon' ? r.color : undefined;
50628
+ }
50629
+ imageSrc(n) {
50630
+ const r = this.resolveIcon(n);
50631
+ return r.kind === 'image' || r.kind === 'avatar' ? r.src : '';
50632
+ }
50633
+ ionColor(token) {
50634
+ if (!token)
50635
+ return 'var(--ion-color-primary)';
50636
+ return `var(--ion-color-${token})`;
50637
+ }
50638
+ relativeTime(n) {
50639
+ const d = n.createdAt instanceof Date ? n.createdAt : null;
50640
+ if (!d)
50641
+ return '';
50642
+ const diffMs = Date.now() - d.getTime();
50643
+ const min = Math.floor(diffMs / 60000);
50644
+ if (min < 1)
50645
+ return this.tt('timeNow');
50646
+ if (min < 60)
50647
+ return this.tt('timeMinutes').replace('{n}', String(min));
50648
+ const h = Math.floor(min / 60);
50649
+ if (h < 24)
50650
+ return this.tt('timeHours').replace('{n}', String(h));
50651
+ const days = Math.floor(h / 24);
50652
+ return this.tt('timeDays').replace('{n}', String(days));
50653
+ }
50654
+ /** Devuelve label "Desde {appName}" si la notif viene de otra app, sino null. */
50655
+ showCrossAppBadge(n) {
50656
+ const currentApp = 'showcase';
50657
+ if (!n.appId || n.appId === currentApp)
50658
+ return null;
50659
+ return this.tt('crossApp').replace('{app}', n.appId);
50660
+ }
50661
+ // ---------------------------------------------------------------------------
50662
+ // Actions
50663
+ // ---------------------------------------------------------------------------
50664
+ async onOpen(n) {
50665
+ try {
50666
+ const result = await this.action.open(n);
50667
+ this.resolvedConfig().onNotificationOpen?.(n, result);
50668
+ }
50669
+ catch (err) {
50670
+ this.errors.handle(err, {
50671
+ context: 'notifications.open',
50672
+ fallbackKey: 'openError',
50673
+ i18nNamespace: this.ns,
50674
+ });
50675
+ }
50676
+ }
50677
+ async onMarkAll() {
50678
+ if (this._bulkBusy())
50679
+ return;
50680
+ this._bulkBusy.set(true);
50681
+ try {
50682
+ await this.notifs.markAllAsRead();
50683
+ this.toast.show({
50684
+ message: this.tt('markedAllSuccess'),
50685
+ color: 'dark',
50686
+ duration: 2500,
50687
+ position: 'top',
50688
+ });
50689
+ }
50690
+ catch (err) {
50691
+ this.errors.handle(err, {
50692
+ context: 'notifications.markAll',
50693
+ fallbackKey: 'markedAllError',
50694
+ i18nNamespace: this.ns,
50695
+ });
50696
+ }
50697
+ finally {
50698
+ this._bulkBusy.set(false);
50699
+ }
50700
+ }
50701
+ onLoadMore() {
50702
+ if (this._loadingMore())
50703
+ return;
50704
+ this._loadingMore.set(true);
50705
+ // Slice viene del cache local — no hay fetch real, pero un breve spinner
50706
+ // confirma al user que el click registró y suaviza la aparición de items.
50707
+ setTimeout(() => {
50708
+ this._pageSize.update(n => n + this.pageStep);
50709
+ this._loadingMore.set(false);
50710
+ }, 250);
50711
+ }
50712
+ onGoToSettings() {
50713
+ this.nav.navigateByUrl(this.resolvedConfig().settingsNotificationsPath);
50714
+ }
50715
+ // ---------------------------------------------------------------------------
50716
+ // Grouping helpers
50717
+ // ---------------------------------------------------------------------------
50718
+ bucketOf(n, now) {
50719
+ const d = n.createdAt instanceof Date ? n.createdAt : null;
50720
+ if (!d)
50721
+ return 'earlier';
50722
+ if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate()) {
50723
+ return 'today';
50724
+ }
50725
+ if (d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth()) {
50726
+ return 'thisMonth';
50727
+ }
50728
+ return 'earlier';
50729
+ }
50730
+ tt(key) {
50731
+ return this.i18n.t(key, this.ns);
50732
+ }
50733
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationsViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
50734
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: NotificationsViewComponent, isStandalone: true, selector: "val-notifications-view", inputs: { config: "config" }, ngImport: i0, template: `
50735
+ <div class="page">
50736
+ <header class="page-header">
50737
+ <val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
50738
+ <val-title
50739
+ [props]="{
50740
+ size: 'large',
50741
+ color: 'dark',
50742
+ bold: false,
50743
+ content: pageDescription(),
50744
+ }"
50745
+ />
50746
+ @if (showMarkAll()) {
50747
+ <div class="row-actions">
50748
+ <val-button [props]="markAllButtonProps()" (click)="onMarkAll()" />
50749
+ </div>
50750
+ }
50751
+ </header>
50752
+
50753
+ @if (resolvedConfig().showPushOffCta && !pushEnabled()) {
50754
+ <section class="notif-cta">
50755
+ <ion-icon class="notif-cta__icon" name="notifications-off-outline" aria-hidden="true" />
50756
+ <div class="notif-cta__body">
50757
+ <val-text
50758
+ [props]="{
50759
+ content: ctaTitle(),
50760
+ size: 'medium',
50761
+ color: 'dark',
50762
+ bold: true,
50763
+ }"
50764
+ />
50765
+ <val-text
50766
+ [props]="{
50767
+ content: ctaHint(),
50768
+ size: 'small',
50769
+ color: 'medium',
50770
+ bold: false,
50771
+ }"
50772
+ />
50773
+ <div class="row-actions">
50774
+ <val-button [props]="ctaButtonProps()" (click)="onGoToSettings()" />
50775
+ </div>
50776
+ </div>
50777
+ </section>
50778
+ }
50779
+
50780
+ @if (loading()) {
50781
+ <val-skeleton-layout [props]="{ preset: 'list', rows: 6 }" />
50782
+ } @else if (errorState()) {
50783
+ <val-empty-state [props]="errorState()!" />
50784
+ } @else if (allNotifs().length === 0) {
50785
+ <val-empty-state [props]="emptyStateProps()" />
50786
+ } @else {
50787
+ @for (group of groups(); track group.key) {
50788
+ <section class="group">
50789
+ <val-title
50790
+ [props]="{
50791
+ size: 'medium',
50792
+ color: 'dark',
50793
+ bold: true,
50794
+ content: group.label,
50795
+ }"
50796
+ />
50797
+ <ul class="notif-list">
50798
+ @for (n of group.items; track n.id) {
50799
+ <li class="notif" [class.notif--unread]="!n.isRead">
50800
+ <div class="notif__icon">
50801
+ @switch (resolveIcon(n).kind) {
50802
+ @case ('ionicon') {
50803
+ <ion-icon
50804
+ [name]="ioniconName(n)"
50805
+ [style.color]="ionColor(ioniconColor(n))"
50806
+ aria-hidden="true"
50807
+ />
50808
+ }
50809
+ @case ('image') {
50810
+ <img [src]="imageSrc(n)" alt="" loading="lazy" />
50811
+ }
50812
+ @case ('avatar') {
50813
+ <img class="notif__avatar" [src]="imageSrc(n)" alt="" loading="lazy" />
50814
+ }
50815
+ }
50816
+ </div>
50817
+
50818
+ <div class="notif__body">
50819
+ <div class="notif__head">
50820
+ <val-text
50821
+ [props]="{
50822
+ content: n.title || '—',
50823
+ size: 'medium',
50824
+ color: 'dark',
50825
+ bold: true,
50826
+ }"
50827
+ />
50828
+ <span class="notif__time">{{ relativeTime(n) }}</span>
50829
+ </div>
50830
+ @if (n.body) {
50831
+ <p class="notif__text" [class.notif__text--clamped]="!isExpanded(n.id) && isLong(n.body)">
50832
+ {{ n.body }}
50833
+ </p>
50834
+ @if (isLong(n.body)) {
50835
+ <button class="notif__expand" type="button" (click)="toggleExpand(n.id)">
50836
+ {{ isExpanded(n.id) ? seeLess() : seeMore() }}
50837
+ </button>
50838
+ }
50839
+ }
50840
+ @if (showCrossAppBadge(n); as appName) {
50841
+ <span class="notif__app">{{ appName }}</span>
50842
+ }
50843
+ @if (n.actionRoute) {
50844
+ <div class="row-actions row-actions--end">
50845
+ <val-button [props]="openButtonProps()" (click)="onOpen(n)" />
50846
+ </div>
50847
+ }
50848
+ </div>
50849
+ </li>
50850
+ }
50851
+ </ul>
50852
+ </section>
50853
+ }
50854
+
50855
+ @if (hasMore()) {
50856
+ <div class="row-actions row-actions--center">
50857
+ <val-button [props]="loadMoreButtonProps()" (click)="onLoadMore()" />
50858
+ </div>
50859
+ }
50860
+ }
50861
+ </div>
50862
+ `, isInline: true, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:20px}.row-actions--end{display:flex;justify-content:flex-end;margin-top:8px}.notif-cta{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;margin-bottom:16px;border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));border-radius:14px;background:var(--ion-color-light)}.notif-cta__icon{font-size:24px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0;margin-top:2px}.notif-cta__body{display:flex;flex-direction:column;gap:2px}.group{padding:12px 0}.group+.group{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.notif-list{list-style:none;padding:0;margin:12px 0 0;display:flex;flex-direction:column;gap:10px}.notif{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:12px;border-radius:12px;background:transparent;transition:background .12s ease}.notif--unread{background:var(--ion-color-light, rgba(0, 0, 0, .04))}.notif__icon{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--ion-color-light, rgba(0, 0, 0, .06));overflow:hidden}.notif__icon ion-icon{font-size:22px}.notif__icon img{width:100%;height:100%;object-fit:cover}.notif__avatar{border-radius:50%}.notif__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif__head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.notif__time{font-size:12px;color:var(--ion-color-medium, #92949c);flex-shrink:0}.notif__text{margin:0;font-size:14px;line-height:1.4;color:var(--ion-color-dark, #1a1a1a);white-space:pre-wrap}.notif__text--clamped{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notif__expand{background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-dark, #1a1a1a);cursor:pointer;align-self:flex-start;text-decoration:underline}.notif__app{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--ion-color-medium, #92949c);margin-top:2px}\n"], dependencies: [{ kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: DisplayComponent, selector: "val-display", inputs: ["props"] }, { kind: "component", type: TitleComponent, selector: "val-title", inputs: ["props"] }, { kind: "component", type: TextComponent, selector: "val-text", inputs: ["props"] }, { kind: "component", type: ButtonComponent, selector: "val-button", inputs: ["preset", "props"], outputs: ["onClick"] }, { kind: "component", type: SkeletonLayoutComponent, selector: "val-skeleton-layout", inputs: ["props"] }, { kind: "component", type: EmptyStateComponent, selector: "val-empty-state", inputs: ["props"] }] }); }
50863
+ }
50864
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: NotificationsViewComponent, decorators: [{
50865
+ type: Component,
50866
+ args: [{ selector: 'val-notifications-view', standalone: true, imports: [
50867
+ IonIcon,
50868
+ DisplayComponent,
50869
+ TitleComponent,
50870
+ TextComponent,
50871
+ ButtonComponent,
50872
+ SkeletonLayoutComponent,
50873
+ EmptyStateComponent,
50874
+ ], template: `
50875
+ <div class="page">
50876
+ <header class="page-header">
50877
+ <val-display [props]="{ size: 'small', color: 'dark', content: pageTitle() }" />
50878
+ <val-title
50879
+ [props]="{
50880
+ size: 'large',
50881
+ color: 'dark',
50882
+ bold: false,
50883
+ content: pageDescription(),
50884
+ }"
50885
+ />
50886
+ @if (showMarkAll()) {
50887
+ <div class="row-actions">
50888
+ <val-button [props]="markAllButtonProps()" (click)="onMarkAll()" />
50889
+ </div>
50890
+ }
50891
+ </header>
50892
+
50893
+ @if (resolvedConfig().showPushOffCta && !pushEnabled()) {
50894
+ <section class="notif-cta">
50895
+ <ion-icon class="notif-cta__icon" name="notifications-off-outline" aria-hidden="true" />
50896
+ <div class="notif-cta__body">
50897
+ <val-text
50898
+ [props]="{
50899
+ content: ctaTitle(),
50900
+ size: 'medium',
50901
+ color: 'dark',
50902
+ bold: true,
50903
+ }"
50904
+ />
50905
+ <val-text
50906
+ [props]="{
50907
+ content: ctaHint(),
50908
+ size: 'small',
50909
+ color: 'medium',
50910
+ bold: false,
50911
+ }"
50912
+ />
50913
+ <div class="row-actions">
50914
+ <val-button [props]="ctaButtonProps()" (click)="onGoToSettings()" />
50915
+ </div>
50916
+ </div>
50917
+ </section>
50918
+ }
50919
+
50920
+ @if (loading()) {
50921
+ <val-skeleton-layout [props]="{ preset: 'list', rows: 6 }" />
50922
+ } @else if (errorState()) {
50923
+ <val-empty-state [props]="errorState()!" />
50924
+ } @else if (allNotifs().length === 0) {
50925
+ <val-empty-state [props]="emptyStateProps()" />
50926
+ } @else {
50927
+ @for (group of groups(); track group.key) {
50928
+ <section class="group">
50929
+ <val-title
50930
+ [props]="{
50931
+ size: 'medium',
50932
+ color: 'dark',
50933
+ bold: true,
50934
+ content: group.label,
50935
+ }"
50936
+ />
50937
+ <ul class="notif-list">
50938
+ @for (n of group.items; track n.id) {
50939
+ <li class="notif" [class.notif--unread]="!n.isRead">
50940
+ <div class="notif__icon">
50941
+ @switch (resolveIcon(n).kind) {
50942
+ @case ('ionicon') {
50943
+ <ion-icon
50944
+ [name]="ioniconName(n)"
50945
+ [style.color]="ionColor(ioniconColor(n))"
50946
+ aria-hidden="true"
50947
+ />
50948
+ }
50949
+ @case ('image') {
50950
+ <img [src]="imageSrc(n)" alt="" loading="lazy" />
50951
+ }
50952
+ @case ('avatar') {
50953
+ <img class="notif__avatar" [src]="imageSrc(n)" alt="" loading="lazy" />
50954
+ }
50955
+ }
50956
+ </div>
50957
+
50958
+ <div class="notif__body">
50959
+ <div class="notif__head">
50960
+ <val-text
50961
+ [props]="{
50962
+ content: n.title || '—',
50963
+ size: 'medium',
50964
+ color: 'dark',
50965
+ bold: true,
50966
+ }"
50967
+ />
50968
+ <span class="notif__time">{{ relativeTime(n) }}</span>
50969
+ </div>
50970
+ @if (n.body) {
50971
+ <p class="notif__text" [class.notif__text--clamped]="!isExpanded(n.id) && isLong(n.body)">
50972
+ {{ n.body }}
50973
+ </p>
50974
+ @if (isLong(n.body)) {
50975
+ <button class="notif__expand" type="button" (click)="toggleExpand(n.id)">
50976
+ {{ isExpanded(n.id) ? seeLess() : seeMore() }}
50977
+ </button>
50978
+ }
50979
+ }
50980
+ @if (showCrossAppBadge(n); as appName) {
50981
+ <span class="notif__app">{{ appName }}</span>
50982
+ }
50983
+ @if (n.actionRoute) {
50984
+ <div class="row-actions row-actions--end">
50985
+ <val-button [props]="openButtonProps()" (click)="onOpen(n)" />
50986
+ </div>
50987
+ }
50988
+ </div>
50989
+ </li>
50990
+ }
50991
+ </ul>
50992
+ </section>
50993
+ }
50994
+
50995
+ @if (hasMore()) {
50996
+ <div class="row-actions row-actions--center">
50997
+ <val-button [props]="loadMoreButtonProps()" (click)="onLoadMore()" />
50998
+ </div>
50999
+ }
51000
+ }
51001
+ </div>
51002
+ `, styles: [".page{padding:16px 0;max-width:720px;margin:0 auto}.page-header{margin-bottom:16px}.row-actions{margin-top:12px}.row-actions--center{display:flex;justify-content:center;margin-top:20px}.row-actions--end{display:flex;justify-content:flex-end;margin-top:8px}.notif-cta{display:flex;gap:12px;align-items:flex-start;padding:14px 16px;margin-bottom:16px;border:1px solid var(--ion-color-light-shade, rgba(0, 0, 0, .08));border-radius:14px;background:var(--ion-color-light)}.notif-cta__icon{font-size:24px;color:var(--ion-color-dark, #1a1a1a);flex-shrink:0;margin-top:2px}.notif-cta__body{display:flex;flex-direction:column;gap:2px}.group{padding:12px 0}.group+.group{border-top:1px solid var(--val-border-color, rgba(0, 0, 0, .08))}.notif-list{list-style:none;padding:0;margin:12px 0 0;display:flex;flex-direction:column;gap:10px}.notif{display:grid;grid-template-columns:44px 1fr;gap:12px;padding:12px;border-radius:12px;background:transparent;transition:background .12s ease}.notif--unread{background:var(--ion-color-light, rgba(0, 0, 0, .04))}.notif__icon{width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:var(--ion-color-light, rgba(0, 0, 0, .06));overflow:hidden}.notif__icon ion-icon{font-size:22px}.notif__icon img{width:100%;height:100%;object-fit:cover}.notif__avatar{border-radius:50%}.notif__body{display:flex;flex-direction:column;gap:4px;min-width:0}.notif__head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}.notif__time{font-size:12px;color:var(--ion-color-medium, #92949c);flex-shrink:0}.notif__text{margin:0;font-size:14px;line-height:1.4;color:var(--ion-color-dark, #1a1a1a);white-space:pre-wrap}.notif__text--clamped{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notif__expand{background:none;border:0;padding:0;font-size:13px;color:var(--ion-color-dark, #1a1a1a);cursor:pointer;align-self:flex-start;text-decoration:underline}.notif__app{font-size:11px;text-transform:uppercase;letter-spacing:.4px;color:var(--ion-color-medium, #92949c);margin-top:2px}\n"] }]
51003
+ }], ctorParameters: () => [], propDecorators: { config: [{
51004
+ type: Input
51005
+ }] } });
51006
+
51007
+ /**
51008
+ * Helper para montar el inbox de notificaciones (`val-notifications-view`) como
51009
+ * ruta en una app del factory. El `config` se pasa por route `data`
51010
+ * (`notificationsConfig`) y el componente lo lee como fallback de su
51011
+ * `@Input() config`.
51012
+ *
51013
+ * @example
51014
+ * ```ts
51015
+ * // app.routes.ts
51016
+ * export const appRoutes: Routes = [
51017
+ * ...provideValtechNotificationsRoutes(), // → /app/notifications
51018
+ * { path: 'settings', loadChildren: () => ... },
51019
+ * ];
51020
+ *
51021
+ * // con config acotada:
51022
+ * ...provideValtechNotificationsRoutes({ config: { enableMarkAll: false } }),
51023
+ * ...provideValtechNotificationsRoutes({
51024
+ * config: { settingsNotificationsPath: '/app/preferences/push' },
51025
+ * }),
51026
+ * ```
51027
+ */
51028
+ function provideValtechNotificationsRoutes(opts) {
51029
+ return [
51030
+ {
51031
+ path: opts?.path ?? 'notifications',
51032
+ component: NotificationsViewComponent,
51033
+ data: { notificationsConfig: opts?.config },
51034
+ },
51035
+ ];
51036
+ }
51037
+
50101
51038
  /** Built-in label sets for the three platform locales. */
50102
51039
  const CALLOUT_LABELS = {
50103
51040
  es: {
@@ -52140,124 +53077,6 @@ function beautifyLegalArticle(article) {
52140
53077
  * Cliente NUNCA escribe directo — todas las mutaciones via PUT /v2/auth/preferences.
52141
53078
  */
52142
53079
 
52143
- /**
52144
- * Refreshable Stream
52145
- *
52146
- * Helper estándar para vistas que consumen datos de Firestore. Soporta DOS
52147
- * modos — el caller decide cuál vía la `factory`:
52148
- *
52149
- * - **one-shot** (recomendado por defecto): `() => from(svc.getAllOnce())`.
52150
- * Carga una vez y completa. Más barato — sin listener vivo.
52151
- * - **real-time**: `() => svc.getAll()`. Listener Firestore que auto-actualiza.
52152
- * Solo donde importa (inbox de notificaciones, badges).
52153
- *
52154
- * En ambos modos la primera suscripción se gatea por `firebaseAuthReady`
52155
- * (sesión de Firebase Auth lista) — así el listener/lectura nunca se adjunta
52156
- * antes de que `request.auth` esté disponible en las reglas de Firestore, lo
52157
- * que cierra la ventana de `permission-denied` en cold start de PWA.
52158
- *
52159
- * El `retry` con backoff corto es solo red de seguridad para reconexiones
52160
- * transitorias (no para cubrir cold start — eso lo cubre el gate de auth).
52161
- */
52162
- /**
52163
- * Crea un stream refrescable a partir de una factory de Observable.
52164
- *
52165
- * DEBE llamarse en un injection context (field initializer o constructor) —
52166
- * usa `toSignal`/`toObservable`/`inject` internamente.
52167
- *
52168
- * @param factory - Función que produce el Observable a consumir. El caller
52169
- * decide el modo: `() => from(svc.getAllOnce())` (one-shot) o
52170
- * `() => svc.getAll()` (real-time).
52171
- * @param options - Configuración opcional (fallback, retry, gate).
52172
- *
52173
- * @example
52174
- * ```typescript
52175
- * // one-shot (recomendado por defecto)
52176
- * private readonly stream = createRefreshableStream(
52177
- * () => from(this.svc.getAllOnce()),
52178
- * );
52179
- *
52180
- * // real-time (inbox)
52181
- * private readonly stream = createRefreshableStream(
52182
- * () => this.notifs.getAll(50),
52183
- * );
52184
- *
52185
- * readonly items = this.stream.data;
52186
- * readonly loading = this.stream.loading;
52187
- *
52188
- * ionViewWillEnter() {
52189
- * this.pageRefresh.register(() => this.stream.reload());
52190
- * }
52191
- * ```
52192
- */
52193
- function createRefreshableStream(factory, options) {
52194
- const injector = options?.injector ?? inject(Injector);
52195
- const fallback = (options?.fallback ?? null);
52196
- const retryCount = options?.retryCount ?? 4;
52197
- const retryBaseDelayMs = options?.retryBaseDelayMs ?? 500;
52198
- const gateOnFirebaseAuth = options?.gateOnFirebaseAuth ?? true;
52199
- const authGateTimeoutMs = options?.authGateTimeoutMs ?? 20000;
52200
- const firstEmitTimeoutMs = options?.firstEmitTimeoutMs ?? 20000;
52201
- // FirebaseService es opcional — sin él (apps sin Firebase) no se gatea.
52202
- const firebase = gateOnFirebaseAuth ? injector.get(FirebaseService, null) : null;
52203
- const _reload = signal(0);
52204
- const _loading = signal(true);
52205
- const _error = signal(false);
52206
- // Gate de Firebase Auth: el stream no abre la primera suscripción hasta que
52207
- // la sesión esté confirmada. `race` con un `timer` de seguridad — si la
52208
- // sesión NUNCA se establece (token inválido, signInWithFirebase falló), el
52209
- // gate igual se libera tras `authGateTimeoutMs` y la factory procede: si no
52210
- // hay permiso terminará en estado `error`, jamás en skeleton infinito.
52211
- // Tag los dos inputs del `race` para distinguir en logs cuál ganó: la sesión
52212
- // de Firebase Auth (`auth-ready`) o el timeout de seguridad (`timeout`).
52213
- const authGate$ = firebase
52214
- ? race(firebase.firebaseAuthReady$.pipe(map$1(() => 'auth-ready')), timer(authGateTimeoutMs).pipe(map$1(() => 'timeout'))).pipe(take(1), tap(winner => {
52215
- if (winner === 'timeout') {
52216
- console.warn(`[RefreshStream] auth gate resolved by TIMEOUT after ${authGateTimeoutMs}ms ` +
52217
- '(Firebase Auth session never confirmed)');
52218
- }
52219
- else {
52220
- console.log('[RefreshStream] auth gate resolved by firebaseAuthReady$ (session ready)');
52221
- }
52222
- }))
52223
- : of(true);
52224
- const data = toSignal(toObservable(_reload, { injector }).pipe(tap(() => {
52225
- _loading.set(true);
52226
- _error.set(false);
52227
- }),
52228
- // Esperar a Firebase Auth listo antes de cada (re)suscripción. Como
52229
- // `firebaseAuthReady$` completa tras la primera emisión, las reloads
52230
- // posteriores resuelven el gate de inmediato (auth ya está listo).
52231
- switchMap(() => authGate$.pipe(switchMap(() => factory().pipe(tap(() => {
52232
- _loading.set(false);
52233
- _error.set(false);
52234
- console.log('[RefreshStream] factory emitted first value — loading=false');
52235
- }),
52236
- // Backoff corto exponencial acotado — solo red de seguridad para
52237
- // reconexiones transitorias, NO para cubrir cold start.
52238
- retry({
52239
- count: retryCount,
52240
- delay: (_err, attempt) => timer(Math.min(retryBaseDelayMs * 2 ** (attempt - 1), 4000)),
52241
- }),
52242
- // Techo absoluto para la PRIMERA emisión. Si la factory no emite
52243
- // ni valor ni error (servicio nunca inicializado → `EMPTY` →
52244
- // silencio), `timeout` dispara un error → `catchError` → estado
52245
- // `error`. Nunca skeleton perpetuo. Va DESPUÉS de `retry` para
52246
- // que cuente el tiempo total, no por intento.
52247
- timeout({ first: firstEmitTimeoutMs }), catchError(err => {
52248
- _loading.set(false);
52249
- _error.set(true);
52250
- console.error(`[RefreshStream] factory failed/timed out → error state: ${err instanceof Error ? err.message : String(err)}`);
52251
- return of(fallback);
52252
- })))))), { initialValue: null, injector });
52253
- return {
52254
- data,
52255
- loading: _loading.asReadonly(),
52256
- error: _error.asReadonly(),
52257
- reload: () => _reload.update(n => n + 1),
52258
- };
52259
- }
52260
-
52261
53080
  /**
52262
53081
  * Refreshable Stream
52263
53082
  *
@@ -60422,5 +61241,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
60422
61241
  * Generated bundle index. Do not edit.
60423
61242
  */
60424
61243
 
60425
- export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
61244
+ export { ACTION_CARD_DEFAULTS, AD_SIZE_MAP, API_TABLE_COLUMN_LABELS, ARTICLE_CARD_DEFAULTS, ARTICLE_SPACING, ARTICLE_STRIP_DEFAULTS, AUTH_CTA_DEFAULTS, AVATAR_UPLOAD_DEFAULTS, AccordionComponent, AccountViewComponent, ActionCardComponent, ActionHeaderComponent, ActionType, AdSlotComponent, AdsLoaderService, AdsService, AlertBoxComponent, AnalyticsErrorHandler, AnalyticsRouterTracker, AnalyticsService, AnimatedTerminalComponent, AppConfigService, AppVersionService, ArticleBuilder, ArticleCardComponent, ArticleComponent, ArticleStripComponent, AttachmentUploaderComponent, AuthBackgroundComponent, AuthCtaComponent, AuthService, AuthStateService, AuthStorageService, AuthSyncService, AvatarComponent, AvatarUploadComponent, BOTTOM_NAV_DEFAULTS, BannerComponent, BaseDefault, BlogPostBuilder, BottomNavComponent, BoxComponent, BreadcrumbComponent, ButtonComponent, ButtonGroupComponent, CALLOUT_LABELS, CHEV_KEYS, CIRCLE_KEYS, COMMON_COUNTRY_CODES, COMMON_CURRENCIES, CONTENT_CARD_DEFAULTS, CORNER_KEYS, CTA_CARD_DEFAULTS, CURRENCY_INFO, CardComponent, CardSection, CardType, CardsCarouselComponent, ChangeEmailModalComponent, ChangePasswordModalComponent, CheckInputComponent, CheckboxRadioInputComponent, ChipGroupComponent, ClearDefault, ClearDefaultBlock, ClearDefaultFull, ClearDefaultRound, ClearDefaultRoundBlock, ClearDefaultRoundFull, CodeDisplayComponent, CommandDisplayComponent, CommentComponent, CommentInputComponent, CommentSectionComponent, CompanyFooterComponent, ComponentStates, ConfirmationDialogService, ContainerComponent, ContentCardComponent, ContentLoaderComponent, ContentReactionComponent, ContentService, ContentTransformer, CookieBannerComponent, CountdownComponent, CreateOrgModalComponent, CtaCardComponent, CurrencyInputComponent, DEFAULT_ADS_CONFIG, DEFAULT_APP_CONFIG_SERVICE_CONFIG, DEFAULT_APP_VERSION_SERVICE_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_BACK_HEADER, DEFAULT_CANCEL_BUTTON, DEFAULT_CHECK_INTERVAL_MS, DEFAULT_CONFIRM_BUTTON, DEFAULT_COUNTDOWN_LABELS, DEFAULT_COUNTDOWN_LABELS_EN, DEFAULT_DEBUG_CONSOLE_CONFIG, DEFAULT_DONATION_CONFIG, DEFAULT_EMPTY_STATE, DEFAULT_EMULATOR_CONFIG, DEFAULT_FEEDBACK_CONFIG, DEFAULT_FEEDBACK_TYPE_OPTIONS, DEFAULT_HOME_HEADER, DEFAULT_INFINITE_LIST_METADATA, DEFAULT_MODAL_CANCEL_BUTTON, DEFAULT_MODAL_CONFIRM_BUTTON, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PLATFORMS, DEFAULT_REFRESHER_METADATA, DEFAULT_SKELETON_CONFIG, DEFAULT_SPLASH_SCREEN_CONFIG, DataTableComponent, DateInputComponent, DateRangeInputComponent, DebugConsoleComponent, DeleteAccountModalComponent, DetailSkeletonComponent, DeviceService, DisplayComponent, DividerComponent, DocsApiTableComponent, DocsBreadcrumbComponent, DocsBuilder, DocsCalloutComponent, DocsCodeExampleComponent, DocsLayoutComponent, DocsNavLinksComponent, DocsNavigationService, DocsPageComponent, DocsSearchComponent, DocsSectionComponent, DocsShellComponent, DocsSidebarComponent, DocsTocComponent, DonationService, DownloadService, EditOrgModalComponent, EmailInputComponent, EmptyStateComponent, ExpandableTextComponent, FEATURES_LIST_DEFAULTS, FUN_MODAL_DEFAULTS, FabComponent, FaqComponent, FeaturesListComponent, FeedbackFormComponent, FeedbackService, FileInputComponent, FirebaseService, FirestoreCollectionFactory, FirestoreService, FontSizeOption, FontSizeService, FooterComponent, FooterLinksComponent, FormComponent, FormSkeletonComponent, FunHeaderComponent, FunModalComponent, GlassComponent, GlowCardComponent, GlowComponent, GridSkeletonComponent, HANDOFF_ROUTE_PARAM, HANDOFF_TOKEN_PARAM, HandoffService, HasPermissionDirective, HeaderComponent, HintComponent, HorizontalScrollComponent, HourInputComponent, HrefComponent, I18nService, IMAGE_DEFAULTS, INFO_CARD_DEFAULTS, INITIAL_AUTH_STATE, INITIAL_MFA_STATE, INVITATION_CARD_DEFAULTS, Icon, IconComponent, IconService, ImageComponent, ImageCropComponent, ImageService, InAppBrowserService, InfiniteListComponent, InfoCardComponent, InfoComponent, InputI18nHelper, InputType, InvitationCardComponent, InviteMemberModalComponent, ItemListComponent, LANG_STORAGE_KEY$1 as LANG_STORAGE_KEY, LEGAL_CONTENT_CONFIG, LOGIN_DEFAULTS, LandingSplitComponent, LandingStepsComponent, LanguageSelectorComponent, LayeredCardComponent, LegalContentService, LegalLinkService, LinkComponent, LinkProcessorService, LinkedProvidersComponent, LinksAccordionComponent, LinksCakeComponent, ListSkeletonComponent, LoadingDirective, LocalStorageService, LocaleService, LoginAttemptModalComponent, LoginComponent, MEDIA_OBJECT_DEFAULTS, MEMBER_CARD_DEFAULTS, METRIC_CARD_DEFAULTS, MODAL_SIZES, MOTIF_KEYS, MOTION, MaintenancePageComponent, MarkdownArticleParserService, MediaObjectComponent, MemberCardComponent, MemberDetailModalComponent, MenuComponent, MessagingService, MetaService, MetricCardComponent, MfaModalComponent, ModalService, ModalShellComponent, MultiSelectSearchComponent, NUM_KEYS, NavigationService, NetworkBannerComponent, NetworkStatusService, NewsBuilder, NoContentComponent, NotesBoxComponent, NotificationActionService, NotificationsService, NotificationsViewComponent, NumberFromToComponent, NumberInputComponent, NumberStepperComponent, OAUTH_PROVIDERS_INFO, OAuthCallbackComponent, OAuthService, OrgInfoSheetComponent, OrgService, OrgSwitchService, OrganizationViewComponent, OutlineDefault, OutlineDefaultBlock, OutlineDefaultFull, OutlineDefaultRound, OutlineDefaultRoundBlock, OutlineDefaultRoundFull, PATTERN_MOTIFS, PATTERN_PALETTES, PATTERN_STYLE_CONFIGS, PLATFORM_CONFIGS, PageContentComponent, PageLinksComponent, PageRefreshService, PageTemplateComponent, PageWavesComponent, PageWrapperComponent, PaginationComponent, PaginationService, PasswordInputComponent, PatternComponent, PhoneInputComponent, PillComponent, PinInputComponent, PlainCodeBoxComponent, PopoverSelectorComponent, PreferencesService, PreferencesViewComponent, PresetService, PriceTagComponent, PrimarySolidBlockButton, PrimarySolidBlockHrefButton, PrimarySolidBlockIconButton, PrimarySolidBlockIconHrefButton, PrimarySolidDefaultRoundButton, PrimarySolidDefaultRoundHrefButton, PrimarySolidDefaultRoundIconButton, PrimarySolidDefaultRoundIconHrefButton, PrimarySolidFullButton, PrimarySolidFullHrefButton, PrimarySolidFullIconButton, PrimarySolidFullIconHrefButton, PrimarySolidLargeRoundButton, PrimarySolidLargeRoundHrefButton, PrimarySolidLargeRoundIconButton, PrimarySolidLargeRoundIconHrefButton, PrimarySolidSmallRoundButton, PrimarySolidSmallRoundHrefButton, PrimarySolidSmallRoundIconButton, PrimarySolidSmallRoundIconHrefButton, ProcessLinksPipe, ProfileSkeletonComponent, ProfileViewComponent, ProgressBarComponent, ProgressRingComponent, ProgressStatusComponent, PrompterComponent, QR_PRESETS, QrCodeComponent, QrGeneratorService, QueryBuilder, QuoteBoxComponent, REQUEST_STATUSES, RadioInputComponent, RangeInputComponent, RatingComponent, RefresherComponent, RequestFirestoreService, RequestFormBuilderService, RequestService, RightsFooterComponent, RotatingTextComponent, SEARCH_HEADER_DEFAULTS, SETTINGS_SECTIONS_CATALOG, SHAPE_KEYS, SHARE_PROFILE_MODAL_DEFAULTS, SKELETON_LAYOUT_DEFAULT_ROWS, SKELETON_PRESETS, SOLID_KEYS, STATS_BAR_DEFAULTS, STROKE_KEYS, SearchHeaderComponent, SearchSelectorComponent, SearchbarComponent, SecondarySolidBlockButton, SecondarySolidBlockHrefButton, SecondarySolidBlockIconButton, SecondarySolidBlockIconHrefButton, SecondarySolidDefaultRoundButton, SecondarySolidDefaultRoundHrefButton, SecondarySolidDefaultRoundIconButton, SecondarySolidDefaultRoundIconHrefButton, SecondarySolidFullButton, SecondarySolidFullHrefButton, SecondarySolidFullIconButton, SecondarySolidFullIconHrefButton, SecondarySolidLargeRoundButton, SecondarySolidLargeRoundHrefButton, SecondarySolidLargeRoundIconButton, SecondarySolidLargeRoundIconHrefButton, SecondarySolidSmallRoundButton, SecondarySolidSmallRoundHrefButton, SecondarySolidSmallRoundIconButton, SecondarySolidSmallRoundIconHrefButton, SecurityViewComponent, SegmentControlComponent, SelectSearchComponent, SessionListModalComponent, SessionService, SettingsHubComponent, ShareButtonsComponent, ShareProfileModalComponent, SimpleComponent, SkeletonComponent, SkeletonLayoutComponent, SkeletonService, SolidBlockButton, SolidDefault, SolidDefaultBlock, SolidDefaultButton, SolidDefaultFull, SolidDefaultRound, SolidDefaultRoundBlock, SolidDefaultRoundButton, SolidDefaultRoundFull, SolidFullButton, SolidLargeButton, SolidLargeRoundButton, SolidSmallButton, SolidSmallRoundButton, SplashScreenService, StatsBarComponent, StatsCardComponent, StepperComponent, StorageService, SwipeCarouselComponent, SwitchOrgModalComponent, TRI_KEYS, TabbedContentComponent, TableSkeletonComponent, TabsComponent, Terminal404Component, TestimonialCardComponent, TestimonialCarouselComponent, TextComponent, TextInputComponent, TextareaInputComponent, ThemeOption, ThemeService, TimelineComponent, TitleBlockComponent, TitleComponent, ToastService, ToggleInputComponent, TokenService, ToolbarActionType, ToolbarComponent, TransferOwnershipModalComponent, TranslatePipe, TypedCollection, UPDATE_BANNER_DEFAULT_CONTENT, UPDATE_BANNER_I18N_NAMESPACE, UpdateBannerComponent, UserAvatarComponent, UsernameInputComponent, VALTECH_ADS_CONFIG, VALTECH_APP_CONFIG, VALTECH_APP_VERSION, VALTECH_AUTH_CONFIG, VALTECH_COMPANY_LINKS, VALTECH_CONTENT_CONFIG, VALTECH_DEBUG_CONSOLE, VALTECH_DEFAULT_CONTENT, VALTECH_DONATION_CONFIG, VALTECH_FEEDBACK_CONFIG, VALTECH_FIREBASE_CONFIG, VALTECH_FOOTER_I18N, VALTECH_FOOTER_LOGO, VALTECH_LANGUAGE_SELECTOR, VALTECH_LEGAL_CONFIG, VALTECH_NETWORK_ERROR_KEY, VALTECH_SITE_PATHS, VALTECH_SOCIAL_LINKS, VALTECH_SPLASH_SCREEN, VALTECH_WEB_BASE_URLS, VERSION, ValtechErrorService, WizardComponent, WizardFooterComponent, applyDefaultValueToControl, authGuard, authInterceptor, authPasswordValidator, beautifyLegalArticle, blogPost, buildCompanyFooterProps, buildFooterLinks, buildLegalLinkResolver, buildPath, buildSettingsCards, collections, connectPageRefresh, createErrorStateProps, createFirebaseConfig, createGlowCardProps, createInitialPaginationState, createNumberFromToField, createRefreshableStream, createTitleProps, docs, errorLoggingInterceptor, extractPathParams, generatePatternTiles, generateRandomTile, getAppInfo, getAppVersion, getCollectionPath, getDocumentId, getTimeOfDayKey, goToTop, guestGuard, hasEmulators, interpretError, isAtEnd, isCollectionPath, isDocumentPath, isEmulatorMode, isValidPath, joinPath, maxLength, mulberry32, news, parseMarkdownArticle, permissionGuard, permissionGuardFromRoute, provideLegalContent, provideSplashScreen, provideValtechAccountRoutes, provideValtechAds, provideValtechAppConfig, provideValtechAppVersion, provideValtechAuth, provideValtechAuthInterceptor, provideValtechContent, provideValtechDebugConsole, provideValtechDonations, provideValtechErrorHandling, provideValtechFeedback, provideValtechFirebase, provideValtechI18n, provideValtechLegal, provideValtechNotificationsRoutes, provideValtechOrganizationRoutes, provideValtechPreferencesRoutes, provideValtechPresets, provideValtechProfileRoutes, provideValtechSecurityRoutes, provideValtechSettingsRoutes, provideValtechSite, provideValtechSkeleton, query, renderPatternSvgInner, replaceSpecialChars, resolveColor$1 as resolveColor, resolveInputDefaultValue, resolveWebBaseUrl, roleGuard, storagePaths, superAdminGuard, toArticle };
60426
61245
  //# sourceMappingURL=valtech-components.mjs.map