Cómo migrar tu código a TypeScript 6.0 con `strict` activado

migrar-typescript-6-0-strict

¿Tu repo se va a romper cuando actualices a TypeScript 6.0? Probablemente sí. Si no, felicidades: estás en el 10% que ya hacía las cosas bien.

Tiempo estimado de lectura: 7 min

  • Strict: true en TS 6.0 es un cambio por defecto que destapará muchos errores latentes.
  • Activa banderas gradualmente (noImplicitThis, useUnknownInCatchVariables, noImplicitAny, etc.) y haz PRs pequeños.
  • Plan de migración de 6 semanas práctico y enfocado para repos medianos.
  • Herramientas y atajos (skipLibCheck, unknown vs any, validadores) para acelerar sin sacrificar seguridad.

Introducción

Poca gente habla de esto en voz alta: “strict: true por defecto en TS 6.0” no es una actualización menor. Es un detector de mierda técnica. Es un escáner que va a mostrar lo que has estado ignorando con // TODO: arreglar después.

Esto no es moralina. Es ingeniería. Y sí: te voy a dar el plan para sobrevivir sin incendiar el CI.

Resumen rápido (lectores con prisa)

Strict: true en TS 6.0 activa varias banderas que convierten advertencias silenciosas en errores compilables. Migrar requiere activar banderas por fases, priorizar endpoints/serializadores y usar atajos como skipLibCheck y unknown para avanzar sin bloquear el desarrollo.

Primero, lo obvio (pero que nadie quiere admitir)

  • Si ya tienes “strict”: true: no cambia nada.
  • Si no lo tenías, al actualizar a TS 6.0 el compilador se pondrá estricto y te lloverán errores.
  • Puedes parchear rápido con “strict”: false en tsconfig, pero eso es anestesia. No arregla la enfermedad.

strict no es una única regla. Es un paraguas que enciende varias banderas. Cada una toca piezas distintas del código y cada una duele distinto.

Piensa en strict como un filtro de rayos X: te muestra huesos rotos que antes eran invisibles.

Las banderas, qué hacen y por qué importan (con ejemplos reales)

1) noImplicitAny — El fin de los `any` silenciosos

Qué hace: hace error todo lugar donde TS no pueda inferir el tipo y quedaría any.

Problema típico:

const fn = (payload) => { return payload.name; }

Antes: compila. Después: error.

Arreglo:

const fn = (payload: { name: string }) => payload.name;

Por qué importa: porque any es una mentira que llega a producción sin avisar.

2) strictNullChecks — La regla que evita el “Cannot read properties of undefined”

Qué hace: separa null y undefined de otros tipos.

Problema típico:

function greet(name: string) { return name.toLowerCase(); }
const maybeName: string | null = getName();
greet(maybeName); // Boom si es null

Arreglo:

function greet(name: string) { return name.toLowerCase(); }
const maybeName = getName();
if (maybeName) greet(maybeName);

O escribe la firma:

function greet(name: string | null) { if (!name) return; return name.toLowerCase(); }

Por qué importa: obliga a pensar en ausencia de datos. Nada peor que toLowerCase() explotando en prod.

3) strictFunctionTypes — Contravarianza correcta

Qué hace: comprueba que los tipos de parámetros en funciones sean seguros al asignarlas.

Ejemplo peligroso:

type Handler = (e: Event) => void;
function specificHandler(e: MouseEvent) { /* usa e.clientX */ }
const h: Handler = specificHandler; // antes ok, ahora error

Arreglo: Alinea firmas o usa overloads. No metas handlers con supuestos concretos donde se espera generalidad.

4) strictBindCallApply — Validación de bind/call/apply

Qué hace: valida que los argumentos pasados con call/apply/bind casen con la firma original.

Caso:

function sum(a: number, b: number) { return a + b; }
sum.call(null, "str", 3); // antes pasaba, ahora no

Por qué importa: evita bugs raros cuando se manipula this y argumentos dinámicos.

5) strictPropertyInitialization — Clases más predecibles

Qué hace: exige inicializar propiedades o declararlas como posiblemente undefined.

Antes:

class Foo { value: number; constructor() {} }
const f = new Foo(); console.log(f.value); // undefined

Con la bandera activada: error en compilación. Arreglo:

class Foo { value: number = 0; }

O si la inicialización viene después:

class Foo { value!: number } // use with care
o
class Foo { value?: number }

6) noImplicitThis — Control del “this”

Qué hace: si TS no sabe qué es this en una función, da error.

Ejemplo:

const obj = {
  x: 1,
  getX() { return this.x; }
}
const f = obj.getX;
f(); // this es global — error ahora

Arreglo: tipa this en la función:

getX(this: { x: number }) { return this.x; }

7) useUnknownInCatchVariables — catch seguro

Qué hace: cambia catch (e) de any a unknown.

Ejemplo:

try { ... } catch (e) { console.log(e.message); } // ahora error

Arreglo:

try { ... } catch (e: unknown) {
  if (e instanceof Error) console.log(e.message);
}

Esto fuerza buenos patrones de manejo de errores y evita suposiciones peligrosas.

Estrategia práctica: cómo migrar sin suicidarte

No intentes arreglar 5.000 errores en un PR. Eso no es noble; es destructivo.

Plan de 6 semanas (realista para repos medianos)

Semana 0 — Preparación

  • Crea branch “ts-migration/strict”.
  • Añade CI que compile en strict y que no bloquee master todavía.
  • Si la migración debe ser suave, temporalmente coloca “strict”: false para que CI siga pasando mientras trabajas.

