Curso de TypeScript desde cero, en español

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.

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.

A la izquierda, código TypeScript con anotaciones de tipo; a la derecha, el mismo código sin ellas, que es lo que se ejecutaLo que escribes y lo que se ejecuta: los tipos se borranhola.tslo que escribesfunction saludar(nombre: string): string {  return `Hola, ${nombre}`;}const veces: number = 3;lo que ejecuta NodeJavaScript normalfunction saludar(nombre) {  return `Hola, ${nombre}`;}const veces = 3;se borranLos recuadros son tipos: solo los lee el compilador. Al ejecutar no queda ni rastro de ellos.

Las anotaciones de tipo desaparecen al ejecutar: TypeScript no cambia lo que hace el programa, solo comprueba que tenga sentido. Esquema: decodigo.com.

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".

Con un error de tipos, node hola.ts ejecuta el programa sin avisar, y npx tsc –noEmit lo revisa y muestra el error sin ejecutarloEjecutar no es revisar: dos órdenes, dos trabajos distintoshola.tscon un error de tiposnode hola.tsquita los tipos y ejecutaHola, 42el error pasa sin avisonpx tsc –noEmitrevisa los tipos, no ejecutaerror TS2345te avisa antes de ejecutar

Node solo quita los tipos; tsc los comprueba. En un proyecto se usan los dos: tsc en el editor y antes de publicar, y Node para ejecutar. Esquema: decodigo.com.

Leer un error de tsc

Todos los mensajes del compilador tienen la misma forma:

ParteEn el ejemploQué dice
Archivohola.tsdónde está el problema
(línea,columna)(6,21)la posición exacta: línea 6, columna 21, donde empieza el 42
CódigoTS2345el número del error; sirve para buscarlo en internet aunque el mensaje esté en otro idioma
MensajeNo 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.

¿Por qué 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:

unknown contiene todos los valores; string, number y boolean son subconjuntos; un tipo literal como "S" es un subconjunto de string, y never es el conjunto vacíoUn tipo es un conjunto de valores posiblesunknown: cualquier valorstring"hola""S"""number423.14NaNbooleantruefalsenullundefined{ x: 1 }…recuadro: el tipo literal "S"never: conjunto vacío

unknown abarca todos los valores; never no tiene ninguno. Un tipo cabe en otro si su conjunto está dentro. Esquema: decodigo.com.

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

ParaEscribeRecuerda
Revisar sin ejecutarnpx tsc --noEmitnode archivo.ts ejecuta, pero no revisa
Un texto, un número…string, number, booleanen minúscula, no String
Una listastring[]o Array<string>, es lo mismo
Varios valores de tipos fijos[string, number]una tupla tiene longitud fija
Algo que puede faltardescuento?: numberes number | undefined: compruébalo
Algo que no debe cambiarreadonly id: numbersolo lo comprueba el compilador
No sé qué llegaunknownany 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.