shared-lib-angular 2.1.0 → 2.1.2

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.
@@ -3,13 +3,12 @@ import { signal, computed, Injectable, inject, input, Component, HostListener }
3
3
  import * as i1 from 'angular-oauth2-oidc';
4
4
  import { OAuthEvent } from 'angular-oauth2-oidc';
5
5
  import { BehaviorSubject, firstValueFrom, of, Observable } from 'rxjs';
6
- import * as i4 from '@angular/router';
6
+ import * as i3 from '@angular/router';
7
7
  import { Router, RouterModule } from '@angular/router';
8
8
  import * as i1$1 from '@angular/common/http';
9
9
  import { HttpParams, HttpHeaders } from '@angular/common/http';
10
10
  import { map, catchError, take } from 'rxjs/operators';
11
- import * as i2 from '@angular/common';
12
- import { CommonModule } from '@angular/common';
11
+ import { NgStyle, AsyncPipe, NgTemplateOutlet } from '@angular/common';
13
12
 
14
13
  // ========================================
15
14
  // VERSION - Auto-generado por sync-version.js
@@ -20,14 +19,14 @@ import { CommonModule } from '@angular/common';
20
19
  * Versión actual de la librería @dinafi/frmk
21
20
  * Sincronizada con package.json
22
21
  */
23
- const VERSION = '2.1.0';
22
+ const VERSION = '2.1.2';
24
23
  /**
25
24
  * Información completa de la versión
26
25
  */
27
26
  const VERSION_INFO = {
28
- version: '2.1.0',
27
+ version: '2.1.2',
29
28
  name: 'shared-lib-angular',
30
- buildDate: '2026-02-18T20:42:24.863Z',
29
+ buildDate: '2026-02-18T21:46:28.366Z',
31
30
  angular: '^20.0.0'
32
31
  };
33
32
 
@@ -116,10 +115,23 @@ class FrmkConfigStore {
116
115
  }
117
116
  return cfg;
118
117
  }, ...(ngDevMode ? [{ debugName: "libraryConfig" }] : []));
119
- /** Configuración del componente Login (defaults + libraryConfig.loginConfig). */
118
+ /** Configuración del componente Login (defaults + libraryConfig.loginConfig).
119
+ * Las URLs de forgot links se resuelven dinámicamente con datos del Config Server.
120
+ * Placeholders soportados: {keycloak}, {realm}, {clientId}, {authServer}, {proto}
121
+ */
120
122
  loginConfig = computed(() => {
121
123
  const lib = this.libraryConfig();
122
- return { ...DEFAULT_LOGIN_CONFIG, ...(lib.loginConfig || {}) };
124
+ const merged = { ...DEFAULT_LOGIN_CONFIG, ...(lib.loginConfig || {}) };
125
+ // Resolver placeholders en URLs de forgot links con datos del appConfig
126
+ try {
127
+ const app = this.appConfig();
128
+ merged.forgotPasswordUrl = this.resolveUrlPlaceholders(merged.forgotPasswordUrl, app);
129
+ merged.forgotUsernameUrl = this.resolveUrlPlaceholders(merged.forgotUsernameUrl, app);
130
+ }
131
+ catch {
132
+ // appConfig puede lanzar si no hay server config aún — dejamos las URLs tal cual
133
+ }
134
+ return merged;
123
135
  }, ...(ngDevMode ? [{ debugName: "loginConfig" }] : []));
124
136
  /** Configuración del componente Dashboard (defaults + libraryConfig.dashboardConfig). */
125
137
  dashboardConfig = computed(() => {
@@ -339,6 +351,31 @@ class FrmkConfigStore {
339
351
  c.authorization.baseUrl = `${proto}://${c.authorization.baseUrl}`;
340
352
  return c;
341
353
  }
354
+ /**
355
+ * Resuelve placeholders dinámicos en URLs de forgot links.
356
+ * Placeholders soportados:
357
+ * {keycloak} → hosts.keycloak
358
+ * {realm} → paths.realm
359
+ * {clientId} → auth.clientId
360
+ * {authServer} → hosts.authServer
361
+ * {proto} → 'https' o 'http' según requireHttps
362
+ *
363
+ * @example
364
+ * // Input: '{proto}://{keycloak}/realms/{realm}/login-actions/reset-credentials?client_id={clientId}'
365
+ * // Output: 'https://keycloak.mh.gob.sv/realms/MI_REALM/login-actions/reset-credentials?client_id=mi-app'
366
+ */
367
+ resolveUrlPlaceholders(url, app) {
368
+ if (!url || !url.includes('{')) {
369
+ return url;
370
+ }
371
+ const proto = app.environment.requireHttps ? 'https' : 'http';
372
+ return url
373
+ .replace(/\{proto\}/g, proto)
374
+ .replace(/\{keycloak\}/g, app.hosts.keycloak)
375
+ .replace(/\{realm\}/g, app.paths.realm)
376
+ .replace(/\{clientId\}/g, app.auth.clientId)
377
+ .replace(/\{authServer\}/g, app.hosts.authServer);
378
+ }
342
379
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FrmkConfigStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
343
380
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FrmkConfigStore, providedIn: 'root' });
344
381
  }
@@ -903,7 +940,7 @@ class AuthService {
903
940
  return {};
904
941
  }
905
942
  }
906
- 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 });
943
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, deps: [{ token: i1.OAuthService }, { token: i3.Router }, { token: FrmkConfigStore }, { token: TokenStorageService }], target: i0.ɵɵFactoryTarget.Injectable });
907
944
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, providedIn: 'root' });
908
945
  }
909
946
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: AuthService, decorators: [{
@@ -911,7 +948,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
911
948
  args: [{
912
949
  providedIn: 'root'
913
950
  }]
914
- }], ctorParameters: () => [{ type: i1.OAuthService }, { type: i4.Router }, { type: FrmkConfigStore }, { type: TokenStorageService }] });
951
+ }], ctorParameters: () => [{ type: i1.OAuthService }, { type: i3.Router }, { type: FrmkConfigStore }, { type: TokenStorageService }] });
915
952
 
916
953
  class ConfigService {
917
954
  http;
@@ -1264,11 +1301,11 @@ class LoginComponent {
1264
1301
  }
1265
1302
  }
1266
1303
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: LoginComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1267
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.16", type: LoginComponent, isStandalone: true, selector: "lib-login", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"login-page\" [ngStyle]=\"getCustomStyles()\">\r\n <div class=\"login-page__split\">\r\n\r\n <!-- LEFT: Branding Panel -->\r\n <div class=\"login-page__branding\">\r\n <div class=\"login-page__branding-inner\">\r\n <div class=\"login-page__branding-deco\">\r\n <span class=\"deco-line deco-line--1\"></span>\r\n <span class=\"deco-line deco-line--2\"></span>\r\n <span class=\"deco-line deco-line--3\"></span>\r\n </div>\r\n <div class=\"login-page__branding-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__branding-logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n <h2 class=\"login-page__branding-title\">{{ getBrandingTitle() }}</h2>\r\n <p *ngIf=\"getBrandingSubtitle()\" class=\"login-page__branding-subtitle\">{{ getBrandingSubtitle() }}</p>\r\n <p *ngIf=\"getConfig('institutionName')\" class=\"login-page__branding-institution\">\r\n {{ getConfig('institutionName') }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT: Login Form Panel -->\r\n <div class=\"login-page__form-panel\">\r\n <div class=\"login-page__form-card\">\r\n <!-- Logo (visible on mobile when branding panel is hidden) -->\r\n <div class=\"login-page__mobile-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n\r\n <!-- Header -->\r\n <div class=\"login-page__header\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <p *ngIf=\"getConfig('subtitle')\" class=\"subtitle\">{{ getConfig('subtitle') }}</p>\r\n </div>\r\n\r\n <!-- Login Form -->\r\n <div *ngIf=\"(authService.isDoneLoading$ | async)\" class=\"login-page__form\">\r\n <div *ngIf=\"!(authService.isAuthenticated$ | async)\">\r\n <!-- Status Message -->\r\n <div *ngIf=\"statusMessage\" class=\"auth-alert auth-alert--info\">\r\n <div class=\"auth-alert__content\">\r\n <p>{{ statusMessage }}</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Login Button -->\r\n <button\r\n class=\"btn btn--primary btn--lg\"\r\n (click)=\"login()\"\r\n [disabled]=\"isLoggingIn\"\r\n [class.loading]=\"isLoggingIn\">\r\n {{ isLoggingIn ? 'Redirigiendo...' : getConfig('loginButtonText') }}\r\n </button>\r\n\r\n <!-- Forgot Links -->\r\n <div *ngIf=\"getConfig('showForgotLinks')\" class=\"login-page__forgot-links\">\r\n <a\r\n *ngIf=\"getConfig('forgotPasswordUrl')\"\r\n [href]=\"getConfig('forgotPasswordUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotPasswordText') }}\r\n </a>\r\n <a\r\n *ngIf=\"getConfig('forgotUsernameUrl')\"\r\n [href]=\"getConfig('forgotUsernameUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotUsernameText') }}\r\n </a>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"login-page__footer\" *ngIf=\"getConfig('showFooter')\">\r\n <p *ngIf=\"getConfig('footerText'); else defaultFooter\">\r\n {{ getConfig('footerText') }}\r\n </p>\r\n <ng-template #defaultFooter>\r\n <p>\r\n Acceso seguro para usuarios de\r\n <strong>{{ getConfig('institutionName') }}</strong>\r\n </p>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Already Authenticated -->\r\n <div *ngIf=\"authService.isAuthenticated$ | async\" class=\"auth-alert auth-alert--success\">\r\n <div class=\"auth-alert__content\">\r\n <h4>\u00A1Sesi\u00F3n activa!</h4>\r\n <p>Ya tienes una sesi\u00F3n iniciada en el sistema.</p>\r\n </div>\r\n\r\n <div class=\"login-page__actions\">\r\n <button class=\"btn btn--primary btn--lg\" (click)=\"goToDashboard()\">\r\n {{ getConfig('dashboardButtonText') }}\r\n </button>\r\n\r\n <button\r\n *ngIf=\"getConfig('showLogoutButton')\"\r\n class=\"btn btn--secondary btn--lg\"\r\n (click)=\"logout()\">\r\n {{ getConfig('logoutButtonText') }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading State -->\r\n <div *ngIf=\"!(authService.isDoneLoading$ | async)\" class=\"auth-loading\">\r\n <div class=\"auth-loading__spinner\"></div>\r\n <p class=\"auth-loading__text\">Procesando autenticaci\u00F3n...</p>\r\n </div>\r\n\r\n <!-- Version -->\r\n <div *ngIf=\"getConfig('showVersion')\" class=\"login-page__version\">\r\n <span>v{{ getConfig('version') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100vh}.login-page{min-height:100vh;display:flex;background:var(--color-primary, #313945);position:relative}.login-page__split{display:flex;width:100%;min-height:100vh}.login-page__branding{flex:0 0 42%;max-width:42%;display:flex;align-items:center;justify-content:center;background:var(--color-primary, #313945);color:var(--login-text-color, var(--color-white, #ffffff));position:relative;overflow:hidden;padding:var(--spacing-8, 2rem)}.login-page__branding-inner{position:relative;z-index:1;text-align:center;max-width:380px;padding:var(--spacing-8, 2rem)}.login-page__branding-deco{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:hidden}.login-page__branding-deco .deco-line{position:absolute;background:#ffffff0f;border-radius:50%}.login-page__branding-deco .deco-line--1{width:400px;height:400px;top:-80px;left:-120px;border:2px solid rgba(255,255,255,.08);background:transparent}.login-page__branding-deco .deco-line--2{width:300px;height:300px;bottom:-60px;right:-80px;border:2px solid rgba(255,255,255,.06);background:transparent}.login-page__branding-deco .deco-line--3{width:150px;height:150px;top:50%;left:60%;transform:translate(-50%,-50%);border:1px solid rgba(255,255,255,.05);background:#ffffff05}.login-page__branding-logo{margin-bottom:var(--spacing-6, 1.5rem);display:flex;justify-content:center;align-items:center}.login-page__branding-logo-img{object-fit:contain;filter:drop-shadow(0 4px 12px rgba(0,0,0,.3));transition:transform .25s cubic-bezier(.4,0,.2,1)}.login-page__branding-logo-img:hover{transform:scale(1.05)}.login-page__branding-title{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0;line-height:1.2}.login-page__branding-subtitle{font-size:var(--font-size-base, 1rem);color:#fffc;margin:0 0 var(--spacing-4, 1rem) 0;line-height:1.5}.login-page__branding-institution{font-size:var(--font-size-sm, .875rem);color:#fff9;margin:0;font-style:italic}@media(max-width:768px){.login-page__branding{display:none}}.login-page__form-panel{flex:1;display:flex;align-items:center;justify-content:center;background:var(--login-bg-color, #2a3140);padding:var(--spacing-8, 2rem);position:relative}.login-page__form-panel:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:6px;background:linear-gradient(180deg,#3c4557,#313945,#3c4557)}@media(max-width:768px){.login-page__form-panel:before{display:none}}@media(max-width:768px){.login-page__form-panel{padding:var(--spacing-4, 1rem)}}.login-page__form-card{width:100%;max-width:420px;animation:slideInUp .6s cubic-bezier(.4,0,.2,1)}.login-page__mobile-logo{display:none;width:100px;height:100px;margin:0 auto var(--spacing-6, 1.5rem) auto;justify-content:center;align-items:center}@media(max-width:768px){.login-page__mobile-logo{display:flex}}.login-page__logo-img{width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 4px 8px rgba(30,58,95,.1))}.login-page__header{text-align:center;margin-bottom:var(--spacing-8, 2rem)}.login-page__header h1{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0}@media(max-width:576px){.login-page__header h1{font-size:var(--font-size-2xl, 1.5rem)}}.login-page__header .subtitle{color:var(--login-text-color, var(--color-white, #ffffff));font-size:var(--font-size-base, 1rem);margin:0;font-weight:var(--font-weight-normal, 400);opacity:.8}.login-page__form .btn{width:100%;margin-bottom:var(--spacing-3, .75rem)}.login-page__forgot-links{display:flex;justify-content:center;gap:var(--spacing-4, 1rem);margin-top:var(--spacing-3, .75rem);margin-bottom:var(--spacing-3, .75rem);flex-wrap:wrap}.login-page__forgot-link{font-size:var(--font-size-sm, .875rem);color:#ffffffb3;text-decoration:none;transition:color .2s ease,text-decoration .2s ease;cursor:pointer}.login-page__forgot-link:hover{color:var(--color-white, #ffffff);text-decoration:underline}.login-page__forgot-link:focus-visible{outline:2px solid var(--color-white, #ffffff);outline-offset:2px;border-radius:2px}.login-page__actions{margin-top:var(--spacing-6, 1.5rem);display:flex;flex-direction:column;gap:var(--spacing-3, .75rem)}.login-page__footer{text-align:center;margin-top:var(--spacing-8, 2rem);padding-top:var(--spacing-6, 1.5rem);border-top:1px solid rgba(255,255,255,.2)}.login-page__footer p{color:#fffc;font-size:var(--font-size-sm, .875rem);margin:0}.login-page__version{text-align:center;margin-top:var(--spacing-4, 1rem)}.login-page__version span{font-size:var(--font-size-xs, .75rem);color:#ffffff80}.btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--spacing-3, .75rem) var(--spacing-6, 1.5rem);font-size:var(--font-size-base, 1rem);font-weight:var(--font-weight-medium, 500);border-radius:var(--border-radius-lg, .5rem);border:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1)}.btn--lg{height:48px;font-size:var(--font-size-base, 1rem)}.btn--primary{background-color:var(--color-gray-900, #212529);color:var(--color-white, #ffffff);border:1px solid var(--color-primary, #313945)}.btn--primary:hover:not(:disabled){background-color:var(--color-primary-hover, #1c1e4d);border-color:var(--color-primary-hover, #1c1e4d);transform:translateY(-2px);box-shadow:0 10px 15px -3px #0000001a}.btn--primary:active:not(:disabled){transform:translateY(0)}.btn--primary:disabled{opacity:.7;cursor:not-allowed}.btn--primary.loading{pointer-events:none}.btn--secondary{background-color:transparent;color:var(--color-white, #ffffff);border:1px solid rgba(255,255,255,.3)}.btn--secondary:hover:not(:disabled){background-color:#ffffff1a;border-color:#ffffff80}.auth-alert{padding:var(--spacing-4, 1rem);border-radius:var(--border-radius-lg, .5rem);margin-bottom:var(--spacing-4, 1rem)}.auth-alert--info{background:#2ca8ff1a;border:1px solid rgba(44,168,255,.3)}.auth-alert--success{background:#018e111a;border:1px solid rgba(1,142,17,.3)}.auth-alert__content h4{margin:0 0 var(--spacing-2, .5rem) 0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600)}.auth-alert__content p{margin:0;font-size:var(--font-size-sm, .875rem);opacity:.9}.auth-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--spacing-8, 2rem)}.auth-loading__spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top:3px solid var(--color-white, #ffffff);border-radius:50%;animation:spin 1s linear infinite}.auth-loading__text{margin-top:var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);color:#fffc}@keyframes slideInUp{0%{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.login-page__form-card{animation:none}.login-page__branding-logo-img{transition:none}.auth-loading__spinner{animation:none}}@media(prefers-contrast:high){.login-page__form-card{border:2px solid var(--color-white, #ffffff)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] });
1304
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: LoginComponent, isStandalone: true, selector: "lib-login", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"login-page\" [ngStyle]=\"getCustomStyles()\">\r\n <div class=\"login-page__split\">\r\n\r\n <!-- LEFT: Branding Panel -->\r\n <div class=\"login-page__branding\">\r\n <div class=\"login-page__branding-inner\">\r\n <div class=\"login-page__branding-deco\">\r\n <span class=\"deco-line deco-line--1\"></span>\r\n <span class=\"deco-line deco-line--2\"></span>\r\n <span class=\"deco-line deco-line--3\"></span>\r\n </div>\r\n <div class=\"login-page__branding-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__branding-logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n <h2 class=\"login-page__branding-title\">{{ getBrandingTitle() }}</h2>\r\n @if (getBrandingSubtitle()) {\r\n <p class=\"login-page__branding-subtitle\">{{ getBrandingSubtitle() }}</p>\r\n }\r\n @if (getConfig('institutionName')) {\r\n <p class=\"login-page__branding-institution\">\r\n {{ getConfig('institutionName') }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT: Login Form Panel -->\r\n <div class=\"login-page__form-panel\">\r\n <div class=\"login-page__form-card\">\r\n <!-- Logo (visible on mobile when branding panel is hidden) -->\r\n <div class=\"login-page__mobile-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n\r\n <!-- Header -->\r\n <div class=\"login-page__header\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n @if (getConfig('subtitle')) {\r\n <p class=\"subtitle\">{{ getConfig('subtitle') }}</p>\r\n }\r\n </div>\r\n\r\n <!-- Login Form -->\r\n @if (authService.isDoneLoading$ | async) {\r\n <div class=\"login-page__form\">\r\n @if (!(authService.isAuthenticated$ | async)) {\r\n <div>\r\n <!-- Status Message -->\r\n @if (statusMessage) {\r\n <div class=\"auth-alert auth-alert--info\">\r\n <div class=\"auth-alert__content\">\r\n <p>{{ statusMessage }}</p>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Login Button -->\r\n <button\r\n class=\"btn btn--primary btn--lg\"\r\n (click)=\"login()\"\r\n [disabled]=\"isLoggingIn\"\r\n [class.loading]=\"isLoggingIn\">\r\n {{ isLoggingIn ? 'Redirigiendo...' : getConfig('loginButtonText') }}\r\n </button>\r\n\r\n <!-- Forgot Links -->\r\n @if (getConfig('showForgotLinks')) {\r\n <div class=\"login-page__forgot-links\">\r\n @if (getConfig('forgotPasswordUrl')) {\r\n <a\r\n [href]=\"getConfig('forgotPasswordUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotPasswordText') }}\r\n </a>\r\n }\r\n @if (getConfig('forgotUsernameUrl')) {\r\n <a\r\n [href]=\"getConfig('forgotUsernameUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotUsernameText') }}\r\n </a>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer -->\r\n @if (getConfig('showFooter')) {\r\n <div class=\"login-page__footer\">\r\n @if (getConfig('footerText')) {\r\n <p>{{ getConfig('footerText') }}</p>\r\n } @else {\r\n <p>\r\n Acceso seguro para usuarios de\r\n <strong>{{ getConfig('institutionName') }}</strong>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Already Authenticated -->\r\n <div class=\"auth-alert auth-alert--success\">\r\n <div class=\"auth-alert__content\">\r\n <h4>\u00A1Sesi\u00F3n activa!</h4>\r\n <p>Ya tienes una sesi\u00F3n iniciada en el sistema.</p>\r\n </div>\r\n\r\n <div class=\"login-page__actions\">\r\n <button class=\"btn btn--primary btn--lg\" (click)=\"goToDashboard()\">\r\n {{ getConfig('dashboardButtonText') }}\r\n </button>\r\n\r\n @if (getConfig('showLogoutButton')) {\r\n <button\r\n class=\"btn btn--secondary btn--lg\"\r\n (click)=\"logout()\">\r\n {{ getConfig('logoutButtonText') }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Loading State -->\r\n <div class=\"auth-loading\">\r\n <div class=\"auth-loading__spinner\"></div>\r\n <p class=\"auth-loading__text\">Procesando autenticaci\u00F3n...</p>\r\n </div>\r\n }\r\n\r\n <!-- Version -->\r\n @if (getConfig('showVersion')) {\r\n <div class=\"login-page__version\">\r\n <span>v{{ getConfig('version') }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100vh}.login-page{min-height:100vh;display:flex;background:var(--color-primary, #313945);position:relative}.login-page__split{display:flex;width:100%;min-height:100vh}.login-page__branding{flex:0 0 42%;max-width:42%;display:flex;align-items:center;justify-content:center;background:var(--color-primary, #313945);color:var(--login-text-color, var(--color-white, #ffffff));position:relative;overflow:hidden;padding:var(--spacing-8, 2rem)}.login-page__branding-inner{position:relative;z-index:1;text-align:center;max-width:380px;padding:var(--spacing-8, 2rem)}.login-page__branding-deco{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:hidden}.login-page__branding-deco .deco-line{position:absolute;background:#ffffff0f;border-radius:50%}.login-page__branding-deco .deco-line--1{width:400px;height:400px;top:-80px;left:-120px;border:2px solid rgba(255,255,255,.08);background:transparent}.login-page__branding-deco .deco-line--2{width:300px;height:300px;bottom:-60px;right:-80px;border:2px solid rgba(255,255,255,.06);background:transparent}.login-page__branding-deco .deco-line--3{width:150px;height:150px;top:50%;left:60%;transform:translate(-50%,-50%);border:1px solid rgba(255,255,255,.05);background:#ffffff05}.login-page__branding-logo{margin-bottom:var(--spacing-6, 1.5rem);display:flex;justify-content:center;align-items:center}.login-page__branding-logo-img{object-fit:contain;filter:drop-shadow(0 4px 12px rgba(0,0,0,.3));transition:transform .25s cubic-bezier(.4,0,.2,1)}.login-page__branding-logo-img:hover{transform:scale(1.05)}.login-page__branding-title{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0;line-height:1.2}.login-page__branding-subtitle{font-size:var(--font-size-base, 1rem);color:#fffc;margin:0 0 var(--spacing-4, 1rem) 0;line-height:1.5}.login-page__branding-institution{font-size:var(--font-size-sm, .875rem);color:#fff9;margin:0;font-style:italic}@media(max-width:768px){.login-page__branding{display:none}}.login-page__form-panel{flex:1;display:flex;align-items:center;justify-content:center;background:var(--login-bg-color, #2a3140);padding:var(--spacing-8, 2rem);position:relative}.login-page__form-panel:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:6px;background:linear-gradient(180deg,#3c4557,#313945,#3c4557)}@media(max-width:768px){.login-page__form-panel:before{display:none}}@media(max-width:768px){.login-page__form-panel{padding:var(--spacing-4, 1rem)}}.login-page__form-card{width:100%;max-width:420px;animation:slideInUp .6s cubic-bezier(.4,0,.2,1)}.login-page__mobile-logo{display:none;width:100px;height:100px;margin:0 auto var(--spacing-6, 1.5rem) auto;justify-content:center;align-items:center}@media(max-width:768px){.login-page__mobile-logo{display:flex}}.login-page__logo-img{width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 4px 8px rgba(30,58,95,.1))}.login-page__header{text-align:center;margin-bottom:var(--spacing-8, 2rem)}.login-page__header h1{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0}@media(max-width:576px){.login-page__header h1{font-size:var(--font-size-2xl, 1.5rem)}}.login-page__header .subtitle{color:var(--login-text-color, var(--color-white, #ffffff));font-size:var(--font-size-base, 1rem);margin:0;font-weight:var(--font-weight-normal, 400);opacity:.8}.login-page__form .btn{width:100%;margin-bottom:var(--spacing-3, .75rem)}.login-page__forgot-links{display:flex;justify-content:center;gap:var(--spacing-4, 1rem);margin-top:var(--spacing-3, .75rem);margin-bottom:var(--spacing-3, .75rem);flex-wrap:wrap}.login-page__forgot-link{font-size:var(--font-size-sm, .875rem);color:#ffffffb3;text-decoration:none;transition:color .2s ease,text-decoration .2s ease;cursor:pointer}.login-page__forgot-link:hover{color:var(--color-white, #ffffff);text-decoration:underline}.login-page__forgot-link:focus-visible{outline:2px solid var(--color-white, #ffffff);outline-offset:2px;border-radius:2px}.login-page__actions{margin-top:var(--spacing-6, 1.5rem);display:flex;flex-direction:column;gap:var(--spacing-3, .75rem)}.login-page__footer{text-align:center;margin-top:var(--spacing-8, 2rem);padding-top:var(--spacing-6, 1.5rem);border-top:1px solid rgba(255,255,255,.2)}.login-page__footer p{color:#fffc;font-size:var(--font-size-sm, .875rem);margin:0}.login-page__version{text-align:center;margin-top:var(--spacing-4, 1rem)}.login-page__version span{font-size:var(--font-size-xs, .75rem);color:#ffffff80}.btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--spacing-3, .75rem) var(--spacing-6, 1.5rem);font-size:var(--font-size-base, 1rem);font-weight:var(--font-weight-medium, 500);border-radius:var(--border-radius-lg, .5rem);border:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1)}.btn--lg{height:48px;font-size:var(--font-size-base, 1rem)}.btn--primary{background-color:var(--color-gray-900, #212529);color:var(--color-white, #ffffff);border:1px solid var(--color-primary, #313945)}.btn--primary:hover:not(:disabled){background-color:var(--color-primary-hover, #1c1e4d);border-color:var(--color-primary-hover, #1c1e4d);transform:translateY(-2px);box-shadow:0 10px 15px -3px #0000001a}.btn--primary:active:not(:disabled){transform:translateY(0)}.btn--primary:disabled{opacity:.7;cursor:not-allowed}.btn--primary.loading{pointer-events:none}.btn--secondary{background-color:transparent;color:var(--color-white, #ffffff);border:1px solid rgba(255,255,255,.3)}.btn--secondary:hover:not(:disabled){background-color:#ffffff1a;border-color:#ffffff80}.auth-alert{padding:var(--spacing-4, 1rem);border-radius:var(--border-radius-lg, .5rem);margin-bottom:var(--spacing-4, 1rem)}.auth-alert--info{background:#2ca8ff1a;border:1px solid rgba(44,168,255,.3)}.auth-alert--success{background:#018e111a;border:1px solid rgba(1,142,17,.3)}.auth-alert__content h4{margin:0 0 var(--spacing-2, .5rem) 0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600)}.auth-alert__content p{margin:0;font-size:var(--font-size-sm, .875rem);opacity:.9}.auth-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--spacing-8, 2rem)}.auth-loading__spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top:3px solid var(--color-white, #ffffff);border-radius:50%;animation:spin 1s linear infinite}.auth-loading__text{margin-top:var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);color:#fffc}@keyframes slideInUp{0%{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.login-page__form-card{animation:none}.login-page__branding-logo-img{transition:none}.auth-loading__spinner{animation:none}}@media(prefers-contrast:high){.login-page__form-card{border:2px solid var(--color-white, #ffffff)}}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
1268
1305
  }
