Saltar a contenido

Autenticación y Autorización

En el Backend implementamos la autenticacion con JWT Token y la autorizacion lo controlamos con modelos que implican desde el password hasta el manejo de roles, permisos y rutas.

Implementacion Auth

Creamos el modelo en src/app/models/auth.ts

export interface LoginI {
  email: string;
  password: string;
}


export interface LoginResponseI {
  token: string;
  user: {
    id: number;
    username: string;
    email: string;
    password: string;
    is_active: "ACTIVE" | "INACTIVE";
    avatar: string;
  };
}


export interface RegisterI {
  username: string;  
  email: string;
  password: string;
}

export interface RegisterResponseI {
  token: string;
  user: {
    id: number;
    username: string;
    email: string;
    password: string;
    is_active: "ACTIVE" | "INACTIVE";
    avatar: string;
  };
}



export interface UserI {
  id: number;
  username: string;
  email: string;
  password: string;
  is_active: "ACTIVE" | "INACTIVE";
  avatar: string;
}

Luego, creamos el servicio en src/app/services/auth.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, tap } from 'rxjs';
import { LoginI, LoginResponseI, RegisterI, RegisterResponseI} from '../models/auth';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private baseUrl = 'http://localhost:4000/api';
  private tokenKey = 'auth_token';

  constructor(private http: HttpClient) {}

  login(credentials: LoginI): Observable<LoginResponseI> {
    return this.http.post<LoginResponseI>(`${this.baseUrl}/login`, credentials)
      .pipe(
        tap(response => {
          if (response.token) {
            this.setToken(response.token);
          }
        })
      );
  }

  register(userData: RegisterI): Observable<RegisterResponseI> {
    return this.http.post<RegisterResponseI>(`${this.baseUrl}/register`, userData)
      .pipe(
        tap(response => {
          if (response.token) {
            this.setToken(response.token);
          }
        })
      );
  }

  logout(): void {
    localStorage.removeItem(this.tokenKey);
  }

  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  setToken(token: string): void {
    localStorage.setItem(this.tokenKey, token);
  }

  isLoggedIn(): boolean {
    return !!this.getToken();
  }
}

Ajustamos el Template Base para que aparezca las opciones de:

Iniciar Sesion y

Cerrar Sesion

src/app/app.html

<main class="min-h-screen flex flex-col border-4 border-red-500">
  <!-- HEADER -->
  <header class="h-16 bg-white border-2 border-blue-500 flex items-center w-full font-bold text-blue-600">
    <app-header class="w-full"></app-header>
  </header>

  <!-- CONTENEDOR CENTRAL (ASIDE + CONTENT) -->
  <div class="flex flex-1">
    <!-- ASIDE -->
    <aside class="w-64 bg-white border-2 border-blue-500 flex items-start justify-start font-bold text-blue-400">
      <app-aside></app-aside>
    </aside>

    <!-- CONTENT -->
    <section class="flex-1 bg-white border-2 border-blue-500 flex items-center justify-center font-bold text-blue-600">

      <router-outlet />
    </section>
  </div>

  <!-- FOOTER (pegado al fondo) -->
  <footer class="h-16 bg-white border-2 border-blue-500 flex items-center justify-center font-bold text-blue-600 mt-auto">
    <app-footer></app-footer>
  </footer>
</main>

src/app/components/layout/header/header.ts

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { OverlayBadge } from 'primeng/overlaybadge';
import { TieredMenu } from 'primeng/tieredmenu';
import { CommonModule } from '@angular/common';
import { AuthService } from '../../../services/auth.service';

@Component({
  selector: 'app-header',
  standalone: true,
  imports: [CommonModule, OverlayBadge, TieredMenu],
  templateUrl: './header.html',
  styleUrl: './header.css'
})
export class Header {
  items: MenuItem[] = [];

  constructor(
    private router: Router,
    private authService: AuthService
  ) {
    this.items = [
      {
        label: 'Configuración',
        icon: 'pi pi-cog',
        command: () => {
          console.log('Configuración clicked');
        }
      },
      {
        label: 'Información',
        icon: 'pi pi-info-circle',
        command: () => {
          console.log('Información clicked');
        }
      },
      {
        separator: true
      },
      {
        label: 'Cerrar sesión',
        icon: 'pi pi-sign-out',
        command: () => {
          this.logout();
        }
      },
      {
        label: 'Iniciar sesión',
        icon: 'pi pi-sign-in',
        command: () => {
          this.goToLogin();
        }
      }
    ];
  }

  private logout(): void {
    this.authService.logout();
    this.router.navigate(['/login']);
  }

  private goToLogin(): void {
    this.router.navigate(['/login']);
  }
}

src/app/components/layout/header/header.html

<!-- Botones derechos alineados totalmente a la derecha -->
<div class="w-full flex justify-end items-center gap-2">

    <button type="button" class="text-orange-500 bg-gray-100 hover:bg-gray-200 rounded shadow hover:shadow-lg flex items-center justify-center w-10 h-10">
        <p-overlaybadge value="2">
            <i class="pi pi-bell text-2xl"></i>
        </p-overlaybadge>
    </button>

    <button type="button"
        class="text-orange-500 bg-gray-100 hover:bg-gray-200 rounded shadow hover:shadow-lg w-10 h-10 flex items-center justify-center"
        (click)="menu.toggle($event)">
        <i class="pi pi-users text-xl"></i>
    </button>
</div>

<!-- Menú fuera del contenedor de botones -->
<p-tieredmenu #menu [model]="items" [popup]="true" />

Confimamos en src/app.config.ts

import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideAnimationsAsync(),
    provideHttpClient(),
        providePrimeNG({
            theme: {
                preset: Aura
            }
        })
  ]
};

Adicionalmente instalar en el terminal

npm install primeicons

y el archivo src/styles.cssinsertar al final la linea

@import "primeicons/primeicons.css";

Ahora cerramos el ciclo con la creacion de los componentes:

ng g c components/auth/login
ng g c components/auth/register

Componente Login

src/app/components/auth/login.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { AuthService } from '../../../services/auth.service';
import { CardModule } from 'primeng/card';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, ButtonModule, InputTextModule, ToastModule, CardModule],
  templateUrl: './login.html',
  styleUrl: './login.css',
  providers: [MessageService, AuthService]
})
export class Login {
  form: FormGroup;
  loading: boolean = false;

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private authService: AuthService,
    private messageService: MessageService
  ) {
    this.form = this.fb.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]]
    });
  }

  submit(): void {
    if (this.form.valid) {
      this.loading = true;
      const credentials = this.form.value;

      this.authService.login(credentials).subscribe({
        next: (response) => {
          this.messageService.add({
            severity: 'success',
            summary: 'Éxito',
            detail: 'Sesión iniciada correctamente'
          });
          setTimeout(() => {
            this.router.navigate(['/clients']);
          }, 1000);
        },
        error: (error) => {
          console.error('Error logging in:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: 'Credenciales incorrectas'
          });
          this.loading = false;
        }
      });
    } else {
      this.markFormGroupTouched();
      this.messageService.add({
        severity: 'warn',
        summary: 'Advertencia',
        detail: 'Por favor complete todos los campos requeridos'
      });
    }
  }

  goToRegister(): void {
    this.router.navigate(['/register']);
  }

  private markFormGroupTouched(): void {
    Object.keys(this.form.controls).forEach(key => {
      this.form.get(key)?.markAsTouched();
    });
  }

  getFieldError(fieldName: string): string {
    const field = this.form.get(fieldName);
    if (field?.errors && field?.touched) {
      if (field.errors['required']) return `${fieldName} es requerido`;
      if (field.errors['email']) return 'Email no válido';
      if (field.errors['minlength']) return `${fieldName} debe tener al menos ${field.errors['minlength'].requiredLength} caracteres`;
    }
    return '';
  }
}

src/app/components/auth/login.html

