valtech-components 2.0.718 → 2.0.720

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.
@@ -50,7 +50,7 @@ import 'prismjs/components/prism-json';
50
50
  * Current version of valtech-components.
51
51
  * This is automatically updated during the publish process.
52
52
  */
53
- const VERSION = '2.0.718';
53
+ const VERSION = '2.0.720';
54
54
 
55
55
  /**
56
56
  * Servicio para gestionar presets de componentes.
@@ -3911,6 +3911,9 @@ const VALTECH_DEFAULT_CONTENT = {
3911
3911
  goodMorning: 'Buenos días',
3912
3912
  goodAfternoon: 'Buenas tardes',
3913
3913
  goodEvening: 'Buenas noches',
3914
+ // Auth — confirmación cierre de sesión
3915
+ logoutConfirmTitle: 'Cerrar sesión',
3916
+ logoutConfirmMessage: '¿Seguro que deseas cerrar sesión?',
3914
3917
  // Idiomas
3915
3918
  languageName_es: 'Español',
3916
3919
  languageName_en: 'English',
@@ -4055,6 +4058,9 @@ const VALTECH_DEFAULT_CONTENT = {
4055
4058
  goodMorning: 'Good morning',
4056
4059
  goodAfternoon: 'Good afternoon',
4057
4060
  goodEvening: 'Good evening',
4061
+ // Auth — sign out confirmation
4062
+ logoutConfirmTitle: 'Sign out',
4063
+ logoutConfirmMessage: 'Are you sure you want to sign out?',
4058
4064
  // Languages
4059
4065
  languageName_es: 'Español',
4060
4066
  languageName_en: 'English',
@@ -11413,14 +11419,14 @@ addIcons({ trendingUp, trendingDown, remove, analytics, people, cash, cart, eye,
11413
11419
  *
11414
11420
  * @input preset: string - Name of preset to apply
11415
11421
  * @input props: StatsCardMetadata - Configuration for the stats card
11416
- * @output cardClick: void - Emits when card is clicked
11422
+ * @output onClick: void - Emits when card is clicked
11417
11423
  */
11418
11424
  class StatsCardComponent {
11419
11425
  constructor() {
11420
11426
  this.presets = inject(PresetService);
11421
11427
  this.props = {};
11422
11428
  this.resolvedProps = {};
11423
- this.cardClick = new EventEmitter();
11429
+ this.onClick = new EventEmitter();
11424
11430
  this.Math = Math;
11425
11431
  }
11426
11432
  ngOnInit() {
@@ -11432,9 +11438,7 @@ class StatsCardComponent {
11432
11438
  }
11433
11439
  }
11434
11440
  resolveProps() {
11435
- const presetProps = this.preset
11436
- ? this.presets.get('statsCard', this.preset)
11437
- : {};
11441
+ const presetProps = this.preset ? this.presets.get('statsCard', this.preset) : {};
11438
11442
  this.resolvedProps = {
11439
11443
  ...presetProps,
11440
11444
  ...this.props,
@@ -11466,13 +11470,13 @@ class StatsCardComponent {
11466
11470
  return 'remove';
11467
11471
  }
11468
11472
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: StatsCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11469
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StatsCardComponent, isStandalone: true, selector: "val-stats-card", inputs: { preset: "preset", props: "props" }, outputs: { cardClick: "cardClick" }, usesOnChanges: true, ngImport: i0, template: `
11473
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: StatsCardComponent, isStandalone: true, selector: "val-stats-card", inputs: { preset: "preset", props: "props" }, outputs: { onClick: "onClick" }, usesOnChanges: true, ngImport: i0, template: `
11470
11474
  <ion-card
11471
11475
  class="stats-card"
11472
11476
  [class]="getBackgroundClass()"
11473
11477
  [style.--card-color]="getCardColor()"
11474
11478
  [style.min-height.px]="resolvedProps.minHeight"
11475
- (click)="cardClick.emit()"
11479
+ (click)="onClick.emit()"
11476
11480
  >
11477
11481
  <ion-card-content>
11478
11482
  <div class="stats-header">
@@ -11536,7 +11540,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11536
11540
  [class]="getBackgroundClass()"
11537
11541
  [style.--card-color]="getCardColor()"
11538
11542
  [style.min-height.px]="resolvedProps.minHeight"
11539
- (click)="cardClick.emit()"
11543
+ (click)="onClick.emit()"
11540
11544
  >
11541
11545
  <ion-card-content>
11542
11546
  <div class="stats-header">
@@ -11595,7 +11599,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11595
11599
  type: Input
11596
11600
  }], props: [{
11597
11601
  type: Input
11598
- }], cardClick: [{
11602
+ }], onClick: [{
11599
11603
  type: Output
11600
11604
  }] } });
11601
11605
 
@@ -18776,10 +18780,7 @@ const ACTION_CARD_DEFAULTS = {
18776
18780
  };
18777
18781
 
18778
18782
  addIcons({ chevronForwardOutline });
18779
- const IONIC_COLORS = [
18780
- 'primary', 'secondary', 'tertiary', 'success',
18781
- 'warning', 'danger', 'light', 'medium', 'dark'
18782
- ];
18783
+ const IONIC_COLORS = ['primary', 'secondary', 'tertiary', 'success', 'warning', 'danger', 'light', 'medium', 'dark'];
18783
18784
  /**
18784
18785
  * val-action-card
18785
18786
  *
@@ -18794,7 +18795,7 @@ const IONIC_COLORS = [
18794
18795
  * title: 'Settings',
18795
18796
  * description: 'Manage your preferences'
18796
18797
  * }"
18797
- * (cardClick)="onCardClick($event)"
18798
+ * (onClick)="onCardClick($event)"
18798
18799
  * />
18799
18800
  * ```
18800
18801
  *
@@ -18830,7 +18831,7 @@ class ActionCardComponent {
18830
18831
  /** Component configuration */
18831
18832
  this.props = input({});
18832
18833
  /** Event emitted when card is clicked */
18833
- this.cardClick = new EventEmitter();
18834
+ this.onClick = new EventEmitter();
18834
18835
  /** Merged configuration with defaults */
