El chequeo de tipos ocurre antes de ejecutar el código.
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.
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.
En el navegador termina ejecutándose JavaScript; los tipos desaparecen.
const saludar = (nombre: string): string => {
return `Hola, ${nombre}`;
};
saludar("Alejandro");
// saludar(42); // TypeScript lo rechaza¿Los tipos de TypeScript existen en runtime?
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.
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"
};typeMuy flexible para unions, aliases y composiciones.
interfaceExcelente para describir la forma de objetos y contratos extensibles.
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.
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]
};
}¿Qué ventaja ofrece declarar : Alumno como retorno?
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.
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.
inComprueba si una propiedad existe.
Una propiedad común permite modelar estados excluyentes.
Funciones propias que refinan tipos complejos.
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.
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>;¿Qué hace Partial<T>?
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.
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 });
});form?. evita asumir que el nodo existe.
querySelector<HTMLInputElement> hace disponible value con su tipo correcto.
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.
type/
├── models/ # entidades y contratos
├── functions/ # lógica reutilizable
└── index.ts # orquestación del DOM
assets/js/ # salida compilada
docs/ # teoría complementaria¿Dónde debería vivir una regla como “un curso no termina antes de empezar”?
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.
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"strict": true,
"noEmitOnError": true
}
}Obliga a contemplar null y undefined.
Evita generar JavaScript con el contrato roto.
¿Qué opción evita emitir JavaScript si hay errores?
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.
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;
}
}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.
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);
}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.
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";
}¿Un cast con as valida datos externos?
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.
Preserva seguridad en los bordes.
Las discriminated unions evitan combinaciones contradictorias.
Los tipos no prueban comportamiento.
Typecheck, tests y build deben ser reproducibles.
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.
- Definí el DTO de entrada.
- Creá un type guard.
- Mapeá el DTO a un modelo usable.
- Mostrá un error visible si el dato no es válido.
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.