Leer errores de TypeScript: entender mensajes complejos, pretty-ts-errors y técnicas de depuración

Los errores de TypeScript pueden parecer crípticos cuando involucran tipos genéricos profundamente anidados, pero con la estrategia correcta se pueden descifrar en segundos. La clave es leer de abajo a arriba, aislar el problema con @ts-expect-error o reveal_type, y usar herramientas que simplifican la representación.

Estrategia de lectura de abajo a arriba

TypeScript muestra los errores con el mensaje más específico al final. El punto de partida siempre es la última línea del error:

// Error típico al usar tipos profundamente anidados:
// Type '{ nombre: string; }' is not assignable to type 'DeepReadonly<Config>'.
//   Types of property 'db' are incompatible.
//     Type '{ host: string; }' is not assignable to type 'DeepReadonly<{ host: string; puerto: number; }>'.
//       Property 'puerto' is missing in type '{ host: string; }' but required
//       in type 'DeepReadonly<{ host: string; puerto: number; }>'.

// Lectura: el problema REAL está al final: falta 'puerto' en el objeto 'db'.
// El resto son capas de contexto que ayudan a ubicarlo.

@ts-expect-error para aislar el problema

Cuando un error aparece en medio de una expresión compleja, añadir @ts-expect-error en líneas candidatas ayuda a encontrar dónde empieza realmente:

// Error en cadena:
const resultado = pipeline(datos).filtrar(pred).mapear(fn).reducir(acc, 0);
//                                                        ^^^^^^ error aquí

// Aislar paso a paso:
const paso1 = pipeline(datos);
const paso2 = paso1.filtrar(pred);
// @ts-expect-error
const paso3 = paso2.mapear(fn);  // Si aquí no hay error, el problema está antes

// Una vez aislado, leer el tipo de paso2 con reveal_type:
// reveal_type(paso2); // Type: Pipeline<string[]> — ahí está la pista

reveal_type: inspeccionar tipos en tiempo de compilación

reveal_type es una función incorporada en TypeScript que causa un error con el tipo exacto del argumento. Es perfecta para depurar tipos sin salir del editor:

const usuarios = [
  { id: 1, nombre: 'Ana', activo: true },
  { id: 2, nombre: 'Ben', activo: false },
];

reveal_type(usuarios);
// Type is { id: number; nombre: string; activo: boolean; }[]

const activos = usuarios.filter((u) => u.activo);
reveal_type(activos);
// Type is { id: number; nombre: string; activo: boolean; }[]
// (filter no estrecha el tipo; habría que usar un type predicate)

AssertEqual para comparar tipos exactos

type AssertEqual<A, B> =
  [A] extends [B] ? ([B] extends [A] ? true : false) : false;

type Verificar<T extends true> = T;

// Tests de tipo en fichero separado:
type T1 = Verificar<AssertEqual<string | number, number | string>>; // true: OK
type T2 = Verificar<AssertEqual<ReturnType<typeof JSON.parse>, any>>; // true
// type T3 = Verificar<AssertEqual<string, number>>; // Error: false no extiende true

pretty-ts-errors: extensión para VS Code

La extensión pretty-ts-errors para VS Code formatea los errores de TypeScript con colores, sangría y enlaces a la documentación. Es especialmente útil con tipos genéricos anidados porque muestra la jerarquía del error con claridad visual.

Simplify: expandir tipos opacos

// Los tipos que resultan de intersecciones o mapped types suelen mostrarse
// como expresiones, no como objetos planos:
type Simplify<T> = { [K in keyof T]: T[K] } & {};

// Sin Simplify:
type A = { nombre: string } & { edad: number };
// Hover en VS Code: { nombre: string; } & { edad: number; }

// Con Simplify:
type B = Simplify<{ nombre: string } & { edad: number }>;
// Hover: { nombre: string; edad: number }  — mucho más legible

// Uso práctico en una utility type compleja:
type DeepRequired<T> = Simplify<{
  [K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K]
}>;

Tipos circulares

Los errores de tipo circular aparecen cuando un tipo se referencia a sí mismo de forma directa, sin la capa de indirección que TypeScript necesita para diferir la resolución:

// Error: Type alias 'Nodo' circularly references itself
// type Nodo = { valor: number; siguiente: Nodo };

// Solución 1: usar interfaz (las interfaces sí permiten recursividad directa):
interface Nodo { valor: number; siguiente: Nodo | null; }

// Solución 2: añadir indirección con un objeto intermedio:
type Nodo = { valor: number; siguiente: { valor: number } | null };

// Solución 3: usar un genérico deferido:
type Arbol<T> = { valor: T; hijos: Array<Arbol<T>> };

Imagen: Pexels / César Gaviria

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP