GUIDE

TypeScript: Desarrollo Type-Safe a Escala

Guía completa del sistema de tipos de TypeScript, patrones avanzados, configuración del compilador, arquitectura monorepo y migración desde JavaScript. Basada en ejecutar todos los microservicios de la plataforma en TypeScript con un monorepo NestJS gestionado por Nx.

TypeScriptGenericsMapped TypesConditional TypesTemplate Literal TypesNestJSNxStrict ModeDeclaration FilesType GuardsBranded TypestsconfigMigration

Índice de Contenidos

  1. 1. El Sistema de Tipos: Genéricos, Mapped Types, Conditional Types
  2. 2. Modo Estricto y Opciones del Compilador
  3. 3. Integración con Node.js, React, Angular
  4. 4. Archivos de Declaración y Definiciones de Tipos
  5. 5. Patrones Avanzados
  6. 6. Configuración Monorepo con Nx
  7. 7. Mejores Prácticas de tsconfig
  8. 8. Migración desde JavaScript
  9. 9. ECMAScript 2025 y Roadmap de TypeScript

1. El Sistema de Tipos: Genéricos, Mapped Types, Conditional Types

Genéricos: Polimorfismo Paramétrico

Los genéricos te permiten escribir funciones, clases e interfaces que funcionan con cualquier tipo mientras preservan las relaciones de tipos. La idea clave es que los genéricos no son solo "plantillas" sino restricciones en las relaciones entre entradas y salidas. Un parámetro de tipo genérico captura el tipo específico en el sitio de llamada y lo propaga a través de toda la computación.

// Generic repository pattern with constrained types
interface Entity { id: string; createdAt: Date; updatedAt: Date; }

interface Repository<T extends Entity> {
  findById(id: string): Promise<T | null>;
  findMany(filter: Partial<Omit<T, keyof Entity>>): Promise<T[]>;
  create(data: Omit<T, keyof Entity>): Promise<T>;
  update(id: string, data: Partial<Omit<T, keyof Entity>>): Promise<T>;
  delete(id: string): Promise<void>;
}

// Usage: the filter type is automatically narrowed
interface User extends Entity { email: string; name: string; role: Role; }
const userRepo: Repository<User> = new MySQLRepository('users');
// filter is typed as Partial<{ email: string; name: string; role: Role }>
const admins = await userRepo.findMany({ role: Role.ADMIN });

Mapped Types

Los mapped types iteran sobre las claves de un tipo y transforman cada propiedad. Son la base de los tipos utilitarios como Partial, Required, Readonly y Pick. La sintaxis [K in keyof T] itera sobre todas las claves, mientras que las cláusulas "as" permiten remapeo y filtrado de claves.

// Deep readonly: recursively makes all properties immutable
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? T[K] extends Function
      ? T[K]
      : DeepReadonly<T[K]>
    : T[K];
};

// Create a type where all methods return Promises (for RPC clients)
type Promisify<T> = {
  [K in keyof T]: T[K] extends (...args: infer A) => infer R
    ? (...args: A) => Promise<R>
    : T[K];
};

// Filter type to only include string properties
type StringKeys<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};

Conditional Types

Los conditional types siguen la forma T extends U ? X : Y. Cuando se combinan con infer, extraen tipos de estructuras complejas. Los conditional types distributivos (donde el tipo verificado es un parámetro de tipo desnudo) se distribuyen automáticamente sobre uniones. Este comportamiento es fundamental para construir lógica a nivel de tipos.T extends U ? X : Y. When combined with infer, they extract types from complex structures. Distributive conditional types (where the checked type is a naked type parameter) automatically distribute over unions. This behavior is fundamental for building type-level logic.

// Extract return type of async functions
type AsyncReturnType<T extends (...args: any) => Promise<any>> =
  T extends (...args: any) => Promise<infer R> ? R : never;

// Extract event payload type from an event map
type EventPayload<M, E extends keyof M> =
  M[E] extends { payload: infer P } ? P : never;

// Recursive type: flatten nested arrays
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
// Flatten<number[][][]> = number

// Type-safe path accessor (e.g., "user.address.city")
type PathValue<T, P extends string> =
  P extends `${infer K}.${infer Rest}`
    ? K extends keyof T
      ? PathValue<T[K], Rest>
      : never
    : P extends keyof T
      ? T[P]
      : never;

Template Literal Types

Los template literal types combinan tipos literales con sintaxis de interpolación de strings para producir nuevos tipos de string literales. Permiten manipulación type-safe de strings a nivel de tipos, lo cual es invaluable para definiciones de rutas API, nombres de eventos, propiedades CSS y cualquier dominio donde los strings siguen patrones predecibles. Combinados con conditional types y tipos recursivos, pueden parsear y validar formatos de strings en tiempo de compilación.

// Type-safe event emitter using template literal types
type EventName = 'user' | 'order' | 'payment';
type EventAction = 'created' | 'updated' | 'deleted';
type EventString = `${EventName}:${EventAction}`;
// "user:created" | "user:updated" | "user:deleted" | "order:created" | ...

// Type-safe CSS utility classes
type Size = 'sm' | 'md' | 'lg' | 'xl';
type Direction = 'top' | 'right' | 'bottom' | 'left';
type SpacingClass = `m${Direction extends infer D extends string ? Capitalize<D> : never}-${Size}`;

// HTTP route builder with type-safe path parameters
type ExtractParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<Rest>
    : T extends `${string}:${infer Param}`
      ? Param
      : never;

type UserRouteParams = ExtractParams<'/users/:userId/posts/:postId'>;
// "userId" | "postId"

// Enforce dot-notation paths for nested objects
type DotPath<T, Prefix extends string = ''> = T extends object
  ? { [K in keyof T & string]:
      | `${Prefix}${K}`
      | DotPath<T[K], `${Prefix}${K}.`>
    }[keyof T & string]
  : never;

2. Modo Estricto y Opciones del Compilador

El flag "strict": true habilita una familia de verificaciones estrictas que son esenciales para código de producción. Cada sub-flag aborda una clase específica de errores en runtime que TypeScript puede prevenir en tiempo de compilación. Ejecutar sin modo estricto anula el propósito de usar TypeScript y permite que categorías enteras de bugs pasen."strict": true flag enables a family of strict checks that are essential for production code. Each sub-flag addresses a specific class of runtime errors that TypeScript can prevent at compile time. Running without strict mode defeats the purpose of using TypeScript and allows entire categories of bugs to pass through.

Flags Estrictos Críticos

// tsconfig.json - production-grade strict configuration
{
  "compilerOptions": {
    // Strict family (all enabled by "strict": true)
    "strict": true,
    "noImplicitAny": true,           // No implicit 'any' types
    "strictNullChecks": true,         // null/undefined are distinct types
    "strictFunctionTypes": true,      // Contravariant function parameter checking
    "strictBindCallApply": true,      // Check bind/call/apply argument types
    "strictPropertyInitialization": true, // Class properties must be initialized
    "noImplicitThis": true,           // No implicit 'this' type
    "useUnknownInCatchVariables": true,   // catch(e) is 'unknown', not 'any'
    "alwaysStrict": true,             // Emit "use strict" in every file

    // Additional safety flags (NOT included in "strict")
    "noUncheckedIndexedAccess": true, // T[key] returns T | undefined
    "exactOptionalPropertyTypes": true, // Distinguish missing vs. undefined
    "noFallthroughCasesInSwitch": true,
    "noImplicitReturns": true,
    "noImplicitOverride": true,       // Require 'override' keyword
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true           // Required for esbuild/swc
  }
}

strictNullChecks en Práctica

Sin strictNullChecks, cada tipo incluye implícitamente null y undefined. Esto significa que string en realidad significa string | null | undefined, y TypeScript no puede detectar errores de dereferencia nula. Con strict null checks habilitado, debes manejar explícitamente la nulabilidad usando optional chaining (?.), nullish coalescing (??), o type guards.strictNullChecks, every type implicitly includes null and undefined. This means string actually means string | null | undefined, and TypeScript cannot catch null dereference errors. With strict null checks enabled, you must explicitly handle nullability using optional chaining (?.), nullish coalescing (??), or type guards.

// Without strictNullChecks: this compiles but crashes at runtime
function getLength(s: string): number {
  return s.length; // Runtime: Cannot read property 'length' of null
}
getLength(null); // No error without strictNullChecks

// With strictNullChecks: the compiler catches it
function getLength(s: string | null): number {
  if (s === null) return 0;  // Type narrowed to 'string' after this check
  return s.length;           // Safe: s is guaranteed to be string here
}
En producción, habilitar noUncheckedIndexedAccess en todo el monorepo reveló 200+ bugs potenciales de dereferencia nula en patrones de acceso a arrays/objetos. Los resolvimos en dos semanas, y la tasa de errores en runtime en producción cayó un 35% en el mes siguiente.noUncheckedIndexedAccess across the monorepo revealed 200+ potential null dereference bugs in array/object access patterns. We resolved them over two weeks, and the runtime error rate in production dropped by 35% in the following month.

3. Integración con Node.js, React, Angular

Node.js + TypeScript

Para microservicios Node.js, TypeScript provee seguridad de tipos a través de todo el ciclo de vida del request: cuerpos de request validados, queries tipadas de base de datos, configuración tipada y comunicación inter-servicios tipada. El paquete @types/node provee tipos para todos los módulos built-in de Node.js. Con NestJS, TypeScript es ciudadano de primera: decoradores, inyección de dependencias y límites de módulo están todos verificados por tipos.@types/node package provides types for all Node.js built-in modules. With NestJS, TypeScript is a first-class citizen: decorators, dependency injection, and module boundaries are all type-checked.

// Typed configuration with validation
import { z } from 'zod';

const ConfigSchema = z.object({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
  CORS_ORIGINS: z.string().transform(s => s.split(',')),
});

type Config = z.infer<typeof ConfigSchema>;

// Validated at startup - crashes immediately if config is invalid
export const config: Config = ConfigSchema.parse(process.env);

React + TypeScript

El modelo de componentes de React se mapea naturalmente a interfaces TypeScript. Los props se tipan como interfaces, los hooks infieren tipos de retorno automáticamente, y los context providers imponen contratos de tipo. El tipo React.FC ahora se desaconseja a favor del tipado explícito de props, que da mejor inferencia de tipos y evita props children implícitos.React.FC type is now discouraged in favor of explicit prop typing, which gives better type inference and avoids implicit children props.

// Modern React component typing (no React.FC)
interface UserCardProps {
  user: User;
  onEdit: (id: string) => void;
  variant?: 'compact' | 'expanded';
}

function UserCard({ user, onEdit, variant = 'compact' }: UserCardProps) {
  const [isLoading, setIsLoading] = useState(false);
  // isLoading inferred as boolean, setIsLoading as Dispatch<SetStateAction<boolean>>

  const handleEdit = useCallback(() => {
    onEdit(user.id); // TypeScript ensures user.id is string
  }, [user.id, onEdit]);

  return (/* JSX */);
}

Angular + TypeScript

Angular fue construido con TypeScript desde su inicio. Los decoradores (@Component, @Injectable, @NgModule) son decoradores experimentales de TypeScript. La verificación de tipos de templates de Angular (strictTemplates) valida bindings en templates HTML contra los tipos del componente, detectando errores como typos en nombres de propiedades o firmas incorrectas de event handlers en tiempo de compilación.@Component, @Injectable, @NgModule) are TypeScript experimental decorators. Angular's template type checking (strictTemplates) validates bindings in HTML templates against component types, catching errors like typos in property names or incorrect event handler signatures at compile time.

4. Archivos de Declaración y Definiciones de Tipos

Los archivos de declaración (.d.ts) describen la forma de librerías JavaScript sin contener implementación. El ecosistema @types en npm provee declaraciones mantenidas por la comunidad para miles de paquetes. Cuando una librería incluye sus propios tipos (campo "types" en package.json), no se necesita un paquete @types separado..d.ts) describe the shape of JavaScript libraries without containing implementation. The @types ecosystem on npm provides community-maintained declarations for thousands of packages. When a library includes its own types ("types" field in package.json), no separate @types package is needed.

Escribiendo Archivos de Declaración

// types/zeromq-extended.d.ts - augmenting ZeroMQ types for our use case
import { Socket } from 'zeromq';

declare module 'zeromq' {
  interface Socket {
    sendJson(data: unknown): Promise<void>;
    receiveJson<T>(): Promise<T>;
  }
}

// types/global.d.ts - ambient declarations for environment
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'staging' | 'production';
    DATABASE_URL: string;
    REDIS_URL: string;
    JWT_SECRET: string;
  }
}

// types/express-extensions.d.ts - extending Express Request
declare namespace Express {
  interface Request {
    userId?: string;
    tenantId?: string;
    permissions?: string[];
  }
}

Estrategia de Declaraciones para Microservicios

En un monorepo de microservicios, las definiciones de tipos compartidas van en un paquete @company/types. Este paquete contiene entidades de dominio, DTOs, schemas de eventos y contratos de API. Se publica como paquete TypeScript (con archivos .d.ts) y lo consumen todos los servicios. Los cambios en tipos compartidos disparan verificación de tipos en todos los servicios dependientes en el pipeline de CI, detectando cambios breaking antes del despliegue.@company/types package. This package contains domain entities, DTOs, event schemas, and API contracts. It is published as a TypeScript package (with .d.ts files) and consumed by all services. Changes to shared types trigger type checking across all dependent services in the CI pipeline, catching breaking changes before deployment.

5. Patrones Avanzados

Branded Types (Tipado Nominal)

TypeScript usa tipado estructural: dos tipos son compatibles si sus formas coinciden. Esto significa que UserId y OrderId son intercambiables si ambos son string. Los branded types agregan una propiedad fantasma para crear distinción nominal, previniendo la mezcla accidental de identificadores de dominio.UserId and OrderId are interchangeable if both are string. Branded types add a phantom property to create nominal distinction, preventing accidental mixing of domain identifiers.

// Branded types: prevent mixing IDs from different domains
type Brand<T, B extends string> = T & { readonly __brand: B };

type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
type TenantId = Brand<string, 'TenantId'>;

function createUserId(id: string): UserId { return id as UserId; }
function createOrderId(id: string): OrderId { return id as OrderId; }

function getUser(id: UserId): Promise<User> { /* ... */ }
function getOrder(id: OrderId): Promise<Order> { /* ... */ }

const userId = createUserId('usr_123');
const orderId = createOrderId('ord_456');

getUser(userId);    // OK
getUser(orderId);   // COMPILE ERROR: OrderId is not assignable to UserId
getOrder(orderId);  // OK

Uniones Discriminadas

Las uniones discriminadas usan una propiedad literal compartida (el discriminante) para habilitar narrowing de tipos exhaustivo en sentencias switch. Este patrón es esencial para modelar máquinas de estado, sistemas de eventos y respuestas de API donde diferentes variantes llevan datos diferentes.switch statements. This pattern is essential for modeling state machines, event systems, and API responses where different variants carry different data.

// Event system with discriminated unions
type DomainEvent =
  | { type: 'USER_CREATED'; payload: { userId: string; email: string } }
  | { type: 'USER_UPDATED'; payload: { userId: string; changes: Partial<User> } }
  | { type: 'ORDER_PLACED'; payload: { orderId: string; items: OrderItem[] } }
  | { type: 'PAYMENT_RECEIVED'; payload: { orderId: string; amount: number } };

// Exhaustive handler: TypeScript ensures all event types are handled
function handleEvent(event: DomainEvent): void {
  switch (event.type) {
    case 'USER_CREATED':
      // event.payload is narrowed to { userId: string; email: string }
      sendWelcomeEmail(event.payload.email);
      break;
    case 'USER_UPDATED':
      invalidateUserCache(event.payload.userId);
      break;
    case 'ORDER_PLACED':
      processOrder(event.payload.orderId, event.payload.items);
      break;
    case 'PAYMENT_RECEIVED':
      confirmPayment(event.payload.orderId, event.payload.amount);
      break;
    default:
      // exhaustiveCheck: if a new event type is added, this line errors
      const _exhaustive: never = event;
      throw new Error(`Unhandled event: ${(_exhaustive as any).type}`);
  }
}

Patrón Builder Type-Safe

El sistema de tipos de TypeScript puede asegurar que un builder produzca un objeto válido solo cuando todas las propiedades requeridas han sido establecidas. Este patrón usa mapped types para rastrear qué campos han sido configurados, haciendo que el método build() esté disponible solo cuando todos los campos requeridos están presentes.build() method available only when all required fields are present.

// Type-safe query builder
class QueryBuilder<T, Selected extends keyof T = never> {
  private selectFields: string[] = [];
  private whereConditions: string[] = [];

  select<K extends keyof T>(...fields: K[]): QueryBuilder<T, Selected | K> {
    this.selectFields.push(...fields.map(String));
    return this as any;
  }

  where<K extends keyof T>(field: K, op: '=' | '>' | '<', value: T[K]): this {
    this.whereConditions.push(`${String(field)} ${op} ?`);
    return this;
  }

  // Returns only the selected fields
  async execute(): Promise<Pick<T, Selected>[]> {
    // ... execute SQL query
    return [] as any;
  }
}

// Usage: result is typed as { name: string; email: string }[]
const users = await new QueryBuilder<User>()
  .select('name', 'email')
  .where('role', '=', Role.ADMIN)
  .execute();

Type Guards y Narrowing

Los type guards son verificaciones en runtime que estrechan un tipo dentro de un bloque condicional. TypeScript soporta narrowing nativo con typeof, instanceof e in, pero las funciones de type guard personalizadas (usando la palabra clave is) son esenciales para lógica de dominio compleja. Las funciones de aserción (asserts x is T) estrechan tipos para el resto del scope en lugar de solo dentro de un bloque if.typeof, instanceof, and in, but custom type guard functions (using the is keyword) are essential for complex domain logic. Assertion functions (asserts x is T) narrow types for the rest of the scope rather than just within an if block.

// Custom type guard for API responses
interface SuccessResponse<T> { ok: true; data: T; }
interface ErrorResponse { ok: false; error: string; code: number; }
type ApiResponse<T> = SuccessResponse<T> | ErrorResponse;

function isSuccess<T>(res: ApiResponse<T>): res is SuccessResponse<T> {
  return res.ok === true;
}

async function fetchUser(id: string): Promise<User> {
  const res: ApiResponse<User> = await api.get(`/users/${id}`);
  if (isSuccess(res)) {
    return res.data; // narrowed to SuccessResponse<User>
  }
  throw new AppError(res.error, res.code); // narrowed to ErrorResponse
}

// Assertion function: narrows for the rest of the scope
function assertDefined<T>(val: T | undefined, msg: string): asserts val is T {
  if (val === undefined) throw new Error(msg);
}

function processOrder(order: Order | undefined) {
  assertDefined(order, 'Order not found');
  // After assertion, order is narrowed to Order for the rest of this function
  console.log(order.id, order.items.length);
}

// Discriminated union guard with "in" operator
interface Dog { bark(): void; breed: string; }
interface Cat { meow(): void; color: string; }
type Pet = Dog | Cat;

function isDog(pet: Pet): pet is Dog {
  return 'bark' in pet;
}

6. Configuración Monorepo con Nx

Nx es un sistema de build optimizado para monorepos. Provee caché de computación, orquestación de tareas, análisis de grafo de dependencias y generadores de código. Para un monorepo de microservicios NestJS, Nx gestiona el orden de build, asegura que solo los servicios afectados se reconstruyan en cambios, y cachea artefactos de build local y remotamente.

Estructura del Monorepo

app-monorepo/
  apps/
    user-service/          # NestJS microservice
    billing-service/       # NestJS microservice
    notification-service/  # NestJS microservice
    admin-api/             # NestJS API gateway
  libs/
    shared/
      types/               # @myorg/types - shared domain types
      utils/               # @myorg/utils - shared utilities
      testing/             # @myorg/testing - test helpers and factories
    database/              # @myorg/database - TypeORM entities and migrations
    messaging/             # @myorg/messaging - ZeroMQ/Redis abstractions
    auth/                  # @myorg/auth - JWT validation, guards
  tools/
    generators/            # Custom Nx generators for scaffolding
  nx.json
  tsconfig.base.json       # Shared TypeScript config

Project References de TypeScript