18835
18836
  this.config = computed(() => ({
18836
18837
  ...ACTION_CARD_DEFAULTS,
@@ -18894,7 +18895,7 @@ class ActionCardComponent {
18894
18895
  return;
18895
18896
  }
18896
18897
  // Emit click event
18897
- this.cardClick.emit({
18898
+ this.onClick.emit({
18898
18899
  token: cfg.token,
18899
18900
  navigated: !!cfg.routerLink || !!cfg.href,
18900
18901
  });
@@ -18904,7 +18905,7 @@ class ActionCardComponent {
18904
18905
  }
18905
18906
  }
18906
18907
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ActionCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
18907
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ActionCardComponent, isStandalone: true, selector: "val-action-card", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardClick: "cardClick" }, ngImport: i0, template: `
18908
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ActionCardComponent, isStandalone: true, selector: "val-action-card", inputs: { props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onClick: "onClick" }, ngImport: i0, template: `
18908
18909
  <article
18909
18910
  class="action-card"
18910
18911
  [class.action-card--small]="config().size === 'small'"
@@ -18936,11 +18937,7 @@ class ActionCardComponent {
18936
18937
  }
18937
18938
 
18938
18939
  <!-- Icon Container -->
18939
- <div
18940
- class="action-card__icon"
18941
- [style.color]="getIconColor()"
18942
- [style.background-color]="getIconBackgroundColor()"
18943
- >
18940
+ <div class="action-card__icon" [style.color]="getIconColor()" [style.background-color]="getIconBackgroundColor()">
18944
18941
  @if (config().icon?.ionicon) {
18945
18942
  <ion-icon [name]="config().icon!.ionicon!"></ion-icon>
18946
18943
  } @else if (config().icon?.svgPath) {
@@ -18955,11 +18952,7 @@ class ActionCardComponent {
18955
18952
  <path [attr.d]="config().icon!.svgPath" />
18956
18953
  </svg>
18957
18954
  } @else if (config().icon?.imageUrl) {
18958
- <img
18959
- [src]="config().icon!.imageUrl"
18960
- [alt]="getTitle()"
18961
- class="action-card__icon-image"
18962
- />
18955
+ <img [src]="config().icon!.imageUrl" [alt]="getTitle()" class="action-card__icon-image" />
18963
18956
  }
18964
18957
  </div>
18965
18958
 
@@ -18973,10 +18966,7 @@ class ActionCardComponent {
18973
18966
 
18974
18967
  <!-- Chevron (optional) -->
18975
18968
  @if (config().showChevron && !config().disabled) {
18976
- <ion-icon
18977
- name="chevron-forward-outline"
18978
- class="action-card__chevron"
18979
- ></ion-icon>
18969
+ <ion-icon name="chevron-forward-outline" class="action-card__chevron"></ion-icon>
18980
18970
  }
18981
18971
  </article>
18982
18972
  `, isInline: true, styles: [":host{display:block}.action-card{position:relative;display:flex;align-items:center;gap:1rem;padding:1rem;background:var(--card-bg, var(--ion-card-background, var(--ion-background-color)));border-radius:12px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease,background-color .2s ease;text-decoration:none;overflow:hidden;--ripple-color: var(--ion-color-primary)}.action-card--small{padding:.75rem;gap:.75rem}.action-card--small .action-card__icon{width:36px;height:36px;font-size:18px;border-radius:8px}.action-card--small .action-card__icon svg{width:18px;height:18px}.action-card--small .action-card__title{font-size:.9rem}.action-card--small .action-card__description{font-size:.8rem}.action-card--medium{padding:1rem;gap:1rem}.action-card--medium .action-card__icon{width:48px;height:48px;font-size:24px;border-radius:10px}.action-card--medium .action-card__icon svg{width:24px;height:24px}.action-card--medium .action-card__title{font-size:1rem}.action-card--medium .action-card__description{font-size:.875rem}.action-card--large{padding:1.25rem;gap:1.25rem}.action-card--large .action-card__icon{width:56px;height:56px;font-size:28px;border-radius:12px}.action-card--large .action-card__icon svg{width:28px;height:28px}.action-card--large .action-card__title{font-size:1.1rem}.action-card--large .action-card__description{font-size:.9rem}.action-card--bordered{border:1px solid var(--card-border-color, var(--ion-color-light-shade))}.action-card--shadowed{box-shadow:0 2px 8px #00000014}.action-card--clickable:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.action-card--clickable:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.action-card--clickable:active{transform:translateY(0)}.action-card--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.action-card__badge{position:absolute;top:8px;right:8px;padding:2px 8px;font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;border-radius:10px;z-index:1}.action-card__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.action-card__icon ion-icon{font-size:inherit}.action-card__icon svg{display:block}.action-card__icon-image{width:100%;height:100%;object-fit:cover;border-radius:8px}.action-card__content{flex:1;min-width:0}.action-card__title{margin:0 0 .25rem;font-weight:600;color:var(--ion-text-color);line-height:1.3}.action-card__description{margin:0;color:var(--ion-color-medium);line-height:1.4}.action-card__chevron{flex-shrink:0;font-size:1.25rem;color:var(--ion-color-medium);transition:transform .2s ease}.action-card--clickable:hover .action-card__chevron{transform:translate(4px)}@media (prefers-color-scheme: dark){.action-card--shadowed{box-shadow:0 2px 8px #0000004d}.action-card--clickable:hover{box-shadow:0 4px 16px #0006}}:host-context(.dark) .action-card--shadowed,:host-context(body.dark) .action-card--shadowed,:host-context([data-theme=dark]) .action-card--shadowed{box-shadow:0 2px 8px #0000004d}:host-context(.dark) .action-card--clickable:hover,:host-context(body.dark) .action-card--clickable:hover,:host-context([data-theme=dark]) .action-card--clickable:hover{box-shadow:0 4px 16px #0006}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonRippleEffect, selector: "ion-ripple-effect", inputs: ["type"] }] }); }
@@ -19015,11 +19005,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19015
19005
  }
19016
19006
 
19017
19007
  <!-- Icon Container -->
19018
- <div
19019
- class="action-card__icon"
19020
- [style.color]="getIconColor()"
19021
- [style.background-color]="getIconBackgroundColor()"
19022
- >
19008
+ <div class="action-card__icon" [style.color]="getIconColor()" [style.background-color]="getIconBackgroundColor()">
19023
19009
  @if (config().icon?.ionicon) {
19024
19010
  <ion-icon [name]="config().icon!.ionicon!"></ion-icon>
19025
19011
  } @else if (config().icon?.svgPath) {
@@ -19034,11 +19020,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19034
19020
  <path [attr.d]="config().icon!.svgPath" />
19035
19021
  </svg>
19036
19022
  } @else if (config().icon?.imageUrl) {
19037
- <img
19038
- [src]="config().icon!.imageUrl"
19039
- [alt]="getTitle()"
19040
- class="action-card__icon-image"
19041
- />
19023
+ <img [src]="config().icon!.imageUrl" [alt]="getTitle()" class="action-card__icon-image" />
19042
19024
  }
19043
19025
  </div>
19044
19026
 
@@ -19052,14 +19034,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
19052
19034
 
19053
19035
  <!-- Chevron (optional) -->
19054
19036
  @if (config().showChevron && !config().disabled) {
19055
- <ion-icon
19056
- name="chevron-forward-outline"
19057
- class="action-card__chevron"
19058
- ></ion-icon>
19037
+ <ion-icon name="chevron-forward-outline" class="action-card__chevron"></ion-icon>
19059
19038
  }
19060
19039
  </article>
19061
19040
  `, styles: [":host{display:block}.action-card{position:relative;display:flex;align-items:center;gap:1rem;padding:1rem;background:var(--card-bg, var(--ion-card-background, var(--ion-background-color)));border-radius:12px;cursor:pointer;transition:transform .2s ease,box-shadow .2s ease,background-color .2s ease;text-decoration:none;overflow:hidden;--ripple-color: var(--ion-color-primary)}.action-card--small{padding:.75rem;gap:.75rem}.action-card--small .action-card__icon{width:36px;height:36px;font-size:18px;border-radius:8px}.action-card--small .action-card__icon svg{width:18px;height:18px}.action-card--small .action-card__title{font-size:.9rem}.action-card--small .action-card__description{font-size:.8rem}.action-card--medium{padding:1rem;gap:1rem}.action-card--medium .action-card__icon{width:48px;height:48px;font-size:24px;border-radius:10px}.action-card--medium .action-card__icon svg{width:24px;height:24px}.action-card--medium .action-card__title{font-size:1rem}.action-card--medium .action-card__description{font-size:.875rem}.action-card--large{padding:1.25rem;gap:1.25rem}.action-card--large .action-card__icon{width:56px;height:56px;font-size:28px;border-radius:12px}.action-card--large .action-card__icon svg{width:28px;height:28px}.action-card--large .action-card__title{font-size:1.1rem}.action-card--large .action-card__description{font-size:.9rem}.action-card--bordered{border:1px solid var(--card-border-color, var(--ion-color-light-shade))}.action-card--shadowed{box-shadow:0 2px 8px #00000014}.action-card--clickable:hover{transform:translateY(-2px);box-shadow:0 4px 16px #0000001f}.action-card--clickable:focus-visible{outline:2px solid var(--ion-color-primary);outline-offset:2px}.action-card--clickable:active{transform:translateY(0)}.action-card--disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.action-card__badge{position:absolute;top:8px;right:8px;padding:2px 8px;font-size:.7rem;font-weight:600;text-transform:uppercase;letter-spacing:.5px;border-radius:10px;z-index:1}.action-card__icon{flex-shrink:0;display:flex;align-items:center;justify-content:center}.action-card__icon ion-icon{font-size:inherit}.action-card__icon svg{display:block}.action-card__icon-image{width:100%;height:100%;object-fit:cover;border-radius:8px}.action-card__content{flex:1;min-width:0}.action-card__title{margin:0 0 .25rem;font-weight:600;color:var(--ion-text-color);line-height:1.3}.action-card__description{margin:0;color:var(--ion-color-medium);line-height:1.4}.action-card__chevron{flex-shrink:0;font-size:1.25rem;color:var(--ion-color-medium);transition:transform .2s ease}.action-card--clickable:hover .action-card__chevron{transform:translate(4px)}@media (prefers-color-scheme: dark){.action-card--shadowed{box-shadow:0 2px 8px #0000004d}.action-card--clickable:hover{box-shadow:0 4px 16px #0006}}:host-context(.dark) .action-card--shadowed,:host-context(body.dark) .action-card--shadowed,:host-context([data-theme=dark]) .action-card--shadowed{box-shadow:0 2px 8px #0000004d}:host-context(.dark) .action-card--clickable:hover,:host-context(body.dark) .action-card--clickable:hover,:host-context([data-theme=dark]) .action-card--clickable:hover{box-shadow:0 4px 16px #0006}\n"] }]
19062
- }], propDecorators: { cardClick: [{
19041
+ }], propDecorators: { onClick: [{
19063
19042
  type: Output
19064
19043
  }] } });
19065
19044
 
@@ -28737,6 +28716,210 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
28737
28716
  args: [VALTECH_AUTH_CONFIG]
28738
28717
  }] }, { type: i1$8.HttpClient }, { type: i0.NgZone }] });
28739
28718
 
28719
+ /**
28720
+ * Default confirmation dialog options.
28721
+ */
28722
+ const DEFAULT_CONFIRM_BUTTON = {
28723
+ text: 'Confirm',
28724
+ role: 'confirm',
28725
+ color: 'primary',
28726
+ };
28727
+ const DEFAULT_CANCEL_BUTTON = {
28728
+ text: 'Cancel',
28729
+ role: 'cancel',
28730
+ color: 'medium',
28731
+ };
28732
+
28733
+ /**
28734
+ * Service for displaying confirmation dialogs.
28735
+ *
28736
+ * @example Basic usage
28737
+ * const result = await confirmationService.confirm({
28738
+ * title: 'Delete Item',
28739
+ * message: 'Are you sure you want to delete this item?',
28740
+ * });
28741
+ *
28742
+ * if (result.confirmed) {
28743
+ * // User confirmed
28744
+ * }
28745
+ *
28746
+ * @example Custom buttons
28747
+ * const result = await confirmationService.confirm({
28748
+ * title: 'Save Changes',
28749
+ * message: 'Do you want to save your changes before leaving?',
28750
+ * confirmButton: { text: 'Save', color: 'success' },
28751
+ * cancelButton: { text: 'Discard', color: 'danger' },
28752
+ * extraButtons: [{ text: 'Cancel', role: 'cancel' }]
28753
+ * });
28754
+ *
28755
+ * @example Destructive action
28756
+ * const result = await confirmationService.confirmDestructive({
28757
+ * title: 'Delete Account',
28758
+ * message: 'This action cannot be undone. Are you sure?',
28759
+ * });
28760
+ */
28761
+ class ConfirmationDialogService {
28762
+ constructor() {
28763
+ this.alertController = inject(AlertController);
28764
+ }
28765
+ /**
28766
+ * Shows a confirmation dialog and returns the result.
28767
+ * @param options - Configuration for the dialog
28768
+ * @returns Promise resolving to the confirmation result
28769
+ */
28770
+ async confirm(options) {
28771
+ const buttons = this.buildButtons(options);
28772
+ // Default: usa val-alert (estilo iOS dark unificado de la lib)
28773
+ const userClasses = options.cssClass
28774
+ ? Array.isArray(options.cssClass)
28775
+ ? options.cssClass
28776
+ : [options.cssClass]
28777
+ : [];
28778
+ const cssClass = ['val-alert', ...userClasses];
28779
+ const alert = await this.alertController.create({
28780
+ header: options.title,
28781
+ subHeader: options.subHeader,
28782
+ message: options.message,
28783
+ buttons,
28784
+ backdropDismiss: options.backdropDismiss ?? false,
28785
+ cssClass,
28786
+ mode: options.mode ?? 'ios',
28787
+ translucent: options.translucent ?? true,
28788
+ animated: options.animated ?? true,
28789
+ });
28790
+ await alert.present();
28791
+ const { role, data } = await alert.onDidDismiss();
28792
+ return {
28793
+ confirmed: role === 'confirm',
28794
+ role,
28795
+ data,
28796
+ };
28797
+ }
28798
+ /**
28799
+ * Shows a simple confirmation dialog with default buttons.
28800
+ * @param title - Dialog title
28801
+ * @param message - Dialog message
28802
+ * @returns Promise resolving to true if confirmed, false otherwise
28803
+ */
28804
+ async confirmSimple(title, message) {
28805
+ const result = await this.confirm({ title, message });
28806
+ return result.confirmed;
28807
+ }
28808
+ /**
28809
+ * Shows a destructive action confirmation with red confirm button.
28810
+ * @param options - Configuration for the dialog
28811
+ * @returns Promise resolving to the confirmation result
28812
+ */
28813
+ async confirmDestructive(options) {
28814
+ // Marca el modal con val-alert--destructive para que el CSS pinte
28815
+ // el texto del botón destructivo en rojo (sin tocar el fondo).
28816
+ const extraClass = 'val-alert--destructive';
28817
+ const userClasses = options.cssClass
28818
+ ? Array.isArray(options.cssClass)
28819
+ ? options.cssClass
28820
+ : [options.cssClass]
28821
+ : [];
28822
+ return this.confirm({
28823
+ ...options,
28824
+ cssClass: [...userClasses, extraClass],
28825
+ confirmButton: {
28826
+ text: options.confirmButton?.text || 'Delete',
28827
+ role: 'destructive',
28828
+ ...options.confirmButton,
28829
+ },
28830
+ });
28831
+ }
28832
+ /**
28833
+ * Shows an info alert with just an OK button.
28834
+ * @param title - Alert title
28835
+ * @param message - Alert message
28836
+ */
28837
+ async alert(title, message) {
28838
+ const alert = await this.alertController.create({
28839
+ header: title,
28840
+ message,
28841
+ buttons: [{ text: 'OK', role: 'confirm' }],
28842
+ });
28843
+ await alert.present();
28844
+ await alert.onDidDismiss();
28845
+ }
28846
+ /**
28847
+ * Shows a three-option dialog (Save, Discard, Cancel).
28848
+ * Common for unsaved changes scenarios.
28849
+ * @param title - Dialog title
28850
+ * @param message - Dialog message
28851
+ * @returns Promise resolving to 'save' | 'discard' | 'cancel'
28852
+ */
28853
+ async confirmSaveDiscard(title, message) {
28854
+ const alert = await this.alertController.create({
28855
+ header: title,
28856
+ message,
28857
+ backdropDismiss: false,
28858
+ buttons: [
28859
+ {
28860
+ text: 'Cancel',
28861
+ role: 'cancel',
28862
+ },
28863
+ {
28864
+ text: 'Discard',
28865
+ role: 'destructive',
28866
+ cssClass: 'text-danger',
28867
+ },
28868
+ {
28869
+ text: 'Save',
28870
+ role: 'confirm',
28871
+ },
28872
+ ],
28873
+ });
28874
+ await alert.present();
28875
+ const { role } = await alert.onDidDismiss();
28876
+ if (role === 'confirm')
28877
+ return 'save';
28878
+ if (role === 'destructive')
28879
+ return 'discard';
28880
+ return 'cancel';
28881
+ }
28882
+ buildButtons(options) {
28883
+ const buttons = [];
28884
+ // Cancel button — sin color ionic por default (el CSS de val-alert se encarga)
28885
+ const cancelBtn = options.cancelButton || DEFAULT_CANCEL_BUTTON;
28886
+ buttons.push({
28887
+ text: cancelBtn.text,
28888
+ role: cancelBtn.role || 'cancel',
28889
+ cssClass: cancelBtn.cssClass,
28890
+ handler: cancelBtn.handler,
28891
+ });
28892
+ // Extra buttons (if any)
28893
+ if (options.extraButtons) {
28894
+ options.extraButtons.forEach(btn => {
28895
+ buttons.push({
28896
+ text: btn.text,
28897
+ role: btn.role,
28898
+ cssClass: btn.cssClass,
28899
+ handler: btn.handler,
28900
+ });
28901
+ });
28902
+ }
28903
+ // Confirm button
28904
+ const confirmBtn = options.confirmButton || DEFAULT_CONFIRM_BUTTON;
28905
+ buttons.push({
28906
+ text: confirmBtn.text,
28907
+ role: confirmBtn.role || 'confirm',
28908
+ cssClass: confirmBtn.cssClass,
28909
+ handler: confirmBtn.handler,
28910
+ });
28911
+ return buttons;
28912
+ }
28913
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
28914
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, providedIn: 'root' }); }
28915
+ }
28916
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, decorators: [{
28917
+ type: Injectable,
28918
+ args: [{
28919
+ providedIn: 'root',
28920
+ }]
28921
+ }] });
28922
+
28740
28923
  /**
28741
28924
  * Servicio principal de autenticación.
28742
28925
  *
@@ -28760,7 +28943,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
28760
28943
  * ```
28761
28944
  */