Semana 1 — Protección inmediata

  • Activa noImplicitThis y useUnknownInCatchVariables.
  • Razon: cambios pequeños, refactors simples, impacto bajo.
  • Haz PRs pequeños. Revisa con atención en code owners.

Semana 2 — Sanear `any`

  • Activa noImplicitAny.
  • Prioriza endpoints y parsers: handlers de API, converters, deserializadores.
  • Empieza a reemplazar any por tipos concretos o unknown + validación.

Semana 3-4 — Funciones y propiedades

  • Activa strictFunctionTypes.
  • Refactoriza callbacks y firmas exportadas.
  • Activa strictPropertyInitialization y arregla inicializaciones en clases públicas.

Semana 5 — El gran salto

  • Activa strictNullChecks.
  • Este es el más complicado. Reescribe funciones que asumían no-null implícito.
  • Amplía tests unitarios. Añade pruebas para casos nulos/undefined.

Semana 6 — Finalización

  • Ejecuta npx tsc --noEmit en todo el repo.
  • Revisa errores residuales; limpia // @ts-ignore que se usó como parche.
  • Mueve strict a true en tsconfig principal y mergea.

Comandos útiles que vas a repetir hasta odiarlos

  • Inicializar config: npx tsc --init
  • Compilar sin emitir: npx tsc --noEmit
  • Compilar un proyecto con build mode: npx tsc -b
  • Detectar errores en workspace monorepo: npx lerna exec -- npx tsc --noEmit (o tu equivalente)

Trucos y atajos para acelerar la migración

  • Usa skipLibCheck: true mientras migras para no pelear con tipos de dependencias.
  • Introduce types o index.d.ts temporales sólo para las partes que bloquean.
  • Prefiere unknown a any y escribe validadores pequeños si es necesario.
  • Añade eslint rule para prohibir // @ts-ignore excepto con razón y ticket.

Checklist mínimo antes de mergear a master

  • CI compila con strict: true.
  • Tests cubren entradas nulas y errores.
  • No any sin ticket que lo justifique.
  • Documentación de breaking changes en PR.
  • Plan de rollback (tag + branch) por si algo explota en prod.

Errores comunes que vas a ver (y cómo solucionarlos rápido)

  • "Object is possibly 'null'." → Optional chaining o comprobación previa.
  • "Parameter implicitly has an 'any' type." → Añade tipo explícito o infiere con generics.
  • "Property has no initializer and is not definitely assigned in the constructor." → Inicializa o marca como optional.

Metáfora corta: ¿por qué todo esto vale la pena?

Poner strict por defecto es como obligar a instalar cinturón de seguridad en todos los coches nuevos. Al principio fastidia, pero salva vidas y reduce reclamaciones. Te obliga a pensar, a documentar y a dejar menos trampas para producción.

Argumentos para convencer a la gerencia

  • Menos bugs en producción = menos on-call nocturno.
  • Código más legible = onboarding más rápido.
  • Mejores contratos = menos regresiones en refactors.

Cuando NO migrar ahora

  • Repos monolíticos gigantes sin tests.
  • Sistemas que requieren compatibilidad inmediata con CommonJS profundo.
  • Migraciones sincronizadas de múltiples equipos donde el riesgo de romper pipelines es alto.

Si caes en estos casos, planifica la migración, no la improvises.

Cierre agresivo pero útil

No es una moda. Es una obligación técnica. Si tu equipo no puede permitirse el tiempo de migrar, al menos que lo documente y tenga roadmap. No dejes el problema para “cuando tengamos tiempo” — ese cuando nunca llega.

¿Quieres una ayuda real y directa?

Respóndeme con “MIGRAR MI REPO” y te envío:

  • Un script que crea el branch de migración,
  • Activa strict en un tsconfig de prueba,
  • Ejecuta npx tsc --noEmit,
  • Genera un reporte con los 100 errores más comunes ordenados por archivo y tipo.

Esto no acaba aquí. Si lo ejecutas, empezaremos la siguiente nota: cómo redactar PRs pequeños para migraciones de types (plantillas incluidas). ¿Lo quieres?

FAQ

¿Qué es “strict: true” en TypeScript 6.0?

Es una configuración por defecto que habilita un conjunto de banderas (noImplicitAny, strictNullChecks, strictFunctionTypes, etc.) que convierten inferencias inseguras en errores de compilación.

¿Por qué voy a ver tantos errores al actualizar?

Porque el compilador ahora detecta patrones que antes pasaban silenciosamente (usos implícitos de any, accesos a null, firmas incompatibles, etc.). Son errores latentes que la nueva configuración expone.

¿Puedo parchear temporalmente la migración?

Sí: puedes mantener "strict": false en el tsconfig principal mientras trabajas en un branch de migración y añadir CI que compile en modo estricto sin bloquear master.

¿Cuál es la bandera que menos impacto tiene al principio?

Activar noImplicitThis y useUnknownInCatchVariables suele tener impacto bajo y es buena para comenzar la limpieza sin grandes refactors.

¿UseUnknownInCatchVariables romperá mi manejo de errores actual?

Puede exigir cambios si tu código asume que catch ofrece un objeto con propiedades específicas. La solución típica es tipar como unknown y validar con instanceof Error u otros guard checks.

¿Qué hacer con librerías de terceros con tipos rotos?

Usa skipLibCheck: true temporalmente y/o añade types o index.d.ts auxiliares para las partes que bloquean la migración.

¿Cómo evitar PRs enormes durante la migración?

Divide la migración en PRs pequeños por área funcional; prioriza endpoints y parsers; documenta los cambios y pide revisión por code owners.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *