Programación · n.º 6 entre los lenguajes más usados

Aprende TypeScript desde cero

JavaScript con tipos: detecta los errores antes de ejecutar el código.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 17 conceptos clave
  • 43,8 % lo usa*

Qué es TypeScript y para qué sirve

TypeScript es JavaScript con anotaciones de tipo. Un compilador revisa el código antes de ejecutarlo y te avisa si, por ejemplo, pasas un texto donde se esperaba un número. Después genera JavaScript normal, así que funciona en cualquier navegador o en Node.js. Conviene saber algo de JavaScript antes de empezar.

  • Aplicaciones web grandes
  • Servidores con Node.js
  • Bibliotecas y herramientas

Para empezar: Prueba los ejemplos en el TypeScript Playground oficial, en el navegador, sin instalar nada.

Nivel 1 Básico

Lección 1 · Básico

Qué es TypeScript y por qué usarlo

En JavaScript, un error como sumar un número a un texto solo se descubre al ejecutar el programa. TypeScript añade tipos y un compilador (tsc) que revisa el código antes y te marca esos fallos en el editor.

El resultado es JavaScript corriente: los tipos se eliminan al compilar. Los archivos llevan la extensión .ts.

Ejemplo práctico
function doble(n: number) {
  return n * 2;
}

doble(4);        // 8
doble("cuatro"); // Error del compilador:
// Argument of type 'string' is not assignable to parameter of type 'number'.

Ejercicio

¿Cuándo detecta TypeScript un error de tipos?

Ver la solución

Respuesta: Al compilar, antes de ejecutar. El compilador revisa los tipos antes de generar el JavaScript, así que el fallo aparece mientras programas.

Lección 2 · Básico

Tipos básicos e inferencia

Los tipos básicos son string, number y boolean; las listas se escriben number[] o string[]. Se anotan con dos puntos tras el nombre.

No hace falta anotarlo todo: si escribes let edad = 30, TypeScript deduce que es number (inferencia de tipos).

Ejemplo práctico
let nombre: string = "Ana";
let edad = 30;            // inferido: number
const notas: number[] = [7, 8.5, 9];

edad = "treinta";
// Error: Type 'string' is not assignable to type 'number'.

Ejercicio

Completa el tipo de una lista de textos.

const nombres: [] = ["Ana", "Luis"];
Ver la solución

Respuesta: string. Una lista de textos es string[].

Lección 3 · Básico

Funciones con tipos

En una función se anotan los parámetros y, opcionalmente, lo que devuelve, después del paréntesis. Un ? tras el nombre hace que el parámetro sea opcional.

Así, quien use la función sabe exactamente qué tiene que pasarle, y el editor lo autocompleta.

Ejemplo práctico
function saludar(nombre: string, titulo?: string): string {
  return titulo ? `Hola, ${titulo} ${nombre}` : `Hola, ${nombre}`;
}

saludar("García", "Sra.");  // "Hola, Sra. García"
saludar("Luis");            // "Hola, Luis"

Ejercicio

¿Qué indica el ? en titulo?: string?

Ver la solución

Respuesta: Que el parámetro es opcional. Con ? el parámetro puede omitirse; dentro de la función su tipo es string | undefined.

Escribe el código

Añade los tipos a esta función: precio es un número, moneda es un texto que vale "€" si no se indica y la función devuelve un texto.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
function formatearPrecio(precio: number, moneda: string = "€"): string {
  return precio.toFixed(2) + " " + moneda;
}

Con un valor por defecto, TypeScript ya deduce que moneda es string, así que moneda = "€" también es válido. El tipo de retorno va después de los paréntesis.

Conceptos clave

Lección 4 · Básico

Interfaces: la forma de un objeto

Una interfaz describe qué propiedades tiene un objeto y de qué tipo. Si falta una obligatoria o sobra una desconocida al crear el objeto, el compilador avisa.

Con type puedes definir formas muy parecidas; las interfaces se suelen preferir para objetos.

Ejemplo práctico
interface Producto {
  nombre: string;
  precio: number;
  enOferta?: boolean;
}

const teclado: Producto = { nombre: "Teclado", precio: 25.9 };

function etiqueta(p: Producto): string {
  return `${p.nombre}: ${p.precio.toFixed(2)} €`;
}

Ejercicio

Completa la palabra que declara la forma de un objeto.

 Usuario {
  nombre: string;
}
Ver la solución

Respuesta: interface. interface Nombre { … } define las propiedades que debe tener un objeto.

Escribe el código

Declara la interfaz Producto con nombre (texto), precio (número) y stock (número, opcional). Después crea una constante teclado de tipo Producto con nombre «Teclado» y precio 35, sin stock.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
interface Producto {
  nombre: string;
  precio: number;
  stock?: number;
}

