# Migrar JavaScript a TypeScript con IA: un plan seguro

URL: https://agentvera.dev/es/blog/migrate-javascript-to-typescript-with-ai/ · Publicado: 2026-10-27

Cómo migrar JavaScript a TypeScript con agentes de IA: un tsconfig que acepta JS, orden archivo a archivo, rigor gradual, un presupuesto de any y tsc como verificación.

**En resumen:**

- Empieza con un tsconfig que acepte JavaScript (allowJs y strict en false a propósito), para que la aplicación siga compilando mientras los agentes convierten archivos de uno en uno, primero las hojas.
- Da a cada agente la misma definición de terminado: tsc pasa, los tests pasan, nada de @ts-ignore y el número de any explícitos se queda dentro de un presupuesto que solo baja.
- Activa las opciones estrictas una a una y reparte el trabajo entre agentes en paralelo por carpeta y en worktrees separados, con los tipos y la configuración compartidos en manos de un solo agente.

Para migrar JavaScript a TypeScript con IA no le pides a un agente que «convierta el proyecto». Preparas una configuración de TypeScript que acepte el JavaScript que ya tienes y luego dejas que los agentes conviertan archivos en un orden fijo, con el comprobador de tipos y los tests decidiendo cuándo está terminado cada paso. El compilador es el mejor revisor que le darás nunca a un agente: es rápido, no se cansa y no hay forma de convencerlo de que «parece que está bien».

Esta guía es la versión práctica de esa idea: el `tsconfig.json` exacto con el que empezar, cómo elegir el orden de los archivos, cómo endurecer el rigor sin una semana de builds en rojo, cómo tener `any` bajo control, cómo convertir la comprobación de tipos en el paso de verificación del propio agente y cómo repartir el trabajo entre agentes en paralelo sin conflictos de fusión. Para la red de seguridad general de un refactor de código legacy (tests de caracterización, rollback, revisión), lee antes [refactorizar código legacy con IA](/es/blog/refactor-legacy-code-with-ai/ "Refactorizar código legacy con IA"); este artículo no lo repite.

Los detalles que dependen de la versión siguen la documentación oficial de TypeScript a octubre de 2026.

## Por qué a los agentes se les da bien este trabajo

Una migración de JS a TS es sobre todo trabajo mecánico con una señal clara al final de cada paso. Justo lo que mejor hacen los agentes de código:

- **La respuesta es inmediata.** `tsc` imprime archivo, línea y código de error. Un agente puede leerlo y arreglarlo sin adivinar.
- **El trabajo se divide solo.** Los archivos y las carpetas se pueden convertir de uno en uno, así que el trabajo se parte en muchos pasos pequeños y revisables.
- **La parte aburrida es enorme.** Añadir tipos a los parámetros, escribir interfaces para objetos que ya existen e instalar paquetes `@types` es el tipo de trabajo que la gente deja para dentro de unos años.

El riesgo es igual de claro. Un agente presionado para poner el compilador en verde echará mano de `any`, de conversiones con `as` y de `@ts-ignore`, porque hacen desaparecer los errores. Casi toda esta guía trata de cerrar esas salidas.

## Paso 1: un tsconfig que acepte tu JavaScript

El primer commit no debería cambiar código de la aplicación. Añade TypeScript y una configuración que comprueba los tipos del proyecto mientras todos los archivos siguen siendo `.js`.

```bash
npm install --save-dev typescript @types/node
```

```json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noEmit": true,
    "incremental": true,
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "types": ["node"],
    "skipLibCheck": true
  },
  "include": ["src"]
}
```

Qué hace cada opción:

