Skip to content

TheJSArchitects/InventorySoftDevsenior

Repository files navigation

🚀 Guía Completa: Sistema de Autenticación desde Cero

Autor: GitHub Copilot
Fecha: 27 de octubre de 2025
Proyecto: devseniorInventory - Sistema de Inventarios

Esta guía documenta paso a paso cómo implementar un sistema de autenticación completo en Angular con componentes standalone, formularios reactivos y rutas protegidas.

Screenshot 2025-10-27 001159

📋 Tabla de Contenidos

  1. Vista Previa del Login
  2. Arquitectura del Proyecto
  3. Estructura del Proyecto
  4. Configuración Inicial
  5. Componentes de Autenticación
  6. Rutas y Navegación
  7. Estilos y Diseño
  8. Flujo de Autenticación
  9. Comandos y Ejecución
  10. Próximos Pasos

👀 Vista Previa del Login

Así es como se verá tu pantalla de login una vez implementado:

Login Preview

Nota: Para agregar esta imagen al proyecto, toma una captura de pantalla de tu login en http://localhost:4200/login y guárdala como docs/login-preview.png

Características Visuales

Diseño Moderno:

  • Fondo oscuro con patrón topográfico sutil
  • Logo de calabaza (pumpkin) centrado
  • Formulario con estilo minimalista

🎨 Paleta de Colores:

  • Primario: #ff6a00 (Naranja)
  • Oscuro: #e65a00 (Naranja más oscuro)
  • Fondo: #111111 / #151515 (Negro/Gris oscuro)
  • Texto: #ffffff / #dfdfdf (Blanco/Gris claro)

🎯 Elementos Clave:

  1. Logo: Calabaza decorativa (120×120px)
  2. Título: "Software de Inventarios"
  3. Subtítulo: "Ingresa tus credenciales para acceder"
  4. Campos:
    • Username (con placeholder "LuneskaDev")
    • Password (con asteriscos enmascarados)
  5. Botón: "Ingresar" (color naranja, deshabilitado si el form es inválido)
  6. Link: "¿No tienes una cuenta? Regístrate aquí" (en naranja)

📱 Responsive:

  • Se adapta a pantallas móviles (max-width: 420px)
  • El formulario mantiene su legibilidad en todos los tamaños

🏗️ Arquitectura del Proyecto

Estructura Recomendada para Angular 20

Esta implementación sigue las mejores prácticas de Angular moderno:

Arquitectura Angular 20

Nota: Guarda la imagen de referencia de la arquitectura en docs/angular-architecture.png para visualizar la estructura completa del proyecto.

📦 ANGULAR 20 PROJECT STRUCTURE
├── Clean, Scalable & Future-Ready
│
├── 📁 my-angular20-app/
│   ├── 📁 src/
│   │   ├── 📁 app/
│   │   │   ├── 📁 core/                    ← Singleton services, interceptors, guards
│   │   │   │   ├── 📁 shared/              ← Reusable components, directives, pipes
│   │   │   │   ├── 📁 features/            ← Feature modules
│   │   │   │   │   ├── 📁 auth/            ← Módulo de autenticación
│   │   │   │   │   │   ├── 📁 login/
│   │   │   │   │   │   │   ├── login-auth.component.ts
│   │   │   │   │   │   │   ├── login-auth.component.html
│   │   │   │   │   │   │   ├── login-auth.component.css
│   │   │   │   │   │   │   └── 📁 assets/
│   │   │   │   │   │   │       └── 📁 images/
│   │   │   │   │   │   ├── 📁 register/
│   │   │   │   │   │   │   ├── register-auth.component.ts
│   │   │   │   │   │   │   ├── register-auth.component.html
│   │   │   │   │   │   │   └── register-auth.component.css
│   │   │   │   │   │   └── 📁 services/   ← (Futuro) AuthService
│   │   │   │   │   └── 📁 dashboard/       ← (Futuro) Área autenticada
│   │   │   │   └── 📁 layouts/             ← Feature modules (dashboard, auth, etc.)
│   │   │   │       ├── 📁 auth-layout/
│   │   │   │       │   ├── auth-layout.component.ts
│   │   │   │       │   ├── auth-layout.component.html
│   │   │   │       │   └── auth-layout.component.css
│   │   │   │       └── 📁 main-layout/
│   │   │   │           ├── main-layout.component.ts
│   │   │   │           ├── main-layout.component.html
│   │   │   │           └── main-layout.component.css
│   │   │   ├── 📁 state/                   ← State management (opcional)
│   │   │   ├── 📄 app.config.ts            ← Angular 20 application config
│   │   │   ├── 📄 app.routes.ts            ← Route definitions
│   │   │   └── 📄 app.component.ts
│   │   ├── 📁 assets/                      ← Images, fonts, JSON, icons
│   │   │   └── 📁 images/
│   │   │       └── 3dicons-pumpkin-dynamic-color.png
│   │   ├── 📁 environments/                ← Environment configs (dev, prod, staging)
│   │   ├── 📄 main.ts                      ← Entry point with bootstrapApplication()
│   │   └── 📄 index.html
│   ├── 📄 angular.json
│   ├── 📄 package.json
│   └── 📄 tsconfig.json

Principios de Arquitectura Aplicados

1. Core Module Pattern

core/
  ├── features/     → Funcionalidades específicas (auth, dashboard, etc.)
  ├── layouts/      → Layouts reutilizables (shell components)
  ├── shared/       → Componentes, pipes, directives compartidos
  └── state/        → Gestión de estado global (opcional)

Beneficios:

  • Separación clara de responsabilidades
  • Facilita el escalado del proyecto
  • Código más mantenible

2. Feature-Based Organization

features/auth/
  ├── login/        → Componente de login
  ├── register/     → Componente de registro
  └── services/     → AuthService (próximo paso)

Beneficios:

  • Todo lo relacionado con autenticación en un solo lugar
  • Facilita el lazy loading
  • Mejor colaboración en equipos

3. Standalone Components (Angular 14+)

@Component({
  standalone: true,              // ← No necesita NgModule
  imports: [ReactiveFormsModule] // ← Imports directos
})

Beneficios:

  • Menos boilerplate
  • Mejor tree-shaking (bundle más pequeño)
  • Componentes más portables

4. Layouts Pattern

AuthLayoutComponent
  └── <router-outlet>  ← Renderiza Login o Register

MainLayoutComponent
  └── <router-outlet>  ← Renderiza Dashboard, Home, etc.

Beneficios:

  • Reutilización de estructura visual
  • Headers/footers/sidebars compartidos
  • Fácil cambio de diseño por sección

Flujo de Navegación

Usuario visita app
      ↓
   app.routes.ts
      ↓
┌─────────────────┐
│ AuthLayoutComponent │  (Path: '')
│  (Wrapper oscuro)  │
└────────┬──────────┘
         │
    <router-outlet>
         │
    ┌────┴────┐
    │         │
┌───▼────┐ ┌─▼─────────┐
│ /login │ │ /register │
│ (Login)│ │ (Register)│
└───┬────┘ └─┬─────────┘
    │        │
    └────┬───┘
         │ Autenticación exitosa
         ▼
┌─────────────────┐
│ MainLayoutComponent │  (Path: 'home')
│  (Área privada)    │
└─────────────────┘

Comparación: Antes vs Ahora

Aspecto ❌ Antes (Módulos) ✅ Ahora (Standalone)
Configuración NgModule + imports Directo en component
Boilerplate Alto Mínimo
Bundle size Más grande Optimizado
Lazy loading Complejo Simplificado
Portabilidad Baja Alta

📁 Estructura del Proyecto

devseniorInventory/
├── src/
│   ├── app/
│   │   ├── core/
│   │   │   ├── features/
│   │   │   │   └── auth/
│   │   │   │       ├── login/
│   │   │   │       │   ├── login-auth.component.ts
│   │   │   │       │   ├── login-auth.component.html
│   │   │   │       │   ├── login-auth.component.css
│   │   │   │       │   └── assets/
│   │   │   │       │       └── images/
│   │   │   │       │           └── 3dicons-pumpkin-dynamic-color.png
│   │   │   │       └── register/
│   │   │   │           ├── register-auth.component.ts
│   │   │   │           ├── register-auth.component.html
│   │   │   │           └── register-auth.component.css
│   │   │   └── layouts/
│   │   │       ├── auth-layout/
│   │   │       │   ├── auth-layout.component.ts
│   │   │       │   ├── auth-layout.component.html
│   │   │       │   └── auth-layout.component.css
│   │   │       └── main-layout/
│   │   │           ├── main-layout.component.ts
│   │   │           ├── main-layout.component.html
│   │   │           └── main-layout.component.css
│   │   ├── app.routes.ts
│   │   ├── app.config.ts
│   │   ├── app.ts
│   │   ├── app.html
│   │   └── app.css
│   ├── assets/
│   │   └── images/
│   │       └── 3dicons-pumpkin-dynamic-color.png
│   ├── index.html
│   ├── main.ts
│   └── styles.css
├── angular.json
├── package.json
├── tsconfig.json
└── README.md

⚙️ Configuración Inicial

1. Configurar angular.json para Assets

Archivo: angular.json

¿Por qué?: Para que Angular copie las imágenes y archivos estáticos al build output.

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "devseniorInventory": {
      "projectType": "application",
      "schematics": {},
      "root": "",
      "sourceRoot": "src",
      "prefix": "app",
      "architect": {
        "build": {
          "builder": "@angular/build:application",
          "options": {
            "browser": "src/main.ts",
            "tsConfig": "tsconfig.app.json",
            "assets": [
              "src/favicon.ico",
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              },
              {
                "glob": "**/*",
                "input": "src/app/core/features/auth/login/assets",
                "output": "assets"
              },
              {
                "glob": "**/*",
                "input": "public",
                "output": ""
              }
            ],
            "styles": ["src/styles.css"]
          },
          "configurations": {
            "production": {
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "500kB",
                  "maximumError": "1MB"
                },
                {
                  "type": "anyComponentStyle",
                  "maximumWarning": "4kB",
                  "maximumError": "8kB"
                }
              ],
              "outputHashing": "all"
            },
            "development": {
              "optimization": false,
              "extractLicenses": false,
              "sourceMap": true
            }
          },
          "defaultConfiguration": "production"
        },
        "serve": {
          "builder": "@angular/build:dev-server",
          "configurations": {
            "production": {
              "buildTarget": "devseniorInventory:build:production"
            },
            "development": {
              "buildTarget": "devseniorInventory:build:development"
            }
          },
          "defaultConfiguration": "development"
        },
        "test": {
          "builder": "@angular/build:karma",
          "options": {
            "tsConfig": "tsconfig.spec.json",
            "assets": [
              "src/favicon.ico",
              {
                "glob": "**/*",
                "input": "src/assets",
                "output": "assets"
              },
              {
                "glob": "**/*",
                "input": "src/app/core/features/auth/login/assets",
                "output": "assets"
              },
              {
                "glob": "**/*",
                "input": "public",
                "output": ""
              }
            ],
            "styles": ["src/styles.css"]
          }
        }
      }
    }
  }
}

Puntos clave:

  • src/assets → se copia a dist/assets
  • src/app/core/features/auth/login/assets → también se copia a dist/assets
  • Esto permite usar rutas como assets/images/logo.png en las plantillas

2. Configurar Rutas (app.routes.ts)

Archivo: src/app/app.routes.ts

¿Por qué?: Define las rutas de la aplicación y la navegación entre componentes.

import { Routes } from '@angular/router';
import { AuthLayoutComponent } from './core/layouts/auth-layout/auth-layout.component';
import { Login } from './core/features/auth/login/login-auth.component';
import { Register } from './core/features/auth/register/register-auth.component';
import { MainLayoutComponent } from './core/layouts/main-layout/main-layout.component';

export const routes: Routes = [
  {
    path: '',
    component: AuthLayoutComponent,
    children: [
      {
        path: '',
        redirectTo: 'login',
        pathMatch: 'full',
      },
      {
        path: 'login',
        component: Login,
      },
      {
        path: 'register',
        component: Register,
      },
    ],
  },
  {
    path: '',
    component: MainLayoutComponent,
    children: [{ path: 'home', component: MainLayoutComponent }],
  },
];

Explicación:

  • Ruta raíz ('') usa AuthLayoutComponent como layout
  • Redirige automáticamente a /login
  • Rutas hijas: /login y /register
  • /home usa MainLayoutComponent (para área autenticada)

3. Configurar Aplicación (app.config.ts)

Archivo: src/app/app.config.ts

¿Por qué?: Configura los providers de Angular (router, formularios, etc.)

import {
  ApplicationConfig,
  provideBrowserGlobalErrorListeners,
  provideZonelessChangeDetection,
} from '@angular/core';
import { provideRouter } from '@angular/router';
import { importProvidersFrom } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';

import { routes } from './app.routes';
import { provideAnimations } from '@angular/platform-browser/animations';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZonelessChangeDetection(),
    provideRouter(routes),
    // Habilita animaciones Angular
    importProvidersFrom(ReactiveFormsModule), // Permite usar formularios reactivos
  ],
};

Puntos clave:

  • provideRouter(routes): habilita el sistema de rutas
  • ReactiveFormsModule: necesario para formularios reactivos
  • provideZonelessChangeDetection(): mejora el rendimiento (Angular 18+)

🎨 Componentes de Autenticación

1. Layout de Autenticación

Archivo: src/app/core/layouts/auth-layout/auth-layout.component.ts

import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-layout-auth',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './auth-layout.component.html',
  styleUrls: ['./auth-layout.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AuthLayoutComponent {}

Archivo: src/app/core/layouts/auth-layout/auth-layout.component.html

<div class="auth-layout">
  <div class="auth-container">
    <p>Welcome to the authentication page</p>
    <router-outlet></router-outlet>
  </div>
</div>

Archivo: src/app/core/layouts/auth-layout/auth-layout.component.css

.auth-layout {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
}

.auth-container {
  width: 100%;
  max-width: 450px;
  padding: 1rem;
}

.auth-container p {
  text-align: center;
  color: #dfdfdf;
  margin-bottom: 1rem;
}

Explicación:

  • RouterOutlet: marca donde se renderizan los componentes hijos (login/register)
  • Diseño centrado verticalmente y horizontalmente
  • Fondo degradado oscuro

2. Componente de Login

Archivo: src/app/core/features/auth/login/login-auth.component.ts

import { Component } from '@angular/core';
import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { RouterLink, Router } from '@angular/router';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './login-auth.component.html',
  styleUrls: ['./login-auth.component.css'],
})
export class Login {
  loginForm: FormGroup;

  constructor(private fb: FormBuilder, private router: Router) {
    this.loginForm = this.fb.group({
      username: ['', [Validators.required]],
      password: ['', [Validators.required]],
    });
  }

  onSubmit() {
    if (this.loginForm.valid) {
      const { username, password } = this.loginForm.value;
      const userData = localStorage.getItem('user');

      if (userData) {
        const user = JSON.parse(userData);
        // Allow login by username OR by email (user may have registered with email)
        const input = (username || '').toString().trim();
        const matchesUser =
          (user.username && user.username === input) ||
          (user.email && user.email.toLowerCase() === input.toLowerCase());

        if (matchesUser && user.password === password) {
          alert('Inicio de sesión exitoso 🎉');
          this.router.navigate(['/home']);
        } else {
          alert('Credenciales incorrectas');
        }
      } else {
        alert('No existe ningún usuario registrado.');
      }
    }
  }

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

Explicación del código:

  1. Imports necesarios:

    • ReactiveFormsModule: para usar formularios reactivos
    • FormGroup, FormBuilder, Validators: construcción y validación de formularios
    • Router: navegación entre rutas
  2. FormGroup:

    • Define los campos del formulario: username y password
    • Ambos son requeridos (Validators.required)
  3. Lógica de login:

