Gograb Admin Requirements

Documentation for GoGrab Admin

GoGrab Admin - Requisitos de la Aplicación Administrativa

Resumen Ejecutivo

GoGrab Admin es una aplicación Angular 20 standalone que sirve como panel administrativo para la gestión de usuarios, configuración del sistema y control de accesos. Es la primera aplicación construida sobre el ecosistema de librerías core de GoGrab Space, diseñada específicamente para administradores del sistema, managers y editores en un entorno single-tenant.

Objetivos

Objetivo Principal

Crear una aplicación administrativa completa y funcional que integre todas las librerías core existentes, proporcionando una interfaz robusta para la gestión de usuarios con autenticación, autorización basada en roles y permisos (RBAC), y comunicación basada en eventos.

Objetivos Específicos

Alcance del Proyecto

En Alcance

Fuera de Alcance

Arquitectura de la Aplicación

Estructura de Directorios

apps/gograb-admin/
├── src/
│   ├── app/
│   │   ├── core/                          # Core application layer
│   │   │   ├── layout/                    # Layout components
│   │   │   │   ├── app-shell.component.ts
│   │   │   │   ├── header.component.ts
│   │   │   │   ├── sidebar.component.ts
│   │   │   │   └── footer.component.ts
│   │   │   ├── guards/                    # Application-level guards
│   │   │   └── services/
│   │   │       └── navigation.service.ts  # Navigation management
│   │   │
│   │   ├── features/                      # Feature modules
│   │   │   ├── dashboard/                 # Main dashboard
│   │   │   │   ├── dashboard.component.ts
│   │   │   │   ├── dashboard.component.html
│   │   │   │   ├── dashboard.component.scss
│   │   │   │   └── dashboard.routes.ts
│   │   │   └── auth/                      # Authentication pages
│   │   │       ├── login.component.ts
│   │   │       └── auth.routes.ts
│   │   │
│   │   ├── shared/                        # Shared components (if needed)
│   │   │
│   │   ├── app.config.ts                  # Application configuration
│   │   ├── app.routes.ts                  # Main routing configuration
│   │   ├── app.component.ts               # Root component
│   │   ├── app.component.html             # Root template
│   │   └── app.component.scss             # Root styles
│   │
│   ├── assets/                            # Static assets
│   │   ├── images/
│   │   └── icons/
│   │
│   ├── styles/                            # Global styles
│   │   ├── _variables.scss
│   │   ├── _mixins.scss
│   │   └── _theme.scss
│   │
│   ├── main.ts                            # Application bootstrap
│   ├── styles.scss                        # Global styles entry point
│   └── index.html                         # HTML entry point
│
├── public/                                # Public assets (favicon, etc.)
├── project.json                           # Nx project configuration
├── tsconfig.app.json                      # TypeScript app configuration
├── tsconfig.spec.json                     # TypeScript test configuration
└── vite.config.ts                         # Vite configuration for tests

apps/gograb-admin-e2e/                     # E2E tests project
├── src/
│   ├── fixtures/                          # Test fixtures
│   │   ├── auth.fixture.ts
│   │   ├── permissions.fixture.ts
│   │   └── users.fixture.ts
│   │
│   ├── pages/                             # Page Object Models
│   │   ├── base.page.ts
│   │   ├── login.page.ts
│   │   ├── dashboard.page.ts
│   │   ├── user-list.page.ts
│   │   └── user-form.page.ts
│   │
│   ├── tests/                             # Test suites
│   │   ├── auth/
│   │   │   ├── login.spec.ts
│   │   │   ├── logout.spec.ts
│   │   │   └── auth-guards.spec.ts
│   │   ├── authorization/
│   │   │   ├── permission-guards.spec.ts
│   │   │   ├── role-based-ui.spec.ts
│   │   │   └── granular-permissions.spec.ts
│   │   ├── user-management/
│   │   │   ├── user-list.spec.ts
│   │   │   ├── user-create.spec.ts
│   │   │   ├── user-edit.spec.ts
│   │   │   ├── user-delete.spec.ts
│   │   │   ├── user-search.spec.ts
│   │   │   └── bulk-operations.spec.ts
│   │   ├── navigation/
│   │   │   ├── routing.spec.ts
│   │   │   ├── sidebar-navigation.spec.ts
│   │   │   └── breadcrumbs.spec.ts
│   │   └── integration/
│   │       ├── dashboard-metrics.spec.ts
│   │       ├── events-system.spec.ts
│   │       └── mock-api-integration.spec.ts
│   │
│   └── support/
│       ├── helpers.ts
│       ├── constants.ts
│       └── matchers.ts
│
├── playwright.config.ts                   # Playwright configuration
└── project.json                           # Nx E2E project configuration

Funcionalidades Requeridas

1. Sistema de Layout

App Shell Component

Descripción: Contenedor principal con layout persistente.

Características:

Integración:

Header Component

Funcionalidades:

Sidebar Component

Funcionalidades:

Footer Component

Funcionalidades:

2. Dashboard Principal

Descripción: Pantalla inicial con resumen del sistema y accesos rápidos.

Elementos:

Integración:

3. Gestión de Usuarios (User Management)

Descripción: Integración completa con la librería @gograb-space/user-management.

Routing:

{
  path: 'users',
  loadChildren: () => import('@gograb-space/user-management')
    .then(m => m.USER_MANAGEMENT_ROUTES),
  canActivate: [authGuard, permissionGuard]
}

Características heredadas de user-management:

Mock Data:

4. Autenticación

Descripción: Sistema de login simulado con JWT mock.

Login Component (features/auth/login.component.ts):

