---
name: typescript-avanzado
description: >
  TypeScript avanzado: generics, conditional types, mapped types, template literal types,
  utility types, type guards, discriminated unions, branded types, operador satisfies,
  extensión de módulo (module augmentation). Con ejemplos prácticos orientados a producción.
version: "1.0.0"
herramientasPermitidas: [Read]
exclusiones:
  - "No cargar para errores de compilación TypeScript (TS2322, TS2345, tsconfig) — para diagnóstico de errores del compilador cargar `build-errors-typescript` o `typescript-diagnosticos`."
  - "No cargar para configurar tsconfig.json desde cero en un proyecto nuevo — la configuración básica (target, module, strict) no requiere types avanzados; cargar `node-experto` o el skill del framework."
  - "No cargar para patrones básicos de TypeScript (interfaces, enums, type aliases simples) — este skill es para tipos avanzados en código de producción; para introducción a TypeScript el contenido es excesivo."
  - "No cargar para problemas de tipos en Angular o React específicos del framework — los tipos de `@angular/core` o `react` tienen peculiaridades de framework; cargar `angular-moderno` o `react-experto`."
evolvable: true  # default para skill estandar
---
# TypeScript Avanzado — Sistema de Tipos Profundo

## Cuándo NO cargar

- La pregunta es sobre errores del compilador TypeScript (TS2322, TS2345, tsconfig) — para diagnóstico de errores cargar `build-errors-typescript` o `typescript-diagnosticos`.
- La tarea es configurar tsconfig.json desde cero — la configuración básica (target, module, strict) no requiere tipos avanzados; cargar `node-experto` o el skill del framework.
- La pregunta es sobre TypeScript básico (interfaces simples, enums, type aliases sin composición) — este skill cubre el sistema de tipos avanzado; para introducción a TypeScript el contenido es excesivo.
- La pregunta involucra tipos específicos de Angular (`Signal`, `DestroyRef`) o React (`FC`, `ReactNode`) — esos tienen peculiaridades de framework; cargar `angular-moderno` o `react-experto`.

## Por qué el sistema de tipos importa

TypeScript no es "JavaScript con tipos". Un sistema de tipos bien diseñado:
- Elimina clases enteras de bugs en tiempo de compilación.
- Actúa como documentación ejecutable y verificada.
- Permite refactoring seguro a escala.

**Regla de oro**: si usas `any` o `as X`, justifica por qué en un comentario.
Si no puedes justificarlo, el diseño tiene un problema.

---

## Reglas Obligatorias

1. **`strict: true` en tsconfig.json** — SIEMPRE activado, sin excepción.
2. **Cero `any` sin justificación** — cada uso de `any` requiere comentario explicando por qué.
3. **Discriminated unions con verificación exhaustiva** — usar `never` en default de switch.
4. **`satisfies` sobre `as`** — preferir `satisfies` cuando el narrowing importa.
5. **Type guards retornan `valor is Tipo`** — nunca `boolean` a secas.
6. **Branded types para IDs de distintos dominios** — previene mezclas silenciosas.

---

## Generics — Constraints con `extends`

```typescript
// MAL — demasiado permisivo
function obtenerPropiedad<T, K>(objeto: T, clave: K): any {
  return (objeto as any)[clave];
}

// BIEN — constraint garantiza que K es clave de T
function obtenerPropiedad<T, K extends keyof T>(objeto: T, clave: K): T[K] {
  return objeto[clave];
}
```

### Inferencia con `infer`

```typescript
type DesenvuelvePromesa<T> = T extends Promise<infer U> ? U : T;
type PrimerParametro<T extends (...args: any[]) => any> =
  T extends (primero: infer P, ...resto: any[]) => any ? P : never;
```

---

## Type Guards — Narrowing Seguro

```typescript
// Type guard para discriminated union
type Figura =
  | { tipo: "circulo"; radio: number }
  | { tipo: "rectangulo"; ancho: number; alto: number }
  | { tipo: "triangulo"; base: number; altura: number };

function calcularArea(figura: Figura): number {
  switch (figura.tipo) {
    case "circulo":
      return Math.PI * figura.radio ** 2;
    case "rectangulo":
      return figura.ancho * figura.alto;
    case "triangulo":
      return (figura.base * figura.altura) / 2;
    default:
      const _nunca: never = figura; // Verificacion exhaustiva
      throw new Error(`Figura desconocida: ${JSON.stringify(_nunca)}`);
  }
}

// Assertion function (TypeScript 3.7+)
function afirmarDefinido<T>(
  valor: T | null | undefined, campo: string
): asserts valor is T {
  if (valor == null) throw new Error(`Se requiere ${campo} pero es ${valor}`);
}
```

---

## Operador `satisfies` (TypeScript 4.9+)

```typescript
// as X elimina el narrowing — satisfies lo preserva
const coloresValidados = {
  rojo:  "#FF0000",
  verde: "#00FF00",
  azul:  "#0000FF",
} satisfies Record<string, `#${string}`>;
// coloresValidados.rojo es "#FF0000" (literal, no string)
```

---

## Checklist de Revisión

- [ ] Cero usos de `any` sin comentario justificando por qué.
- [ ] Los discriminated unions tienen verificación exhaustiva con `never`.
- [ ] Los IDs de distintos dominios usan branded types para evitar mezclas.
- [ ] `satisfies` se usa en lugar de `as` donde el narrowing importa.
- [ ] Los utility types se componen en lugar de redefinir interfaces manualmente.
- [ ] Los type guards retornan `valor is Tipo` (no `boolean`).
- [ ] `infer` se usa para extraer tipos en lugar de duplicarlos manualmente.
- [ ] El `tsconfig.json` tiene `strict: true` activado.

---

Para ejemplos completos de conditional types, mapped types, template literal types, branded types, utility types, extensión de módulo (module augmentation) y patrones compuestos (builder tipado), ver [recursos/patrones-y-ejemplos-completos.md](recursos/patrones-y-ejemplos-completos.md).

---

## Gotchas / Errores comunes no obvios

**`keyof` de un tipo con índice de string incluye `string | number`**: `keyof { [key: string]: string }` retorna `string | number`, no `string`. Causa: JavaScript convierte índices numéricos a string, así que TypeScript incluye `number` en el keyof de tipos con índice de string. Fix: si se espera solo `string`, usar `Extract<keyof T, string>` explícitamente.

**Conditional type con `extends` no distribuye sobre uniones cuando el tipo está envuelto**: `type EsString<T> = T extends string ? true : false` distribuye sobre `string | number` y retorna `boolean`. Pero `type EsString<T> = [T] extends [string] ? true : false` no distribuye — retorna `false` para `string | number`. Causa: envolver en tupla desactiva la distribución. Fix: elegir la forma correcta según si se quiere distribución o comportamiento de subtype check exacto.

**`satisfies` no restringe el tipo inferido — solo lo valida**: `const config = { ... } satisfies Record<string, number>` valida que cada valor es `number`, pero el tipo inferido de `config.puerto` sigue siendo el literal `3000`, no `number`. Esto es la funcionalidad correcta, pero causa confusión cuando se espera que `satisfies` genere un tipo más ancho para reutilización. Fix: si se necesita el tipo ancho, combinar `satisfies` con anotación explícita: `const config: Record<string, number> = { ... } satisfies Record<string, number>`.

**`infer` en posición contravariante infiere `never`**: `type Parametro<T> = T extends (arg: infer U) => void ? U : never` sobre una unión `((a: string) => void) | ((a: number) => void)` infiere `string & number` (never), no `string | number`. Causa: los parámetros de función son contravariantes — TypeScript intersecta en lugar de unir. Fix: extraer el tipo en posición covariante (retorno) cuando sea posible, o aceptar que el tipo de parámetros de una unión de funciones es la intersección.
