Design

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

Design - User Management Module

Objetivos de Diseño

Diseñar una arquitectura modular y escalable para el módulo de gestión de usuarios que implemente los requerimientos definidos en requirements.md:

Dependencias y Límites del Módulo

Dependencias Internas

Dependencias Externas

Límites del Módulo

Arquitectura y Componentes

Contexto del Módulo

[Security Layer] ←→ [Route Guards] ←→ [Permission Validation]
       ↓                   ↓                      ↓
[Mock API] ←→ [UserApiService] ←→ [UserStateService] ←→ [UI Components]
                      ↓                    ↓                    ↓
[UserFilterService] ←→ [UserFormService] ←→ [Event System] ←→ [Log System]
                                                ↑
                                    [Security Directives]

División de Responsabilidades

Capa de Presentación

Capa de Lógica

Capa de Utilidades

Contratos y Modelos

Entidades Principales

User Interface (Extendiendo @libs-core/shared-models)

import { UserProfile, Role, Permission, BaseEntity, SystemRoles } from '@libs-core/shared-models';

// Reutiliza UserProfile de shared-models como base, omitiendo tenantId (contexto implícito)
interface User extends Omit<UserProfile, 'tenantId'> {
  // Campos adicionales específicos del módulo user-management
  status: UserStatus;     // Estado específico del módulo
  lastActive: string;     // Última actividad (ISO string)
  // roles: Role[] - ya incluido en UserProfile (usando Role base sin tenantId)
  // permissions: Permission[] - ya incluido en UserProfile (usando Permission base)
  primaryRole: SystemRoles; // Role primario usando enum de shared-models
}

// Extender entidades base sin tenantId redundante (contexto de aplicación)
interface UserEntity extends BaseEntity {
  email: string;
  username: string;
  firstName: string;
  lastName: string;
  roles: Role[];          // Roles base de shared-models (sin tenantId redundante)
  permissions: Permission[]; // Permisos base de shared-models (sin tenantId redundante)
  status: UserStatus;
  lastActive: Date;
  // tenantId se maneja como contexto de aplicación, no como campo
}

// Usar SystemRoles de shared-models para consistencia
type UserRole = SystemRoles; // Alias para compatibilidad
type UserStatus = 'active' | 'inactive' | 'suspended' | 'pending';

API Contracts (Usando @libs-core/shared-models)

import { ApiResponse, FilterCriteria } from '@libs-core/shared-models';

// Usa ApiResponse estándar de shared-models
type UserListResponse = ApiResponse<{
  users: User[];
}>;

// Filtros simplificados - tenantId como contexto implícito
interface UserFilters {
  // Filtros básicos sin tenantId redundante
  search?: string;        // Búsqueda por nombre/email
  roleIds?: string[];     // Filtro por IDs de roles (contexto del tenant actual)
  status?: UserStatus[];  // Filtro por estados
  createdFrom?: string;   // Fecha desde
  createdTo?: string;     // Fecha hasta
  isActive?: boolean;     // Filtro por estado activo

  // Criterios complejos usando FilterCriteria cuando sea necesario
  advancedFilters?: FilterCriteria[];
}

// Request/Response types simplificados - tenant del contexto de aplicación
interface CreateUserRequest {
  email: string;
  firstName: string;
  lastName: string;
  roleIds: string[];      // Array de Role.id (usando Role base de shared-models)
  primaryRole?: SystemRoles; // Role principal usando enum de shared-models
  isActive?: boolean;
  metadata?: Record<string, unknown>; // Compatible con UserProfile
  // tenantId omitido - se obtiene del contexto de aplicación
}

interface UpdateUserRequest extends Partial<CreateUserRequest> {
  id: string;
  version?: number;       // Optimistic concurrency control
  // tenantId omitido - contexto implícito
}

// Para casos donde el tenantId sea crítico (logs, auditoría)
interface UserAuditEvent {
  userId: string;
  action: string;
  tenantId: string;       // Explícito para auditoría y logs
  timestamp: Date;
  correlationId: string;
}

APIs y Endpoints

Operaciones CRUD

Operaciones Especiales

Errores Relevantes

Decisiones de Diseño

DEC-001: Signals vs Observables

Decisión: Usar Signals para estado local y Observables para HTTP Alternativa: Observables en todo el módulo Justificación: Signals optimizan change detection y son más legibles para estado Impacto: Mejor performance y código más maintible

DEC-002: Filtrado Server vs Client Side

Decisión: Filtrado server-side por defecto, client-side para refinamiento Alternativa: Solo filtrado client-side Justificación: Performance con datasets grandes, flexibilidad para refinamiento Impacto: Menos transferencia de datos, UX más fluida

DEC-003: Formularios Reactivos vs Template-driven

Decisión: Formularios reactivos con FormBuilder Alternativa: Template-driven forms Justificación: Mayor control, validaciones complejas, testing más sencillo Impacto: Más código inicial pero mayor flexibilidad y maintibilidad

DEC-004: Optimistic Updates

Decisión: Updates optimistas para operaciones rápidas (toggle status) Alternativa: Siempre esperar confirmación del servidor Justificación: Mejor UX percibida, rollback automático en caso de error Impacto: UX más fluida pero lógica de rollback necesaria

DEC-005: Operaciones Masivas

Decisión: Procesamiento secuencial con feedback de progreso Alternativa: Procesamiento paralelo o batch único Justificación: Control granular de errores, feedback visual detallado Impacto: Más tiempo de procesamiento pero mejor experiencia y control de errores

DEC-006: Estrategia de Permisos

Decisión: Uso de @libs-core/security con PermissionService y directivas estructurales Alternativa: Sistema de permisos custom o role-based simple Justificación: Consistencia con arquitectura GoGrab, multi-tenant, cache optimizado Impacto: Dependencia fuerte pero funcionalidad robusta y consistente

DEC-007: Guards vs Service Validation

Decisión: Guards para rutas + Service validation para acciones en componentes Alternativa: Solo guards o solo service validation Justificación: Doble capa de protección: navegación y UI Impacto: Mayor código inicial pero seguridad más robusta

DEC-008: Component State vs Global State

Decisión: Estado global en NGXS para datos de dominio, estado local para UI temporal Alternativa: Todo en componentes o todo en store Justificación: Datos de usuarios son compartidos, estado UI como modals es local Impacto: Mejor separación de responsabilidades, state management más claro

DEC-009: Effects vs Service Calls

Decisión: Effects para operaciones complejas con side effects, service calls directos para operaciones simples Alternativa: Todo en effects o todo en services Justificación: Effects permiten composición, logging centralizado, error handling Impacto: Código más testeable y mantenible para operaciones complejas

Riesgos y Mitigaciones

Riesgo R-001: Performance con grandes datasets

Mitigación: Paginación server-side, virtual scrolling para casos extremos Monitoreo: Métricas de tiempo de carga y memoria usada

Riesgo R-002: Consistencia de datos en updates optimistas

Mitigación: Sistema de rollback automático, invalidación de caché Monitoreo: Logs de rollbacks y errores de sincronización

Riesgo R-003: Validaciones client-side únicamente

Mitigación: Validaciones defensivas, confianza en validaciones server-side Monitoreo: Logs de errores de validación del servidor

Riesgo R-004: Estado desincronizado entre componentes

Mitigación: Estado centralizado con Signals, eventos para comunicación Monitoreo: Testing de integración extensivo

Estrategia de Pruebas y Observabilidad

Testing

Observabilidad

Validaciones Clave

Mapeo de Cobertura

Requerimiento Componente Principal Responsabilidad
US-001 (Visualización) UserListComponent, UserCardComponent, UserDetailComponent Renderizado flexible de datos
US-002 (Crear) UserFormComponent, UserApiService Formulario + validaciones + API
US-003 (Editar) UserFormComponent, UserApiService Formulario precargado + actualización
US-004 (Eliminar) UserDeleteConfirmComponent, UserApiService Confirmación + eliminación segura
US-005 (Estados) UserStatusToggleComponent, UserApiService Control rápido de estados
US-006 (Filtrado) UserFiltersComponent, UserFilterService Filtros + query building
US-007 (Masivas) UserBulkActionsComponent, UserApiService Selección múltiple + operaciones batch
US-008 (Roles) UserFormComponent, RoleSelectorComponent Asignación + visualización de roles

Trazabilidad Requirements → Design

Arquitectura de Seguridad Integrada

Route Protection

// user-management.routes.ts
{
  path: 'users',
  loadComponent: () => import('./user-list.component'),
  canActivate: [authGuard, modulePermissionGuard('user-management')]
},
{
  path: 'users/create',
  loadComponent: () => import('./user-form.component'),
  canActivate: [authGuard, permissionGuard(['users:create'])]
}

Template Security

<!-- user-list.component.html -->
<button *hasPermission="'users:create'"
        (click)="createUser()">
  Nuevo Usuario
</button>

<div *hasAnyRole="['admin', 'user-manager']">
  <app-user-bulk-actions></app-user-bulk-actions>
</div>

<td *hasPermission="'users:delete'; else: disabledAction">
  <button (click)="deleteUser(user)">Eliminar</button>
</td>

Service Integration

// user-state.service.ts
constructor(
  private permissionService: PermissionService,
  private contextService: ContextService
) {
  // Auto-refresh permissions on context change
  this.contextService.contextChange$.subscribe(() => {
    this.refreshUserPermissions();
  });
}

NGXS Integration Examples

// user.state.ts
@State<UserStateModel>({
  name: 'users',
  defaults: {
    users: [],
    selectedUser: null,
    loading: false,
    error: null,
    filters: {}
  }
})
export class UserState {
  @Selector()
  static getUsers(state: UserStateModel): User[] {
    return state.users;
  }

  @Action(UserActions.LoadUsers)
  loadUsers(ctx: StateContext<UserStateModel>, action: UserActions.LoadUsers) {
    ctx.patchState({ loading: true, error: null });
    // Logic handled by Effects
  }
}

// user.effects.ts
@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UserActions.LoadUsers),
      switchMap(action =>
        this.userApiService.getUsers(action.filters).pipe(
          map(users => UserActions.LoadUsersSuccess({ users })),
          catchError(error => of(UserActions.LoadUsersFailure({ error })))
        )
      )
    )
  );
}

// user-list.component.ts
export class UserListComponent {
  users$ = this.store.select(UserSelectors.getUsers);
  loading$ = this.store.select(UserSelectors.isLoading);

  constructor(private store: Store) {}

  loadUsers() {
    this.store.dispatch(new UserActions.LoadUsers());
  }
}

El diseño está optimizado para cumplir todos los requerimientos funcionales y no funcionales, con integración completa del sistema de seguridad GoGrab Space y NGXS para state management robusto, manteniendo separación de responsabilidades y facilidad de testing y mantenimiento.