Ir al contenido

TypeScript

TypeScript es un superset de JavaScript desarrollado por Microsoft que añade tipado estático opcional. Todo código JavaScript válido es también TypeScript válido. Al compilar, TypeScript produce JavaScript puro que cualquier navegador puede ejecutar.

La principal limitación de JavaScript es que los errores solo se detectan en tiempo de ejecución, cuando el usuario ya está usando la aplicación. TypeScript los detecta mientras se escribe el código, antes de ejecutar nada.

TypeScript permite declarar explícitamente el tipo de cada variable. Si no se declara, el compilador infiere el tipo según el valor asignado. Las dos palabras clave modernas son const (inmutable) y let (mutable). Evite var, tiene alcance de función y genera comportamientos inesperados.

// Tipado explícito
const nombre: string = "Ana";
let edad: number = 21;
const activo: boolean = true;
// TypeScript infiere el tipo del valor inicial
const pi = 3.14; // inferido: number
const saludo = "Hola"; // inferido: string
// let puede reasignarse al mismo tipo
let contador: number = 0;
contador = 1; // ✓ OK
// contador = "uno"; ✗ Error: string no es number

TypeScript usa las mismas estructuras de control que JavaScript, if/else, switch y bucles, con la ventaja de que el compilador verifica que las condiciones sean del tipo correcto.

// if / else
const nota: number = 3.5;
if (nota >= 3.0) {
console.log("Aprobado");
} else {
console.log("Reprobado");
}
// switch
enum Dia { Lunes, Martes, Miercoles }
const hoy: Dia = Dia.Lunes;
switch (hoy) {
case Dia.Lunes: console.log("Inicio de semana"); break;
case Dia.Martes: console.log("Segundo día"); break;
default: console.log("Otro día");
}
// for...of, iterar un arreglo tipado
const cursos: string[] = ["ISIS2603", "MISO4208"];
for (const curso of cursos) {
console.log(curso);
}
// while
let i: number = 0;
while (i < 3) {
console.log(i);
i++;
}
enum Dia { Lunes, Martes, Miercoles }
const hoy: Dia = Dia.Lunes;

 (hoy) {
  case Dia.Lunes:  console.log("Inicio de semana"); ;
  default:         console.log("Otro día");
}

En TypeScript (y JavaScript) todos los números, enteros y decimales, son del tipo number. No existe un tipo int separado. Internamente usa el estándar IEEE 754 de doble precisión.

// Enteros y decimales: mismo tipo
const edad: number = 21;
const precio: number = 19.99;
const negativo: number = -5;
// Operadores aritméticos
const suma = edad + precio; // 40.99
const resto = 10 % 3; // 1, módulo
const potencia = 2 ** 8; // 256, exponenciación
// Valores especiales
const infinito = 1 / 0; // Infinity
const noEsNum = 0 / 0; // NaN
Number.isNaN(noEsNum); // true
Number.isFinite(infinito); // false
Number.parseInt("42px"); // 42
Number.parseFloat("3.14abc"); // 3.14
// Math, operaciones comunes
Math.round(3.7); // 4, redondear al entero más cercano
Math.floor(3.9); // 3, redondear hacia abajo
Math.ceil(3.1); // 4, redondear hacia arriba
Math.abs(-7); // 7, valor absoluto
Math.max(1,5,3); // 5, máximo
Math.min(1,5,3); // 1, mínimo
Math.sqrt(16); // 4, raíz cuadrada
Math.pow(2, 10); // 1024

Los strings representan texto. TypeScript soporta tres formas de delimitarlos: comillas simples, dobles y acentos graves (template literals). Las tres crean el mismo tipo string; la diferencia está en sus capacidades.

// ① Comillas simples
const a: string = 'Hola mundo';
// ② Comillas dobles
const b: string = "Hola mundo";
// ③ Template literals (acento grave / backtick `)
// · Interpolación con ${expresión}
// · Pueden ocupar varias líneas sin \n
const nombre = "Ana";
const c: string = `Hola, ${nombre}!`; // "Hola, Ana!"
const multi = `Primera línea
Segunda línea`;
// Métodos de manipulación
const s = " TypeScript ";
s.trim(); // "TypeScript"
s.toUpperCase(); // " TYPESCRIPT "
s.toLowerCase(); // " typescript "
const t = "ISIS2603";
t.length; // 8
t.includes("2603"); // true
t.startsWith("ISIS"); // true
t.endsWith("3"); // true
t.indexOf("26"); // 4
t.slice(0, 4); // "ISIS"
t.replace("ISIS", "MISO"); // "MISO2603"
t.split(""); // ["I","S","I","S","2","6","0","3"]
"ha".repeat(3); // "hahaha"
const s = "  TypeScript  ";
s.();                 // "TypeScript"

const t = "ISIS2603";
t.("2603");      // true
t.();          // "ISIS2603"

El tipo boolean solo tiene dos valores posibles: true y false. Es el tipo de retorno de las comparaciones y el tipo esperado en condiciones de if y while.

const activo: boolean = true;
const inactivo: boolean = false;
// Operadores de comparación, producen boolean
5 === 5; // true, igualdad estricta (tipo + valor)
5 !== 3; // true, desigualdad estricta
5 > 3; // true
5 < 3; // false
"a" === "a"; // true
// Operadores lógicos
const mayorDeEdad = activo && edad >= 18; // AND, ambos true
const puedeAcceder = activo || esAdmin; // OR, al menos uno true
const noActivo = !activo; // NOT → false
// Truthy y falsy
// Falsy: 0, "", null, undefined, NaN
// Truthy: cualquier otro valor
if (0) { /* nunca ejecuta */ }
if ("") { /* nunca ejecuta */ }
if (42) { /* sí ejecuta */ }
if ("x") { /* sí ejecuta */ }
5  5;         // true, mismo tipo y valor
"1" == 1;       // true, == sí convierte tipos
"1"  1;      // false, string distinto de number

🔵 null, undefined y tipos especiales de TS

Sección titulada «🔵 null, undefined y tipos especiales de TS»

TypeScript añade varios tipos para describir situaciones especiales: ausencia de valor, tipos incógnitos, funciones que nunca retornan, etc.

// undefined, variable declarada sin valor inicial
let sinValor: string | undefined;
console.log(sinValor); // undefined
// null, ausencia intencional de valor
let usuario: string | null = null;
usuario = "Ana"; // se asigna después
// any, desactiva el chequeo de tipos (evitar)
let dato: any = "texto";
dato = 42; // sin error, sin protección
// unknown, alternativa segura a any
// hay que verificar el tipo antes de usar
let entrada: unknown = obtenerValor();
if (typeof entrada === "string") {
console.log(entrada.toUpperCase()); // seguro
}
// void, función que no retorna valor
function log(msg: string): void {
console.log(msg);
}
// never, función que NUNCA retorna (siempre lanza error)
function fallo(msg: string): never {
throw new Error(msg);
}
// ??, nullish coalescing: valor por defecto si null/undefined
const nombre = usuario ?? "Anónimo";
// ?., optional chaining: acceso seguro a propiedades
const longitud = usuario?.length; // undefined si null
// as, type assertion: indicar el tipo exacto al compilador
const btn = document.getElementById("btn") as HTMLButtonElement;
let usuario: string | null = null;
const nombre = usuario  "Anónimo";       // "Anónimo"

const longitud = usuariolength;            // undefined, sin error

Además de los primitivos, TypeScript soporta estructuras de datos más ricas: enumeraciones para conjuntos de constantes nombradas, arreglos para colecciones homogéneas, y tuplas para colecciones de longitud y tipos fijos.

// Enum, conjunto de constantes nombradas
enum Rol { Estudiante, Profesor, Admin }
const miRol: Rol = Rol.Estudiante;
// Array, dos sintaxis equivalentes
const notas: number[] = [4.5, 3.8, 5.0];
const nombres: Array<string> = ["Ana", "Luis"];
// Tuple, longitud y tipos estrictamente fijos
const coordenada: [number, number] = [4.71, -74.07];
const entrada: [string, number] = ["Ana", 21];

En TypeScript las funciones declaran el tipo de cada parámetro y el tipo de retorno. Un parámetro seguido de ? es opcional. Las funciones también son objetos, se pueden pasar como argumentos o devolver desde otras funciones.

// Parámetros tipados y tipo de retorno explícito
function sumar(a: number, b: number): number {
return a + b;
}
// Parámetro opcional con ?
function saludar(nombre: string, titulo?: string): string {
return `Hola, ${titulo ?? ''} ${nombre}`;
}
saludar("Ana"); // "Hola, Ana"
saludar("Ana", "Dra."); // "Hola, Dra. Ana"
// Arrow function
const doble = (x: number): number => x * 2;
// Función como argumento (las funciones son objetos)
function aplicar(fn: (n: number) => number, val: number): number {
return fn(val);
}
aplicar(doble, 5); // 10

🔮 ¿Qué crees que imprime este código?

function saludar(nombre: string, titulo?: string): string {
  return `Hola, ${titulo ?? ''} ${nombre}`;
}

console.log(saludar("Ana"));

El operador => define una función flecha (arrow function). Es una sintaxis compacta que reemplaza la palabra clave function y tiene dos variantes según la complejidad del cuerpo.

// Sintaxis completa: con llaves y return explícito
const cuadrado = (x: number): number => {
return x * x;
};
// Retorno implícito: sin llaves, una sola expresión
const cuadrado = (x: number): number => x * x;
// Un solo parámetro: los paréntesis son opcionales
const doble = (x: number) => x * 2;
// Sin parámetros: paréntesis vacíos obligatorios
const ahora = (): Date => new Date();

La flecha brilla especialmente en los métodos de array map, filter y forEach, donde la función se pasa como argumento:

const notas: number[] = [3.2, 4.5, 2.8, 4.9];
// map, transforma cada elemento
const redondeadas = notas.map(n => Math.round(n));
// [3, 5, 3, 5]
// filter, conserva los que cumplen la condición
const aprobadas = notas.filter(n => n >= 3.0);
// [3.2, 4.5, 4.9]
// forEach, ejecuta una acción por cada elemento
aprobadas.forEach(n => console.log(`Nota: ${n}`));
// Retorno implícito, sin llaves ni return
const doble = (x: number) => x * 2;

// Retorno explícito, con llaves
const cuadrado = (x: number): number => {
   x * x;
};

Pruébelo usted mismo: modifique el arreglo notas, cambie el umbral del filter, o agregue una nueva operación al map, y ejecute el código para ver la salida real.

🛝 Playground, edita y ejecuta TypeScript

Consola:

// la salida aparece aquí al presionar Ejecutar

JavaScript, a partir de ECMAScript 2015, introdujo la palabra clave class al lenguaje. TypeScript se apoya en esa sintaxis y le añade tipos: los atributos, los parámetros del constructor y los métodos quedan tipados, y el compilador verifica que todo compile antes de ejecutar nada.

Aquí conviene separar dos ideas que se confunden fácilmente:

  • Una clase es una plantilla que, al invocarla con new, produce objetos concretos en memoria. La clase existe en el JavaScript compilado.
  • Una interface es un contrato: describe qué forma debe tener un objeto (o qué métodos debe tener una clase), pero no crea nada por sí misma. Es una construcción exclusiva de TypeScript que desaparece por completo al compilar a JavaScript.

Declarar una clase no crea ningún objeto todavía, solo define la forma que tendrán sus instancias. El objeto real aparece cuando se invoca el constructor con new.

class Course {
name: string;
credits: number;
teacherName: string;
constructor(name: string, credits: number, teacherName: string) {
this.name = name;
this.credits = credits;
this.teacherName = teacherName;
}
}
// Course es la plantilla; new Course(...) crea el objeto en memoria
const isis2603 = new Course("Ingeniería de Software", 3, "Ana Ruiz");
const miso4203 = new Course("Bases de Datos", 4, "Luis Pardo");
console.log(isis2603.name); // "Ingeniería de Software"
console.log(isis2603.credits); // 3
// Cada new produce un objeto independiente: modificar uno no afecta al otro
isis2603.credits = 4;
console.log(miso4203.credits); // sigue siendo 4, no cambió
// Un arreglo de objetos creados con la misma clase
const cursos: Course[] = [isis2603, miso4203];
cursos.forEach(c => console.log(`${c.name}: ${c.credits} créditos`));

TypeScript permite abreviar el constructor cuando lo único que hace es declarar y asignar atributos: agregando public antes de cada parámetro, el atributo se declara y se asigna en una sola línea, sin repetir this.x = x.

class Course {
constructor(
public name: string,
public credits: number,
public teacherName: string
) {}
}
const isis2603 = new Course("Ingeniería de Software", 3, "Ana Ruiz");
console.log(isis2603.name); // "Ingeniería de Software"

📜 Interfaces: contratos que un objeto debe cumplir

Sección titulada «📜 Interfaces: contratos que un objeto debe cumplir»

La forma más simple de interface describe únicamente la forma de los datos: qué atributos debe tener un objeto y de qué tipo.

interface CourseInfo {
name: string;
credits: number;
}
// Un objeto literal cumple la interface si tiene la forma correcta,
// sin decir en ningún lado "yo soy un CourseInfo"
const isis2603: CourseInfo = { name: "Ingeniería de Software", credits: 3 };
// Error: falta credits, TypeScript no compila
// const incompleto: CourseInfo = { name: "Cálculo" };

Una interface también puede exigir métodos, no solo atributos. Cuando una clase se compromete a cumplir ese contrato, lo declara explícitamente con implements:

interface Evaluable {
calcularPromedio(): number;
}
class Student implements Evaluable {
constructor(public name: string, private grades: number[]) {}
calcularPromedio(): number {
const suma = this.grades.reduce((acc, n) => acc + n, 0);
return suma / this.grades.length;
}
}
const ana = new Student("Ana", [4.5, 3.8, 5.0]);
console.log(ana.calcularPromedio()); // 4.433...

🔮 ¿Qué crees que imprime este código?

class Student implements Evaluable {
  constructor(public name: string, private grades: number[]) {}

  calcularPromedio(): number {
    const suma = this.grades.reduce((acc, n) => acc + n, 0);
    return suma / this.grades.length;
  }
}

const ana = new Student("Ana", [4.5, 3.8, 5.0]);
console.log(ana.calcularPromedio());

🧰 Utility Types: transformar tipos existentes

Sección titulada «🧰 Utility Types: transformar tipos existentes»

A veces necesita un tipo que es casi igual a otro, pero le falta o le sobra alguna propiedad. En vez de declarar una interface nueva y repetir todos los atributos, TypeScript trae utility types: tipos genéricos que transforman un tipo existente.

interface Artista {
id: number;
nombre: string;
paisOrigen: string;
albumes: string[];
}
// Omit<T, K>: el mismo tipo, sin las propiedades listadas
type NuevoArtista = Omit<Artista, 'id' | 'albumes'>;
// equivale a: { nombre: string; paisOrigen: string }
const nuevo: NuevoArtista = {
nombre: 'Shakira',
paisOrigen: 'Colombia',
// id y albumes no van: Omit los quitó del tipo
};

Omit<Artista, 'id' | 'albumes'> describe exactamente lo que envía un formulario de creación: todavía no hay id (lo asigna el servidor) y todavía no hay albumes (ese formulario no los pide). Es el mismo contrato Artista, menos las dos propiedades que no aplican todavía.

Escriba el nombre del tipo o la palabra clave correcta en cada campo. Use el botón Verificar para comprobar sus respuestas.

Complete las anotaciones de tipo para que cada variable corresponda correctamente al valor que se le asigna.

const nombre:  = "Ana";
let   edad:    = 25;
const activo:  = true;

Ejercicio 2, Función con parámetro opcional

Sección titulada «Ejercicio 2, Función con parámetro opcional»

Complete la función: el parámetro titulo debe ser opcional y la función debe retornar un string.

function saludar(nombre: string, titulo: string):  {
  return `Hola, ${titulo ?? ''} ${nombre}`;
}

Complete el código para crear dos objetos Course distintos a partir de la misma clase y acceder a uno de sus atributos.

class Course {
  constructor(public name: string, public credits: number) {}
}

const isis2603 =  Course("Ingeniería de Software", 3);
const miso4203 =  Course("Bases de Datos", 4);

console.log(isis2603.);  // "Ingeniería de Software"

Ejercicio 4, Clase que implementa una interface

Sección titulada «Ejercicio 4, Clase que implementa una interface»

Complete el nombre de la interface, la palabra clave que enlaza la clase con ella, y el tipo de retorno del método.

interface  {
  describir(): string;
}

class Curso  Describible {
  constructor(public nombre: string) {}

  describir():  {
    return `Curso: ${this.nombre}`;
  }
}
1. ¿Qué ventaja principal ofrece TypeScript sobre JavaScript?
2. ¿Qué palabra clave se prefiere en TypeScript para declarar variables que pueden cambiar de valor?
3. ¿Cómo se marca un parámetro de función como opcional en TypeScript?
4. ¿Cómo se declara correctamente un arreglo de strings en TypeScript?
5. ¿Qué devuelve document.getElementById("id") si el elemento no existe en el HTML?
6. ¿Qué es una interface en TypeScript?
7. Tiene interface Artista { id: number; nombre: string; albumes: string[] }. ¿Qué produce Omit<Artista, 'id' | 'albumes'>?

Ejercicios de análisis, funciones como parámetros

Sección titulada «Ejercicios de análisis, funciones como parámetros»

Estos ejemplos profundizan en el patrón que ya vio en map, filter y forEach: una función que recibe otra función como parámetro. No tienen una respuesta que el sitio pueda verificar automáticamente, resuélvalos por su cuenta o discútalos con un compañero.

function saludar(nombre: string, callback: (mensaje: string) => void): void {
const mensaje = `Hola, ${nombre}!`;
callback(mensaje);
}
saludar("Ana", (msg) => {
console.log(msg);
});

Ejercicio (explicar):

  1. ¿Cuál de los dos parámetros de saludar es en sí mismo una función? ¿Cómo se nota eso en su tipo?
  2. Explique en sus palabras, paso a paso, qué pasa desde que se llama saludar("Ana", ...) hasta que aparece el mensaje en consola.
  3. ¿Qué pasaría si dentro de saludar se borra la línea callback(mensaje);? ¿Se produciría un error, o simplemente no pasaría nada? ¿Por qué?
function aplicarATodos(numeros: number[], operacion: (n: number) => number): number[] {
const resultado: number[] = [];
for (const n of numeros) {
resultado.push(operacion(n));
}
return resultado;
}
const numeros = [1, 2, 3, 4];
const duplicados = aplicarATodos(numeros, (n) => n * 2);
console.log(duplicados);

Ejercicio (explicar):

  1. Sin ejecutar el código, diga qué imprime console.log(duplicados).
  2. operacion es un parámetro de aplicarATodos, pero también es una función. Explique qué “sabe” aplicarATodos sobre lo que hace operacion por dentro (¿nada, algo, todo?).
  3. Modifique la llamada para que en vez de duplicar, le sume 10 a cada número. ¿Qué línea cambió y por qué esa y no otra?
  4. Este patrón es exactamente lo que hace map internamente. ¿Por qué cree que a numeros.map(n => n * 2) no hay que decirle “a qué arreglo recorrer” mientras que a aplicarATodos sí?

Ejemplo 3, dos funciones como parámetros (condición + acción)

Sección titulada «Ejemplo 3, dos funciones como parámetros (condición + acción)»
function procesarSiCumple(
numeros: number[],
condicion: (n: number) => boolean,
accion: (n: number) => void
): void {
for (const n of numeros) {
if (condicion(n)) {
accion(n);
}
}
}
procesarSiCumple(
[10, 15, 22, 7, 30],
(n) => n % 2 === 0,
(n) => console.log(`${n} es par`)
);

Ejercicio (explicar):

  1. Identifique cuál función juega el papel de filter y cuál el de forEach dentro de procesarSiCumple.
  2. Recorra el arreglo [10, 15, 22, 7, 30] a mano, elemento por elemento, y diga cuáles pasan la condicion y cuáles no.
  3. ¿Qué imprime el programa completo?
  4. Reescriba la misma llamada usando numeros.filter(...).forEach(...) en cadena. ¿Cambia el resultado?
type Validador = (valor: string) => boolean;
function validarEntrada(valor: string, validador: Validador): void {
if (validador(valor)) {
console.log(`"${valor}" es válido`);
} else {
console.log(`"${valor}" no es válido`);
}
}
const noVacio: Validador = (v) => v.trim().length > 0;
validarEntrada("Hola", noVacio);
validarEntrada(" ", noVacio);

Ejercicio (explicar):

  1. Validador no es una clase ni una interfaz, ¿qué es exactamente? ¿Qué describe: un valor, o la forma de una función?
  2. ¿Qué imprime cada llamada a validarEntrada? Justifique con el comportamiento de v.trim().length > 0.
  3. Escriba un segundo validador, soloNumeros: Validador, que retorne true solo si el string contiene únicamente dígitos, y pruébelo con "123" y "12a".
  4. ¿Por qué declarar el tipo Validador aparte es mejor que escribir (valor: string, validador: (v: string) => boolean) directamente en la firma de validarEntrada?