const teclado: Producto = { nombre: "Teclado", precio: 35 };

El ? hace que la propiedad sea opcional: el objeto puede no tenerla. Si stock no fuera opcional, TypeScript daría un error al crear el teclado sin él.

Nivel 2 Intermedio

Lección 5 · Intermedio

Uniones y estrechamiento

Un tipo unión admite varias opciones: string | number. También sirve con valores concretos, como "claro" | "oscuro".

Para usar algo específico de una de las opciones, compruébalo antes (con typeof, por ejemplo); TypeScript entiende que dentro de ese if el tipo ya es el concreto. Es el estrechamiento de tipos.

Ejemplo práctico
type Tema = "claro" | "oscuro";
let tema: Tema = "oscuro";

function formatear(valor: string | number): string {
  if (typeof valor === "number") {
    return valor.toFixed(2);   // aquí valor es number
  }
  return valor.trim();         // aquí valor es string
}

Ejercicio

Con type Tema = "claro" | "oscuro", ¿qué asignación da error?

Ver la solución

Respuesta: tema = "azul". Solo se admiten los dos valores de la unión; "azul" no es uno de ellos.

Encuentra los errores

normalizar recibe un texto o un número y siempre debería devolver un número. TypeScript marca 2 errores: encuéntralos.

Ver la solución
  • Línea 4: typeof devuelve "string" para los textos, nunca "text"; TypeScript avisa de que esa comparación no puede cumplirse. Corrección: if (typeof entrada === "string") {
  • Línea 7: Aquí entrada ya solo puede ser un número, que no tiene trim(). Basta con devolverlo. Corrección: return entrada;

Lección 6 · Intermedio

Genéricos

Un genérico es un tipo que se rellena al usarlo, escrito entre < y >. Permite escribir una función que sirve para listas de cualquier tipo sin perder la información de cuál es.

Array<number> es lo mismo que number[], y Promise<string> es una promesa que dará un texto.

Ejemplo práctico
function primero<T>(lista: T[]): T | undefined {
  return lista[0];
}

const n = primero([10, 20]);        // n: number | undefined
const s = primero(["a", "b"]);      // s: string | undefined

Ejercicio

En primero<T>(lista: T[]), ¿qué es T?

Ver la solución

Respuesta: Un tipo que se decide al llamar a la función. T es un parámetro de tipo: se sustituye por el tipo real de la lista que pases.

Encuentra los errores

ultimo debería devolver el último elemento de cualquier lista, y n debería valer 3. Hay 2 errores de tipos.

Ver la solución
  • Línea 1: El parámetro es una lista de elementos de tipo T, así que su tipo es T[]; con T a secas no tiene length ni posiciones. Corrección: function ultimo<T>(lista: T[]): T | undefined {
  • Línea 5: Se fuerza T a string, pero la lista tiene números. Lo más sencillo es no indicarlo y dejar que TypeScript lo deduzca (number). Corrección: const n: number | undefined = ultimo([1, 2, 3]);

Lección 7 · Intermedio

null y undefined sin sustos

Con la opción strict activada, un valor que puede faltar se escribe string | null y TypeScript te obliga a comprobarlo antes de usarlo. Así desaparecen muchos errores de «undefined is not a function».

El encadenamiento opcional ?. devuelve undefined si algo falta, en lugar de fallar, y ?? da un valor por defecto solo cuando el valor es null o undefined.

Ejemplo práctico
interface Usuario {
  nombre: string;
  direccion?: { ciudad: string };
}

function ciudad(u: Usuario): string {
  return u.direccion?.ciudad ?? "Sin ciudad";
}

ciudad({ nombre: "Eva" });                              // "Sin ciudad"
ciudad({ nombre: "Leo", direccion: { ciudad: "Vigo" } }); // "Vigo"

Ejercicio

Completa para usar «Anónimo» si el nombre es null o undefined.

const visible = nombre  "Anónimo";
Ver la solución

Respuesta: ??. ?? devuelve la parte derecha solo si la izquierda es null o undefined (a diferencia de ||, no cambia un texto vacío ni un 0).

Nivel 3 Avanzado

Lección 8 · Avanzado

Clases con modificadores

TypeScript añade a las clases de JavaScript modificadores como private (solo accesible dentro de la clase) y readonly (no se puede cambiar tras crearla).

Con implements una clase se compromete a cumplir una interfaz, y el compilador comprueba que tiene todo lo necesario.

Ejemplo práctico
interface Avisador {
  avisar(mensaje: string): void;
}

class AvisoConsola implements Avisador {
  private enviados = 0;
  constructor(readonly prefijo: string) {}

  avisar(mensaje: string): void {
    this.enviados++;
    console.log(`${this.prefijo} ${mensaje} (#${this.enviados})`);
  }
}

new AvisoConsola("[info]").avisar("Copia terminada");

Ejercicio

¿Qué impide readonly?

Ver la solución

Respuesta: Cambiar la propiedad después de crear el objeto. Una propiedad readonly se asigna al crear el objeto y ya no puede reasignarse.

Lección 9 · Avanzado

Tipos utilitarios

TypeScript incluye tipos que transforman otros: Partial<T> hace opcionales todas las propiedades, Pick<T, …> se queda con algunas, Readonly<T> impide modificarlas y Record<K, V> describe un objeto con claves de un tipo y valores de otro.

Te ahorran repetir definiciones casi iguales, por ejemplo para los datos de un formulario de edición.

Ejemplo práctico
interface Producto { id: number; nombre: string; precio: number }

type CambiosProducto = Partial<Producto>;      // todo opcional
type Resumen = Pick<Producto, "id" | "nombre">;
const stock: Record<string, number> = { teclado: 10, raton: 0 };

function actualizar(p: Producto, cambios: CambiosProducto): Producto {
  return { ...p, ...cambios };
}

Ejercicio

¿Qué tipo hace opcionales todas las propiedades de otro?

Ver la solución

Respuesta: Partial. Partial<T> crea un tipo igual a T pero con todas sus propiedades opcionales.

Lección 10 · Avanzado

Configurar un proyecto

TypeScript se instala con npm en cada proyecto y se configura con tsconfig.json. Activa "strict": true: incluye las comprobaciones más útiles, como las de null.

npx tsc compila todo el proyecto según esa configuración, y npx tsc --noEmit solo comprueba los tipos sin generar archivos.

Ejemplo práctico
# En la terminal
npm install --save-dev typescript
npx tsc --init      # crea tsconfig.json

# tsconfig.json (fragmento)
{
  "compilerOptions": {
    "strict": true,
    "target": "es2022",
    "outDir": "dist"
  }
}

npx tsc             # compila a la carpeta dist

Ejercicio

Completa la opción que activa todas las comprobaciones estrictas.

"compilerOptions": { "": true }
Ver la solución

Respuesta: strict. strict agrupa opciones como strictNullChecks o noImplicitAny.

Lección 11 · Avanzado

Tipos literales y as const

Un tipo literal admite un valor concreto y no cualquiera de su clase: "S" | "M" | "L" solo acepta esos tres textos. Es la forma habitual de tipar opciones fijas, como tallas, estados de un pedido o direcciones, y el editor te las sugiere al escribir.

as const convierte un objeto o una lista en valores literales de solo lectura. Con (typeof lista)[number] obtienes la unión de sus valores sin escribirlos dos veces.

Ejemplo práctico
type Talla = "S" | "M" | "L";

function precioCamiseta(talla: Talla): number {
  return talla === "L" ? 15 : 12;
}

console.log(precioCamiseta("M"));  // 12
// precioCamiseta("XL");           // error: "XL" no es una Talla

const ESTADOS = ["pendiente", "enviado", "entregado"] as const;
type Estado = (typeof ESTADOS)[number];  // "pendiente" | "enviado" | "entregado"

const estado: Estado = "enviado";
console.log(ESTADOS.indexOf(estado));    // 1

Escribe el código

Declara el tipo Direccion que solo admita "norte", "sur", "este" y "oeste", y escribe la función mover(dir: Direccion): string que devuelva "Vas hacia el " + dir.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
type Direccion = "norte" | "sur" | "este" | "oeste";

function mover(dir: Direccion): string {
  return "Vas hacia el " + dir;
}

Con este tipo, mover("arriba") da un error antes de ejecutar el programa, y el editor sugiere las cuatro opciones válidas.

Lección 12 · Avanzado

Uniones discriminadas

Cuando una variable puede ser varias formas de objeto, dales a todas una propiedad común con un valor literal distinto, como tipo: "circulo" o tipo: "rectangulo". Al comprobarla en un switch, TypeScript sabe qué otras propiedades existen en cada caso.

Si añades el caso default asignando el valor a una variable de tipo never, TypeScript te avisará cuando agregues una forma nueva y olvides tratarla en el switch.

Ejemplo práctico
type Forma =
  | { tipo: "circulo"; radio: number }
  | { tipo: "rectangulo"; ancho: number; alto: number };

function area(forma: Forma): number {
  switch (forma.tipo) {
    case "circulo":
      return Math.PI * forma.radio ** 2;    // aquí solo existe radio
    case "rectangulo":
      return forma.ancho * forma.alto;      // aquí, ancho y alto
    default: {
      const imposible: never = forma;       // error si falta algún caso
      return imposible;
    }
  }
}

console.log(area({ tipo: "rectangulo", ancho: 3, alto: 4 })); // 12
console.log(area({ tipo: "circulo", radio: 1 }).toFixed(2));  // "3.14"

Ejercicio

En el case "circulo", ¿por qué TypeScript permite usar forma.radio sin error?

Ver la solución

Respuesta: Porque al comprobar forma.tipo sabe que en ese caso es un círculo. La propiedad tipo «discrimina» la unión: cada valor corresponde a una sola forma, y TypeScript estrecha el tipo en cada case.

Lección 13 · Avanzado

unknown y guardas de tipo propias

Los datos que llegan de fuera (un JSON, lo que escribe una persona) no tienen un tipo garantizado. Tiparlos como unknown en vez de any obliga a comprobarlos antes de usarlos: TypeScript no te deja acceder a nada hasta que demuestres qué son.

Una guarda de tipo es una función que devuelve valor is Tipo: si devuelve true, TypeScript trata el valor como ese tipo a partir de ahí. Así la comprobación se escribe una vez y se reutiliza.

Ejemplo práctico
interface Usuario {
  nombre: string;
  edad: number;
}

function esUsuario(dato: unknown): dato is Usuario {
  return typeof dato === "object" && dato !== null
    && typeof (dato as Usuario).nombre === "string"
    && typeof (dato as Usuario).edad === "number";
}

const recibido: unknown = JSON.parse('{"nombre": "Ana", "edad": 31}');

if (esUsuario(recibido)) {
  console.log(recibido.nombre.toUpperCase()); // "ANA": aquí ya es Usuario
} else {
  console.log("Datos con un formato inesperado");
}

Escribe el código

Escribe la guarda de tipo esTexto(valor: unknown): devuelve true solo si valor es un texto, y su tipo de retorno tiene que ser valor is string.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
function esTexto(valor: unknown): valor is string {
  return typeof valor === "string";
}

Con : valor is string, después de if (esTexto(x)) TypeScript te deja usar los métodos de texto sobre x.

Lección 14 · Avanzado

Promesas con tipo: async y Promise<T>

Una función async siempre devuelve una promesa. Su tipo se escribe Promise<T>, donde T es lo que obtendrás con await: Promise<Usuario>, Promise<string[]>… Así, quien la llame sabe qué recibirá.

Los datos que llegan por red se tratan con cuidado: response.json() devuelve any, así que conviene darles un tipo (o comprobarlos con una guarda) en el momento de recibirlos.

Ejemplo práctico
interface Usuario {
  id: number;
  nombre: string;
}

// Simula una petición que tarda medio segundo
function pedirUsuario(id: number): Promise<Usuario> {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id, nombre: "Ana" }), 500);
  });
}

