[{"data":1,"prerenderedAt":1573},["ShallowReactive",2],{"blog-como-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento-es":3},{"post":4,"surround":845,"translatedPost":851},{"id":5,"title":6,"author":7,"body":8,"date":831,"dateModified":831,"description":832,"extension":567,"img":833,"meta":834,"navigation":589,"path":835,"published":589,"readingTime":641,"seo":836,"sitemap":837,"slug":833,"stem":838,"tags":839,"union":843,"__hash__":844},"blog_es\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento.md","Cómo hacer migraciones con Claude Code y no morir en el intento","Sergio Azócar",{"type":9,"value":10,"toc":815},"minimark",[11,16,20,23,26,30,33,41,44,48,51,79,82,86,89,92,105,108,113,116,147,150,204,207,211,214,260,263,322,325,329,393,396,402,406,433,436,443,446,451,455,469,480,483,491,495,498,539,543,546,562,729,738,751,768,772,798,802,805,808,811],[12,13,15],"h2",{"id":14},"migrar-dejó-de-doler","Migrar dejó de doler",[17,18,19],"p",{},"Migré el diseño completo de este sitio en un par de días. Antes de Claude Code, esa misma migración me la habría comido en dos semanas, y probablemente la habría postergado seis meses por pura flojera.",[17,21,22],{},"Llevo años haciendo migraciones. Vue 2 a Vue 3, design systems completos, refactors arquitectónicos que tocaban cientos de archivos. Todas tenían algo en común, dolían. Eran proyectos largos, mecánicos, con riesgo alto y poca recompensa visible. La clase de tarea que sabes que hay que hacer pero que sigues postergando porque suena a infierno.",[17,24,25],{},"Ya no.",[12,27,29],{"id":28},"el-dolor-histórico-de-migrar","El dolor histórico de migrar",[17,31,32],{},"Migrar de Vue 2 a Vue 3 era un proyecto trimestral. No por las dificultades técnicas reales (la mayoría era reemplazar APIs deprecadas), sino por el volumen. Cientos de componentes, cada uno con sus particularidades, todos había que tocarlos. Sumarle Pinia, Vue Router 4, los plugins de terceros que no soportaban v3 todavía. Y mientras hacías la migración, el resto del equipo seguía mergeando features en la rama principal. Cuando intentabas mergear de vuelta, era un infierno.",[17,34,35,36,40],{},"Migrar un design system era parecido pero peor. Cambias el sistema de tokens, los componentes base, los nombres de las props. De pronto cada ",[37,38,39],"code",{},"\u003CButton variant=\"primary\" \u002F>"," que existía en la app necesita revisión, y cuando son cientos de ocurrencias, ningún equipo te firma un Q completo solo para eso.",[17,42,43],{},"El patrón siempre era el mismo, alta carga, baja motivación, postergación indefinida.",[12,45,47],{"id":46},"por-qué-las-migraciones-son-el-escenario-perfecto-para-un-agente","Por qué las migraciones son el escenario perfecto para un agente",[17,49,50],{},"Después de hacer varias con Claude Code, identifiqué tres condiciones dónde son ideales:",[52,53,54,62,68],"ol",{},[55,56,57,61],"li",{},[58,59,60],"strong",{},"Alto volumen de cambios mecánicos."," El PR de oxlint en mi trabajo actual tocó más de 700 archivos. La mayoría eran ajustes triviales (orden de imports, patrones equivalentes). Es exactamente el tipo de trabajo que es humanamente tedioso y que un agente lo hace en minutos.",[55,63,64,67],{},[58,65,66],{},"Feedback claro y automatizable."," Linter, Typecheck, Tests, pasan o no pasan. El Build compila o no compila. Cada paso es un loop con respuesta binaria, justo el tipo de señal que un agente necesita para iterar sin supervisión continua.",[55,69,70,73,74,78],{},[58,71,72],{},"Documentación accesible."," La herramienta de destino casi siempre tiene un ",[75,76,77],"em",{},"migration guide",". Le pasas el guide al agente, le das el contexto del proyecto, y deja de inventar.",[17,80,81],{},"Si tu tarea cumple las tres, deja de procrastinar. Es trabajo para un agente.",[12,83,85],{"id":84},"caso-1-el-toolchain-de-un-monorepo","Caso 1: el toolchain de un monorepo",[17,87,88],{},"El equipo en mi trabajo actual operaba un monorepo con 7 aplicaciones en Cloudflare Workers, alrededor de 1.670 archivos de TypeScript gestionados con pnpm workspaces. El pipeline de calidad usaba Biome para lint y format, y tsc para type-check.",[17,90,91],{},"Dos cuellos de botella:",[93,94,95,102],"ul",{},[55,96,97,98,101],{},"Biome y tsc requerían ",[37,99,100],{},"NODE_OPTIONS='--max-old-space-size=8192'"," para no caer por out of memory durante CI.",[55,103,104],{},"tsc, single-threaded, se demoraba 25 segundos en chequear los tipos del monorepo completo.",[17,106,107],{},"Llevábamos meses mirando lo que el equipo de oxc (oxlint, oxfmt, todo lo que están construyendo en Rust) iba sacando. La pregunta no era si valía la pena migrar, sino cuánto esfuerzo iba a costar.",[109,110,112],"h3",{"id":111},"migración-a-oxlint-oxfmt","Migración a oxlint + oxfmt",[17,114,115],{},"Los pasos:",[52,117,118,121,128,134,141,144],{},[55,119,120],{},"Reemplazar Biome por oxlint y oxfmt.",[55,122,123,124,127],{},"Configurar ",[37,125,126],{},".oxlintrc.json"," con 129 reglas activas.",[55,129,123,130,133],{},[37,131,132],{},".oxfmtrc.json"," equivalente.",[55,135,136,137,140],{},"Actualizar scripts en ",[37,138,139],{},"package.json",".",[55,142,143],{},"Ajustar el pipeline en GitHub Actions.",[55,145,146],{},"Ajustar la config del editor.",[17,148,149],{},"El bulk del trabajo no fue cambiar las herramientas. Fue ajustar el código para cumplir las nuevas reglas (la mayoría sobre orden de imports y patrones equivalentes). Claude Code hacía los cambios, corríamos las validaciones, ajustábamos, repetíamos.",[151,152,153,172],"table",{},[154,155,156],"thead",{},[157,158,159,163,166,169],"tr",{},[160,161,162],"th",{},"Métrica",[160,164,165],{},"Biome",[160,167,168],{},"oxlint (ene 2026)",[160,170,171],{},"oxlint (mar 2026)",[173,174,175,190],"tbody",{},[157,176,177,181,184,187],{},[178,179,180],"td",{},"Tiempo promedio",[178,182,183],{},"3.70s",[178,185,186],{},"1.04s",[178,188,189],{},"0.49s",[157,191,192,195,198,201],{},[178,193,194],{},"Archivos analizados",[178,196,197],{},"1.227",[178,199,200],{},"1.262",[178,202,203],{},"~1.670",[17,205,206],{},"oxlint procesaba más archivos con más reglas en una fracción del tiempo. Y la herramienta sigue mejorando con cada release.",[109,208,210],{"id":209},"migración-a-tsgo","Migración a tsgo",[17,212,213],{},"TypeScript 7 (tsgo) es la reescritura de tsc (TypeScript Compiler) en Go. Microsoft prometía mejoras de 7x a 10x. La migración fue todavía más directa:",[52,215,216,222,232,247,257],{},[55,217,218,219,140],{},"Instalar ",[37,220,221],{},"@typescript\u002Fnative-preview",[55,223,224,225,228,229,140],{},"Cambiar el script de ",[37,226,227],{},"NODE_OPTIONS='--max-old-space-size=8192' tsc"," a ",[37,230,231],{},"tsgo",[55,233,234,235,238,239,242,243,246],{},"Ajustar ",[37,236,237],{},"tsconfig.json"," (sacar ",[37,240,241],{},"baseUrl",", agregar ",[37,244,245],{},".\u002F"," como prefijo en paths).",[55,248,249,250,228,253,256],{},"Actualizar la caché en CI (de ",[37,251,252],{},".tsbuildinfo",[37,254,255],{},".tsgo-cache",").",[55,258,259],{},"Corregir tipos en los lugares donde tsgo es más estricto que tsc.",[17,261,262],{},"El bonus, ya no necesitas la config de memoria. tsgo corre en Go sin las restricciones de Node.",[151,264,265,280],{},[154,266,267],{},[157,268,269,271,274,277],{},[160,270,162],{},[160,272,273],{},"tsc",[160,275,276],{},"tsgo (ene 2026)",[160,278,279],{},"tsgo (mar 2026)",[173,281,282,296,309],{},[157,283,284,287,290,293],{},[178,285,286],{},"Tiempo (cold)",[178,288,289],{},"25.34s",[178,291,292],{},"5.32s",[178,294,295],{},"0.76s",[157,297,298,301,304,307],{},[178,299,300],{},"Uso CPU",[178,302,303],{},"~132%",[178,305,306],{},"~430%",[178,308,306],{},[157,310,311,314,317,320],{},[178,312,313],{},"Requiere config de memoria",[178,315,316],{},"Sí (8 GB)",[178,318,319],{},"No",[178,321,319],{},[17,323,324],{},"33 veces más rápido en typecheck, sin tocar una sola línea de código TypeScript del proyecto.",[109,326,328],{"id":327},"el-número-final","El número final",[151,330,331,347],{},[154,332,333],{},[157,334,335,338,341,344],{},[160,336,337],{},"Fase",[160,339,340],{},"Antes",[160,342,343],{},"Migración",[160,345,346],{},"Hoy (abril 2026)",[173,348,349,360,371],{},[157,350,351,354,356,358],{},[178,352,353],{},"Lint",[178,355,183],{},[178,357,186],{},[178,359,189],{},[157,361,362,365,367,369],{},[178,363,364],{},"Typecheck",[178,366,289],{},[178,368,292],{},[178,370,295],{},[157,372,373,378,383,388],{},[178,374,375],{},[58,376,377],{},"Total",[178,379,380],{},[58,381,382],{},"29.04s",[178,384,385],{},[58,386,387],{},"6.36s",[178,389,390],{},[58,391,392],{},"1.25s",[17,394,395],{},"De 29 segundos a 1.25. 23 veces más rápido. Y lo que terminó de convencer al equipo fueron precisamente esos resultados. Sin medir antes y después, la migración es solo \"Sergio cambió las herramientas\". Con los resultados, es \"el linter y typecheck de código es 23X más rápido\".",[17,397,398,401],{},[58,399,400],{},"Lección:"," mide siempre. Es lo que convierte una migración en una historia que el equipo entiende y firma.",[12,403,405],{"id":404},"caso-2-screaming-architecture-en-un-frontend-grande","Caso 2: screaming architecture en un frontend grande",[17,407,408,409,414,415,418,419,418,422,425,426,418,429,432],{},"Antes de la migración del toolchain, había hecho otra: aplicar ",[410,411,413],"a",{"href":412},"\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable","screaming architecture"," al frontend del producto principal. Reorganizar el repo desde carpetas técnicas (",[37,416,417],{},"components\u002F",", ",[37,420,421],{},"services\u002F",[37,423,424],{},"store\u002F",") a módulos por feature (",[37,427,428],{},"modules\u002FAuth\u002F",[37,430,431],{},"modules\u002FOrders\u002F",", etc).",[17,434,435],{},"El cambio en sí es simple: mover archivos y actualizar imports. Pero cuando son cientos de archivos en cientos de imports, es la definición de \"trabajo mecánico que nadie quiere hacer un viernes\".",[17,437,438,439,442],{},"Acá es donde Claude Code se luce. El patrón es uniforme, los cambios son verificables (compila o no), y la única regla nueva es \"todo lo de feature X vive en ",[37,440,441],{},"modules\u002FX\u002F","\". El agente entiende el patrón al primer ejemplo y lo aplica al resto.",[17,444,445],{},"Lo único que hice fue definir el contrato (specs). Qué módulos crear, qué se considera shared, cómo manejar los archivos que tocaba más de una feature. Después fue iterar, validar build, validar tests, ajustar.",[17,447,448,450],{},[58,449,400],{}," los refactors estructurales son ideales cuando el patrón es claro pero el volumen es alto. Define las reglas una vez, deja que el agente las aplique en todas partes.",[12,452,454],{"id":453},"caso-3-el-rediseño-de-este-sitio","Caso 3: el rediseño de este sitio",[17,456,457,458,461,462,465,466,140],{},"El más reciente y el más cercano a este post. Migrar sergioazocar.com desde el diseño anterior al actual: layout edge-to-edge con BentoGrid, Nuxt UI v4, Tailwind v4, sistema de colores custom (",[37,459,460],{},"beacon"," y ",[37,463,464],{},"orbit","), tema dark-only, borders translúcidas con ",[37,467,468],{},"color-mix",[17,470,471,472,475,476,479],{},"La diferencia con los casos anteriores es que el contexto del proyecto era todo mío. Tengo un ",[37,473,474],{},"CLAUDE.md"," cuidado, con las convenciones del repo, el stack, las reglas de i18n, los patrones de blog posts, los gotchas. Cuando le pido a Claude Code que migre un componente al nuevo sistema de borders, no necesito explicarle qué es ",[37,477,478],{},"border-(muted)"," ni dónde está definido. Ya lo sabe.",[17,481,482],{},"Eso es dogfooding. El proyecto está preparado para que el agente sea útil desde el primer prompt. Y eso paga dividendos rapidísimo, cada turno avanza en lugar de gastarse en re-explicar contexto.",[17,484,485,487,488,490],{},[58,486,400],{}," invertir en un buen ",[37,489,474],{}," no es overhead, es la diferencia entre un agente útil y uno que alucina.",[12,492,494],{"id":493},"el-playbook","El playbook",[17,496,497],{},"Después de varias migraciones, esto es lo que hago siempre:",[52,499,500,506,512,518,527,533],{},[55,501,502,505],{},[58,503,504],{},"Planifica antes de generar."," Una migración mal scopeada es una migración fallida. Define el alcance, los pasos, los archivos. Si no tienes claridad antes de empezar, el agente tampoco la va a tener.",[55,507,508,511],{},[58,509,510],{},"PRs atómicos por fase."," En el caso del toolchain, oxlint y tsgo fueron PRs separados. Si algo se rompe, sabes exactamente qué fase lo causó.",[55,513,514,517],{},[58,515,516],{},"Valida en cada paso."," Lint, type-check, tests, preview. Claude Code corre los comandos, tú lees el output. Saltarte la validación porque \"el cambio anterior pasó\" es la receta para acumular 50 errores antes de darte cuenta.",[55,519,520,526],{},[58,521,522,523,525],{},"Mantén un ",[37,524,474],{}," actualizado."," Stack, convenciones, comandos, gotchas del proyecto. Es la diferencia entre un agente que entiende el repo y uno que inventa.",[55,528,529,532],{},[58,530,531],{},"Mide antes y después."," Sin números no hay historia que contar, ni argumento para venderle al equipo.",[55,534,535,538],{},[58,536,537],{},"Pair programming, no autopilot."," El agente no reemplaza criterio, lo amplifica. Tú decides qué se hace, él lo ejecuta y trae el feedback.",[12,540,542],{"id":541},"las-herramientas-que-uso-en-una-migración","Las herramientas que uso en una migración",[17,544,545],{},"Después de varias migraciones, hay un setup que repito. Son las piezas que hacen la diferencia entre Claude Code útil y Claude Code que pierde el tiempo.",[17,547,548,554,555,557,558,561],{},[58,549,550,551,553],{},"Un ",[37,552,474],{}," específico al contexto actual."," Convenciones, comandos, gotchas y la migración en curso. No copies el ",[37,556,139],{}," (Claude Code ya puede leerlo), escribe lo que ",[58,559,560],{},"no"," se infiere de las deps. Ejemplo mínimo:",[563,564,569],"pre",{"className":565,"code":566,"language":567,"meta":568,"style":568},"language-md shiki shiki-themes material-theme-ocean","# Project name\n\n## Stack relevante (lo que no se infiere del package.json)\n\n- Desplegado a Cloudflare Workers\n- i18n con prefix strategy (ES\u002FEN)\n- Composition API only, sin Options API\n- oxlint en vez de eslint\n\n## Commands\n\n- `pnpm lint` — oxlint\n- `pnpm test` — vitest\n\n## Migration in progress\n\nBiome → oxlint + oxfmt. Reemplaza cada regla por su equivalente,\ncorre `pnpm lint` después de cada batch, no toques tests en este PR.\n","md","",[37,570,571,584,591,600,605,615,623,631,639,644,652,657,675,690,695,703,708,714],{"__ignoreMap":568},[572,573,576,580],"span",{"class":574,"line":575},"line",1,[572,577,579],{"class":578},"sAklC","# ",[572,581,583],{"class":582},"s5Dmg","Project name\n",[572,585,587],{"class":574,"line":586},2,[572,588,590],{"emptyLinePlaceholder":589},true,"\n",[572,592,594,597],{"class":574,"line":593},3,[572,595,596],{"class":578},"## ",[572,598,599],{"class":582},"Stack relevante (lo que no se infiere del package.json)\n",[572,601,603],{"class":574,"line":602},4,[572,604,590],{"emptyLinePlaceholder":589},[572,606,608,611],{"class":574,"line":607},5,[572,609,610],{"class":578},"-",[572,612,614],{"class":613},"s0W1g"," Desplegado a Cloudflare Workers\n",[572,616,618,620],{"class":574,"line":617},6,[572,619,610],{"class":578},[572,621,622],{"class":613}," i18n con prefix strategy (ES\u002FEN)\n",[572,624,626,628],{"class":574,"line":625},7,[572,627,610],{"class":578},[572,629,630],{"class":613}," Composition API only, sin Options API\n",[572,632,634,636],{"class":574,"line":633},8,[572,635,610],{"class":578},[572,637,638],{"class":613}," oxlint en vez de eslint\n",[572,640,642],{"class":574,"line":641},9,[572,643,590],{"emptyLinePlaceholder":589},[572,645,647,649],{"class":574,"line":646},10,[572,648,596],{"class":578},[572,650,651],{"class":582},"Commands\n",[572,653,655],{"class":574,"line":654},11,[572,656,590],{"emptyLinePlaceholder":589},[572,658,660,662,665,669,672],{"class":574,"line":659},12,[572,661,610],{"class":578},[572,663,664],{"class":578}," `",[572,666,668],{"class":667},"sfyAc","pnpm lint",[572,670,671],{"class":578},"`",[572,673,674],{"class":613}," — oxlint\n",[572,676,678,680,682,685,687],{"class":574,"line":677},13,[572,679,610],{"class":578},[572,681,664],{"class":578},[572,683,684],{"class":667},"pnpm test",[572,686,671],{"class":578},[572,688,689],{"class":613}," — vitest\n",[572,691,693],{"class":574,"line":692},14,[572,694,590],{"emptyLinePlaceholder":589},[572,696,698,700],{"class":574,"line":697},15,[572,699,596],{"class":578},[572,701,702],{"class":582},"Migration in progress\n",[572,704,706],{"class":574,"line":705},16,[572,707,590],{"emptyLinePlaceholder":589},[572,709,711],{"class":574,"line":710},17,[572,712,713],{"class":613},"Biome → oxlint + oxfmt. Reemplaza cada regla por su equivalente,\n",[572,715,717,720,722,724,726],{"class":574,"line":716},18,[572,718,719],{"class":613},"corre ",[572,721,671],{"class":578},[572,723,668],{"class":667},[572,725,671],{"class":578},[572,727,728],{"class":613}," después de cada batch, no toques tests en este PR.\n",[17,730,731,737],{},[58,732,733,736],{},[37,734,735],{},"\u002Fplan"," antes de generar."," Hace que el agente investigue el repo, te proponga un plan estructurado y solo ejecute después de aprobarlo. Para migraciones es perfecto, te ahorra empezar tres veces porque el primer scope estaba mal.",[17,739,740,743,744,747,748,750],{},[58,741,742],{},"Sub-agents para paralelizar."," Mientras el agente principal sigue editando, lanza un sub-agent que corra ",[37,745,746],{},"pnpm typecheck"," o ",[37,749,684],{}," en otro módulo y te traiga el resultado. Para migraciones grandes, 2-3 sub-agents en paralelo dividen el trabajo y mantienen el contexto del principal limpio.",[17,752,753,756,757,760,761,763,764,767],{},[58,754,755],{},"Hooks para validación automática."," Un hook ",[37,758,759],{},"PostToolUse"," que corra ",[37,762,668],{}," después de cada ",[37,765,766],{},"Edit"," es la forma más barata de no tener que pedirle \"valida\" cada vez. Corre solo, si falla el agente lo ve y arregla en el siguiente turno.",[12,769,771],{"id":770},"lo-que-no-hagas","Lo que NO hagas",[93,773,774,780,786,792],{},[55,775,776,779],{},[58,777,778],{},"No le pidas todo de una vez."," \"Migra el repo entero\" es la peor instrucción posible. Divide en fases, una a la vez.",[55,781,782,785],{},[58,783,784],{},"No te saltes la validación."," Aunque el cambio se vea trivial, corre los checks. El error que asumes que no existe es el que termina en producción.",[55,787,788,791],{},[58,789,790],{},"No dejes que invente paths o APIs."," Si cita una función que no reconoces, verifica que existe antes de aceptar el cambio.",[55,793,794,797],{},[58,795,796],{},"No mergees sin entender los cambios."," El agente acelera tu trabajo, no reemplaza tu responsabilidad. Si no entiendes lo que se cambió, no estás migrando, estás cruzando los dedos.",[12,799,801],{"id":800},"cierre","Cierre",[17,803,804],{},"La barrera de entrada para migrar bajó dramáticamente. Lo que antes era un proyecto trimestral hoy es un proyecto de fin de semana. Lo que antes postergabas seis meses, lo puedes iniciar hoy mismo y terminarlo en un par de días.",[17,806,807],{},"Si tienes una migración estancada hace tiempo, abre Claude Code esta semana. Probablemente la termines antes de que termine el sprint.",[17,809,810],{},"Y aunque las métricas finales no salgan tan lindas, igual vas a entender más de tu codebase en dos días que en los seis meses que llevabas postergándolo.",[812,813,814],"style",{},"html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}html pre.shiki code .s0W1g, html code.shiki .s0W1g{--shiki-default:#BABED8}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":568,"searchDepth":586,"depth":593,"links":816},[817,818,819,820,825,826,827,828,829,830],{"id":14,"depth":586,"text":15},{"id":28,"depth":586,"text":29},{"id":46,"depth":586,"text":47},{"id":84,"depth":586,"text":85,"children":821},[822,823,824],{"id":111,"depth":593,"text":112},{"id":209,"depth":593,"text":210},{"id":327,"depth":593,"text":328},{"id":404,"depth":586,"text":405},{"id":453,"depth":586,"text":454},{"id":493,"depth":586,"text":494},{"id":541,"depth":586,"text":542},{"id":770,"depth":586,"text":771},{"id":800,"depth":586,"text":801},"2026-04-28","Tres migraciones reales con Claude Code: el toolchain de un monorepo, screaming architecture y el rediseño de este sitio. Lo que antes tomaba semanas, hoy se hace en días.",null,{},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento",{"title":6,"description":832},{"loc":835},"es\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento",[840,841,842],"claude-code","migrations","tooling","how-to-migrate-with-claude-code-and-not-die-trying","swc_lRsLyhJhzRsrweazhkvIr9eimtEVQ07MwISjMAc",[833,846],{"title":847,"path":848,"stem":849,"description":850,"children":-1},"Lo que aprendí construyendo Design Systems y que haría diferente","\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente","es\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente","Lecciones reales construyendo Design Systems en Vue y TypeScript: errores comunes, decisiones de arquitectura y qué haría diferente hoy.",{"id":852,"title":853,"author":7,"body":854,"date":831,"dateModified":831,"description":1565,"extension":567,"img":833,"meta":1566,"navigation":589,"path":1567,"published":589,"readingTime":641,"seo":1568,"sitemap":1569,"slug":833,"stem":1570,"tags":1571,"union":843,"__hash__":1572},"blog_en\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying.md","How to migrate with Claude Code and not die trying",{"type":9,"value":855,"toc":1549},[856,860,863,866,869,873,876,882,885,889,892,915,918,922,925,928,939,942,946,949,976,979,1023,1026,1030,1033,1070,1073,1125,1128,1132,1190,1193,1199,1203,1221,1224,1230,1233,1238,1242,1253,1262,1265,1273,1277,1280,1321,1325,1328,1344,1469,1477,1489,1504,1508,1534,1538,1541,1544,1547],[12,857,859],{"id":858},"migrating-no-longer-hurts","Migrating no longer hurts",[17,861,862],{},"I migrated the entire design of this site in a couple of days. Before Claude Code, that same migration would have eaten up two weeks of my time, and I'd probably have put it off for six months out of pure laziness.",[17,864,865],{},"I've been doing migrations for years. Vue 2 to Vue 3, full design systems, architectural refactors that touched hundreds of files. They all had one thing in common, they hurt. Long, mechanical projects with high risk and little visible reward. The kind of work you know has to be done but keep putting off because it sounds like hell.",[17,867,868],{},"Not anymore.",[12,870,872],{"id":871},"the-historical-pain-of-migrating","The historical pain of migrating",[17,874,875],{},"Migrating from Vue 2 to Vue 3 was a quarter-long project. Not because of real technical difficulty (most of it was replacing deprecated APIs), but because of sheer volume. Hundreds of components, each with its own quirks, all of them needed touching. Add Pinia, Vue Router 4, the third-party plugins that didn't yet support v3. And while you were migrating, the rest of the team kept merging features into main. By the time you tried to merge back, it was hell.",[17,877,878,879,881],{},"Migrating a design system was similar but worse. You change the token system, the base components, the prop names. Suddenly every ",[37,880,39],{}," in the app needs review, and when you have hundreds of those, no team signs off on a full quarter just for that.",[17,883,884],{},"The pattern was always the same, high load, low motivation, indefinite procrastination.",[12,886,888],{"id":887},"why-migrations-are-the-perfect-scenario-for-an-agent","Why migrations are the perfect scenario for an agent",[17,890,891],{},"After doing several with Claude Code, I've identified three conditions where they're ideal:",[52,893,894,900,906],{},[55,895,896,899],{},[58,897,898],{},"High volume of mechanical changes."," The oxlint PR at my current job touched more than 700 files. Most were trivial adjustments (import order, equivalent patterns). Exactly the kind of work that is humanly tedious and that an agent does in minutes.",[55,901,902,905],{},[58,903,904],{},"Clear, automatable feedback."," Linter, Typecheck, Tests, they pass or they don't. The Build compiles or it doesn't. Every step is a loop with binary response, exactly the kind of signal an agent needs to iterate without continuous supervision.",[55,907,908,911,912,914],{},[58,909,910],{},"Accessible documentation."," The target tool almost always has a ",[75,913,77],{},". Pass the guide to the agent, give it the project context, and it stops making things up.",[17,916,917],{},"If your task meets all three, stop procrastinating. It's work for an agent.",[12,919,921],{"id":920},"case-1-a-monorepos-toolchain","Case 1: a monorepo's toolchain",[17,923,924],{},"The team at my current job operated a monorepo with 7 applications on Cloudflare Workers, around 1,670 TypeScript files managed with pnpm workspaces. The quality pipeline used Biome for lint and format, and tsc for type-check.",[17,926,927],{},"Two bottlenecks:",[93,929,930,936],{},[55,931,932,933,935],{},"Biome and tsc required ",[37,934,100],{}," to avoid out of memory crashes during CI.",[55,937,938],{},"tsc, single-threaded, took 25 seconds to typecheck the entire monorepo.",[17,940,941],{},"We'd been watching for months what the oxc team (oxlint, oxfmt, everything they're building in Rust) was shipping. The question wasn't whether the migration was worth it, but how much effort it was going to cost.",[109,943,945],{"id":944},"migration-to-oxlint-oxfmt","Migration to oxlint + oxfmt",[17,947,948],{},"The steps:",[52,950,951,954,960,965,970,973],{},[55,952,953],{},"Replace Biome with oxlint and oxfmt.",[55,955,956,957,959],{},"Configure ",[37,958,126],{}," with 129 active rules.",[55,961,962,963,140],{},"Configure an equivalent ",[37,964,132],{},[55,966,967,968,140],{},"Update scripts in ",[37,969,139],{},[55,971,972],{},"Adjust the GitHub Actions pipeline.",[55,974,975],{},"Adjust the editor config.",[17,977,978],{},"The bulk of the work wasn't switching tools. It was adjusting code to meet the new rules (mostly about import order and equivalent patterns). Claude Code made the changes, we ran the validations, adjusted, repeated.",[151,980,981,996],{},[154,982,983],{},[157,984,985,988,990,993],{},[160,986,987],{},"Metric",[160,989,165],{},[160,991,992],{},"oxlint (Jan 2026)",[160,994,995],{},"oxlint (Mar 2026)",[173,997,998,1009],{},[157,999,1000,1003,1005,1007],{},[178,1001,1002],{},"Average time",[178,1004,183],{},[178,1006,186],{},[178,1008,189],{},[157,1010,1011,1014,1017,1020],{},[178,1012,1013],{},"Files analyzed",[178,1015,1016],{},"1,227",[178,1018,1019],{},"1,262",[178,1021,1022],{},"~1,670",[17,1024,1025],{},"oxlint was processing more files with more rules in a fraction of the time. And the tool keeps improving with every release.",[109,1027,1029],{"id":1028},"migration-to-tsgo","Migration to tsgo",[17,1031,1032],{},"TypeScript 7 (tsgo) is the rewrite of tsc (TypeScript Compiler) in Go. Microsoft promised 7x to 10x improvements. The migration was even more straightforward:",[52,1034,1035,1040,1048,1060,1067],{},[55,1036,1037,1038,140],{},"Install ",[37,1039,221],{},[55,1041,1042,1043,1045,1046,140],{},"Change the script from ",[37,1044,227],{}," to ",[37,1047,231],{},[55,1049,1050,1051,1053,1054,1056,1057,1059],{},"Adjust ",[37,1052,237],{}," (drop ",[37,1055,241],{},", add ",[37,1058,245],{}," as prefix in paths).",[55,1061,1062,1063,1045,1065,256],{},"Update the CI cache (from ",[37,1064,252],{},[37,1066,255],{},[55,1068,1069],{},"Fix types in places where tsgo is stricter than tsc.",[17,1071,1072],{},"The bonus, you no longer need the memory config. tsgo runs in Go without Node's constraints.",[151,1074,1075,1089],{},[154,1076,1077],{},[157,1078,1079,1081,1083,1086],{},[160,1080,987],{},[160,1082,273],{},[160,1084,1085],{},"tsgo (Jan 2026)",[160,1087,1088],{},"tsgo (Mar 2026)",[173,1090,1091,1102,1113],{},[157,1092,1093,1096,1098,1100],{},[178,1094,1095],{},"Time (cold)",[178,1097,289],{},[178,1099,292],{},[178,1101,295],{},[157,1103,1104,1107,1109,1111],{},[178,1105,1106],{},"CPU usage",[178,1108,303],{},[178,1110,306],{},[178,1112,306],{},[157,1114,1115,1118,1121,1123],{},[178,1116,1117],{},"Requires memory config",[178,1119,1120],{},"Yes (8 GB)",[178,1122,319],{},[178,1124,319],{},[17,1126,1127],{},"33 times faster on typecheck, without touching a single line of TypeScript code in the project.",[109,1129,1131],{"id":1130},"the-final-number","The final number",[151,1133,1134,1150],{},[154,1135,1136],{},[157,1137,1138,1141,1144,1147],{},[160,1139,1140],{},"Phase",[160,1142,1143],{},"Before",[160,1145,1146],{},"Migration",[160,1148,1149],{},"Today (Apr 2026)",[173,1151,1152,1162,1172],{},[157,1153,1154,1156,1158,1160],{},[178,1155,353],{},[178,1157,183],{},[178,1159,186],{},[178,1161,189],{},[157,1163,1164,1166,1168,1170],{},[178,1165,364],{},[178,1167,289],{},[178,1169,292],{},[178,1171,295],{},[157,1173,1174,1178,1182,1186],{},[178,1175,1176],{},[58,1177,377],{},[178,1179,1180],{},[58,1181,382],{},[178,1183,1184],{},[58,1185,387],{},[178,1187,1188],{},[58,1189,392],{},[17,1191,1192],{},"From 29 seconds to 1.25. 23 times faster. And what finally convinced the team were precisely those results. Without measuring before and after, the migration is just \"Sergio changed the tools\". With the results, it's \"lint and typecheck are 23X faster\".",[17,1194,1195,1198],{},[58,1196,1197],{},"Lesson:"," always measure. That's what turns a migration into a story the team understands and signs off on.",[12,1200,1202],{"id":1201},"case-2-screaming-architecture-in-a-large-frontend","Case 2: screaming architecture in a large frontend",[17,1204,1205,1206,1209,1210,418,1212,418,1214,1216,1217,418,1219,432],{},"Before the toolchain migration, I'd done another one: applying ",[410,1207,413],{"href":1208},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend"," to the main product's frontend. Reorganizing the repo from technical folders (",[37,1211,417],{},[37,1213,421],{},[37,1215,424],{},") into feature modules (",[37,1218,428],{},[37,1220,431],{},[17,1222,1223],{},"The change itself is simple: move files and update imports. But when it's hundreds of files in hundreds of imports, it's the textbook definition of \"mechanical work nobody wants to do on a Friday\".",[17,1225,1226,1227,1229],{},"This is where Claude Code shines. The pattern is uniform, changes are verifiable (compiles or doesn't), and the only new rule is \"everything in feature X lives in ",[37,1228,441],{},"\". The agent gets the pattern from the first example and applies it everywhere else.",[17,1231,1232],{},"All I had to do was define the contract (specs). Which modules to create, what counts as shared, how to handle files that touched more than one feature. After that it was iterate, validate build, validate tests, adjust.",[17,1234,1235,1237],{},[58,1236,1197],{}," structural refactors are ideal when the pattern is clear but the volume is high. Define the rules once, let the agent apply them everywhere.",[12,1239,1241],{"id":1240},"case-3-the-redesign-of-this-site","Case 3: the redesign of this site",[17,1243,1244,1245,1247,1248,1250,1251,140],{},"The most recent one and the closest to this post. Migrating sergioazocar.com from the previous design to the current one: edge-to-edge layout with BentoGrid, Nuxt UI v4, Tailwind v4, custom color system (",[37,1246,460],{}," and ",[37,1249,464],{},"), dark-only theme, translucent borders with ",[37,1252,468],{},[17,1254,1255,1256,1258,1259,1261],{},"The difference with the previous cases is that the project context was entirely mine. I have a well-kept ",[37,1257,474],{},", with the repo conventions, the stack, the i18n rules, the blog post patterns, the gotchas. When I ask Claude Code to migrate a component to the new border system, I don't need to explain what ",[37,1260,478],{}," is or where it's defined. It already knows.",[17,1263,1264],{},"That's dogfooding. The project is ready for the agent to be useful from the first prompt. And that pays dividends fast, every turn moves forward instead of being spent re-explaining context.",[17,1266,1267,1269,1270,1272],{},[58,1268,1197],{}," investing in a good ",[37,1271,474],{}," isn't overhead, it's the difference between a useful agent and one that hallucinates.",[12,1274,1276],{"id":1275},"the-playbook","The playbook",[17,1278,1279],{},"After several migrations, this is what I always do:",[52,1281,1282,1288,1294,1300,1309,1315],{},[55,1283,1284,1287],{},[58,1285,1286],{},"Plan before generating."," A poorly scoped migration is a failed migration. Define the scope, the steps, the files. If you don't have clarity before starting, the agent won't either.",[55,1289,1290,1293],{},[58,1291,1292],{},"Atomic PRs per phase."," In the toolchain case, oxlint and tsgo were separate PRs. If something breaks, you know exactly which phase caused it.",[55,1295,1296,1299],{},[58,1297,1298],{},"Validate every step."," Lint, type-check, tests, preview. Claude Code runs the commands, you read the output. Skipping validation because \"the previous change passed\" is the recipe for piling up 50 errors before you notice.",[55,1301,1302,1308],{},[58,1303,1304,1305,1307],{},"Keep a ",[37,1306,474],{}," up to date."," Stack, conventions, commands, project gotchas. The difference between an agent that understands the repo and one that makes things up.",[55,1310,1311,1314],{},[58,1312,1313],{},"Measure before and after."," Without numbers there's no story to tell, nor argument to pitch to the team.",[55,1316,1317,1320],{},[58,1318,1319],{},"Pair programming, not autopilot."," The agent doesn't replace judgment, it amplifies it. You decide what gets done, it runs and brings the feedback.",[12,1322,1324],{"id":1323},"the-tools-i-use-in-a-migration","The tools I use in a migration",[17,1326,1327],{},"After several migrations, there's a setup I repeat. These are the pieces that make the difference between useful Claude Code and Claude Code that wastes your time.",[17,1329,1330,1336,1337,1339,1340,1343],{},[58,1331,1332,1333,1335],{},"A ",[37,1334,474],{}," specific to the current context."," Conventions, commands, gotchas, and the migration in progress. Don't copy ",[37,1338,139],{}," (Claude Code can read it already), write what ",[58,1341,1342],{},"isn't"," inferable from the deps. Minimal example:",[563,1345,1347],{"className":565,"code":1346,"language":567,"meta":568,"style":568},"# Project name\n\n## Relevant stack (what isn't inferable from package.json)\n\n- Deployed to Cloudflare Workers\n- i18n with prefix strategy (ES\u002FEN)\n- Composition API only, no Options API\n- oxlint instead of eslint\n\n## Commands\n\n- `pnpm lint` — oxlint\n- `pnpm test` — vitest\n\n## Migration in progress\n\nBiome → oxlint + oxfmt. Replace each rule with its equivalent,\nrun `pnpm lint` after every batch, don't touch tests in this PR.\n",[37,1348,1349,1355,1359,1366,1370,1377,1384,1391,1398,1402,1408,1412,1424,1436,1440,1446,1450,1455],{"__ignoreMap":568},[572,1350,1351,1353],{"class":574,"line":575},[572,1352,579],{"class":578},[572,1354,583],{"class":582},[572,1356,1357],{"class":574,"line":586},[572,1358,590],{"emptyLinePlaceholder":589},[572,1360,1361,1363],{"class":574,"line":593},[572,1362,596],{"class":578},[572,1364,1365],{"class":582},"Relevant stack (what isn't inferable from package.json)\n",[572,1367,1368],{"class":574,"line":602},[572,1369,590],{"emptyLinePlaceholder":589},[572,1371,1372,1374],{"class":574,"line":607},[572,1373,610],{"class":578},[572,1375,1376],{"class":613}," Deployed to Cloudflare Workers\n",[572,1378,1379,1381],{"class":574,"line":617},[572,1380,610],{"class":578},[572,1382,1383],{"class":613}," i18n with prefix strategy (ES\u002FEN)\n",[572,1385,1386,1388],{"class":574,"line":625},[572,1387,610],{"class":578},[572,1389,1390],{"class":613}," Composition API only, no Options API\n",[572,1392,1393,1395],{"class":574,"line":633},[572,1394,610],{"class":578},[572,1396,1397],{"class":613}," oxlint instead of eslint\n",[572,1399,1400],{"class":574,"line":641},[572,1401,590],{"emptyLinePlaceholder":589},[572,1403,1404,1406],{"class":574,"line":646},[572,1405,596],{"class":578},[572,1407,651],{"class":582},[572,1409,1410],{"class":574,"line":654},[572,1411,590],{"emptyLinePlaceholder":589},[572,1413,1414,1416,1418,1420,1422],{"class":574,"line":659},[572,1415,610],{"class":578},[572,1417,664],{"class":578},[572,1419,668],{"class":667},[572,1421,671],{"class":578},[572,1423,674],{"class":613},[572,1425,1426,1428,1430,1432,1434],{"class":574,"line":677},[572,1427,610],{"class":578},[572,1429,664],{"class":578},[572,1431,684],{"class":667},[572,1433,671],{"class":578},[572,1435,689],{"class":613},[572,1437,1438],{"class":574,"line":692},[572,1439,590],{"emptyLinePlaceholder":589},[572,1441,1442,1444],{"class":574,"line":697},[572,1443,596],{"class":578},[572,1445,702],{"class":582},[572,1447,1448],{"class":574,"line":705},[572,1449,590],{"emptyLinePlaceholder":589},[572,1451,1452],{"class":574,"line":710},[572,1453,1454],{"class":613},"Biome → oxlint + oxfmt. Replace each rule with its equivalent,\n",[572,1456,1457,1460,1462,1464,1466],{"class":574,"line":716},[572,1458,1459],{"class":613},"run ",[572,1461,671],{"class":578},[572,1463,668],{"class":667},[572,1465,671],{"class":578},[572,1467,1468],{"class":613}," after every batch, don't touch tests in this PR.\n",[17,1470,1471,1476],{},[58,1472,1473,1475],{},[37,1474,735],{}," before generating."," Makes the agent investigate the repo, propose a structured plan, and only execute after you approve it. Perfect for migrations, it saves you from starting three times because the first scope was wrong.",[17,1478,1479,1482,1483,1485,1486,1488],{},[58,1480,1481],{},"Sub-agents to parallelize."," While the main agent keeps editing, launch a sub-agent that runs ",[37,1484,746],{}," or ",[37,1487,684],{}," on another module and brings back the result. For large migrations, 2-3 sub-agents in parallel split the work and keep the main agent's context clean.",[17,1490,1491,1494,1495,1497,1498,1500,1501,1503],{},[58,1492,1493],{},"Hooks for automatic validation."," A ",[37,1496,759],{}," hook that runs ",[37,1499,668],{}," after every ",[37,1502,766],{}," is the cheapest way to stop having to ask \"validate\" every time. It runs on its own, if it fails the agent sees it and fixes it on the next turn.",[12,1505,1507],{"id":1506},"what-not-to-do","What NOT to do",[93,1509,1510,1516,1522,1528],{},[55,1511,1512,1515],{},[58,1513,1514],{},"Don't ask for everything at once."," \"Migrate the whole repo\" is the worst possible instruction. Split into phases, one at a time.",[55,1517,1518,1521],{},[58,1519,1520],{},"Don't skip validation."," Even if the change looks trivial, run the checks. The error you assume doesn't exist is the one that ends up in production.",[55,1523,1524,1527],{},[58,1525,1526],{},"Don't let it invent paths or APIs."," If it cites a function you don't recognize, verify it exists before accepting the change.",[55,1529,1530,1533],{},[58,1531,1532],{},"Don't merge without understanding the changes."," The agent accelerates your work, it doesn't replace your responsibility. If you don't understand what changed, you're not migrating, you're crossing your fingers.",[12,1535,1537],{"id":1536},"closing","Closing",[17,1539,1540],{},"The barrier to entry for migrating dropped dramatically. What used to be a quarterly project is now a weekend project. What you used to push back six months, you can start today and finish in a couple of days.",[17,1542,1543],{},"If you have a migration that's been stuck for a while, open Claude Code this week. You'll probably finish it before the sprint ends.",[17,1545,1546],{},"And even if the final metrics don't come out that pretty, you'll still understand more about your codebase in two days than in the six months you'd been putting it off.",[812,1548,814],{},{"title":568,"searchDepth":586,"depth":593,"links":1550},[1551,1552,1553,1554,1559,1560,1561,1562,1563,1564],{"id":858,"depth":586,"text":859},{"id":871,"depth":586,"text":872},{"id":887,"depth":586,"text":888},{"id":920,"depth":586,"text":921,"children":1555},[1556,1557,1558],{"id":944,"depth":593,"text":945},{"id":1028,"depth":593,"text":1029},{"id":1130,"depth":593,"text":1131},{"id":1201,"depth":586,"text":1202},{"id":1240,"depth":586,"text":1241},{"id":1275,"depth":586,"text":1276},{"id":1323,"depth":586,"text":1324},{"id":1506,"depth":586,"text":1507},{"id":1536,"depth":586,"text":1537},"Three real migrations with Claude Code: monorepo toolchain, screaming architecture, and a full site redesign. What used to take weeks, now takes days.",{},"\u002Fen\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying",{"title":853,"description":1565},{"loc":1567},"en\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying",[840,841,842],"aU8ttlKPAuMP9COvKF__cnYna-07tGW5owpfNi9uL88",1777609840899]