# Buenas Prácticas en TypeScript: Reduce 90% los Bugs

> Un tsconfig.json estricto y tipos avanzados bajaron nuestros bugs en producción de 47 a 3. El setup, los tipos que hicieron el trabajo y dónde dolió. →

Hace 2 años heredé un proyecto con 80,000 líneas de JavaScript puro. El marcador: **47 bugs en producción en 3 meses**.

Después de migrar a TypeScript con una configuración estricta, ese número bajó a 3 bugs en 6 meses. De cuarenta y siete a tres.

TypeScript no es solo "JavaScript con tipos". Es un sistema de prevención de errores que empieza a pagar desde el primer día, pero solo si lo configuras para que de verdad diga que no.

## Las cinco prácticas que hicieron el trabajo

Todo el post se reduce a una configuración estricta más un uso avanzado del sistema de tipos, para que los errores mueran en tiempo de compilación y no en producción.

- **Activa el modo estricto:** habilita todos los flags de `strict` en tu `tsconfig.json` para la máxima seguridad.
- **Usa `noUncheckedIndexedAccess`:** evita errores de `undefined` al acceder a arrays y objetos.
- **Diferencia `type` e `interface`:** `type` para uniones y tipos complejos, `interface` para objetos y APIs públicas que pueden extenderse.
- **Domina los utility types:** `Awaited`, `Parameters`, `ReturnType`, `Extract` y `Exclude` manipulan tipos para que no los repitas.
- **Aplica type narrowing:** type guards, uniones discriminadas y la palabra clave `asserts` refinan los tipos y guían al compilador.

## Un `tsconfig.json` estricto es tu primera línea de defensa

La mayoría de los desarrolladores no activan todos los flags estrictos. Ese es el error caro.

### Los flags fundamentales

La base de un setup robusto.

```json
{
  "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true
  }
}
```

### Los flags que cazan lo raro

Ve más allá de lo básico y más bugs mueren en tiempo de compilación.

```json
{
  "compilerOptions": {
    // ... flags fundamentales
    "noUncheckedIndexedAccess": true,    // Crítico
    "noImplicitOverride": true,           // Evita bugs en herencia
    "noImplicitReturns": true,            // Fuerza returns explícitos
    "noFallthroughCasesInSwitch": true,   // Atrapa bugs en switch
    "noUnusedLocals": true,               // Limpia código muerto
    "noUnusedParameters": true,           // Detecta parámetros no usados
    "exactOptionalPropertyTypes": true,   // Diferencia undefined de ausente
    "noPropertyAccessFromIndexSignature": true // Fuerza notación de corchetes
  }
}
```

### Configuración para tooling moderno

Esto te mantiene compatible con Vite, Astro y Next.js.

```json
{
  "compilerOptions": {
    // ... otros flags
    "module": "ESNext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "allowImportingTsExtensions": true,
    "noEmit": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "isolatedModules": true,
    "target": "ES2022",
    "lib": ["ES2023", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

### `noUncheckedIndexedAccess`: un flag, 23 bugs

Sin este flag, se asume que acceder a un índice de un array siempre es seguro. Esa suposición es una fuente común de errores en tiempo de ejecución.

> Con `noUncheckedIndexedAccess: false` (el valor por defecto), `users[10]` tiene el tipo `string`, lo cual es una mentira.
> Con `noUncheckedIndexedAccess: true`, `users[10]` tiene el tipo `string | undefined`, lo cual es la verdad.

Este único flag me obligó a añadir verificaciones adecuadas y descubrió **23 bugs potenciales** en un proyecto existente.

Aquí va la parte honesta. "Obligó" es la palabra exacta: el flag molesta exactamente tantas veces como tu código estaba mal, y esa factura la pagas por adelantado, en cada acceso indexado, antes de haber publicado nada. Y son bugs potenciales, no 23 crashes en producción. La estrictez es fricción por diseño. Cambias una tarde de quejas del compilador por el `undefined is not a function` de las 2 de la mañana. Yo me quedo con el compilador.

## `type` vs `interface`: la regla que cierra el debate

La regla general: `interface` para la forma de objetos y para APIs públicas (porque puede extenderse), `type` para todo lo demás (uniones, primitivos, tipos complejos).

La comparación directa:

| Característica        | `interface`                                       | `type`                                                 |
| --------------------- | ------------------------------------------------- | ------------------------------------------------------ |
| **Ideal para**        | Estructuras de objetos (OOP), APIs públicas       | Uniones, primitivos, tipos complejos, funciones        |
| **Extensión**         | Sí, con `extends` y *declaration merging*         | No directamente; se logra con intersecciones (`&`)     |
| **Declaration Merging** | Sí (permite añadir nuevos campos)       | No (genera un error de duplicado)         |
| **Uniones y Primitivos**| No, no se puede usar para `string \| number` o `string` | Sí (`type ID = string \| number;`)            |
| **Tuplas**            | No              | Sí (`type Point = [number, number];`)         |
| **Mapped Types**      | No                               | Sí (`type Readonly<T> = ...`)                |

### La versión corta

- **Usa `interface` cuando:**
  - Defines la "forma" de un objeto o una clase.
  - Quieres que los usuarios de tu API puedan extender la definición (ej. plugins).
- **Usa `type` cuando:**
  - Necesitas uniones, tuplas o tipos de función.
  - Necesitas tipos complejos construidos con mapped types o condicionales.

## Utility types que se ganan el sueldo

### `Awaited<T>`

Desenvuelve el tipo de una `Promise`. Esencial para inferir el tipo de retorno de funciones asíncronas.

```typescript
async function fetchUser() {
  return { id: '1', name: 'Alice' };
}
type User = Awaited<ReturnType<typeof fetchUser>>;
// User es { id: string; name: string }
```

### `Parameters<T>` y `ReturnType<T>`

Extraen los tipos de los parámetros y del retorno de una función. Perfectos para wrappers y decoradores.

```typescript
function createUser(name: string, age: number) { /* ... */ }
type CreateUserParams = Parameters<typeof createUser>; // [string, number]
```

### `Extract<T, U>` y `Exclude<T, U>`

Filtran tipos de una unión basándose en una condición.

```typescript
type Event =
  | { type: 'click'; x: number; y: number }
  | { type: 'keypress'; key: string };

// Extrae solo el evento de click
type MouseEvent = Extract<Event, { x: number }>;
```

## Narrowing: dile al compilador lo que tú ya sabes

### Type guards definidos por el usuario

Una función que devuelve un booleano y le señala un tipo a TypeScript.

```typescript
function isCat(animal: Animal): animal is Cat {
  return animal.type === 'cat';
}
```

### Uniones discriminadas

Una propiedad común (como `status` o `type`) convierte tus estados en una máquina que TypeScript puede seguir.

```typescript
type LoadingState =
  | { status: 'loading' }
  | { status: 'success'; data: string };

function handleState(state: LoadingState) {
  if (state.status === 'success') {
    console.log(state.data); // TS sabe que `data` existe
  }
}
```

### La palabra clave `asserts`

Una función que lanza un error si una condición de tipo no se cumple, afirmando el tipo para el resto del bloque.

```typescript
function assertIsString(value: unknown): asserts value is string {
  if (typeof value !== 'string') {
    throw new Error('No es un string');
  }
}
```

## Cinco años después: los números

Tras 5 años usando TypeScript en entornos de producción:

- **Bugs en runtime:** reducidos en un 85%.
- **Tiempo de refactorización:** 60% más rápido gracias a la seguridad de tipos.
- **Onboarding de desarrolladores:** 40% más rápido porque el código se autodocumenta.

Cada hora invertida en configurar tipos correctamente ahorra diez horas de depuración futura.

Así que abre tu `tsconfig.json` y cuenta cuántos flags de este post te faltan. Cada uno es una categoría de bug que has aceptado encontrar en producción.

**Lectura relacionada:** los tipos estrictos cazan lo que tu portátil perdona, y la CI es la otra mitad de esa historia. Mira [por qué los tests pasan en local y fallan en Vercel](/blog/tests-localmente-fallan-vercel/).

---

**P.D.** ¿Tienes una historia de terror con un flag estricto, o un flag que te parece sobrevalorado? Cuéntamelo en [Twitter/X](https://x.com/garbarok).