- **`allowJs`** permite que archivos `.js` y `.ts` convivan en el mismo programa, así que un archivo `.ts` puede importar uno `.js` y al revés.
- **`checkJs: false`** evita que TypeScript informe de errores en archivos que todavía no has tocado. Los irás incorporando uno a uno.
- **`strict: false`** es deliberado. Desde TypeScript 6.0, `strict` vale `true` por defecto, así que omitirlo activaría todas las comprobaciones estrictas el primer día.
- **`types: ["node"]`** importa por el mismo motivo: en TypeScript 6.0 el valor por defecto de `types` es un array vacío, así que los globales de Node no se cargan si no los indicas. Añade `"jest"` o `"vitest/globals"` si tus tests dependen de globales.
- **`noEmit`** encaja en proyectos donde un bundler, `tsx` o un framework ya convierte el código en JavaScript y TypeScript solo lo comprueba. Si es `tsc` quien compila tu aplicación, quítalo y define `outDir` en su lugar.
- **`module` y `moduleResolution`**: usa `nodenext` para aplicaciones de Node; para código que construye un bundler, `"module": "esnext"` con `"moduleResolution": "bundler"` suele ser la pareja correcta.

Añade un script y haz que CI lo ejecute:

```json
{
  "scripts": {
    "typecheck": "tsc -p tsconfig.json"
  }
}
```

Ejecútalo una vez. En un código JavaScript con `checkJs` desactivado, debería pasar o mostrar solo unos pocos errores de configuración. Arréglalos, haz commit y ya tienes una línea base. A partir de aquí, «el build está en verde» significa que `npm run typecheck` y `npm test` pasan los dos.

## Paso 2: previsualiza los errores con @ts-check antes de renombrar

Antes de renombrar un archivo puedes ver qué opina TypeScript de él. Añade `// @ts-check` como primera línea de un archivo `.js` y TypeScript informará de sus errores, usando los comentarios JSDoc como tipos:

```js
// @ts-check

/**
 * @param {number} cents
 * @param {string} currency
 * @returns {string}
 */
export function formatPrice(cents, currency) {
  return new Intl.NumberFormat('en', { style: 'currency', currency }).format(cents / 100);
}
```

Es un buen primer trabajo para un agente en un archivo grande: «Añade `// @ts-check` a `src/orders/pricing.js`, arregla los errores solo con tipos JSDoc y no cambies el comportamiento.» Consigues seguridad de tipos sin renombrar nada, y la conversión posterior se vuelve casi mecánica. Si prefieres el enfoque contrario, pon `checkJs: true` y añade `// @ts-nocheck` a los archivos que aún no están listos; entonces el paso consiste en quitar esa línea.

## Paso 3: elige el orden, primero las hojas

Convierte primero los archivos que no importan nada de tu propio código, luego los que solo importan esos, y así hasta llegar a los puntos de entrada. De esta forma cada archivo que conviertes puede apoyarse en tipos que ya existen.

