# TypeScript desde cero para proyectos profesionales

**Categoría:** Programación · **Nivel:** Inicial · **Lectura:** 14 min
**Publicada:** 2026-07-27 · **Actualizada:** 2026-07-27 · **Autoría:** Underc0de
**Versión HTML (canónica):** https://underc0de.org/guias/programacion/typescript-desde-cero/

Una capa de tipos que existe solo mientras programás y desaparece al compilar. Qué resuelve de verdad, qué configurar el primer día, y cuál es la escapatoria que arruina proyectos enteros.

## Respuesta rápida

**TypeScript** no es un lenguaje aparte: según su propio manual, «ofrece todas las funciones de JavaScript, y una capa adicional encima: el sistema de tipos de TypeScript». Tu JavaScript que funciona ya es TypeScript válido, así que se adopta de a poco. Esa capa **existe solo mientras programás**: al compilar, los tipos se borran y queda JavaScript común, por lo que **no hay ninguna comprobación en tiempo de ejecución**. Su tipado es **estructural**: se fija en la forma de los valores y no en el nombre del tipo. Lo primero que hay que hacer en un proyecto nuevo es activar `strict`, y lo primero que hay que evitar es `any`.

## Qué es, con precisión

El manual oficial define la relación en una frase que conviene leer entera: «TypeScript ofrece todas las funciones de JavaScript, y **una capa adicional encima: el sistema de tipos de TypeScript**». Y agrega la consecuencia: «tu código JavaScript que ya funciona también es código TypeScript».

De ahí salen las tres propiedades que hay que tener claras desde el principio:

![Diagrama de TypeScript. Arriba, el flujo: tu código TypeScript se compila y produce JavaScript común sin anotaciones, por lo que no hay comprobación de tipos en tiempo de ejecución. Al centro, el mismo error en JavaScript, que falla con un usuario real adelante, y en TypeScript con strict, donde aparece en el editor. Después, el tipado estructural. Abajo, la configuración recomendada con strict activado.](../../assets/img/guias/typescript-desde-cero-sistema-de-tipos.svg)

- **Es un superconjunto.** Se adopta gradualmente, archivo por archivo. No hay que reescribir un proyecto para empezar.
- **Los tipos se borran al compilar.** Lo que se ejecuta es JavaScript sin anotaciones, así que ninguna comprobación de tipos ocurre en tiempo de ejecución.
- **El tipado es estructural.** El manual lo dice así: «uno de los principios centrales de TypeScript es que la comprobación de tipos se centra en la *forma* que tienen los valores», lo que a veces se llama tipado estructural. «En un sistema de tipos estructural, si dos objetos tienen la misma forma, se consideran del mismo tipo.»

## Qué problema resuelve

El manual es sobrio en su promesa, y vale citarla porque marca la expectativa correcta: «el beneficio principal de TypeScript es que **puede señalar comportamiento inesperado en tu código, bajando la probabilidad de errores**».

Fijate en lo que no dice: no promete código sin errores. Lo que hace es **mover una clase concreta de errores** del momento en que un usuario los encuentra al momento en que estás escribiendo. Esa clase incluye lo que en JavaScript se descubre siempre tarde:

| Error | En JavaScript aparece… |
|---|---|
| Usar una propiedad que puede no existir | Cuando el dato viene incompleto, en producción |
| Un nombre de propiedad mal escrito | Como `undefined` silencioso, a veces meses después |
| Llamar una función con los argumentos al revés | Como un resultado raro, sin error |
| Olvidar un caso al agregar una opción nueva | Cuando alguien usa esa opción |
| Renombrar un campo y olvidar un lugar | En el lugar que se olvidó |

Y hay un beneficio secundario que en la práctica pesa tanto como el primero: **los tipos son documentación que no se desactualiza**. Cuando volvés a una función a los seis meses, su firma te dice qué recibe y qué devuelve sin tener que leer el cuerpo.

## Cómo empezar

Lo mínimo es instalar el compilador y crear la configuración:

```bash
# En el proyecto, como dependencia de desarrollo
npm install --save-dev typescript

# Crear el archivo de configuración
npx tsc --init

# Comprobar tipos sin generar archivos: el comando que más vas a usar
npx tsc --noEmit
```

Y la decisión que más impacto tiene en todo el proyecto va en el archivo de configuración. La documentación describe la opción `strict` así: «habilita una amplia variedad de comportamientos de comprobación de tipos que resultan en **garantías más fuertes de corrección del programa**. Activarla equivale a habilitar todas las opciones de la familia de modo estricto», y aclara que después se pueden desactivar comprobaciones individuales si hace falta.

```json
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "sourceMap": true
  },
  "include": ["src"]
}
```

> **Por qué `strict` el primer día y no «más adelante».** Es una decisión económica, no estética. En un proyecto nuevo activarlo cuesta **cero**: no hay código previo que se queje. Dos años después significa enfrentar cientos de errores acumulados de golpe, y el resultado casi siempre es apagarlo o llenar el código de escapatorias. Vale además saber que «las versiones futuras de TypeScript pueden introducir comprobaciones más estrictas bajo esta opción», así que actualizar puede traer errores nuevos: es el precio de tener la red más ajustada, y conviene pagarlo desde el principio.

## Inferencia: escribir menos tipos

El error más común de quien empieza es **anotar todo**. TypeScript deduce solo la mayoría de los tipos, y anotar de más agrega ruido sin agregar seguridad:

```typescript
// Ruido: el tipo es evidente y el compilador ya lo sabe
const nombre: string = 'Ana';
const total: number = items.length;

// Suficiente: se infieren string y number
const nombre = 'Ana';
const total = items.length;

// Acá sí conviene anotar: los parámetros y el retorno son el contrato
function calcularEnvio(peso: number, destino: string): number {
  // …
}
```

La regla práctica: **anotá los bordes, deducí el interior**. Los parámetros y el tipo de retorno de una función son un contrato con quien la usa, y conviene escribirlos; las variables locales dentro del cuerpo casi nunca lo necesitan.

Anotar el retorno tiene un beneficio extra que no se ve al principio: si el cuerpo cambia y deja de devolver lo declarado, el error aparece **en esa función** y no en los diez lugares que la llaman.

## Uniones y estrechamiento

Acá está la parte donde TypeScript devuelve la mayor parte de su valor, porque ataca la fuente de errores más común de JavaScript: **los valores que pueden faltar**.

Una **unión** declara que un valor puede ser de varios tipos, y el compilador entonces exige que trates todos los casos antes de usarlo:

```typescript
function buscarUsuario(id: string): Usuario | null {
  // …
}

const u = buscarUsuario(id);

u.nombre;  // ✗ Error: «u» puede ser null

// Estrechamiento: después del if, TypeScript sabe que u es Usuario
if (u === null) {
  return respuestaNoEncontrado();
}
u.nombre;  // ✓ acá ya es seguro
```

Ese mecanismo se llama **estrechamiento**: el compilador sigue el flujo del código y va descartando posibilidades a medida que las comprobás. Es lo que hace que el sistema resulte cómodo en lugar de burocrático, porque no hay que declarar nada extra: alcanza con escribir la comprobación que de todos modos correspondía.

Una variante que rinde mucho en la práctica son las **uniones discriminadas**, para modelar estados que se excluyen entre sí:

```typescript
type Resultado =
  | { estado: 'cargando' }
  | { estado: 'error'; mensaje: string }
  | { estado: 'listo'; datos: Usuario[] };

// El compilador no te deja leer «datos» hasta comprobar el estado,
// ni acceder a «mensaje» en el caso 'listo'.
switch (r.estado) {
  case 'cargando': return spinner();
  case 'error':    return aviso(r.mensaje);
  case 'listo':    return lista(r.datos);
}
```

Este patrón elimina de raíz una familia entera de errores: los estados imposibles. Con esta forma no se puede tener un error y datos al mismo tiempo, porque el tipo no lo permite.

## interface o type

La pregunta clásica, y la respuesta honesta es que **para describir la forma de un objeto son casi intercambiables**. Conviene elegir una convención de equipo y respetarla, en lugar de discutirlo en cada revisión. Las dos diferencias que sí importan:

| | `interface` | `type` |
|---|---|---|
| Qué puede nombrar | Formas de objetos | Cualquier tipo: uniones, funciones, derivados |
| Declaraciones repetidas | Se combinan y suman miembros | Error: no se puede redeclarar |
| Cuándo conviene | Formas de objetos, y extender tipos de bibliotecas ajenas | Uniones, alias y tipos calculados a partir de otros |

La combinación de declaraciones de `interface` tiene un uso concreto y valioso: agregar propiedades a tipos que definió otra biblioteca. Fuera de ese caso, es más una fuente de sorpresas que una ventaja.

## Genéricos, lo justo

Los **genéricos** permiten escribir algo que funciona con varios tipos *sin perder la información de cuál*. Es la diferencia entre una función que devuelve «algo» y una que devuelve exactamente lo que le pasaste:

```typescript
// Sin genéricos: se pierde el tipo y hay que volver a afirmarlo
function primero(lista: unknown[]): unknown { return lista[0]; }

// Con genéricos: si entra Usuario[], sale Usuario
function primero<T>(lista: T[]): T | undefined {
  return lista[0];
}

const u = primero(usuarios);  // Usuario | undefined, sin anotar nada
```

Un consejo que ahorra mucho tiempo: **no salgas a buscar dónde usar genéricos**. Aparecen naturalmente cuando escribís una función que se repite con tipos distintos, y ese es el momento de introducirlos. Escribir tipos genéricos complicados antes de necesitarlos es la versión TypeScript de la sobreingeniería, y el resultado son firmas que nadie —incluido su autor la semana siguiente— entiende.

## any, unknown y las escapatorias

TypeScript tiene tres formas de decirle al compilador «confiá en mí», y conviene saber qué cuesta cada una.

| Escapatoria | Qué hace | Cuándo se justifica |
|---|---|---|
| `any` | Desactiva la comprobación en ese punto y en todo lo derivado | Casi nunca. Es la que arruina proyectos |
| `unknown` | Acepta cualquier valor pero **obliga a comprobar** antes de usarlo | Siempre que de verdad no sepas el tipo |
| `as` (aserción) | Afirma un tipo sin verificarlo | Cuando sabés algo que el compilador no puede saber, y es puntual |

La diferencia entre `any` y `unknown` es la más útil de entender: **`any` dice «confiá en mí» y `unknown` dice «probá que es lo que decís»**. Con `unknown`, el compilador te fuerza a escribir la comprobación, y esa comprobación es justo el código que faltaba.

> **El síntoma que conviene vigilar en una revisión.** Un `any` agregado para «hacer que compile» no resuelve nada: **traslada el error al futuro y le quita la etiqueta**. Y se propaga, porque todo lo que se derive de ese valor también deja de comprobarse. Un proyecto con `any` repartido paga el costo de compilar sin recibir la garantía: es JavaScript con pasos extra.

## Los bordes del sistema

Esta es la sección que más malentendidos evita, y se desprende directamente de que los tipos se borran al compilar: **TypeScript no valida los datos que entran a tu programa**.

```typescript
// Esto NO comprueba nada en ejecución. Es una promesa tuya.
const usuario = await res.json() as Usuario;

// Si la API cambió y ya no manda «nombre», el error va a aparecer
// mucho más adelante, en un lugar que no tiene nada que ver.
usuario.nombre.toUpperCase();
```

En los bordes hace falta **validación real, escrita en código que se ejecuta**: respuestas de API, formularios, variables de entorno, archivos de configuración, mensajes de una cola, filas de una base de datos. El patrón correcto es al revés de lo que hace la mayoría: **primero se escribe la validación y de ahí se derivan los tipos**, en lugar de declarar un tipo y confiar.

Adentro del sistema, en cambio, los tipos alcanzan: si el dato entró validado, el compilador se encarga del resto del recorrido.

## Un proyecto profesional

Lo que distingue un proyecto que aprovecha TypeScript de uno que solo lo tiene instalado:

- `strict` activado desde el primer día, sin excepciones apagadas.
- La comprobación de tipos corre en integración continua, con `tsc --noEmit`, y falla el pipeline.
- Cero `any`, con una regla del analizador estático que lo prohíba y obligue a justificar cada excepción.
- Validación real en cada borde: API, formularios, variables de entorno, configuración.
- Tipos derivados de una única fuente de verdad, no duplicados a mano en varios lugares.
- Los tipos de las bibliotecas instaladas, cuando no vienen incluidos, agregados como dependencia de desarrollo.

El punto tres es el que más se descuida y el que más determina si el resto sirve: sin una regla que lo impida, `any` entra de a uno por vez y en un año el sistema de tipos es decorativo.

## Errores frecuentes

- **Anotar todo.** Agrega ruido sin agregar seguridad. Anotá los bordes de las funciones y dejá que el resto se deduzca.
- **Usar `any` para avanzar.** Es la decisión que, repetida, vuelve inútil todo el esfuerzo.
- **Confiar en `as` para datos externos.** Una aserción no comprueba nada: es una promesa que hacés vos.
- **Postergar `strict`.** Cuesta cero al principio y muchísimo después.
- **No correr la comprobación en integración continua.** Si solo corre en el editor de quien programa, deja de correr.
- **Escribir genéricos antes de necesitarlos.** Produce firmas ilegibles sin resolver ningún problema real.
- **Duplicar tipos a mano.** Cuando el mismo dato se declara en tres lugares, se desincronizan; conviene derivarlos de uno.
- **Creer que reemplaza a las pruebas.** Los tipos verifican formas; que la lógica sea correcta lo verifican las pruebas.

## Preguntas frecuentes

### ¿TypeScript es un lenguaje distinto de JavaScript?