<div class="min-h-screen flex items-center justify-center">
  <div class="max-w-md w-full">
    <p-card header="Iniciar Sesión" class="shadow-lg">
      <form [formGroup]="form" (ngSubmit)="submit()" class="space-y-6">
        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Email *</label>
          <input 
            pInputText 
            type="email"
            formControlName="email" 
            class="w-full"
            placeholder="ejemplo@correo.com"
            [class.ng-invalid]="form.get('email')?.invalid && form.get('email')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('email')">
            {{ getFieldError('email') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Contraseña *</label>
          <input 
            pInputText 
            type="password" 
            formControlName="password" 
            class="w-full"
            placeholder="Ingrese su contraseña"
            [class.ng-invalid]="form.get('password')?.invalid && form.get('password')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('password')">
            {{ getFieldError('password') }}
          </small>
        </div>

        <div class="flex flex-col space-y-3 mt-6">
          <button
            pButton
            type="submit"
            class="p-button bg-blue-500 hover:bg-blue-600 text-white w-full"
            label="Iniciar Sesión"
            icon="pi pi-sign-in"
            [loading]="loading"
            [disabled]="loading"
          ></button>

          <button
            pButton
            type="button"
            class="p-button p-button-text w-full"
            label="¿No tienes cuenta? Registrarse"
            icon="pi pi-user-plus"
            (click)="goToRegister()"
            [disabled]="loading"
          ></button>
        </div>
      </form>
    </p-card>
  </div>
</div>

<p-toast></p-toast>

Componente Register

src/app/components/auth/register.ts

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators, AbstractControl } from '@angular/forms';
import { Router } from '@angular/router';
import { ButtonModule } from 'primeng/button';
import { InputTextModule } from 'primeng/inputtext';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';
import { AuthService } from '../../../services/auth.service';
import { CardModule } from 'primeng/card';

@Component({
  selector: 'app-register',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, ButtonModule, InputTextModule, ToastModule, CardModule],
  templateUrl: './register.html',
  styleUrl: './register.css',
  providers: [MessageService]
})
export class Register {
  form: FormGroup;
  loading: boolean = false;

  constructor(
    private fb: FormBuilder,
    private router: Router,
    private authService: AuthService,
    private messageService: MessageService
  ) {
    this.form = this.fb.group({
      username: ['', [Validators.required, Validators.minLength(2)]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(6)]],
      confirmPassword: ['', [Validators.required]]
    }, { validators: this.passwordMatchValidator });
  }

  // Validador personalizado para confirmar contraseñas
  passwordMatchValidator(control: AbstractControl) {
    const password = control.get('password');
    const confirmPassword = control.get('confirmPassword');

    if (password && confirmPassword && password.value !== confirmPassword.value) {
      confirmPassword.setErrors({ passwordMismatch: true });
      return { passwordMismatch: true };
    }

    if (confirmPassword?.hasError('passwordMismatch')) {
      confirmPassword.setErrors(null);
    }

    return null;
  }

  submit(): void {
    if (this.form.valid) {
      this.loading = true;
      const { confirmPassword, ...registerData } = this.form.value;

      this.authService.register(registerData).subscribe({
        next: (response) => {
          this.messageService.add({
            severity: 'success',
            summary: 'Éxito',
            detail: 'Usuario registrado correctamente'
          });
          setTimeout(() => {
            this.router.navigate(['/clients']);
          }, 1000);
        },
        error: (error) => {
          console.error('Error registering user:', error);
          this.messageService.add({
            severity: 'error',
            summary: 'Error',
            detail: error.error?.message || 'Error al registrar usuario'
          });
          this.loading = false;
        }
      });
    } else {
      this.markFormGroupTouched();
      this.messageService.add({
        severity: 'warn',
        summary: 'Advertencia',
        detail: 'Por favor complete todos los campos requeridos'
      });
    }
  }

  goToLogin(): void {
    this.router.navigate(['/login']);
  }

  private markFormGroupTouched(): void {
    Object.keys(this.form.controls).forEach(key => {
      this.form.get(key)?.markAsTouched();
    });
  }

  getFieldError(fieldName: string): string {
    const field = this.form.get(fieldName);
    if (field?.errors && field?.touched) {
      if (field.errors['required']) return `${fieldName} es requerido`;
      if (field.errors['email']) return 'Email no válido';
      if (field.errors['minlength']) return `${fieldName} debe tener al menos ${field.errors['minlength'].requiredLength} caracteres`;
      if (field.errors['passwordMismatch']) return 'Las contraseñas no coinciden';
    }
    return '';
  }
}

src/app/components/auth/register.html

<div class="min-h-screen flex items-center justify-center">
  <div class="max-w-md w-full">
    <p-card header="Registrar Usuario" class="shadow-lg">
      <form [formGroup]="form" (ngSubmit)="submit()" class="space-y-6">
        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Username *</label>
          <input 
            pInputText 
            type="text"
            formControlName="username" 
            class="w-full"
            placeholder="Ingrese su username"
            [class.ng-invalid]="form.get('username')?.invalid && form.get('username')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('username')">
            {{ getFieldError('username') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Email *</label>
          <input 
            pInputText 
            type="email"
            formControlName="email" 
            class="w-full"
            placeholder="ejemplo@correo.com"
            [class.ng-invalid]="form.get('email')?.invalid && form.get('email')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('email')">
            {{ getFieldError('email') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Contraseña *</label>
          <input 
            pInputText 
            type="password" 
            formControlName="password" 
            class="w-full"
            placeholder="Ingrese su contraseña"
            [class.ng-invalid]="form.get('password')?.invalid && form.get('password')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('password')">
            {{ getFieldError('password') }}
          </small>
        </div>

        <div class="flex flex-col">
          <label class="block mb-2 font-semibold text-gray-700">Confirmar Contraseña *</label>
          <input 
            pInputText 
            type="password" 
            formControlName="confirmPassword" 
            class="w-full"
            placeholder="Confirme su contraseña"
            [class.ng-invalid]="form.get('confirmPassword')?.invalid && form.get('confirmPassword')?.touched"
          />
          <small class="text-red-500 mt-1" *ngIf="getFieldError('confirmPassword')">
            {{ getFieldError('confirmPassword') }}
          </small>
        </div>

        <div class="flex flex-col space-y-3 mt-6">
          <button
            pButton
            type="submit"
            class="p-button bg-blue-500 hover:bg-blue-600 text-white w-full"
            label="Registrarse"
            icon="pi pi-user-plus"
            [loading]="loading"
            [disabled]="loading"
          ></button>

          <button
            pButton
            type="button"
            class="p-button p-button-text w-full"
            label="¿Ya tienes cuenta? Iniciar Sesión"
            icon="pi pi-sign-in"
            (click)="goToLogin()"
            [disabled]="loading"
          ></button>
        </div>
      </form>
    </p-card>
  </div>
</div>

<p-toast></p-toast>

Ajustamos las rutas

src/app/app.routes.ts

import { Routes } from '@angular/router';
import { Login } from './components/auth/login/login';
import { Register } from './components/auth/register/register';

// Client components with aliases
import { Getall as ClientGetall } from './components/client/getall/getall';
import { Create as ClientCreate } from './components/client/create/create';
import { Update as ClientUpdate } from './components/client/update/update';
import { Delete as ClientDelete } from './components/client/delete/delete';

// Product components with aliases
import { Getall as ProductGetall } from './components/product/getall/getall';
import { Create as ProductCreate } from './components/product/create/create';
import { Update as ProductUpdate } from './components/product/update/update';
import { Delete as ProductDelete } from './components/product/delete/delete';

// Sale components with aliases
import { Getall as SaleGetall } from './components/sale/getall/getall';
import { Create as SaleCreate } from './components/sale/create/create';
import { Update as SaleSaleUpdate } from './components/sale/update/update';
import { Delete as SaleDelete } from './components/sale/delete/delete';

export const routes: Routes = [
    { 
        path: '', 
        redirectTo: '/', 
        pathMatch: 'full' 
    },
    {
        path: "clients",
        component: ClientGetall
    },
    {
        path: "clients/new",
        component: ClientCreate
    },
    {
        path: "clients/edit/:id",
        component: ClientUpdate
    },
    {
        path: "clients/delete/:id",
        component: ClientDelete
    },
    {
        path: "products",
        component: ProductGetall
    },
    {
        path: "products/new",
        component: ProductCreate
    },
    {
        path: "products/edit/:id",
        component: ProductUpdate
    },
    {
        path: "products/delete/:id",
        component: ProductDelete
    },
    {
        path: "sales",
        component: SaleGetall
    },
    {
        path: "sales/new",
        component: SaleCreate
    },
    {
        path: "sales/edit/:id",
        component: SaleSaleUpdate
    },
    {
        path: "sales/delete/:id",
        component: SaleDelete
    },
    {
        path: "login",
        component: Login
    },
    {
        path: "register",
        component: Register
    },
    {
        path: "**",
        redirectTo: "login",
        pathMatch: "full"
    }
];

Hasta aqui ya funciona Login, Logout y Register.\ Pero hay que ajustar que cuando hasta que no haga login, no mueste ningun componete ni acceda a ninguna ruta, claro a excepcion de Login y register. Y adicionalmente que haga login en el menu solo aparezca Cerrar Sesion y cuando no haya realizado login solo aparezca Iniciar Sesion.

a. Crear un Guard para proteger las rutas:

src/app/guards/authguard.ts

import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {

  constructor(
    private authService: AuthService,
    private router: Router
  ) {}

  canActivate(): boolean {
    if (this.authService.isLoggedIn()) {
      return true;
    } else {
      this.router.navigate(['/login']);
      return false;
    }
  }
}

b. Actualizar las rutas con proteccion

import { Routes } from '@angular/router';
import { Login } from './components/auth/login/login';
import { Register } from './components/auth/register/register';
import { AuthGuard } from './guards/authguard';

// Client components with aliases
import { Getall as ClientGetall } from './components/client/getall/getall';
import { Create as ClientCreate } from './components/client/create/create';
import { Update as ClientUpdate } from './components/client/update/update';
import { Delete as ClientDelete } from './components/client/delete/delete';

// Product components with aliases
import { Getall as ProductGetall } from './components/product/getall/getall';
import { Create as ProductCreate } from './components/product/create/create';
import { Update as ProductUpdate } from './components/product/update/update';
import { Delete as ProductDelete } from './components/product/delete/delete';

// Sale components with aliases
import { Getall as SaleGetall } from './components/sale/getall/getall';
import { Create as SaleCreate } from './components/sale/create/create';
import { Update as SaleSaleUpdate } from './components/sale/update/update';
import { Delete as SaleDelete } from './components/sale/delete/delete';

export const routes: Routes = [
    { 
        path: '', 
        redirectTo: '/login', 
        pathMatch: 'full' 
    },
    {
        path: "login",
        component: Login
    },
    {
        path: "register",
        component: Register
    },
    {
        path: "clients",
        component: ClientGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/new",
        component: ClientCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/edit/:id",
        component: ClientUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "clients/delete/:id",
        component: ClientDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "products",
        component: ProductGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "products/new",
        component: ProductCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "products/edit/:id",
        component: ProductUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "products/delete/:id",
        component: ProductDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "sales",
        component: SaleGetall,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/new",
        component: SaleCreate,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/edit/:id",
        component: SaleSaleUpdate,
        canActivate: [AuthGuard]
    },
    {
        path: "sales/delete/:id",
        component: SaleDelete,
        canActivate: [AuthGuard]
    },
    {
        path: "**",
        redirectTo: "/login",
        pathMatch: "full"
    }
];

c. Actualizar el header para mostrar menu dinamico de Iniciar y Cerrar Sesion

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { MenuItem } from 'primeng/api';
import { OverlayBadge } from 'primeng/overlaybadge';
import { TieredMenu } from 'primeng/tieredmenu';
import { CommonModule } from '@angular/common';
import { AuthService } from '../../../services/auth.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-header',
  standalone: true,
  imports: [CommonModule, OverlayBadge, TieredMenu],
  templateUrl: './header.html',
  styleUrl: './header.css'
})
export class Header implements OnInit, OnDestroy {
  items: MenuItem[] = [];
  isLoggedIn = false;
  private authSubscription?: Subscription;

  constructor(
    private router: Router,
    private authService: AuthService
  ) {}

  ngOnInit() {
    this.updateMenuItems();
    // Escuchar cambios en el estado de autenticación
    this.authSubscription = this.authService.authState$.subscribe(() => {
      this.updateMenuItems();
    });
  }

  ngOnDestroy() {
    if (this.authSubscription) {
      this.authSubscription.unsubscribe();
    }
  }

  private updateMenuItems(): void {
    this.isLoggedIn = this.authService.isLoggedIn();

    if (this.isLoggedIn) {
      this.items = [
        {
          label: 'Configuración',
          icon: 'pi pi-cog',
          command: () => {
            console.log('Configuración clicked');
          }
        },
        {
          label: 'Información',
          icon: 'pi pi-info-circle',
          command: () => {
            console.log('Información clicked');
          }
        },
        {
          separator: true
        },
        {
          label: 'Cerrar sesión',
          icon: 'pi pi-sign-out',
          command: () => {
            this.logout();
          }
        }
      ];
    } else {
      this.items = [
        {
          label: 'Iniciar sesión',
          icon: 'pi pi-sign-in',
          command: () => {
            this.goToLogin();
          }
        },
        {
          label: 'Registrarse',
          icon: 'pi pi-user-plus',
          command: () => {
            this.goToRegister();
          }
        }
      ];
    }
  }

  private logout(): void {
    this.authService.logout();
    this.router.navigate(['/login']);
  }

  private goToLogin(): void {
    this.router.navigate(['/login']);
  }

  private goToRegister(): void {
    this.router.navigate(['/register']);
  }
}

d. Actualizar el AuthService para emitir los cambios de estado

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, tap, BehaviorSubject } from 'rxjs';
import { LoginI, LoginResponseI, RegisterI, RegisterResponseI} from '../models/auth';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private baseUrl = 'http://localhost:4000/api';
  private tokenKey = 'auth_token';
  private authStateSubject = new BehaviorSubject<boolean>(this.hasValidToken());
  public authState$ = this.authStateSubject.asObservable();

  constructor(private http: HttpClient) {}

  login(credentials: LoginI): Observable<LoginResponseI> {
    return this.http.post<LoginResponseI>(`${this.baseUrl}/login`, credentials)
      .pipe(
        tap(response => {
          if (response.token) {
            this.setToken(response.token);
            this.authStateSubject.next(true);
          }
        })
      );
  }

  register(userData: RegisterI): Observable<RegisterResponseI> {
    return this.http.post<RegisterResponseI>(`${this.baseUrl}/register`, userData)
      .pipe(
        tap(response => {
          if (response.token) {
            this.setToken(response.token);
            this.authStateSubject.next(true);
          }
        })
      );
  }

  logout(): void {
    localStorage.removeItem(this.tokenKey);
    this.authStateSubject.next(false);
  }

  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  setToken(token: string): void {
    localStorage.setItem(this.tokenKey, token);
  }

  isLoggedIn(): boolean {
    return this.hasValidToken();
  }

  private hasValidToken(): boolean {
    const token = this.getToken();
    if (!token) return false;

    // Aquí puedes agregar validación adicional del token si es necesario
    // Por ejemplo, verificar si el token no ha expirado
    return true;
  }
}