No tienes que calcular el orden a mano. [madge](https://github.com/pahen/madge "madge") lee el grafo de imports:

```bash
npx madge --leaves --extensions js,jsx src/
npx madge --circular --extensions js,jsx src/
```

`--leaves` lista los módulos sin dependencias y `--circular` lista los ciclos de imports. Conviene conocer los ciclos de antemano: los archivos de un ciclo tienen que convertirse juntos, así que planifícalos como un solo paso.

Una regla práctica para agrupar: un paso es o un archivo grande o una carpeta de archivos pequeños, lo bastante pequeño como para que el diff se revise de una sentada. Pon la lista en un archivo versionado, como `MIGRATION.md`, una línea por paso, y deja que los agentes la vayan marcando. Ese archivo se convierte en el plan compartido de todos los agentes y todas las sesiones.

## Paso 4: un prompt de conversión, una definición de terminado

Usa la misma forma de prompt en cada paso, para que los agentes no se inventen sus propias reglas:

```text
Convert src/billing/ from JavaScript to TypeScript.

- Rename with git mv so history follows the files (.js -> .ts, .jsx -> .tsx).
- Add types for parameters, return values and exported objects.
  Prefer types inferred from real usage over wide ones.
- Don't change runtime behaviour. No refactoring, no renaming,
  no "while I'm here" fixes. If you find a bug, note it in
  MIGRATION.md under "Found during migration" and leave it.
- No @ts-ignore, no @ts-nocheck. @ts-expect-error only with a reason
  on the same line. No new `any` beyond the budget in .any-budget.
- Don't edit tsconfig.json, package.json or src/types/.
  If you need a change there, stop and tell me what and why.

Done means: npm run typecheck passes, npm test passes,
npm run lint passes, and MIGRATION.md is updated.
```

Dos líneas de ese prompt hacen casi todo el trabajo. «No cambies el comportamiento en tiempo de ejecución» mantiene el diff revisable, porque una migración de tipos pura solo debería añadir anotaciones, renombrados e imports. «No edites tsconfig.json» cierra el camino más barato al verde: aflojar el compilador.

Pon las partes estables de este prompt en `CLAUDE.md` o `AGENTS.md` para que cada sesión empiece con ellas; [CLAUDE.md y AGENTS.md](/es/blog/claude-md-agents-md/ "CLAUDE.md y AGENTS.md") explica cómo compartir un mismo conjunto de reglas entre agentes.

## Paso 5: un presupuesto de any que solo baja

No llegarás a cero `any` en una pasada, y prohibirlo sin más suele producir escapes peores, como `as unknown as Foo`. Un presupuesto es más honesto: permites un número, y ese número solo baja.

Primero haz visibles `any` y los comentarios de supresión con typescript-eslint:

```js
// eslint.config.js (excerpt)
rules: {
  '@typescript-eslint/no-explicit-any': 'warn',
  '@typescript-eslint/ban-ts-comment': ['error', {
    'ts-expect-error': 'allow-with-description',
    'ts-ignore': true,
    'ts-nocheck': true,
    minimumDescriptionLength: 10,
  }],
}
```

Con estas opciones, `ban-ts-comment` rechaza `@ts-ignore` y `@ts-nocheck` y solo acepta `@ts-expect-error` con un motivo escrito de al menos diez caracteres. De todos modos, `@ts-expect-error` es la mejor salida: cuando el error se arregla, el propio comentario pasa a ser un error, así que no puede quedarse obsoleto.

Después cuenta los `any` explícitos y compara el resultado con un número guardado en el repositorio:

```bash
#!/usr/bin/env bash
# scripts/any-budget.sh
count=$(npx eslint src --format json \
  | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.log(r.flatMap(f=>f.messages).filter(m=>m.ruleId==="@typescript-eslint/no-explicit-any").length)})')
budget=$(cat .any-budget)
echo "explicit any: $count (budget $budget)"
[ "$count" -le "$budget" ] || { echo "any budget exceeded"; exit 1; }
```

Ejecútalo en CI y en la definición de terminado del agente. Cuando un paso baje la cuenta, reduce `.any-budget` en el mismo commit. Dile al agente dónde está el presupuesto; los agentes respetan un número mucho mejor que un «evita any siempre que puedas».

Vigila las otras salidas, que la cuenta no detecta: conversiones con `as` sobre valores que vienen de fuera (`JSON.parse`, cuerpos de peticiones), `Function` y `object` como tipos de parámetro, y una línea `declare module '*'` que convierte cada import en `any`. Pide al revisor, persona o agente, que liste cada conversión nueva del diff.

## Paso 6: haz de la comprobación de tipos el paso de verificación del agente

Un agente que dice «hecho» sin ejecutar `tsc` está adivinando. Haz que la comprobación forme parte de cómo el agente termina un turno, no algo que tengas que acordarte de pedir.

En Claude Code, un hook Stop puede impedir que el turno termine mientras la comprobación de tipos falle. Guarda esto como `.claude/hooks/typecheck.sh`:

```bash
#!/usr/bin/env bash
# Exit 2 sends stderr back to Claude and keeps it working.
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0   # already retried once; let the turn end
fi
npx tsc -p tsconfig.json --pretty false 1>&2 || exit 2
```

y regístralo en `.claude/settings.json`:

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": ".claude/hooks/typecheck.sh" }
        ]
      }
    ]
  }
}
```

Cuando un hook Stop termina con el código 2, Claude Code no se detiene y le muestra a Claude el stderr del hook, así que el agente ve los errores del compilador y sigue arreglando. La comprobación de `stop_hook_active` deja que el turno termine en el segundo intento en lugar de entrar en bucle. La [guía de hooks de Claude Code](/es/blog/claude-code-hooks-guide/ "Guía de hooks de Claude Code") trata los hooks con más detalle. Un hook `PostToolUse` sobre `Edit|Write` puede lanzar una comprobación más rápida después de cada edición, aunque en proyectos grandes un `tsc` completo por edición se vuelve lento.

Codex y Gemini CLI no leen los hooks de Claude, así que pon la misma regla en `AGENTS.md` o `GEMINI.md`: «Antes de decir que una tarea está terminada, ejecuta `npm run typecheck` y `npm test` y pega las últimas líneas de la salida.» Pedir la salida es lo que hace que la instrucción se cumpla.

## Paso 7: sube el rigor de opción en opción

Cuando todos los archivos sean `.ts`, o lo sea una zona, activa las comprobaciones estrictas en este orden, con un commit y una tarea de agente para cada una:

1. **`noImplicitAny`**: cada parámetro y cada variable debe tener un tipo conocido. Es la que más errores produce y la que da los tipos más útiles.
2. **`strictNullChecks`**: `null` y `undefined` pasan a ser tipos propios. Esta encuentra bugs reales, los de «cannot read property of undefined».
3. **El resto de la familia strict**: `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `useUnknownInCatchVariables`. Normalmente con menos errores cada una.
4. **`"strict": true`** sustituye a las opciones individuales cuando todas pasan.
5. **Extras opcionales** que no forman parte de `strict`: `noUncheckedIndexedAccess` (las lecturas por índice pueden ser `undefined`) y `exactOptionalPropertyTypes`.

