Estándares y Reglas de Desarrollo

Este documento describe las reglas y estándares de desarrollo que deben seguirse en el proyecto UnoSportClub. Estas reglas garantizan la consistencia, calidad y mantenibilidad del código.

Principios Fundamentales

Convenciones de Nomenclatura

  • Código en inglés: Todo el código debe estar escrito en inglés

  • Funciones, atributos y variables: Usar CamelCase

  • Clases, Enums e Interfaces: Usar PascalCase

  • Rutas URL: Usar kebab-case

  • Constantes: Usar UPPER_SNAKE_CASE

  • Archivos y directorios: Preferir una sola palabra cuando sea posible (ej: "birthday" en lugar de "dateOfBirth")

Principios de Diseño

  • KISS (Keep It Simple, Stupid): Mantén el código simple y directo

  • Principio de Rubik: Divide problemas complejos en partes más pequeñas y simples

  • Una exportación por archivo: Cada archivo debe exportar un solo elemento principal

  • Sin líneas en blanco dentro de funciones: Mantén las funciones compactas

  • Sin comentarios explicativos: El código debe ser autoexplicativo; no uses comentarios para enseñar

Lo que NUNCA debe estar en el código

  • ❌ Errores

  • ❌ Lorem Ipsum

  • ❌ Hacks o soluciones temporales

  • ❌ Delays artificiales (setTimeout, setInterval, etc.)

  • ❌ Código innecesario o desordenado

  • ❌ Comentarios sin valor educativo

TypeScript y Angular

TypeScript

Type Checking Estricto

Siempre habilita y respeta el type checking estricto. Esto ayuda a detectar errores tempranamente.

Preferir Inferencia de Tipos

Permite que TypeScript infiera los tipos cuando son obvios del contexto:

// ❌ INCORRECTO
let name: string = 'Angular';

// ✅ CORRECTO
let name = 'Angular';

Evitar any

No uses el tipo any a menos que sea absolutamente necesario. Si conoces la estructura de datos, usa la interfaz correspondiente. Para respuestas HTTP, usa HttpResponse o ErrorResponse, o la interfaz específica del endpoint según la documentación Swagger.

// ❌ INCORRECTO
const data: any = response;

// ✅ CORRECTO
interface UserResponse {
  id: number;
  name: string;
  email: string;
}
const data: UserResponse = response;

Angular - Componentes Standalone

Componentes Standalone (Obligatorio)

Siempre usa componentes standalone. No necesitas especificar standalone: true explícitamente, está implícito por defecto.

// ✅ CORRECTO
@Component({
  selector: 'app-user-card',
  template: `<div>{{ user().name }}</div>`,
  imports: [CommonModule]
})
export class UserCardComponent {
  user = input.required<IUser>();
}

Signals para Gestión de Estado

Utiliza Angular Signals para la gestión de estado reactivo:

// ✅ CORRECTO
private count = signal(0);
private name = signal('');
private users = signal<IUser[]>([]);

// Computed signals
protected doubleCount = computed(() => this.count() * 2);

// Actualizar signals
increaseCount() {
  this.count.update(c => c + 1);
  // O
  this.count.set(this.count() + 1);
}

// ❌ NUNCA usar mutate
this.users.mutate(arr => arr.push(newUser)); // ❌ INCORRECTO
this.users.update(arr => [...arr, newUser]); // ✅ CORRECTO

Input/Output Functions

Prefiere las funciones input() y output() sobre los decoradores @Input() y @Output():

// ❌ ANTIGUO
@Input() userId!: string;
@Output() userSelected = new EventEmitter<string>();

// ✅ NUEVO
import { input, output } from '@angular/core';

userId = input<string>('');
userSelected = output<string>();

Host Bindings

NO uses los decoradores @HostBinding y @HostListener. Coloca los host bindings dentro del objeto host del decorador:

// ✅ CORRECTO
@Component({
  selector: 'app-button',
  host: {
    '[class.active]': 'isActive()',
    '[class.disabled]': '!enabled()',
    '(click)': 'onClick()'
  }
})
export class ButtonComponent {
  isActive = input(false);
  enabled = input(true);
}

Change Detection OnPush

Siempre configura changeDetection: ChangeDetectionStrategy.OnPush para mejor rendimiento:

@Component({
  selector: 'app-card',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CardComponent {}

NgOptimizedImage

Usa NgOptimizedImage para todas las imágenes estáticas:

<!-- ✅ CORRECTO -->
<img
  ngSrc="assets/image.webp"
  alt="Description"
  width="300"
  height="200"
  priority
/>

<!-- ❌ INCORRECTO -->
<img src="image.jpg" alt="Description" />

Control Flow Nativo

Usa el nuevo control flow nativo de Angular en lugar de las directivas estructurales:

<!-- ❌ ANTIGUO -->
<div *ngIf="isLoading">Loading...</div>
<div *ngFor="let user of users; trackBy: trackByUserId">
  {{ user.name }}
</div>

<!-- ✅ NUEVO -->
@if (isLoading()) {
  <div>Loading...</div>
}

@for (user of users(); track user.id) {
  <div>{{ user.name }}</div>
}

Forms Reactivos

Prefiere Reactive Forms sobre Template-driven forms:

// ✅ CORRECTO
export class LoginComponent {
  private fb = inject(FormBuilder);

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

Binding de Estilos y Clases

Usa bindings nativos en lugar de ngClass y ngStyle:

<!-- ✅ CORRECTO -->
<div
  [class.active]="isActive()"
  [class.disabled]="!enabled()"
  [style.color]="color()"
  [style.font-size.px]="fontSize()"
>

<!-- ❌ INCORRECTO -->
<div [ngClass]="{ active: isActive(), disabled: !enabled() }">
<div [ngStyle]="{ color: color(), fontSize: fontSize() }">

Services y Dependency Injection

Inject Function

Prefiere la función inject() sobre la inyección por constructor:

// ✅ CORRECTO
@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private router = inject(Router);

  getUser(id: number) {
    return this.http.get<IUser>(`/users/${id}`);
  }
}

Singleton Pattern

Usa providedIn: 'root' para servicios singleton:

@Injectable({ providedIn: 'root' })
export class AuthService {
  // Singleton automáticamente
}

Principios SOLID

Single Responsibility (Responsabilidad Única)

Cada clase y función debe tener una única razón clara para cambiar. Divide clases o funciones complejas en partes más pequeñas y enfocadas.

// ❌ INCORRECTO - Múltiples responsabilidades
export class UserComponent {
  users: IUser[] = [];
  async loadUsers() { /* ... */ }
  async saveUser(user: IUser) { /* ... */ }
  validateUserPermission() { /* ... */ }
  sendNotification() { /* ... */ }
}

// ✅ CORRECTO - Separar responsabilidades
@Component({ /* ... */ })
export class UserListComponent {
  users = toSignal(this.userService.getUsers());
}

@Injectable({ providedIn: 'root' })
export class UserService {
  getUsers() {
    return this.http.get<IUser[]>('/api/users');
  }
}

Open/Closed Principle

Las clases deben estar abiertas para extensión pero cerradas para modificación:

// ✅ CORRECTO - Abierto a extensión
abstract class PaymentStrategy {
  abstract process(amount: number): Promise<void>;
}

class CreditCardPayment extends PaymentStrategy {
  async process(amount: number) { /* ... */ }
}

// Nueva estrategia sin modificar existentes
class PayPalPayment extends PaymentStrategy {
  async process(amount: number) { /* ... */ }
}

Liskov Substitution

Los tipos derivados deben ser perfectamente sustituibles por sus tipos base:

// ✅ CORRECTO
abstract class Animal {
  abstract makeSound(): string;
}

class Dog extends Animal {
  makeSound() { return 'Woof'; }
}

class Cat extends Animal {
  makeSound() { return 'Meow'; }
}

// Ambos pueden usarse de forma intercambiable
function makeAnimalSound(animal: Animal) {
  console.log(animal.makeSound());
}

Interface Segregation

Prefiere interfaces pequeñas y específicas sobre una interfaz grande:

// ✅ CORRECTO
interface Readable {
  read(): string;
}

interface Writable {
  write(data: string): void;
}

interface FileHandler extends Readable, Writable {}

// ❌ INCORRECTO
interface FileOperations {
  read(): string;
  write(data: string): void;
  delete(): void;
  compress(): void;
  encrypt(): void;
}

Dependency Inversion

Depende de abstracciones, no de implementaciones concretas:

// ✅ CORRECTO
interface IUserRepository {
  findById(id: number): Promise<IUser>;
  save(user: IUser): Promise<void>;
}

class FirebaseUserRepository implements IUserRepository {
  async findById(id: number) { /* Firebase logic */ }
  async save(user: IUser) { /* Firebase logic */ }
}

class UserService {
  constructor(private repo: IUserRepository) {}

  async getUser(id: number) {
    return this.repo.findById(id);
  }
}

Seguridad

OWASP Top 10

Sigue las recomendaciones del estándar OWASP Top 10:

  • Validación de Entrada: Valida y sanitiza todas las entradas del usuario, tanto del lado cliente como en el servidor

  • Protección contra Inyección: Usa consultas parametrizadas y validaciones estrictas

  • Autenticación y Autorización: Implementa controles robustos de autenticación y gestión de sesiones

  • Principio de Mínimo Privilegio: Concede solo los permisos necesarios

  • Protección IDOR: Restringe el acceso directo a objetos basados en referencias

  • Manejo de Errores: Gestiona correctamente los errores sin exponer detalles internos

  • Control de Acceso: Emplea controles estrictos sobre permisos y roles

  • HTTPS: Usa HTTPS en todas las comunicaciones

  • Almacenamiento Seguro: Almacena credenciales y datos sensibles de forma segura

  • Protección de APIs: Implementa autenticación, autorización y rate limiting

Angular Security

  • Sanitización HTML: Siempre sanitiza HTML no confiable usando la sanitización de Angular, especialmente con [innerHTML]

  • No exponer secretos: No expongas API keys, secrets o configuración sensible en el frontend

  • HttpClient: Usa HttpClient para todas las peticiones HTTP

  • Route Guards: Aplica guards de ruta para autenticación y autorización

  • CSP: Usa una política de seguridad de contenido estricta en producción

  • Validación del servidor: Nunca confíes solo en la validación del cliente; siempre valida en el servidor

Express.js Security

  • Orden de Middleware: Body parsers, middleware personalizado, rutas, manejadores de errores

  • Async/Await: Usa async/await con manejo adecuado de errores

  • Validación: Implementa validación de requests usando express-validator

  • Middleware de Autenticación: Usa middleware para autenticación y autorización

  • Códigos HTTP: Usa códigos de estado HTTP apropiados

Calidad de Código

Verificación Antes de Completar

IMPORTANTE: NO DAR POR TERMINADO SIN VERIFICAR ERRORES

  • SIEMPRE ejecutar read_lints en los archivos modificados

  • SIEMPRE verificar que no haya warnings de componentes no usados

  • SIEMPRE verificar que no haya errores de TypeScript

  • SIEMPRE verificar que los imports sean correctos

  • SIEMPRE verificar que los templates usen los componentes importados

No Delays en el Código

IMPORTANTE: NO HAY DELAY EN EL CÓDIGO

  • NUNCA usar setTimeout(), setInterval(), timer(), o cualquier método de delay artificial

  • NUNCA deshabilitar botones con estados de carga artificiales

  • NUNCA usar signals de loading para crear estados de espera artificiales

Filosofía: * Si algo va a tardar, tardará naturalmente (por ejemplo, una petición HTTP) * Si algo va antes o después, hay que observar al elemento correcto cuando cambie * Los ganadores no esperan a que las cosas sucedan, reaccionan cuando suceden

// ❌ INCORRECTO - Delay artificial
setTimeout(() => {
  this.router.navigate(['/']);
}, 1000);

// ✅ CORRECTO - Reaccionar cuando el observable emite
this.authService.login(email, password).subscribe({
  next: () => {
    this.router.navigate(['/']); // Navega inmediatamente cuando el login es exitoso
  }
});

Checklist de Code Review

Antes de enviar código, verifica:

  • ✅ No hay console.log() en código de producción

  • console.error() y console.warn() están permitidos cuando son necesarios

  • ✅ No hay comentarios TODO o FIXME sin referencia a issue

  • ✅ Todas las funciones tienen comentarios JSDoc

  • ✅ Todos los tipos están correctamente definidos (no any)

  • ✅ Hay manejo de errores para todas las operaciones asíncronas

  • ✅ No hay valores hardcodeados o números mágicos

  • ✅ No hay código duplicado (principio DRY)

  • ✅ El código es modular y testeable

Testing

Framework de Testing

  • Angular: Usa Karma y Jasmine (estándar de Angular)

  • Firebase Functions: Usa Jest

  • NO improvises: No uses frameworks fuera del estándar sin justificación

Escribir Tests

  • Tests Unitarios: Para funciones puras y componentes aislados

  • Tests de Integración: Para interacción entre módulos y servicios

  • Tests E2E: Para simular comportamiento real del usuario

Mocking

  • Firebase Auth: Mockea correctamente con _delegate y propiedades internas

  • HttpClient: Usa HttpTestingController para tests de servicios HTTP

  • Observables: Usa toSignal() o async pipe para manejar observables en tests

Express.js

Orden de Middleware

// ✅ CORRECTO - Orden específico
const app = express();

// 1. Body parsers
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// 2. Custom middleware
app.use(corsMiddleware);
app.use(authMiddleware);

// 3. Routes
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);

// 4. Error handler (ÚLTIMO)
app.use(errorHandlerMiddleware);

Async/Await con Manejo de Errores

// ✅ CORRECTO
router.get('/:id', async (req, res, next) => {
  try {
    const user = await UserService.getUser(req.params.id);
    res.json(user);
  } catch (error) {
    next(error); // Pasar al error handler
  }
});

Rutas Modulares

Organiza las rutas usando Express Router:

// ✅ CORRECTO
// users/routes.ts
const router = Router();

router.get('/', getUserList);
router.get('/:id', getUser);
router.post('/', createUser);

export const userRoutes = router;

// app.ts
app.use('/api/users', userRoutes);

Referencias