Guía de TypeScript: De 0 a 100

Guía de TypeScript: De 0 a 100

Desde la primera línea de tipos hasta producción: instalación, ecosistema, frameworks, proyectos reales y recursos en español.

FECHA:
ACTUALIZADO:
AUTOR:Jorge Beneyto Castelló
LECTURA:52 MINUTOS DE LECTURA

Guía de TypeScript: De 0 a 100


¿Qué es TypeScript?

TypeScript es un lenguaje de programación de tipado estático opcional que compila a JavaScript. Fue creado por Anders Hejlsberg (arquitecto de Turbo Pascal y C#) en Microsoft y lanzado públicamente en octubre de 2012. Es un superset de JavaScript: todo JS válido es TS válido, y el compilador añade análisis de tipos en tiempo de desarrollo sin modificar el comportamiento runtime.

Principios clave:

  • Tipado estructural (duck typing en compilación): si dos objetos tienen la misma forma, son del mismo tipo — no importa el nombre de la clase
  • Inferencia de tipos: TypeScript deduce tipos automáticamente sin necesidad de anotarlos siempre
  • Errores en compilación, no en runtime: los errores de tipo se detectan antes de ejecutar
  • Sin costo runtime: los tipos se borran en la compilación; el JS generado no tiene overhead de tipos

¿Dónde se usa?

  • Frontend web: React, Vue, Angular, Svelte — todos con soporte TS nativo
  • Backend: Node.js con Express, Fastify, NestJS, Hono, tRPC
  • Full-stack: Next.js, Nuxt, Remix, SvelteKit — frameworks que integran frontend y backend
  • Infraestructura: AWS CDK, Terraform CDK, Pulumi están escritos en TypeScript
  • Herramientas: Vite, Astro, Turborepo, Playwright, Prisma, Drizzle
  • Mobile: React Native, Capacitor, NativeScript

¿Quién lo usa? Microsoft (VS Code, Teams), Google (Angular), Figma (aplicación de escritorio), Slack (desktop y web), Airbnb, Asana, Notion, Vercel, Linear.

Fuente: TypeScript Handbook — The Basics, TypeScript GitHub — History


Prerrequisitos

Antes de empezar, necesitas:

  • Conocimientos básicos de terminal: navegar directorios, ejecutar comandos, usar un editor de texto
  • El lenguaje/herramienta instalado en tu sistema (sigue la sección de Instalación si aún no lo tienes)

Si no cumples algún requisito, no te preocupes: cada sección te guiará paso a paso.

¿Cómo empezar? Instalación en todos los entornos

npm (Node.js — necesario para compilar)

npm install -g typescript
tsc --version
# En un proyecto
npm init -y
npm install --save-dev typescript @types/node
npx tsc --init  # crea tsconfig.json

npx (ejecutar sin instalar global)

npx tsc --version
npx tsc archivo.ts        # Compila un archivo
npx tsc --init --strict   # Crea tsconfig con strict mode

ts-node (ejecutar TS directamente en Node)

npm install -g ts-node
ts-node script.ts

tsx (alternativa moderna, más rápida)

npm install -g tsx
tsx script.ts    # Usa esbuild bajo el capó

Bun (runtime nativo con TS incorporado)

curl -fsSL https://bun.sh/install | bash
bun run archivo.ts
bun init

Bun entiende TypeScript nativamente. No necesita tsc ni tsconfig.json.

Deno (runtime nativo con TS incorporado)

curl -fsSL https://deno.land/install.sh | sh
deno run main.ts

Deno ejecuta TypeScript nativamente con TSC integrado.

Docker

FROM node:22-slim AS build
WORKDIR /app
COPY package*.json tsconfig.json ./
RUN npm ci
COPY . .
RUN npx tsc

FROM node:22-slim
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]

Versiones (nvm)

nvm install node    # Última versión de Node (contiene TS)
nvm use node

Alias útiles

alias tsc='npx tsc'
alias tsn='npx tsx'
alias tsb='npx tsc --noEmit'
alias tsbuild='npm run build'
alias tsclean='rm -rf dist/'

Fuente: TypeScript Download, Bun docs, Deno docs


Escala de aprendizaje: de 0 a 100

Nivel 0–15: Fundamentos absolutos

Qué aprender:

  • Sintaxis básica: let, const, tipos primitivos, inferencia
  • Funciones: parámetros tipados, retorno tipado
  • Arrays, objetos e interfaces básicas
  • tsc básico: compilar .ts a .js
  • Conceptos clave: los tipos se borran al compilar. Tipado estructural. const impide reasignación, no inmutabilidad.

Proyecto: Conversor de temperaturas — función que convierte Celsius a Fahrenheit. Interfaz para parámetros. Tipos literales para unidades. CLI simple.

Nivel 15–30: Union types, narrowing y arrays

Qué aprender:

  • Union types: string | number
  • Type narrowing: typeof, in, instanceof, truthy/falsy
  • Tuplas, readonly arrays, as const
  • Enums, unknown vs any
  • Conceptos clave: narrowing automático en switch. any desactiva el checker. as const para tipos literales profundos.

Proyecto: Validador de formularios — función que valida campos (email, teléfono, url). Union type para tipos de campo. Type guards personalizados.

Nivel 30–45: Interfaces, types, genéricos y POO

Qué aprender:

  • interface vs type, extensión, declaration merging
  • Genéricos: básicos, constraints, keyof
  • Clases: public/private/protected, readonly, abstract
  • implements, composición sobre herencia
  • Conceptos clave: las interfaces soportan declaration merging. Los genéricos se borran en compilación.

Proyecto: Gestor de tareas — clases TareaBase, TareaSimple, TareaConFecha. Interfaz Exportable. Genéricos en Repositorio<T>. Enum EstadoTarea.

Nivel 45–60: Tipos avanzados y utility types

Qué aprender:

  • Utility types: Partial, Required, Pick, Omit, Record
  • Mapped types, Conditional types, Template literal types
  • keyof, typeof, satisfies, infer
  • Branded types, DeepPartial
  • Conceptos clave: los utility types son mapped/conditional types de la stdlib. satisfies verifica sin ensanchar.

Proyecto: API client tipado — función genérica createApiClient<T>(). Template literal types para rutas. Conditional types para respuesta según método HTTP.

Nivel 60–75: Backend, BD y async

Qué aprender:

  • Express/Fastify con TypeScript
  • Prisma o Drizzle: schema, queries, migrations
  • Zod para validación, async/await, Promise.all
  • Conceptos clave: el event loop no tiene GIL. Zod infiere tipos automáticamente.

Proyecto: API de recetas — Fastify + Prisma + PostgreSQL. Zod para validación. CRUD tipado. Tests con vitest.

Nivel 75–90: Tiempo real, workers y producción

Qué aprender:

  • WebSockets con Socket.IO
  • worker_threads en Node.js, Web Workers
  • Docker multi-etapa, CI/CD, ESLint, vitest
  • Conceptos clave: Workers tienen su propio V8 isolate. --strict desde el día 1.

Proyecto: Dashboard de monitorización — Fastify + Socket.IO + worker_threads. Métricas del sistema. Redis. Tests.

Nivel 90–100: Arquitectura y edge

Qué aprender:

  • tRPC, arquitectura hexagonal
  • BullMQ para colas, GraphQL con Pothos
  • Edge computing (Cloudflare Workers, Deno Deploy)
  • Monorepos con Turborepo + Project References
  • Conceptos clave: edge tiene limitaciones (sin Node APIs). tRPC elimina REST/GraphQL. Monorepos necesitan composite: true.

Proyecto: Sistema de notificaciones — tRPC, BullMQ, Edge Functions, Turborepo, OpenTelemetry.

🔗 Para saber más


Primeros pasos y configuración del entorno

Si prefieres ver directamente el “Hola Mundo” en TypeScript, consulta El Atlas del Hola Mundo.

tsconfig.json — el corazón de la configuración

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "noUncheckedIndexedAccess": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "dist"]
}

Opciones críticas:

  • strict: true — activa todas las comprobaciones estrictas
  • noUncheckedIndexedAccess: true — arr[0] es T | undefined
  • target: "ES2022" — a qué versión de JS compilar
  • module: "NodeNext" — módulos ES nativos con Node.js
  • declaration: true — genera .d.ts para librerías