e. Actualizar el Header.html para reflejar los estados de Inicio y Cerrar Sesion

<!-- Botones derechos alineados totalmente a la derecha -->
<div class="w-full flex justify-end items-center gap-2">

    <!-- Solo mostrar notificaciones si está logueado -->
    <button 
        *ngIf="isLoggedIn"
        type="button" 
        class="text-orange-500 bg-gray-100 hover:bg-gray-200 rounded shadow hover:shadow-lg flex items-center justify-center w-10 h-10">
        <p-overlaybadge value="2">
            <i class="pi pi-bell text-2xl"></i>
        </p-overlaybadge>
    </button>

    <button type="button"
        class="text-orange-500 bg-gray-100 hover:bg-gray-200 rounded shadow hover:shadow-lg w-10 h-10 flex items-center justify-center"
        (click)="menu.toggle($event)">
        <i class="pi pi-users text-xl"></i>
    </button>
</div>

<!-- Menú fuera del contenedor de botones -->
<p-tieredmenu #menu [model]="items" [popup]="true" />

f. actualizar el app.html para mostrar layout solo si está logueado

import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { CommonModule } from '@angular/common';
import { Aside } from './components/layout/aside/aside';
import { Header } from './components/layout/header/header';
import { Footer } from './components/layout/footer/footer';
import { AuthService } from './services/auth.service';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet, Header, Footer, Aside, CommonModule],
  templateUrl: './app.html',
  styleUrl: './app.css'
})
export class App {
  protected readonly title = signal('appfront');

  constructor(public authService: AuthService) {}
}