Volver al blog

TypeScript en 2025: Por qué 80% de las Vacantes Exigen y Cómo Dominar

Hola HaWkers, si aún estás usando JavaScript puro en 2025, necesito ser directo: estás quedándote atrás. TypeScript no es más una "tecnología de moda" - es el nuevo estándar de la industria.

La adopción de TypeScript explotó de 12% en 2017 para 35% en 2024, y en 2025 cerca de 80% de las vacantes mid-level y senior listan TypeScript como requisito obligatorio. No opcional. Obligatorio.

¿Por qué este cambio drástico aconteció?

La Historia de Cómo TypeScript Venció

TypeScript fue lanzado por Microsoft en 2012, pero durante años fue visto como "JavaScript con tipos aburridos" por muchos desarrolladores. ¿Qué cambió?

2017-2019: El Giro

Tres factores convergieron:

  1. Angular 2+ fue totalmente reescrito en TypeScript
  2. VS Code ofreció soporte excepcional para TypeScript out of the box
  3. React comenzó a adoptar TypeScript en su documentación oficial

Pero el verdadero punto de giro fue cuando grandes empresas comenzaron a reportar reducción de 15-30% en bugs de producción después de migrar para TypeScript.

// Ejemplo real: Bug que TypeScript habría prevenido
// JavaScript - Compila sin errores
function calculateDiscount(price, discount) {
  return price - (price * discount);
}

// Código parece correcto, pero...
calculateDiscount('100', 0.2);  // Resultado: "1000.2" (bug!)
calculateDiscount(100, '20%');  // Resultado: NaN (bug!)

// TypeScript - Errores en tiempo de desarrollo
function calculateDiscount(price: number, discount: number): number {
  return price - (price * discount);
}

calculateDiscount('100', 0.2);  // ❌ Error: Argument of type 'string' is not assignable
calculateDiscount(100, '20%');  // ❌ Error: Argument of type 'string' is not assignable

// Código correcto y type-safe
const finalPrice = calculateDiscount(100, 0.2); // ✅ OK: 80

Por Qué Empresas Exigen TypeScript en 2025

1. Reducción Drástica de Bugs

// Ejemplo: API response handling
// JavaScript - Propenso a errores en runtime
async function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  const user = await response.json();

  // Si la API cambia, solo descubres en producción
  console.log(user.fullName); // Puede explotar si field no existe
  return user;
}

// TypeScript - Errores en tiempo de desarrollo
interface User {
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  role: 'admin' | 'user';
  createdAt: string;
}

interface ApiResponse<T> {
  data: T;
  error?: string;
  meta: {
    timestamp: number;
    requestId: string;
  };
}

async function getUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const result: ApiResponse<User> = await response.json();

  if (result.error) {
    throw new Error(result.error);
  }

  const user = result.data;

  // ❌ TypeScript error: Property 'fullName' does not exist on type 'User'
  // console.log(user.fullName);

  // ✅ Correcto - TypeScript sugiere los campos disponibles
  console.log(`${user.firstName} ${user.lastName}`);

  return user;
}

2. Refactorización Segura en Grandes Códigos

Imagina renombrar una propiedad usada en 50 archivos diferentes. En JavaScript, cruzas los dedos para que tu buscar-y-reemplazar funcione. En TypeScript, el compilador encuentra todos los usos.

// Before: interface User con campo 'role'
interface User {
  id: string;
  email: string;
  role: string;  // ← Vamos a cambiar esto
}

// Después: cambiamos para un union type más específico
interface User {
  id: string;
  email: string;
  permissions: 'admin' | 'user' | 'moderator';  // ← Renombrado y mejorado
}

// TypeScript automáticamente marca TODOS los lugares que necesitan ser actualizados
function checkAccess(user: User) {
  // ❌ Error: Property 'role' does not exist on type 'User'
  if (user.role === 'admin') {
    return true;
  }

  // ✅ Correcto después de refactorización
  if (user.permissions === 'admin') {
    return true;
  }
}

3. Documentación Viva en el Código

// TypeScript sirve como documentación siempre actualizada
/**
 * Crea un nuevo usuario en el sistema
 *
 * @throws {ValidationError} Si los datos son inválidos
 * @throws {DuplicateEmailError} Si email ya existe
 */
async function createUser(
  data: {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
    dateOfBirth?: Date;  // Opcional
  }
): Promise<User> {
  // Implementación...
}