async function saludar(id: number): Promise<string> {
  const usuario = await pedirUsuario(id);  // usuario: Usuario
  return `Hola, ${usuario.nombre}`;
}

saludar(1).then((texto) => console.log(texto)); // "Hola, Ana"

Ejercicio

Completa el tipo de retorno de una función async que obtiene un Usuario.

async function cargar(): <Usuario> {
Ver la solución

Respuesta: Promise. Las funciones async devuelven Promise<T>; con await se obtiene el T.

Lección 15 · Avanzado

Tuplas y datos de solo lectura

Una tupla es una lista de tamaño fijo en la que cada posición tiene su propio tipo: [string, number] es exactamente un texto y después un número. Se usa para devolver dos valores juntos, como hace useState en React.

readonly impide modificar una propiedad o una lista después de crearla: TypeScript da un error si intentas cambiarla. Sirve para dejar claro qué datos no deben cambiar, como una configuración.

Ejemplo práctico
function minMax(numeros: number[]): [number, number] {
  return [Math.min(...numeros), Math.max(...numeros)];
}

const [menor, mayor] = minMax([7, 2, 9]);
console.log(menor, mayor);  // 2 9

interface Config {
  readonly url: string;
  readonly reintentos: number;
}

const config: Config = { url: "https://api.ejemplo.es", reintentos: 3 };
// config.reintentos = 5;   // error: es de solo lectura

const dias: readonly string[] = ["lunes", "martes"];
// dias.push("miércoles");  // error: no existe push en una lista readonly
console.log(config.url, dias.length);

Ejercicio

¿Qué tipo describe exactamente un par «nombre y edad», como ["Ana", 31]?

Ver la solución

Respuesta: [string, number]. [string, number] es una tupla: dos posiciones con su tipo. (string | number)[] admitiría cualquier cantidad de elementos y en cualquier orden.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

  • Texto y programación

    Base64, URL y JSON

    Codifica y decodifica Base64 y URL, y valida o formatea JSON con la línea del error.

  • Texto y programación

    Comparar dos textos

    Encuentra las diferencias entre dos versiones de un texto, línea a línea y palabra a palabra.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado TypeScript en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.