Arquitectura del Frontend

Arquitectura del Frontend

El proyecto frontend utiliza una combinación de Clean Architecture con Vertical Slicing, organizando el código por funcionalidades en lugar de por tipo de archivo. Esto evita archivos de configuración gigantes y mantiene cada módulo cohesivo e independiente.

Además, se implementa i18n para el manejo de traducciones multilenguaje.


Estructura General de Carpetas

        • identity.routes.ts

Convención de Nombres de Módulos

⚠️

Los nombres de las carpetas de funcionalidades deben coincidir exactamente con los nombres usados en el backend (Spring Boot). Esto facilita la ubicación del código entre ambos entornos y reduce la fricción al navegar entre proyectos.

Por ejemplo, si en el backend existe el paquete identity, la carpeta en el frontend también debe llamarse identity.


Descripción de Cada Capa

Core

Contiene todo lo que es base o fundacional para la aplicación: piezas que no funcionan de forma independiente pero que otros módulos necesitan para operar.

Ejemplos de lo que vive en core:

  • Componentes base/abstractos: componentes genéricos que no se usan directamente, sino como base para componentes específicos. Por ejemplo, un GenericPickerComponent que sirve de base para un AlmacenPickerComponent o un ProveedorPickerComponent.
  • Interceptores de red globales: interceptores HTTP que aplican a toda la aplicación (no a una funcionalidad en específico).
  • Providers y configuración de arranque: setup de i18n, configuración de TanStack Query, tokens de inyección globales, etc.

Si algo en core empieza a usarse solo dentro de una funcionalidad específica, es señal de que debe moverse a esa feature o a shared/.


Componentes

Elementos visuales que pertenecen a una funcionalidad y se reutilizan en múltiples páginas dentro de la misma (ej. un AvatarComponent que aparece en el perfil y en el menú lateral).

Si un componente es exclusivo de una sola página y no se compartirá con otras, debe ubicarse anidado dentro de la carpeta de esa página (pages/login/components/). Esto optimiza la carga y mantiene la cohesión.


Modelos

Representan la estructura de datos del módulo. Inicialmente son un reflejo directo del domain/model de JPA definido en el backend.

// Ejemplo: reflejo del dominio JPA WebUsuario
export interface WebUsuario {
  id: number;
  usuario: string;
  activo: boolean;
  locale: string;
  rol: WebRol;
  tipoUsuario: TipoUsuario;
  createdAt: string;
  fechaPassword: string;
}

Servicios

Se encargan exclusivamente de la lógica de negocio y las peticiones HTTP. Cada funcionalidad tiene sus propios servicios (ej. authService, roleService).


Queries (TanStack Query para Angular)

Se utiliza TanStack Query para el manejo asíncrono de datos y peticiones. Sus principales ventajas son:

  • Reutilizar y deduplicar solicitudes idénticas.
  • Sincronizar el estado entre múltiples componentes.
  • Manejar cachés, temporizadores e invalidaciones programáticamente.
  • Realizar actualizaciones optimistas: actualizar la UI antes de que la petición en segundo plano finalice.
⚠️

Se deben definir Keys (llaves) centralizadas para gestionar todas las consultas. Esto evita inconsistencias y facilita la invalidación de caché.

// Ejemplo de keys centralizadas
export const identityKeys = {
  all: ['identity'] as const,
  users: () => [...identityKeys.all, 'users'] as const,
  user: (id: number) => [...identityKeys.users(), id] as const,
}

Store (Estado Global)

El Store se usa cuando hay estado propio alrededor de una consulta o funcionalidad, más allá del simple resultado de la petición.

La distinción clave frente a las Queries es:

QueryStore
PropósitoObtener datos del servidor y cachearlosMantener estado derivado o lógica sobre esos datos
EjemploConsultar el usuario actual desde la APISaber si el usuario está autenticado, cuál es su rol activo, o si tiene un permiso específico
Cuándo usarloSolo necesito el dato y su estado de cargaNecesito lógica, utilidades o estado que no me da la query sola

Ejemplo concreto con el usuario:

Una query de usuario devuelve el objeto WebUsuario. Pero el store de identidad expone además:

  • isAuthenticated — boolean derivado de si la query devolvió un usuario válido.
  • currentRol — el rol activo del usuario.
  • hasRole(rol) — utilidad para verificar permisos desde cualquier componente.

Si solo necesitas mostrar datos de una petición, usa una Query. Si necesitas derivar estado o exponer utilidades sobre esos datos, usa el Store.


Directivas

Atributos HTML personalizados para extender el comportamiento del template. Ejemplos:

  • Directiva de clasificación: ordena columnas de tablas.
  • Directiva de rol: oculta o muestra elementos según los permisos del usuario autenticado.

Guards (Guardias de Rutas)

Controlan el acceso a las rutas de la aplicación. Tipos comunes:

GuardPropósito
authGuardPermite acceso solo a usuarios autenticados
guestGuardPermite acceso solo a usuarios no autenticados (ej. pantalla de login)
roleGuardRestringe el acceso según el rol del usuario

Interceptores

Intervienen en las peticiones HTTP salientes y las respuestas entrantes:

InterceptorComportamiento
CredencialesAdjunta cookies/tokens a cada petición saliente
Sesión expiradaDetecta errores 401 y redirige automáticamente al login

Rutas

Archivo de definición de rutas públicas y privadas específicas de la funcionalidad. Se importa en el enrutador principal de la aplicación.

// identity.routes.ts
export const identityRoutes: Routes = [
  {
    path: 'login',
    component: LoginComponent,
    canActivate: [guestGuard],
  },
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [authGuard],
  },
]

Carpeta Shared (Compartidos)

Ubicada en la raíz de src/, agrupa todo lo que es genérico y transversal a la aplicación, sin pertenecer a ninguna funcionalidad en específico:

  • Componentes reutilizables globales (botones, modales, alertas).
  • Mensajes de error estándar.
  • Pipes y utilidades comunes.

Si un componente, pipe o utilidad solo lo usa un módulo, va dentro de ese módulo. Si lo usan dos o más módulos, va en shared/.