// El editor muestra exactamente lo que necesitas pasar
createUser({
  email: 'john@example.com',
  // ← El autocomplete muestra los campos obligatorios y opcionales
  password: 'secure123',
  firstName: 'John',
  lastName: 'Doe'
});

Conceptos Avanzados que Necesitas Dominar

Generics: El Poder de la Reutilización Type-Safe

// Ejemplo: Creando un cache genérico type-safe
class Cache<T> {
  private storage = new Map<string, {
    data: T;
    expiresAt: number;
  }>();

  set(key: string, value: T, ttl: number = 3600): void {
    this.storage.set(key, {
      data: value,
      expiresAt: Date.now() + (ttl * 1000)
    });
  }

  get(key: string): T | null {
    const item = this.storage.get(key);

    if (!item) return null;

    if (Date.now() > item.expiresAt) {
      this.storage.delete(key);
      return null;
    }

    return item.data;
  }

  // Generic method para transformar valores
  transform<R>(key: string, transformer: (value: T) => R): R | null {
    const value = this.get(key);
    return value ? transformer(value) : null;
  }
}

// Uso type-safe
interface User {
  id: string;
  name: string;
  email: string;
}

const userCache = new Cache<User>();

userCache.set('user:123', {
  id: '123',
  name: 'John Doe',
  email: 'john@example.com'
});

const user = userCache.get('user:123');  // Type: User | null

// ✅ TypeScript sabe que user puede ser null
if (user) {
  console.log(user.email);  // ← ¡Autocomplete funciona!
}

// Transform con type inference
const userName = userCache.transform('user:123', user => user.name);
// Type: string | null ← ¡TypeScript infirió automáticamente!

Utility Types: Herramientas Poderosas Built-in

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  stock: number;
  category: string;
  createdAt: Date;
  updatedAt: Date;
}

// Partial - Todos los campos opcionales
type ProductUpdate = Partial<Product>;

async function updateProduct(id: string, updates: ProductUpdate) {
  // Puede pasar cualquier combinación de campos
  // { price: 99.99 } ✅
  // { name: 'New Name', stock: 10 } ✅
}

// Pick - Seleccionar apenas campos específicos
type ProductPreview = Pick<Product, 'id' | 'name' | 'price'>;

function displayProductCard(product: ProductPreview) {
  // Solo tiene acceso a id, name y price
  console.log(product.name, product.price);
  // product.description ← ❌ Error: Property does not exist
}

// Omit - Todos excepto los especificados
type ProductCreate = Omit<Product, 'id' | 'createdAt' | 'updatedAt'>;

async function createProduct(data: ProductCreate): Promise<Product> {
  // No necesita pasar id, createdAt, updatedAt
  // Sistema genera automáticamente
  return {
    ...data,
    id: crypto.randomUUID(),
    createdAt: new Date(),
    updatedAt: new Date()
  };
}

// Record - Crear objetos con llaves específicas
type ProductsByCategory = Record<string, Product[]>;

const groupedProducts: ProductsByCategory = {
  'electronics': [/* productos */],
  'books': [/* productos */],
  'clothing': [/* productos */]
};

// Readonly - Inmutabilidad
type ImmutableProduct = Readonly<Product>;

const product: ImmutableProduct = {
  /* ... */
};

// product.price = 999; ← ❌ Error: Cannot assign to 'price'

Conditional Types: Lógica en Nivel de Tipos

// Ejemplo avanzado: Tipos condicionales para API responses
type ApiResponse<T> = T extends { error: any }
  ? { success: false; error: string }
  : { success: true; data: T };

// Extraer tipos de arrays
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type Numbers = ArrayElement<number[]>;  // number
type Strings = ArrayElement<string[]>;  // string

// Extraer tipo de retorno de función
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;

async function getUsers() {
  return [{ id: '1', name: 'John' }];
}

type Users = ReturnTypeOf<typeof getUsers>;
// Type: Promise<{ id: string; name: string; }[]>

// Unwrap Promise
type Awaited<T> = T extends Promise<infer U> ? U : T;

type UnwrappedUsers = Awaited<Users>;
// Type: { id: string; name: string; }[]

Patrones Reales de Producción

1. Type Guards para Validación en Runtime

// Type guard personalizado
interface Dog {
  type: 'dog';
  bark: () => void;
}

interface Cat {
  type: 'cat';
  meow: () => void;
}

type Pet = Dog | Cat;

// Type guard function
function isDog(pet: Pet): pet is Dog {
  return pet.type === 'dog';
}

