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
tscbásico: compilar.tsa.js- Conceptos clave: los tipos se borran al compilar. Tipado estructural.
constimpide 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,
readonlyarrays,as const - Enums,
unknownvsany - Conceptos clave: narrowing automático en switch.
anydesactiva el checker.as constpara 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:
interfacevstype, 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.
satisfiesverifica 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.
--strictdesde 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 estrictasnoUncheckedIndexedAccess: true—arr[0]esT | undefinedtarget: "ES2022"— a qué versión de JS compilarmodule: "NodeNext"— módulos ES nativos con Node.jsdeclaration: true— genera.d.tspara 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 VersionCtrl+Shift+P → TypeScript: Restart TS ServerCtrl+.— 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.
| Paradigma | Soporte nativo | Librerías | Ejemplo |
|---|---|---|---|
| Imperativo / procedural | Sí | — | let x = 0; for (let i = 0; i < 10; i++) { x += i; } |
| Orientado a objetos | Sí (clases, herencia, interfaces) | NestJS, TypeORM | class Usuario extends Entidad |
| Funcional | Sí (funciones puras, HOF, inmutabilidad) | fp-ts, Effect TS | [1,2,3].map(x => x * 2).filter(x => x > 3) |
| Declarativo | Parcial (JSX, configs) | React, Prisma schema | <Button onClick={fn}>Click</Button> |
| Reactivo | Sí (Observables, async generadores) | RxJS, Signals | fromEvent(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
unknowncuando no sepas el tipo - ✅ Activa
strict: truedesde 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 constpara tipos literales profundos - ❌ No uses
numberpara IDs que representan conceptos distintos — usa branded types - ✅
nullyundefinedson 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>oreadonly 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
neverpara 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 enumsolo 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
anyexplícitamente. Configura@typescript-eslint/no-explicit-any: "error" - ✅ Usa
unknownpara datos externos (APIs, JSON parse) - ✅ Usa
neverpara 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
neverpara 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 typepara imports que solo son tipos (se borran en compilación) - ✅ Usa barriles (
index.ts) para exportar múltiples módulos - ✅ Activa
verbatimModuleSyntaxen 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/promisessobre callbacks - ✅ Usa
path.joinpara 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
Mapsobre 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ística | Interface | Type |
|---|---|---|
| Declaration merging | Sí | No |
| Extender | extends | & (intersección) |
| Uniones | No | Sí |
| Mapped types | No | Sí |
| Rendimiento | Generalmente mejor | Bien |
✅ Buenas prácticas
- ✅ Usa
interfacepara objetos/API públicas - ✅ Usa
typepara 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
inferpara 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
- ✅
asen 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
inferpara 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 Typepara type guards personalizados - ✅ Combínalos con
unknownpara 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
- ✅
satisfieses mejor queaspara verificar sin perder tipos - ✅
typeofen tipos para extraer tipos de configuraciones/constantes - ❌ No uses
typeofcuando 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.tsautomáticamente condeclaration: true - ✅ Busca tipos en
@types/para librerías JS - ❌ No escribas
.d.tsmanuales 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.
| Flag | Efecto |
|---|---|
strictNullChecks | null/undefined no asignables a otros tipos |
strictFunctionTypes | Contravarianza en funciones |
strictBindCallApply | bind/call/apply tipados |
strictPropertyInitialization | Props de clase deben inicializarse |
noImplicitAny | Error si TS no infiere tipo |
noImplicitThis | this debe tener tipo explícito |
alwaysStrict | "use strict" en todo el JS emitido |
✅ Buenas prácticas
- ✅ Activa
strict: truedesde el día 1 en todo proyecto nuevo - ✅ Añade
noUncheckedIndexedAccess: truepara 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ón | En TypeScript |
|---|---|
| Singleton | Módulos ES: export const config = {...} |
| Factory | Función que retorna tipos según discriminante |
| Strategy | Función como parámetro o interfaz |
| Observer | EventEmitter, RxJS Subjects |
| Adapter | Interfaces estructurales |
| Decorator | @decorador — integrado en el lenguaje |
| Repository | Interfaces genéricas Repositorio<T> |
| Builder | Method 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 classpara comportamiento compartido,interfacepara 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);
| Tipo | Librería | Formatos | Async |
|---|---|---|---|
| Imágenes | sharp | JPEG, PNG, WebP, AVIF | Sí |
| Video | fluent-ffmpeg | MP4, WebM, AVI | Sí |
| Audio | node-wav, audiobuffer-to-wav | WAV, MP3 | Sí |
| Canvas | node-canvas | PNG, JPEG | No |
🧪 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ía | Async | ORM | Para qué |
|---|---|---|---|
| Prisma | Sí | ORM completo | Schema declarativo, tipos generados, migrations |
| Drizzle ORM | Sí | ORM ligero | SQL-like, tipos inferidos |
| TypeORM | Sí | ORM completo | Decoradores, active record |
| Kysely | Sí | Query builder | SQL tipado, sin abstracción |
| MikroORM | Sí | ORM completo | Unit 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));
| Escenario | Modelo | Ejemplo |
|---|---|---|
| HTTP/API calls | async/await + Promise.all | Microservicios |
| Cálculo intensivo | worker_threads / Web Workers | Imágenes, ML |
| Streaming | async/await + streams | WebSockets |
| Background jobs | BullMQ, Agenda | Colas, 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.allpara operaciones I/O independientes - ✅ Usa
p-limitpara controlar concurrencia - ✅ Workers para CPU-bound, async para I/O-bound
🔗 Para saber más
Testing y calidad
Frameworks
| Framework | Propósito | Async | CLI |
|---|---|---|---|
| vitest | Unitario + integración | Sí | npx vitest run |
| jest | Unitario + integración | Sí | npx jest |
| playwright | E2E multi-navegador | Sí | npx playwright test |
| msw | Mock HTTP | Sí | 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:
- Clona el repo:
git clone https://github.com/type-challenges/type-challenges - Empieza por warm-ups → easy → medium → hard → extreme
- Cada reto tiene tests de tipo que verifican tu solución
- 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
| Componente | Implementación |
|---|---|
| File system | Lectura/escritura de archivo cifrado |
| Algoritmo | AES-256-GCM (cifrado simétrico) |
| POO | Clase Vault con métodos privados |
| Testing | Tests 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
tscintegrado - Railway — despliegue automático desde GitHub
- Fly.io —
fly launchcon 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
- type-challenges — Retos de tipos
- total-typescript — Workshops de Matt Pocock
- awesome-typescript — Recursos curados
- typescript-exercises — Ejercicios progresivos
- DefinitelyTyped — Tipos para librerías JS
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
| Error | Causa | Solució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 type | Revisa 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 tipo | Añ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 primero | Usa 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 declarations | Falta instalar @types/X o el paquete no tiene tipos integrados | Ejecuta 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 type | Usas any y el linter o strict lo flaggea como anti-patrón | Sustituye 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' type | En strictNullChecks o noImplicitAny, un parámetro de función no tiene anotación ni se infiere | Añ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 tipada | Comprueba 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:
- Guía de JavaScript: de 0 a 100 — repasa la base de JS antes de profundizar en tipos avanzados de TypeScript
- Guía de Astro: de 0 a 100 — aplica TypeScript en un framework moderno con SSR, Islands y content collections tipadas
Referencias y documentación oficial
- TypeScript oficial: typescriptlang.org
- TS Handbook: typescriptlang.org/docs/handbook
- TS Playground: typescriptlang.org/play
- TSConfig Reference: typescriptlang.org/tsconfig
- TypeScript en GitHub: github.com/microsoft/TypeScript
- DefinitelyTyped: github.com/DefinitelyTyped/DefinitelyTyped
- Node.js + TypeScript: nodejs.org/en/learn/getting-started/nodejs-with-typescript
- typescript-eslint: typescript-eslint.io
- Prisma Docs: prisma.io/docs
- Drizzle Docs: orm.drizzle.team
- Kysely Docs: kysely.dev
- Socket.IO Docs: socket.io/docs/v4
- Zod Docs: zod.dev
- Fastify Docs: fastify.dev
- NestJS Docs: docs.nestjs.com
- tRPC Docs: trpc.io
- Vitest Docs: vitest.dev
- MDN — WebSockets: developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
- Node.js — worker_threads: nodejs.org/api/worker_threads.html
- V8 — Object Shapes: v8.dev/blog/shapes
Fuentes de esta guía
- TypeScript Handbook — Documentación oficial
- TypeScript Deep Dive — Libro gratuito
- Total TypeScript — Workshops de Matt Pocock
- Type Challenges — Retos de tipos
- Prisma Docs
- Drizzle Docs
- Kysely Docs
- Socket.IO Docs
- Zod Docs
- Fastify Docs
- NestJS Docs
- tRPC Docs
- Awesome TypeScript — Lista curada
- r/typescript — Comunidad
- dev.to/typescript — Artículos
- Node.js — worker_threads
- typescript-eslint
- V8 — Object Shapes
Retos Relacionados
Pon a prueba lo aprendido con estos desafíos:
