shared-lib-angular 2.0.2 → 2.0.4

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.
@@ -20,14 +20,14 @@ import { CommonModule } from '@angular/common';
20
20
  * Versión actual de la librería @dinafi/frmk
21
21
  * Sincronizada con package.json
22
22
  */
23
- const VERSION = '2.0.2';
23
+ const VERSION = '2.0.4';
24
24
  /**
25
25
  * Información completa de la versión
26
26
  */
27
27
  const VERSION_INFO = {
28
- version: '2.0.2',
28
+ version: '2.0.4',
29
29
  name: 'shared-lib-angular',
30
- buildDate: '2026-02-05T20:47:48.315Z',
30
+ buildDate: '2026-02-09T22:43:40.953Z',
31
31
  angular: '^20.0.0'
32
32
  };
33
33
 
@@ -340,19 +340,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
340
340
  args: [{ providedIn: 'root' }]
341
341
  }] });
342
342
 
343
+ /**
344
+ * Keys de tokens OAuth/OIDC manejados por angular-oauth2-oidc y Keycloak.
345
+ * Única fuente de verdad — elimina listas duplicadas e inconsistentes.
346
+ */
347
+ const TOKEN_KEYS = [
348
+ 'access_token',
349
+ 'refresh_token',
350
+ 'id_token',
351
+ 'token_type',
352
+ 'expires_at',
353
+ 'nonce',
354
+ 'PKCE_verifier',
355
+ 'code_verifier',
356
+ 'session_state',
357
+ 'id_token_claims_obj',
358
+ 'id_token_expires_at',
359
+ 'id_token_stored_at',
360
+ 'access_token_stored_at'
361
+ ];
362
+ /**
363
+ * Keys de estado OAuth que deben preservarse durante un callback
364
+ * (necesarias para validar code + state en el intercambio PKCE).
365
+ */
366
+ const STATE_KEYS = new Set([
367
+ 'nonce',
368
+ 'PKCE_verifier',
369
+ 'code_verifier',
370
+ 'session_state',
371
+ 'state'
372
+ ]);
373
+ /**
374
+ * Servicio centralizado para gestión de tokens en localStorage / sessionStorage.
375
+ *
376
+ * - Elimina las 4+ listas de keys duplicadas e inconsistentes que existían en AuthService.
377
+ * - Opera simétricamente sobre ambos storages.
378
+ * - Soporta la key dinámica `refresh_token_{clientId}` que usa angular-oauth2-oidc.
379
+ * - Expone métodos públicos para que la plantilla pueda leer tokens sin acceder a storage directamente.
380
+ *
381
+ * @example
382
+ * // Desde la librería (AuthService):
383
+ * this.tokenStorage.clearAll();
384
+ *
385
+ * // Desde la plantilla (skeleton):
386
+ * const expired = this.tokenStorage.isTokenExpired();
387
+ */
388
+ class TokenStorageService {
389
+ // ── Limpieza ──────────────────────────────────────────────────────
390
+ /**
391
+ * Limpia TODOS los tokens y estado OAuth de ambos storages.
392
+ * Incluye la key dinámica `refresh_token_{clientId}` si se proporciona.
393
+ *
394
+ * @param clientId — clientId de OAuth para limpiar `refresh_token_{clientId}` (opcional)
395
+ */
396
+ clearAll(clientId) {
397
+ TOKEN_KEYS.forEach(key => {
398
+ localStorage.removeItem(key);
399
+ sessionStorage.removeItem(key);
400
+ });
401
+ // Key dinámica de angular-oauth2-oidc
402
+ if (clientId) {
403
+ localStorage.removeItem(`refresh_token_${clientId}`);
404
+ sessionStorage.removeItem(`refresh_token_${clientId}`);
405
+ }
406
+ // También limpiar 'state' que no está en TOKEN_KEYS pero puede persistir
407
+ localStorage.removeItem('state');
408
+ sessionStorage.removeItem('state');
409
+ }
410
+ /**
411
+ * Limpia tokens pero MANTIENE las keys de estado OAuth necesarias para
412
+ * validar el callback (nonce, PKCE_verifier, code_verifier, session_state, state).
413
+ *
414
+ * Usar durante un callback OAuth antes de procesar el authorization code.
415
+ */
416
+ clearTokensKeepState() {
417
+ TOKEN_KEYS.forEach(key => {
418
+ if (!STATE_KEYS.has(key)) {
419
+ localStorage.removeItem(key);
420
+ sessionStorage.removeItem(key);
421
+ }
422
+ });
423
+ console.log('[TokenStorageService] Cleared tokens, kept state/nonce/PKCE for callback validation');
424
+ }
425
+ // ── Lectura ───────────────────────────────────────────────────────
426
+ /**
427
+ * Lee un valor de localStorage o sessionStorage (en ese orden de prioridad).
428
+ */
429
+ getItem(key) {
430
+ return localStorage.getItem(key) ?? sessionStorage.getItem(key);
431
+ }
432
+ /**
433
+ * Obtiene el refresh_token, incluyendo la variante dinámica `refresh_token_{clientId}`.
434
+ *
435
+ * @param clientId — clientId de OAuth (opcional)
436
+ */
437
+ getRefreshToken(clientId) {
438
+ const token = this.getItem('refresh_token');
439
+ if (token)
440
+ return token;
441
+ if (clientId) {
442
+ return localStorage.getItem(`refresh_token_${clientId}`)
443
+ ?? sessionStorage.getItem(`refresh_token_${clientId}`)
444
+ ?? null;
445
+ }
446
+ return null;
447
+ }
448
+ /**
449
+ * Obtiene el access_token de cualquier storage.
450
+ */
451
+ getAccessToken() {
452
+ return this.getItem('access_token');
453
+ }
454
+ // ── Estado ────────────────────────────────────────────────────────
455
+ /**
456
+ * Verifica si hay un token almacenado y está expirado.
457
+ * Retorna `false` si no existe `expires_at` (no hay token, no está "expirado").
458
+ */
459
+ isTokenExpired() {
460
+ const expiresAt = this.getItem('expires_at');
461
+ if (!expiresAt) {
462
+ return false;
463
+ }
464
+ return parseInt(expiresAt, 10) < Date.now();
465
+ }
466
+ // ── Utilidades ────────────────────────────────────────────────────
467
+ /**
468
+ * Elimina una key específica de ambos storages.
469
+ */
470
+ removeItem(key) {
471
+ localStorage.removeItem(key);
472
+ sessionStorage.removeItem(key);
473
+ }
474
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TokenStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
475
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TokenStorageService, providedIn: 'root' });
476
+ }
477
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: TokenStorageService, decorators: [{
478
+ type: Injectable,
479
+ args: [{ providedIn: 'root' }]
480
+ }] });
481
+
343
482
  class AuthService {
344
483
  oauthService;
345
484
  router;
346
485
  store;
486
+ tokenStorage;
347
487
  isAuthenticatedSubject$ = new BehaviorSubject(false);
348
488
  isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
349
489
  isDoneLoadingSubject$ = new BehaviorSubject(false);
350
490
  isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();
351
491
  isInitialLogin = true;
352
- constructor(oauthService, router, store) {
492
+ constructor(oauthService, router, store, tokenStorage) {
353
493
  this.oauthService = oauthService;
354
494
  this.router = router;
355
495
  this.store = store;
496
+ this.tokenStorage = tokenStorage;
356
497
  // IMPORTANTE: Limpiar tokens expirados ANTES de cualquier otra operación
357
498
  this.clearExpiredTokensOnStartup();
358
499
  this.oauthService.events
@@ -392,48 +533,11 @@ class AuthService {
392
533
  return;
393
534
  }
394
535
  // Verificar si hay un token y si está expirado
395
- const expiresAt = localStorage.getItem('expires_at') || sessionStorage.getItem('expires_at');
396
- if (expiresAt) {
397
- const expirationTime = parseInt(expiresAt, 10);
398
- const now = Date.now();
399
- if (expirationTime < now) {
400
- console.log('[AuthService] Expired token detected, clearing storage');
401
- this.clearAllTokens();
402
- }
536
+ if (this.tokenStorage.isTokenExpired()) {
537
+ console.log('[AuthService] Expired token detected, clearing storage');
538
+ this.tokenStorage.clearAll();
403
539
  }
404
540
  }
405
- /**
406
- * Limpia todos los tokens del storage
407
- */
408
- clearAllTokens() {
409
- const keysToRemove = [
410
- 'access_token', 'refresh_token', 'id_token', 'token_type',
411
- 'expires_at', 'nonce', 'PKCE_verifier', 'session_state',
412
- 'id_token_claims_obj', 'id_token_expires_at', 'id_token_stored_at',
413
- 'access_token_stored_at'
414
- ];
415
- keysToRemove.forEach(key => {
416
- localStorage.removeItem(key);
417
- sessionStorage.removeItem(key);
418
- });
419
- }
420
- /**
421
- * Limpia tokens antiguos pero MANTIENE state, nonce y PKCE_verifier
422
- * que son necesarios para validar el callback OAuth
423
- */
424
- clearOldTokensKeepState() {
425
- // Solo limpiar tokens, NO state/nonce/PKCE_verifier
426
- const keysToRemove = [
427
- 'access_token', 'refresh_token', 'id_token', 'token_type',
428
- 'expires_at', 'id_token_claims_obj', 'id_token_expires_at',
429
- 'id_token_stored_at', 'access_token_stored_at'
430
- ];
431
- keysToRemove.forEach(key => {
432
- localStorage.removeItem(key);
433
- sessionStorage.removeItem(key);
434
- });
435
- console.log('[AuthService] Cleared old tokens, kept state/nonce/PKCE for callback validation');
436
- }
437
541
  handleSuccessfulAuthentication() {
438
542
  if (this.oauthService.hasValidAccessToken()) {
439
543
  this.isAuthenticatedSubject$.next(true);
@@ -484,28 +588,23 @@ class AuthService {
484
588
  this.isDoneLoadingSubject$.next(true);
485
589
  }
486
590
  /**
487
- * Limpia todos los tokens del storage
591
+ * Limpia todos los tokens del storage y resetea OAuthService
488
592
  */
489
593
  clearTokenStorage() {
490
594
  this.oauthService.logOut(true); // true = no redirect, solo limpiar
491
- localStorage.removeItem('access_token');
492
- localStorage.removeItem('refresh_token');
493
- localStorage.removeItem('id_token');
494
- localStorage.removeItem('nonce');
495
- localStorage.removeItem('expires_at');
496
- sessionStorage.removeItem('access_token');
497
- sessionStorage.removeItem('refresh_token');
498
- sessionStorage.removeItem('id_token');
595
+ this.tokenStorage.clearAll();
499
596
  }
500
597
  /**
501
598
  * Verifica si el token actual está expirado
502
599
  */
503
600
  isTokenExpired() {
601
+ // Primero verificar vía OAuthService (tokens en memoria)
504
602
  const expiresAt = this.oauthService.getAccessTokenExpiration();
505
- if (!expiresAt) {
506
- return false; // Si no hay token, no está "expirado", simplemente no existe
603
+ if (expiresAt) {
604
+ return expiresAt < Date.now();
507
605
  }
508
- return expiresAt < Date.now();
606
+ // Fallback: verificar en storage
607
+ return this.tokenStorage.isTokenExpired();
509
608
  }
510
609
  async runInitialLoginSequence() {
511
610
  try {
@@ -529,7 +628,7 @@ class AuthService {
529
628
  if (isOAuthCallback) {
530
629
  console.log('[AuthService] OAuth callback detected, clearing old tokens before processing');
531
630
  // Limpiar solo tokens, NO el state/nonce que necesitamos para validar el callback
532
- this.clearOldTokensKeepState();
631
+ this.tokenStorage.clearTokensKeepState();
533
632
  this.isInitialLogin = true;
534
633
  await this.oauthService.tryLoginCodeFlow();
535
634
  }
@@ -571,18 +670,16 @@ class AuthService {
571
670
  if (hasCode) {
572
671
  console.log('[AuthService] OAuth callback detected: resetting OAuthService state');
573
672
  // Limpiar storage EXCEPTO state/nonce/PKCE
574
- this.clearOldTokensKeepState();
673
+ this.tokenStorage.clearTokensKeepState();
575
674
  // CRÍTICO: Resetear propiedades internas de OAuthService
576
675
  // para que no valide tokens viejos en memoria
577
676
  this.resetOAuthServiceInternalState();
578
677
  }
579
678
  else {
580
679
  // No es callback - verificar si hay tokens expirados
581
- const expiresAt = localStorage.getItem('expires_at');
582
- const isExpired = expiresAt && parseInt(expiresAt, 10) < Date.now();
583
- if (isExpired) {
680
+ if (this.tokenStorage.isTokenExpired()) {
584
681
  console.log('[AuthService] Clearing all expired tokens before configure');
585
- this.clearAllTokens();
682
+ this.tokenStorage.clearAll();
586
683
  this.resetOAuthServiceInternalState();
587
684
  }
588
685
  }
@@ -640,9 +737,7 @@ class AuthService {
640
737
  const logoutUrl = authConfig.logoutUrl;
641
738
  let refreshToken = this.oauthService.getRefreshToken();
642
739
  if (!refreshToken) {
643
- refreshToken = localStorage.getItem('refresh_token')
644
- || localStorage.getItem('refresh_token_' + authConfig.clientId)
645
- || sessionStorage.getItem('refresh_token');
740
+ refreshToken = this.tokenStorage.getRefreshToken(authConfig.clientId);
646
741
  }
647
742
  if (!logoutUrl) {
648
743
  console.warn('Logout URL not configured, skipping Keycloak logout');
@@ -675,20 +770,9 @@ class AuthService {
675
770
  }
676
771
  }
677
772
  performLocalLogout() {
773
+ const authConfig = this.store.authConfig();
678
774
  this.oauthService.logOut(true);
679
- // Limpiar tokens del localStorage
680
- localStorage.removeItem('access_token');
681
- localStorage.removeItem('id_token');
682
- localStorage.removeItem('refresh_token');
683
- localStorage.removeItem('code_verifier');
684
- localStorage.removeItem('nonce');
685
- localStorage.removeItem('PKCE_verifier');
686
- // Limpiar tokens del sessionStorage
687
- sessionStorage.removeItem('access_token');
688
- sessionStorage.removeItem('id_token');
689
- sessionStorage.removeItem('refresh_token');
690
- sessionStorage.removeItem('nonce');
691
- sessionStorage.removeItem('PKCE_verifier');
775
+ this.tokenStorage.clearAll(authConfig.clientId);
692
776
  this.isAuthenticatedSubject$.next(false);
693
777
  this.isDoneLoadingSubject$.next(true);
694
778
  const currentUrl = this.router.url;
@@ -765,7 +849,7 @@ class AuthService {
765
849
  if (accessToken) {
766
850
  try {
767
851
  const payload = this.decodeJwtPayload(accessToken);
768
- console.log('[AuthService] Access token payload:', payload);
852
+ // console.log('[AuthService] Access token payload:', payload);
769
853
  // Extract groups from access token
770
854
  if (payload.groups) {
771
855
  if (Array.isArray(payload.groups)) {
@@ -794,7 +878,7 @@ class AuthService {
794
878
  }
795
879
  }
796
880
  const uniqueGroups = [...new Set(groups)];
797
- console.log('[AuthService] getUserGroups result:', uniqueGroups);
881
+ // console.log('[AuthService] getUserGroups result:', uniqueGroups);
798
882
  return uniqueGroups;
799
883
  }
800
884
  decodeJwtPayload(token) {
@@ -812,7 +896,7 @@ class AuthService {
812
896
  return {};
813
897
  }
814
898
  }
815
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, deps: [{ token: i1.OAuthService }, { token: i4.Router }, { token: FrmkConfigStore }], target: i0.ɵɵFactoryTarget.Injectable });
899
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, deps: [{ token: i1.OAuthService }, { token: i4.Router }, { token: FrmkConfigStore }, { token: TokenStorageService }], target: i0.ɵɵFactoryTarget.Injectable });
816
900
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, providedIn: 'root' });
817
901
  }
818
902
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, decorators: [{
@@ -820,7 +904,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
820
904
  args: [{
821
905
  providedIn: 'root'
822
906
  }]
823
- }], ctorParameters: () => [{ type: i1.OAuthService }, { type: i4.Router }, { type: FrmkConfigStore }] });
907
+ }], ctorParameters: () => [{ type: i1.OAuthService }, { type: i4.Router }, { type: FrmkConfigStore }, { type: TokenStorageService }] });
824
908
 
825
909
  class ConfigService {
826
910
  http;
@@ -1405,11 +1489,11 @@ class DashboardComponent {
1405
1489
  document.body.style.overflow = '';
1406
1490
  }
1407
1491
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DashboardComponent, deps: [{ token: AuthService }, { token: AuthorizationService }], target: i0.ɵɵFactoryTarget.Component });
1408
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DashboardComponent, isStandalone: true, selector: "lib-dashboard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<div class=\"dashboard\">\r\n <!-- Header -->\r\n <header class=\"dashboard-header\">\r\n <!-- Mobile Header Top -->\r\n <div class=\"header-mobile-top\">\r\n <div class=\"ministry-logo-mobile\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <h1 class=\"mobile-title\">{{ getConfig('title') }}</h1>\r\n </div>\r\n\r\n <!-- Mobile Header Bottom -->\r\n <div class=\"header-mobile-bottom\">\r\n <button class=\"hamburger-btn\" \r\n (click)=\"toggleMobileMenu()\"\r\n [class.active]=\"isMobileMenuOpen\"\r\n aria-label=\"Toggle menu\">\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n </button>\r\n\r\n <div class=\"user-profile mobile-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" \r\n (click)=\"toggleUserPanel($event)\"\r\n [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n \r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Desktop Header -->\r\n <div class=\"header-desktop-layout\">\r\n <div class=\"header-left\">\r\n <div class=\"ministry-logo\">\r\n <div class=\"logo-icon\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <div class=\"ministry-info\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <span class=\"subtitle\" *ngIf=\"getConfig('subtitle')\">{{ getConfig('subtitle') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-right\">\r\n <div class=\"user-profile desktop-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" \r\n (click)=\"toggleUserPanel($event)\"\r\n [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n \r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n <div class=\"user-panel\" *ngIf=\"isUserPanelOpen\">\r\n <div class=\"user-panel-header\">\r\n <span class=\"panel-title\">Perfil de Usuario</span>\r\n </div>\r\n <div class=\"user-panel-content\">\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">person</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">C\u00F3digo de Usuario:</span>\r\n <span class=\"info-value\">{{ getUsername() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">badge</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Nombre de Usuario:</span>\r\n <span class=\"info-value\">{{ getUserFullName() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">credit_card</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Documento:</span>\r\n <span class=\"info-value\">{{ getDocument() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">business</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Instituci\u00F3n:</span>\r\n <span class=\"info-value\">{{ getInstitution() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">apartment</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Dependencia:</span>\r\n <span class=\"info-value\">{{ getDependency() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">work</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Rol:</span>\r\n <span class=\"info-value\">{{ getRole() }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"user-panel-footer\">\r\n <button class=\"logout-btn-panel\" (click)=\"logoutAndRedirect()\" title=\"Cerrar sesi\u00F3n\">\r\n <span class=\"material-symbols-outlined\">logout</span>\r\n <span class=\"logout-text\">CERRAR SESI\u00D3N</span>\r\n </button>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n <div class=\"nav-overlay\" *ngIf=\"isMobileMenuOpen\" (click)=\"closeMobileMenu()\"></div>\r\n\r\n <!-- Navigation (Horizontal Desktop / Sidebar Mobile) -->\r\n <nav class=\"dashboard-nav\" [class.mobile-open]=\"isMobileMenuOpen\">\r\n <div class=\"nav-mobile-header\">\r\n <div class=\"nav-mobile-logo\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n <span class=\"nav-mobile-title\">Men\u00FA</span>\r\n </div>\r\n <button class=\"nav-close-btn\" (click)=\"closeMobileMenu()\" aria-label=\"Cerrar men\u00FA\">\r\n <span class=\"material-symbols-outlined\">close</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"nav-container\">\r\n <!-- Static Menu Items -->\r\n <ng-container *ngIf=\"getConfig('showStaticMenu')\">\r\n <a routerLink=\".\" \r\n routerLinkActive=\"active\" \r\n [routerLinkActiveOptions]=\"{exact: true}\" \r\n class=\"nav-item\"\r\n (click)=\"closeMobileMenu()\">\r\n <span class=\"material-symbols-outlined nav-icon\">home</span>\r\n <span class=\"nav-text\">Inicio</span>\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Separator -->\r\n <div class=\"nav-separator\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\"></div>\r\n \r\n <!-- Dynamic Menu -->\r\n <div class=\"dynamic-menu\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\">\r\n <ng-container *ngFor=\"let rootItem of getRootMenuItems()\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(rootItem)\">\r\n <a \r\n [routerLink]=\"hasChildren(rootItem) ? null : rootItem.path\" \r\n routerLinkActive=\"active\" \r\n class=\"nav-item root-item\"\r\n [class.expandable]=\"hasChildren(rootItem)\"\r\n (click)=\"onMenuItemClick($event, rootItem)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(rootItem) }}</span>\r\n <span class=\"nav-text\">{{ rootItem.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(rootItem)\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n \r\n <!-- Submenu -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(rootItem) && isMenuItemOpen(rootItem.code)\">\r\n <ng-container *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n \r\n <!-- Loading Indicator -->\r\n <div class=\"menu-loading\" *ngIf=\"isLoadingMenu\">\r\n <span class=\"material-symbols-outlined nav-icon\">hourglass_empty</span>\r\n <span class=\"nav-text\">Cargando men\u00FA...</span>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Recursive Menu Template -->\r\n <ng-template #menuTemplate let-items=\"items\" let-level=\"level\">\r\n <ng-container *ngFor=\"let item of items\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(item)\" [attr.data-level]=\"level\">\r\n <a \r\n [routerLink]=\"hasChildren(item) ? null : item.path\" \r\n routerLinkActive=\"active\" \r\n class=\"nav-item submenu-item\"\r\n [class.expandable]=\"hasChildren(item)\"\r\n (click)=\"onMenuItemClick($event, item)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(item) }}</span>\r\n <span class=\"nav-text\">{{ item.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(item)\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(item) && isMenuItemOpen(item.code) && level < 5\">\r\n <ng-container *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- Main Content -->\r\n <main class=\"dashboard-content\">\r\n <div class=\"content-wrapper\">\r\n <router-outlet></router-outlet>\r\n </div>\r\n </main>\r\n\r\n <!-- Footer -->\r\n <footer class=\"dashboard-footer\">\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n {{ getConfig('footerText') }}\r\n <ng-container *ngIf=\"getConfig('showVersion')\">\r\n Versi\u00F3n {{ appVersion }}.\r\n </ng-container>\r\n Todos los derechos reservados.\r\n </span>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-height:100vh}.dashboard{min-height:100vh;display:flex;flex-direction:column;background:var(--color-primary, #313945);font-family:var(--font-family-primary, \"Museo Sans\", sans-serif)}.dashboard-header{background:var(--color-primary, #313945);color:var(--color-white, #ffffff);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);border-bottom:1px solid rgba(255,255,255,.1);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:1000}@media(max-width:768px){.dashboard-header{flex-direction:column;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem);align-items:stretch}}.header-mobile-top{display:none}@media(max-width:768px){.header-mobile-top{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem)}}.ministry-logo-mobile{display:none}@media(max-width:768px){.ministry-logo-mobile{display:flex;align-items:center}.ministry-logo-mobile img{height:50px;width:auto;filter:brightness(0) invert(1)}}.mobile-title{display:none}@media(max-width:768px){.mobile-title{display:block;font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff);margin:0;text-align:right;flex:1}}.header-mobile-bottom{display:none}@media(max-width:768px){.header-mobile-bottom{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem);padding-top:var(--spacing-1, .25rem)}}.header-desktop-layout{display:flex;justify-content:space-between;align-items:center;width:100%}@media(max-width:768px){.header-desktop-layout{display:none}}.header-left{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.header-right{display:flex;align-items:center}.ministry-logo{display:flex;align-items:center;gap:var(--spacing-3, .75rem)}.ministry-logo .logo-icon{display:flex;width:80px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ministry-logo .logo-icon img{width:100%;height:auto}.ministry-logo .ministry-info h1{font-family:var(--font-family-primary, sans-serif);font-size:var(--font-size-xl, 1.25rem);font-weight:var(--font-weight-bold, 700);margin:0;color:var(--color-white, #ffffff);line-height:1.2}.ministry-logo .ministry-info .subtitle{font-size:var(--font-size-xs, .75rem);color:#ffffffd9;font-weight:var(--font-weight-medium, 500)}.hamburger-btn{display:none;flex-direction:column;justify-content:space-around;width:36px;height:36px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;padding:6px;z-index:1003;transition:all .3s ease;flex-shrink:0}@media(max-width:768px){.hamburger-btn{display:flex}}.hamburger-btn .hamburger-line{width:100%;height:2px;background:var(--color-white, #ffffff);border-radius:2px;transition:all .3s ease;transform-origin:center}.hamburger-btn.active{background:#fff3}.hamburger-btn.active .hamburger-line:nth-child(1){transform:translateY(7px) rotate(45deg)}.hamburger-btn.active .hamburger-line:nth-child(2){opacity:0}.hamburger-btn.active .hamburger-line:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.hamburger-btn:hover{background:#fff3}.user-profile{position:relative;display:flex;align-items:center}.user-profile .user-info-trigger{display:flex;align-items:center;gap:var(--spacing-2, .5rem);background:#ffffff1a;padding:var(--spacing-1, .25rem) var(--spacing-3, .75rem);border-radius:var(--border-radius-lg, .5rem);border:1px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s ease;color:inherit;font-family:inherit}.user-profile .user-info-trigger:hover{background:#ffffff26}.user-profile .user-avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--color-white, #ffffff) 0%,#e2e8f0 100%);display:flex;align-items:center;justify-content:center;font-weight:var(--font-weight-bold, 700);color:var(--color-primary, #313945);font-size:var(--font-size-sm, .875rem);box-shadow:0 2px 6px #0000001a}.user-profile .user-details{display:flex;flex-direction:column}.user-profile .user-details .user-name{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-white, #ffffff)}.user-profile .dropdown-icon{color:#fffc;font-size:18px;transition:transform .2s ease}.user-panel{position:absolute;top:calc(100% + var(--spacing-2, .5rem));right:0;min-width:320px;background:var(--color-white, #ffffff);border-radius:var(--border-radius-lg, .5rem);box-shadow:0 10px 40px #0003;border:1px solid var(--color-gray-200, #e9ecef);z-index:1100;overflow:hidden;animation:slideDown .2s ease}@media(max-width:768px){.user-panel{position:absolute;top:calc(100% + var(--spacing-1, .25rem));right:0;left:auto;min-width:280px;max-width:calc(100vw - 1rem);max-height:80vh;overflow-y:auto}}.user-panel-header{background:linear-gradient(135deg,#3c4557,#2a3142);padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem)}.user-panel-header .panel-title{font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff)}.user-panel-content{padding:var(--spacing-3, .75rem)}.user-info-item{display:flex;align-items:flex-start;gap:var(--spacing-3, .75rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);border-bottom:1px solid var(--color-gray-100, #f1f3f4)}.user-info-item:last-child{border-bottom:none}.user-info-item .info-icon{font-size:var(--font-size-lg, 1.125rem);flex-shrink:0;color:var(--color-primary, #313945)}.user-info-item .info-content{display:flex;flex-direction:column;gap:2px;flex:1}.user-info-item .info-content .info-label{font-size:var(--font-size-xs, .75rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-gray-600, #5a6268);text-transform:uppercase;letter-spacing:.5px}.user-info-item .info-content .info-value{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-gray-900, #212529);word-break:break-word}@media(max-width:768px){.mobile-hidden{display:none!important}}.user-panel-footer{padding:var(--spacing-3, .75rem);background:var(--color-gray-50, #f8f9fa);border-top:1px solid var(--color-gray-200, #e9ecef)}.user-panel-footer .logout-btn-panel{width:100%;background:linear-gradient(135deg,#dc2626,#b91c1c);color:var(--color-white, #ffffff);border:none;padding:var(--spacing-3, .75rem);border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-bold, 700);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:var(--spacing-2, .5rem);text-transform:uppercase}.user-panel-footer .logout-btn-panel:hover{background:linear-gradient(135deg,#b91c1c,#991b1b)}.nav-overlay{display:none}@media(max-width:768px){.nav-overlay{display:block;position:fixed;inset:0;background:#00000080;z-index:1001;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}}.dashboard-nav{background-color:#3c4557;box-shadow:0 2px 8px #0000001a}@media(max-width:768px){.dashboard-nav{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:85%;transform:translate(-100%);z-index:1002;overflow-y:auto;box-shadow:2px 0 10px #0000004d;transition:transform .3s ease}.dashboard-nav.mobile-open{transform:translate(0)}}.dashboard-nav .nav-mobile-header{display:none}@media(max-width:768px){.dashboard-nav .nav-mobile-header{display:flex;justify-content:space-between;align-items:center;padding:var(--spacing-3, .75rem);background:var(--color-primary, #313945);border-bottom:1px solid rgba(255,255,255,.1);position:sticky;top:0;z-index:10}}.dashboard-nav .nav-mobile-logo{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.dashboard-nav .nav-mobile-logo img{width:36px;height:auto;filter:brightness(0) invert(1)}.dashboard-nav .nav-mobile-logo .nav-mobile-title{color:var(--color-white, #ffffff);font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-semibold, 600)}.dashboard-nav .nav-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;color:var(--color-white, #ffffff)}.dashboard-nav .nav-close-btn:hover{background:#fff3}.nav-container{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-1, .25rem);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem)}@media(max-width:768px){.nav-container{flex-direction:column;align-items:stretch;padding:var(--spacing-3, .75rem);gap:var(--spacing-1, .25rem)}}.nav-item{display:flex;align-items:center;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);color:var(--color-white, #ffffff);text-decoration:none;border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);transition:all .2s ease;cursor:pointer;background:transparent;border:none;font-family:inherit;white-space:nowrap}.nav-item:hover{background:#ffffff1a}.nav-item.active{background:#fff3}.nav-item .nav-icon{font-size:18px;flex-shrink:0}.nav-item .nav-text{flex:1;text-align:left}.nav-item .expand-icon{font-size:16px;flex-shrink:0;margin-left:var(--spacing-1, .25rem)}@media(max-width:768px){.nav-item{width:100%;padding:var(--spacing-3, .75rem)}}.nav-separator{width:1px;height:20px;background:#fff3;margin:0 var(--spacing-1, .25rem);align-self:center}@media(max-width:768px){.nav-separator{width:100%;height:1px;margin:var(--spacing-2, .5rem) 0}}.dynamic-menu{display:flex;flex-wrap:wrap;gap:var(--spacing-1, .25rem);align-items:flex-start}@media(max-width:768px){.dynamic-menu{flex-direction:column;width:100%}}.menu-item-container{position:relative}@media(max-width:768px){.menu-item-container{width:100%}}@media(min-width:769px){.menu-item-container>.submenu{position:absolute;top:100%;left:0;min-width:220px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1000;margin-top:var(--spacing-1, .25rem)}}@media(max-width:768px){.menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}@media(min-width:769px){.submenu{min-width:200px}}@media(max-width:768px){.submenu{width:100%}}.submenu .menu-item-container{width:100%}@media(min-width:769px){.submenu .menu-item-container>.submenu{position:absolute;top:0;left:100%;min-width:200px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1001;margin-left:2px}}@media(max-width:768px){.submenu .menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}.submenu .submenu-item{padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);width:100%;box-sizing:border-box}.submenu .submenu-item:hover{background:#ffffff1a}.menu-loading{display:flex;align-items:center;gap:var(--spacing-2, .5rem);color:#ffffffb3;padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);font-size:var(--font-size-sm, .875rem)}.dashboard-content{flex:1;background:var(--color-white, #ffffff)}.dashboard-content .content-wrapper{padding:var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);max-width:1400px;margin:0 auto}@media(max-width:768px){.dashboard-content .content-wrapper{padding:var(--spacing-3, .75rem)}}.dashboard-footer{background:#3c4557;color:#c5c8cf;padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem);border-top:1px solid rgba(255,255,255,.1)}.dashboard-footer .footer-content{text-align:center}.dashboard-footer .footer-text{font-size:var(--font-size-xs, .75rem)}@media(min-width:769px){.mobile-user{display:none}}@media(max-width:768px){.desktop-user{display:none}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i4.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i4.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
1492
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: DashboardComponent, isStandalone: true, selector: "lib-dashboard", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "<div class=\"dashboard\">\r\n <!-- Header -->\r\n <header class=\"dashboard-header\">\r\n <!-- Mobile Header Top -->\r\n <div class=\"header-mobile-top\">\r\n <div class=\"ministry-logo-mobile\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <h1 class=\"mobile-title\">{{ getConfig('title') }}</h1>\r\n </div>\r\n\r\n <!-- Mobile Header Bottom -->\r\n <div class=\"header-mobile-bottom\">\r\n <button class=\"hamburger-btn\" (click)=\"toggleMobileMenu()\" [class.active]=\"isMobileMenuOpen\"\r\n aria-label=\"Toggle menu\">\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n </button>\r\n\r\n <div class=\"user-profile mobile-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" (click)=\"toggleUserPanel($event)\" [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n\r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Desktop Header -->\r\n <div class=\"header-desktop-layout\">\r\n <div class=\"header-left\">\r\n <div class=\"ministry-logo\">\r\n <div class=\"logo-icon\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <div class=\"ministry-info\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <span class=\"subtitle\" *ngIf=\"getConfig('subtitle')\">{{ getConfig('subtitle') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n <div class=\"user-profile desktop-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" (click)=\"toggleUserPanel($event)\" [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n\r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n <div class=\"user-panel\" *ngIf=\"isUserPanelOpen\">\r\n <div class=\"user-panel-header\">\r\n <span class=\"panel-title\">Perfil de Usuario</span>\r\n </div>\r\n <div class=\"user-panel-content\">\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">person</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">C\u00F3digo de Usuario:</span>\r\n <span class=\"info-value\">{{ getUsername() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">badge</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Nombre de Usuario:</span>\r\n <span class=\"info-value\">{{ getUserFullName() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">credit_card</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Documento:</span>\r\n <span class=\"info-value\">{{ getDocument() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">business</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Instituci\u00F3n:</span>\r\n <span class=\"info-value\">{{ getInstitution() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">apartment</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Dependencia:</span>\r\n <span class=\"info-value\">{{ getDependency() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">work</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Rol:</span>\r\n <span class=\"info-value\">{{ getRole() }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"user-panel-footer\">\r\n <button class=\"logout-btn-panel\" (click)=\"logoutAndRedirect()\" title=\"Cerrar sesi\u00F3n\">\r\n <span class=\"material-symbols-outlined\">logout</span>\r\n <span class=\"logout-text\">CERRAR SESI\u00D3N</span>\r\n </button>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n <div class=\"nav-overlay\" *ngIf=\"isMobileMenuOpen\" (click)=\"closeMobileMenu()\"></div>\r\n\r\n <!-- Navigation (Horizontal Desktop / Sidebar Mobile) -->\r\n <nav class=\"dashboard-nav\" [class.mobile-open]=\"isMobileMenuOpen\">\r\n <div class=\"nav-mobile-header\">\r\n <div class=\"nav-mobile-logo\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n <span class=\"nav-mobile-title\">Men\u00FA</span>\r\n </div>\r\n <button class=\"nav-close-btn\" (click)=\"closeMobileMenu()\" aria-label=\"Cerrar men\u00FA\">\r\n <span class=\"material-symbols-outlined\">close</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"nav-container\">\r\n <!-- Static Menu Items -->\r\n <ng-container *ngIf=\"getConfig('showStaticMenu')\">\r\n <a routerLink=\".\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-item\"\r\n (click)=\"closeMobileMenu()\">\r\n <span class=\"material-symbols-outlined nav-icon\">home</span>\r\n <span class=\"nav-text\">Inicio</span>\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Separator -->\r\n <div class=\"nav-separator\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\"></div>\r\n\r\n <!-- Dynamic Menu -->\r\n <div class=\"dynamic-menu\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\">\r\n <ng-container *ngFor=\"let rootItem of getRootMenuItems()\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(rootItem)\">\r\n <a [routerLink]=\"hasChildren(rootItem) ? null : rootItem.path\" routerLinkActive=\"active\"\r\n class=\"nav-item root-item\" [class.expandable]=\"hasChildren(rootItem)\"\r\n (click)=\"onMenuItemClick($event, rootItem)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(rootItem) }}</span>\r\n <span class=\"nav-text\">{{ rootItem.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(rootItem)\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Submenu -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(rootItem) && isMenuItemOpen(rootItem.code)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Loading Indicator -->\r\n <div class=\"menu-loading\" *ngIf=\"isLoadingMenu\">\r\n <span class=\"material-symbols-outlined nav-icon\">hourglass_empty</span>\r\n <span class=\"nav-text\">Cargando men\u00FA...</span>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Recursive Menu Template -->\r\n <ng-template #menuTemplate let-items=\"items\" let-level=\"level\">\r\n <ng-container *ngFor=\"let item of items\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(item)\" [attr.data-level]=\"level\">\r\n <a [routerLink]=\"hasChildren(item) ? null : item.path\" routerLinkActive=\"active\" class=\"nav-item submenu-item\"\r\n [class.expandable]=\"hasChildren(item)\" (click)=\"onMenuItemClick($event, item)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(item) }}</span>\r\n <span class=\"nav-text\">{{ item.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(item)\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(item) && isMenuItemOpen(item.code) && level < 5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- Main Content -->\r\n <main class=\"dashboard-content\">\r\n <div class=\"content-wrapper\">\r\n <router-outlet></router-outlet>\r\n </div>\r\n </main>\r\n\r\n <!-- Footer -->\r\n <footer class=\"dashboard-footer\">\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n {{ getConfig('footerText') }}\r\n <ng-container *ngIf=\"getConfig('showVersion')\">\r\n Versi\u00F3n {{ appVersion }}.\r\n </ng-container>\r\n Todos los derechos reservados.\r\n </span>\r\n </div>\r\n </footer>\r\n</div>", styles: [":host{display:block;width:100%;min-height:100vh}.dashboard{min-height:100vh;display:flex;flex-direction:column;background:var(--color-primary, #313945);font-family:var(--font-family-primary, \"Museo Sans\", sans-serif)}.dashboard-header{background:var(--color-primary, #313945);color:var(--color-white, #ffffff);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);border-bottom:1px solid rgba(255,255,255,.1);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:1000}@media(max-width:768px){.dashboard-header{flex-direction:column;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem);align-items:stretch}}.header-mobile-top{display:none}@media(max-width:768px){.header-mobile-top{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem)}}.ministry-logo-mobile{display:none}@media(max-width:768px){.ministry-logo-mobile{display:flex;align-items:center}.ministry-logo-mobile img{height:50px;width:auto;filter:brightness(0) invert(1)}}.mobile-title{display:none}@media(max-width:768px){.mobile-title{display:block;font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff);margin:0;text-align:right;flex:1}}.header-mobile-bottom{display:none}@media(max-width:768px){.header-mobile-bottom{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem);padding-top:var(--spacing-1, .25rem)}}.header-desktop-layout{display:flex;justify-content:space-between;align-items:center;width:100%}@media(max-width:768px){.header-desktop-layout{display:none}}.header-left{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.header-right{display:flex;align-items:center}.ministry-logo{display:flex;align-items:center;gap:var(--spacing-3, .75rem)}.ministry-logo .logo-icon{display:flex;width:80px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ministry-logo .logo-icon img{width:100%;height:auto}.ministry-logo .ministry-info h1{font-family:var(--font-family-primary, sans-serif);font-size:var(--font-size-xl, 1.25rem);font-weight:var(--font-weight-bold, 700);margin:0;color:var(--color-white, #ffffff);line-height:1.2}.ministry-logo .ministry-info .subtitle{font-size:var(--font-size-xs, .75rem);color:#ffffffd9;font-weight:var(--font-weight-medium, 500)}.hamburger-btn{display:none;flex-direction:column;justify-content:space-around;width:36px;height:36px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;padding:6px;z-index:1003;transition:all .3s ease;flex-shrink:0}@media(max-width:768px){.hamburger-btn{display:flex}}.hamburger-btn .hamburger-line{width:100%;height:2px;background:var(--color-white, #ffffff);border-radius:2px;transition:all .3s ease;transform-origin:center}.hamburger-btn.active{background:#fff3}.hamburger-btn.active .hamburger-line:nth-child(1){transform:translateY(7px) rotate(45deg)}.hamburger-btn.active .hamburger-line:nth-child(2){opacity:0}.hamburger-btn.active .hamburger-line:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.hamburger-btn:hover{background:#fff3}.user-profile{position:relative;display:flex;align-items:center}.user-profile .user-info-trigger{display:flex;align-items:center;gap:var(--spacing-2, .5rem);background:#ffffff1a;padding:var(--spacing-1, .25rem) var(--spacing-3, .75rem);border-radius:var(--border-radius-lg, .5rem);border:1px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s ease;color:inherit;font-family:inherit}.user-profile .user-info-trigger:hover{background:#ffffff26}.user-profile .user-avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--color-white, #ffffff) 0%,#e2e8f0 100%);display:flex;align-items:center;justify-content:center;font-weight:var(--font-weight-bold, 700);color:var(--color-primary, #313945);font-size:var(--font-size-sm, .875rem);box-shadow:0 2px 6px #0000001a}.user-profile .user-details{display:flex;flex-direction:column}.user-profile .user-details .user-name{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-white, #ffffff)}.user-profile .dropdown-icon{color:#fffc;font-size:18px;transition:transform .2s ease}.user-panel{position:absolute;top:calc(100% + var(--spacing-2, .5rem));right:0;min-width:320px;background:var(--color-white, #ffffff);border-radius:var(--border-radius-lg, .5rem);box-shadow:0 10px 40px #0003;border:1px solid var(--color-gray-200, #e9ecef);z-index:1100;overflow:hidden;animation:slideDown .2s ease}@media(max-width:768px){.user-panel{position:absolute;top:calc(100% + var(--spacing-1, .25rem));right:0;left:auto;min-width:280px;max-width:calc(100vw - 1rem);max-height:80vh;overflow-y:auto}}.user-panel-header{background:linear-gradient(135deg,#3c4557,#2a3142);padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem)}.user-panel-header .panel-title{font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff)}.user-panel-content{padding:var(--spacing-3, .75rem)}.user-info-item{display:flex;align-items:flex-start;gap:var(--spacing-3, .75rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);border-bottom:1px solid var(--color-gray-100, #f1f3f4)}.user-info-item:last-child{border-bottom:none}.user-info-item .info-icon{font-size:var(--font-size-lg, 1.125rem);flex-shrink:0;color:var(--color-primary, #313945)}.user-info-item .info-content{display:flex;flex-direction:column;gap:2px;flex:1}.user-info-item .info-content .info-label{font-size:var(--font-size-xs, .75rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-gray-600, #5a6268);text-transform:uppercase;letter-spacing:.5px}.user-info-item .info-content .info-value{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-gray-900, #212529);word-break:break-word}@media(max-width:768px){.mobile-hidden{display:none!important}}.user-panel-footer{padding:var(--spacing-3, .75rem);background:var(--color-gray-50, #f8f9fa);border-top:1px solid var(--color-gray-200, #e9ecef)}.user-panel-footer .logout-btn-panel{width:100%;background:linear-gradient(135deg,#dc2626,#b91c1c);color:var(--color-white, #ffffff);border:none;padding:var(--spacing-3, .75rem);border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-bold, 700);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:var(--spacing-2, .5rem);text-transform:uppercase}.user-panel-footer .logout-btn-panel:hover{background:linear-gradient(135deg,#b91c1c,#991b1b)}.nav-overlay{display:none}@media(max-width:768px){.nav-overlay{display:block;position:fixed;inset:0;background:#00000080;z-index:1001;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}}.dashboard-nav{background-color:#3c4557;box-shadow:0 2px 8px #0000001a}@media(max-width:768px){.dashboard-nav{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:85%;transform:translate(-100%);z-index:1002;overflow-y:auto;box-shadow:2px 0 10px #0000004d;transition:transform .3s ease}.dashboard-nav.mobile-open{transform:translate(0)}}.dashboard-nav .nav-mobile-header{display:none}@media(max-width:768px){.dashboard-nav .nav-mobile-header{display:flex;justify-content:space-between;align-items:center;padding:var(--spacing-3, .75rem);background:var(--color-primary, #313945);border-bottom:1px solid rgba(255,255,255,.1);position:sticky;top:0;z-index:10}}.dashboard-nav .nav-mobile-logo{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.dashboard-nav .nav-mobile-logo img{width:36px;height:auto;filter:brightness(0) invert(1)}.dashboard-nav .nav-mobile-logo .nav-mobile-title{color:var(--color-white, #ffffff);font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-semibold, 600)}.dashboard-nav .nav-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;color:var(--color-white, #ffffff)}.dashboard-nav .nav-close-btn:hover{background:#fff3}.nav-container{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-1, .25rem);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem)}@media(max-width:768px){.nav-container{flex-direction:column;align-items:stretch;padding:var(--spacing-3, .75rem);gap:var(--spacing-1, .25rem)}}.nav-item{display:flex;align-items:center;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);color:var(--color-white, #ffffff);text-decoration:none;border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);transition:all .2s ease;cursor:pointer;background:transparent;border:none;font-family:inherit;white-space:nowrap}.nav-item:hover{background:#ffffff1a}.nav-item.active{background:#fff3}.nav-item .nav-icon{font-size:18px;flex-shrink:0}.nav-item .nav-text{flex:1;text-align:left}.nav-item .expand-icon{font-size:16px;flex-shrink:0;margin-left:var(--spacing-1, .25rem)}@media(max-width:768px){.nav-item{width:100%;padding:var(--spacing-3, .75rem)}}.nav-separator{width:1px;height:20px;background:#fff3;margin:0 var(--spacing-1, .25rem);align-self:center}@media(max-width:768px){.nav-separator{width:100%;height:1px;margin:var(--spacing-2, .5rem) 0}}.dynamic-menu{display:flex;flex-wrap:wrap;gap:var(--spacing-1, .25rem);align-items:flex-start}@media(max-width:768px){.dynamic-menu{flex-direction:column;width:100%}}.menu-item-container{position:relative}@media(max-width:768px){.menu-item-container{width:100%}}@media(min-width:769px){.menu-item-container>.submenu{position:absolute;top:100%;left:0;min-width:220px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1000;margin-top:var(--spacing-1, .25rem)}}@media(max-width:768px){.menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}@media(min-width:769px){.submenu{min-width:200px}}@media(max-width:768px){.submenu{width:100%}}.submenu .menu-item-container{width:100%}@media(min-width:769px){.submenu .menu-item-container>.submenu{position:absolute;top:0;left:100%;min-width:200px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1001;margin-left:2px}}@media(max-width:768px){.submenu .menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}.submenu .submenu-item{padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);width:100%;box-sizing:border-box}.submenu .submenu-item:hover{background:#ffffff1a}.menu-loading{display:flex;align-items:center;gap:var(--spacing-2, .5rem);color:#ffffffb3;padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);font-size:var(--font-size-sm, .875rem)}.dashboard-content{flex:1;background:var(--color-white, #ffffff)}.dashboard-content .content-wrapper{padding:var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);max-width:1400px;margin:0 auto}@media(max-width:768px){.dashboard-content .content-wrapper{padding:var(--spacing-3, .75rem)}}.dashboard-footer{background:#3c4557;color:#c5c8cf;padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem);border-top:1px solid rgba(255,255,255,.1)}.dashboard-footer .footer-content{text-align:center}.dashboard-footer .footer-text{font-size:var(--font-size-xs, .75rem)}@media(min-width:769px){.mobile-user{display:none}}@media(max-width:768px){.desktop-user{display:none}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i4.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i4.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i4.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
1409
1493
  }
1410
1494
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DashboardComponent, decorators: [{
1411
1495
  type: Component,
1412
- args: [{ selector: 'lib-dashboard', standalone: true, imports: [CommonModule, RouterModule], template: "<div class=\"dashboard\">\r\n <!-- Header -->\r\n <header class=\"dashboard-header\">\r\n <!-- Mobile Header Top -->\r\n <div class=\"header-mobile-top\">\r\n <div class=\"ministry-logo-mobile\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <h1 class=\"mobile-title\">{{ getConfig('title') }}</h1>\r\n </div>\r\n\r\n <!-- Mobile Header Bottom -->\r\n <div class=\"header-mobile-bottom\">\r\n <button class=\"hamburger-btn\" \r\n (click)=\"toggleMobileMenu()\"\r\n [class.active]=\"isMobileMenuOpen\"\r\n aria-label=\"Toggle menu\">\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n </button>\r\n\r\n <div class=\"user-profile mobile-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" \r\n (click)=\"toggleUserPanel($event)\"\r\n [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n \r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Desktop Header -->\r\n <div class=\"header-desktop-layout\">\r\n <div class=\"header-left\">\r\n <div class=\"ministry-logo\">\r\n <div class=\"logo-icon\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <div class=\"ministry-info\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <span class=\"subtitle\" *ngIf=\"getConfig('subtitle')\">{{ getConfig('subtitle') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n \r\n <div class=\"header-right\">\r\n <div class=\"user-profile desktop-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" \r\n (click)=\"toggleUserPanel($event)\"\r\n [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n \r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n <div class=\"user-panel\" *ngIf=\"isUserPanelOpen\">\r\n <div class=\"user-panel-header\">\r\n <span class=\"panel-title\">Perfil de Usuario</span>\r\n </div>\r\n <div class=\"user-panel-content\">\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">person</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">C\u00F3digo de Usuario:</span>\r\n <span class=\"info-value\">{{ getUsername() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">badge</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Nombre de Usuario:</span>\r\n <span class=\"info-value\">{{ getUserFullName() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">credit_card</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Documento:</span>\r\n <span class=\"info-value\">{{ getDocument() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">business</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Instituci\u00F3n:</span>\r\n <span class=\"info-value\">{{ getInstitution() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">apartment</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Dependencia:</span>\r\n <span class=\"info-value\">{{ getDependency() }}</span>\r\n </div>\r\n </div>\r\n \r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">work</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Rol:</span>\r\n <span class=\"info-value\">{{ getRole() }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"user-panel-footer\">\r\n <button class=\"logout-btn-panel\" (click)=\"logoutAndRedirect()\" title=\"Cerrar sesi\u00F3n\">\r\n <span class=\"material-symbols-outlined\">logout</span>\r\n <span class=\"logout-text\">CERRAR SESI\u00D3N</span>\r\n </button>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n <div class=\"nav-overlay\" *ngIf=\"isMobileMenuOpen\" (click)=\"closeMobileMenu()\"></div>\r\n\r\n <!-- Navigation (Horizontal Desktop / Sidebar Mobile) -->\r\n <nav class=\"dashboard-nav\" [class.mobile-open]=\"isMobileMenuOpen\">\r\n <div class=\"nav-mobile-header\">\r\n <div class=\"nav-mobile-logo\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n <span class=\"nav-mobile-title\">Men\u00FA</span>\r\n </div>\r\n <button class=\"nav-close-btn\" (click)=\"closeMobileMenu()\" aria-label=\"Cerrar men\u00FA\">\r\n <span class=\"material-symbols-outlined\">close</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"nav-container\">\r\n <!-- Static Menu Items -->\r\n <ng-container *ngIf=\"getConfig('showStaticMenu')\">\r\n <a routerLink=\".\" \r\n routerLinkActive=\"active\" \r\n [routerLinkActiveOptions]=\"{exact: true}\" \r\n class=\"nav-item\"\r\n (click)=\"closeMobileMenu()\">\r\n <span class=\"material-symbols-outlined nav-icon\">home</span>\r\n <span class=\"nav-text\">Inicio</span>\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Separator -->\r\n <div class=\"nav-separator\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\"></div>\r\n \r\n <!-- Dynamic Menu -->\r\n <div class=\"dynamic-menu\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\">\r\n <ng-container *ngFor=\"let rootItem of getRootMenuItems()\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(rootItem)\">\r\n <a \r\n [routerLink]=\"hasChildren(rootItem) ? null : rootItem.path\" \r\n routerLinkActive=\"active\" \r\n class=\"nav-item root-item\"\r\n [class.expandable]=\"hasChildren(rootItem)\"\r\n (click)=\"onMenuItemClick($event, rootItem)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(rootItem) }}</span>\r\n <span class=\"nav-text\">{{ rootItem.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(rootItem)\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n \r\n <!-- Submenu -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(rootItem) && isMenuItemOpen(rootItem.code)\">\r\n <ng-container *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n \r\n <!-- Loading Indicator -->\r\n <div class=\"menu-loading\" *ngIf=\"isLoadingMenu\">\r\n <span class=\"material-symbols-outlined nav-icon\">hourglass_empty</span>\r\n <span class=\"nav-text\">Cargando men\u00FA...</span>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Recursive Menu Template -->\r\n <ng-template #menuTemplate let-items=\"items\" let-level=\"level\">\r\n <ng-container *ngFor=\"let item of items\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(item)\" [attr.data-level]=\"level\">\r\n <a \r\n [routerLink]=\"hasChildren(item) ? null : item.path\" \r\n routerLinkActive=\"active\" \r\n class=\"nav-item submenu-item\"\r\n [class.expandable]=\"hasChildren(item)\"\r\n (click)=\"onMenuItemClick($event, item)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(item) }}</span>\r\n <span class=\"nav-text\">{{ item.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(item)\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(item) && isMenuItemOpen(item.code) && level < 5\">\r\n <ng-container *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- Main Content -->\r\n <main class=\"dashboard-content\">\r\n <div class=\"content-wrapper\">\r\n <router-outlet></router-outlet>\r\n </div>\r\n </main>\r\n\r\n <!-- Footer -->\r\n <footer class=\"dashboard-footer\">\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n {{ getConfig('footerText') }}\r\n <ng-container *ngIf=\"getConfig('showVersion')\">\r\n Versi\u00F3n {{ appVersion }}.\r\n </ng-container>\r\n Todos los derechos reservados.\r\n </span>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{display:block;width:100%;min-height:100vh}.dashboard{min-height:100vh;display:flex;flex-direction:column;background:var(--color-primary, #313945);font-family:var(--font-family-primary, \"Museo Sans\", sans-serif)}.dashboard-header{background:var(--color-primary, #313945);color:var(--color-white, #ffffff);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);border-bottom:1px solid rgba(255,255,255,.1);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:1000}@media(max-width:768px){.dashboard-header{flex-direction:column;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem);align-items:stretch}}.header-mobile-top{display:none}@media(max-width:768px){.header-mobile-top{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem)}}.ministry-logo-mobile{display:none}@media(max-width:768px){.ministry-logo-mobile{display:flex;align-items:center}.ministry-logo-mobile img{height:50px;width:auto;filter:brightness(0) invert(1)}}.mobile-title{display:none}@media(max-width:768px){.mobile-title{display:block;font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff);margin:0;text-align:right;flex:1}}.header-mobile-bottom{display:none}@media(max-width:768px){.header-mobile-bottom{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem);padding-top:var(--spacing-1, .25rem)}}.header-desktop-layout{display:flex;justify-content:space-between;align-items:center;width:100%}@media(max-width:768px){.header-desktop-layout{display:none}}.header-left{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.header-right{display:flex;align-items:center}.ministry-logo{display:flex;align-items:center;gap:var(--spacing-3, .75rem)}.ministry-logo .logo-icon{display:flex;width:80px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ministry-logo .logo-icon img{width:100%;height:auto}.ministry-logo .ministry-info h1{font-family:var(--font-family-primary, sans-serif);font-size:var(--font-size-xl, 1.25rem);font-weight:var(--font-weight-bold, 700);margin:0;color:var(--color-white, #ffffff);line-height:1.2}.ministry-logo .ministry-info .subtitle{font-size:var(--font-size-xs, .75rem);color:#ffffffd9;font-weight:var(--font-weight-medium, 500)}.hamburger-btn{display:none;flex-direction:column;justify-content:space-around;width:36px;height:36px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;padding:6px;z-index:1003;transition:all .3s ease;flex-shrink:0}@media(max-width:768px){.hamburger-btn{display:flex}}.hamburger-btn .hamburger-line{width:100%;height:2px;background:var(--color-white, #ffffff);border-radius:2px;transition:all .3s ease;transform-origin:center}.hamburger-btn.active{background:#fff3}.hamburger-btn.active .hamburger-line:nth-child(1){transform:translateY(7px) rotate(45deg)}.hamburger-btn.active .hamburger-line:nth-child(2){opacity:0}.hamburger-btn.active .hamburger-line:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.hamburger-btn:hover{background:#fff3}.user-profile{position:relative;display:flex;align-items:center}.user-profile .user-info-trigger{display:flex;align-items:center;gap:var(--spacing-2, .5rem);background:#ffffff1a;padding:var(--spacing-1, .25rem) var(--spacing-3, .75rem);border-radius:var(--border-radius-lg, .5rem);border:1px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s ease;color:inherit;font-family:inherit}.user-profile .user-info-trigger:hover{background:#ffffff26}.user-profile .user-avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--color-white, #ffffff) 0%,#e2e8f0 100%);display:flex;align-items:center;justify-content:center;font-weight:var(--font-weight-bold, 700);color:var(--color-primary, #313945);font-size:var(--font-size-sm, .875rem);box-shadow:0 2px 6px #0000001a}.user-profile .user-details{display:flex;flex-direction:column}.user-profile .user-details .user-name{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-white, #ffffff)}.user-profile .dropdown-icon{color:#fffc;font-size:18px;transition:transform .2s ease}.user-panel{position:absolute;top:calc(100% + var(--spacing-2, .5rem));right:0;min-width:320px;background:var(--color-white, #ffffff);border-radius:var(--border-radius-lg, .5rem);box-shadow:0 10px 40px #0003;border:1px solid var(--color-gray-200, #e9ecef);z-index:1100;overflow:hidden;animation:slideDown .2s ease}@media(max-width:768px){.user-panel{position:absolute;top:calc(100% + var(--spacing-1, .25rem));right:0;left:auto;min-width:280px;max-width:calc(100vw - 1rem);max-height:80vh;overflow-y:auto}}.user-panel-header{background:linear-gradient(135deg,#3c4557,#2a3142);padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem)}.user-panel-header .panel-title{font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff)}.user-panel-content{padding:var(--spacing-3, .75rem)}.user-info-item{display:flex;align-items:flex-start;gap:var(--spacing-3, .75rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);border-bottom:1px solid var(--color-gray-100, #f1f3f4)}.user-info-item:last-child{border-bottom:none}.user-info-item .info-icon{font-size:var(--font-size-lg, 1.125rem);flex-shrink:0;color:var(--color-primary, #313945)}.user-info-item .info-content{display:flex;flex-direction:column;gap:2px;flex:1}.user-info-item .info-content .info-label{font-size:var(--font-size-xs, .75rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-gray-600, #5a6268);text-transform:uppercase;letter-spacing:.5px}.user-info-item .info-content .info-value{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-gray-900, #212529);word-break:break-word}@media(max-width:768px){.mobile-hidden{display:none!important}}.user-panel-footer{padding:var(--spacing-3, .75rem);background:var(--color-gray-50, #f8f9fa);border-top:1px solid var(--color-gray-200, #e9ecef)}.user-panel-footer .logout-btn-panel{width:100%;background:linear-gradient(135deg,#dc2626,#b91c1c);color:var(--color-white, #ffffff);border:none;padding:var(--spacing-3, .75rem);border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-bold, 700);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:var(--spacing-2, .5rem);text-transform:uppercase}.user-panel-footer .logout-btn-panel:hover{background:linear-gradient(135deg,#b91c1c,#991b1b)}.nav-overlay{display:none}@media(max-width:768px){.nav-overlay{display:block;position:fixed;inset:0;background:#00000080;z-index:1001;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}}.dashboard-nav{background-color:#3c4557;box-shadow:0 2px 8px #0000001a}@media(max-width:768px){.dashboard-nav{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:85%;transform:translate(-100%);z-index:1002;overflow-y:auto;box-shadow:2px 0 10px #0000004d;transition:transform .3s ease}.dashboard-nav.mobile-open{transform:translate(0)}}.dashboard-nav .nav-mobile-header{display:none}@media(max-width:768px){.dashboard-nav .nav-mobile-header{display:flex;justify-content:space-between;align-items:center;padding:var(--spacing-3, .75rem);background:var(--color-primary, #313945);border-bottom:1px solid rgba(255,255,255,.1);position:sticky;top:0;z-index:10}}.dashboard-nav .nav-mobile-logo{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.dashboard-nav .nav-mobile-logo img{width:36px;height:auto;filter:brightness(0) invert(1)}.dashboard-nav .nav-mobile-logo .nav-mobile-title{color:var(--color-white, #ffffff);font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-semibold, 600)}.dashboard-nav .nav-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;color:var(--color-white, #ffffff)}.dashboard-nav .nav-close-btn:hover{background:#fff3}.nav-container{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-1, .25rem);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem)}@media(max-width:768px){.nav-container{flex-direction:column;align-items:stretch;padding:var(--spacing-3, .75rem);gap:var(--spacing-1, .25rem)}}.nav-item{display:flex;align-items:center;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);color:var(--color-white, #ffffff);text-decoration:none;border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);transition:all .2s ease;cursor:pointer;background:transparent;border:none;font-family:inherit;white-space:nowrap}.nav-item:hover{background:#ffffff1a}.nav-item.active{background:#fff3}.nav-item .nav-icon{font-size:18px;flex-shrink:0}.nav-item .nav-text{flex:1;text-align:left}.nav-item .expand-icon{font-size:16px;flex-shrink:0;margin-left:var(--spacing-1, .25rem)}@media(max-width:768px){.nav-item{width:100%;padding:var(--spacing-3, .75rem)}}.nav-separator{width:1px;height:20px;background:#fff3;margin:0 var(--spacing-1, .25rem);align-self:center}@media(max-width:768px){.nav-separator{width:100%;height:1px;margin:var(--spacing-2, .5rem) 0}}.dynamic-menu{display:flex;flex-wrap:wrap;gap:var(--spacing-1, .25rem);align-items:flex-start}@media(max-width:768px){.dynamic-menu{flex-direction:column;width:100%}}.menu-item-container{position:relative}@media(max-width:768px){.menu-item-container{width:100%}}@media(min-width:769px){.menu-item-container>.submenu{position:absolute;top:100%;left:0;min-width:220px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1000;margin-top:var(--spacing-1, .25rem)}}@media(max-width:768px){.menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}@media(min-width:769px){.submenu{min-width:200px}}@media(max-width:768px){.submenu{width:100%}}.submenu .menu-item-container{width:100%}@media(min-width:769px){.submenu .menu-item-container>.submenu{position:absolute;top:0;left:100%;min-width:200px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1001;margin-left:2px}}@media(max-width:768px){.submenu .menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}.submenu .submenu-item{padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);width:100%;box-sizing:border-box}.submenu .submenu-item:hover{background:#ffffff1a}.menu-loading{display:flex;align-items:center;gap:var(--spacing-2, .5rem);color:#ffffffb3;padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);font-size:var(--font-size-sm, .875rem)}.dashboard-content{flex:1;background:var(--color-white, #ffffff)}.dashboard-content .content-wrapper{padding:var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);max-width:1400px;margin:0 auto}@media(max-width:768px){.dashboard-content .content-wrapper{padding:var(--spacing-3, .75rem)}}.dashboard-footer{background:#3c4557;color:#c5c8cf;padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem);border-top:1px solid rgba(255,255,255,.1)}.dashboard-footer .footer-content{text-align:center}.dashboard-footer .footer-text{font-size:var(--font-size-xs, .75rem)}@media(min-width:769px){.mobile-user{display:none}}@media(max-width:768px){.desktop-user{display:none}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}\n"] }]
1496
+ args: [{ selector: 'lib-dashboard', standalone: true, imports: [CommonModule, RouterModule], template: "<div class=\"dashboard\">\r\n <!-- Header -->\r\n <header class=\"dashboard-header\">\r\n <!-- Mobile Header Top -->\r\n <div class=\"header-mobile-top\">\r\n <div class=\"ministry-logo-mobile\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <h1 class=\"mobile-title\">{{ getConfig('title') }}</h1>\r\n </div>\r\n\r\n <!-- Mobile Header Bottom -->\r\n <div class=\"header-mobile-bottom\">\r\n <button class=\"hamburger-btn\" (click)=\"toggleMobileMenu()\" [class.active]=\"isMobileMenuOpen\"\r\n aria-label=\"Toggle menu\">\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n <span class=\"hamburger-line\"></span>\r\n </button>\r\n\r\n <div class=\"user-profile mobile-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" (click)=\"toggleUserPanel($event)\" [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n\r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n\r\n <!-- Desktop Header -->\r\n <div class=\"header-desktop-layout\">\r\n <div class=\"header-left\">\r\n <div class=\"ministry-logo\">\r\n <div class=\"logo-icon\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n </div>\r\n <div class=\"ministry-info\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <span class=\"subtitle\" *ngIf=\"getConfig('subtitle')\">{{ getConfig('subtitle') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n <div class=\"user-profile desktop-user\" *ngIf=\"userInfo\">\r\n <button class=\"user-info-trigger\" (click)=\"toggleUserPanel($event)\" [attr.aria-expanded]=\"isUserPanelOpen\"\r\n aria-label=\"Abrir men\u00FA de usuario\">\r\n <div class=\"user-avatar\">\r\n <span>{{ getUserInitials() }}</span>\r\n </div>\r\n <div class=\"user-details\">\r\n <span class=\"user-name\">{{ getUsername() }}</span>\r\n </div>\r\n <span class=\"material-symbols-outlined dropdown-icon\">\r\n {{ isUserPanelOpen ? 'expand_less' : 'expand_more' }}\r\n </span>\r\n </button>\r\n\r\n <!-- User Panel -->\r\n <ng-container *ngTemplateOutlet=\"userPanelTemplate\"></ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n <div class=\"user-panel\" *ngIf=\"isUserPanelOpen\">\r\n <div class=\"user-panel-header\">\r\n <span class=\"panel-title\">Perfil de Usuario</span>\r\n </div>\r\n <div class=\"user-panel-content\">\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">person</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">C\u00F3digo de Usuario:</span>\r\n <span class=\"info-value\">{{ getUsername() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">badge</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Nombre de Usuario:</span>\r\n <span class=\"info-value\">{{ getUserFullName() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item\">\r\n <span class=\"material-symbols-outlined info-icon\">credit_card</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Documento:</span>\r\n <span class=\"info-value\">{{ getDocument() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">business</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Instituci\u00F3n:</span>\r\n <span class=\"info-value\">{{ getInstitution() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">apartment</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Dependencia:</span>\r\n <span class=\"info-value\">{{ getDependency() }}</span>\r\n </div>\r\n </div>\r\n\r\n <div class=\"user-info-item mobile-hidden\">\r\n <span class=\"material-symbols-outlined info-icon\">work</span>\r\n <div class=\"info-content\">\r\n <span class=\"info-label\">Rol:</span>\r\n <span class=\"info-value\">{{ getRole() }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"user-panel-footer\">\r\n <button class=\"logout-btn-panel\" (click)=\"logoutAndRedirect()\" title=\"Cerrar sesi\u00F3n\">\r\n <span class=\"material-symbols-outlined\">logout</span>\r\n <span class=\"logout-text\">CERRAR SESI\u00D3N</span>\r\n </button>\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n <div class=\"nav-overlay\" *ngIf=\"isMobileMenuOpen\" (click)=\"closeMobileMenu()\"></div>\r\n\r\n <!-- Navigation (Horizontal Desktop / Sidebar Mobile) -->\r\n <nav class=\"dashboard-nav\" [class.mobile-open]=\"isMobileMenuOpen\">\r\n <div class=\"nav-mobile-header\">\r\n <div class=\"nav-mobile-logo\">\r\n <img [src]=\"getConfig('logoUrl')\" [alt]=\"getConfig('logoAlt')\" />\r\n <span class=\"nav-mobile-title\">Men\u00FA</span>\r\n </div>\r\n <button class=\"nav-close-btn\" (click)=\"closeMobileMenu()\" aria-label=\"Cerrar men\u00FA\">\r\n <span class=\"material-symbols-outlined\">close</span>\r\n </button>\r\n </div>\r\n\r\n <div class=\"nav-container\">\r\n <!-- Static Menu Items -->\r\n <ng-container *ngIf=\"getConfig('showStaticMenu')\">\r\n <a routerLink=\".\" routerLinkActive=\"active\" [routerLinkActiveOptions]=\"{exact: true}\" class=\"nav-item\"\r\n (click)=\"closeMobileMenu()\">\r\n <span class=\"material-symbols-outlined nav-icon\">home</span>\r\n <span class=\"nav-text\">Inicio</span>\r\n </a>\r\n </ng-container>\r\n\r\n <!-- Separator -->\r\n <div class=\"nav-separator\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\"></div>\r\n\r\n <!-- Dynamic Menu -->\r\n <div class=\"dynamic-menu\" *ngIf=\"!isLoadingMenu && menuHierarchy.length > 0\">\r\n <ng-container *ngFor=\"let rootItem of getRootMenuItems()\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(rootItem)\">\r\n <a [routerLink]=\"hasChildren(rootItem) ? null : rootItem.path\" routerLinkActive=\"active\"\r\n class=\"nav-item root-item\" [class.expandable]=\"hasChildren(rootItem)\"\r\n (click)=\"onMenuItemClick($event, rootItem)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(rootItem) }}</span>\r\n <span class=\"nav-text\">{{ rootItem.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(rootItem)\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Submenu -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(rootItem) && isMenuItemOpen(rootItem.code)\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </div>\r\n\r\n <!-- Loading Indicator -->\r\n <div class=\"menu-loading\" *ngIf=\"isLoadingMenu\">\r\n <span class=\"material-symbols-outlined nav-icon\">hourglass_empty</span>\r\n <span class=\"nav-text\">Cargando men\u00FA...</span>\r\n </div>\r\n </div>\r\n </nav>\r\n\r\n <!-- Recursive Menu Template -->\r\n <ng-template #menuTemplate let-items=\"items\" let-level=\"level\">\r\n <ng-container *ngFor=\"let item of items\">\r\n <div class=\"menu-item-container\" [class.has-children]=\"hasChildren(item)\" [attr.data-level]=\"level\">\r\n <a [routerLink]=\"hasChildren(item) ? null : item.path\" routerLinkActive=\"active\" class=\"nav-item submenu-item\"\r\n [class.expandable]=\"hasChildren(item)\" (click)=\"onMenuItemClick($event, item)\">\r\n <span class=\"material-symbols-outlined nav-icon\">{{ getMenuIcon(item) }}</span>\r\n <span class=\"nav-text\">{{ item.name }}</span>\r\n <span class=\"material-symbols-outlined expand-icon\" *ngIf=\"hasChildren(item)\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n <div class=\"submenu\" *ngIf=\"hasChildren(item) && isMenuItemOpen(item.code) && level < 5\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n </div>\r\n </ng-container>\r\n </ng-template>\r\n\r\n <!-- Main Content -->\r\n <main class=\"dashboard-content\">\r\n <div class=\"content-wrapper\">\r\n <router-outlet></router-outlet>\r\n </div>\r\n </main>\r\n\r\n <!-- Footer -->\r\n <footer class=\"dashboard-footer\">\r\n <div class=\"footer-content\">\r\n <span class=\"footer-text\">\r\n {{ getConfig('footerText') }}\r\n <ng-container *ngIf=\"getConfig('showVersion')\">\r\n Versi\u00F3n {{ appVersion }}.\r\n </ng-container>\r\n Todos los derechos reservados.\r\n </span>\r\n </div>\r\n </footer>\r\n</div>", styles: [":host{display:block;width:100%;min-height:100vh}.dashboard{min-height:100vh;display:flex;flex-direction:column;background:var(--color-primary, #313945);font-family:var(--font-family-primary, \"Museo Sans\", sans-serif)}.dashboard-header{background:var(--color-primary, #313945);color:var(--color-white, #ffffff);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);border-bottom:1px solid rgba(255,255,255,.1);display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:1000}@media(max-width:768px){.dashboard-header{flex-direction:column;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem);align-items:stretch}}.header-mobile-top{display:none}@media(max-width:768px){.header-mobile-top{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem)}}.ministry-logo-mobile{display:none}@media(max-width:768px){.ministry-logo-mobile{display:flex;align-items:center}.ministry-logo-mobile img{height:50px;width:auto;filter:brightness(0) invert(1)}}.mobile-title{display:none}@media(max-width:768px){.mobile-title{display:block;font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff);margin:0;text-align:right;flex:1}}.header-mobile-bottom{display:none}@media(max-width:768px){.header-mobile-bottom{display:flex;justify-content:space-between;align-items:center;width:100%;gap:var(--spacing-2, .5rem);padding-top:var(--spacing-1, .25rem)}}.header-desktop-layout{display:flex;justify-content:space-between;align-items:center;width:100%}@media(max-width:768px){.header-desktop-layout{display:none}}.header-left{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.header-right{display:flex;align-items:center}.ministry-logo{display:flex;align-items:center;gap:var(--spacing-3, .75rem)}.ministry-logo .logo-icon{display:flex;width:80px;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2))}.ministry-logo .logo-icon img{width:100%;height:auto}.ministry-logo .ministry-info h1{font-family:var(--font-family-primary, sans-serif);font-size:var(--font-size-xl, 1.25rem);font-weight:var(--font-weight-bold, 700);margin:0;color:var(--color-white, #ffffff);line-height:1.2}.ministry-logo .ministry-info .subtitle{font-size:var(--font-size-xs, .75rem);color:#ffffffd9;font-weight:var(--font-weight-medium, 500)}.hamburger-btn{display:none;flex-direction:column;justify-content:space-around;width:36px;height:36px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;padding:6px;z-index:1003;transition:all .3s ease;flex-shrink:0}@media(max-width:768px){.hamburger-btn{display:flex}}.hamburger-btn .hamburger-line{width:100%;height:2px;background:var(--color-white, #ffffff);border-radius:2px;transition:all .3s ease;transform-origin:center}.hamburger-btn.active{background:#fff3}.hamburger-btn.active .hamburger-line:nth-child(1){transform:translateY(7px) rotate(45deg)}.hamburger-btn.active .hamburger-line:nth-child(2){opacity:0}.hamburger-btn.active .hamburger-line:nth-child(3){transform:translateY(-7px) rotate(-45deg)}.hamburger-btn:hover{background:#fff3}.user-profile{position:relative;display:flex;align-items:center}.user-profile .user-info-trigger{display:flex;align-items:center;gap:var(--spacing-2, .5rem);background:#ffffff1a;padding:var(--spacing-1, .25rem) var(--spacing-3, .75rem);border-radius:var(--border-radius-lg, .5rem);border:1px solid rgba(255,255,255,.2);cursor:pointer;transition:all .2s ease;color:inherit;font-family:inherit}.user-profile .user-info-trigger:hover{background:#ffffff26}.user-profile .user-avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,var(--color-white, #ffffff) 0%,#e2e8f0 100%);display:flex;align-items:center;justify-content:center;font-weight:var(--font-weight-bold, 700);color:var(--color-primary, #313945);font-size:var(--font-size-sm, .875rem);box-shadow:0 2px 6px #0000001a}.user-profile .user-details{display:flex;flex-direction:column}.user-profile .user-details .user-name{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-white, #ffffff)}.user-profile .dropdown-icon{color:#fffc;font-size:18px;transition:transform .2s ease}.user-panel{position:absolute;top:calc(100% + var(--spacing-2, .5rem));right:0;min-width:320px;background:var(--color-white, #ffffff);border-radius:var(--border-radius-lg, .5rem);box-shadow:0 10px 40px #0003;border:1px solid var(--color-gray-200, #e9ecef);z-index:1100;overflow:hidden;animation:slideDown .2s ease}@media(max-width:768px){.user-panel{position:absolute;top:calc(100% + var(--spacing-1, .25rem));right:0;left:auto;min-width:280px;max-width:calc(100vw - 1rem);max-height:80vh;overflow-y:auto}}.user-panel-header{background:linear-gradient(135deg,#3c4557,#2a3142);padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem)}.user-panel-header .panel-title{font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-bold, 700);color:var(--color-white, #ffffff)}.user-panel-content{padding:var(--spacing-3, .75rem)}.user-info-item{display:flex;align-items:flex-start;gap:var(--spacing-3, .75rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);border-bottom:1px solid var(--color-gray-100, #f1f3f4)}.user-info-item:last-child{border-bottom:none}.user-info-item .info-icon{font-size:var(--font-size-lg, 1.125rem);flex-shrink:0;color:var(--color-primary, #313945)}.user-info-item .info-content{display:flex;flex-direction:column;gap:2px;flex:1}.user-info-item .info-content .info-label{font-size:var(--font-size-xs, .75rem);font-weight:var(--font-weight-semibold, 600);color:var(--color-gray-600, #5a6268);text-transform:uppercase;letter-spacing:.5px}.user-info-item .info-content .info-value{font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);color:var(--color-gray-900, #212529);word-break:break-word}@media(max-width:768px){.mobile-hidden{display:none!important}}.user-panel-footer{padding:var(--spacing-3, .75rem);background:var(--color-gray-50, #f8f9fa);border-top:1px solid var(--color-gray-200, #e9ecef)}.user-panel-footer .logout-btn-panel{width:100%;background:linear-gradient(135deg,#dc2626,#b91c1c);color:var(--color-white, #ffffff);border:none;padding:var(--spacing-3, .75rem);border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-bold, 700);cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:var(--spacing-2, .5rem);text-transform:uppercase}.user-panel-footer .logout-btn-panel:hover{background:linear-gradient(135deg,#b91c1c,#991b1b)}.nav-overlay{display:none}@media(max-width:768px){.nav-overlay{display:block;position:fixed;inset:0;background:#00000080;z-index:1001;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}}.dashboard-nav{background-color:#3c4557;box-shadow:0 2px 8px #0000001a}@media(max-width:768px){.dashboard-nav{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:85%;transform:translate(-100%);z-index:1002;overflow-y:auto;box-shadow:2px 0 10px #0000004d;transition:transform .3s ease}.dashboard-nav.mobile-open{transform:translate(0)}}.dashboard-nav .nav-mobile-header{display:none}@media(max-width:768px){.dashboard-nav .nav-mobile-header{display:flex;justify-content:space-between;align-items:center;padding:var(--spacing-3, .75rem);background:var(--color-primary, #313945);border-bottom:1px solid rgba(255,255,255,.1);position:sticky;top:0;z-index:10}}.dashboard-nav .nav-mobile-logo{display:flex;align-items:center;gap:var(--spacing-2, .5rem)}.dashboard-nav .nav-mobile-logo img{width:36px;height:auto;filter:brightness(0) invert(1)}.dashboard-nav .nav-mobile-logo .nav-mobile-title{color:var(--color-white, #ffffff);font-size:var(--font-size-md, 1rem);font-weight:var(--font-weight-semibold, 600)}.dashboard-nav .nav-close-btn{display:flex;align-items:center;justify-content:center;width:32px;height:32px;background:#ffffff1a;border:none;border-radius:var(--border-radius-md, .375rem);cursor:pointer;color:var(--color-white, #ffffff)}.dashboard-nav .nav-close-btn:hover{background:#fff3}.nav-container{display:flex;flex-wrap:wrap;align-items:center;gap:var(--spacing-1, .25rem);padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem)}@media(max-width:768px){.nav-container{flex-direction:column;align-items:stretch;padding:var(--spacing-3, .75rem);gap:var(--spacing-1, .25rem)}}.nav-item{display:flex;align-items:center;gap:var(--spacing-2, .5rem);padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);color:var(--color-white, #ffffff);text-decoration:none;border-radius:var(--border-radius-md, .375rem);font-size:var(--font-size-sm, .875rem);font-weight:var(--font-weight-medium, 500);transition:all .2s ease;cursor:pointer;background:transparent;border:none;font-family:inherit;white-space:nowrap}.nav-item:hover{background:#ffffff1a}.nav-item.active{background:#fff3}.nav-item .nav-icon{font-size:18px;flex-shrink:0}.nav-item .nav-text{flex:1;text-align:left}.nav-item .expand-icon{font-size:16px;flex-shrink:0;margin-left:var(--spacing-1, .25rem)}@media(max-width:768px){.nav-item{width:100%;padding:var(--spacing-3, .75rem)}}.nav-separator{width:1px;height:20px;background:#fff3;margin:0 var(--spacing-1, .25rem);align-self:center}@media(max-width:768px){.nav-separator{width:100%;height:1px;margin:var(--spacing-2, .5rem) 0}}.dynamic-menu{display:flex;flex-wrap:wrap;gap:var(--spacing-1, .25rem);align-items:flex-start}@media(max-width:768px){.dynamic-menu{flex-direction:column;width:100%}}.menu-item-container{position:relative}@media(max-width:768px){.menu-item-container{width:100%}}@media(min-width:769px){.menu-item-container>.submenu{position:absolute;top:100%;left:0;min-width:220px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1000;margin-top:var(--spacing-1, .25rem)}}@media(max-width:768px){.menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}@media(min-width:769px){.submenu{min-width:200px}}@media(max-width:768px){.submenu{width:100%}}.submenu .menu-item-container{width:100%}@media(min-width:769px){.submenu .menu-item-container>.submenu{position:absolute;top:0;left:100%;min-width:200px;background:#3c4557;border-radius:var(--border-radius-md, .375rem);box-shadow:0 4px 12px #0000004d;padding:var(--spacing-2, .5rem) 0;z-index:1001;margin-left:2px}}@media(max-width:768px){.submenu .menu-item-container>.submenu{padding-left:var(--spacing-4, 1rem)}}.submenu .submenu-item{padding:var(--spacing-2, .5rem) var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);width:100%;box-sizing:border-box}.submenu .submenu-item:hover{background:#ffffff1a}.menu-loading{display:flex;align-items:center;gap:var(--spacing-2, .5rem);color:#ffffffb3;padding:var(--spacing-2, .5rem) var(--spacing-3, .75rem);font-size:var(--font-size-sm, .875rem)}.dashboard-content{flex:1;background:var(--color-white, #ffffff)}.dashboard-content .content-wrapper{padding:var(--spacing-4, 1rem) var(--spacing-6, 1.5rem);max-width:1400px;margin:0 auto}@media(max-width:768px){.dashboard-content .content-wrapper{padding:var(--spacing-3, .75rem)}}.dashboard-footer{background:#3c4557;color:#c5c8cf;padding:var(--spacing-3, .75rem) var(--spacing-4, 1rem);border-top:1px solid rgba(255,255,255,.1)}.dashboard-footer .footer-content{text-align:center}.dashboard-footer .footer-text{font-size:var(--font-size-xs, .75rem)}@media(min-width:769px){.mobile-user{display:none}}@media(max-width:768px){.desktop-user{display:none}}@keyframes slideDown{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}\n"] }]
1413
1497
  }], ctorParameters: () => [{ type: AuthService }, { type: AuthorizationService }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], onDocumentClick: [{
1414
1498
  type: HostListener,
1415
1499
  args: ['document:click', ['$event']]
@@ -1425,5 +1509,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
1425
1509
  * Generated bundle index. Do not edit.
1426
1510
  */
1427
1511
 
1428
- export { AuthService, AuthorizationService, ConfigService, DEFAULT_DASHBOARD_CONFIG, DEFAULT_LIBRARY_CONFIG, DEFAULT_LOGIN_CONFIG, DashboardComponent, FrmkConfigStore, LoginComponent, VERSION, VERSION_INFO, authGuard };
1512
+ export { AuthService, AuthorizationService, ConfigService, DEFAULT_DASHBOARD_CONFIG, DEFAULT_LIBRARY_CONFIG, DEFAULT_LOGIN_CONFIG, DashboardComponent, FrmkConfigStore, LoginComponent, TokenStorageService, VERSION, VERSION_INFO, authGuard };
1429
1513
  //# sourceMappingURL=shared-lib-angular.mjs.map