@Component({
  selector: 'app-login',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="login-container">
      <form [formGroup]="loginForm" (ngSubmit)="onLogin()">
        <h1>GoGrab Admin</h1>
        <input type="email" formControlName="email" placeholder="Email" />
        <input type="password" formControlName="password" placeholder="Password" />
        <button type="submit" [disabled]="!loginForm.valid">Login</button>
      </form>
    </div>
  `
})
export class LoginComponent {
  private authService = inject(AuthService);
  private router = inject(Router);

  loginForm = new FormGroup({
    email: new FormControl('', [Validators.required, Validators.email]),
    password: new FormControl('', Validators.required)
  });

  onLogin() {
    this.authService.login$(this.loginForm.value)
      .subscribe(() => this.router.navigate(['/dashboard']));
  }
}

Credenciales Mock:

// Mock users for login
{
  email: '[email protected]',
  password: 'admin123',
  role: 'admin'
},
{
  email: '[email protected]',
  password: 'manager123',
  role: 'manager'
},
{
  email: '[email protected]',
  password: 'editor123',
  role: 'editor'
}

JWT Mock:

5. Sistema de Autorización Basado en Permisos y Roles

Descripción: Sistema de autorización granular usando Permission entities de @libs-core/shared-models combinado con roles para control de acceso fino.

Modelo de Permisos

Permission Entity (de @libs-core/shared-models):

interface Permission {
  id: string;
  tenantId: string;
  name: string;          // e.g., "users:create", "users:read:all"
  resource: string;      // e.g., "users", "settings", "system"
  action: string;        // e.g., "create", "read", "update", "delete"
  description: string;
}

Role Entity (de @libs-core/shared-models):

interface Role {
  id: string;
  tenantId: string;
  name: string;              // e.g., "admin", "manager", "editor"
  description: string;
  permissions: Permission[]; // Array de permisos asignados al rol
}

Permisos Granulares Definidos

Permisos de Usuario (resource: "users"):

Permission Name Action Descripción Admin Manager Editor Viewer
users:read:own read Ver información propia ✅ ✅ ✅ ✅
users:read:all read Ver todos los usuarios ✅ ✅ ✅ ✅
users:create create Crear nuevos usuarios ✅ ✅ ❌ ❌
users:update:own update Actualizar información propia ✅ ✅ ✅ ❌
users:update:all update Actualizar cualquier usuario ✅ ✅ ✅ ❌
users:delete delete Eliminar usuarios ✅ ❌ ❌ ❌
users:manage:roles manage Asignar/cambiar roles de usuarios ✅ ✅ ❌ ❌
users:activate manage Activar/desactivar usuarios ✅ ✅ ❌ ❌
users:suspend manage Suspender usuarios ✅ ❌ ❌ ❌

Permisos de Sistema (resource: "system"):

Permission Name Action Descripción Admin Manager Editor Viewer
system:read:metrics read Ver métricas del sistema ✅ ✅ ❌ ❌
system:read:logs read Ver logs del sistema ✅ ✅ ❌ ❌
system:configure manage Configurar parámetros del sistema ✅ ❌ ❌ ❌

Permisos de Configuración (resource: "settings"):

Permission Name Action Descripción Admin Manager Editor Viewer
settings:read read Ver configuraciones ✅ ✅ ✅ ❌
settings:update update Modificar configuraciones ✅ ✅ ❌ ❌

Guards Aplicados con Permisos Granulares

Route Guards:

// app.routes.ts - Guards basados en permisos específicos
{
  path: 'users',
  loadChildren: () => import('@gograb-space/user-management')
    .then(m => m.USER_MANAGEMENT_ROUTES),
  canActivate: [authGuard, permissionGuard],
  data: { requiredPermission: 'users:read:all' } // Requiere permiso específico
},
{
  path: 'users/create',
  canActivate: [authGuard, permissionGuard],
  data: { requiredPermission: 'users:create' }
},
{
  path: 'dashboard',
  canActivate: [authGuard, permissionGuard],
  data: {
    requiredPermissions: ['system:read:metrics'] // Múltiples permisos
  }
}

Validation con PermissionService:

import { PermissionService } from '@libs-core/security';
import type { Permission } from '@libs-core/shared-models';

@Component({...})
export class UserListComponent {
  private permissionService = inject(PermissionService);

  // Validar permiso único
  canCreateUser = computed(() =>
    this.permissionService.hasPermission('users:create')()
  );

  // Validar múltiples permisos (AND)
  canManageRoles = computed(() =>
    this.permissionService.hasAllPermissions([
      'users:read:all',
      'users:manage:roles'
    ])()
  );

  // Validar cualquier permiso (OR)
  canEditUsers = computed(() =>
    this.permissionService.hasAnyPermission([
      'users:update:own',
      'users:update:all'
    ])()
  );

  // Validar permiso + rol
  canDeleteUser = computed(() =>
    this.permissionService.hasPermission('users:delete')() &&
    this.roleService.hasProfile('admin')()
  );
}

Directivas en Templates con Permisos Granulares

Directiva *hasPermission (basada en Permission entities):

<!-- Botón visible solo con permiso específico -->
<button *hasPermission="'users:create'">Create User</button>

<!-- Botón visible con múltiples permisos (AND) -->
<button *hasPermission="['users:update:all', 'users:manage:roles']">
  Manage User Roles
</button>

<!-- Sección visible con cualquier permiso (OR) - usar método en componente -->
<div *ngIf="canEditUsers()">
  <button>Edit User</button>
</div>

<!-- Delete solo para admin con permiso específico -->
<button *hasPermission="'users:delete'" *hasProfile="'admin'">
  Delete User
</button>

Directiva *hasProfile (basada en Role entities):

<!-- Solo visible para admin -->
<div *hasProfile="'admin'">Admin Panel</div>

<!-- Solo visible para admin y manager -->
<button *hasProfile="['admin', 'manager']">Advanced Options</button>

Combinación de Permisos y Roles:

<!-- Approach 1: Directivas anidadas -->
<div *hasProfile="'admin'">
  <button *hasPermission="'system:configure'">System Settings</button>
</div>

<!-- Approach 2: Computed signal en componente (recomendado) -->
<button *ngIf="canAccessSystemSettings()">System Settings</button>

Service Layer Permission Validation

UserApiService con validación de permisos:

import { PermissionService } from '@libs-core/security';

@Injectable()
export class UserApiService {
  private permissionService = inject(PermissionService);
  private http = inject(HttpClient);

  createUser(user: User): Observable<User> {
    // Validación de permiso antes de la operación
    if (!this.permissionService.hasPermission('users:create')()) {
      return throwError(() => new Error('Permission denied: users:create'));
    }
    return this.http.post<User>('/api/users', user);
  }

  deleteUser(userId: string): Observable<void> {
    // Validación de múltiples condiciones
    if (!this.permissionService.hasPermission('users:delete')() ||
        !this.roleService.hasProfile('admin')()) {
      return throwError(() => new Error('Permission denied: users:delete requires admin role'));
    }
    return this.http.delete<void>(`/api/users/${userId}`);
  }
}

Mock Data de Permisos

Permisos Mock por Rol (configurados en mock-api):

// Mock permissions data
const adminPermissions: Permission[] = [
  { id: '1', tenantId: 'default-tenant', name: 'users:read:all', resource: 'users', action: 'read', description: 'Read all users' },
  { id: '2', tenantId: 'default-tenant', name: 'users:create', resource: 'users', action: 'create', description: 'Create users' },
  { id: '3', tenantId: 'default-tenant', name: 'users:update:all', resource: 'users', action: 'update', description: 'Update all users' },
  { id: '4', tenantId: 'default-tenant', name: 'users:delete', resource: 'users', action: 'delete', description: 'Delete users' },
  { id: '5', tenantId: 'default-tenant', name: 'users:manage:roles', resource: 'users', action: 'manage', description: 'Manage user roles' },
  { id: '6', tenantId: 'default-tenant', name: 'users:suspend', resource: 'users', action: 'manage', description: 'Suspend users' },
  { id: '7', tenantId: 'default-tenant', name: 'system:read:metrics', resource: 'system', action: 'read', description: 'Read system metrics' },
  { id: '8', tenantId: 'default-tenant', name: 'system:read:logs', resource: 'system', action: 'read', description: 'Read system logs' },
  { id: '9', tenantId: 'default-tenant', name: 'system:configure', resource: 'system', action: 'manage', description: 'Configure system' },
  { id: '10', tenantId: 'default-tenant', name: 'settings:read', resource: 'settings', action: 'read', description: 'Read settings' },
  { id: '11', tenantId: 'default-tenant', name: 'settings:update', resource: 'settings', action: 'update', description: 'Update settings' }
];

const managerPermissions: Permission[] = [
  { id: '1', tenantId: 'default-tenant', name: 'users:read:all', resource: 'users', action: 'read', description: 'Read all users' },
  { id: '2', tenantId: 'default-tenant', name: 'users:create', resource: 'users', action: 'create', description: 'Create users' },
  { id: '3', tenantId: 'default-tenant', name: 'users:update:all', resource: 'users', action: 'update', description: 'Update all users' },
  { id: '5', tenantId: 'default-tenant', name: 'users:manage:roles', resource: 'users', action: 'manage', description: 'Manage user roles' },
  { id: '12', tenantId: 'default-tenant', name: 'users:activate', resource: 'users', action: 'manage', description: 'Activate/deactivate users' },
  { id: '7', tenantId: 'default-tenant', name: 'system:read:metrics', resource: 'system', action: 'read', description: 'Read system metrics' },
  { id: '8', tenantId: 'default-tenant', name: 'system:read:logs', resource: 'system', action: 'read', description: 'Read system logs' },
  { id: '10', tenantId: 'default-tenant', name: 'settings:read', resource: 'settings', action: 'read', description: 'Read settings' },
  { id: '11', tenantId: 'default-tenant', name: 'settings:update', resource: 'settings', action: 'update', description: 'Update settings' }
];

const editorPermissions: Permission[] = [
  { id: '1', tenantId: 'default-tenant', name: 'users:read:all', resource: 'users', action: 'read', description: 'Read all users' },
  { id: '3', tenantId: 'default-tenant', name: 'users:update:all', resource: 'users', action: 'update', description: 'Update all users' },
  { id: '13', tenantId: 'default-tenant', name: 'users:update:own', resource: 'users', action: 'update', description: 'Update own user' },
  { id: '10', tenantId: 'default-tenant', name: 'settings:read', resource: 'settings', action: 'read', description: 'Read settings' }
];

const viewerPermissions: Permission[] = [
  { id: '1', tenantId: 'default-tenant', name: 'users:read:all', resource: 'users', action: 'read', description: 'Read all users' },
  { id: '14', tenantId: 'default-tenant', name: 'users:read:own', resource: 'users', action: 'read', description: 'Read own user' }
];

// Mock roles with permissions
const mockRoles: Role[] = [
  { id: 'role-1', tenantId: 'default-tenant', name: 'admin', description: 'System Administrator', permissions: adminPermissions },
  { id: 'role-2', tenantId: 'default-tenant', name: 'manager', description: 'User Manager', permissions: managerPermissions },
  { id: 'role-3', tenantId: 'default-tenant', name: 'editor', description: 'Content Editor', permissions: editorPermissions },
  { id: 'role-4', tenantId: 'default-tenant', name: 'viewer', description: 'Read-only User', permissions: viewerPermissions }
];

Resumen del Sistema de Autorización

Principios Clave:

  1. Permisos Granulares: Control fino usando Permission entities con resource:action pattern
  2. Roles como Agrupadores: Role entities agrupan conjuntos de permisos relacionados
  3. Validación en Múltiples Capas:
    • Guards en rutas (navigation-level)
    • Directivas en templates (UI-level)
    • Services para operaciones (business-level)
  4. Performance: Cache de permisos en PermissionService con expiración automática
  5. Multi-tenant Aware: Todos los permisos incluyen tenantId para aislamiento

Ventajas:

6. Mock API Configuration

Endpoints Simulados:

// Mock API definitions
GET  /api/auth/login       → Login con credenciales mock
GET  /api/auth/refresh     → Refresh token mock
GET  /api/users            → Lista paginada de usuarios
GET  /api/users/:id        → Usuario por ID
POST /api/users            → Crear usuario
PUT  /api/users/:id        → Actualizar usuario
DELETE /api/users/:id      → Eliminar usuario
GET  /api/metrics/system   → Métricas del dashboard

Configuración en app.config.ts:

import { provideMockApi } from '@libs-core/mock-api';
import { UserMockApi } from '@libs-core/user-management';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([mockHttpInterceptor])
    ),
    provideMockApi([
      UserMockApi,           // User endpoints
      AuthMockApi,           // Auth endpoints
      MetricsMockApi         // Dashboard metrics
    ])
  ]
};

7. Sistema de Logging

Configuración del Logger:

import { provideLogger, LogLevel, ConsoleAppender } from '@libs-core/log-system';

provideLogger({
  appenders: [
    ConsoleAppender,
    // RemoteAppender (disabled in mock mode)
  ],
  level: LogLevel.DEBUG,
  context: {
    appName: 'gograb-admin',
    tenantId: 'default-tenant'
  }
})

Uso con Decorators:

import { loggable, logError, performance } from '@libs-core/log-system';

@loggable('NavigationService')
export class NavigationService {

  @logError('Navigation failed')
  @performance('navigate')
  navigateToModule(moduleId: string): Observable<boolean> {
    // navigation logic
  }
}

Logs Generados:

8. Sistema de Eventos

Eventos del Sistema:

// System events
export enum SystemEventType {
  USER_LOGGED_IN = 'USER_LOGGED_IN',
  USER_LOGGED_OUT = 'USER_LOGGED_OUT',
  NAVIGATION_CHANGED = 'NAVIGATION_CHANGED',
  MODULE_LOADED = 'MODULE_LOADED',
  PERMISSION_DENIED = 'PERMISSION_DENIED'
}

Publicación de Eventos:

import { EventBus } from '@libs-core/event-system';

@Component({...})
export class DashboardComponent {
  private eventBus = inject(EventBus);

  ngOnInit() {
    this.eventBus.publish(new ModuleLoadedEvent('dashboard'));
  }
}

Suscripción a Eventos:

this.eventBus.subscribe(SystemEventType.USER_LOGGED_IN, (event) => {
  console.log('User logged in:', event.payload);
});

9. Testing E2E con Playwright

Descripción: Suite completa de tests End-to-End usando Playwright para validar flujos críticos de usuario, autorización basada en permisos, y comportamiento de la aplicación en un navegador real.

Configuración de Playwright

Estructura de Tests E2E:

apps/gograb-admin-e2e/
├── src/
│   ├── fixtures/
│   │   ├── auth.fixture.ts          # Fixtures de autenticación
│   │   ├── permissions.fixture.ts   # Fixtures de permisos
│   │   └── users.fixture.ts         # Fixtures de datos de usuarios
│   │
│   ├── pages/                       # Page Object Models
│   │   ├── login.page.ts
│   │   ├── dashboard.page.ts
│   │   ├── user-list.page.ts
│   │   ├── user-form.page.ts
│   │   └── base.page.ts
│   │
│   ├── tests/                       # Test suites
│   │   ├── auth/
│   │   │   ├── login.spec.ts
│   │   │   ├── logout.spec.ts
│   │   │   └── auth-guards.spec.ts
│   │   │
│   │   ├── authorization/
│   │   │   ├── permission-guards.spec.ts
│   │   │   ├── role-based-ui.spec.ts
│   │   │   └── granular-permissions.spec.ts
│   │   │
│   │   ├── user-management/
│   │   │   ├── user-list.spec.ts
│   │   │   ├── user-create.spec.ts
│   │   │   ├── user-edit.spec.ts
│   │   │   ├── user-delete.spec.ts
│   │   │   ├── user-search.spec.ts
│   │   │   └── bulk-operations.spec.ts
│   │   │
│   │   ├── navigation/
│   │   │   ├── routing.spec.ts
│   │   │   ├── sidebar-navigation.spec.ts
│   │   │   └── breadcrumbs.spec.ts
│   │   │
│   │   └── integration/
│   │       ├── dashboard-metrics.spec.ts
│   │       ├── events-system.spec.ts
│   │       └── mock-api-integration.spec.ts
│   │
│   └── support/
│       ├── helpers.ts               # Helper functions
│       ├── constants.ts             # Test constants
│       └── matchers.ts              # Custom matchers
│
├── playwright.config.ts             # Playwright configuration
└── project.json                     # Nx project configuration

playwright.config.ts:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './src/tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html', { outputFolder: '../../dist/playwright-report/gograb-admin-e2e' }],
    ['junit', { outputFile: '../../dist/test-results/gograb-admin-e2e/junit.xml' }],
    ['list']
  ],
  use: {
    baseURL: 'http://localhost:5100',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure'
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] }
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] }
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] }
    }
  ],
  webServer: {
    command: 'npx nx serve gograb-admin',
    url: 'http://localhost:5100',
    reuseExistingServer: !process.env.CI,
    timeout: 120000
  }
});

Page Object Models (POM)

Base Page Object:

// src/pages/base.page.ts
import { Page, Locator } from '@playwright/test';

export abstract class BasePage {
  constructor(protected page: Page) {}

  async navigate(path: string): Promise<void> {
    await this.page.goto(path);
  }

  async waitForPageLoad(): Promise<void> {
    await this.page.waitForLoadState('networkidle');
  }

  async getElementByTestId(testId: string): Promise<Locator> {
    return this.page.locator(`[data-testid="${testId}"]`);
  }
}

Login Page Object:

// src/pages/login.page.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './base.page';

export class LoginPage extends BasePage {
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;

  constructor(page: Page) {
    super(page);
    this.emailInput = page.locator('input[formControlName="email"]');
    this.passwordInput = page.locator('input[formControlName="password"]');
    this.loginButton = page.locator('button[type="submit"]');
    this.errorMessage = page.locator('[data-testid="login-error"]');
  }

  async login(email: string, password: string): Promise<void> {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
    await this.waitForPageLoad();
  }

  async loginAsAdmin(): Promise<void> {
    await this.login('[email protected]', 'admin123');
  }

  async loginAsManager(): Promise<void> {
    await this.login('[email protected]', 'manager123');
  }

  async loginAsEditor(): Promise<void> {
    await this.login('[email protected]', 'editor123');
  }
}

User List Page Object:

// src/pages/user-list.page.ts
import { Page, Locator } from '@playwright/test';
import { BasePage } from './base.page';

export class UserListPage extends BasePage {
  readonly createUserButton: Locator;
  readonly searchInput: Locator;
  readonly userRows: Locator;
  readonly deleteButton: Locator;
  readonly editButton: Locator;

  constructor(page: Page) {
    super(page);
    this.createUserButton = page.locator('[data-testid="create-user-btn"]');
    this.searchInput = page.locator('[data-testid="user-search"]');
    this.userRows = page.locator('[data-testid="user-row"]');
    this.deleteButton = page.locator('[data-testid="delete-user-btn"]');
    this.editButton = page.locator('[data-testid="edit-user-btn"]');
  }

  async getUserRowByEmail(email: string): Promise<Locator> {
    return this.page.locator(`[data-testid="user-row"][data-email="${email}"]`);
  }

  async searchUser(query: string): Promise<void> {
    await this.searchInput.fill(query);
    await this.page.waitForTimeout(500); // Debounce
  }

  async clickCreateUser(): Promise<void> {
    await this.createUserButton.click();
  }
}

Test Suites Críticos

1. Authentication Tests (auth/login.spec.ts):

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/login.page';
import { DashboardPage } from '../../pages/dashboard.page';

test.describe('Authentication', () => {
  test('should login successfully with admin credentials', async ({ page }) => {
    const loginPage = new LoginPage(page);
    const dashboardPage = new DashboardPage(page);

    await page.goto('/login');
    await loginPage.loginAsAdmin();

    await expect(page).toHaveURL('/dashboard');
    await expect(dashboardPage.welcomeMessage).toContainText('Welcome, Admin');
  });

  test('should show error with invalid credentials', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await page.goto('/login');
    await loginPage.login('[email protected]', 'wrongpass');

    await expect(loginPage.errorMessage).toBeVisible();
    await expect(loginPage.errorMessage).toContainText('Invalid credentials');
  });

  test('should redirect to login when accessing protected route without auth', async ({ page }) => {
    await page.goto('/dashboard');
    await expect(page).toHaveURL('/login');
  });

  test('should logout successfully', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await page.goto('/login');
    await loginPage.loginAsAdmin();

    const userMenu = page.locator('[data-testid="user-menu"]');
    await userMenu.click();

    const logoutButton = page.locator('[data-testid="logout-btn"]');
    await logoutButton.click();

    await expect(page).toHaveURL('/login');
  });
});

2. Granular Permission Tests (authorization/granular-permissions.spec.ts):

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/login.page';
import { UserListPage } from '../../pages/user-list.page';

test.describe('Granular Permissions', () => {
  test('Admin should see all CRUD buttons', async ({ page }) => {
    const loginPage = new LoginPage(page);
    const userListPage = new UserListPage(page);

    await page.goto('/login');
    await loginPage.loginAsAdmin();
    await page.goto('/users');

    await expect(userListPage.createUserButton).toBeVisible();
    await expect(userListPage.editButton.first()).toBeVisible();
    await expect(userListPage.deleteButton.first()).toBeVisible();
  });

  test('Manager should see create and edit but not delete', async ({ page }) => {
    const loginPage = new LoginPage(page);
    const userListPage = new UserListPage(page);

    await page.goto('/login');
    await loginPage.loginAsManager();
    await page.goto('/users');

    await expect(userListPage.createUserButton).toBeVisible();
    await expect(userListPage.editButton.first()).toBeVisible();
    await expect(userListPage.deleteButton.first()).not.toBeVisible();
  });

  test('Editor should see edit but not create or delete', async ({ page }) => {
    const loginPage = new LoginPage(page);
    const userListPage = new UserListPage(page);

    await page.goto('/login');
    await loginPage.loginAsEditor();
    await page.goto('/users');

    await expect(userListPage.createUserButton).not.toBeVisible();
    await expect(userListPage.editButton.first()).toBeVisible();
    await expect(userListPage.deleteButton.first()).not.toBeVisible();
  });

  test('Viewer should only see read-only view', async ({ page }) => {
    const loginPage = new LoginPage(page);
    const userListPage = new UserListPage(page);

    await page.goto('/login');
    await loginPage.login('[email protected]', 'viewer123');
    await page.goto('/users');

    await expect(userListPage.createUserButton).not.toBeVisible();
    await expect(userListPage.editButton.first()).not.toBeVisible();
    await expect(userListPage.deleteButton.first()).not.toBeVisible();
  });

  test('should block API calls without proper permissions', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await page.goto('/login');
    await loginPage.loginAsEditor();

    // Intentar crear usuario sin permiso users:create
    const response = await page.request.post('/api/users', {
      data: { name: 'Test', email: '[email protected]' }
    });

    expect(response.status()).toBe(403); // Forbidden
  });
});

3. User CRUD Tests (user-management/user-create.spec.ts):

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/login.page';
import { UserListPage } from '../../pages/user-list.page';
import { UserFormPage } from '../../pages/user-form.page';

test.describe('User Creation', () => {
  test.beforeEach(async ({ page }) => {
    const loginPage = new LoginPage(page);
    await page.goto('/login');
    await loginPage.loginAsAdmin();
  });

  test('should create new user successfully', async ({ page }) => {
    const userListPage = new UserListPage(page);
    const userFormPage = new UserFormPage(page);

    await page.goto('/users');
    await userListPage.clickCreateUser();

    await userFormPage.fillForm({
      firstName: 'John',
      lastName: 'Doe',
      email: '[email protected]',
      role: 'editor'
    });

    await userFormPage.submitForm();

    await expect(page).toHaveURL('/users');
    await expect(page.locator('[data-testid="success-toast"]')).toBeVisible();
    await expect(userListPage.getUserRowByEmail('[email protected]')).toBeVisible();
  });

  test('should validate email format', async ({ page }) => {
    const userListPage = new UserListPage(page);
    const userFormPage = new UserFormPage(page);

    await page.goto('/users');
    await userListPage.clickCreateUser();

    await userFormPage.fillForm({
      firstName: 'John',
      lastName: 'Doe',
      email: 'invalid-email',
      role: 'editor'
    });

    await userFormPage.submitForm();

    await expect(userFormPage.emailError).toBeVisible();
    await expect(userFormPage.emailError).toContainText('Invalid email format');
  });

  test('should prevent duplicate emails', async ({ page }) => {
    const userListPage = new UserListPage(page);
    const userFormPage = new UserFormPage(page);

    await page.goto('/users');
    await userListPage.clickCreateUser();

    await userFormPage.fillForm({
      firstName: 'Existing',
      lastName: 'User',
      email: '[email protected]', // Email existente
      role: 'viewer'
    });

    await userFormPage.submitForm();

    await expect(page.locator('[data-testid="error-toast"]')).toBeVisible();
    await expect(page.locator('[data-testid="error-toast"]')).toContainText('Email already exists');
  });
});

4. Role-Based UI Tests (authorization/role-based-ui.spec.ts):

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/login.page';

test.describe('Role-Based UI Visibility', () => {
  const testRoleVisibility = async (
    page: any,
    role: 'admin' | 'manager' | 'editor',
    expectations: {
      dashboard: boolean;
      users: boolean;
      systemSettings: boolean;
    }
  ) => {
    const loginPage = new LoginPage(page);

    const credentials = {
      admin: { email: '[email protected]', password: 'admin123' },
      manager: { email: '[email protected]', password: 'manager123' },
      editor: { email: '[email protected]', password: 'editor123' }
    };

    await page.goto('/login');
    await loginPage.login(credentials[role].email, credentials[role].password);

    const sidebar = page.locator('[data-testid="sidebar"]');

    if (expectations.dashboard) {
      await expect(sidebar.locator('[data-testid="nav-dashboard"]')).toBeVisible();
    }

    if (expectations.users) {
      await expect(sidebar.locator('[data-testid="nav-users"]')).toBeVisible();
    } else {
      await expect(sidebar.locator('[data-testid="nav-users"]')).not.toBeVisible();
    }

    if (expectations.systemSettings) {
      await expect(sidebar.locator('[data-testid="nav-settings"]')).toBeVisible();
    } else {
      await expect(sidebar.locator('[data-testid="nav-settings"]')).not.toBeVisible();
    }
  };

  test('Admin should see all navigation items', async ({ page }) => {
    await testRoleVisibility(page, 'admin', {
      dashboard: true,
      users: true,
      systemSettings: true
    });
  });

  test('Manager should see dashboard and users', async ({ page }) => {
    await testRoleVisibility(page, 'manager', {
      dashboard: true,
      users: true,
      systemSettings: false
    });
  });

  test('Editor should see limited options', async ({ page }) => {
    await testRoleVisibility(page, 'editor', {
      dashboard: true,
      users: true,
      systemSettings: false
    });
  });
});

5. Integration Tests (integration/mock-api-integration.spec.ts):

import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/login.page';

test.describe('Mock API Integration', () => {
  test('should intercept and respond to API calls', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await page.goto('/login');
    await loginPage.loginAsAdmin();
    await page.goto('/users');

    const response = await page.waitForResponse('/api/users');
    expect(response.status()).toBe(200);

    const data = await response.json();
    expect(data).toHaveProperty('users');
    expect(Array.isArray(data.users)).toBe(true);
    expect(data.users.length).toBeGreaterThan(0);
  });

  test('should handle network errors gracefully', async ({ page }) => {
    await page.route('/api/users', route => route.abort('failed'));

    const loginPage = new LoginPage(page);
    await page.goto('/login');
    await loginPage.loginAsAdmin();
    await page.goto('/users');

    await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
    await expect(page.locator('[data-testid="error-message"]')).toContainText('Failed to load users');
  });
});

Test Fixtures

Auth Fixture (fixtures/auth.fixture.ts):

import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/login.page';

type AuthFixtures = {
  authenticatedAdmin: void;
  authenticatedManager: void;
  authenticatedEditor: void;
};

export const test = base.extend<AuthFixtures>({
  authenticatedAdmin: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await page.goto('/login');
    await loginPage.loginAsAdmin();
    await use();
  },

  authenticatedManager: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await page.goto('/login');
    await loginPage.loginAsManager();
    await use();
  },

  authenticatedEditor: async ({ page }, use) => {
    const loginPage = new LoginPage(page);
    await page.goto('/login');
    await loginPage.loginAsEditor();
    await use();
  }
});

export { expect } from '@playwright/test';

Uso de Fixtures:

import { test, expect } from '../fixtures/auth.fixture';

test.describe('Protected Routes', () => {
  test('should access users page as admin', async ({ page, authenticatedAdmin }) => {
    await page.goto('/users');
    await expect(page).toHaveURL('/users');
  });
});

Comandos E2E

Comandos Playwright:

# Instalar Playwright
npx playwright install

# Ejecutar todos los tests E2E
npx nx e2e gograb-admin-e2e

# Ejecutar tests específicos
npx nx e2e gograb-admin-e2e --spec=auth/login.spec.ts

# Ejecutar en modo UI (interactive)
npx nx e2e gograb-admin-e2e --ui

# Ejecutar en navegador específico
npx nx e2e gograb-admin-e2e --project=chromium

# Debug mode
npx nx e2e gograb-admin-e2e --debug

# Ver reporte HTML
npx playwright show-report dist/playwright-report/gograb-admin-e2e

Cobertura de Tests E2E

Flujos Críticos Cubiertos:

  1. ✅ Autenticación: Login, logout, session management
  2. ✅ Autorización: Permission guards, role-based UI, granular permissions
  3. ✅ User CRUD: Create, read, update, delete users
  4. ✅ Búsqueda y Filtrado: Search, filters, pagination
  5. ✅ Operaciones Masivas: Bulk selection, batch actions
  6. ✅ Navegación: Routing, guards, breadcrumbs
  7. ✅ Mock API: HTTP interceptors, error handling
  8. ✅ UI Responsiveness: Layout, components visibility
  9. ✅ Validaciones: Form validation, business rules
  10. ✅ Integration: Events system, logging, state management

Métricas de Calidad:

Routing Configuration

Arquitectura de Rutas con Module System

IMPORTANTE: Las rutas de módulos NO se definen directamente en app.routes.ts. En su lugar, cada módulo exporta sus propias rutas que son descubiertas y registradas dinámicamente por el ModuleIntegratorService.

Patrón de Exportación de Rutas en Módulos

Cada módulo exporta sus rutas con guards y permisos ya configurados:

// En @gograb-space/user-management/src/lib/user-management.routes.ts
import { Routes } from '@angular/router';
import { authGuard, permissionGuard, roleGuard } from '@libs-core/security';

export const userManagementRoutes: Routes = [
  {
    path: '',
    redirectTo: 'list',
    pathMatch: 'full',
  },
  {
    path: 'list',
    loadComponent: () => import('./components/user-list/user-list.component')
      .then((m) => m.UserListComponent),
    canActivate: [authGuard, permissionGuard('users:read')],
    data: {
      title: 'User List',
      breadcrumb: 'Users',
      permissions: ['users:read'],
    },
  },
  {
    path: 'create',
    loadComponent: () => import('./components/user-form/user-form.component')
      .then((m) => m.UserFormComponent),
    canActivate: [authGuard, permissionGuard('users:create')],
    data: {
      title: 'Create User',
      permissions: ['users:create'],
      mode: 'create',
    },
  },
  // ... más rutas del módulo
];

Main Routes (app.routes.ts)

import { Routes } from '@angular/router';
import { authGuard } from '@libs-core/security';
import { AppShellComponent } from './core/layout/app-shell.component';

export const routes: Routes = [
  // Public routes
  {
    path: 'login',
    loadComponent: () => import('./features/auth/login.component')
      .then(m => m.LoginComponent)
  },

  // Protected routes (require authentication)
  {
    path: '',
    component: AppShellComponent,
    canActivate: [authGuard],
    children: [
      {
        path: '',
        redirectTo: '/dashboard',
        pathMatch: 'full'
      },
      {
        path: 'dashboard',
        loadComponent: () => import('./features/dashboard/dashboard.component')
          .then(m => m.DashboardComponent),
        data: { title: 'Dashboard' }
      },
      // Las rutas de módulos se registran dinámicamente via ModuleIntegratorService
      // NO se definen aquí manualmente
    ]
  },

  // Fallback
  {
    path: '**',
    redirectTo: '/dashboard'
  }
];

Integración Dinámica de Rutas

El ModuleIntegratorService descubre y registra las rutas de módulos en runtime:

// En app.config.ts - El módulo se registra automáticamente
import { provideModuleIntegrator } from '@libs-core/module-integrator';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideModuleIntegrator({
      autoDiscovery: true,
      validateDependencies: true,
      enableLifecycleLogging: true
    }),
    // ... otros providers
  ]
};

Flujo de Registro de Rutas:

  1. Auto-registro: Módulos se auto-registran mediante @ModuleDecorator al iniciar
  2. Descubrimiento: ModuleIntegratorService descubre módulos registrados
  3. Configuración API: Backend devuelve configuración de módulos habilitados por tenant
  4. Activación: Módulos habilitados pasan por ciclo de vida (configure → initialize → start)
  5. Registro de Rutas: Rutas de módulos activos se integran dinámicamente al router
  6. Validación: Guards y permisos se validan en cada navegación

Application Configuration

app.config.ts (Complete)

import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { provideStore } from '@ngxs/store';

// Core libraries
import { authInterceptor, contextInterceptor } from '@libs-core/security';
import { mockHttpInterceptor, provideMockApi } from '@libs-core/mock-api';
import { provideLogger, LogLevel, ConsoleAppender } from '@libs-core/log-system';
import { provideEventBus } from '@libs-core/event-system';
import { provideModuleIntegrator } from '@libs-core/module-integrator';

// User management
import { UserState } from '@libs-core/user-management';
import { UserMockApi } from '@libs-core/user-management';

// App routes
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    // Router with input binding
    provideRouter(routes, withComponentInputBinding()),

    // Module Integrator - Descubrimiento y registro dinámico de módulos
    provideModuleIntegrator({
      autoDiscovery: true,
      validateDependencies: true,
      enableLifecycleLogging: true,
      tenantId: 'default-tenant'
    }),

    // HTTP Client with interceptors
    provideHttpClient(
      withInterceptors([
        authInterceptor,
        contextInterceptor,
        mockHttpInterceptor
      ])
    ),

    // NGXS State Management
    provideStore([UserState]),

    // Mock API
    provideMockApi([UserMockApi]),

    // Event System
    provideEventBus(),

    // Log System
    provideLogger({
      appenders: [ConsoleAppender],
      level: LogLevel.DEBUG,
      context: {
        appName: 'gograb-admin',
        tenantId: 'default-tenant'
      }
    })
  ]
};

Configuración del Module Integrator:

Ciclo de Vida del Módulo y Registro de Rutas

Implementación del Contrato de Módulo

Los módulos deben implementar BrowserModuleContract:

// En @gograb-space/user-management
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { BrowserModuleContract, ModuleManifest, ModuleDecorator } from '@libs-core/module-base';

@Injectable({ providedIn: 'root' })
@ModuleDecorator({
  id: 'user-management',
  version: '1.0.0',
  displayName: 'User Management',
  permissions: ['users:read', 'users:create', 'users:update', 'users:delete', 'users:manage']
})
export class UserManagementModule implements BrowserModuleContract {
  readonly manifest: ModuleManifest = {
    id: 'user-management',
    version: '1.0.0',
    displayName: 'User Management',
    permissions: ['users:read', 'users:create', 'users:update', 'users:delete', 'users:manage']
  };

  configure<T = Record<string, unknown>>(config: T): Observable<void> {
    // Configuración del módulo con config de API
    return of(void 0);
  }

  initialize(): Observable<void> {
    // Inicialización de servicios, state, etc.
    return of(void 0);
  }

  start(): Observable<void> {
    // Activación del módulo y registro de rutas
    // IntegratorService registra las rutas exportadas automáticamente
    return of(void 0);
  }

  stop(): Observable<void> {
    // Desactivación del módulo
    return of(void 0);
  }

  destroy(): Observable<void> {
    // Limpieza de recursos
    return of(void 0);
  }
}

Flujo Completo de Integración

┌──────────────────────────────────────────────────────────────┐
│ 1. APP BOOTSTRAP                                             │
│    - provideModuleIntegrator() se registra en app.config.ts │
│    - IntegratorService se inicializa                         │
└──────────────────────────────────────────────────────────────┘
                           ↓
┌──────────────────────────────────────────────────────────────┐
│ 2. AUTO-REGISTRO DE MÓDULOS                                 │
│    - @ModuleDecorator registra módulos automáticamente      │
│    - ModuleDiscoveryService descubre módulos disponibles    │
└──────────────────────────────────────────────────────────────┘
                           ↓
┌──────────────────────────────────────────────────────────────┐
│ 3. CONFIGURACIÓN API (Mock en desarrollo)                   │
│    - GET /api/tenants/default-tenant/modules/config         │
│    - Respuesta: { modules: [{ id: 'user-management',        │
│                                enabled: true, config: {...} }│
└──────────────────────────────────────────────────────────────┘
                           ↓
┌──────────────────────────────────────────────────────────────┐
│ 4. RESOLUCIÓN DE DEPENDENCIAS                               │
│    - DependencySolverService valida dependencias             │
│    - Ordena módulos según grafo de dependencias              │
└──────────────────────────────────────────────────────────────┘
                           ↓
┌──────────────────────────────────────────────────────────────┐
│ 5. CICLO DE VIDA DEL MÓDULO                                 │
│    - configure(config) - Aplica configuración de API        │
│    - initialize() - Inicializa servicios y state            │
│    - start() - Activa módulo y registra rutas               │
└──────────────────────────────────────────────────────────────┘
                           ↓
┌──────────────────────────────────────────────────────────────┐
│ 6. REGISTRO DINÁMICO DE RUTAS                               │
│    - Router.resetConfig() con rutas base + rutas de módulos │
│    - Guards aplicados (authGuard, permissionGuard)          │
│    - Navegación disponible en /users/*                      │
└──────────────────────────────────────────────────────────────┘

Validación de Permisos en Rutas

Cada ruta de módulo debe especificar permisos requeridos:

{
  path: 'list',
  loadComponent: () => import('./components/user-list.component')
    .then(m => m.UserListComponent),
  canActivate: [authGuard, permissionGuard('users:read')],
  data: {
    permissions: ['users:read'],
    title: 'User List'
  }
}

El permissionGuard valida contra los permisos del usuario actual:

Requisitos Técnicos

Framework y Librerías

Principios de Desarrollo

Code Quality Standards

Nx Configuration

project.json

{
  "name": "gograb-admin",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "projectType": "application",
  "prefix": "app",
  "sourceRoot": "apps/gograb-admin/src",
  "tags": ["type:app", "scope:admin"],
  "targets": {
    "build": {
      "executor": "@angular/build:application",
      "outputs": ["{options.outputPath}"],
      "options": {
        "outputPath": "dist/apps/gograb-admin",
        "browser": "apps/gograb-admin/src/main.ts",
        "polyfills": ["zone.js"],
        "tsConfig": "apps/gograb-admin/tsconfig.app.json",
        "inlineStyleLanguage": "scss",
        "assets": [
          {
            "glob": "**/*",
            "input": "apps/gograb-admin/public"
          }
        ],
        "styles": ["apps/gograb-admin/src/styles.scss"]
      },
      "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": {
      "executor": "@angular/build:dev-server",
      "configurations": {
        "production": {
          "buildTarget": "gograb-admin:build:production"
        },
        "development": {
          "buildTarget": "gograb-admin:build:development"
        }
      },
      "defaultConfiguration": "development",
      "options": {
        "port": 5100
      }
    },
    "extract-i18n": {
      "executor": "@angular/build:extract-i18n",
      "options": {
        "buildTarget": "gograb-admin:build"
      }
    },
    "lint": {
      "executor": "@nx/eslint:lint"
    },
    "test": {
      "executor": "@nx/vite:test",
      "outputs": ["{options.reportsDirectory}"],
      "options": {
        "reportsDirectory": "../../coverage/apps/gograb-admin"
      }
    }
  }
}

Comandos de Desarrollo

Comandos Principales

# Generar la aplicación gograb-admin
npx nx g @nx/angular:app gograb-admin --directory=apps/gograb-admin --standalone --routing --style=scss

# Servir la aplicación (puerto 5100)
npx nx serve gograb-admin

# Build para producción
npx nx build gograb-admin

# Ejecutar tests
npx nx test gograb-admin

# Linting
npx nx lint gograb-admin

# Ver detalles del proyecto
npx nx show project gograb-admin

# Visualizar el grafo de dependencias
npx nx graph

Comandos de Generación de Componentes

# Generar componente en core/layout
npx nx g @nx/angular:component core/layout/app-shell --project=gograb-admin --standalone --changeDetection=OnPush

npx nx g @nx/angular:component core/layout/header --project=gograb-admin --standalone --changeDetection=OnPush

npx nx g @nx/angular:component core/layout/sidebar --project=gograb-admin --standalone --changeDetection=OnPush

npx nx g @nx/angular:component core/layout/footer --project=gograb-admin --standalone --changeDetection=OnPush

# Generar dashboard
npx nx g @nx/angular:component features/dashboard/dashboard --project=gograb-admin --standalone --changeDetection=OnPush

# Generar login
npx nx g @nx/angular:component features/auth/login --project=gograb-admin --standalone --changeDetection=OnPush

# Generar servicio de navegación
npx nx g @nx/angular:service core/services/navigation --project=gograb-admin

Comandos de Testing E2E

# Instalar Playwright browsers
npx playwright install

# Ejecutar todos los tests E2E
npx nx e2e gograb-admin-e2e

# Ejecutar tests específicos por suite
npx nx e2e gograb-admin-e2e --spec=auth/login.spec.ts
npx nx e2e gograb-admin-e2e --spec=authorization/granular-permissions.spec.ts
npx nx e2e gograb-admin-e2e --spec=user-management/user-create.spec.ts

# Ejecutar en modo UI (interactivo)
npx nx e2e gograb-admin-e2e --ui

# Ejecutar en navegador específico
npx nx e2e gograb-admin-e2e --project=chromium
npx nx e2e gograb-admin-e2e --project=firefox
npx nx e2e gograb-admin-e2e --project=webkit

# Debug mode (con inspector)
npx nx e2e gograb-admin-e2e --debug

# Ejecutar con headed browser (ver navegador)
npx nx e2e gograb-admin-e2e --headed

# Ver reporte HTML de tests
npx playwright show-report dist/playwright-report/gograb-admin-e2e

# Generar nuevo test E2E
cd apps/gograb-admin-e2e/src/tests
# Crear manualmente siguiendo la estructura de Page Object Model

Criterios de Aceptación

Funcionales

  1. ✅ La aplicación debe servir correctamente en puerto 5100
  2. ✅ Login funcional con credenciales mock (admin, manager, editor, viewer)
  3. ✅ Dashboard muestra métricas del sistema simuladas
  4. ✅ Navegación a User Management funciona correctamente
  5. ✅ CRUD de usuarios operativo (heredado de user-management library)
  6. ✅ Guards de autenticación bloquean acceso sin login
  7. ✅ Guards de permisos granulares bloquean accesos no autorizados
  8. ✅ Directivas de seguridad ocultan/muestran elementos según permisos específicos
  9. ✅ Mock API intercepta todas las peticiones HTTP y responde correctamente
  10. ✅ Logging se registra en consola para todas las operaciones
  11. ✅ Eventos del sistema se publican y reciben correctamente
  12. ✅ Validación de permisos funciona en rutas, UI y service layer

Técnicos

  1. ✅ Código sigue principios SOLID
  2. ✅ 100% standalone components (no NgModules)
  3. ✅ OnPush change detection en todos los componentes
  4. ✅ Sin uso de any type (usar tipos estrictos)
  5. ✅ RxJS para todas las operaciones asíncronas (no Promises)
  6. ✅ Sin setTimeout (excepto en tests)
  7. ✅ TypeScript strict mode habilitado
  8. ✅ Integración correcta con todas las librerías core
  9. ✅ Permission entities de @libs-core/shared-models usadas consistentemente
  10. ✅ Role entities con arrays de Permission[] correctamente implementados

Performance

  1. ✅ Carga inicial < 3 segundos
  2. ✅ Navegación entre rutas < 500ms
  3. ✅ Respuesta de mock-api < 200ms
  4. ✅ Sin memory leaks detectables
  5. ✅ Bundle size dentro de los límites configurados

Testing E2E

  1. ✅ Suite E2E ejecuta sin errores en Chromium, Firefox y WebKit
  2. ✅ Tests de autenticación cubren login, logout, guards
  3. ✅ Tests de autorización cubren permisos granulares por rol (admin, manager, editor, viewer)
  4. ✅ Tests de CRUD cubren todas las operaciones de usuario
  5. ✅ Tests de UI validan visibilidad de elementos según permisos
  6. ✅ Tests de integración validan mock-api, eventos y logging
  7. ✅ Tests de navegación validan routing y breadcrumbs
  8. ✅ Tests de búsqueda y filtrado validan funcionalidad de búsqueda
  9. ✅ Tests de operaciones masivas validan bulk actions
  10. ✅ Cobertura de flujos críticos > 90%
  11. ✅ Cobertura de permisos: 100% combinaciones role/permission testeadas
  12. ✅ Tasa de flakiness < 1%
  13. ✅ Tiempo de ejecución suite completa < 5 minutos
  14. ✅ Screenshots y videos capturados en fallos
  15. ✅ Reportes HTML generados correctamente

Integraciones con Librerías Core

Resumen de Dependencias

Librería Uso en gograb-admin
@libs-core/security Auth, guards, interceptors, directivas RBAC
@gograb-space/user-management Módulo completo de gestión de usuarios
@libs-core/shared-models Tipos compartidos (User, Role, Permission, Context)
@libs-core/ui-framework Componentes UI (buttons, forms, cards, modals)
@libs-core/log-system Logging multi-tenant con decorators
@libs-core/event-system Bus de eventos para comunicación entre componentes
@libs-core/mock-api Interceptor HTTP para simular APIs
@gograb-space/utilities Utilidades (DateUtils, ValidationUtils, StringUtils)
@libs-core/module-integrator (Futuro) Carga dinámica de módulos

Estructura de Estado (NGXS)

User State (heredado de user-management)

export interface UserStateModel {
  users: User[];
  selectedUser: User | null;
  loading: boolean;
  error: string | null;
  filters: UserFilters;
  pagination: {
    page: number;
    pageSize: number;
    total: number;
  };
}

Actions Disponibles

// User actions from @gograb-space/user-management
export namespace UserActions {
  export class LoadUsers { static readonly type = '[Users] Load'; }
  export class LoadUserById { static readonly type = '[Users] Load By ID'; constructor(public id: string) {} }
  export class CreateUser { static readonly type = '[Users] Create'; constructor(public user: User) {} }
  export class UpdateUser { static readonly type = '[Users] Update'; constructor(public id: string, public user: Partial<User>) {} }
  export class DeleteUser { static readonly type = '[Users] Delete'; constructor(public id: string) {} }
  export class SetFilters { static readonly type = '[Users] Set Filters'; constructor(public filters: UserFilters) {} }
}

Selectors Disponibles

// User selectors from @gograb-space/user-management
export class UserSelectors {
  @Selector([UserState]) static getUsers(state: UserStateModel): User[] { return state.users; }
  @Selector([UserState]) static getSelectedUser(state: UserStateModel): User | null { return state.selectedUser; }
  @Selector([UserState]) static isLoading(state: UserStateModel): boolean { return state.loading; }
  @Selector([UserState]) static getError(state: UserStateModel): string | null { return state.error; }
}

Datos Mock

Usuarios Mock (20 usuarios de ejemplo)

Los datos mock se proveen automáticamente desde @gograb-space/user-management/mocks:

Estados distribuidos:

Métricas Mock para Dashboard

export interface SystemMetrics {
  totalUsers: number;
  activeUsers: number;
  loadedModules: string[];
  systemHealth: 'good' | 'warning' | 'critical';
  memoryUsage: number;
  lastUpdate: string;
}

// Mock data
{
  totalUsers: 20,
  activeUsers: 15,
  loadedModules: ['user-management'],
  systemHealth: 'good',
  memoryUsage: 45,
  lastUpdate: new Date().toISOString()
}

Estimación de Desarrollo

Tarea Estimación
Generación de app con Nx 0.5 día
Layout components (shell, header, sidebar, footer) 1 día
Dashboard component 0.5 día
Login component y auth flow 1 día
Routing configuration 0.5 día
app.config.ts setup (providers con permisos) 1.5 días
Integración user-management 0.5 día
Mock API configuration (auth + permisos) 1 día
Implementación permisos granulares 1 día
Guards y directivas con permisos 1 día
Styling y refinamiento UI (data-testid) 1 día
Subtotal Implementación 9.5 días
Testing E2E
Setup Playwright project 0.5 día
Page Object Models (5 pages) 1 día
Test fixtures (auth, permissions, users) 0.5 día
Auth tests (4 test suites) 1 día
Authorization tests (3 test suites) 1.5 días
User CRUD tests (6 test suites) 2 días
Navigation tests (3 test suites) 1 día
Integration tests (3 test suites) 1 día
Debugging y estabilización tests 1 día
Subtotal Testing E2E 9.5 días
Documentación 0.5 día
Total 19-20 días

Riesgos y Mitigaciones

Riesgo 1: Incompatibilidad entre versiones de librerías

Probabilidad: Baja Impacto: Alto Mitigación: Todas las librerías core ya están en el workspace con versiones compatibles. Usar npx nx show project <lib> para validar dependencias.

Riesgo 2: Complejidad en la configuración de providers

Probabilidad: Media Impacto: Medio Mitigación: Seguir el patrón de app.config.ts documentado. Revisar ejemplos de gograb-space existente.

Riesgo 3: Performance en carga de user-management

Probabilidad: Baja Impacto: Bajo Mitigación: Lazy loading ya implementado en routing. OnPush change detection optimiza renders.

Riesgo 4: Confusión con multi-tenant (solo single-tenant)

Probabilidad: Media Impacto: Bajo Mitigación: Documentar claramente que solo se usa default-tenant. Configurar ContextService con tenant fijo.

Riesgo 5: Flakiness en tests E2E

Probabilidad: Media Impacto: Medio Mitigación:

Riesgo 6: Complejidad en permisos granulares

Probabilidad: Media Impacto: Medio Mitigación:

Entregables

Código Fuente

  1. Aplicación Angular 20 completa en /apps/gograb-admin
  2. Componentes de layout (header, sidebar, footer, app-shell)
  3. Dashboard component con métricas mock
  4. Login component con autenticación mock
  5. Routing configuration con lazy loading
  6. app.config.ts con todos los providers configurados

Configuración

  1. project.json para Nx
  2. tsconfig.app.json y tsconfig.spec.json
  3. vite.config.ts para testing
  4. Mock API definitions (auth, metrics)

Documentación

  1. Este documento de requerimientos
  2. README.md en /apps/gograb-admin con instrucciones de uso
  3. Comentarios en código donde sea necesario

Conclusión

GoGrab Admin es la aplicación administrativa foundation del ecosistema GoGrab Space. Integra todas las librerías core existentes, proporcionando una base sólida para la gestión de usuarios con:

Características Distintivas

  1. Sistema de Permisos Granulares: Control fino por recurso:acción (ej. users:create, users:delete, system:configure)
  2. Validación Multi-Capa: Guards en routing, directivas en templates, validación en services
  3. Testing E2E Comprehensivo: 100% de combinaciones role/permission testeadas en navegadores reales
  4. Page Object Models: Tests mantenibles y reutilizables con abstracción robusta
  5. Mock API Completo: Simulación realista de backend con interceptores HTTP

Extensibilidad

La aplicación está diseñada para ser extensible, permitiendo la adición futura de más módulos administrativos (Product Catalog, Analytics, Settings) siguiendo el mismo patrón de integración establecido con User Management.

Patrones Reutilizables:

Next Steps para Implementación

  1. Setup Inicial (1 día):

    • Generar aplicación con Nx CLI
    • Generar proyecto E2E con Playwright
    • Configurar estructura de directorios
  2. Desarrollo Core (5-6 días):

    • Implementar layout components con data-testid
    • Crear dashboard con métricas simuladas
    • Desarrollar login y auth flow
    • Configurar routing con guards de permisos
    • Setup app.config.ts con providers
  3. Integración Permisos (3 días):

    • Implementar permisos granulares en guards
    • Configurar directivas de seguridad en templates
    • Validación en service layer
    • Mock API con datos de permisos
  4. Testing E2E (9-10 días):

    • Setup Playwright y Page Object Models
    • Implementar test suites (auth, authorization, CRUD, navigation)
    • Validar 100% combinaciones role/permission
    • Estabilizar tests y reducir flakiness
  5. Refinamiento y Documentación (1 día):

    • Ajustes de UI y UX
    • Documentación de uso
    • README con instrucciones

Tiempo Total Estimado: 19-20 días de desarrollo

Métricas de Éxito