28762
28945
  class AuthService {
28763
- constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, oauthService, messagingService, i18nService) {
28946
+ constructor(config, http, router, stateService, tokenService, storageService, syncService, firebaseService, oauthService, messagingService, i18nService, confirmationService) {
28764
28947
  this.config = config;
28765
28948
  this.http = http;
28766
28949
  this.router = router;
@@ -28772,6 +28955,7 @@ class AuthService {
28772
28955
  this.oauthService = oauthService;
28773
28956
  this.messagingService = messagingService;
28774
28957
  this.i18nService = i18nService;
28958
+ this.confirmationService = confirmationService;
28775
28959
  // Timer para refresh proactivo
28776
28960
  this.refreshTimerId = null;
28777
28961
  this.syncSubscription = null;
@@ -29097,6 +29281,34 @@ class AuthService {
29097
29281
  tokenType: 'Bearer',
29098
29282
  });
29099
29283
  }
29284
+ /**
29285
+ * Cierra sesión tras pedir confirmación al usuario.
29286
+ *
29287
+ * Muestra un diálogo nativo (estilo destructivo). Si el usuario confirma,
29288
+ * ejecuta `logout()`. Si cancela, no pasa nada.
29289
+ *
29290
+ * Texto por defecto desde `_global` i18n: `logoutConfirmTitle`,
29291
+ * `logoutConfirmMessage`, `logout`, `cancel`. Override via `opts`.
29292
+ *
29293
+ * @returns true si el usuario confirmó (logout ejecutado), false si canceló.
29294
+ *
29295
+ * @example
29296
+ * onLogoutClick() { this.auth.logoutWithConfirmation(); }
29297
+ */
29298
+ async logoutWithConfirmation(opts) {
29299
+ const t = (key, fallback) => this.i18nService?.t(key) || fallback;
29300
+ const result = await this.confirmationService.confirmDestructive({
29301
+ title: opts?.title ?? t('logoutConfirmTitle', 'Cerrar sesión'),
29302
+ message: opts?.message ?? t('logoutConfirmMessage', '¿Seguro que deseas cerrar sesión?'),
29303
+ confirmButton: { text: opts?.confirmText ?? t('logout', 'Cerrar sesión') },
29304
+ cancelButton: { text: opts?.cancelText ?? t('cancel', 'Cancelar') },
29305
+ });
29306
+ if (result.confirmed) {
29307
+ await this.logout();
29308
+ return true;
29309
+ }
29310
+ return false;
29311
+ }
29100
29312
  /**
29101
29313
  * Cierra sesión.
29102
29314
  */
@@ -29811,7 +30023,7 @@ class AuthService {
29811
30023
  }
29812
30024
  return { platform: 'web', browser, os };
29813
30025
  }
29814
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: OAuthService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable }); }
30026
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, deps: [{ token: VALTECH_AUTH_CONFIG }, { token: i1$8.HttpClient }, { token: i1$1.Router }, { token: AuthStateService }, { token: TokenService }, { token: AuthStorageService }, { token: AuthSyncService }, { token: FirebaseService }, { token: OAuthService }, { token: MessagingService, optional: true }, { token: I18nService, optional: true }, { token: ConfirmationDialogService }], target: i0.ɵɵFactoryTarget.Injectable }); }
29815
30027
  static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, providedIn: 'root' }); }
29816
30028
  }
29817
30029
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: AuthService, decorators: [{
@@ -29824,7 +30036,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
29824
30036
  type: Optional
29825
30037
  }] }, { type: I18nService, decorators: [{
29826
30038
  type: Optional
29827
- }] }] });
30039
+ }] }, { type: ConfirmationDialogService }] });
29828
30040
 
29829
30041
  // Control de estado de refresco (singleton a nivel de módulo)
29830
30042
  let isRefreshing = false;
@@ -33838,196 +34050,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
33838
34050
  // ValtechConfig and LangProvider have been removed in v3.0.0
33839
34051
  // Use LocaleService for language management instead
33840
34052
 
33841
- /**
33842
- * Default confirmation dialog options.
33843
- */
33844
- const DEFAULT_CONFIRM_BUTTON = {
33845
- text: 'Confirm',
33846
- role: 'confirm',
33847
- color: 'primary',
33848
- };
33849
- const DEFAULT_CANCEL_BUTTON = {
33850
- text: 'Cancel',
33851
- role: 'cancel',
33852
- color: 'medium',
33853
- };
33854
-
33855
- /**
33856
- * Service for displaying confirmation dialogs.
33857
- *
33858
- * @example Basic usage
33859
- * const result = await confirmationService.confirm({
33860
- * title: 'Delete Item',
33861
- * message: 'Are you sure you want to delete this item?',
33862
- * });
33863
- *
33864
- * if (result.confirmed) {
33865
- * // User confirmed
33866
- * }
33867
- *
33868
- * @example Custom buttons
33869
- * const result = await confirmationService.confirm({
33870
- * title: 'Save Changes',
33871
- * message: 'Do you want to save your changes before leaving?',
33872
- * confirmButton: { text: 'Save', color: 'success' },
33873
- * cancelButton: { text: 'Discard', color: 'danger' },
33874
- * extraButtons: [{ text: 'Cancel', role: 'cancel' }]
33875
- * });
33876
- *
33877
- * @example Destructive action
33878
- * const result = await confirmationService.confirmDestructive({
33879
- * title: 'Delete Account',
33880
- * message: 'This action cannot be undone. Are you sure?',
33881
- * });
33882
- */
33883
- class ConfirmationDialogService {
33884
- constructor() {
33885
- this.alertController = inject(AlertController);
33886
- }
33887
- /**
33888
- * Shows a confirmation dialog and returns the result.
33889
- * @param options - Configuration for the dialog
33890
- * @returns Promise resolving to the confirmation result
33891
- */
33892
- async confirm(options) {
33893
- const buttons = this.buildButtons(options);
33894
- const alert = await this.alertController.create({
33895
- header: options.title,
33896
- subHeader: options.subHeader,
33897
- message: options.message,
33898
- buttons,
33899
- backdropDismiss: options.backdropDismiss ?? false,
33900
- cssClass: options.cssClass,
33901
- mode: options.mode,
33902
- translucent: options.translucent ?? false,
33903
- animated: options.animated ?? true,
33904
- });
33905
- await alert.present();
33906
- const { role, data } = await alert.onDidDismiss();
33907
- return {
33908
- confirmed: role === 'confirm',
33909
- role,
33910
- data,
33911
- };
33912
- }
33913
- /**
33914
- * Shows a simple confirmation dialog with default buttons.
33915
- * @param title - Dialog title
33916
- * @param message - Dialog message
33917
- * @returns Promise resolving to true if confirmed, false otherwise
33918
- */
33919
- async confirmSimple(title, message) {
33920
- const result = await this.confirm({ title, message });
33921
- return result.confirmed;
33922
- }
33923
- /**
33924
- * Shows a destructive action confirmation with red confirm button.
33925
- * @param options - Configuration for the dialog
33926
- * @returns Promise resolving to the confirmation result
33927
- */
33928
- async confirmDestructive(options) {
33929
- return this.confirm({
33930
- ...options,
33931
- confirmButton: {
33932
- text: options.confirmButton?.text || 'Delete',
33933
- role: 'destructive',
33934
- color: 'danger',
33935
- cssClass: 'destructive-button',
33936
- ...options.confirmButton,
33937
- },
33938
- });
33939
- }
33940
- /**
33941
- * Shows an info alert with just an OK button.
33942
- * @param title - Alert title
33943
- * @param message - Alert message
33944
- */
33945
- async alert(title, message) {
33946
- const alert = await this.alertController.create({
33947
- header: title,
33948
- message,
33949
- buttons: [{ text: 'OK', role: 'confirm' }],
33950
- });
33951
- await alert.present();
33952
- await alert.onDidDismiss();
33953
- }
33954
- /**
33955
- * Shows a three-option dialog (Save, Discard, Cancel).
33956
- * Common for unsaved changes scenarios.
33957
- * @param title - Dialog title
33958
- * @param message - Dialog message
33959
- * @returns Promise resolving to 'save' | 'discard' | 'cancel'
33960
- */
33961
- async confirmSaveDiscard(title, message) {
33962
- const alert = await this.alertController.create({
33963
- header: title,
33964
- message,
33965
- backdropDismiss: false,
33966
- buttons: [
33967
- {
33968
- text: 'Cancel',
33969
- role: 'cancel',
33970
- },
33971
- {
33972
- text: 'Discard',
33973
- role: 'destructive',
33974
- cssClass: 'text-danger',
33975
- },
33976
- {
33977
- text: 'Save',
33978
- role: 'confirm',
33979
- },
33980
- ],
33981
- });
33982
- await alert.present();
33983
- const { role } = await alert.onDidDismiss();
33984
- if (role === 'confirm')
33985
- return 'save';
33986
- if (role === 'destructive')
33987
- return 'discard';
33988
- return 'cancel';
33989
- }
33990
- buildButtons(options) {
33991
- const buttons = [];
33992
- // Cancel button
33993
- const cancelBtn = options.cancelButton || DEFAULT_CANCEL_BUTTON;
33994
- buttons.push({
33995
- text: cancelBtn.text,
33996
- role: cancelBtn.role || 'cancel',
33997
- cssClass: cancelBtn.cssClass || `button-${cancelBtn.color || 'medium'}`,
33998
- handler: cancelBtn.handler,
33999
- });
34000
- // Extra buttons (if any)
34001
- if (options.extraButtons) {
34002
- options.extraButtons.forEach((btn) => {
34003
- buttons.push({
34004
- text: btn.text,
34005
- role: btn.role,
34006
- cssClass: btn.cssClass || `button-${btn.color || 'medium'}`,
34007
- handler: btn.handler,
34008
- });
34009
- });
34010
- }
34011
- // Confirm button
34012
- const confirmBtn = options.confirmButton || DEFAULT_CONFIRM_BUTTON;
34013
- buttons.push({
34014
- text: confirmBtn.text,
34015
- role: confirmBtn.role || 'confirm',
34016
- cssClass: confirmBtn.cssClass || `button-${confirmBtn.color || 'primary'}`,
34017
- handler: confirmBtn.handler,
34018
- });
34019
- return buttons;
34020
- }
34021
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
34022
- static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, providedIn: 'root' }); }
34023
- }
34024
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ConfirmationDialogService, decorators: [{
34025
- type: Injectable,
34026
- args: [{
34027
- providedIn: 'root',
34028
- }]
34029
- }] });
34030
-
34031
34053
  /**
34032
34054
  * Default modal sizes.
34033
34055
  */