{"schemaVersion":1,"id":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference","title":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","description":"Análisis de las características más grandes de TypeScript 5.5: type predicates inferidos que funcionan de verdad, isolated declarations para builds más rápidos, mejoras en config extends, regex type narrowing y patrones que resultarán problemáticos.","language":"es","datePublished":"2025-12-24","dateModified":null,"contentFormat":"text/markdown","content":"TypeScript\n\n# TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real\n\n2025-12-249 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/typescript-5-5-type-predicates-inference>)\n\n## Type predicates inferidos: la mejora de calidad de vida más grande en años\n\nEsta es probablemente la mejora de calidad de vida más anticipada para desarrolladores TypeScript. Considerar el patrón más común en cualquier codebase:\n\n```typescript\n// Antes de TS 5.5\nconst values: (string | null)[] = [\"hola\", null, \"mundo\", null, \"!\"];\n\n// Esto DEBERÍA darnos string[], pero no\nconst filtered = values.filter((v) => v !== null);\n// Tipo: (string | null)[], MAL! Acabamos de filtrar los nulls!\n\n// El workaround era este código adicional\nconst filteredCorrect = values.filter((v): v is string => v !== null);\n// Tipo: string[], Correcto, pero boilerplate molesto\n```\n\nEn TypeScript 5.5, el compilador infiere automáticamente que ` (v) => v !== null ` es un type predicate. Sin anotación:\n\n```typescript\n// TypeScript 5.5\nconst values: (string | null)[] = [\"hola\", null, \"mundo\", null, \"!\"];\n\nconst filtered = values.filter((v) => v !== null);\n// Tipo: string[], POR FIN!\n\n// Funciona con patrones más complejos también\ninterface User {\n id: string;\n name: string;\n email: string | null;\n deletedAt: Date | null;\n}\n\nconst users: User[] = getUsers();\n\nconst activeWithEmail = users.filter((u) => {\n return u.deletedAt === null && u.email !== null;\n});\n```\n\n### Dónde funciona la inferencia (y dónde no)\n\nLa inferencia tiene reglas específicas que se necesita conocer:\n\n```typescript\n// FUNCIONA: sentencia return explícita\nconst isString = (x: unknown) => {\n return typeof x === \"string\";\n};\n// Inferido: (x: unknown) => x is string\n\n// NO FUNCIONA: cuerpo de expresión (sin llaves)\nconst isStringExpr = (x: unknown) => typeof x === \"string\";\n// Inferido: (x: unknown) => boolean, SIN predicado!\n// Esto es porque los cuerpos de expresión podrían tener side effects\n// que hacen la inferencia de predicados unsound\n\n// FUNCIONA: chequeos de null/undefined\nconst isDefined = <T>(x: T | null | undefined) => {\n return x !== null && x !== undefined;\n};\n// Inferido: (x: T | null | undefined) => x is T\n\n// FUNCIONA: chequeos instanceof\nconst isDate = (x: unknown) => {\n return x instanceof Date;\n};\n// Inferido: (x: unknown) => x is Date\n\n// FUNCIONA: chequeos de propiedad\nconst hasName = (x: object) => {\n return \"name\" in x;\n};\n// Inferido: (x: object) => x is object & Record<\"name\", unknown>\n\n// NO FUNCIONA: condiciones complejas con side effects\nlet sideEffect = false;\nconst isBad = (x: unknown) => {\n sideEffect = true; // Side effect previene inferencia\n return typeof x === \"string\";\n};\n// Inferido: (x: unknown) => boolean\n```\n\n### El gotcha de la migración\n\nEste es un gotcha común de migración. Consideremos código así:\n\n```typescript\ninterface ApiResponse {\n data: User | null;\n error: string | null;\n}\n\nconst responses: ApiResponse[] = await Promise.all(requests);\n\nconst successful = responses.filter((r) => r.data !== null);\n\n// Antes: successful[0].data era User | null (sin narrowing)\n// Después: TypeScript ahora hace narrowing mejor a través de filter\n\n// El PROBLEMA: código downstream que hacía null checks redundantes\n// ahora se marca porque TS sabe que el valor no puede ser null\nsuccessful.forEach((r) => {\n if (r.data === null) {\n // TS 5.5: Error! Esta condición siempre será false\n // Porque .filter() ya hizo narrowing\n return;\n }\n processUser(r.data);\n});\n```\n\nEl fix suele ser remover los null checks ahora redundantes. Pero si teners un codebase grande, eso significa docenas de cambios. Un approach práctico: corregirlos gradualmente con comentarios ` @ts-expect-error ` y una regla de lint para limpiarlos con el tiempo.\n\n## Isolated declarations: revolución de velocidad de build en monorepos\n\nEsta es la feature que más importa para equipos grandes. ` isolatedDeclarations ` es una nueva opción del compilador que requiere que las funciones y valores exportados tengan anotaciones de tipo de retorno explícitas:\n\n```typescript\n// tsconfig.json\n{\n \"compilerOptions\": {\n \"isolatedDeclarations\": true,\n \"declaration\": true\n }\n}\n\n// SIN isolatedDeclarations (return types implícitos están bien)\nexport function add(a: number, b: number) {\n return a + b; // Return type inferido como: number\n}\n\n// CON isolatedDeclarations (return type explícito requerido)\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\n// Error sin return type explícito:\nexport function processData(input: string) {\n // ^^^ Error!\n // La función exportada debe tener una anotación de tipo de retorno\n // explícita con --isolatedDeclarations\n const parsed = JSON.parse(input);\n return { ...parsed, processed: true };\n}\n\n// Fix: agregar return type explícito\ninterface ProcessedData {\n processed: true;\n [key: string]: unknown;\n}\n\nexport function processData(input: string): ProcessedData {\n const parsed = JSON.parse(input);\n return { ...parsed, processed: true };\n}\n```\n\n¿Por qué importa? Porque con tipos explícitos en todas las exportaciones, una herramienta puede generar archivos ` .d.ts ` para un paquete **sin hacer type-checking del paquete entero**. Esto significa:\n\n1. **Generación paralela de .d.ts**: Cada paquete en tu monorepo puede generar declarations independientemente\n2. **Builds incrementales**: Solo regenerar declarations para paquetes que cambiaron\n3. **Herramientas de terceros**: Tools como ` oxc `, ` swc ` y ` esbuild ` pueden generar archivos ` .d.ts ` sin correr el compilador TypeScript completo\n\nPara un monorepo con 23 paquetes, esto baja el tiempo de ` tsc --build ` de 94 segundos a 31 segundos.\n\n### Estrategia de adopción gradual\n\nNo se necesita habilitarlo en todos lados de una:\n\n```json\n// packages/shared-utils/tsconfig.json (paquete hoja, comenzar aquí)\n{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"isolatedDeclarations\": true\n }\n}\n\n// packages/api-client/tsconfig.json (depende de shared-utils)\n// Habilitar DESPUÉS de migrar shared-utils\n{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"isolatedDeclarations\": true\n }\n}\n\n// apps/web/tsconfig.json (app, no necesita habilitarse)\n{\n \"extends\": \"../../tsconfig.base.json\"\n}\n```\n\nComenzar con tus paquetes hoja (sin dependencias internas), después avanzar hacia adentro. Los paquetes de app no lo necesitan porque no producen declarations.\n\n## Config extends: la variable template ${configDir}\n\nTypeScript 5.5 introduce ` ${configDir} ` como variable template en paths de tsconfig. Esto resuelve un dolor de larga data en monorepos:\n\n```json\n// ANTES: los paths son relativos a la ubicación del config que extiende\n// tsconfig.base.json\n{\n \"compilerOptions\": {\n \"outDir\": \"./dist\", // Relativo a... ¿qué archivo?\n \"rootDir\": \"./src\", // Cambia de significado cuando se extiende\n \"paths\": {\n \"@/*\": [\"./src/*\"] // Se rompe en configs anidados\n }\n }\n}\n\n// DESPUÉS: ${configDir} siempre se refiere al directorio del archivo\n// tsconfig.base.json\n{\n \"compilerOptions\": {\n \"outDir\": \"${configDir}/dist\",\n \"rootDir\": \"${configDir}/src\",\n \"paths\": {\n \"@/*\": [\"${configDir}/src/*\"]\n }\n }\n}\n```\n\nAhora cuando ` packages/api/tsconfig.json ` extiende ` ../../tsconfig.base.json `, ` ${configDir} ` resuelve a ` packages/api/ `, no al root. No más paths rotos.\n\n## Regex type narrowing\n\nTypeScript 5.5 agrega type narrowing a través de llamadas ` .test() ` y ` .match() ` de expresiones regulares:\n\n```typescript\nfunction processInput(value: string | number) {\n if (typeof value === \"string\") {\n if (/^\\d+$/.test(value)) {\n // value sigue siendo string, pero TS ahora sabe que matcheó la regex\n const num = parseInt(value, 10); // TS sabe que esto es seguro\n }\n }\n}\n\n// Donde NO funciona\nconst result = \"hola\".search(/patron/);\n// search devuelve un number, no un boolean, sin narrowing\n\nconst replaced = \"hola\".replace(/h/, \"H\");\n// replace devuelve un string, sin narrowing\n```\n\n## Patrones del mundo real que mejoraron\n\nAlgunos patrones de un codebase real que mejoraron con la 5.5:\n\n### Filtrado de uniones discriminadas\n\n```typescript\ntype Event =\n | { type: \"click\"; x: number; y: number }\n | { type: \"keypress\"; key: string }\n | { type: \"scroll\"; delta: number };\n\nconst events: Event[] = getEvents();\n\n// Antes de TS 5.5: necesitabas predicado explícito\nconst clicks = events.filter(\n (e): e is Extract<Event, { type: \"click\" }> => e.type === \"click\"\n);\n\n// Después de TS 5.5: el predicado se infiere\nconst clicks = events.filter((e) => {\n return e.type === \"click\";\n});\n// Tipo: { type: \"click\"; x: number; y: number }[]\n```\n\n### Array.from con map y filter\n\n```typescript\nconst validEntries = Array.from(formData.entries())\n .map(([key, value]) => {\n if (typeof value !== \"string\") return null;\n if (value.trim() === \"\") return null;\n return { key, value: value.trim() };\n })\n .filter((entry) => {\n return entry !== null;\n });\n// TS 5.5: { key: string; value: string }[]\n// TS 5.4: ({ key: string; value: string } | null)[]\n```\n\n## Los números de rendimiento\n\nNúmeros reales de una migración de monorepo (23 paquetes, ~180K líneas de TypeScript):\n\n| Métrica | TS 5.4 | TS 5.5 | TS 5.5 + isolatedDecl |\n| --- | --- | --- | --- |\n| Build completo | 94s | 89s | 31s |\n| Incremental (1 archivo) | 12s | 11s | 3.2s |\n| Generación declarations | 28s | 26s | 8.1s |\n| Responsividad IDE | ~800ms | ~650ms | ~650ms |\n\nLa mejora con ` isolatedDeclarations ` es el número titular. Pasar de 94s a 31s para un build completo es la diferencia entre \"andá a buscar un café\" y \"ya terminó.\"\n\n## Consideraciones conocidas de actualización\n\nAlgunas cosas que vale la pena saber antes de upgradear:\n\n1. **Los predicados inferidos pueden ensanchar tus tipos**: Si una función antes devolvía ` boolean ` y ahora devuelve ` x is Foo `, código que dependía del return value booleano puede comportarse distinto con el análisis de control de flujo.\n2. **isolatedDeclarations + declaration maps**: Si usa ` declarationMap `, los maps ahora apuntan al source original incluso sin type checking completo. Asegurarse que tu configuración de source maps sea consistente.\n3. **La interacción satisfies + predicados inferidos**: ` satisfies ` no preserva el predicado inferido. Si teners ` const isString = ((x: unknown) => typeof x === 'string') satisfies (x: unknown) => boolean `, el predicado se pierde.\n4. **Regresión de rendimiento con conditional types profundamente anidados**: Existe un caso conocido donde la nueva inferencia de tipos de TS 5.5 causó un slowdown de 10x en un archivo específico con cadenas de ` Omit<Pick<Partial<...>>> ` muy anidadas. El fix fue simplificar los tipos.\n\nTypeScript 5.5 es una release genuinamente genial. Los type predicates inferidos arreglan uno de los papercuts más comunes del lenguaje. Isolated declarations desbloquean mejoras reales de velocidad de build para monorepos. Y las mejoras de config hacen que las configuraciones compartidas funcionen de verdad. Actualizar con cuidado, esperar algunos errores de tipo que en realidad son bug fixes, y disfrutar el viaje.\n\n## Preguntas frecuentes\n\n### ¿Qué son los type predicates inferidos en TypeScript 5.5?\n\nTypeScript 5.5 puede inferir automáticamente que una función como (x) =\\> x !== null actúa como type predicate (x is NonNullable\\<T\\>), sin que tengas que escribir la anotación 'is' manualmente. Esto hace que .filter(Boolean) y patrones similares funcionen correctamente con type narrowing.\n\n### ¿Qué es isolatedDeclarations y por qué me debería importar?\n\nisolatedDeclarations requiere que todas las funciones y valores exportados tengan anotaciones de tipo explícitas, permitiendo la generación de archivos de declaración sin type-checking completo. Esto habilita generación de .d.ts paralela e incremental, acelerando dramáticamente los builds de monorepos.\n\n### ¿TypeScript 5.5 rompe código existente?\n\nPotencialmente sí. Los type predicates inferidos pueden cambiar los tipos inferidos en llamadas .filter() existentes, lo que podría revelar errores de tipo previamente ocultos. El fix suele ser correcto, el comportamiento anterior era técnicamente incorrecto, pero puede sorprender.\n\n## Tips\n\n1. **Tip:** Los type predicates inferidos solo funcionan con sentencias ` return `, arrow functions con cuerpo de expresión como ` (x) => x !== null ` no infieren predicados. Envolverlos en llaves.\n2. **Tip:** Habilitar ` isolatedDeclarations ` gradualmente, comenzar con tus paquetes hoja en un monorepo y avanzar hacia adentro. Requiere return types explícitos en funciones exportadas.\n3. **Tip:** La variable template ` configDir ` en tsconfig extends mejora significativamente el flujo de trabajo en monorepos, utilizar ` ${configDir}/src ` en vez de paths relativos que se rompen con configs anidados.\n4. **Tip:** El regex type narrowing solo funciona con ` .test() ` y ` .match() `, ` .search() ` y ` .replace() ` no hacen narrowing de tipos. Esto agarra desprevenida a la gente constantemente.\n\n- typescript\n- type-predicates\n- isolated-declarations\n- type-narrowing\n- javascript\n- dx\n- compiler\n- type-system\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)","headings":[{"level":1,"text":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#title"},{"level":2,"text":"Type predicates inferidos: la mejora de calidad de vida más grande en años","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#type-predicates-inferidos-la-mejora-de-calidad-de-vida-ms-grande-en-aos"},{"level":3,"text":"Dónde funciona la inferencia (y dónde no)","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#dnde-funciona-la-inferencia-y-dnde-no"},{"level":3,"text":"El gotcha de la migración","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#el-gotcha-de-la-migracin"},{"level":2,"text":"Isolated declarations: revolución de velocidad de build en monorepos","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#isolated-declarations-revolucin-de-velocidad-de-build-en-monorepos"},{"level":3,"text":"Estrategia de adopción gradual","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#estrategia-de-adopcin-gradual"},{"level":2,"text":"Config extends: la variable template ${configDir}","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#config-extends-la-variable-template-configdir"},{"level":2,"text":"Regex type narrowing","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#regex-type-narrowing"},{"level":2,"text":"Patrones del mundo real que mejoraron","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#patrones-del-mundo-real-que-mejoraron"},{"level":3,"text":"Filtrado de uniones discriminadas","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#filtrado-de-uniones-discriminadas"},{"level":3,"text":"Array.from con map y filter","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#arrayfrom-con-map-y-filter"},{"level":2,"text":"Los números de rendimiento","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#los-nmeros-de-rendimiento"},{"level":2,"text":"Consideraciones conocidas de actualización","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#consideraciones-conocidas-de-actualizacin"},{"level":2,"text":"Preguntas frecuentes","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#questions"},{"level":3,"text":"¿Qué son los type predicates inferidos en TypeScript 5.5?","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-1"},{"level":3,"text":"¿Qué es isolatedDeclarations y por qué me debería importar?","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-2"},{"level":3,"text":"¿TypeScript 5.5 rompe código existente?","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-3"},{"level":2,"text":"Tips","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#article-tips"}],"sections":[{"heading":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","level":1,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference","content":"TypeScript"},{"heading":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","level":1,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#title","content":"2025-12-249 min lectura\n\nLucio Durán\nEngineering Manager & AI Solutions Architect\n\nTambién disponible en: [English](<https://lucioduran.com/blog/typescript-5-5-type-predicates-inference>)"},{"heading":"Type predicates inferidos: la mejora de calidad de vida más grande en años","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#type-predicates-inferidos-la-mejora-de-calidad-de-vida-ms-grande-en-aos","content":"Esta es probablemente la mejora de calidad de vida más anticipada para desarrolladores TypeScript. Considerar el patrón más común en cualquier codebase:\n\n```typescript\n// Antes de TS 5.5\nconst values: (string | null)[] = [\"hola\", null, \"mundo\", null, \"!\"];\n\n// Esto DEBERÍA darnos string[], pero no\nconst filtered = values.filter((v) => v !== null);\n// Tipo: (string | null)[], MAL! Acabamos de filtrar los nulls!\n\n// El workaround era este código adicional\nconst filteredCorrect = values.filter((v): v is string => v !== null);\n// Tipo: string[], Correcto, pero boilerplate molesto\n```\n\nEn TypeScript 5.5, el compilador infiere automáticamente que ` (v) => v !== null ` es un type predicate. Sin anotación:\n\n```typescript\n// TypeScript 5.5\nconst values: (string | null)[] = [\"hola\", null, \"mundo\", null, \"!\"];\n\nconst filtered = values.filter((v) => v !== null);\n// Tipo: string[], POR FIN!\n\n// Funciona con patrones más complejos también\ninterface User {\n id: string;\n name: string;\n email: string | null;\n deletedAt: Date | null;\n}\n\nconst users: User[] = getUsers();\n\nconst activeWithEmail = users.filter((u) => {\n return u.deletedAt === null && u.email !== null;\n});\n```"},{"heading":"Dónde funciona la inferencia (y dónde no)","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#dnde-funciona-la-inferencia-y-dnde-no","content":"La inferencia tiene reglas específicas que se necesita conocer:\n\n```typescript\n// FUNCIONA: sentencia return explícita\nconst isString = (x: unknown) => {\n return typeof x === \"string\";\n};\n// Inferido: (x: unknown) => x is string\n\n// NO FUNCIONA: cuerpo de expresión (sin llaves)\nconst isStringExpr = (x: unknown) => typeof x === \"string\";\n// Inferido: (x: unknown) => boolean, SIN predicado!\n// Esto es porque los cuerpos de expresión podrían tener side effects\n// que hacen la inferencia de predicados unsound\n\n// FUNCIONA: chequeos de null/undefined\nconst isDefined = <T>(x: T | null | undefined) => {\n return x !== null && x !== undefined;\n};\n// Inferido: (x: T | null | undefined) => x is T\n\n// FUNCIONA: chequeos instanceof\nconst isDate = (x: unknown) => {\n return x instanceof Date;\n};\n// Inferido: (x: unknown) => x is Date\n\n// FUNCIONA: chequeos de propiedad\nconst hasName = (x: object) => {\n return \"name\" in x;\n};\n// Inferido: (x: object) => x is object & Record<\"name\", unknown>\n\n// NO FUNCIONA: condiciones complejas con side effects\nlet sideEffect = false;\nconst isBad = (x: unknown) => {\n sideEffect = true; // Side effect previene inferencia\n return typeof x === \"string\";\n};\n// Inferido: (x: unknown) => boolean\n```"},{"heading":"El gotcha de la migración","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#el-gotcha-de-la-migracin","content":"Este es un gotcha común de migración. Consideremos código así:\n\n```typescript\ninterface ApiResponse {\n data: User | null;\n error: string | null;\n}\n\nconst responses: ApiResponse[] = await Promise.all(requests);\n\nconst successful = responses.filter((r) => r.data !== null);\n\n// Antes: successful[0].data era User | null (sin narrowing)\n// Después: TypeScript ahora hace narrowing mejor a través de filter\n\n// El PROBLEMA: código downstream que hacía null checks redundantes\n// ahora se marca porque TS sabe que el valor no puede ser null\nsuccessful.forEach((r) => {\n if (r.data === null) {\n // TS 5.5: Error! Esta condición siempre será false\n // Porque .filter() ya hizo narrowing\n return;\n }\n processUser(r.data);\n});\n```\n\nEl fix suele ser remover los null checks ahora redundantes. Pero si teners un codebase grande, eso significa docenas de cambios. Un approach práctico: corregirlos gradualmente con comentarios ` @ts-expect-error ` y una regla de lint para limpiarlos con el tiempo."},{"heading":"Isolated declarations: revolución de velocidad de build en monorepos","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#isolated-declarations-revolucin-de-velocidad-de-build-en-monorepos","content":"Esta es la feature que más importa para equipos grandes. ` isolatedDeclarations ` es una nueva opción del compilador que requiere que las funciones y valores exportados tengan anotaciones de tipo de retorno explícitas:\n\n```typescript\n// tsconfig.json\n{\n \"compilerOptions\": {\n \"isolatedDeclarations\": true,\n \"declaration\": true\n }\n}\n\n// SIN isolatedDeclarations (return types implícitos están bien)\nexport function add(a: number, b: number) {\n return a + b; // Return type inferido como: number\n}\n\n// CON isolatedDeclarations (return type explícito requerido)\nexport function add(a: number, b: number): number {\n return a + b;\n}\n\n// Error sin return type explícito:\nexport function processData(input: string) {\n // ^^^ Error!\n // La función exportada debe tener una anotación de tipo de retorno\n // explícita con --isolatedDeclarations\n const parsed = JSON.parse(input);\n return { ...parsed, processed: true };\n}\n\n// Fix: agregar return type explícito\ninterface ProcessedData {\n processed: true;\n [key: string]: unknown;\n}\n\nexport function processData(input: string): ProcessedData {\n const parsed = JSON.parse(input);\n return { ...parsed, processed: true };\n}\n```\n\n¿Por qué importa? Porque con tipos explícitos en todas las exportaciones, una herramienta puede generar archivos ` .d.ts ` para un paquete **sin hacer type-checking del paquete entero**. Esto significa:\n\n1. **Generación paralela de .d.ts**: Cada paquete en tu monorepo puede generar declarations independientemente\n2. **Builds incrementales**: Solo regenerar declarations para paquetes que cambiaron\n3. **Herramientas de terceros**: Tools como ` oxc `, ` swc ` y ` esbuild ` pueden generar archivos ` .d.ts ` sin correr el compilador TypeScript completo\n\nPara un monorepo con 23 paquetes, esto baja el tiempo de ` tsc --build ` de 94 segundos a 31 segundos."},{"heading":"Estrategia de adopción gradual","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#estrategia-de-adopcin-gradual","content":"No se necesita habilitarlo en todos lados de una:\n\n```json\n// packages/shared-utils/tsconfig.json (paquete hoja, comenzar aquí)\n{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"isolatedDeclarations\": true\n }\n}\n\n// packages/api-client/tsconfig.json (depende de shared-utils)\n// Habilitar DESPUÉS de migrar shared-utils\n{\n \"extends\": \"../../tsconfig.base.json\",\n \"compilerOptions\": {\n \"isolatedDeclarations\": true\n }\n}\n\n// apps/web/tsconfig.json (app, no necesita habilitarse)\n{\n \"extends\": \"../../tsconfig.base.json\"\n}\n```\n\nComenzar con tus paquetes hoja (sin dependencias internas), después avanzar hacia adentro. Los paquetes de app no lo necesitan porque no producen declarations."},{"heading":"Config extends: la variable template ${configDir}","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#config-extends-la-variable-template-configdir","content":"TypeScript 5.5 introduce ` ${configDir} ` como variable template en paths de tsconfig. Esto resuelve un dolor de larga data en monorepos:\n\n```json\n// ANTES: los paths son relativos a la ubicación del config que extiende\n// tsconfig.base.json\n{\n \"compilerOptions\": {\n \"outDir\": \"./dist\", // Relativo a... ¿qué archivo?\n \"rootDir\": \"./src\", // Cambia de significado cuando se extiende\n \"paths\": {\n \"@/*\": [\"./src/*\"] // Se rompe en configs anidados\n }\n }\n}\n\n// DESPUÉS: ${configDir} siempre se refiere al directorio del archivo\n// tsconfig.base.json\n{\n \"compilerOptions\": {\n \"outDir\": \"${configDir}/dist\",\n \"rootDir\": \"${configDir}/src\",\n \"paths\": {\n \"@/*\": [\"${configDir}/src/*\"]\n }\n }\n}\n```\n\nAhora cuando ` packages/api/tsconfig.json ` extiende ` ../../tsconfig.base.json `, ` ${configDir} ` resuelve a ` packages/api/ `, no al root. No más paths rotos."},{"heading":"Regex type narrowing","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#regex-type-narrowing","content":"TypeScript 5.5 agrega type narrowing a través de llamadas ` .test() ` y ` .match() ` de expresiones regulares:\n\n```typescript\nfunction processInput(value: string | number) {\n if (typeof value === \"string\") {\n if (/^\\d+$/.test(value)) {\n // value sigue siendo string, pero TS ahora sabe que matcheó la regex\n const num = parseInt(value, 10); // TS sabe que esto es seguro\n }\n }\n}\n\n// Donde NO funciona\nconst result = \"hola\".search(/patron/);\n// search devuelve un number, no un boolean, sin narrowing\n\nconst replaced = \"hola\".replace(/h/, \"H\");\n// replace devuelve un string, sin narrowing\n```"},{"heading":"Patrones del mundo real que mejoraron","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#patrones-del-mundo-real-que-mejoraron","content":"Algunos patrones de un codebase real que mejoraron con la 5.5:"},{"heading":"Filtrado de uniones discriminadas","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#filtrado-de-uniones-discriminadas","content":"```typescript\ntype Event =\n | { type: \"click\"; x: number; y: number }\n | { type: \"keypress\"; key: string }\n | { type: \"scroll\"; delta: number };\n\nconst events: Event[] = getEvents();\n\n// Antes de TS 5.5: necesitabas predicado explícito\nconst clicks = events.filter(\n (e): e is Extract<Event, { type: \"click\" }> => e.type === \"click\"\n);\n\n// Después de TS 5.5: el predicado se infiere\nconst clicks = events.filter((e) => {\n return e.type === \"click\";\n});\n// Tipo: { type: \"click\"; x: number; y: number }[]\n```"},{"heading":"Array.from con map y filter","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#arrayfrom-con-map-y-filter","content":"```typescript\nconst validEntries = Array.from(formData.entries())\n .map(([key, value]) => {\n if (typeof value !== \"string\") return null;\n if (value.trim() === \"\") return null;\n return { key, value: value.trim() };\n })\n .filter((entry) => {\n return entry !== null;\n });\n// TS 5.5: { key: string; value: string }[]\n// TS 5.4: ({ key: string; value: string } | null)[]\n```"},{"heading":"Los números de rendimiento","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#los-nmeros-de-rendimiento","content":"Números reales de una migración de monorepo (23 paquetes, ~180K líneas de TypeScript):\n\n| Métrica | TS 5.4 | TS 5.5 | TS 5.5 + isolatedDecl |\n| --- | --- | --- | --- |\n| Build completo | 94s | 89s | 31s |\n| Incremental (1 archivo) | 12s | 11s | 3.2s |\n| Generación declarations | 28s | 26s | 8.1s |\n| Responsividad IDE | ~800ms | ~650ms | ~650ms |\n\nLa mejora con ` isolatedDeclarations ` es el número titular. Pasar de 94s a 31s para un build completo es la diferencia entre \"andá a buscar un café\" y \"ya terminó.\""},{"heading":"Consideraciones conocidas de actualización","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#consideraciones-conocidas-de-actualizacin","content":"Algunas cosas que vale la pena saber antes de upgradear:\n\n1. **Los predicados inferidos pueden ensanchar tus tipos**: Si una función antes devolvía ` boolean ` y ahora devuelve ` x is Foo `, código que dependía del return value booleano puede comportarse distinto con el análisis de control de flujo.\n2. **isolatedDeclarations + declaration maps**: Si usa ` declarationMap `, los maps ahora apuntan al source original incluso sin type checking completo. Asegurarse que tu configuración de source maps sea consistente.\n3. **La interacción satisfies + predicados inferidos**: ` satisfies ` no preserva el predicado inferido. Si teners ` const isString = ((x: unknown) => typeof x === 'string') satisfies (x: unknown) => boolean `, el predicado se pierde.\n4. **Regresión de rendimiento con conditional types profundamente anidados**: Existe un caso conocido donde la nueva inferencia de tipos de TS 5.5 causó un slowdown de 10x en un archivo específico con cadenas de ` Omit<Pick<Partial<...>>> ` muy anidadas. El fix fue simplificar los tipos.\n\nTypeScript 5.5 es una release genuinamente genial. Los type predicates inferidos arreglan uno de los papercuts más comunes del lenguaje. Isolated declarations desbloquean mejoras reales de velocidad de build para monorepos. Y las mejoras de config hacen que las configuraciones compartidas funcionen de verdad. Actualizar con cuidado, esperar algunos errores de tipo que en realidad son bug fixes, y disfrutar el viaje."},{"heading":"Preguntas frecuentes","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#questions","content":""},{"heading":"¿Qué son los type predicates inferidos en TypeScript 5.5?","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-1","content":"TypeScript 5.5 puede inferir automáticamente que una función como (x) =\\> x !== null actúa como type predicate (x is NonNullable\\<T\\>), sin que tengas que escribir la anotación 'is' manualmente. Esto hace que .filter(Boolean) y patrones similares funcionen correctamente con type narrowing."},{"heading":"¿Qué es isolatedDeclarations y por qué me debería importar?","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-2","content":"isolatedDeclarations requiere que todas las funciones y valores exportados tengan anotaciones de tipo explícitas, permitiendo la generación de archivos de declaración sin type-checking completo. Esto habilita generación de .d.ts paralela e incremental, acelerando dramáticamente los builds de monorepos."},{"heading":"¿TypeScript 5.5 rompe código existente?","level":3,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#question-3","content":"Potencialmente sí. Los type predicates inferidos pueden cambiar los tipos inferidos en llamadas .filter() existentes, lo que podría revelar errores de tipo previamente ocultos. El fix suele ser correcto, el comportamiento anterior era técnicamente incorrecto, pero puede sorprender."},{"heading":"Tips","level":2,"url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#article-tips","content":"1. **Tip:** Los type predicates inferidos solo funcionan con sentencias ` return `, arrow functions con cuerpo de expresión como ` (x) => x !== null ` no infieren predicados. Envolverlos en llaves.\n2. **Tip:** Habilitar ` isolatedDeclarations ` gradualmente, comenzar con tus paquetes hoja en un monorepo y avanzar hacia adentro. Requiere return types explícitos en funciones exportadas.\n3. **Tip:** La variable template ` configDir ` en tsconfig extends mejora significativamente el flujo de trabajo en monorepos, utilizar ` ${configDir}/src ` en vez de paths relativos que se rompen con configs anidados.\n4. **Tip:** El regex type narrowing solo funciona con ` .test() ` y ` .match() `, ` .search() ` y ` .replace() ` no hacen narrowing de tipos. Esto agarra desprevenida a la gente constantemente.\n\n- typescript\n- type-predicates\n- isolated-declarations\n- type-narrowing\n- javascript\n- dx\n- compiler\n- type-system\n\n[Volver al Blog](<https://lucioduran.com/es/blog>)"}],"links":[{"text":"English","url":"https://lucioduran.com/blog/typescript-5-5-type-predicates-inference"},{"text":"Volver al Blog","url":"https://lucioduran.com/es/blog"}],"structuredData":[{"@context":"https://schema.org","@graph":[{"@type":"BlogPosting","@id":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference#article","mainEntityOfPage":{"@type":"WebPage","@id":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference"},"headline":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","description":"Análisis de las características más grandes de TypeScript 5.5: type predicates inferidos que funcionan de verdad, isolated declarations para builds más rápidos, mejoras en config extends, regex type narrowing y patrones que resultarán problemáticos.","datePublished":"2025-12-24","author":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"publisher":{"@type":"Person","@id":"https://lucioduran.com/#person","name":"Lucio Durán","url":"https://lucioduran.com"},"image":"https://lucioduran.com/favicons/og-default.png","url":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference","inLanguage":"es","isPartOf":{"@id":"https://lucioduran.com/blog#blog"},"articleSection":"TypeScript","keywords":"typescript, type-predicates, isolated-declarations, type-narrowing, javascript, dx, compiler, type-system","wordCount":1564,"timeRequired":"PT9M","workTranslation":[{"@type":"BlogPosting","inLanguage":"en","url":"https://lucioduran.com/blog/typescript-5-5-type-predicates-inference"}]},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Blog","item":"https://lucioduran.com/es/blog"},{"@type":"ListItem","position":3,"name":"TypeScript 5.5: Type Predicates Inferidos, Isolated Declarations y Gotchas del Mundo Real","item":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference"}]},{"@type":"FAQPage","mainEntity":[{"@type":"Question","name":"¿Qué son los type predicates inferidos en TypeScript 5.5?","acceptedAnswer":{"@type":"Answer","text":"TypeScript 5.5 puede inferir automáticamente que una función como (x) => x !== null actúa como type predicate (x is NonNullable<T>), sin que tengas que escribir la anotación 'is' manualmente. Esto hace que .filter(Boolean) y patrones similares funcionen correctamente con type narrowing."}},{"@type":"Question","name":"¿Qué es isolatedDeclarations y por qué me debería importar?","acceptedAnswer":{"@type":"Answer","text":"isolatedDeclarations requiere que todas las funciones y valores exportados tengan anotaciones de tipo explícitas, permitiendo la generación de archivos de declaración sin type-checking completo. Esto habilita generación de .d.ts paralela e incremental, acelerando dramáticamente los builds de monorepos."}},{"@type":"Question","name":"¿TypeScript 5.5 rompe código existente?","acceptedAnswer":{"@type":"Answer","text":"Potencialmente sí. Los type predicates inferidos pueden cambiar los tipos inferidos en llamadas .filter() existentes, lo que podría revelar errores de tipo previamente ocultos. El fix suele ser correcto, el comportamiento anterior era técnicamente incorrecto, pero puede sorprender."}}]}]}],"contentHash":"bee4053d4cae3fb220e10ba117d9998fe1727729f7ba2545cd9fc160f9250fb6","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/blog/typescript-5-5-type-predicates-inference","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":68156,"contentBytes":13287}}