Editores recomendados

Los IDEs y editores más usados son VSCode, JetBrains y Neovim. Extensiones y configuraciones recomendadas.

VSCode + TypeScript

VSCode incluye TypeScript nativamente (TSServer). Comandos útiles:

  • Ctrl+Shift+P → TypeScript: Select TypeScript Version
  • Ctrl+Shift+P → TypeScript: Restart TS Server
  • Ctrl+. — quick fix, auto-import

ESLint + typescript-eslint

npm install --save-dev eslint @eslint/js typescript-eslint
// eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';

export default tseslint.config(
  eslint.configs.recommended,
  ...tseslint.configs.recommended,
  {
    rules: {
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
      '@typescript-eslint/explicit-function-return-type': 'warn',
      '@typescript-eslint/no-explicit-any': 'warn',
    },
  },
);

Project References (monorepos)

{
  "files": [],
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/web" },
    { "path": "./packages/api" }
  ]
}
npx tsc --build
npx tsc --build --clean
npx tsc --build --watch

Estructura de proyecto

mi-proyecto/
├── src/
│   ├── index.ts
│   ├── types/
│   │   └── usuario.ts
│   └── utils/
│       └── helpers.ts
├── dist/
├── tests/
├── tsconfig.json
├── eslint.config.js
└── package.json

Fuente: TypeScript tsconfig docs, typescript-eslint docs


Paradigmas de programación

TypeScript hereda los paradigmas de JavaScript y añade tipado estático, lo que lo hace único en su combinación.

ParadigmaSoporte nativoLibreríasEjemplo
Imperativo / proceduralSí—let x = 0; for (let i = 0; i < 10; i++) { x += i; }
Orientado a objetosSí (clases, herencia, interfaces)NestJS, TypeORMclass Usuario extends Entidad
FuncionalSí (funciones puras, HOF, inmutabilidad)fp-ts, Effect TS[1,2,3].map(x => x * 2).filter(x => x > 3)
DeclarativoParcial (JSX, configs)React, Prisma schema<Button onClick={fn}>Click</Button>
ReactivoSí (Observables, async generadores)RxJS, SignalsfromEvent(btn, 'click').pipe(map(e => e.x))

Paradigma principal: multiparadigma con inclinación a POO + funcional. El ecosistema moderno (React hooks, composición sobre herencia, pipes) favorece el estilo funcional, mientras que NestJS y TypeORM usan POO clásica con decoradores.

Recomendación para empezar: comienza con imperativo + funcional básico (tipos, funciones, arrays), luego añade POO (clases, interfaces) y finalmente tipos avanzados.

Fuente: MDN — JavaScript Paradigms, TypeScript Handbook — Classes


Tipos de datos y variables

Sistema de tipos estructural

¿Qué es?

TypeScript usa tipado estructural (structural typing): si dos tipos tienen la misma forma, son compatibles. No importa el nombre ni la herencia. Es diferente del tipado nominal de Java o C#.

La inferencia de tipos deduce automáticamente el tipo sin anotaciones explícitas.

Sintaxis básica

interface Persona { nombre: string; edad: number; }
interface Empleado { nombre: string; edad: number; salario: number; }

const e: Empleado = { nombre: "Luis", edad: 28, salario: 50000 };
const p2: Persona = e;  // OK: estructuralmente compatible

// Inferencia
let x = 5;           // x: number
let nombre = "Ana";  // nombre: string
const nums = [1, 2, 3].map(n => n * 2);  // n inferido como number

🧪 Cómo probarlo

npx tsx -e "
interface A { x: number }
interface B { x: number }
const b: B = { x: 10 };
const a: A = b;  // Compatible por estructura
console.log(a.x); // 10
"

💡 Memoria y rendimiento

Los tipos se borran en compilación — no hay costo runtime. El tipado estructural se evalúa en compilación, no afecta al rendimiento. La inferencia no añade overhead.

✅ Buenas prácticas

  • ❌ No uses any — desactiva el checker completamente
  • ✅ Prefiere unknown cuando no sepas el tipo
  • ✅ Activa strict: true desde el día 1
  • ✅ Usa inferencia siempre que puedas; anota solo parámetros de función

🏗️ Metodología

El tipado estructural es ideal para duck typing y APIs flexibles. Úsalo cuando trabajes con datos externos (JSON, APIs) donde no controlas la herencia. Para sistemas que necesitan identidad nominal, usa branded types.

🔗 Para saber más

Tipos primitivos

¿Qué es?

TypeScript tiene los mismos primitivos que JavaScript con tipos literales que permiten valores concretos como tipos.

Sintaxis básica

const nombre: string = "Ana";
const edad: number = 30;
const activo: boolean = true;
const id: symbol = Symbol("id");
const big: bigint = 100n;
let nulo: null = null;
let indefinido: undefined = undefined;

// Tipos literales
type Direccion = "norte" | "sur" | "este" | "oeste";
type Par = 2 | 4 | 6 | 8 | 10;
const dir: Direccion = "norte";

🧪 Cómo probarlo

npx tsx -e "
type Status = 'activo' | 'inactivo';
const s: Status = 'activo';
console.log(typeof s, s);
"

💡 Memoria y rendimiento

Primitivos son inmutables y se almacenan por valor en la pila (stack). Objetos van al heap. string y number tienen optimizaciones del motor V8 (string interning, Smi para enteros pequeños).

✅ Buenas prácticas

  • ✅ Usa as const para tipos literales profundos
  • ❌ No uses number para IDs que representan conceptos distintos — usa branded types
  • ✅ null y undefined son tipos distintos; úsalos explícitamente

🏗️ Metodología

Los tipos literales son ideales para estados finitos (status, direcciones, configuraciones). Para valores que cambian en runtime, usa tipos primitivos generales.

🔗 Para saber más

Objetos, arrays y tuplas

¿Qué es?

Objetos se definen con interfaces o types. Arrays con T[] o Array<T>. Tuplas son arrays de longitud y tipos fijos.

Sintaxis básica

// Objetos
interface Usuario { nombre: string; edad: number; }
type Usuario2 = { nombre: string; edad: number; };

// Arrays
const nums: number[] = [1, 2, 3];
const strs: Array<string> = ["a", "b"];
const readonly: ReadonlyArray<number> = [1, 2, 3];

// Tuplas
type Rango = [inicio: number, fin: number];  // Labeled tuple
const r: Rango = [0, 100];
type Variadica<T extends any[]> = [...T, string, number];
type Result = Variadica<[boolean]>;  // [boolean, string, number]

🧪 Cómo probarlo

npx tsx -e "
type Rango = [number, number];
const r: Rango = [0, 100];
console.log(r);
"

💡 Memoria y rendimiento

Arrays homogéneos (todos del mismo tipo) son más rápidos en V8. Las tuplas tienen ligero overhead de tipo en compilación, no en runtime. Los tipos se borran: una tupla [string, number] es un array JS normal.

✅ Buenas prácticas

  • ✅ Usa ReadonlyArray<T> o readonly T[] para arrays inmutables
  • ✅ Prefiere tuplas con labeled para claridad
  • ❌ Evita arrays heterogéneos sin tupla

🏗️ Metodología

Objetos: para modelar entidades. Arrays: colecciones homogéneas. Tuplas: pares clave-valor, rangos, coordenadas.

🔗 Para saber más

Uniones, intersecciones y narrowing

¿Qué es?

Uniones (|) permiten que un valor sea de varios tipos. Intersecciones (&) combinan tipos. Narrowing es el proceso por el que TypeScript reduce el tipo basándose en comprobaciones.

Sintaxis básica

// Unión
type ID = string | number;

// Intersección
type Admin = Usuario & { permisos: string[] };

// Narrowing automático
function procesar(valor: string | number | Date) {
  if (typeof valor === "string") return valor.toUpperCase();
  if (valor instanceof Date) return valor.getFullYear();
  return valor * 2;
}

// Uniones discriminadas
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle": return Math.PI * shape.radius ** 2;
    case "rectangle": return shape.width * shape.height;
  }
}

🧪 Cómo probarlo

npx tsx -e "
type Estado = 'ok' | 'error';
type Resultado = { estado: Estado; datos?: string; error?: string };
const r: Resultado = { estado: 'ok', datos: 'funciona' };
console.log(r);
"

💡 Memoria y rendimiento

Narrowing es puramente en compilación. El código JS generado no tiene narrowing — solo las comprobaciones runtime que escribiste.

✅ Buenas prácticas

  • ✅ Usa uniones discriminadas para modelar estados (loading, success, error)
  • ❌ No anides narrowing excesivo — refactoriza a type guards
  • ✅ Usa never para exhaustiveness checking

🏗️ Metodología

Uniones discriminadas + switch = la forma más segura de modelar máquinas de estado. Evita jerarquías de clases complejas cuando puedas usar una unión.

🔗 Para saber más

Enums

¿Qué es?

Enums permiten definir un conjunto de constantes con nombre. TypeScript soporta enums numéricos y de string.

Sintaxis básica

// Numérico
enum Color { Rojo, Verde, Azul }
console.log(Color.Rojo);    // 0
console.log(Color[0]);      // "Rojo" (reverse mapping)

// String
enum Direccion { Norte = "N", Sur = "S", Este = "E", Oeste = "O" }

// Const enum (sin overhead runtime)
const enum Status { Activo = "activo", Inactivo = "inactivo" }

🧪 Cómo probarlo

npx tsx -e "
enum Color { Rojo, Verde, Azul }
const c: Color = Color.Verde;
console.log(c);  // 1
"

💡 Memoria y rendimiento

Los enums numéricos generan código JS (objeto con reverse mapping). const enum no genera código — los valores se inlinan. String enums siempre generan código.

✅ Buenas prácticas

  • ✅ Prefiere uniones de string literales sobre enums en APIs públicas
  • ✅ Usa const enum solo en código interno
  • ❌ No uses enums numéricos mixtos (con y sin inicializador)

🏗️ Metodología

Usa enums para conjuntos cerrados de valores que se serializan (guardan en BD, API). Para valores solo en frontend, prefiere uniones literales.

🔗 Para saber más

unknown vs any vs never vs void

¿Qué es?

Cuatro tipos especiales que controlan cómo TypeScript maneja valores desconocidos, imposibles o vacíos.

Sintaxis básica

// any: desactiva el checker — EVITAR
let anyVal: any = 42;
anyVal.toUpperCase(); // Sin error (runtime: TypeError)

// unknown: tipo seguro para valores desconocidos
let unkVal: unknown = 42;
// unkVal.toUpperCase();  // Error
if (typeof unkVal === "string") unkVal.toUpperCase(); // OK

// never: valores que nunca ocurren
function error(msg: string): never { throw new Error(msg); }

// void: funciones que no retornan valor útil
function log(msg: string): void { console.log(msg); }

🧪 Cómo probarlo

npx tsx -e "
function assertNever(x: never): never { throw new Error('Inalcanzable'); }
type E = 'a' | 'b';
function f(e: E) { if (e === 'a') return 1; if (e === 'b') return 2; return assertNever(e); }
console.log(f('a'));
"

💡 Memoria y rendimiento

any y unknown son idénticos en runtime (sin costo). never no genera código. void es undefined en runtime.

✅ Buenas prácticas

  • ❌ Nunca uses any explícitamente. Configura @typescript-eslint/no-explicit-any: "error"
  • ✅ Usa unknown para datos externos (APIs, JSON parse)
  • ✅ Usa never para exhaustiveness checking en switches

🏗️ Metodología

unknown → verificar → usar. Es la cadena segura para datos externos. never como comprobación de compilación para mantener tu código a prueba de cambios.

🔗 Para saber más


Control de flujo y modularidad

Condicionales con narrowing

¿Qué es?

TypeScript extiende los condicionales JS con narrowing automático: dentro de un bloque if, el tipo se estrecha basándose en la condición.

Sintaxis básica

function procesar(valor: string | number | Date | null) {
  if (typeof valor === "string") return valor.toUpperCase();
  if (valor instanceof Date) return valor.getFullYear();
  if (valor == null) return "nulo";
  return valor * 2;
}

// Uniones discriminadas + switch
type Evento =
  | { tipo: "click"; x: number; y: number }
  | { tipo: "keydown"; tecla: string }
  | { tipo: "focus" };

function manejar(e: Evento) {
  switch (e.tipo) {
    case "click": return `Click en ${e.x},${e.y}`;
    case "keydown": return `Tecla: ${e.tecla}`;
    case "focus": return "Focus";
  }
}

🧪 Cómo probarlo

npx tsx -e "
function f(x: string | number) {
  if (typeof x === 'string') return x.length;
  return x * 2;
}
console.log(f('hola'), f(5));
"

💡 Memoria y rendimiento

Narrowing no tiene costo runtime — el código JS generado solo tiene las comprobaciones que escribiste. El switch con uniones discriminadas compila a un switch JS normal.

✅ Buenas prácticas

  • ✅ Usa uniones discriminadas + switch para máquinas de estado
  • ✅ Usa never para exhaustiveness checking
  • ❌ No abuses de type guards innecesarios — deja que TS infiera

🔗 Para saber más

Bucles

¿Qué es?

TypeScript hereda los bucles de JavaScript con tipos inferidos en cada iteración.

Sintaxis básica

const nums: number[] = [1, 2, 3, 4, 5];

// for clásico
for (let i = 0; i < nums.length; i++) {
  console.log(nums[i]);  // nums[i]: number | undefined (con noUncheckedIndexedAccess)
}

// for...of (recomendado)
for (const num of nums) {
  console.log(num);  // num: number
}

// forEach con tipos
nums.forEach((num, index) => console.log(index, num));

// Métodos de iteración tipados
const dobles = nums.map(n => n * 2);          // number[]
const pares = nums.filter(n => n % 2 === 0);  // number[]
const suma = nums.reduce((a, b) => a + b, 0); // number

🧪 Cómo probarlo

npx tsx -e "
const nums = [1, 2, 3];
console.log(nums.map(n => n * 2));
"

🔗 Para saber más

Excepciones

¿Qué es?

TypeScript no tiene checked exceptions. Los errores se manejan con try/catch y el catch por defecto es unknown (desde TS 4.0).

Sintaxis básica

class AppError extends Error {
  constructor(public codigo: number, mensaje: string) {
    super(mensaje);
    this.name = "AppError";
  }
}

function dividir(a: number, b: number): number {
  if (b === 0) throw new AppError(400, "División por cero");
  return a / b;
}

try {
  console.log(dividir(10, 0));
} catch (error) {
  if (error instanceof AppError) {
    console.error(`Error ${error.codigo}: ${error.message}`);
  } else if (error instanceof Error) {
    console.error(error.message);
  }
}

🧪 Cómo probarlo

npx tsx -e "
try { throw new Error('test'); }
catch (e) { if (e instanceof Error) console.log(e.message); }
"

✅ Buenas prácticas

  • ✅ Crea clases de error personalizadas con código
  • ✅ En catch, verifica el tipo antes de usar
  • ❌ No captures errores genéricos sin verificar

🔗 Para saber más

Módulos y organización

¿Qué es?

TypeScript sigue el sistema de módulos ES (import/export) y añade tipos para imports.

Sintaxis básica

// math.ts
export function sumar(a: number, b: number): number { return a + b; }
export const PI = 3.1416;
export type Operacion = (a: number, b: number) => number;

// app.ts
import { sumar, PI, type Operacion } from "./math.js";

// Re-export
export { sumar } from "./math.js";
export type { Operacion } from "./math.js";

// Import tipo-only (más seguro)
import type { Usuario } from "./types.js";

🧪 Cómo probarlo

npx tsx -e "
export function hola(n: string): string { return 'Hola ' + n; }
"

✅ Buenas prácticas

  • ✅ Usa import type para imports que solo son tipos (se borran en compilación)
  • ✅ Usa barriles (index.ts) para exportar múltiples módulos
  • ✅ Activa verbatimModuleSyntax en tsconfig

🔗 Para saber más


Sistema de archivos

TypeScript usa las mismas APIs de Node.js que JavaScript, con tipos para fs, path, streams.

¿Qué es?

El módulo fs de Node.js tiene tipos incluidos en @types/node. Soporta síncrono, callback y promesas.

Sintaxis básica

import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
import { readFile, writeFile, readdir, mkdir } from "node:fs/promises";
import { join, dirname, basename, extname } from "node:path";

// Síncrono (tipado)
const data: string = readFileSync("datos.json", "utf-8");
const parsed: unknown = JSON.parse(data);

// Async (tipado)
async function leerJSON<T>(path: string): Promise<T> {
  const content = await readFile(path, "utf-8");
  return JSON.parse(content) as T;
}

// Directorios
const files: string[] = await readdir("./src");
for (const file of files) {
  const ext = extname(file);
  if (ext === ".ts") console.log(`TypeScript: ${file}`);
}

🧪 Cómo probarlo

npx tsx -e "
import { writeFileSync, readFileSync } from 'node:fs';
writeFileSync('/tmp/test.txt', 'Hola TypeScript');
console.log(readFileSync('/tmp/test.txt', 'utf-8'));
"

💡 Memoria y rendimiento

Usa fs.promises para no bloquear el event loop. Para archivos grandes, usa streams. Los tipos no afectan rendimiento.

✅ Buenas prácticas

  • ✅ Prefiere fs/promises sobre callbacks
  • ✅ Usa path.join para rutas multiplataforma
  • ✅ Tipa los datos parseados con unknown + validación (Zod)

🏗️ Metodología

File system síncrono: scripts y CLIs. Async: servidores y APIs. Streams: archivos grandes (>100MB).

🔗 Para saber más


Algoritmos y estructuras de datos

¿Qué es?

TypeScript hereda las estructuras de datos de JavaScript con tipos genéricos para colecciones.

Sintaxis básica

// Arrays y métodos tipados
const nums: number[] = [3, 1, 4, 1, 5];
const ordenado = [...nums].sort((a, b) => a - b);
const encontrado = nums.find(n => n > 3);     // number | undefined
const filtrados = nums.filter(n => n > 2);    // number[]

// Map y Set genéricos
const mapa = new Map<string, number>();
mapa.set("a", 1);
mapa.set("b", 2);

const conjunto = new Set<number>([1, 2, 3, 3]); // Set {1, 2, 3}

// Búsqueda binaria manual (tipada)
function busquedaBinaria<T>(arr: T[], target: T): number {
  let left = 0, right = arr.length - 1;
  while (left <= right) {
    const mid = Math.floor((left + right) / 2);
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) left = mid + 1;
    else right = mid - 1;
  }
  return -1;
}

// Ordenación por múltiples criterios
interface Producto { nombre: string; precio: number; stock: number; }
const productos: Producto[] = [
  { nombre: "Laptop", precio: 1200, stock: 5 },
  { nombre: "Mouse", precio: 25, stock: 100 },
];
const ordenado2 = [...productos].sort((a, b) =>
  a.precio - b.precio || b.stock - a.stock
);

🧪 Cómo probarlo

npx tsx -e "
function busquedaBinaria<T>(arr: T[], target: T): number {
  let l = 0, r = arr.length - 1;
  while (l <= r) { const m = Math.floor((l+r)/2); if (arr[m] === target) return m; if (arr[m] < target) l = m+1; else r = m-1; }
  return -1;
}
console.log(busquedaBinaria([1,3,5,7,9], 5)); // 2
"

💡 Memoria y rendimiento

Los genéricos no tienen overhead runtime (se borran). Map y Set tienen rendimiento O(1) promedio. sort() es O(n log n), estable desde ES2019.

✅ Buenas prácticas

  • ✅ Usa ReadonlyArray<T> para arrays que no modifiques
  • ✅ Prefiere Map sobre objetos para diccionarios dinámicos
  • ❌ No modifiques el array original — crea copias con spread

🔗 Para saber más


Conceptos clave de TypeScript

Interfaces vs Types

¿Qué es?

Ambos definen la forma de un objeto. interface se puede extender por declaración merging; type es más expresivo (uniones, tuplas, mapped types).

Sintaxis básica

// Interface — extensible por declaration merging
interface Usuario { nombre: string; email: string; }
interface Usuario { edad?: number; }  // Se fusiona

// Type — expresivo pero no fusionable
type ID = string | number;
type Estado = "activo" | "inactivo" | "pendiente";
type Callback<T> = (data: T) => void;

// Extension
interface Admin extends Usuario { permisos: string[]; }
type Admin2 = Usuario & { permisos: string[]; };

🧪 Cómo probarlo

npx tsx -e "
interface A { x: number }
interface A { y: number }
const a: A = { x: 1, y: 2 };
console.log(a);
"
CaracterísticaInterfaceType
Declaration mergingSíNo
Extenderextends& (intersección)
UnionesNoSí
Mapped typesNoSí
RendimientoGeneralmente mejorBien

✅ Buenas prácticas

  • ✅ Usa interface para objetos/API públicas
  • ✅ Usa type para uniones, tuplas, tipos computados
  • ❌ No mezcles interface y type en el mismo proyecto sin criterio

🔗 Para saber más

Genéricos

¿Qué es?

Los genéricos permiten escribir código reusable sin perder el tipo. Son como parámetros de tipo que se resuelven en compilación.

Sintaxis básica

// Función genérica
function identidad<T>(arg: T): T { return arg; }
const num = identidad(42);     // T inferido como number
const str = identidad("hola"); // T inferido como string

// Múltiples parámetros
function zip<A, B>(a: A[], b: B[]): [A, B][] {
  return a.map((x, i) => [x, b[i]]);
}

// Constraints
interface Lengthwise { length: number; }
function logLongitud<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

// Keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

🧪 Cómo probarlo

npx tsx -e "
function primero<T>(arr: T[]): T | undefined { return arr[0]; }
console.log(primero([1,2,3])); // 1
"

💡 Memoria y rendimiento

Los genéricos se borran en compilación → cero overhead runtime. No hay reificación como en C# o Java. TS no puede inspeccionar tipos en runtime.

✅ Buenas prácticas

  • ✅ Usa constraints (extends) para restringir genéricos
  • ✅ Prefiere inferencia sobre anotación explícita de type params
  • ❌ No uses genéricos cuando un tipo concreto basta

🏗️ Metodología

Genéricos en: funciones de utilidad, repositorios, colecciones, builders, factories. No uses genéricos en: props internas, tipos que nunca cambian.

🔗 Para saber más

Conditional Types

¿Qué es?

Tipos que dependen de una condición evaluada en compilación. Usan T extends U ? X : Y.

Sintaxis básica

type IsString<T> = T extends string ? "sí" : "no";
type A = IsString<string>;  // "sí"
type B = IsString<number>;  // "no"

// Extraer tipos con infer
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = (x: number) => string;
type R = ReturnType<Fn>;  // string

// Distributive conditional types
type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<string | number>;  // string[] | number[]

🧪 Cómo probarlo

npx tsx -e "
type IsString<T> = T extends string ? true : false;
const a: IsString<'hola'> = true;
const b: IsString<42> = false;
console.log(a, b);
"

💡 Memoria y rendimiento

Los conditional types se evalúan en compilación. No tienen representación runtime. Con tipos muy grandes y condicionales anidados, pueden ralentizar el compilador.

✅ Buenas prácticas

  • ✅ Usa infer para extraer tipos de otros tipos
  • ✅ Los conditional types distribuyen sobre uniones
  • ❌ No anides conditional types más de 3 niveles

🔗 Para saber más

Mapped Types

¿Qué es?

Transforman un tipo existente en uno nuevo, propiedad por propiedad. Son la base de los utility types de la stdlib.

Sintaxis básica

type Readonly<T> = { readonly [K in keyof T]: T[K]; };
type Optional<T> = { [K in keyof T]?: T[K]; };
type Nullable<T> = { [K in keyof T]: T[K] | null; };

// Con modificación de keys
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Persona { nombre: string; edad: number; }
type GettersPersona = Getters<Persona>;
// { getNombre: () => string; getEdad: () => number; }

🧪 Cómo probarlo

npx tsx -e "
type Optional<T> = { [K in keyof T]?: T[K] };
interface U { nombre: string; edad: number; }
const u: Optional<U> = { nombre: 'test' };
console.log(u);
"

✅ Buenas prácticas

  • ✅ Úsalos para crear utility types personalizados
  • ✅ as en mapped types permite renombrar/filtrar keys
  • ❌ No uses mapped types cuando un type literal basta

🔗 Para saber más

Template Literal Types

¿Qué es?

Construyen tipos a partir de manipulación de strings literales en compilación.

Sintaxis básica

type EventName = `on${Capitalize<string>}`;
type Route = `/api/${string}`;

// Extracción de parámetros de ruta
type ExtractParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<Rest>
    : T extends `${string}:${infer Param}`
      ? Param
      : never;

type R = ExtractParams<"/api/users/:userId/posts/:postId">;
// "userId" | "postId"

// Manipuladores nativos
type Upper = Uppercase<"hola">;     // "HOLA"
type Lower = Lowercase<"HOLA">;     // "hola"
type Cap = Capitalize<"hola">;      // "Hola"
type Uncap = Uncapitalize<"Hola">;  // "hola"

🧪 Cómo probarlo

npx tsx -e "
type Saludo = 'Hola' | 'Adios';
type Mensaje = \`\${Saludo}, mundo!\`;
const m: Mensaje = 'Hola, mundo!';
console.log(m);
"

💡 Memoria y rendimiento

Se evalúan en compilación. Con strings muy largos o template types recursivos, pueden afectar el tiempo de compilación.

✅ Buenas prácticas

  • ✅ Úsalos para tipar rutas, eventos, convenciones de nomenclatura
  • ✅ Combínalos con infer para parsing de strings
  • ❌ Evita recursión profunda en template literal types

🔗 Para saber más

Type Guards personalizados

¿Qué es?

Funciones que verifican un tipo en runtime y le informan a TypeScript mediante valor is Tipo.

Sintaxis básica

interface Pajaro { tipo: "pajaro"; volar: () => void; }
interface Pez { tipo: "pez"; nadar: () => void; }

type Animal = Pajaro | Pez;

function esPajaro(animal: Animal): animal is Pajaro {
  return animal.tipo === "pajaro";
}

function mover(animal: Animal) {
  if (esPajaro(animal)) {
    animal.volar();  // TypeScript sabe que es Pajaro
  } else {
    animal.nadar();  // TypeScript sabe que es Pez
  }
}

🧪 Cómo probarlo

npx tsx -e "
function isString(v: unknown): v is string { return typeof v === 'string'; }
console.log(isString('hola'), isString(42));
"

✅ Buenas prácticas

  • ✅ Usa v is Type para type guards personalizados
  • ✅ Combínalos con unknown para datos externos
  • ❌ No mientas en el type guard — debe reflejar la realidad runtime

🔗 Para saber más

keyof, typeof, satisfies

¿Qué es?

Tres operadores que conectan el mundo de valores con el mundo de tipos.

Sintaxis básica

// keyof — unión de claves de un tipo
interface Persona { nombre: string; edad: number; email: string; }
type Claves = keyof Persona;  // "nombre" | "edad" | "email"

// typeof en contexto de tipo — obtiene el tipo de un valor
const config = { url: "https://api.example.com", timeout: 5000 };
type ConfigType = typeof config;
// { url: string; timeout: number; }

// satisfies — verifica sin ensanchar (TS 4.9+)
const palette = {
  rojo: "#FF0000",
  azul: ["#0000FF", "#00BFFF"],
} satisfies Record<string, string | string[]>;

palette.rojo.toUpperCase();    // OK: TS sabe que es string
palette.azul.map(c => c);     // OK: TS sabe que es string[]

🧪 Cómo probarlo

npx tsx -e "
const obj = { a: 1, b: 'dos' } as const;
type T = typeof obj;
console.log(obj);
"

✅ Buenas prácticas

  • ✅ satisfies es mejor que as para verificar sin perder tipos
  • ✅ typeof en tipos para extraer tipos de configuraciones/constantes
  • ❌ No uses typeof cuando puedes definir el tipo explícitamente

🔗 Para saber más

Decoradores

¿Qué es?

Funciones que modifican clases, métodos o propiedades. TypeScript soporta decoradores experimentales y TC39 estándar.

Sintaxis básica

// Decorador experimental
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const original = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`Llamando ${propertyKey} con`, args);
    return original.apply(this, args);
  };
  return descriptor;
}

class Calculadora {
  @logMethod
  sumar(a: number, b: number): number { return a + b; }
}

// Decorador TC39 estándar (TS 5.0+)
function logged<T extends (...args: any[]) => any>(
  target: T, context: ClassMethodDecoratorContext
) {
  return function (this: any, ...args: Parameters<T>) {
    console.log(`Llamando ${String(context.name)}`);
    return target.apply(this, args);
  };
}

🧪 Cómo probarlo

npx tsx -e "
function log(target: any, key: string) { console.log('Decorated:', key); }
class A { @log metodo() {} }
"

✅ Buenas prácticas

  • ✅ Usa decoradores TC39 estándar (sin experimentalDecorators) para código nuevo
  • ✅ Prefiere composición sobre decoradores cuando sea posible
  • ❌ No abuses de decoradores para lógica de negocio crítica

🔗 Para saber más

Archivos .d.ts y declare

¿Qué es?

.d.ts son declaration files con solo tipos, sin implementación. declare informa al compilador sobre cosas que existen en runtime.

Sintaxis básica

// ejemplo.d.ts
export interface Usuario { id: number; nombre: string; email: string; }
export function obtenerUsuario(id: number): Promise<Usuario>;

// declare — variables globales
declare const API_KEY: string;

// declare module — para librerías sin tipos
declare module "mi-lib-sin-tipos" {
  export function saludar(nombre: string): string;
}

✅ Buenas prácticas

  • ✅ TS genera .d.ts automáticamente con declaration: true
  • ✅ Busca tipos en @types/ para librerías JS
  • ❌ No escribas .d.ts manuales si la librería ya tiene tipos

🔗 Para saber más

Strict Mode

¿Qué es?

strict: true activa 7 flags de comprobación que convierten errores potenciales en errores de compilación.

FlagEfecto
strictNullChecksnull/undefined no asignables a otros tipos
strictFunctionTypesContravarianza en funciones
strictBindCallApplybind/call/apply tipados
strictPropertyInitializationProps de clase deben inicializarse
noImplicitAnyError si TS no infiere tipo
noImplicitThisthis debe tener tipo explícito
alwaysStrict"use strict" en todo el JS emitido

✅ Buenas prácticas

  • ✅ Activa strict: true desde el día 1 en todo proyecto nuevo
  • ✅ Añade noUncheckedIndexedAccess: true para arrays seguros
  • ❌ No desactives strict mode porque “molesta” — arregla los tipos

🔗 Para saber más


POO y patrones de diseño

¿Qué es?

TypeScript aporta clases ES6+ con tipos, modificadores de acceso, abstract, y decoradores. Los patrones de diseño se expresan de forma natural con el sistema de tipos.

Sintaxis básica — clases

abstract class Animal {
  constructor(protected nombre: string) {}
  abstract hacerSonido(): string;
  mover(): void { console.log(`${this.nombre} se mueve`); }
}

interface Volador { volar(): void; }
interface Nadador { nadar(): void; }

class Pato extends Animal implements Volador, Nadador {
  constructor(nombre: string) { super(nombre); }
  hacerSonido(): string { return "Cuac"; }
  volar(): void { console.log(`${this.nombre} vuela`); }
  nadar(): void { console.log(`${this.nombre} nada`); }
}

// Clase completa con getters/setters
class Usuario {
  public nombre: string;
  private _edad: number;
  readonly id: string;

  get edad(): number { return this._edad; }
  set edad(valor: number) {
    if (valor < 0 || valor > 150) throw new Error("Edad inválida");
    this._edad = valor;
  }

  constructor(public nombre: string, edad: number, readonly id: string = crypto.randomUUID()) {
    this._edad = edad;
  }

  static crearAdmin(nombre: string): Usuario {
    return new Usuario(nombre, 30);
  }
}

🧪 Cómo probarlo

npx tsx -e "
class Perro { constructor(public nombre: string) {} ladrar(): string { return 'Guau'; } }
const p = new Perro('Rex');
console.log(p.ladrar());
"

Ejemplo completo — sistema multimedia

import { randomUUID } from "node:crypto";

abstract class Contenido {
  protected _reproducciones = 0;
  readonly id: string;
  constructor(protected _metadatos: Metadatos, id?: string) {
    this.id = id ?? randomUUID();
  }
  get metadatos(): Metadatos { return this._metadatos; }
  abstract obtenerInfo(): string;
  reproducir(): string {
    this._reproducciones++;
    return `▶️ ${this._metadatos.titulo}`;
  }
}

interface Metadatos {
  readonly titulo: string;
  readonly duracionSegundos: number;
  readonly fechaCreacion: Date;
  readonly tags: string[];
}

class Audio extends Contenido {
  constructor(metadatos: Metadatos, public readonly bitrateKbps: number) { super(metadatos); }
  obtenerInfo(): string { return `🎵 ${this.metadatos.titulo} | ${this.bitrateKbps}kbps`; }
}

class Video extends Contenido {
  constructor(metadatos: Metadatos, public readonly resolucion: string) { super(metadatos); }
  obtenerInfo(): string { return `🎬 ${this.metadatos.titulo} | ${this.resolucion}`; }
}

Patrones de diseño

PatrónEn TypeScript
SingletonMódulos ES: export const config = {...}
FactoryFunción que retorna tipos según discriminante
StrategyFunción como parámetro o interfaz
ObserverEventEmitter, RxJS Subjects
AdapterInterfaces estructurales
Decorator@decorador — integrado en el lenguaje
RepositoryInterfaces genéricas Repositorio<T>
BuilderMethod chaining con this tipado
// Factory con registro dinámico
type Constructor<T> = new (...args: any[]) => T;
interface Plugin { ejecutar(): string; }

class PluginRegistry {
  private static plugins = new Map<string, Constructor<Plugin>>();
  static registrar(nombre: string, plugin: Constructor<Plugin>): void {
    this.plugins.set(nombre, plugin);
  }
  static crearPlugin(nombre: string): Plugin {
    const PluginClass = this.plugins.get(nombre);
    if (!PluginClass) throw new Error(`Plugin '${nombre}' no existe`);
    return new PluginClass();
  }
}

// Chain of Responsibility
abstract class Handler {
  private siguiente?: Handler;
  conectar(handler: Handler): Handler { this.siguiente = handler; return handler; }
  async manejar(solicitud: string): Promise<string | undefined> {
    const resultado = await this.procesar(solicitud);
    if (resultado === undefined && this.siguiente) return this.siguiente.manejar(solicitud);
    return resultado;
  }
  protected abstract procesar(solicitud: string): Promise<string | undefined>;
}

🧪 Cómo probar patrones

npx tsx -e "
type Estrategia = (a: number, b: number) => number;
const sumar: Estrategia = (a, b) => a + b;
const multiplicar: Estrategia = (a, b) => a * b;
function calcular(a: number, b: number, e: Estrategia) { return e(a, b); }
console.log(calcular(3, 4, sumar), calcular(3, 4, multiplicar));
"

💡 Memoria y rendimiento

Clases tienen overhead similar a JS. Los genéricos y tipos no afectan runtime. Decoradores experimentales añaden ligero overhead (wrap de funciones).

✅ Buenas prácticas

  • ✅ Prefiere composición sobre herencia
  • ✅ Usa abstract class para comportamiento compartido, interface para contratos
  • ❌ No heredes más de 2 niveles — mejor composición

🔗 Para saber más


Polimorfismo en detalle

TypeScript tiene tres formas de polimorfismo que coexisten en el mismo código.

1. Polimorfismo paramétrico (genéricos)

function identidad<T>(valor: T): T { return valor; }
class Caja<T> {
  constructor(public contenido: T) {}
  obtener(): T { return this.contenido; }
}
const cajaString = new Caja("Hola");
const cajaNumber = new Caja(123);

2. Polimorfismo por subtipado (herencia)

abstract class Forma { abstract area(): number; }
class Circulo extends Forma {
  constructor(private radio: number) { super(); }
  area(): number { return Math.PI * this.radio ** 2; }
}
class Rectangulo extends Forma {
  constructor(private ancho: number, private alto: number) { super(); }
  area(): number { return this.ancho * this.alto; }
}
function mostrarArea(forma: Forma): void {
  console.log(`Área: ${forma.area().toFixed(2)}`);
}

3. Polimorfismo estructural (duck typing)

interface Imprimible { imprimir(): string; }
class Documento { imprimir(): string { return "Documento..."; } }
class Foto { imprimir(): string { return "Foto 4x6..."; } }
function enviarAImpresion(objeto: Imprimible): void {
  console.log(objeto.imprimir());
}
enviarAImpresion(new Documento());
enviarAImpresion(new Foto());

🧪 Cómo probarlo

npx tsx -e "
interface TieneLongitud { length: number; }
function mostrar<T extends TieneLongitud>(x: T) { console.log(x.length); }
mostrar('hola'); mostrar([1,2,3]);
"

💡 Memoria y rendimiento

Polimorfismo paramétrico y estructural: cero costo runtime (tipos se borran). Subtipado: mismo costo que JS (vtable implícita en prototipos).

✅ Buenas prácticas

  • ✅ Usa polimorfismo estructural cuando puedas (es lo más TypeScript)
  • ✅ Genéricos para algoritmos que funcionan con cualquier tipo
  • ❌ No crees jerarquías profundas de clases solo por polimorfismo

🔗 Para saber más


Interacción con contenido multimedia

¿Qué es?

TypeScript usa las mismas librerías JS para multimedia, con tipos incluidos o de @types/.

Sintaxis básica

import sharp from "sharp";

// Redimensionar imagen (tipado con sharp)
async function redimensionar(input: string, output: string, ancho: number) {
  await sharp(input)
    .resize(ancho)
    .webp({ quality: 80 })
    .toFile(output);
}

// Canvas 2D (navegador)
const canvas = document.createElement("canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 100, 100);
TipoLibreríaFormatosAsync
ImágenessharpJPEG, PNG, WebP, AVIFSí
Videofluent-ffmpegMP4, WebM, AVISí
Audionode-wav, audiobuffer-to-wavWAV, MP3Sí
Canvasnode-canvasPNG, JPEGNo

🧪 Cómo probarlo

npx tsx -e "
import sharp from 'sharp';
sharp({ create: { width: 100, height: 100, channels: 3, background: { r: 255, g: 0, b: 0 } } })
  .webp().toFile('/tmp/test.webp').then(() => console.log('OK'));
"

🔗 Para saber más


Bases de datos

ORMs y query builders tipados

LibreríaAsyncORMPara qué
PrismaSíORM completoSchema declarativo, tipos generados, migrations
Drizzle ORMSíORM ligeroSQL-like, tipos inferidos
TypeORMSíORM completoDecoradores, active record
KyselySíQuery builderSQL tipado, sin abstracción
MikroORMSíORM completoUnit of Work, Identity Map

Prisma

// schema.prisma
model Usuario {
  id        String   @id @default(cuid())
  nombre    String
  email     String   @unique
  posts     Post[]
  createdAt DateTime @default(now())
}

model Post {
  id        String   @id @default(cuid())
  titulo    String
  contenido String?
  autorId   String
  autor     Usuario   @relation(fields: [autorId], references: [id])
}
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

async function crearUsuario(nombre: string, email: string) {
  const usuario = await prisma.usuario.create({ data: { nombre, email } });
  return usuario;  // Todo tipado
}

Drizzle ORM

import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/node-postgres";

const usuarios = pgTable("usuarios", {
  id: serial("id").primaryKey(),
  nombre: text("nombre").notNull(),
  email: text("email").unique().notNull(),
});

type Usuario = typeof usuarios.$inferSelect;
type NuevoUsuario = typeof usuarios.$inferInsert;

const db = drizzle(pool);
const usuario = await db.insert(usuarios).values({ nombre, email }).returning();

🧪 Cómo probarlo

npx tsx -e "
// Test de tipo — verifica que PrismaClient se importa bien
import { PrismaClient } from '@prisma/client';
console.log(typeof PrismaClient);
"

Migraciones

npx prisma migrate dev --name init
npx prisma migrate deploy
npx drizzle-kit generate
npx drizzle-kit migrate

🔗 Para saber más


WebSockets

ws (raw WebSocket)

import { WebSocketServer, WebSocket } from "ws";

const wss = new WebSocketServer({ port: 8080 });

wss.on("connection", (ws: WebSocket) => {
  ws.on("message", (data: Buffer) => {
    wss.clients.forEach((client) => {
      if (client.readyState === WebSocket.OPEN) {
        client.send(`Echo: ${data.toString()}`);
      }
    });
  });
});

Socket.IO (con salas y eventos)

import { Server } from "socket.io";
import http from "node:http";

const server = http.createServer();
const io = new Server(server, { cors: { origin: "*" } });

io.on("connection", (socket) => {
  socket.on("mensaje-sala", ({ sala, mensaje }: { sala: string; mensaje: string }) => {
    io.to(sala).emit("mensaje", { de: socket.id, mensaje });
  });
});

server.listen(3000);

🧪 Cómo probarlo

npx tsx -e "
import { WebSocketServer } from 'ws';
const wss = new WebSocketServer({ port: 0 }, () => { console.log('WS OK'); wss.close(); });
"

🔗 Para saber más


Concurrencia

TypeScript/JavaScript tiene un solo hilo con event loop asíncrono. La concurrencia real viene de worker_threads (Node) y Web Workers (navegador).

Event Loop

console.log("1: Sincrónico");
setTimeout(() => console.log("2: Macrotarea"), 0);
Promise.resolve().then(() => console.log("3: Microtarea"));
console.log("4: Sincrónico");
// Output: 1, 4, 3, 2

async/await y concurrencia

async function descargar(url: string): Promise<string> {
  const response = await fetch(url);
  return response.text();
}

// Promise.all — concurrencia real (I/O)
const resultados = await Promise.all(urls.map(descargar));

// p-limit — concurrencia controlada
import pLimit from "p-limit";
const limit = pLimit(5);
const datos = await Promise.all(
  urls.map(url => limit(() => fetch(url).then(r => r.text())))
);

worker_threads (Node.js)

import { Worker } from "node:worker_threads";
import { cpus } from "node:os";

function ejecutarEnWorker(n: number): Promise<number> {
  return new Promise((resolve, reject) => {
    const worker = new Worker(new URL("./worker.ts", import.meta.url));
    worker.postMessage(n);
    worker.on("message", resolve);
    worker.on("error", reject);
  });
}

// Usar todos los núcleos
const nums = Array.from({ length: cpus().length }, (_, i) => 40 + i);
const resultados = await Promise.all(nums.map(ejecutarEnWorker));
EscenarioModeloEjemplo
HTTP/API callsasync/await + Promise.allMicroservicios
Cálculo intensivoworker_threads / Web WorkersImágenes, ML
Streamingasync/await + streamsWebSockets
Background jobsBullMQ, AgendaColas, emails

🧪 Cómo probarlo

npx tsx -e "
// Verificar event loop order
console.log('sync');
Promise.resolve().then(() => console.log('micro'));
setTimeout(() => console.log('macro'), 0);
"

💡 Memoria y rendimiento

El event loop no tiene GIL — no bloquea con I/O bien hecho. Workers tienen su propio V8 isolate (~10-30 MB cada uno). Promise.all no crea threads, solo coordina.

✅ Buenas prácticas

  • ✅ Usa Promise.all para operaciones I/O independientes
  • ✅ Usa p-limit para controlar concurrencia
  • ✅ Workers para CPU-bound, async para I/O-bound

🔗 Para saber más


Testing y calidad

Frameworks

FrameworkPropósitoAsyncCLI
vitestUnitario + integraciónSínpx vitest run
jestUnitario + integraciónSínpx jest
playwrightE2E multi-navegadorSínpx playwright test
mswMock HTTPSíIntegrado en vitest/jest

Vitest — configuración y test básico

npm install --save-dev vitest
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { globals: true } });
// math.test.ts
import { describe, it, expect } from "vitest";

function sumar(a: number, b: number): number {
  return a + b;
}

describe("sumar", () => {
  it("suma dos números positivos", () => {
    expect(sumar(2, 3)).toBe(5);
  });
  it("suma con negativos", () => {
    expect(sumar(-1, 1)).toBe(0);
  });
});

Cómo testear cada concepto

Variables y tipos — aserciones simples, type narrowing en tests:

it("type guard funciona", () => {
  function isString(v: unknown): v is string {
    return typeof v === "string";
  }
  expect(isString("hola")).toBe(true);
  expect(isString(42)).toBe(false);
});

POO — mocks y stubs:

import { vi } from "vitest";

it("mock de método", () => {
  const mockFn = vi.fn().mockReturnValue(42);
  expect(mockFn()).toBe(42);
  expect(mockFn).toHaveBeenCalledOnce();
});

Async — test con async/await:

it("promesa resuelve", async () => {
  const data = await fetch("https://httpbin.org/get");
  expect(data.ok).toBe(true);
});

File I/O — directorios temporales:

import { mkdtempSync, writeFileSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

it("escribe y lee archivo", () => {
  const dir = mkdtempSync(join(tmpdir(), "test-"));
  const path = join(dir, "test.txt");
  writeFileSync(path, "contenido", "utf-8");
  expect(readFileSync(path, "utf-8")).toBe("contenido");
});

Tests parametrizados

it.each([
  [1, 1, 2],
  [0, 0, 0],
  [-1, 1, 0],
])("sumar(%i, %i) = %i", (a, b, expected) => {
  expect(sumar(a, b)).toBe(expected);
});

Cobertura

npx vitest run --coverage

Scripts de verificación

# package.json
{
  "scripts": {
    "typecheck": "tsc --noEmit",
    "lint": "eslint src/",
    "test": "vitest run",
    "check": "npm run typecheck && npm run lint && npm run test"
  }
}

🔗 Para saber más


Conceptos avanzados

Template Literal Types avanzados

Parsing de rutas tipadas

type ExtractParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<Rest>
    : T extends `${string}:${infer Param}`
      ? Param
      : never;

type RutasUsuarios = ExtractParams<"/api/users/:userId/posts/:postId">;
// "userId" | "postId"

// Router tipado
type Route = `/api/${string}`;
function apiRoute<T extends string>(path: T): `http://localhost:3000${T}` {
  return `http://localhost:3000${path}`;
}
const url = apiRoute("/api/users/42"); // tipo: "http://localhost:3000/api/users/42"