function handlePet(pet: Pet) {
  if (isDog(pet)) {
    pet.bark();  // ✅ TypeScript sabe que es Dog
  } else {
    pet.meow();  // ✅ TypeScript sabe que es Cat
  }
}

// Type guard con validación de API
function isValidUser(data: unknown): data is User {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    'email' in data &&
    typeof (data as any).email === 'string'
  );
}

async function fetchUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();

  if (!isValidUser(data)) {
    throw new Error('Invalid user data from API');
  }

  return data;  // ✅ TypeScript sabe que es User
}

2. Builder Pattern con TypeScript

// Fluent API type-safe
class QueryBuilder<T> {
  private conditions: string[] = [];
  private orderField?: keyof T;
  private limitValue?: number;

  where(field: keyof T, operator: string, value: any): this {
    this.conditions.push(`${String(field)} ${operator} ${value}`);
    return this;
  }

  orderBy(field: keyof T, direction: 'ASC' | 'DESC' = 'ASC'): this {
    this.orderField = field;
    return this;
  }

  limit(n: number): this {
    this.limitValue = n;
    return this;
  }

  build(): string {
    let query = `SELECT * FROM table`;

    if (this.conditions.length > 0) {
      query += ` WHERE ${this.conditions.join(' AND ')}`;
    }

    if (this.orderField) {
      query += ` ORDER BY ${String(this.orderField)}`;
    }

    if (this.limitValue) {
      query += ` LIMIT ${this.limitValue}`;
    }

    return query;
  }
}

// Uso type-safe
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

const query = new QueryBuilder<User>()
  .where('age', '>', 18)
  .where('email', 'LIKE', '%@example.com')
  .orderBy('name', 'ASC')  // ← ¡Autocomplete solo sugiere campos válidos!
  .limit(10)
  .build();

// .orderBy('invalidField') ← ❌ Error: Argument not assignable

Cómo Aprender TypeScript en 2025

Roadmap de 30 Días

// Semana 1: Fundamentos
const week1Topics = [
  'Tipos básicos (string, number, boolean, array)',
  'Interfaces vs Types',
  'Union types e intersection types',
  'Type annotations y type inference',
  'Functions y sus tipos'
];

// Semana 2: Intermedio
const week2Topics = [
  'Generics',
  'Utility types (Partial, Pick, Omit, Record)',
  'Type guards',
  'Enums y const assertions',
  'Modules y namespaces'
];

// Semana 3: Avanzado
const week3Topics = [
  'Conditional types',
  'Mapped types',
  'Template literal types',
  'Decorators',
  'Type manipulation avanzada'
];

// Semana 4: Práctica Real
const week4Projects = [
  'Migrar proyecto JavaScript existente',
  'Crear API REST type-safe',
  'Implementar state management con tipos',
  'Escribir tests con tipos',
  'Configurar CI/CD con type checking'
];

Recursos Esenciales

  1. TypeScript Handbook (oficial) - Gratuito y completo
  2. TypeScript Exercises - Desafíos prácticos
  3. Type Challenges - Problemas avanzados
  4. Proyectos reales - Migra tus proyectos personales

Errores Comunes que Debes Evitar

// ❌ Usar 'any' demás derrota el propósito
function processData(data: any) {
  return data.value;  // Sin type safety
}

// ✅ Usa types específicos o unknown
function processData(data: unknown) {
  if (typeof data === 'object' && data !== null && 'value' in data) {
    return (data as { value: string }).value;
  }
  throw new Error('Invalid data');
}

// ❌ Ignorar errores con @ts-ignore
// @ts-ignore
const result = someFunction();

// ✅ Resolver el problema o usar assertion consciente
const result = someFunction() as ExpectedType;

// ❌ Tipos muy genéricos
interface Data {
  value: any;
  items: any[];
}

// ✅ Tipos específicos y útiles
interface UserData {
  value: string | number;
  items: User[];
}

El Futuro: TypeScript 6.0 y Más Allá

TypeScript continúa evolucionando rápidamente. Recursos futuros incluyen:

  • Mejor soporte para decorators (stage 3)
  • Type imports más inteligentes
  • Performance mejorada para grandes proyectos
  • Integración más profunda con frameworks

Si quieres ver TypeScript en acción en proyectos reales, recomiendo dar una mirada en otro artículo: Mercado de Trabajo Dev 2025: Las Habilidades Esenciales donde vas a descubrir cómo TypeScript se encaja en el panorama completo de habilidades necesarias.

¡Vamos a por ello! 🦅

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios