Ruta práctica · TypeScript

Aprendé TypeScript entendiendo el sistema de tipos.

De JavaScript tipado a una aplicación real: tipos, contratos, narrowing, genéricos, clases, DOM, módulos y decisiones de arquitectura que vas a reconocer en este mismo repositorio.

Módulo 01 · Modelo mental

TypeScript no es “JavaScript con tipos” solamente

TypeScript agrega un sistema de análisis estático sobre JavaScript. Te ayuda a detectar inconsistencias antes de ejecutar el programa y a expresar contratos que herramientas, editores y otras personas pueden comprender.

Compile time

El chequeo de tipos ocurre antes de ejecutar el código.

Runtime

En el navegador termina ejecutándose JavaScript; los tipos desaparecen.

primer contrato
const saludar = (nombre: string): string => {
  return `Hola, ${nombre}`;
};

saludar("Alejandro");
// saludar(42); // TypeScript lo rechaza
Checkpoint

¿Los tipos de TypeScript existen en runtime?

Módulo 02 · Tipos

Primitivos, unions e interfaces

No se trata de anotar todo. Dejá que TypeScript infiera cuando puede y escribí tipos explícitos en fronteras importantes: funciones públicas, modelos, datos externos y estados compartidos.

modelo.ts
type EstadoCurso = "borrador" | "publicado";

interface Curso {
  id: string;
  nombre: string;
  horas: number;
  estado: EstadoCurso;
  tags?: string[];
}

const curso: Curso = {
  id: "ts-101",
  nombre: "TypeScript Profesional",
  horas: 20,
  estado: "publicado"
};
type

Muy flexible para unions, aliases y composiciones.

interface

Excelente para describir la forma de objetos y contratos extensibles.

Módulo 03 · Funciones y objetos

Entradas claras, salidas previsibles

Una firma de función bien diseñada funciona como documentación ejecutable. Evitá any cuando el dominio puede expresarse de forma segura.

funciones.ts
interface Alumno {
  id: string;
  nombre: string;
  cursoIds: string[];
}

function inscribir(alumno: Alumno, cursoId: string): Alumno {
  if (alumno.cursoIds.includes(cursoId)) return alumno;

  return {
    ...alumno,
    cursoIds: [...alumno.cursoIds, cursoId]
  };
}
Checkpoint

¿Qué ventaja ofrece declarar : Alumno como retorno?

Módulo 04 · Narrowing

Reducir tipos antes de usar datos

Cuando un valor puede tener varias formas, TypeScript necesita evidencia para saber cuál tenés en cada rama. Eso es narrowing.

union discriminada
type Resultado =
  | { ok: true; data: string[] }
  | { ok: false; error: string };

function mostrar(resultado: Resultado) {
  if (resultado.ok) {
    console.log(resultado.data.join(", "));
    return;
  }

  console.error(resultado.error);
}
typeof

Útil para primitivos.

in

Comprueba si una propiedad existe.

discriminated unions

Una propiedad común permite modelar estados excluyentes.

type guards

Funciones propias que refinan tipos complejos.

Módulo 05 · Tipos avanzados

Genéricos y Utility Types

Los genéricos permiten conservar información de tipos al escribir lógica reutilizable. Los Utility Types transforman contratos existentes sin copiarlos.

generics.ts
function primero<T>(items: T[]): T | undefined {
  return items[0];
}

interface Profesor {
  id: string;
  nombre: string;
  email: string;
}

type AltaProfesor = Omit<Profesor, "id">;
type EdicionProfesor = Partial<AltaProfesor>;
Checkpoint

¿Qué hace Partial<T>?

Módulo 06 · Browser APIs

DOM, módulos y datos reales

Este repositorio usa TypeScript directamente sobre APIs del navegador. El tipado ayuda especialmente al trabajar con elementos que pueden no existir y con valores de formularios que llegan como strings.

dom.ts
const form = document.querySelector<HTMLFormElement>("#cursoForm");
const input = document.querySelector<HTMLInputElement>("#nombreCurso");

form?.addEventListener("submit", (event) => {
  event.preventDefault();

  const nombre = input?.value.trim();
  if (!nombre) return;

  console.log({ nombre });
});
Optional chaining

form?. evita asumir que el nodo existe.

Generics del DOM

querySelector<HTMLInputElement> hace disponible value con su tipo correcto.

Módulo 07 · Arquitectura

De archivo suelto a aplicación mantenible

Separá dominio, persistencia y UI. Los modelos no deberían conocer el DOM; la persistencia no debería decidir cómo se renderiza una tabla. Esa división mejora testabilidad y reduce cambios colaterales.

estructura conceptual
type/
├── models/       # entidades y contratos
├── functions/    # lógica reutilizable
└── index.ts      # orquestación del DOM

assets/js/        # salida compilada
docs/             # teoría complementaria
Checkpoint final

¿Dónde debería vivir una regla como “un curso no termina antes de empezar”?

Módulo 08 · Configuración

strict, tsconfig y un compilador que cuide el contrato

TypeScript gana valor cuando la configuración acompaña. strict debería ser el baseline en proyectos nuevos. Opciones más exigentes pueden incorporarse cuando el equipo está preparado.

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "strict": true,
    "noEmitOnError": true
  }
}
strictNullChecks

Obliga a contemplar null y undefined.

noEmitOnError

Evita generar JavaScript con el contrato roto.

Checkpoint

¿Qué opción evita emitir JavaScript si hay errores?

Módulo 09 · Dominio

Clases, composición e invariantes

Una clase tiene sentido cuando agrupa estado y comportamiento. Para datos transportables, una interface suele ser suficiente. Las reglas del dominio deben vivir donde puedan reutilizarse, no dentro de un click.

Curso.ts
class Curso {
  private alumnos = new Set<string>();

  constructor(public readonly id: number, public nombre: string) {}

  inscribir(alumnoId: string): void {
    this.alumnos.add(alumnoId);
  }

  get cantidadAlumnos(): number {
    return this.alumnos.size;
  }
}
Módulo 10 · Asincronía

Promises, async/await y errores seguros

Toda función async devuelve una Promise. El tipo expresa qué valor resolverá, pero los fallos de red y runtime siguen existiendo. En catch, refiná unknown antes de usarlo.

api.ts
async function cargarCurso(id: number): Promise<CursoDTO> {
  const response = await fetch(`/api/cursos/${id}`);
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  return response.json() as Promise<CursoDTO>;
}

try {
  await cargarCurso(1);
} catch (error: unknown) {
  if (error instanceof Error) console.error(error.message);
}
Módulo 11 · Fronteras

APIs, localStorage, DTOs y validación runtime

Los datos externos son unknown hasta validarlos. Un cast con as no valida. APIs, JSON, storage y formularios son boundaries que requieren comprobación explícita.

type guard
interface CursoDTO {
  id: number;
  nombre: string;
}

function isCursoDTO(value: unknown): value is CursoDTO {
  if (typeof value !== "object" || value === null) return false;
  const item = value as Record<string, unknown>;
  return typeof item.id === "number"
    && typeof item.nombre === "string";
}
Checkpoint

¿Un cast con as valida datos externos?

Módulo 12 · Criterio profesional

TypeScript maduro: menos magia, mejores contratos

La madurez no se mide por usar tipos complejos. Se mide por contratos claros, inferencia bien aprovechada, boundaries validados, poco any, estados imposibles eliminados y CI que impide regresiones.

unknown antes que any

Preserva seguridad en los bordes.

Estados válidos

Las discriminated unions evitan combinaciones contradictorias.

Tests siguen siendo necesarios

Los tipos no prueban comportamiento.

CI

Typecheck, tests y build deben ser reproducibles.

Proyecto final · TypeScript

Tipá una frontera real del laboratorio

Tomá una lectura de localStorage como unknown, validala, mapeala a un contrato de dominio y mostrala en UI sin usar any.

  1. Definí el DTO de entrada.
  2. Creá un type guard.
  3. Mapeá el DTO a un modelo usable.
  4. Mostrá un error visible si el dato no es válido.
Abrir laboratorio →

Ruta de TypeScript completada

Ahora podés volver al laboratorio y leer su implementación con otra perspectiva, o completar la ruta de SASS para entender también la arquitectura visual.