Conditional Types avanzados

Infer recursivo

// Extraer tipo de promesas anidadas
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

type P1 = Awaited<Promise<string>>;        // string
type P2 = Awaited<Promise<Promise<number>>>; // number

// Filter de uniones por tipo
type ExtractByType<T, U> = T extends U ? T : never;
type SoloNumeros = ExtractByType<string | number | boolean, number>;
// number

Mapped Types avanzados

// DeepPartial recursivo
type DeepPartial<T> = {
  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};

interface Config {
  db: { url: string; port: number };
  server: { host: string; port: number };
}
type PartialConfig = DeepPartial<Config>;

// PickByValue — filtrar propiedades por tipo de valor
type PickByValue<T, V> = {
  [K in keyof T as T[K] extends V ? K : never]: T[K];
};

interface Usuario { nombre: string; edad: number; email: string; }
type SoloStrings = PickByValue<Usuario, string>;
// { nombre: string; email: string; }

Branded Types (tipado nominal simulado)

type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };

function getUser(id: UserId): Promise<User>;
function getPost(id: PostId): Promise<Post>;

const uid = "abc123" as UserId;
const pid = "abc123" as PostId;
// getUser(pid);  // Error de compilación

type-challenges

type-challenges es un repositorio de retos progresivos de tipos TypeScript. Ejemplos:

// Pick (fácil)
type MyPick<T, K extends keyof T> = { [P in K]: T[P]; };

// Readonly (fácil)
type MyReadonly<T> = { readonly [K in keyof T]: T[K]; };

// ReturnType (medio)
type MyReturnType<T extends (...args: any[]) => any> =
  T extends (...args: any[]) => infer R ? R : never;

// DeepReadonly (medio)
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

Cómo practicar:

  1. Clona el repo: git clone https://github.com/type-challenges/type-challenges
  2. Empieza por warm-ups → easy → medium → hard → extreme
  3. Cada reto tiene tests de tipo que verifican tu solución
  4. Usa el TypeScript Playground para iterar

🔗 Para saber más


Proyecto final integrador

CLI de notas cifradas

Combina file system, cifrado, POO, testing y validación.

import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";

interface Nota {
  titulo: string;
  contenido: string;
  createdAt: string;
}

class Vault {
  private notas: Map<string, Nota> = new Map();

  constructor(private path: string, private key: Buffer) {
    this.cargar();
  }

  private cifrar(texto: string): string {
    const iv = randomBytes(16);
    const cipher = createCipheriv("aes-256-gcm", this.key, iv);
    let enc = cipher.update(texto, "utf8", "hex");
    enc += cipher.final("hex");
    return iv.toString("hex") + ":" + enc + ":" + cipher.getAuthTag().toString("hex");
  }

  private descifrar(texto: string): string {
    const [iv, enc, tag] = texto.split(":");
    const decipher = createDecipheriv("aes-256-gcm", this.key, Buffer.from(iv, "hex"));
    decipher.setAuthTag(Buffer.from(tag, "hex"));
    return decipher.update(enc, "hex", "utf8") + decipher.final("utf8");
  }

  private cargar(): void {
    if (!existsSync(this.path)) return;
    const data = this.descifrar(readFileSync(this.path, "utf-8"));
    this.notas = new Map(Object.entries(JSON.parse(data)));
  }

  private guardar(): void {
    const data = JSON.stringify(Object.fromEntries(this.notas));
    writeFileSync(this.path, this.cifrar(data));
  }

  agregar(titulo: string, contenido: string): void {
    this.notas.set(titulo, { titulo, contenido, createdAt: new Date().toISOString() });
    this.guardar();
  }

  obtener(titulo: string): Nota | undefined {
    return this.notas.get(titulo);
  }

  listar(): string[] {
    return [...this.notas.keys()];
  }
}

Tests (vault.test.ts):

import { describe, it, expect, beforeEach } from "vitest";
import { mkdtempSync, existsSync } from "node:fs";
import { join } from "node:path";
import { tmpdir } from "node:os";

describe("Vault", () => {
  let path: string;
  const key = Buffer.alloc(32, 0xff);

  beforeEach(() => {
    path = join(mkdtempSync(join(tmpdir(), "vault-")), "notas.enc");
  });

  it("guarda y recupera una nota", () => {
    const vault = new Vault(path, key);
    vault.agregar("test", "contenido");
    const nota = vault.obtener("test");
    expect(nota?.titulo).toBe("test");
    expect(nota?.contenido).toBe("contenido");
  });
});

Proyecto integrador: funciones

ComponenteImplementación
File systemLectura/escritura de archivo cifrado
AlgoritmoAES-256-GCM (cifrado simétrico)
POOClase Vault con métodos privados
TestingTests unitarios con vitest
Red (opcional)Compartir vault por WebSocket

🔗 Para saber más


Despliegue a producción

Build para producción

tsc

Docker multi-stage

FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig.json .
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
CMD ["node", "dist/index.js"]

Hosting

  • Vercel — soporte nativo para TypeScript
  • Netlify — build con tsc integrado
  • Railway — despliegue automático desde GitHub
  • Fly.io — fly launch con Dockerfile

CI/CD (GitHub Actions)

name: Deploy
on:
  push:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: "22"
      - run: npm ci
      - run: npm test
  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: superfly/flyctl-actions@1.5
        with:
          args: "deploy"

Canales y recursos en español

YouTube

  • Midulive — JavaScript, TypeScript, desarrollo web práctico
  • MoureDev — TypeScript, retos de programación
  • HolaMundo — Tutoriales cortos sobre JS, TS, React
  • Fazt — TypeScript, Node.js, React, proyectos completos
  • Programación ATS — Curso de TypeScript desde cero
  • CodigoFacilito — Cursos estructurados de TypeScript
  • Platzi — Ruta de TypeScript con proyectos
  • Victor Robles — TypeScript para backend con NestJS
  • jonmircha — Cursos de JavaScript y TypeScript

Comunidades

  • r/typescript (Reddit) — Noticias, discusiones, dudas
  • TypeScript España — Comunidad en español, meetups
  • Discord Midulive / MoureDev — Canales de ayuda
  • Stack Overflow en español — Etiqueta typescript
  • r/SpainDevs (Reddit) — Desarrolladores españoles

Repositorios

Newsletters


Hacks y tips de productividad

1. satisfies — verifica sin ensanchar

const palette = { rojo: "#FF0000", verde: "#00FF00" } satisfies Record<string, string>;
palette.rojo.toUpperCase();  // OK: string

2. Branded types para IDs

type UserId = string & { readonly __brand: "UserId" };
type ProductId = string & { readonly __brand: "ProductId" };
// getUser(pid);  // Error

3. Template literal types para routing

type ExtractParams<T> = T extends `${string}:${infer P}/${infer R}` ? P | ExtractParams<R>
  : T extends `${string}:${infer P}` ? P : never;

4. Utility types que debes saber de memoria

Partial<T>, Required<T>, Pick<T, K>, Omit<T, K>
Record<K, V>, ReturnType<T>, Parameters<T>
Exclude<T, U>, Extract<T, U>, NonNullable<T>

5. as const para configuraciones

export const HTTP_STATUS = { OK: 200, NOT_FOUND: 404 } as const;
type HttpStatus = typeof HTTP_STATUS[keyof typeof HTTP_STATUS]; // 200 | 404

6. Zod para validación + tipos inferidos

const UsuarioSchema = z.object({ nombre: z.string().min(2), email: z.string().email() });
type Usuario = z.infer<typeof UsuarioSchema>;

7. Los 3 comandos esenciales

npx tsc --noEmit        # Type-check
npx eslint src/         # Lint
npx vitest run          # Tests

8. Non-null assertion (!) — con cuidado

// En vez de:
const el = document.getElementById("app")!;
// Mejor:
const el = document.getElementById("app");
if (!el) throw new Error("No encontrado");

9. noUncheckedIndexedAccess para arrays seguros

const arr: string[] = ["a", "b"];
const first = arr[0];  // string | undefined

10. Deep Partial con mapped types

type DeepPartial<T> = { [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]; };

🔗 Para saber más


⚠️ Errores comunes

ErrorCausaSolución
Type 'string' is not assignable to type 'number'Intentas asignar un valor de tipo distinto al declarado en la variable, parámetro o return typeRevisa la fuente del dato; usa Number(), parseInt() o +valor para convertir, o corrige el tipo declarado si es intencionado
Property 'x' does not exist on type 'Y'Accedes a una propiedad que no está definida en la interfaz o tipoAñade la propiedad a la interfaz, usa un type guard, o comprueba si el objeto tiene esa clave con in o typeof
Object is possibly 'null'TypeScript detecta que una variable puede ser null y accedes a ella sin comprobarlo primeroUsa optional chaining (obj?.prop), un if (obj) guard, o el non-null assertion operator (obj!.prop) si estás seguro
Cannot find module 'X' or its corresponding type declarationsFalta instalar @types/X o el paquete no tiene tipos integradosEjecuta npm i -D @types/nombre-paquete; si no existe tipo, crea un archivo d.ts con declare module 'nombre-paquete'
Unexpected any. Specify a different typeUsas any y el linter o strict lo flaggea como anti-patrónSustituye any por un tipo concreto, unknown (más seguro), o un genérico; reserva any solo para migraciones temporales
TS7006: Parameter 'x' implicitly has an 'any' typeEn strictNullChecks o noImplicitAny, un parámetro de función no tiene anotación ni se infiereAñade tipo explícito al parámetro: (x: string), o deja que TypeScript lo infiera con un valor por defecto o return type
Argument of type 'X' is not assignable to parameter of type 'Y'Pasas un objeto con propiedades de más o de menos a una función tipadaComprueba que la estructura coincida exactamente con el tipo esperado; usa satisfies o as const para acotar el tipo del objeto literal

🔗 Guías relacionadas

Profundiza en tu stack con estas guías complementarias:


Referencias y documentación oficial

Fuentes de esta guía


Retos Relacionados

Pon a prueba lo aprendido con estos desafíos:

COMPARTIR:
COMENTARIOS:

📋 Contenido