1269
1306
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: LoginComponent, decorators: [{
1270
1307
  type: Component,
1271
- args: [{ selector: 'lib-login', standalone: true, imports: [CommonModule], template: "<div class=\"login-page\" [ngStyle]=\"getCustomStyles()\">\r\n <div class=\"login-page__split\">\r\n\r\n <!-- LEFT: Branding Panel -->\r\n <div class=\"login-page__branding\">\r\n <div class=\"login-page__branding-inner\">\r\n <div class=\"login-page__branding-deco\">\r\n <span class=\"deco-line deco-line--1\"></span>\r\n <span class=\"deco-line deco-line--2\"></span>\r\n <span class=\"deco-line deco-line--3\"></span>\r\n </div>\r\n <div class=\"login-page__branding-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__branding-logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n <h2 class=\"login-page__branding-title\">{{ getBrandingTitle() }}</h2>\r\n <p *ngIf=\"getBrandingSubtitle()\" class=\"login-page__branding-subtitle\">{{ getBrandingSubtitle() }}</p>\r\n <p *ngIf=\"getConfig('institutionName')\" class=\"login-page__branding-institution\">\r\n {{ getConfig('institutionName') }}\r\n </p>\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT: Login Form Panel -->\r\n <div class=\"login-page__form-panel\">\r\n <div class=\"login-page__form-card\">\r\n <!-- Logo (visible on mobile when branding panel is hidden) -->\r\n <div class=\"login-page__mobile-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n\r\n <!-- Header -->\r\n <div class=\"login-page__header\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n <p *ngIf=\"getConfig('subtitle')\" class=\"subtitle\">{{ getConfig('subtitle') }}</p>\r\n </div>\r\n\r\n <!-- Login Form -->\r\n <div *ngIf=\"(authService.isDoneLoading$ | async)\" class=\"login-page__form\">\r\n <div *ngIf=\"!(authService.isAuthenticated$ | async)\">\r\n <!-- Status Message -->\r\n <div *ngIf=\"statusMessage\" class=\"auth-alert auth-alert--info\">\r\n <div class=\"auth-alert__content\">\r\n <p>{{ statusMessage }}</p>\r\n </div>\r\n </div>\r\n\r\n <!-- Login Button -->\r\n <button\r\n class=\"btn btn--primary btn--lg\"\r\n (click)=\"login()\"\r\n [disabled]=\"isLoggingIn\"\r\n [class.loading]=\"isLoggingIn\">\r\n {{ isLoggingIn ? 'Redirigiendo...' : getConfig('loginButtonText') }}\r\n </button>\r\n\r\n <!-- Forgot Links -->\r\n <div *ngIf=\"getConfig('showForgotLinks')\" class=\"login-page__forgot-links\">\r\n <a\r\n *ngIf=\"getConfig('forgotPasswordUrl')\"\r\n [href]=\"getConfig('forgotPasswordUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotPasswordText') }}\r\n </a>\r\n <a\r\n *ngIf=\"getConfig('forgotUsernameUrl')\"\r\n [href]=\"getConfig('forgotUsernameUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotUsernameText') }}\r\n </a>\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div class=\"login-page__footer\" *ngIf=\"getConfig('showFooter')\">\r\n <p *ngIf=\"getConfig('footerText'); else defaultFooter\">\r\n {{ getConfig('footerText') }}\r\n </p>\r\n <ng-template #defaultFooter>\r\n <p>\r\n Acceso seguro para usuarios de\r\n <strong>{{ getConfig('institutionName') }}</strong>\r\n </p>\r\n </ng-template>\r\n </div>\r\n </div>\r\n\r\n <!-- Already Authenticated -->\r\n <div *ngIf=\"authService.isAuthenticated$ | async\" class=\"auth-alert auth-alert--success\">\r\n <div class=\"auth-alert__content\">\r\n <h4>\u00A1Sesi\u00F3n activa!</h4>\r\n <p>Ya tienes una sesi\u00F3n iniciada en el sistema.</p>\r\n </div>\r\n\r\n <div class=\"login-page__actions\">\r\n <button class=\"btn btn--primary btn--lg\" (click)=\"goToDashboard()\">\r\n {{ getConfig('dashboardButtonText') }}\r\n </button>\r\n\r\n <button\r\n *ngIf=\"getConfig('showLogoutButton')\"\r\n class=\"btn btn--secondary btn--lg\"\r\n (click)=\"logout()\">\r\n {{ getConfig('logoutButtonText') }}\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Loading State -->\r\n <div *ngIf=\"!(authService.isDoneLoading$ | async)\" class=\"auth-loading\">\r\n <div class=\"auth-loading__spinner\"></div>\r\n <p class=\"auth-loading__text\">Procesando autenticaci\u00F3n...</p>\r\n </div>\r\n\r\n <!-- Version -->\r\n <div *ngIf=\"getConfig('showVersion')\" class=\"login-page__version\">\r\n <span>v{{ getConfig('version') }}</span>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100vh}.login-page{min-height:100vh;display:flex;background:var(--color-primary, #313945);position:relative}.login-page__split{display:flex;width:100%;min-height:100vh}.login-page__branding{flex:0 0 42%;max-width:42%;display:flex;align-items:center;justify-content:center;background:var(--color-primary, #313945);color:var(--login-text-color, var(--color-white, #ffffff));position:relative;overflow:hidden;padding:var(--spacing-8, 2rem)}.login-page__branding-inner{position:relative;z-index:1;text-align:center;max-width:380px;padding:var(--spacing-8, 2rem)}.login-page__branding-deco{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:hidden}.login-page__branding-deco .deco-line{position:absolute;background:#ffffff0f;border-radius:50%}.login-page__branding-deco .deco-line--1{width:400px;height:400px;top:-80px;left:-120px;border:2px solid rgba(255,255,255,.08);background:transparent}.login-page__branding-deco .deco-line--2{width:300px;height:300px;bottom:-60px;right:-80px;border:2px solid rgba(255,255,255,.06);background:transparent}.login-page__branding-deco .deco-line--3{width:150px;height:150px;top:50%;left:60%;transform:translate(-50%,-50%);border:1px solid rgba(255,255,255,.05);background:#ffffff05}.login-page__branding-logo{margin-bottom:var(--spacing-6, 1.5rem);display:flex;justify-content:center;align-items:center}.login-page__branding-logo-img{object-fit:contain;filter:drop-shadow(0 4px 12px rgba(0,0,0,.3));transition:transform .25s cubic-bezier(.4,0,.2,1)}.login-page__branding-logo-img:hover{transform:scale(1.05)}.login-page__branding-title{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0;line-height:1.2}.login-page__branding-subtitle{font-size:var(--font-size-base, 1rem);color:#fffc;margin:0 0 var(--spacing-4, 1rem) 0;line-height:1.5}.login-page__branding-institution{font-size:var(--font-size-sm, .875rem);color:#fff9;margin:0;font-style:italic}@media(max-width:768px){.login-page__branding{display:none}}.login-page__form-panel{flex:1;display:flex;align-items:center;justify-content:center;background:var(--login-bg-color, #2a3140);padding:var(--spacing-8, 2rem);position:relative}.login-page__form-panel:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:6px;background:linear-gradient(180deg,#3c4557,#313945,#3c4557)}@media(max-width:768px){.login-page__form-panel:before{display:none}}@media(max-width:768px){.login-page__form-panel{padding:var(--spacing-4, 1rem)}}.login-page__form-card{width:100%;max-width:420px;animation:slideInUp .6s cubic-bezier(.4,0,.2,1)}.login-page__mobile-logo{display:none;width:100px;height:100px;margin:0 auto var(--spacing-6, 1.5rem) auto;justify-content:center;align-items:center}@media(max-width:768px){.login-page__mobile-logo{display:flex}}.login-page__logo-img{width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 4px 8px rgba(30,58,95,.1))}.login-page__header{text-align:center;margin-bottom:var(--spacing-8, 2rem)}.login-page__header h1{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0}@media(max-width:576px){.login-page__header h1{font-size:var(--font-size-2xl, 1.5rem)}}.login-page__header .subtitle{color:var(--login-text-color, var(--color-white, #ffffff));font-size:var(--font-size-base, 1rem);margin:0;font-weight:var(--font-weight-normal, 400);opacity:.8}.login-page__form .btn{width:100%;margin-bottom:var(--spacing-3, .75rem)}.login-page__forgot-links{display:flex;justify-content:center;gap:var(--spacing-4, 1rem);margin-top:var(--spacing-3, .75rem);margin-bottom:var(--spacing-3, .75rem);flex-wrap:wrap}.login-page__forgot-link{font-size:var(--font-size-sm, .875rem);color:#ffffffb3;text-decoration:none;transition:color .2s ease,text-decoration .2s ease;cursor:pointer}.login-page__forgot-link:hover{color:var(--color-white, #ffffff);text-decoration:underline}.login-page__forgot-link:focus-visible{outline:2px solid var(--color-white, #ffffff);outline-offset:2px;border-radius:2px}.login-page__actions{margin-top:var(--spacing-6, 1.5rem);display:flex;flex-direction:column;gap:var(--spacing-3, .75rem)}.login-page__footer{text-align:center;margin-top:var(--spacing-8, 2rem);padding-top:var(--spacing-6, 1.5rem);border-top:1px solid rgba(255,255,255,.2)}.login-page__footer p{color:#fffc;font-size:var(--font-size-sm, .875rem);margin:0}.login-page__version{text-align:center;margin-top:var(--spacing-4, 1rem)}.login-page__version span{font-size:var(--font-size-xs, .75rem);color:#ffffff80}.btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--spacing-3, .75rem) var(--spacing-6, 1.5rem);font-size:var(--font-size-base, 1rem);font-weight:var(--font-weight-medium, 500);border-radius:var(--border-radius-lg, .5rem);border:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1)}.btn--lg{height:48px;font-size:var(--font-size-base, 1rem)}.btn--primary{background-color:var(--color-gray-900, #212529);color:var(--color-white, #ffffff);border:1px solid var(--color-primary, #313945)}.btn--primary:hover:not(:disabled){background-color:var(--color-primary-hover, #1c1e4d);border-color:var(--color-primary-hover, #1c1e4d);transform:translateY(-2px);box-shadow:0 10px 15px -3px #0000001a}.btn--primary:active:not(:disabled){transform:translateY(0)}.btn--primary:disabled{opacity:.7;cursor:not-allowed}.btn--primary.loading{pointer-events:none}.btn--secondary{background-color:transparent;color:var(--color-white, #ffffff);border:1px solid rgba(255,255,255,.3)}.btn--secondary:hover:not(:disabled){background-color:#ffffff1a;border-color:#ffffff80}.auth-alert{padding:var(--spacing-4, 1rem);border-radius:var(--border-radius-lg, .5rem);margin-bottom:var(--spacing-4, 1rem)}.auth-alert--info{background:#2ca8ff1a;border:1px solid rgba(44,168,255,.3)}.auth-alert--success{background:#018e111a;border:1px solid rgba(1,142,17,.3)}.auth-alert__content h4{margin:0 0 var(--spacing-2, .5rem) 0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600)}.auth-alert__content p{margin:0;font-size:var(--font-size-sm, .875rem);opacity:.9}.auth-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--spacing-8, 2rem)}.auth-loading__spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top:3px solid var(--color-white, #ffffff);border-radius:50%;animation:spin 1s linear infinite}.auth-loading__text{margin-top:var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);color:#fffc}@keyframes slideInUp{0%{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.login-page__form-card{animation:none}.login-page__branding-logo-img{transition:none}.auth-loading__spinner{animation:none}}@media(prefers-contrast:high){.login-page__form-card{border:2px solid var(--color-white, #ffffff)}}\n"] }]
1308
+ args: [{ selector: 'lib-login', standalone: true, imports: [AsyncPipe, NgStyle], template: "<div class=\"login-page\" [ngStyle]=\"getCustomStyles()\">\r\n <div class=\"login-page__split\">\r\n\r\n <!-- LEFT: Branding Panel -->\r\n <div class=\"login-page__branding\">\r\n <div class=\"login-page__branding-inner\">\r\n <div class=\"login-page__branding-deco\">\r\n <span class=\"deco-line deco-line--1\"></span>\r\n <span class=\"deco-line deco-line--2\"></span>\r\n <span class=\"deco-line deco-line--3\"></span>\r\n </div>\r\n <div class=\"login-page__branding-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__branding-logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n <h2 class=\"login-page__branding-title\">{{ getBrandingTitle() }}</h2>\r\n @if (getBrandingSubtitle()) {\r\n <p class=\"login-page__branding-subtitle\">{{ getBrandingSubtitle() }}</p>\r\n }\r\n @if (getConfig('institutionName')) {\r\n <p class=\"login-page__branding-institution\">\r\n {{ getConfig('institutionName') }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!-- RIGHT: Login Form Panel -->\r\n <div class=\"login-page__form-panel\">\r\n <div class=\"login-page__form-card\">\r\n <!-- Logo (visible on mobile when branding panel is hidden) -->\r\n <div class=\"login-page__mobile-logo\">\r\n <img\r\n [src]=\"getConfig('logoUrl')\"\r\n [alt]=\"getConfig('logoAlt')\"\r\n class=\"login-page__logo-img\"\r\n [style.width]=\"getConfig('logoWidth')\"\r\n [style.height]=\"getConfig('logoHeight')\" />\r\n </div>\r\n\r\n <!-- Header -->\r\n <div class=\"login-page__header\">\r\n <h1>{{ getConfig('title') }}</h1>\r\n @if (getConfig('subtitle')) {\r\n <p class=\"subtitle\">{{ getConfig('subtitle') }}</p>\r\n }\r\n </div>\r\n\r\n <!-- Login Form -->\r\n @if (authService.isDoneLoading$ | async) {\r\n <div class=\"login-page__form\">\r\n @if (!(authService.isAuthenticated$ | async)) {\r\n <div>\r\n <!-- Status Message -->\r\n @if (statusMessage) {\r\n <div class=\"auth-alert auth-alert--info\">\r\n <div class=\"auth-alert__content\">\r\n <p>{{ statusMessage }}</p>\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Login Button -->\r\n <button\r\n class=\"btn btn--primary btn--lg\"\r\n (click)=\"login()\"\r\n [disabled]=\"isLoggingIn\"\r\n [class.loading]=\"isLoggingIn\">\r\n {{ isLoggingIn ? 'Redirigiendo...' : getConfig('loginButtonText') }}\r\n </button>\r\n\r\n <!-- Forgot Links -->\r\n @if (getConfig('showForgotLinks')) {\r\n <div class=\"login-page__forgot-links\">\r\n @if (getConfig('forgotPasswordUrl')) {\r\n <a\r\n [href]=\"getConfig('forgotPasswordUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotPasswordText') }}\r\n </a>\r\n }\r\n @if (getConfig('forgotUsernameUrl')) {\r\n <a\r\n [href]=\"getConfig('forgotUsernameUrl')\"\r\n target=\"_blank\"\r\n rel=\"noopener noreferrer\"\r\n class=\"login-page__forgot-link\">\r\n {{ getConfig('forgotUsernameText') }}\r\n </a>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer -->\r\n @if (getConfig('showFooter')) {\r\n <div class=\"login-page__footer\">\r\n @if (getConfig('footerText')) {\r\n <p>{{ getConfig('footerText') }}</p>\r\n } @else {\r\n <p>\r\n Acceso seguro para usuarios de\r\n <strong>{{ getConfig('institutionName') }}</strong>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Already Authenticated -->\r\n <div class=\"auth-alert auth-alert--success\">\r\n <div class=\"auth-alert__content\">\r\n <h4>\u00A1Sesi\u00F3n activa!</h4>\r\n <p>Ya tienes una sesi\u00F3n iniciada en el sistema.</p>\r\n </div>\r\n\r\n <div class=\"login-page__actions\">\r\n <button class=\"btn btn--primary btn--lg\" (click)=\"goToDashboard()\">\r\n {{ getConfig('dashboardButtonText') }}\r\n </button>\r\n\r\n @if (getConfig('showLogoutButton')) {\r\n <button\r\n class=\"btn btn--secondary btn--lg\"\r\n (click)=\"logout()\">\r\n {{ getConfig('logoutButtonText') }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Loading State -->\r\n <div class=\"auth-loading\">\r\n <div class=\"auth-loading__spinner\"></div>\r\n <p class=\"auth-loading__text\">Procesando autenticaci\u00F3n...</p>\r\n </div>\r\n }\r\n\r\n <!-- Version -->\r\n @if (getConfig('showVersion')) {\r\n <div class=\"login-page__version\">\r\n <span>v{{ getConfig('version') }}</span>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100vh}.login-page{min-height:100vh;display:flex;background:var(--color-primary, #313945);position:relative}.login-page__split{display:flex;width:100%;min-height:100vh}.login-page__branding{flex:0 0 42%;max-width:42%;display:flex;align-items:center;justify-content:center;background:var(--color-primary, #313945);color:var(--login-text-color, var(--color-white, #ffffff));position:relative;overflow:hidden;padding:var(--spacing-8, 2rem)}.login-page__branding-inner{position:relative;z-index:1;text-align:center;max-width:380px;padding:var(--spacing-8, 2rem)}.login-page__branding-deco{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0;overflow:hidden}.login-page__branding-deco .deco-line{position:absolute;background:#ffffff0f;border-radius:50%}.login-page__branding-deco .deco-line--1{width:400px;height:400px;top:-80px;left:-120px;border:2px solid rgba(255,255,255,.08);background:transparent}.login-page__branding-deco .deco-line--2{width:300px;height:300px;bottom:-60px;right:-80px;border:2px solid rgba(255,255,255,.06);background:transparent}.login-page__branding-deco .deco-line--3{width:150px;height:150px;top:50%;left:60%;transform:translate(-50%,-50%);border:1px solid rgba(255,255,255,.05);background:#ffffff05}.login-page__branding-logo{margin-bottom:var(--spacing-6, 1.5rem);display:flex;justify-content:center;align-items:center}.login-page__branding-logo-img{object-fit:contain;filter:drop-shadow(0 4px 12px rgba(0,0,0,.3));transition:transform .25s cubic-bezier(.4,0,.2,1)}.login-page__branding-logo-img:hover{transform:scale(1.05)}.login-page__branding-title{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0;line-height:1.2}.login-page__branding-subtitle{font-size:var(--font-size-base, 1rem);color:#fffc;margin:0 0 var(--spacing-4, 1rem) 0;line-height:1.5}.login-page__branding-institution{font-size:var(--font-size-sm, .875rem);color:#fff9;margin:0;font-style:italic}@media(max-width:768px){.login-page__branding{display:none}}.login-page__form-panel{flex:1;display:flex;align-items:center;justify-content:center;background:var(--login-bg-color, #2a3140);padding:var(--spacing-8, 2rem);position:relative}.login-page__form-panel:before{content:\"\";position:absolute;top:0;left:0;bottom:0;width:6px;background:linear-gradient(180deg,#3c4557,#313945,#3c4557)}@media(max-width:768px){.login-page__form-panel:before{display:none}}@media(max-width:768px){.login-page__form-panel{padding:var(--spacing-4, 1rem)}}.login-page__form-card{width:100%;max-width:420px;animation:slideInUp .6s cubic-bezier(.4,0,.2,1)}.login-page__mobile-logo{display:none;width:100px;height:100px;margin:0 auto var(--spacing-6, 1.5rem) auto;justify-content:center;align-items:center}@media(max-width:768px){.login-page__mobile-logo{display:flex}}.login-page__logo-img{width:100%;height:100%;object-fit:contain;filter:drop-shadow(0 4px 8px rgba(30,58,95,.1))}.login-page__header{text-align:center;margin-bottom:var(--spacing-8, 2rem)}.login-page__header h1{font-family:var(--font-family-display, \"Bembo Std\", Georgia, serif);font-size:var(--font-size-3xl, 1.875rem);font-weight:var(--font-weight-bold, 700);color:var(--login-text-color, var(--color-white, #ffffff));margin:0 0 var(--spacing-3, .75rem) 0}@media(max-width:576px){.login-page__header h1{font-size:var(--font-size-2xl, 1.5rem)}}.login-page__header .subtitle{color:var(--login-text-color, var(--color-white, #ffffff));font-size:var(--font-size-base, 1rem);margin:0;font-weight:var(--font-weight-normal, 400);opacity:.8}.login-page__form .btn{width:100%;margin-bottom:var(--spacing-3, .75rem)}.login-page__forgot-links{display:flex;justify-content:center;gap:var(--spacing-4, 1rem);margin-top:var(--spacing-3, .75rem);margin-bottom:var(--spacing-3, .75rem);flex-wrap:wrap}.login-page__forgot-link{font-size:var(--font-size-sm, .875rem);color:#ffffffb3;text-decoration:none;transition:color .2s ease,text-decoration .2s ease;cursor:pointer}.login-page__forgot-link:hover{color:var(--color-white, #ffffff);text-decoration:underline}.login-page__forgot-link:focus-visible{outline:2px solid var(--color-white, #ffffff);outline-offset:2px;border-radius:2px}.login-page__actions{margin-top:var(--spacing-6, 1.5rem);display:flex;flex-direction:column;gap:var(--spacing-3, .75rem)}.login-page__footer{text-align:center;margin-top:var(--spacing-8, 2rem);padding-top:var(--spacing-6, 1.5rem);border-top:1px solid rgba(255,255,255,.2)}.login-page__footer p{color:#fffc;font-size:var(--font-size-sm, .875rem);margin:0}.login-page__version{text-align:center;margin-top:var(--spacing-4, 1rem)}.login-page__version span{font-size:var(--font-size-xs, .75rem);color:#ffffff80}.btn{display:inline-flex;align-items:center;justify-content:center;padding:var(--spacing-3, .75rem) var(--spacing-6, 1.5rem);font-size:var(--font-size-base, 1rem);font-weight:var(--font-weight-medium, 500);border-radius:var(--border-radius-lg, .5rem);border:none;cursor:pointer;transition:all .25s cubic-bezier(.4,0,.2,1)}.btn--lg{height:48px;font-size:var(--font-size-base, 1rem)}.btn--primary{background-color:var(--color-gray-900, #212529);color:var(--color-white, #ffffff);border:1px solid var(--color-primary, #313945)}.btn--primary:hover:not(:disabled){background-color:var(--color-primary-hover, #1c1e4d);border-color:var(--color-primary-hover, #1c1e4d);transform:translateY(-2px);box-shadow:0 10px 15px -3px #0000001a}.btn--primary:active:not(:disabled){transform:translateY(0)}.btn--primary:disabled{opacity:.7;cursor:not-allowed}.btn--primary.loading{pointer-events:none}.btn--secondary{background-color:transparent;color:var(--color-white, #ffffff);border:1px solid rgba(255,255,255,.3)}.btn--secondary:hover:not(:disabled){background-color:#ffffff1a;border-color:#ffffff80}.auth-alert{padding:var(--spacing-4, 1rem);border-radius:var(--border-radius-lg, .5rem);margin-bottom:var(--spacing-4, 1rem)}.auth-alert--info{background:#2ca8ff1a;border:1px solid rgba(44,168,255,.3)}.auth-alert--success{background:#018e111a;border:1px solid rgba(1,142,17,.3)}.auth-alert__content h4{margin:0 0 var(--spacing-2, .5rem) 0;font-size:var(--font-size-lg, 1.125rem);font-weight:var(--font-weight-semibold, 600)}.auth-alert__content p{margin:0;font-size:var(--font-size-sm, .875rem);opacity:.9}.auth-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:var(--spacing-8, 2rem)}.auth-loading__spinner{width:40px;height:40px;border:3px solid rgba(255,255,255,.2);border-top:3px solid var(--color-white, #ffffff);border-radius:50%;animation:spin 1s linear infinite}.auth-loading__text{margin-top:var(--spacing-4, 1rem);font-size:var(--font-size-sm, .875rem);color:#fffc}@keyframes slideInUp{0%{opacity:0;transform:translateY(30px)}to{opacity:1;transform:translateY(0)}}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.login-page__form-card{animation:none}.login-page__branding-logo-img{transition:none}.auth-loading__spinner{animation:none}}@media(prefers-contrast:high){.login-page__form-card{border:2px solid var(--color-white, #ffffff)}}\n"] }]
1272
1309
  }], ctorParameters: () => [{ type: AuthService }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
1273
1310
 
1274
1311
  class DashboardComponent {
@@ -1510,11 +1547,11 @@ class DashboardComponent {
1510
1547
  document.body.style.overflow = '';
1511
1548
  }
1512
1549
  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 });
