5 bloques de contenido: fundamentos BDD, proyecto base, Cypress, Login con roles + CRUD, y seguridad anti-inyección SQL. Con ejemplos reales, código y rúbricas de evaluación.
Conceptos, código, comandos y escenarios BDD de cada bloque
Behavior Driven Development es una metodología orientada al comportamiento del sistema desde la perspectiva del usuario. Se basa en:
| TDD | BDD |
|---|---|
| Se enfoca en pruebas técnicas | Se enfoca en comportamiento del negocio |
| Orientado al desarrollador | Orientado a negocio + desarrollador |
| Pruebas unitarias | Escenarios funcionales |
Feature: Gestión de usuarios Scenario: Crear usuario válido Given el usuario ingresa datos válidos When presiona guardar Then el sistema crea el usuario exitosamente Scenario: Login exitoso Given el usuario está autenticado When hace clic en "Guardar" Then el sistema persiste la información correctamente
src/ ├── main.ts ← bootstrapApplication() ├── app.component.ts ← componente raíz standalone └── app.routes.ts ← definición de rutas
El archivo del Bloque 2 no pudo ser procesado para extracción de texto (these documents can only be used in code execution). A continuación se presenta el contenido estándar de este bloque basado en el contexto del curso.
# Instalar Angular CLI npm install -g @angular/cli # Crear nuevo proyecto ng new mi-app-bdd # Ingresar al proyecto cd mi-app-bdd # Ejecutar servidor de desarrollo ng serve
# Componente standalone ng generate component login --standalone ng generate component dashboard --standalone ng generate component usuarios --standalone # Servicio ng generate service auth
import { Component, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; @Component({ selector: 'app-root', standalone: true, imports: [CommonModule, FormsModule], template: `{{ titulo() }}
` }) export class AppComponent { titulo = signal('Angular 20 BDD'); }
import { Routes } from '@angular/router'; import { LoginComponent } from './login/login.component'; import { DashboardComponent } from './dashboard/dashboard.component'; export const routes: Routes = [ { path: '', component: LoginComponent }, { path: 'dashboard', component: DashboardComponent }, ];
# Instalar Cypress como dependencia de desarrollo npm install cypress --save-dev # Abrir interfaz gráfica de Cypress npx cypress open
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cypress run"
}
}
cypress/
└── e2e/
└── login.cy.ts ← pruebas E2E
Feature: Autenticación Scenario: Login exitoso Given el usuario abre la página When ingresa credenciales válidas Then visualiza el dashboard
describe('Login Feature', () => { it('Usuario válido inicia sesión', () => { cy.visit('http://localhost:4200'); cy.get('input[type=email]').type('admin@test.com'); cy.get('input[type=password]').type('1234'); cy.contains('Ingresar').click(); cy.contains('Bienvenido al sistema').should('be.visible'); }); });
Redactar el comportamiento esperado en lenguaje natural
Crear el componente y servicio que satisface el escenario
Ejecutar la prueba E2E que verifica el comportamiento real
El archivo del Bloque 4 no pudo ser procesado para extracción de texto (these documents can only be used in code execution). Se presenta el contenido completo basado en el contexto del curso.
Feature: Sistema de Login con Roles Scenario: Login exitoso como administrador Given el usuario está en la página de login When ingresa email "admin@test.com" y password "Admin123!" And hace clic en el botón "Ingresar" Then debe ver el dashboard de administrador And debe ver el menú de gestión de usuarios Scenario: Login fallido con credenciales incorrectas Given el usuario está en la página de login When ingresa email "user@test.com" y password "wrong" Then debe ver el mensaje "Credenciales inválidas" Scenario: Crear usuario (solo admin) Given el admin está autenticado When completa el formulario de nuevo usuario And hace clic en "Guardar" Then el usuario aparece en la lista
import { Injectable, signal } from '@angular/core'; interface User { email: string; role: 'admin' | 'user'; } @Injectable({ providedIn: 'root' }) export class AuthService { private users: User[] = [ { email: 'admin@test.com', role: 'admin' }, { email: 'user@test.com', role: 'user' }, ]; currentUser = signal<User | null>(null); login(email: string, password: string): boolean { const user = this.users.find(u => u.email === email); if (user && password === '1234') { this.currentUser.set(user); return true; } return false; } logout() { this.currentUser.set(null); } isAdmin(): boolean { return this.currentUser()?.role === 'admin'; } }
import { Component, signal, inject } from '@angular/core'; import { Router } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { AuthService } from '../auth.service'; @Component({ selector: 'app-login', standalone: true, imports: [FormsModule], template: `{{ message() }}
` }) export class LoginComponent { private auth = inject(AuthService); private router = inject(Router); email = signal(''); password = signal(''); message = signal(''); login() { const ok = this.auth.login(this.email(), this.password()); if (ok) { this.router.navigate(['/dashboard']); } else { this.message.set('Credenciales inválidas'); } } }
import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { AuthService } from './auth.service'; export const authGuard: CanActivateFn = () => { const auth = inject(AuthService); const router = inject(Router); if (auth.currentUser()) return true; return router.createUrlTree(['/']); }; export const adminGuard: CanActivateFn = () => { const auth = inject(AuthService); const router = inject(Router); if (auth.isAdmin()) return true; return router.createUrlTree(['/dashboard']); };
describe('Login con Roles', () => { it('admin accede al dashboard', () => { cy.visit('/login'); cy.get('[data-cy=email]').type('admin@test.com'); cy.get('[data-cy=password]').type('Admin123!'); cy.get('[data-cy=submit]').click(); cy.url().should('include', '/dashboard'); cy.get('[data-cy=admin-menu]').should('be.visible'); }); it('credenciales incorrectas muestran error', () => { cy.visit('/login'); cy.get('[data-cy=email]').type('user@test.com'); cy.get('[data-cy=password]').type('wrong'); cy.get('[data-cy=submit]').click(); cy.contains('Credenciales inválidas').should('be.visible'); }); it('usuario sin rol admin no ve gestión de usuarios', () => { cy.visit('/login'); cy.get('[data-cy=email]').type('user@test.com'); cy.get('[data-cy=password]').type('1234'); cy.get('[data-cy=submit]').click(); cy.get('[data-cy=admin-menu]').should('not.exist'); }); });
Feature: Seguridad Login Scenario: Intento de inyección SQL Given el usuario escribe "admin' OR 1=1 --" When intenta iniciar sesión Then el sistema rechaza la entrada
login() { const email = this.email(); // Patrón de detección de SQL Injection const sqlInjectionPattern = /('|--|;|DROP|SELECT|INSERT)/i; if (sqlInjectionPattern.test(email)) { this.message.set('Entrada inválida detectada'); return; } const isValid = this.authService.login(email, this.password()); this.message.set(isValid ? 'Bienvenido' : 'Acceso denegado'); }
// ❌ INSEGURO — concatenación directa const query = `SELECT * FROM users WHERE email = '${email}'`; // ✅ SEGURO — consulta parametrizada const query = 'SELECT * FROM users WHERE email = ?'; db.execute(query, [email]); // ✅ SEGURO — con ORM (TypeORM ejemplo) const user = await userRepository.findOne({ where: { email } // ORM maneja la parametrización });
describe('Seguridad Anti SQL Injection', () => { it('Bloquea intento SQL Injection', () => { cy.visit('/'); cy.get('input[type=email]').type("admin' OR 1=1 --"); cy.contains('Ingresar').click(); cy.contains('Entrada inválida detectada').should('exist'); }); it('Bloquea intento con DROP TABLE', () => { cy.visit('/'); cy.get('input[type=email]').type("'; DROP TABLE users; --"); cy.contains('Ingresar').click(); cy.contains('Entrada inválida detectada').should('exist'); }); });
Sistema de Login con BDD en Angular 20 — evidencias esperadas
Feature: Autenticación Scenario: Usuario válido inicia sesión Given el usuario ingresa credenciales válidas When presiona el botón ingresar Then el sistema muestra mensaje de acceso correcto
signal()
src/app/ ├── login/ │ └── login.component.ts ├── dashboard/ │ └── dashboard.component.ts ├── auth.service.ts └── app.routes.ts cypress/e2e/ └── login.cy.ts
Criterios completos tal como están definidos en el curso
| Criterio | Excelente (5) | Bueno (4) | Regular (3) | Deficiente (1-2) |
|---|---|---|---|---|
| Redacción BDD | Escenarios claros y completos | Escenarios comprensibles | Escenarios incompletos | No aplica formato Given/When/Then |
| Arquitectura Angular | Uso correcto de standalone + servicios | Separación parcial | Lógica mezclada | Sin estructura clara |
| Uso de Signals | Uso correcto y reactivo | Uso básico | Uso incorrecto | No utiliza signals |
| Implementación Funcional | Funciona completamente | Funciona con detalles menores | Errores visibles | No funciona |
| Buenas Prácticas | Código limpio y organizado | Código aceptable | Código desordenado | Código caótico |
Puntaje Total: 25 puntos
| Criterio | Excelente (5) | Bueno (4) | Regular (3) | Deficiente (1-2) |
|---|---|---|---|---|
| Implementación Login | Funcional con roles correctos | Funciona parcialmente | Errores menores | No funciona |
| Protección por Rol | Restricción correcta | Restricción parcial | Lógica incompleta | Sin control |
| CRUD | Crear y eliminar correctos | Funciona con fallos | Inconsistente | No implementado |
| Uso Signals | Reactividad correcta | Uso básico | Uso incorrecto | No usa signals |
| Prueba Cypress | Escenario completo | Parcial | Prueba incompleta | No existe |
Puntaje máximo: 25 puntos
| Criterio | Excelente (5) | Bueno (4) | Regular (3) | Deficiente (1-2) |
|---|---|---|---|---|
| Validación Anti-Inyección | Patrón correcto y funcional | Parcial | Lógica incompleta | No implementado |
| Buenas Prácticas | Justifica técnicamente | Menciona conceptos | Superficial | No aplica |
| Escenario BDD | Claro y verificable | Parcial | Incompleto | No estructurado |
| Prueba Cypress | Detecta correctamente | Funciona parcialmente | Inestable | No existe |
| Separación de Responsabilidades | Código limpio | Aceptable | Mezclado | Desordenado |
Puntaje máximo: 25 puntos