Las project references ("references" en tsconfig) habilitan compilación incremental entre paquetes. Cuando @myorg/types cambia, solo los servicios que dependen de él se recompilan. Combinado con los comandos affected de Nx, esto reduce los tiempos de build en CI de 15 minutos a 2-3 minutos para pull requests típicos."references" in tsconfig) enable incremental compilation across packages. When @myorg/types changes, only services that depend on it are recompiled. Combined with Nx's affected commands, this reduces CI build times from 15 minutes to 2-3 minutes for typical pull requests.

// tsconfig.base.json - shared compiler options
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@myorg/types": ["libs/shared/types/src/index.ts"],
      "@myorg/utils": ["libs/shared/utils/src/index.ts"],
      "@myorg/database": ["libs/database/src/index.ts"],
      "@myorg/messaging": ["libs/messaging/src/index.ts"],
      "@myorg/auth": ["libs/auth/src/index.ts"]
    },
    "strict": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "declaration": true,
    "composite": true,
    "incremental": true
  }
}

// apps/user-service/tsconfig.json
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": { "outDir": "../../dist/apps/user-service" },
  "references": [
    { "path": "../../libs/shared/types" },
    { "path": "../../libs/database" },
    { "path": "../../libs/messaging" },
    { "path": "../../libs/auth" }
  ]
}

Pipeline de Tareas Nx

El pipeline de tareas de Nx asegura orden de build correcto y habilita paralelismo. El comando nx affected:build solo construye servicios afectados por el changeset actual. Combinado con caché distribuido (Nx Cloud), un desarrollador que pushea cambios a @myorg/types ve builds cacheados para servicios sin cambios y solo recompila los afectados.nx affected:build command only builds services affected by the current changeset. Combined with distributed caching (Nx Cloud), a developer pushing changes to @myorg/types sees cached builds for unchanged services and only recompiles the affected ones.

// nx.json - task pipeline configuration
{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],   // build dependencies first
      "cache": true,
      "inputs": ["production", "^production"]
    },
    "test": {
      "dependsOn": ["build"],
      "cache": true,
      "inputs": ["default", "^production"]
    },
    "lint": {
      "cache": true,
      "inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
    }
  },
  "namedInputs": {
    "production": ["default", "!{projectRoot}/**/*.spec.ts"],
    "default": ["{projectRoot}/**/*"]
  }
}
En producción, migrar de 16 repositorios separados a un solo monorepo Nx redujo el tiempo de refactoring cross-service de días a horas. Un renombramiento de un tipo compartido como SubscriptionStatus se propagó automáticamente a los 16 servicios, con TypeScript detectando cada uso en tiempo de compilación. Los tiempos de CI bajaron de 45 minutos (ejecutando todos los builds) a 4 minutos (solo servicios afectados).SubscriptionStatus propagated automatically across all 16 services, with TypeScript catching every usage at compile time. CI times dropped from 45 minutes (running all builds) to 4 minutes (only affected services).

7. Mejores Prácticas de tsconfig

Un tsconfig.json bien configurado es la base de un proyecto TypeScript mantenible. Más allá de los flags de modo estricto, el sistema de módulos, el target y la resolución de rutas tienen un impacto significativo en el rendimiento de build, tamaño del bundle y experiencia del desarrollador. Diferentes tipos de proyecto (librería, servicio Node.js, app React, monorepo) requieren configuraciones diferentes.tsconfig.json is the foundation of a maintainable TypeScript project. Beyond strict mode flags, the module system, target, and path resolution settings have significant impact on build performance, bundle size, and developer experience. Different project types (library, Node.js service, React app, monorepo) require different configurations.

Configuración para Servicio Node.js

// tsconfig.json - production Node.js microservice
{
  "compilerOptions": {
    "target": "ES2022",               // Node 18+ supports ES2022 natively
    "module": "NodeNext",              // Native ESM with .js extensions
    "moduleResolution": "NodeNext",    // Follows Node.js resolution algorithm
    "strict": true,
    "esModuleInterop": true,           // Correct CJS/ESM interop
    "skipLibCheck": true,              // Skip checking node_modules .d.ts
    "declaration": true,               // Generate .d.ts for library consumers
    "declarationMap": true,            // Source maps for declarations (go-to-definition)
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "resolveJsonModule": true,         // Import JSON files with types
    "noUncheckedIndexedAccess": true,  // Array/object access returns T | undefined
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true,
    "forceConsistentCasingInFileNames": true,
    "isolatedModules": true,           // Required for esbuild/swc transpilers
    "verbatimModuleSyntax": true       // Enforce explicit type-only imports
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.spec.ts"]
}

Configuración para Aplicación React

// tsconfig.json - React with Vite
{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",     // Vite/webpack resolution semantics
    "jsx": "react-jsx",                // React 17+ automatic JSX transform
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "isolatedModules": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,                    // Vite handles bundling
    "paths": {
      "@/*": ["./src/*"],
      "@components/*": ["./src/components/*"],
      "@hooks/*": ["./src/hooks/*"]
    }
  },
  "include": ["src"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

Configuración para Librería Compartida

// tsconfig.json - shared library published to npm
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "composite": true,                 // Required for project references
    "incremental": true,               // Cache compilation results
    "stripInternal": true              // Remove @internal JSDoc from .d.ts
  },
  "include": ["src/**/*"],
  "exclude": ["src/**/*.spec.ts"]
}
Dato clave: verbatimModuleSyntax (TypeScript 5.0+) reemplaza a importsNotUsedAsValues y preserveValueImports. Te obliga a usar import type { X } para imports solo de tipos, haciendo la intención explícita y habilitando mejor tree-shaking. Este es ahora el enfoque recomendado para todos los proyectos nuevos.verbatimModuleSyntax (TypeScript 5.0+) replaces importsNotUsedAsValues and preserveValueImports. It forces you to use import type { X } for type-only imports, making the intent explicit and enabling better tree-shaking. This is now the recommended approach for all new projects.

