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
- Implementar aplicación Angular 20 standalone en
/apps/gograb-admin - Integrar módulo de gestión de usuarios (
@gograb-space/user-management) - Configurar autenticación y autorización con
@libs-core/security - Implementar sistema de logging multi-tenant con
@libs-core/log-system - Utilizar sistema de eventos con
@libs-core/event-system - Aplicar componentes UI de
@libs-core/ui-framework - Simular APIs con
@libs-core/mock-api - Aplicar utilidades compartidas de
@gograb-space/utilities
Alcance del Proyecto
En Alcance
- Aplicación base: Estructura Angular 20 en
/apps/gograb-admin - Layout principal: Header, Sidebar, Content Area, Footer
- Dashboard: Pantalla principal con acceso rápido a módulos
- Gestión de usuarios: Integración completa con
@gograb-space/user-management - Autenticación: Login/logout simulado con JWT mock
- Autorización: Control de acceso basado en permisos granulares Y roles usando
Permissionentities de@libs-core/shared-models - Routing: Navegación modular con lazy loading
- Mock API: Interceptor HTTP con datos simulados
- Logging: Sistema de logs multi-tenant integrado
- Eventos: Comunicación entre componentes mediante eventos del sistema
- Testing E2E: Suite completa de tests E2E con Playwright para validar flujos críticos
Fuera de Alcance
- Backend real (todo simulado con mock-api)
- Múltiples tenants (solo single-tenant:
default-tenant) - Internacionalización (i18n)
- PWA features
- Server-side rendering
- Módulos adicionales (Product Catalog, Analytics) - solo User Management
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:
- Header con logo, usuario actual, notificaciones, menú de usuario
- Sidebar con navegación principal (Dashboard, Users, Settings)
- Content area con
<router-outlet>para contenido dinámico - Footer con información del sistema y versión
Integración:
- Uso de componentes de
@libs-core/ui-framework - Signals para estado reactivo del layout (sidebar collapsed, theme)
- OnPush change detection strategy
Header Component
Funcionalidades:
- Logo de la aplicación
- Breadcrumbs dinámicos basados en routing
- Notificaciones badge (simulado)
- User menu dropdown con opciones:
- Profile
- Settings
- Logout
- Theme toggle (light/dark) usando Signals
Sidebar Component
Funcionalidades:
- Navegación principal con íconos
- Indicador de ruta activa
- Collapsible con persistencia en sessionStorage
- Lista de módulos disponibles:
- Dashboard
- User Management
- Settings (placeholder)
- Control de visibilidad basado en permisos usando directivas
*hasPermission
Footer Component
Funcionalidades:
- Información de versión de la app
- Copyright
- Links útiles (Help, About, Terms)
2. Dashboard Principal
Descripción: Pantalla inicial con resumen del sistema y accesos rápidos.
Elementos:
- Cards de métricas del sistema:
- Total de usuarios
- Usuarios activos
- Módulos cargados
- Estado del sistema
- Quick actions:
- Botón "Manage Users" → navega a
/users - Botón "System Settings" → placeholder
- Botón "Manage Users" → navega a
- Lista de actividad reciente (simulada)
- Widget de módulos disponibles
Integración:
- Uso de
@libs-core/log-systempara logging de navegación - Emisión de eventos con
@libs-core/event-systemen acciones - Datos mock de métricas usando
@libs-core/mock-api
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:
- Visualización de usuarios (lista, cards, detalle)
- CRUD completo de usuarios (crear, editar, eliminar)
- Gestión de estados (activo, inactivo, suspendido, pendiente)
- Búsqueda y filtrado (server-side y client-side)
- Operaciones masivas (selección múltiple, batch actions)
- Gestión de roles (Admin, Manager, Editor)
- Control de acceso basado en permisos usando
@libs-core/security
Mock Data:
- 20-30 usuarios mock con datos realistas
- Roles: Admin, Manager, Editor, Viewer
- Estados: Active, Inactive, Suspended, Pending
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:
- Token generado en cliente con estructura real (header.payload.signature)
- Payload contiene: userId, email, role, tenantId, exp
- Token almacenado en localStorage vía
TokenService - Auto-refresh simulado después de 15 minutos
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:
- Permisos Granulares: Control fino usando
Permissionentities conresource:actionpattern - Roles como Agrupadores:
Roleentities agrupan conjuntos de permisos relacionados - Validación en Múltiples Capas:
- Guards en rutas (navigation-level)
- Directivas en templates (UI-level)
- Services para operaciones (business-level)
- Performance: Cache de permisos en PermissionService con expiración automática
- Multi-tenant Aware: Todos los permisos incluyen
tenantIdpara aislamiento
Ventajas:
- ✅ Control de acceso fino y granular por operación específica
- ✅ Flexibilidad para crear roles personalizados combinando permisos
- ✅ Validación consistente en toda la aplicación
- ✅ Fácil extensión con nuevos permisos sin cambiar código
- ✅ Audit trail mediante logging de verificaciones de permisos
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:
- User login/logout
- Navigation entre rutas
- CRUD operations en usuarios
- Errores HTTP
- Eventos del sistema
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:
- ✅ Autenticación: Login, logout, session management
- ✅ Autorización: Permission guards, role-based UI, granular permissions
- ✅ User CRUD: Create, read, update, delete users
- ✅ Búsqueda y Filtrado: Search, filters, pagination
- ✅ Operaciones Masivas: Bulk selection, batch actions
- ✅ Navegación: Routing, guards, breadcrumbs
- ✅ Mock API: HTTP interceptors, error handling
- ✅ UI Responsiveness: Layout, components visibility
- ✅ Validaciones: Form validation, business rules
- ✅ Integration: Events system, logging, state management
Métricas de Calidad:
- Cobertura de Flujos: >90% de user journeys críticos
- Cobertura de Permisos: 100% de combinaciones role/permission testeadas
- Browser Coverage: Chromium, Firefox, WebKit
- Performance: < 5 minutos ejecución completa suite
- Flakiness: < 1% tests flaky
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:
- Auto-registro: Módulos se auto-registran mediante
@ModuleDecoratoral iniciar - Descubrimiento:
ModuleIntegratorServicedescubre módulos registrados - Configuración API: Backend devuelve configuración de módulos habilitados por tenant
- Activación: Módulos habilitados pasan por ciclo de vida (configure → initialize → start)
- Registro de Rutas: Rutas de módulos activos se integran dinámicamente al router
- 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:
- autoDiscovery: Habilita el descubrimiento automático de módulos mediante
@ModuleDecorator - validateDependencies: Valida dependencias entre módulos antes de activación
- enableLifecycleLogging: Registra eventos del ciclo de vida para debugging
- tenantId: Tenant por defecto para single-tenant deployment
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:
- Si el usuario tiene el permiso → permite navegación
- Si el usuario NO tiene el permiso → redirige a página sin permisos
Requisitos Técnicos
Framework y Librerías
- Angular: 20.x con standalone components únicamente
- Nx: 21.x para monorepo management
- TypeScript: 5.8 con strict mode
- RxJS: 7.x para operaciones asíncronas (prohibido Promises)
- NGXS: State management (integrado en user-management)
- SCSS: Para estilos con variables y mixins
- Vitest: Unit testing framework
Principios de Desarrollo
- SOLID: Aplicación estricta de principios SOLID
- DRY: Máxima reutilización de código
- Standalone Components: 100% standalone (no NgModules)
- OnPush Change Detection: Estrategia obligatoria en todos los componentes
- Signals: Preferencia sobre Observables para estado reactivo
- RxJS: Obligatorio para async operations (no Promises)
- No Timeouts: Prohibido
setTimeoutexcepto en tests
Code Quality Standards
- No
anytype: Usar tipos estrictos oSafeAnysi es absolutamente necesario - Kebab-case: Para nombres de archivos y componentes
- Strict TypeScript: Configuración estricta habilitada
- ESLint: Reglas de Angular aplicadas
- Testing: 90%+ coverage en funcionalidades críticas (si se requieren tests)
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
- ✅ La aplicación debe servir correctamente en puerto 5100
- ✅ Login funcional con credenciales mock (admin, manager, editor, viewer)
- ✅ Dashboard muestra métricas del sistema simuladas
- ✅ Navegación a User Management funciona correctamente
- ✅ CRUD de usuarios operativo (heredado de user-management library)
- ✅ Guards de autenticación bloquean acceso sin login
- ✅ Guards de permisos granulares bloquean accesos no autorizados
- ✅ Directivas de seguridad ocultan/muestran elementos según permisos específicos
- ✅ Mock API intercepta todas las peticiones HTTP y responde correctamente
- ✅ Logging se registra en consola para todas las operaciones
- ✅ Eventos del sistema se publican y reciben correctamente
- ✅ Validación de permisos funciona en rutas, UI y service layer
Técnicos
- ✅ Código sigue principios SOLID
- ✅ 100% standalone components (no NgModules)
- ✅ OnPush change detection en todos los componentes
- ✅ Sin uso de
anytype (usar tipos estrictos) - ✅ RxJS para todas las operaciones asíncronas (no Promises)
- ✅ Sin
setTimeout(excepto en tests) - ✅ TypeScript strict mode habilitado
- ✅ Integración correcta con todas las librerías core
- ✅ Permission entities de @libs-core/shared-models usadas consistentemente
- ✅ Role entities con arrays de Permission[] correctamente implementados
Performance
- ✅ Carga inicial < 3 segundos
- ✅ Navegación entre rutas < 500ms
- ✅ Respuesta de mock-api < 200ms
- ✅ Sin memory leaks detectables
- ✅ Bundle size dentro de los límites configurados
Testing E2E
- ✅ Suite E2E ejecuta sin errores en Chromium, Firefox y WebKit
- ✅ Tests de autenticación cubren login, logout, guards
- ✅ Tests de autorización cubren permisos granulares por rol (admin, manager, editor, viewer)
- ✅ Tests de CRUD cubren todas las operaciones de usuario
- ✅ Tests de UI validan visibilidad de elementos según permisos
- ✅ Tests de integración validan mock-api, eventos y logging
- ✅ Tests de navegación validan routing y breadcrumbs
- ✅ Tests de búsqueda y filtrado validan funcionalidad de búsqueda
- ✅ Tests de operaciones masivas validan bulk actions
- ✅ Cobertura de flujos críticos > 90%
- ✅ Cobertura de permisos: 100% combinaciones role/permission testeadas
- ✅ Tasa de flakiness < 1%
- ✅ Tiempo de ejecución suite completa < 5 minutos
- ✅ Screenshots y videos capturados en fallos
- ✅ 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:
- 5 Admin users
- 7 Manager users
- 6 Editor users
- 2 Viewer users
Estados distribuidos:
- 15 Active
- 3 Inactive
- 1 Suspended
- 1 Pending
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:
- Usar Page Object Models para abstracción robusta
- Implementar waits explícitos en lugar de timeouts fijos
- Usar fixtures para setup consistente de auth
- Retry strategy configurado en Playwright (2 retries en CI)
- Screenshots y videos para debugging de fallos
Riesgo 6: Complejidad en permisos granulares
Probabilidad: Media Impacto: Medio Mitigación:
- Definir matriz clara de permisos por rol desde el inicio
- Mock data de permisos bien estructurado y versionado
- Tests E2E que validan 100% de combinaciones role/permission
- Documentación exhaustiva de Permission entities y uso
Entregables
Código Fuente
- Aplicación Angular 20 completa en
/apps/gograb-admin - Componentes de layout (header, sidebar, footer, app-shell)
- Dashboard component con métricas mock
- Login component con autenticación mock
- Routing configuration con lazy loading
- app.config.ts con todos los providers configurados
Configuración
- project.json para Nx
- tsconfig.app.json y tsconfig.spec.json
- vite.config.ts para testing
- Mock API definitions (auth, metrics)
Documentación
- Este documento de requerimientos
- README.md en
/apps/gograb-admincon instrucciones de uso - 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:
- Autenticación JWT simulada con mock-api
- Autorización basada en permisos granulares usando
Permissionentities de@libs-core/shared-models - Control de acceso fino con validación en rutas, UI y service layer
- Roles estructurados (Admin, Manager, Editor, Viewer) con permisos específicos
- Logging multi-tenant integrado en todas las operaciones
- Comunicación basada en eventos del sistema
- Suite completa de tests E2E con Playwright validando >90% de flujos críticos
Características Distintivas
- Sistema de Permisos Granulares: Control fino por recurso:acción (ej.
users:create,users:delete,system:configure) - Validación Multi-Capa: Guards en routing, directivas en templates, validación en services
- Testing E2E Comprehensivo: 100% de combinaciones role/permission testeadas en navegadores reales
- Page Object Models: Tests mantenibles y reutilizables con abstracción robusta
- 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:
- Sistema de permisos granulares extensible a nuevos recursos
- Estructura de tests E2E replicable para nuevos módulos
- Page Object Models como plantilla para nuevas funcionalidades
- Mock API pattern para simular nuevos endpoints
Next Steps para Implementación
Setup Inicial (1 día):
- Generar aplicación con Nx CLI
- Generar proyecto E2E con Playwright
- Configurar estructura de directorios
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
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
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
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
- ✅ Aplicación funcional en puerto 5100
- ✅ Login con 4 roles diferentes (admin, manager, editor, viewer)
- ✅ Permisos granulares funcionando en 3 capas (routing, UI, services)
- ✅ Suite E2E ejecutando en 3 navegadores (Chromium, Firefox, WebKit)
- ✅ Cobertura de flujos críticos >90%
- ✅ Cobertura de permisos 100%
- ✅ Tasa de flakiness <1%
- ✅ Tiempo de ejecución E2E <5 minutos
- ✅ Bundle size dentro de límites (<1MB)