1513
- 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"] }] });
1550
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.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 @if (userInfo) {\r\n <div class=\"user-profile mobile-user\">\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 }\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 @if (getConfig('subtitle')) {\r\n <span class=\"subtitle\">{{ getConfig('subtitle') }}</span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n @if (userInfo) {\r\n <div class=\"user-profile desktop-user\">\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 }\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n @if (isUserPanelOpen) {\r\n <div class=\"user-panel\">\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 }\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n @if (isMobileMenuOpen) {\r\n <div class=\"nav-overlay\" (click)=\"closeMobileMenu()\"></div>\r\n }\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 @if (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 }\r\n\r\n <!-- Separator -->\r\n @if (!isLoadingMenu && menuHierarchy.length > 0) {\r\n <div class=\"nav-separator\"></div>\r\n }\r\n\r\n <!-- Dynamic Menu -->\r\n @if (!isLoadingMenu && menuHierarchy.length > 0) {\r\n <div class=\"dynamic-menu\">\r\n @for (rootItem of getRootMenuItems(); track rootItem.code) {\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 @if (hasChildren(rootItem)) {\r\n <span class=\"material-symbols-outlined expand-icon\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n }\r\n </a>\r\n\r\n <!-- Submenu -->\r\n @if (hasChildren(rootItem) && isMenuItemOpen(rootItem.code)) {\r\n <div class=\"submenu\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Loading Indicator -->\r\n @if (isLoadingMenu) {\r\n <div class=\"menu-loading\">\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 }\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 @for (item of items; track item.code) {\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 @if (hasChildren(item)) {\r\n <span class=\"material-symbols-outlined expand-icon\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n }\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n @if (hasChildren(item) && isMenuItemOpen(item.code) && level < 5) {\r\n <div class=\"submenu\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n }\r\n </div>\r\n }\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 @if (getConfig('showVersion')) {\r\n Versi\u00F3n {{ appVersion }}.\r\n }\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: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i3.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }] });
1514
1551
  }
1515
1552
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DashboardComponent, decorators: [{
1516
1553
  type: Component,
1517
- 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"] }]
1554
+ args: [{ selector: 'lib-dashboard', standalone: true, imports: [NgTemplateOutlet, 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 @if (userInfo) {\r\n <div class=\"user-profile mobile-user\">\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 }\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 @if (getConfig('subtitle')) {\r\n <span class=\"subtitle\">{{ getConfig('subtitle') }}</span>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"header-right\">\r\n @if (userInfo) {\r\n <div class=\"user-profile desktop-user\">\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 }\r\n </div>\r\n </div>\r\n </header>\r\n\r\n <!-- User Panel Template -->\r\n <ng-template #userPanelTemplate>\r\n @if (isUserPanelOpen) {\r\n <div class=\"user-panel\">\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 }\r\n </ng-template>\r\n\r\n <!-- Mobile Menu Overlay -->\r\n @if (isMobileMenuOpen) {\r\n <div class=\"nav-overlay\" (click)=\"closeMobileMenu()\"></div>\r\n }\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 @if (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 }\r\n\r\n <!-- Separator -->\r\n @if (!isLoadingMenu && menuHierarchy.length > 0) {\r\n <div class=\"nav-separator\"></div>\r\n }\r\n\r\n <!-- Dynamic Menu -->\r\n @if (!isLoadingMenu && menuHierarchy.length > 0) {\r\n <div class=\"dynamic-menu\">\r\n @for (rootItem of getRootMenuItems(); track rootItem.code) {\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 @if (hasChildren(rootItem)) {\r\n <span class=\"material-symbols-outlined expand-icon\">\r\n {{ isMenuItemOpen(rootItem.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n }\r\n </a>\r\n\r\n <!-- Submenu -->\r\n @if (hasChildren(rootItem) && isMenuItemOpen(rootItem.code)) {\r\n <div class=\"submenu\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: rootItem.children, level: 2 }\"></ng-container>\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Loading Indicator -->\r\n @if (isLoadingMenu) {\r\n <div class=\"menu-loading\">\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 }\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 @for (item of items; track item.code) {\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 @if (hasChildren(item)) {\r\n <span class=\"material-symbols-outlined expand-icon\">\r\n {{ isMenuItemOpen(item.code) ? 'expand_more' : 'chevron_right' }}\r\n </span>\r\n }\r\n </a>\r\n\r\n <!-- Recursive Submenu (max 5 levels) -->\r\n @if (hasChildren(item) && isMenuItemOpen(item.code) && level < 5) {\r\n <div class=\"submenu\">\r\n <ng-container\r\n *ngTemplateOutlet=\"menuTemplate; context: { items: item.children, level: level + 1 }\"></ng-container>\r\n </div>\r\n }\r\n </div>\r\n }\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 @if (getConfig('showVersion')) {\r\n Versi\u00F3n {{ appVersion }}.\r\n }\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"] }]
1518
1555
  }], ctorParameters: () => [{ type: AuthService }, { type: AuthorizationService }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], onDocumentClick: [{
1519
1556
  type: HostListener,
1520
1557
  args: ['document:click', ['$event']]