8. Migración desde JavaScript

Migrar un codebase de JavaScript a TypeScript se hace mejor de forma incremental. El principio clave es que cada paso debe dejar el codebase en un estado funcional. La opción allowJs de TypeScript permite que archivos .ts y .js coexistan en el mismo proyecto, para que puedas migrar archivo por archivo sin detener el desarrollo de features.allowJs option lets .ts and .js files coexist in the same project, so you can migrate file by file without stopping feature development.

Fases de Migración

// Phase 1: Setup - enable TypeScript alongside JavaScript
// tsconfig.json (initial migration config)
{
  "compilerOptions": {
    "allowJs": true,                   // Allow .js files in the project
    "checkJs": false,                  // Don't type-check .js files yet
    "strict": false,                   // Enable gradually
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"]
}

// Phase 2: Rename .js to .ts one module at a time, fix errors
// Start with leaf modules (no imports from other project files)
// Add explicit types to function signatures and exports

// Phase 3: Enable strict flags incrementally
// 1. "noImplicitAny": true        (biggest impact, most work)
// 2. "strictNullChecks": true     (second biggest impact)
// 3. "strictFunctionTypes": true  (usually few changes needed)
// 4. "strict": true               (enable remaining flags at once)

Estrategias Prácticas

La estrategia de migración más efectiva es tipar los límites primero: handlers de API, queries de base de datos e interfaces compartidas. Esto provee seguridad inmediata en los puntos donde ocurren la mayoría de errores en runtime. Las funciones helper internas pueden migrarse después con menos urgencia. Usa // @ts-expect-error como escape temporal con un comentario de seguimiento, no casts any que ocultan problemas permanentemente.// @ts-expect-error as a temporary escape hatch with a tracking comment, not any casts that hide problems permanently.

// Strategy 1: Type the boundaries first
// Before (JavaScript)
async function getUser(req, res) {
  const user = await db.query('SELECT * FROM users WHERE id = ?', [req.params.id]);
  res.json(user);
}

// After (TypeScript - typed boundaries)
import { Request, Response } from 'express';
import { User } from '@company/types';

async function getUser(req: Request<{ id: string }>, res: Response<User>) {
  const user = await db.query<User>('SELECT * FROM users WHERE id = ?', [req.params.id]);
  res.json(user);
}

// Strategy 2: Use @ts-expect-error with tracking, not blanket "any"
function legacyParser(data: unknown): ParsedResult {
  // @ts-expect-error TODO: MIGRATE-123 - legacy format handling
  return oldParser.parse(data);
}

// Strategy 3: Create a migration tracking script
// Count remaining .js files and untyped boundaries
// npx tsc --noEmit 2>&1 | grep "error TS" | wc -l  # Track error count over time

Checklist de Migración

Un checklist estructurado previene que la migración se estanque. Mide el progreso con una métrica simple: porcentaje de archivos .ts vs. archivos fuente totales, y la cantidad de // @ts-expect-error y anotaciones any explícitas. Ambos números deberían tender a cero con el tiempo..ts files vs. total source files, and the count of // @ts-expect-error and explicit any annotations. Both numbers should trend toward zero over time.

// Migration checklist for each service:
// [ ] Install typescript, @types/node, and framework @types packages
// [ ] Create tsconfig.json with allowJs: true, strict: false
// [ ] Configure build tool (esbuild/swc/tsc) to handle .ts files
// [ ] Rename entry point to .ts, fix immediate errors
// [ ] Add shared types package as dependency
// [ ] Migrate API route handlers (highest-value boundary)
// [ ] Migrate database layer (queries, entities, migrations)
// [ ] Migrate middleware and utility functions
// [ ] Enable noImplicitAny, fix all implicit any errors
// [ ] Enable strictNullChecks, add null handling
// [ ] Enable full strict: true
// [ ] Remove all @ts-expect-error comments
// [ ] Enable noUncheckedIndexedAccess for final safety pass
En producción, migramos 26 microservicios de JavaScript a TypeScript estricto en 4 meses. Priorizamos servicios por frecuencia de errores en logs de producción. Los primeros 3 servicios migrados eran responsables del 70% de errores en runtime. Después de la migración, esos servicios vieron una reducción del 60% en incidentes de producción. La inversión se pagó sola en el primer mes.

9. ECMAScript 2025 y Roadmap de TypeScript

ECMAScript 2025 (finalizado en junio 2025) trae adiciones significativas al lenguaje JavaScript, todas completamente soportadas en TypeScript. Combinado con el propio roadmap de TypeScript (Project Corsa y TS 7.0), estos cambios transforman cómo escribimos JavaScript tipado.

Iterator Helpers

ES2025 agrega .map(), .filter(), .take(), .drop(), .reduce(), .toArray() y más directamente en iteradores. Habilitan cadenas de evaluación perezosa sin convertir a arrays primero, lo cual es crítico para procesar grandes datasets o secuencias infinitas..map(), .filter(), .take(), .drop(), .reduce(), .toArray(), and more directly on iterators. These enable lazy evaluation chains without converting to arrays first, which is critical for processing large datasets or infinite sequences.

// Iterator Helpers (ES2025) - lazy evaluation on iterators
function* fibonacci(): Generator<number> {
  let [a, b] = [0, 1];
  while (true) { yield a; [a, b] = [b, a + b]; }
}

// Lazy: only computes what's needed
const result = fibonacci()
  .drop(5)           // skip first 5
  .filter(n => n % 2 === 0)  // only even
  .take(3)           // first 3 matching
  .toArray();        // materialize: [8, 34, 144]

Métodos de Set

ES2025 agrega union(), intersection(), difference(), symmetricDifference(), isSubsetOf() e isSupersetOf() al prototipo de Set. Se acabó la iteración manual o conversión a arrays para operaciones de conjuntos.union(), intersection(), difference(), symmetricDifference(), isSubsetOf(), and isSupersetOf() to the Set prototype. No more manual iteration or conversion to arrays for set operations.

// Set methods (ES2025)
const frontend = new Set(['react', 'vue', 'angular', 'svelte']);
const used = new Set(['react', 'angular', 'nestjs', 'fastify']);

frontend.intersection(used);       // Set {'react', 'angular'}
frontend.union(used);              // Set {'react','vue','angular','svelte','nestjs','fastify'}
frontend.difference(used);         // Set {'vue', 'svelte'}
used.isSubsetOf(frontend);         // false
new Set(['react']).isSubsetOf(used); // true

Gestión Explícita de Recursos (using / await using)

Las declaraciones using y await using proveen limpieza automática para recursos como handles de archivos, conexiones de base de datos y locks. Cuando la variable sale del scope, su método [Symbol.dispose]() o [Symbol.asyncDispose]() se llama automáticamente -- similar al using de C# o el with de Python.using and await using declarations provide automatic cleanup for resources like file handles, database connections, and locks. When the variable goes out of scope, its [Symbol.dispose]() or [Symbol.asyncDispose]() method is called automatically — similar to C#'s using or Python's with.

// Explicit Resource Management (ES2025 + TypeScript)
class DatabaseConnection implements Disposable {
  [Symbol.dispose]() {
    this.release(); // auto-called when leaving scope
  }
}

function processData() {
  using conn = getConnection();  // auto-released at end of block
  using lock = acquireLock('resource-1');
  conn.query('SELECT ...');
} // conn.release() and lock.release() called automatically

// Async version for I/O resources
async function streamFile() {
  await using file = await openFile('data.csv');
  for await (const line of file) { process(line); }
} // file handle closed automatically

Promise.try, RegExp.escape, Array.fromAsync, JSON Modules

Promise.try(fn) envuelve funciones síncronas o asíncronas en una Promise (reemplazando el patrón new Promise(resolve => resolve(fn()))). RegExp.escape(str) escapa de forma segura caracteres especiales de regex. Array.fromAsync() crea arrays desde iterables asíncronos. Los JSON modules permiten import data from '.

// Promise.try - safe wrapper for sync-or-async functions
const result = await Promise.try(() => maybeThrows());

// JSON Modules - import with type assertion
import config from './config.json' with { type: 'json' };
// config is typed based on the JSON structure

El Operador satisfies (TS 4.9+)

El operador satisfies verifica que un valor conforma a un tipo sin ensancharlo. Es ahora un patrón ampliamente adoptado para objetos de configuración, mapas de rutas y cualquier escenario donde quieres tanto verificación de tipos como inferencia precisa de literales.satisfies operator checks that a value conforms to a type without widening it. This is now a widely adopted pattern for configuration objects, route maps, and any scenario where you want both type checking and precise literal inference.

// satisfies: type-check without widening
type Route = { path: string; method: 'GET' | 'POST'; handler: Function };

const routes = {
  users:  { path: '/users',  method: 'GET',  handler: getUsers },
  create: { path: '/users',  method: 'POST', handler: createUser },
} satisfies Record<string, Route>;

// routes.users.method is inferred as 'GET' (not 'GET' | 'POST')
// Type error if you add a route with method: 'PUT' (not in Route)

Roadmap de TypeScript: Project Corsa y TS 7.0

El compilador de TypeScript está siendo reescrito en Go (Project Corsa), apuntando a verificación de tipos y builds 10x más rápidos. TypeScript 7.0 se espera para mediados de 2026 con strict por defecto (no más "strict": true necesario), target ES5 eliminado, y el compilador nativo Go como predeterminado. Para proyectos nuevos, actualiza el target de tsconfig.json de ES2022 a ES2025 para acceder a todas las nuevas características."strict": true needed), ES5 target dropped, and the native Go compiler as the default. For new projects, update tsconfig.json target from ES2022 to ES2025 to access all new features.

