TypeScript es JavaScript con tipos: le dices al programa qué clase de valor espera cada variable y cada función, y un compilador revisa el código antes de ejecutarlo. Si pasas un número donde iba un texto, te lo dice en el editor, no en producción a las tres de la mañana. Lo usan VS Code, Angular, Slack o Airbnb, y es el lenguaje por omisión de casi cualquier proyecto web nuevo. Este curso supone que ya sabes JavaScript; si no, empieza por el curso de JavaScript, porque aquí no repetimos lo que se explica allí.
La ruta: ocho lecciones
Están pensadas para leerse en orden. Las dos primeras están en esta misma página y las demás tienen la suya. Cada lección tiene un recuadro «El error que te ahorra» con un fallo real de JavaScript que TypeScript detiene antes de tiempo, retos al final y enlaces para abrir el código en el editor y cambiarlo. Al final hay un test de 24 preguntas con un formato distinto: adivinar si el código compila.
- 1Primeros pasosQué añade TypeScript, Node 24 ejecuta .ts, tsc 7 y tu primer error.En esta página
- 2Tipos básicosAnotar o dejar que infiera, arrays y tuplas, objetos, any frente a unknown.En esta página
- 3Uniones y estrechamientoUn valor que puede ser varias cosas y cómo el if va acotando su tipo.Leer la lección →
- 4FuncionesParámetros, tipos de función, void, sobrecargas y el primer genérico.Leer la lección →
- 5Interfaces, type y clasesDar forma a los objetos, el tipado estructural y las clases con tipos.Leer la lección →
- 6GenéricosFunciones y tipos que sirven para cualquier tipo sin perder la información.Leer la lección →
- 7Tipos avanzadosTipos mapeados y condicionales, plantillas de texto, as const y satisfies.Leer la lección →
- 8Un proyecto realtsconfig.json, datos externos con Zod y la migración de un programa de JavaScript.Leer la lección →
Lección 1. Primeros pasos
Qué añade TypeScript (y qué no)
TypeScript es un superconjunto de JavaScript: todo programa de JavaScript válido lo es también de TypeScript, y lo único que se añade son anotaciones de tipo, como : string detrás de un parámetro. Esas anotaciones las lee el compilador para buscar errores, y después se borran. Lo que acaba ejecutándose, en el navegador o en Node, es JavaScript normal, sin rastro de los tipos.
Tiene una consecuencia que conviene tener clara desde el principio: los tipos no existen cuando el programa se ejecuta. TypeScript no comprueba nada en ejecución; no puede impedir que una API te devuelva un texto donde esperabas un número. Para eso hay que validar los datos de fuera, y lo veremos en la lección 8.
Node 24 ejecuta TypeScript… sin revisarlo
Hasta hace poco, para ejecutar un archivo .ts había que compilarlo antes a .js. Desde Node 23.6, y en Node 24 de forma estable, basta con node archivo.ts: Node quita los tipos y ejecuta el resto. Guarda esto como hola.ts:
function saludar(nombre: string): string {
return `Hola, ${nombre}`;
}
console.log(saludar("Ana"));Ábrelo en el editor y cámbialo →
node hola.ts
salidaHola, Ana
Ahora añade una llamada equivocada, con un número donde la función espera un texto, y ejecútalo otra vez:
function saludar(nombre: string): string {
return `Hola, ${nombre}`;
}
console.log(saludar("Ana"));
console.log(saludar(42));Ábrelo en el editor y cámbialo →
node hola.ts
salidaHola, Ana Hola, 42
Node no se ha quejado: ha borrado los tipos sin mirarlos y ha ejecutado el programa, que escribe «Hola, 42» como si nada. Ejecutar no es revisar. La revisión la hace el compilador, tsc:
npx tsc --noEmit hola.ts
tschola.ts(6,21): error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
El compilador habla en inglés salvo que le pidas otro idioma con --locale. En este curso todos los mensajes se muestran en español, tal como los da npx tsc --noEmit --locale es hola.ts:
tsc --locale eshola.ts(6,21): error TS2345: No se puede asignar un argumento de tipo "number" al parámetro de tipo "string".
Leer un error de tsc
Todos los mensajes del compilador tienen la misma forma:
| Parte | En el ejemplo | Qué dice |
|---|---|---|
| Archivo | hola.ts | dónde está el problema |
| (línea,columna) | (6,21) | la posición exacta: línea 6, columna 21, donde empieza el 42 |
| Código | TS2345 | el número del error; sirve para buscarlo en internet aunque el mensaje esté en otro idioma |
| Mensaje | No se puede asignar un argumento... | qué tipo había y cuál se esperaba |
Instalar el compilador
Node ya lo tienes si hiciste el curso de JavaScript (si no, mira cómo instalar Node 24). El compilador se instala por proyecto, como dependencia de desarrollo, con npm:
mkdir mi-proyecto && cd mi-proyecto npm init -y npm install --save-dev typescript npx tsc --version
salida de la última ordenVersion 7.0.2
La versión 7, publicada en julio de 2026, es la primera con el compilador reescrito en Go. Microsoft midió compilaciones completas entre 8 y 12 veces más rápidas que con la 6.0: el código de VS Code pasó de 125.7 segundos a 10.6. Los errores que detecta son los mismos; lo que cambia es la espera. Dos detalles de la 7 que afectan a este curso: el modo estricto viene activado por omisión (antes había que pedirlo con --strict) y la versión 7.0 todavía no tiene API para que otros programas lo usen por dentro. Por eso el editor de esta web funciona con la 6.0, que da los mismos errores.
npx? Ejecuta el tsc instalado en la carpeta node_modules del proyecto, no uno global. Así cada proyecto usa su propia versión del compilador, la que dice su package.json, y no te llevas sorpresas al cambiar de equipo. El archivo de configuración, tsconfig.json, lo vemos en la lección 8; hasta entonces, basta con pasarle el archivo a tsc.Lo que Node no sabe quitar
Node solo borra anotaciones, no traduce nada. Por eso rechaza las pocas construcciones de TypeScript que generan código propio, como los enum:
enum Color { Rojo, Verde }
console.log(Color.Verde);Ábrelo en el editor y cámbialo →
node enum_node.tsSyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
Para que el propio compilador te avise de esas construcciones antes de llegar a Node, existe la opción --erasableSyntaxOnly («solo sintaxis que se pueda borrar»). En este curso las evitamos, y en las rarezas de TypeScript explicamos por qué los enum dan más de un susto.
TypeScript sin cambiar de archivo: @ts-check
No hace falta renombrar nada para empezar a aprovechar el compilador. Si en un archivo .js normal escribes // @ts-check en la primera línea, tsc lo revisa con los tipos que es capaz de deducir por su cuenta. Es la forma más suave de empezar en un proyecto que ya existe, y la primera etapa de la migración de la lección 8.
El error que te ahorra: llamar a un método que ese tipo no tiene
// @ts-check const precio = "19.90"; // llegó de un formulario: es un texto console.log(precio.toFixed(2));
node precio.jsTypeError: precio.toFixed is not a function
En JavaScript, el fallo aparece al ejecutar, y solo si ese código llega a ejecutarse. Con // @ts-check, el compilador lo marca sin ejecutar nada:
npx tsc --noEmit --allowJs precio.js
tscprecio.js(3,20): error TS2551: La propiedad "toFixed" no existe en el tipo ""19.90"". ¿Quería decir "fixed"?
Fíjate en el tipo que dice el mensaje: no es string, sino el texto exacto "19.90", porque es una constante (en la lección 2 verás por qué). La pista «¿Quería decir «fixed»?» apunta a un método antiguo de las cadenas que no tiene nada que ver; la sugerencia no siempre acierta, pero el error sí.
Lección 2. Tipos básicos
Anotar o dejar que lo deduzca
No hace falta escribir el tipo de todo. Cuando una variable recibe un valor al declararse, TypeScript infiere su tipo, y a partir de ahí lo exige igual que si lo hubieras escrito:
let edad = 30; // TypeScript deduce: number edad = 31; // bien edad = "treinta y uno"; // error
Ábrelo en el editor y cámbialo →
tsc --strictinferencia.ts(3,1): error TS2322: El tipo 'string' no se puede asignar al tipo 'number'.
La costumbre es anotar lo que entra y sale de las funciones (parámetros y, si no es obvio, el valor devuelto) y dejar que el compilador deduzca las variables locales. Anotar de más no da errores, pero llena el código de ruido.
Ojo con una diferencia entre let y const: como una constante no puede cambiar, su tipo es el valor exacto. const modo = "oscuro" no es de tipo string, sino del tipo literal "oscuro":
let tema = "claro"; // string: puede cambiar a cualquier texto const modo = "oscuro"; // "oscuro": solo puede ser eso let soloOscuro: "oscuro" = modo; // bien let otro: "oscuro" = tema; // error
Ábrelo en el editor y cámbialo →
tsc --strictliteral.ts(5,5): error TS2322: El tipo 'string' no se puede asignar al tipo '"oscuro"'.
Los tipos primitivos
Son los mismos siete de JavaScript (los tienes explicados en la lección 2 del curso de JavaScript), con el nombre en minúsculas: string, number, boolean, bigint, symbol, null y undefined. No los confundas con String o Number en mayúscula, que son los objetos envoltorio y casi nunca se usan como tipo.
const nombre: string = "Lucía"; const precio: number = 19.9; // enteros y decimales: un solo tipo const activo: boolean = true; const grande: bigint = 9007199254740993n; const nada: null = null; console.log(typeof nombre, typeof precio, typeof activo, typeof grande, nada);
Ábrelo en el editor y cámbialo →
salidastring number boolean bigint null
Arrays y tuplas
Un array se anota con el tipo de sus elementos seguido de []. También verás la forma Array<string>, que es lo mismo. Una tupla es un array de longitud fija en el que cada posición tiene su propio tipo, útil para devolver dos valores juntos:
const notas: number[] = [7.5, 9, 6];
const nombres: Array<string> = ["Ana", "Luis"];
notas.push(8);
const punto: [string, number] = ["x", 10]; // tupla: texto y número, en ese orden
const [eje, valor] = punto;
console.log(notas, nombres, eje, valor);
notas.push("diez"); // error
console.log(punto[2]); // errorÁbrelo en el editor y cámbialo →
tsc --strictarrays.ts(9,12): error TS2345: No se puede asignar un argumento de tipo "string" al parámetro de tipo "number". arrays.ts(10,19): error TS2493: El tipo de tupla "[string, number]" de longitud "2" no tiene ningún elemento en el índice "2".
Objetos: propiedades obligatorias, opcionales y de solo lectura
El tipo de un objeto enumera sus propiedades. Un ? detrás del nombre la hace opcional, y readonly impide cambiarla después de crear el objeto:
type Producto = {
readonly id: number;
nombre: string;
precio: number;
descuento?: number; // puede no estar
};
const taza: Producto = { id: 1, nombre: "Taza", precio: 8.5 };
taza.precio = 9; // bien
taza.id = 2; // error: es de solo lectura
const vaso: Producto = { id: 2, nombre: "Vaso" }; // error: falta el precio
console.log(taza.descuento.toFixed(2)); // error: puede ser undefinedÁbrelo en el editor y cámbialo →
tsc --strictobjetos.ts(10,6): error TS2540: No se puede asignar a "id" porque es una propiedad de solo lectura.
objetos.ts(12,7): error TS2741: La propiedad "precio" falta en el tipo "{ id: number; nombre: string; }", pero es obligatoria en el tipo "Producto".
objetos.ts(14,13): error TS18048: "taza.descuento" es posiblemente "undefined".El tercer error es el más valioso: descuento es opcional, así que su tipo es en realidad number | undefined, y TypeScript no te deja usarlo como número sin comprobar antes que está. Ese | se lee «o», es una unión, y es el tema de la lección 3. La palabra type le da un nombre al tipo para no repetirlo; sus diferencias con interface están en la lección 5.
El error que te ahorra: una errata en el nombre de una propiedad
const usuario = { nombre: "Ana", email: "ana@ejemplo.com" };
console.log("Bienvenida, " + usuario.nombr);node errata_antes.mjsBienvenida, undefined
JavaScript no da ningún error: una propiedad que no existe vale undefined, y el saludo sale roto en la pantalla del usuario. El mismo código en un archivo .ts:
const usuario = { nombre: "Ana", email: "ana@ejemplo.com" };
console.log("Bienvenida, " + usuario.nombr);tsc --stricterrata.ts(2,38): error TS2551: La propiedad "nombr" no existe en el tipo "{ nombre: string; email: string; }". ¿Quería decir "nombre"?Tipos como conjuntos
Una forma útil de pensar en los tipos es como conjuntos de valores. number es el conjunto de todos los números; el tipo literal "oscuro" es un conjunto con un solo elemento, y cabe dentro de string. Asignar un valor de un tipo a otro está permitido cuando el primer conjunto cabe dentro del segundo. Con esa idea se entienden los dos tipos extremos:
any frente a unknown
Los dos aceptan cualquier valor, pero hacen cosas opuestas después. any desactiva la revisión: con él puedes hacer cualquier cosa y el compilador no dice nada, igual que en JavaScript. unknown es el conjunto de todos los valores, y por eso no te deja hacer nada con él hasta que compruebes qué es:
const a: any = "hola";
const u: unknown = "hola";
console.log(u.toFixed(2)); // error: primero hay que comprobar qué es
if (typeof u === "number") {
console.log(u.toFixed(2)); // aquí sí: dentro del if, u es un number
}
console.log(a.toFixed(2)); // tsc no se queja... y falla al ejecutarÁbrelo en el editor y cámbialo →
tsc --strictany_unknown.ts(4,13): error TS18046: "u" es de tipo "unknown".
Solo hay un error, el de u. La línea de a pasa la revisión, y al ejecutar revienta exactamente como en JavaScript:
node any_unknown.tsTypeError: u.toFixed is not a function
Regla práctica: cuando no sepas qué llega (la respuesta de una API, un JSON.parse), usa unknown y compruébalo. Por qué any es peor de lo que parece, porque se contagia a todo lo que toca, está en las rarezas.
El otro extremo es never, el conjunto vacío: el tipo de algo que no puede ocurrir, como el valor de una función que siempre lanza un error. Parece inútil, pero en la lección 3 lo usaremos para que el compilador nos avise cuando olvidemos un caso.
Resumen de las lecciones 1 y 2
| Para | Escribe | Recuerda |
|---|---|---|
| Revisar sin ejecutar | npx tsc --noEmit | node archivo.ts ejecuta, pero no revisa |
| Un texto, un número… | string, number, boolean | en minúscula, no String |
| Una lista | string[] | o Array<string>, es lo mismo |
| Varios valores de tipos fijos | [string, number] | una tupla tiene longitud fija |
| Algo que puede faltar | descuento?: number | es number | undefined: compruébalo |
| Algo que no debe cambiar | readonly id: number | solo lo comprueba el compilador |
| No sé qué llega | unknown | any apaga la revisión |
Retos
Reto 1. Este programa compila sin errores, pero escribe NaN. Añade anotaciones de tipo a los parámetros para que tsc encuentre el fallo. function media(a, b) { return (a + b) / 2; } console.log(media(4, "6"));
Ver la solución
function media(a: number, b: number): number {
return (a + b) / 2;
}
console.log(media(4, "6"));Ábrelo en el editor y cámbialo →
tsc --strictreto1.ts(4,22): error TS2345: No se puede asignar un argumento de tipo "string" al parámetro de tipo "number".
En JavaScript, 4 + "6" da el texto "46", y "46" / 2 da 23, no 5. Con los parámetros anotados, el error sale antes de ejecutar.
Reto 2. Escribe el tipo Libro para un objeto con título y autor obligatorios, el año opcional y un ISBN que no se pueda cambiar. Después crea un libro sin año y comprueba que compila.
Ver la solución
type Libro = {
readonly isbn: string;
titulo: string;
autor: string;
anio?: number;
};
const libro: Libro = { isbn: "978-84-376-0494-7", titulo: "Cien años de soledad", autor: "Gabriel García Márquez" };
console.log(libro.titulo, libro.anio ?? "sin año");Ábrelo en el editor y cámbialo →
salidaCien años de soledad sin año
Reto 3. ¿Qué tipo infiere TypeScript para x en let x = [1, "dos"];? Compruébalo intentando añadir true con x.push(true).
Ver la solución
let x = [1, "dos"]; x.push(true);
Ábrelo en el editor y cámbialo →
tsc --strictreto3.ts(2,8): error TS2345: No se puede asignar un argumento de tipo "boolean" al parámetro de tipo "string | number".
Infiere (string | number)[]: un array cuyos elementos pueden ser textos o números. No es una tupla; para eso habría que anotarlo.
Cómo está hecho este curso
Cada fragmento de código se comprobó con tsc 7.0.2 en modo estricto y con los mensajes en español (--locale es), y se ejecutó con Node.js 24.22 en un contenedor limpio. Los mensajes de error y las salidas están copiados de esa ejecución, con su código TS para que los puedas buscar. Debajo de casi todos los bloques hay un enlace para abrirlo en el editor de TypeScript y cambiarlo.
¿Buscas una receta concreta en vez de una lección? En Ejemplos de TypeScript hay tareas resueltas: validar un fetch con Zod, tipar eventos del DOM, un servidor HTTP… Y la base de todo está en el curso de JavaScript.