No es un lenguaje separado sino una capa sobre JavaScript. El manual lo dice claramente: TypeScript ofrece todas las funciones de JavaScript y una capa adicional encima, su sistema de tipos, así que el código JavaScript que ya funciona también es código TypeScript. La consecuencia práctica es que se puede adoptar de forma gradual, archivo por archivo, sin reescribir nada. Y al compilar, esa capa desaparece: lo que se ejecuta es JavaScript común, sin una sola anotación de tipo.

### ¿Los tipos existen cuando el programa está corriendo?

No, y entender esto evita la mayoría de los malentendidos. Los tipos se borran al compilar, así que ninguna comprobación ocurre en tiempo de ejecución. Eso significa que TypeScript no valida los datos que llegan de una API, de un formulario o de una base de datos: si declarás que una respuesta tiene cierta forma, el compilador te cree y no comprueba nada. En los bordes del sistema hacen falta validaciones reales en código, y los tipos se derivan de esas validaciones.

### ¿Conviene activar strict desde el principio?

Sí, y es la decisión que más impacto tiene en todo el proyecto. La documentación describe strict como una opción que habilita una amplia variedad de comprobaciones de tipos que dan garantías más fuertes de corrección del programa, y aclara que se pueden desactivar comprobaciones individuales si hace falta. La razón para hacerlo el primer día es económica: activarlo en un proyecto nuevo cuesta cero, y hacerlo dos años después significa arreglar cientos de errores acumulados de golpe.

### ¿Cuándo uso interface y cuándo type?

En la mayoría de los casos son intercambiables para describir la forma de un objeto, así que la respuesta útil es elegir una convención por equipo y respetarla. Las diferencias que sí importan: interface permite declaraciones múltiples que se combinan, lo que es útil para extender tipos de bibliotecas ajenas; y type es más general, porque puede nombrar cualquier cosa, no solo objetos, incluidas uniones y tipos derivados de otros. Una regla práctica: interface para formas de objetos y type para todo lo demás.

### ¿Por qué es tan malo usar any?

Porque desactiva la comprobación en ese punto y en todo lo que se derive de él, y lo hace en silencio. Un proyecto con any repartido paga el costo de compilar sin recibir la garantía a cambio: es JavaScript con pasos extra. La alternativa cuando de verdad no se conoce el tipo es unknown, que también acepta cualquier valor pero obliga a comprobar de qué se trata antes de usarlo. Esa diferencia es la clave: any dice «confiá en mí» y unknown dice «probá que es lo que decís».

### ¿Vale la pena en un proyecto chico?

Depende de una sola pregunta: ¿va a seguir existiendo en seis meses y lo va a tocar alguien más, incluido vos? Si la respuesta es sí, conviene, y el costo de arranque es bajo. Si es un script de una vez, probablemente no. El punto de inflexión no es el tamaño del código sino su vida útil, porque el valor de los tipos aparece justamente cuando alguien vuelve a un archivo que ya no recuerda y necesita saber qué recibe y qué devuelve cada función.

## Fuentes

Documentación oficial y material de la comunidad consultados para esta guía. Fecha de consulta: 27 de julio de 2026.

### Aportes de la comunidad Underc0de

1. **Underc0de, foro.** [Sección Programación Web](https://underc0de.org/foro/programacion-web-247/). Hilos de la comunidad sobre desarrollo web y sus herramientas.
2. **Underc0de, foro.** [Conjunto de proyectos para practicar](https://underc0de.org/foro/programacion-general/conjunto-de-proyectos-para-practicar/), por *Khala*, 19 de septiembre de 2021. Ideas de proyectos donde aplicar lo de esta guía.

### Documentación oficial

3. **Microsoft.** [TypeScript for JavaScript Programmers](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html). La relación con JavaScript, el beneficio principal y la definición de tipado estructural, citados textualmente.
4. **Microsoft.** [TSConfig: strict](https://www.typescriptlang.org/tsconfig/strict.html). Qué habilita la opción, la posibilidad de desactivar comprobaciones individuales y la advertencia sobre versiones futuras.
5. **Microsoft.** [TSConfig: strictNullChecks](https://www.typescriptlang.org/tsconfig/strictNullChecks.html). La comprobación que sostiene el ejemplo de valores que pueden faltar.
6. **Microsoft.** [TSConfig: noImplicitAny](https://www.typescriptlang.org/tsconfig/noImplicitAny.html). La comprobación que evita que `any` entre sin que nadie lo escriba.

---

Esta guía forma parte de un proyecto de la comunidad Underc0de para organizar conocimiento técnico en español. Fuente canónica: https://underc0de.org/guias/programacion/typescript-desde-cero/