    • Lee el usuario de localStorage (guardado en el registro)
    • Compara el input con user.username o user.email (case-insensitive para email)
    • Valida la contraseña
    • Redirige a /home si es exitoso

Archivo: src/app/core/features/auth/login/login-auth.component.html

<div class="login-header">
  <img
    src="assets/images/3dicons-pumpkin-dynamic-color.png"
    alt="Pumpkin logo"
    width="120"
    height="120"
    loading="lazy"
  />
  <h1>Software de Inventarios</h1>
  <p>Ingresa tus credenciales para acceder</p>
</div>
<section class="login-auth-form">
  <form class="login-auth" [formGroup]="loginForm" (ngSubmit)="onSubmit()">
    <label for="username">Username:</label>
    <input type="text" id="username" formControlName="username" placeholder="LuneskaDev" />

    <label for="password">Password:</label>
    <input type="password" id="password" formControlName="password" placeholder="********" />

    <button type="submit" [disabled]="loginForm.invalid">Ingresar</button>
  </form>
  <p>¿No tienes una cuenta? <a (click)="goToRegister()">Regístrate aquí</a></p>
</section>

Puntos clave del HTML:

  • [formGroup]="loginForm": vincula el formulario con el FormGroup del componente
  • formControlName="username": vincula el input con el control del FormGroup
  • (ngSubmit)="onSubmit()": ejecuta la función al enviar el formulario
  • [disabled]="loginForm.invalid": deshabilita el botón si el formulario es inválido
  • (click)="goToRegister()": navega al registro al hacer clic

Archivo: src/app/core/features/auth/login/login-auth.component.css

/* ========================== */
/* login-auth.component.css     */
/* Estructura: base, layout, formulario, encabezado, utilidades */
/* Archivo de estilos para el componente de login. Comentarios en español. */
/* ========================== */

/* --------------------------
   Base (ámbito del componente)
   Usamos :host para que las fuentes y variables afecten sólo a este componente
   y evitar que los estilos se propaguen fuera de su alcance.
   -------------------------- */
:host {
  display: block;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Open Sans', 'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Brand color palette (oranges) - tweak these to tune the look */
  --brand: #ff6a00; /* primary orange */
  --brand-600: #e65a00; /* darker */
  --brand-700: #cc4a00; /* darkest */
  --bg-dark: #111111;
  --panel-bg: #151515;
  --muted: #dfdfdf;
}

:host *,
:host *::before,
:host *::after {
  box-sizing: border-box;
}

/* --------------------------
   Contenedor principal (layout)
   Centra el formulario y controla la anchura máxima. Define el fondo
   del panel, bordes y sombras para la tarjeta de autenticación.
   -------------------------- */
.login-auth-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  max-width: 360px; /* allows better display on larger screens */
  border: 1px solid #1e1e1e;
  padding: 1rem;
  border-radius: 8px;
  margin: 0.5rem auto; /* horizontally center */
  background: var(--panel-bg);
  box-shadow: 0 1px 4px rgba(16, 24, 40, 0.04);
}

.login-auth-form p {
  font-weight: 500;
  font-size: 0.9rem;
  color: #ffffff;
  text-align: center;
}

.login-auth-form p a {
  color: var(--brand);
  text-decoration: none;
}

.login-auth-form p a:hover {
  text-decoration: underline;
}

/* --------------------------
   Controles del formulario (inputs, selects, textarea)
   Ocupan el 100% del ancho y tienen estados de foco accesibles
   (borde + halo naranja para visibilidad). */
.login-auth-form input[type='text'],
.login-auth-form input[type='password'],
.login-auth-form input,
.login-auth-form textarea,
.login-auth-form select {
  padding: 0.8rem;
  margin-top: 5px;
  font-size: 1rem;
  width: 100%;
  border: 1px solid #303030;
  border-radius: 4px;
  background: #242424;
  transition: border-color 120ms ease-in-out, box-shadow 120ms ease-in-out;
}

.login-auth-form input:focus,
.login-auth-form textarea:focus,
.login-auth-form select:focus {
  outline: none;
  border-color: var(--brand-700);
  /* using rgb of --brand (255,106,0) for subtle glow */
  box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.12);
}

/* --------------------------
   Botón principal: estilos, hover y foco accesible
   Usa la paleta de naranja definida en las variables CSS.
   -------------------------- */
.login-auth-form button {
  padding: 0.6rem 0.75rem;
  font-size: 1rem;
  background-color: var(--brand);
  color: #ffffff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 120ms ease, transform 60ms ease;
  margin: 0 auto;
  display: block;
}

.login-auth-form button:hover {
  background-color: var(--brand-600);
}

.login-auth-form button:active {
  transform: translateY(1px);
}

.login-auth-form button:focus {
  outline: 3px solid rgba(255, 106, 0, 0.18);
  outline-offset: 2px;
}

/* --------------------------
   Encabezado (logo y títulos)
   Contiene el logo de la aplicación y los textos centrados.
   -------------------------- */
.login-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 0.75rem;
  text-align: center;
}

.login-header img {
  width: 100px;
  height: 100px;
  margin: 1.25rem 0 0.5rem 0;
  object-fit: contain;
  display: block;
}

.login-header h1 {
  font-size: 1.5rem;
  margin: 0.125rem 0 0 0;
  color: #fff;
}

.login-header p {
  margin: 0.4rem 0 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.login-auth label {
  font-weight: 600;
  margin-bottom: 0.25rem;
  display: block;
  color: #fff;
}

/* --------------------------
   Utilidades y ajustes responsivos
   Reglas para pantallas pequeñas y pequeños helpers de estilo.
   -------------------------- */
@media (max-width: 420px) {
  .login-auth-form {
    padding: 0.75rem;
    margin: 1rem;
  }

  .login-header img {
    width: 80px;
    height: 80px;
    margin-top: 1rem;
  }
}

/* Helper: estilos para enlaces dentro del formulario (coherencia de color) */
.login-auth-form a {
  color: var(--brand);
  text-decoration: none;
}

.login-auth-form a:hover {
  text-decoration: underline;
}

/* Fin del archivo */

Explicación de los estilos:

  • Variables CSS (--brand, --brand-600, etc.): paleta de colores reutilizable
  • Layout flexbox: centra el formulario
  • Estados de foco: mejora accesibilidad para usuarios de teclado
  • Responsive: ajustes para pantallas pequeñas
  • Tema oscuro: fondo oscuro con contraste para mejor lectura

3. Componente de Registro

Archivo: src/app/core/features/auth/register/register-auth.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-register',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule],
  templateUrl: './register-auth.component.html',
  styleUrls: ['./register-auth.component.css'],
})
export class Register {
  registerForm: FormGroup;

  constructor(private fb: FormBuilder, private router: Router) {
    this.registerForm = this.fb.group({
      fullName: ['', [Validators.required]],
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required]],
      confirmPassword: ['', [Validators.required]],
    });
  }

  onSubmit() {
    if (this.registerForm.valid) {
      const { fullName, email, password, confirmPassword } = this.registerForm.value;

      // Validar que las contraseñas coincidan
      if (password !== confirmPassword) {
        alert('Las contraseñas no coinciden');
        return;
      }

      // Guardar solo los campos necesarios (sin confirmPassword)
      const userToStore = {
        fullName,
        email,
        password,
      };

      localStorage.setItem('user', JSON.stringify(userToStore));
      alert('Registro exitoso 🎉');
      this.router.navigate(['/login']);
    }
  }

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

Explicación del código:

  1. Campos del formulario:

    • fullName: nombre completo (requerido)
    • email: correo electrónico (requerido + validación de email)
    • password: contraseña (requerido)
    • confirmPassword: confirmación de contraseña (requerido)
  2. Validación de contraseñas:

    • Compara password con confirmPassword
    • Si no coinciden, muestra alerta y detiene el proceso
  3. Almacenamiento:

    • Crea objeto userToStore solo con los campos necesarios
    • NO guarda confirmPassword (no es necesario almacenarlo)
    • Guarda en localStorage con la clave 'user'
  4. Navegación:

    • Redirige a /login después del registro exitoso

Archivo: src/app/core/features/auth/register/register-auth.component.html

<div class="register-header">
  <img
    src="assets/images/3dicons-pumpkin-dynamic-color.png"
    alt="Pumpkin logo"
    width="120"
    height="120"
    loading="lazy"
  />
  <h1>Software de Inventarios</h1>
  <p>Regístrate para crear una cuenta</p>
</div>
<section class="register-auth-form">
  <form class="register-auth" [formGroup]="registerForm" (ngSubmit)="onSubmit()">
    <label for="fullName">Nombre completo:</label>
    <input type="text" id="fullName" formControlName="fullName" placeholder="Luneska Dev" />

    <label for="email">Email:</label>
    <input type="email" id="email" formControlName="email" placeholder="[email protected]" />

    <label for="password">Password:</label>
    <input type="password" id="password" formControlName="password" placeholder="********" />

    <label for="confirmPassword">Confirmar contraseña:</label>
    <input
      type="password"
      id="confirmPassword"
      formControlName="confirmPassword"
      placeholder="********"
    />

    <button type="submit" [disabled]="registerForm.invalid">Registrarse</button>
  </form>
  <p>¿Ya tienes una cuenta? <a (click)="goToLogin()">Inicia sesión aquí</a></p>
</section>

Puntos importantes:

  • Cada input usa formControlName que coincide exactamente con el FormGroup
  • El botón está deshabilitado mientras el formulario sea inválido
  • Enlace para navegar de vuelta a login

Archivo: src/app/core/features/auth/register/register-auth.component.css

/* Puedes reutilizar los mismos estilos del login, solo cambia las clases */
/* Copia el contenido de login-auth.component.css y reemplaza:
   - .login-auth-form → .register-auth-form
   - .login-header → .register-header
   - .login-auth → .register-auth
*/

:host {
  display: block;
  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
    'Open Sans', 'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  --brand: #ff6a00;
  --brand-600: #e65a00;
  --brand-700: #cc4a00;
  --bg-dark: #111111;
  --panel-bg: #151515;
  --muted: #dfdfdf;
}

:host *,
:host *::before,
:host *::after {
  box-sizing: border-box;
}

.register-auth-form {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  max-width: 360px;
  border: 1px solid #1e1e1e;
  padding: 1rem;
  border-radius: 8px;
  margin: 0.5rem auto;
  background: var(--panel-bg);
  box-shadow: 0 1px 4px rgba(16, 24, 40, 0.04);
}

.register-auth-form p {
  font-weight: 500;
  font-size: 0.9rem;
  color: #ffffff;
  text-align: center;
}

.register-auth-form p a {
  color: var(--brand);
  text-decoration: none;
  cursor: pointer;
}

.register-auth-form p a:hover {
  text-decoration: underline;
}

.register-auth-form input[type='text'],
.register-auth-form input[type='email'],
.register-auth-form input[type='password'],
.register-auth-form input,
.register-auth-form textarea,
.register-auth-form select {
  padding: 0.8rem;
  margin-top: 5px;
  font-size: 1rem;
  width: 100%;
  border: 1px solid #303030;
  border-radius: 4px;
  background: #242424;
  transition: border-color 120ms ease-in-out, box-shadow 120ms ease-in-out;
  color: #fff;
}

.register-auth-form input:focus,
.register-auth-form textarea:focus,
.register-auth-form select:focus {
  outline: none;
  border-color: var(--brand-700);
  box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.12);
}

.register-auth-form button {
  padding: 0.6rem 0.75rem;
  font-size: 1rem;
  background-color: var(--brand);
  color: #ffffff;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  transition: background-color 120ms ease, transform 60ms ease;
  margin: 0 auto;
  display: block;
}

.register-auth-form button:hover:not(:disabled) {
  background-color: var(--brand-600);
}

.register-auth-form button:active:not(:disabled) {
  transform: translateY(1px);
}

.register-auth-form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.register-auth-form button:focus {
  outline: 3px solid rgba(255, 106, 0, 0.18);
  outline-offset: 2px;
}

.register-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  margin-bottom: 0.75rem;
  text-align: center;
}

.register-header img {
  width: 100px;
  height: 100px;
  margin: 1.25rem 0 0.5rem 0;
  object-fit: contain;
  display: block;
}

.register-header h1 {
  font-size: 1.5rem;
  margin: 0.125rem 0 0 0;
  color: #fff;
}

.register-header p {
  margin: 0.4rem 0 0 0;
  color: var(--muted);
  font-size: 0.95rem;
}

.register-auth label {
  font-weight: 600;
  margin-bottom: 0.25rem;
  display: block;
  color: #fff;
}

@media (max-width: 420px) {
  .register-auth-form {
    padding: 0.75rem;
    margin: 1rem;
  }

  .register-header img {
    width: 80px;
    height: 80px;
    margin-top: 1rem;
  }
}

🔄 Flujo de Autenticación

Diagrama del Flujo

┌─────────────────────────────────────────────────────────────────┐
│                    INICIO DE LA APLICACIÓN                       │
└──────────────────────────┬──────────────────────────────────────┘
                           │
                           ▼
                    ┌──────────────┐
                    │  app.routes  │
                    │  (Route '')  │
                    └──────┬───────┘
                           │
                           ▼
              ┌────────────────────────┐
              │  AuthLayoutComponent   │
              │  (Wrapper con <router-outlet>) │
              └────────┬───────────────┘
                       │
         ┌─────────────┴──────────────┐
         │                            │
         ▼                            ▼
   ┌──────────┐              ┌──────────────┐
   │  /login  │              │  /register   │
   │ (Login)  │◄─────────────┤  (Register)  │
   └────┬─────┘              └──────┬───────┘
        │                           │
        │ Usuario existe            │ Nuevo usuario
        │ Credenciales OK           │ Llena formulario
        │                           │
        ▼                           ▼
   ┌──────────────┐         ┌──────────────────┐
   │ Valida con   │         │ Valida passwords │
   │ localStorage │         │ coincidan        │
   └──────┬───────┘         └──────┬───────────┘
          │                        │
          │ ✅ Correcto            │ ✅ Correcto
          │                        │
          ▼                        ▼
   ┌──────────────┐         ┌──────────────────┐
   │ router.      │         │ Guarda en        │
   │ navigate     │         │ localStorage     │
   │ (['/home'])  │         └──────┬───────────┘
   └──────┬───────┘                │
          │                        │
          │                        ▼
          │                 ┌──────────────┐
          │                 │ router.      │
          │                 │ navigate     │
          │                 │ (['/login']) │
          │                 └──────┬───────┘
          │                        │
          └────────────┬───────────┘
                       │
                       ▼
              ┌────────────────┐
              │  MainLayout    │
              │  /home         │
              │  (Área privada)│
              └────────────────┘

Flujo Paso a Paso

1️⃣ Registro de Usuario (/register)

Usuario → Formulario de Registro
   ↓
Llena: fullName, email, password, confirmPassword
   ↓
Click "Registrarse"
   ↓
Validación:
   - Todos los campos llenos? ✅
   - Email válido? ✅
   - password === confirmPassword? ✅
   ↓
Crea objeto: { fullName, email, password }
   ↓
localStorage.setItem('user', JSON.stringify(userToStore))
   ↓
Alert: "Registro exitoso 🎉"
   ↓
router.navigate(['/login'])

2️⃣ Inicio de Sesión (/login)

Usuario → Formulario de Login
   ↓
Ingresa: username (puede ser email), password
   ↓
Click "Ingresar"
   ↓
Validación:
   - Todos los campos llenos? ✅
   ↓
Lee localStorage.getItem('user')
   ↓
¿Existe usuario guardado?
   │
   ├─ NO → Alert: "No existe ningún usuario registrado"
   │
   └─ SÍ → Parsea JSON: user = JSON.parse(userData)
              ↓
           Compara:
              - input === user.email (case-insensitive) O
              - input === user.username
              - password === user.password
              ↓
           ¿Coinciden?
              │
              ├─ NO → Alert: "Credenciales incorrectas"
              │
              └─ SÍ → Alert: "Inicio de sesión exitoso 🎉"
                       ↓
                    router.navigate(['/home'])

🎯 Conceptos Clave de Angular

1. Componentes Standalone

@Component({
  selector: 'app-login',
  standalone: true,  // ← Componente independiente (no necesita módulo)
  imports: [ReactiveFormsModule],  // ← Importa directamente lo que necesita
  templateUrl: './login-auth.component.html',
  styleUrls: ['./login-auth.component.css'],
})

¿Por qué standalone?

  • No necesitas crear NgModule
  • Imports directos en el componente
  • Mejor tree-shaking (bundle más pequeño)
  • Patrón recomendado en Angular 14+

2. Formularios Reactivos

// 1. Crear FormGroup en el constructor
this.loginForm = this.fb.group({
  username: ['', [Validators.required]],  // valor inicial, validadores
  password: ['', [Validators.required]]
});

// 2. Vincular en el template
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
  <input formControlName="username" />
  <input formControlName="password" />
</form>

// 3. Acceder a valores
const { username, password } = this.loginForm.value;

Ventajas:

  • Validación en el componente (TypeScript)
  • Estado del formulario reactivo
  • Testing más fácil
  • Mejor control de cambios

3. Router y Navegación

// En el componente
constructor(private router: Router) {}

// Navegar programáticamente
this.router.navigate(['/home']);

// En el template
<a [routerLink]="['/register']">Regístrate aquí</a>

// Con evento click
<a (click)="goToRegister()">Regístrate aquí</a>

router-outlet:

<!-- El componente que coincida con la ruta se renderiza aquí -->
<router-outlet></router-outlet>

4. LocalStorage para Persistencia

// Guardar
const user = { fullName: 'Juan', email: '[email protected]', password: '1234' };
localStorage.setItem('user', JSON.stringify(user));

// Leer
const userData = localStorage.getItem('user');
if (userData) {
  const user = JSON.parse(userData);
  console.log(user.email); // '[email protected]'
}

// Eliminar
localStorage.removeItem('user');

// Limpiar todo
localStorage.clear();

⚠️ Importante:

  • LocalStorage NO es seguro para producción
  • Las contraseñas deben ir cifradas en un backend
  • Solo para demos/prototipos

🚀 Comandos y Ejecución

Instalar Dependencias

# Si acabas de clonar el proyecto
npm install

Ejecutar en Desarrollo

# Opción 1: usando npm script
npm start

# Opción 2: Angular CLI directo
ng serve

# Opción 3: con configuración específica
ng serve --configuration development

La aplicación estará disponible en: http://localhost:4200

Compilar para Producción

# Build optimizado
npm run build

# O con Angular CLI
ng build --configuration production

Los archivos compilados estarán en: dist/devseniorInventory/browser/

Limpiar Datos de Prueba

# Abrir DevTools en el navegador (F12)
# → Application → Local Storage → http://localhost:4200
# → Click derecho en 'user' → Delete

O en la consola del navegador:

localStorage.removeItem('user');

📝 Checklist de Implementación

Usa este checklist para replicar el proyecto desde cero:

Configuración Inicial

  • Crear proyecto Angular: ng new devseniorInventory
  • Configurar angular.json con rutas de assets
  • Crear estructura de carpetas: core/features/auth/, core/layouts/

Layouts

  • Crear AuthLayoutComponent
    • TypeScript con RouterOutlet
    • HTML con contenedor y <router-outlet>
    • CSS con diseño centrado y fondo oscuro
  • Crear MainLayoutComponent (placeholder para área autenticada)

Componente Login

  • Crear LoginComponent
  • Agregar imports: ReactiveFormsModule, FormBuilder, Router, Validators
  • Crear loginForm con campos username y password
  • Implementar lógica onSubmit():
    • Validar formulario
    • Leer de localStorage
    • Comparar credenciales
    • Navegar a /home si es correcto
  • Crear template HTML:
    • Header con logo y títulos
    • Form con [formGroup] y formControlName
    • Botón deshabilitado si es inválido
    • Link a registro
  • Crear CSS con:
    • Variables de color (--brand, etc.)
    • Estilos de formulario
    • Estados de foco
    • Responsive

Componente Register

  • Crear RegisterComponent
  • Agregar imports necesarios
  • Crear registerForm con: fullName, email, password, confirmPassword
  • Implementar lógica onSubmit():
    • Validar contraseñas coincidan
    • Guardar en localStorage (sin confirmPassword)
    • Navegar a /login
  • Crear template HTML con todos los campos
  • Crear CSS (reutilizar/adaptar del login)

Rutas

  • Configurar app.routes.ts:
    • Ruta raíz con AuthLayoutComponent
    • Redirigir a /login
    • Rutas hijas: /login y /register
    • Ruta /home con MainLayoutComponent
  • Configurar app.config.ts:
    • provideRouter(routes)
    • ReactiveFormsModule

Assets

  • Colocar imagen del logo en src/assets/images/
  • Verificar que angular.json copie los assets

Testing

  • Ejecutar npm start
  • Probar registro completo
  • Probar login con usuario registrado
  • Verificar navegación entre rutas
  • Verificar localStorage en DevTools

🔐 Consideraciones de Seguridad

⚠️ Problemas Actuales (Solo para Demo)

  1. Contraseñas en texto plano:

    • Se guardan sin cifrar en localStorage
    • Cualquiera con acceso a DevTools puede verlas
  2. Sin backend:

    • Todo está en el cliente (navegador)
    • No hay validación en servidor
  3. Sin JWT/tokens:

    • No hay gestión de sesiones real
    • No hay expiración de login

✅ Soluciones para Producción

  1. Usar un backend real:

    Frontend (Angular) → API REST (Node.js/Spring/etc.) → Base de Datos
    
  2. Cifrar contraseñas:

    • Backend: bcrypt, argon2
    • Nunca enviar/guardar contraseñas en texto plano
  3. Implementar JWT:

    // Login exitoso → servidor devuelve token
    localStorage.setItem('token', response.token);
    
    // En cada petición
    headers: {
      'Authorization': `Bearer ${localStorage.getItem('token')}`
    }
  4. Proteger rutas:

    // AuthGuard
    export const authGuard: CanActivateFn = () => {
      const token = localStorage.getItem('token');
      if (!token) {
        inject(Router).navigate(['/login']);
        return false;
      }
      return true;
    };
    
    // En routes
    { path: 'home', component: HomeComponent, canActivate: [authGuard] }

🎓 Próximos Pasos Recomendados

Mejoras de UX

  1. Mensajes de validación visuales:

    <input formControlName="email" />
    <small *ngIf="registerForm.get('email')?.invalid && registerForm.get('email')?.touched">
      Email inválido
    </small>
  2. Loading spinner en botones:

    isLoading = false;
    
    onSubmit() {
      this.isLoading = true;
      // ... lógica
      this.isLoading = false;
    }
    <button [disabled]="loginForm.invalid || isLoading">
      {{ isLoading ? 'Cargando...' : 'Ingresar' }}
    </button>
  3. Toast notifications (en lugar de alert):

    • Instalar librería como ngx-toastr
    • Mensajes más profesionales

Funcionalidades Adicionales

  1. Recuperar contraseña:

    • Componente /forgot-password
    • Enviar email con token de reset
  2. Validación de contraseña fuerte:

    password: [
      '',
      [
        Validators.required,
        Validators.minLength(8),
        Validators.pattern(/^(?=.*[A-Za-z])(?=.*\d)/), // Letras y números
      ],
    ];
  3. Mostrar/ocultar contraseña:

    <input [type]="showPassword ? 'text' : 'password'" />
    <button (click)="showPassword = !showPassword">👁️</button>
  4. Remember me:

    if (rememberMe) {
      localStorage.setItem('remember', username);
    }

Arquitectura

  1. Crear AuthService:

    @Injectable({ providedIn: 'root' })
    export class AuthService {
      login(username: string, password: string): Observable<User> {}
      register(user: RegisterData): Observable<void> {}
      logout(): void {}
      isAuthenticated(): boolean {}
    }
  2. Crear modelos:

    // models/user.model.ts
    export interface User {
      fullName: string;
      email: string;
    }
    
    export interface LoginCredentials {
      username: string;
      password: string;
    }
  3. Implementar interceptores HTTP:

    // Añadir token automáticamente
    export const authInterceptor: HttpInterceptorFn = (req, next) => {
      const token = localStorage.getItem('token');
      if (token) {
        req = req.clone({
          setHeaders: { Authorization: `Bearer ${token}` },
        });
      }
      return next(req);
    };

📚 Recursos Adicionales

Documentación Oficial

Tutoriales

Herramientas

  • Angular DevTools (extensión Chrome/Edge): inspeccionar componentes
  • JSON Formatter: visualizar localStorage
  • Postman/Insomnia: probar APIs (cuando agregues backend)

🐛 Troubleshooting

Error: "Cannot find module '@angular/forms'"

npm install @angular/forms

Error: "NullInjectorError: No provider for FormBuilder"

Asegúrate de importar ReactiveFormsModule en el componente:

@Component({
  imports: [ReactiveFormsModule],  // ← Aquí
})

El formulario no actualiza valores

Verifica que:

  1. [formGroup]="loginForm" está en el <form>
  2. formControlName coincide con el nombre en el FormGroup
  3. Los imports están correctos

La imagen no se muestra

  1. Verifica que la ruta sea: assets/images/nombre.png
  2. Confirma que angular.json incluye el directorio en assets
  3. Reinicia el servidor: Ctrl+C y npm start

"Cannot read property 'value' of undefined"

El FormGroup no está inicializado. Verifica el constructor:

constructor(private fb: FormBuilder) {
  this.loginForm = this.fb.group({ ... });  // ← Debe estar aquí
}

✅ Resumen Final

Has implementado:

✅ Sistema completo de autenticación (login + registro)
✅ Formularios reactivos con validación
✅ Navegación entre rutas con Angular Router
✅ Layouts reutilizables (AuthLayout, MainLayout)
✅ Diseño responsive con CSS moderno
✅ Paleta de colores consistente (variables CSS)
✅ Componentes standalone (patrón moderno Angular)
✅ Persistencia local con localStorage
✅ Validación de contraseñas coincidentes
✅ Estados de formulario (disabled, invalid, touched)

Archivos Modificados/Creados

Configuración:

  • angular.json - Assets y build config
  • src/app/app.routes.ts - Definición de rutas
  • src/app/app.config.ts - Providers globales

Layouts:

  • src/app/core/layouts/auth-layout/ (3 archivos)
  • src/app/core/layouts/main-layout/ (3 archivos)

Componentes Auth:

  • src/app/core/features/auth/login/ (3 archivos + assets)
  • src/app/core/features/auth/register/ (3 archivos)

Assets:

  • src/assets/images/3dicons-pumpkin-dynamic-color.png

Total: ~15 archivos modificados/creados


🎉 ¡Felicidades!

Ahora tienes un sistema de autenticación completo y funcional. Este código es una base sólida para:

  • Proyectos personales
  • Prototipos rápidos
  • Aprender Angular y formularios reactivos
  • Base para implementar backend real

Siguiente objetivo: Implementar backend con Node.js/Express o Spring Boot y conectar la autenticación real con JWT.


Happy Coding! 🚀


Documento generado el 27 de octubre de 2025
Versión Angular: 18+
Proyecto: devseniorInventory

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published