// Updated tsconfig.json for modern Node.js (2025+)
{
  "compilerOptions": {
    "target": "ES2025",              // was ES2022 -- enables ES2025 features
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,                  // will be default in TS 7.0
    "verbatimModuleSyntax": true,
    "noUncheckedIndexedAccess": true,
    "erasableSyntaxOnly": true       // for native Node.js TS execution
  }
}
La combinación de características ES2025 y el compilador en evolución de TypeScript representa un cambio significativo. Los iterator helpers eliminan categorías enteras de asignaciones de arrays intermedios. La gestión explícita de recursos (using) previene fugas de recursos que antes requerían patrones try/finally. Y con Project Corsa, el cuello de botella de verificación de tipos en monorepos grandes se reducirá drásticamente.using) prevents resource leaks that previously required try/finally patterns. And with Project Corsa, the type-checking bottleneck in large monorepos will be dramatically reduced.

Últimas Actualizaciones (Junio 2026)

TypeScript 5.8: erasableSyntaxOnly y Ejecución Directa en Node.js

TypeScript 5.8 (marzo 2025) introdujo el flag --erasableSyntaxOnly, que restringe el código a construcciones TypeScript que pueden eliminarse de forma segura sin comportamiento en runtime -- prohibiendo enums, namespaces, propiedades de parámetros y formas de import legacy que emiten código en runtime. Este flag permite ejecutar TypeScript directamente en Node.js 23.6+ con --experimental-strip-types (o Node.js 24+ donde está habilitado por defecto), eliminando la necesidad de ts-node o cualquier paso de compilación en desarrollo. Combinado con verbatimModuleSyntax, esto crea un workflow de desarrollo sin build para proyectos TypeScript puros.--erasableSyntaxOnly flag, which restricts code to TypeScript constructs that can be safely erased without runtime behavior -- prohibiting enums, namespaces, parameter properties, and legacy import forms that emit runtime code. This flag enables running TypeScript directly in Node.js 23.6+ with --experimental-strip-types (or Node.js 24+ where it is enabled by default), eliminating the need for ts-node or any compilation step in development. Combined with verbatimModuleSyntax, this creates a zero-build development workflow for pure TypeScript projects.

TypeScript 5.9: Import Defer y Hovers Expandibles

TypeScript 5.9 (lanzado Q1 2026) introdujo la sintaxis import defer para la propuesta de evaluación diferida de módulos de ECMAScript, dando control granular sobre cuándo se ejecutan los efectos secundarios de módulos. El flag de compilador strictInference refuerza el narrowing de tipos condicionales. La experiencia de desarrollo mejoró significativamente con hovers expandibles en editores (expandir/contraer información de tipos al pasar el mouse) y longitud de hover configurable vía el servidor de lenguaje. Las ganancias de rendimiento de build para proyectos grandes son medibles, con integración más estrecha con el próximo compilador nativo Corsa.import defer syntax for ECMAScript's deferred module evaluation proposal, giving fine-grained control over when module side-effects execute. The strictInference compiler flag tightens conditional type narrowing. Developer experience improved significantly with expandable hovers in editors (expand/collapse type information on hover) and configurable hover length via the language server. Build performance gains for large projects are measurable, with tighter integration with the upcoming Corsa native compiler.

TypeScript 7.0 Beta Publicado (21 de Abril, 2026)

Microsoft publicó oficialmente el TypeScript 7.0 Beta el 21 de abril de 2026. El compilador ha sido completamente reescrito en Go (Project Corsa), entregando compilación 10x más rápida que TypeScript 6.0. La ejecución nativa elimina el warmup del JIT de V8 y las pausas de garbage collection, mientras que las goroutines de Go habilitan verificación de tipos paralela en todos los núcleos de CPU. Los benchmarks muestran el proyecto Sentry bajando de 133 segundos a 16 segundos, y el codebase de VS Code de 89 segundos a menos de 9 segundos. El beta ha sido validado por Bloomberg, Canva, Figma, Google, Lattice, Linear, Miro, Notion, Slack, Vanta, Vercel y VoidZero. Instala via npm install @typescript/native-preview@beta o usa el CLI tsgo. TypeScript 7.0 es semánticamente idéntico a TypeScript 6.0 -- el último release del compilador basado en JavaScript, publicado estable el 23 de marzo de 2026 como release puente que depreca las import assertions (usa la sintaxis with) y agrega el flag de migración --stableTypeOrdering -- por lo que el código existente funciona sin cambios.

TypeScript 7.0 Beta está listo para producción en muchos flujos de trabajo diarios y pipelines de CI. Para acción inmediata: instala @typescript/native-preview@beta y mide tu proyecto. La aceleración de 10x es transformadora para monorepos donde la verificación de tipos actualmente toma minutos. Adopta el soporte de import defer de TS 5.9, actualiza el target de tsconfig a ES2025, y testea con el beta para identificar incompatibilidades de API en tu cadena de herramientas antes del release estable -- Microsoft planea publicar TypeScript 7.0 estable dentro de los dos meses posteriores al beta de abril (fines de junio / principios de julio de 2026), con un release candidate unas semanas antes.@typescript/native-preview@beta and benchmark your project. The 10x speedup is transformative for monorepos where type-checking currently takes minutes. Adopt TS 5.9's import defer support, update tsconfig target to ES2025, and test with the beta to identify any API incompatibilities in your toolchain before the stable release -- Microsoft plans to ship TypeScript 7.0 stable within two months of the April beta (late June / early July 2026), with a release candidate a few weeks prior.

Más Guías