No tienes que activar una opción en todo el repositorio de golpe. Una segunda configuración que extiende la primera y cubre solo las carpetas terminadas permite que el código estricto siga siéndolo mientras el resto se pone al día:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": { "strict": true },
  "include": ["src/billing", "src/utils"]
}
```

Guárdala como `tsconfig.strict.json` y ejecuta `tsc -p tsconfig.strict.json` en CI junto a la comprobación principal. Cada vez que un agente termina una carpeta, la añade a `include`. Cuando `include` cubra `src`, pasa `strict: true` a la configuración principal y borra el segundo archivo.

Un aviso sobre `strictNullChecks`: el arreglo más rápido para «possibly undefined» es la aserción de no nulo `!`. Unas pocas están bien; docenas significan que el agente está escondiendo el problema. Pide una guarda, un valor por defecto o un cambio de tipo, y trata cada `!` nuevo como un `any` nuevo durante la revisión.

## Paso 8: reparte el trabajo entre agentes en paralelo

Una migración es uno de los pocos trabajos en los que ejecutar varios agentes a la vez compensa de verdad, porque las carpetas son en gran parte independientes. La forma de que funcione es dividir por carpeta y dar a cada agente su propio [git worktree](/es/glossary/git-worktree/ "Qué es un git worktree"):

```bash
git worktree add ../app-ts-billing -b ts/billing
git worktree add ../app-ts-orders  -b ts/orders
git worktree add ../app-ts-users   -b ts/users
```

Inicia un agente en cada carpeta con el prompt de paso de arriba, apuntado a su directorio. Unas pocas reglas evitan que las ramas choquen:

- **Los archivos compartidos tienen un único dueño.** `tsconfig.json`, `package.json`, el lockfile, `eslint.config.js` y las declaraciones compartidas de `src/types/` solo cambian en la rama principal, por tu mano o por la de un agente coordinador. Los agentes de carpeta piden esos cambios en lugar de hacerlos.
- **Los tipos compartidos van primero.** Si varias carpetas necesitan el mismo tipo `User` u `Order`, conviértelo en un paso secuencial antes de que empiecen los paralelos. Si no, tres agentes escribirán tres interfaces `User` ligeramente distintas.
- **Instala los paquetes `@types` de forma centralizada.** Dos ramas que añaden cada una un paquete distinto reescriben el lockfile y entran en conflicto cada vez.
- **No paralelices un ciclo.** Las carpetas que se importan entre sí en círculo pertenecen a un solo agente.
- **Fusiona una rama cada vez y comprueba los tipos después de cada fusión.** Dos ramas pueden pasar `tsc` por separado y fallar juntas, por ejemplo cuando una cambia un tipo exportado del que depende la otra. Haz rebase de la siguiente rama sobre el nuevo main antes de fusionarla.

[Git worktrees para agentes en paralelo](/es/blog/git-worktree-parallel-agents/ "Git worktrees para agentes en paralelo") cubre en detalle la preparación y la limpieza. Tres o cuatro agentes es un comienzo sensato; a partir de ahí, el cuello de botella suele ser revisar las ramas, no escribirlas.

## Paso 9: deja que los tests demuestren que nada ha cambiado

Los tipos te dicen que el código es coherente consigo mismo. Solo los tests te dicen que sigue haciendo lo que hacía. Durante una migración:

- **Ejecuta la suite completa después de cada paso**, no solo los tests de la carpeta convertida. Un export que cambia puede romper a quien lo llama en otra parte.
- **Convierte los archivos de test después del código que prueban**, en un paso aparte, y no cambies aserciones mientras conviertes. Un test cuyo valor esperado cambió en un commit «solo de tipos» es una señal de alarma.
- **Comprueba que el runner de tests admite `.ts`.** Vitest ejecuta archivos TypeScript sin configuración extra; Jest necesita un transform como `ts-jest` o `babel-jest` con el preset de TypeScript.
- **Añade tests donde los tipos no llegan.** El código que analiza entradas, habla con una API o lee variables de entorno recibe en tiempo de ejecución datos con forma de `any` digan lo que digan los tipos. Unos pocos tests ahí valen más que unas anotaciones perfectas. [Escribir tests unitarios con IA](/es/blog/write-unit-tests-with-ai/ "Escribir tests unitarios con IA") muestra cómo conseguir que los agentes escriban tests útiles.

## Errores habituales

- **Pasar de `require` a `import` cambia la semántica.** Los exports por defecto, `module.exports = fn` y la interoperabilidad funcionan distinto en CommonJS y en módulos ES. Haz el cambio de formato de módulos como un paso propio, o déjalo fuera de la migración.
- **Tipos que mienten.** Una interfaz escrita a partir de un único objeto de ejemplo puede dejarse campos que solo aparecen a veces. Prefiere tipos escritos a partir de datos reales o del esquema de la API.
- **«Arreglos» de comportamiento escondidos en un commit de tipos.** Un `==` convertido en `===`, un parámetro por defecto añadido, un `if` reordenado. Cada uno puede cambiar el comportamiento. Recházalos y pide un commit aparte.
- **Silenciar al compilador desde la configuración.** `skipLibCheck` está bien; desactivar una opción estricta para que una rama pase, no. Por eso los agentes no pueden editar `tsconfig.json`.
- **Archivos generados o de terceros.** Exclúyelos en `tsconfig.json` en lugar de dejar que un agente les «ponga tipos».

## Cómo hacerlo en AgentVera

AgentVera ejecuta Claude Code, Codex, Gemini CLI y otros agentes de CLI lado a lado por proyecto, lo que encaja con una migración dividida en muchos pasos pequeños.

- **Un worktree por carpeta.** Desde el [panel de Git](/es/features/git-and-github/ "Git y GitHub") crea un worktree para cada directorio e inicia un agente en él con un clic: Claude Code en una carpeta y Codex en otra, si quieres.
- **[Propiedad de archivos](/es/features/file-ownership/ "Propiedad de archivos").** Asigna al agente coordinador `tsconfig.json`, `package.json` y `src/types/**`. Mientras está en marcha, las ediciones de otros agentes en esos archivos se rechazan antes de producirse y se les dice que se lo pidan al dueño; a los agentes de Claude Code los detiene un hook, y las demás CLIs reciben la regla en sus instrucciones.
- **[Verify](/es/features/verify/ "Verify").** Escribe en tu mensaje «la comprobación de tipos pasa, los tests pasan, no ha cambiado ninguna aserción de test». Al terminar el turno, un verificador independiente que no puede editar archivos ejecuta los comandos de test permitidos, lee el diff y marca cada criterio como cumplido o no, con evidencias.
- **[Revisión de código](/es/features/code-review/ "Revisión de código").** Un segundo modelo lee cada turno terminado y lista los hallazgos con su file:line, un buen sitio para detectar conversiones nuevas y aserciones `!`.
- **[Cola de fusión](/es/features/merge-queue/ "Cola de fusión").** Las ramas de cada carpeta llegan de una en una sobre el último main, y tu comando de comprobación, como `npm run typecheck && npm test`, se ejecuta sobre el resultado fusionado antes de fusionar nada.
- **[Equipo en paralelo](/es/features/parallel-team/ "Equipo en paralelo") (Pro y Team).** Un agente puede dividir la migración en hasta ocho partes, con los archivos que puede tocar cada una, ejecutar cada parte en su propio worktree, fusionar las partes terminadas en una rama de integración y lanzar la comprobación final, así que revisas un único pull request.

## Para terminar

Migrar JavaScript a TypeScript con IA funciona cuando mandan el compilador y los tests, no la confianza del agente. Empieza con una configuración que acepte JavaScript y ponga `strict: false` a propósito, convierte primero las hojas en pasos pequeños, mantén `any` dentro de un presupuesto que solo baja, haz de `tsc` la comprobación de salida del agente, sube el rigor opción a opción y reparte el trabajo en paralelo por carpeta, con los archivos compartidos en manos de un solo agente. Cada paso es aburrido y revisable, que es justo lo que quieres de una migración.

Si vas a seguir a los agentes paso a paso durante la migración, [programación en pareja con IA](/es/blog/ai-pair-programming/ "Programación en pareja con IA") explica cuándo dejar que el agente lleve el teclado y cuándo revisar cada paso.

## Preguntas frecuentes

**¿Puede la IA convertir un proyecto JavaScript a TypeScript?** Sí, y es uno de los trabajos que mejor hacen los agentes, siempre que les des un comprobador de tipos que funcione, un orden fijo y pasos pequeños. Deja que el agente convierta un archivo o una carpeta cada vez, ejecute tsc y los tests después de cada paso y haga un commit por paso.

**¿Debo activar el modo strict desde el principio?** No en un código ya existente. Desde TypeScript 6.0 strict viene activado por defecto, así que al principio pon "strict": false de forma explícita, convierte los archivos y luego activa noImplicitAny, strictNullChecks y el resto de opciones estrictas de una en una.

**¿Cómo evito que el agente use any por todas partes?** Hazlo medible. Señala los any explícitos con la regla no-explicit-any de typescript-eslint, prohíbe @ts-ignore con ban-ts-comment, guarda un número de presupuesto en el repositorio y haz que CI falle cuando la cuenta lo supere. Indica el presupuesto al agente en sus instrucciones.

**¿Pueden varios agentes migrar carpetas distintas a la vez?** Sí, si cada uno trabaja en su propio git worktree y es dueño de una carpeta. Los archivos compartidos como tsconfig.json, package.json y las declaraciones de tipos globales deben pertenecer a un solo agente, y cada rama debe volver a pasar la comprobación de tipos después de fusionarse.

**¿Tengo que reescribir los tests durante la migración?** No. Primero mantén los tests existentes ejecutándose contra el código convertido; son los que te dicen que el comportamiento no ha cambiado. Convierte los archivos de test después del módulo que cubren y evita cambiar aserciones en el mismo paso.

---

**Sobre AgentVera.** AgentVera es una app de escritorio para macOS, Windows y Linux, desarrollada por Solviera Teknoloji. Ejecuta 18 agentes de programación con IA (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) en paralelo, por proyecto, en terminales reales con las instalaciones y sesiones de CLI del propio usuario. El plan gratuito tiene agentes ilimitados. Descargar: https://agentvera.dev/es/download/ · Precios: https://agentvera.dev/es/pricing/
