Ejercicio: Crear un Sistema de Filtros Tipado

Video
25 min~4 min lectura

Reproductor de video

Concepto clave

Los sistemas de filtros tipados en TypeScript avanzado permiten crear abstracciones que procesan datos manteniendo la seguridad de tipos en tiempo de compilación. En arquitecturas complejas, esto es crucial para evitar errores en runtime y garantizar que las transformaciones de datos sean predecibles y mantenibles.

Imagina un sistema de recomendación de productos donde necesitas filtrar por múltiples criterios (precio, categoría, disponibilidad). Un sistema tipado te asegura que cada filtro reciba exactamente los datos que espera, y que el resultado mantenga la estructura correcta. Esto es similar a una línea de ensamblaje industrial donde cada estación solo acepta piezas específicas y produce componentes validados.

Cómo funciona en la práctica

Vamos a construir un filtro genérico paso a paso. Primero, definimos una interfaz base para nuestros filtros:

interface Filter {
  apply(data: T[]): T[];
  getDescription(): string;
}

Luego, implementamos filtros específicos usando genéricos con restricciones:

class PriceFilter implements Filter {
  constructor(private minPrice: number, private maxPrice: number) {}
  
  apply(data: T[]): T[] {
    return data.filter(item => item.price >= this.minPrice && item.price <= this.maxPrice);
  }
  
  getDescription(): string {
    return `Filtro de precio: ${this.minPrice} - ${this.maxPrice}`;
  }
}

Finalmente, creamos un sistema que compone múltiples filtros:

class FilterSystem {
  private filters: Filter[] = [];
  
  addFilter(filter: Filter): void {
    this.filters.push(filter);
  }
  
  applyAll(data: T[]): T[] {
    return this.filters.reduce((result, filter) => filter.apply(result), data);
  }
}

Caso de estudio

En una aplicación de e-commerce, necesitamos filtrar productos para una página de ofertas. Definimos nuestro tipo de producto:

interface Product {
  id: string;
  name: string;
  price: number;
  category: 'electronics' | 'clothing' | 'home';
  inStock: boolean;
  discount?: number;
}

Creamos filtros específicos:

class CategoryFilter implements Filter {
  constructor(private category: Product['category']) {}
  
  apply(data: Product[]): Product[] {
    return data.filter(item => item.category === this.category);
  }
  
  getDescription(): string {
    return `Filtro de categoría: ${this.category}`;
  }
}

class InStockFilter implements Filter {
  apply(data: Product[]): Product[] {
    return data.filter(item => item.inStock);
  }
  
  getDescription(): string {
    return 'Filtro de disponibilidad: solo en stock';
  }
}

Implementamos el sistema:

const productFilterSystem = new FilterSystem();
productFilterSystem.addFilter(new PriceFilter(50, 200));
productFilterSystem.addFilter(new CategoryFilter('electronics'));
productFilterSystem.addFilter(new InStockFilter());

const filteredProducts = productFilterSystem.applyAll(allProducts);
El sistema tipado garantiza que solo productos válidos pasen cada filtro, y el resultado final mantiene el tipo Product[] con todas sus propiedades.

Errores comunes

  • No usar restricciones en genéricos: Permitir cualquier tipo en filtros puede llevar a errores en runtime. Siempre usa T extends para definir qué propiedades necesita el filtro.
  • Olvidar la inmutabilidad: Los filtros no deben modificar los datos originales. Siempre retorna nuevos arrays usando métodos como filter().
  • Tipos demasiado amplios: Evita usar any o unknown en sistemas de filtros. Define interfaces específicas para cada contexto.
  • No validar datos de entrada: Aunque TypeScript valida en compilación, en runtime podrías recibir datos mal formados. Considera validaciones adicionales para APIs externas.
  • Complejidad innecesaria: No crees filtros hiper-especializados para casos de uso únicos. Busca equilibrio entre reutilización y simplicidad.

Checklist de dominio

  1. Puedo crear una interfaz genérica Filter<T> con métodos tipados correctamente
  2. Sé implementar filtros específicos usando restricciones de tipos (T extends)
  3. Puedo componer múltiples filtros en un sistema que mantiene la seguridad de tipos
  4. Entiendo cómo inferir tipos automáticamente en cadenas de filtros
  5. Puedo crear filtros que trabajen con tipos condicionales y mapeados
  6. Sé optimizar filtros para performance manteniendo la seguridad de tipos
  7. Puedo documentar un sistema de filtros para que otros desarrolladores lo usen correctamente

Implementa un sistema de filtros para una biblioteca de medios

Objetivo

Crea un sistema tipado de filtros para una aplicación que gestiona una biblioteca de medios (libros, películas, música).

Pasos

  1. Define una interfaz MediaItem con propiedades comunes: id (string), title (string), year (number), rating (number del 1 al 5), y un discriminador type ('book' | 'movie' | 'music').
  2. Crea una interfaz genérica Filter<T> con métodos apply(items: T[]): T[] y getName(): string.
  3. Implementa tres filtros específicos:
    • YearRangeFilter: Filtra por rango de años (ej: 2000-2020)
    • MinRatingFilter: Filtra por rating mínimo (ej: 4 estrellas o más)
    • TypeFilter: Filtra por tipo específico de medio
  4. Crea una clase MediaFilterSystem que pueda agregar y aplicar múltiples filtros en secuencia.
  5. Implementa un filtro avanzado SearchFilter que busque en título usando una cadena (case-insensitive).
  6. Escribe tests que verifiquen que el sistema mantiene los tipos correctamente en cada paso.

Requisitos técnicos

  • Usa TypeScript estricto (strict: true)
  • No uses any o aserciones de tipo innecesarias
  • Mantén la inmutabilidad en todos los filtros
  • Documenta cada filtro con comentarios JSDoc
Pistas
  • Usa tipos condicionales para crear filtros que funcionen solo con ciertos subtipos de MediaItem
  • Considera crear una función utilitaria para combinar filtros de forma más eficiente
  • Para el SearchFilter, explora el tipo Template Literal de TypeScript para búsquedas más seguras

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.