[{"data":1,"prerenderedAt":1406},["ShallowReactive",2],{"search-es":3,"blog-como-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento-es":272},[4,10,16,21,26,31,37,42,47,52,57,62,67,72,77,82,87,92,97,102,107,112,117,122,127,132,137,142,147,152,157,162,167,172,177,182,187,192,197,202,207,212,217,222,227,232,237,242,247,252,257,262,267],{"id":5,"title":6,"titles":7,"content":8,"level":9},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento","Cómo hacer migraciones con Claude Code y no morir en el intento",[],"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.",1,{"id":11,"title":12,"titles":13,"content":14,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#migrar-dejó-de-doler","Migrar dejó de doler",[6],"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. 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. Ya no.",2,{"id":17,"title":18,"titles":19,"content":20,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#el-dolor-histórico-de-migrar","El dolor histórico de migrar",[6],"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. Migrar un design system era parecido pero peor. Cambias el sistema de tokens, los componentes base, los nombres de las props. De pronto cada \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. El patrón siempre era el mismo, alta carga, baja motivación, postergación indefinida.",{"id":22,"title":23,"titles":24,"content":25,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#por-qué-las-migraciones-son-el-escenario-perfecto-para-un-agente","Por qué las migraciones son el escenario perfecto para un agente",[6],"Después de hacer varias con Claude Code, identifiqué tres condiciones dónde son ideales: 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.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.Documentación accesible. La herramienta de destino casi siempre tiene un migration guide. Le pasas el guide al agente, le das el contexto del proyecto, y deja de inventar. Si tu tarea cumple las tres, deja de procrastinar. Es trabajo para un agente.",{"id":27,"title":28,"titles":29,"content":30,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#caso-1-el-toolchain-de-un-monorepo","Caso 1: el toolchain de un monorepo",[6],"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. Dos cuellos de botella: Biome y tsc requerían NODE_OPTIONS='--max-old-space-size=8192' para no caer por out of memory durante CI.tsc, single-threaded, se demoraba 25 segundos en chequear los tipos del monorepo completo. 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.",{"id":32,"title":33,"titles":34,"content":35,"level":36},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#migración-a-oxlint-oxfmt","Migración a oxlint + oxfmt",[6,28],"Los pasos: Reemplazar Biome por oxlint y oxfmt.Configurar .oxlintrc.json con 129 reglas activas.Configurar .oxfmtrc.json equivalente.Actualizar scripts en package.json.Ajustar el pipeline en GitHub Actions.Ajustar la config del editor. 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. MétricaBiomeoxlint (ene 2026)oxlint (mar 2026)Tiempo promedio3.70s1.04s0.49sArchivos analizados1.2271.262~1.670 oxlint procesaba más archivos con más reglas en una fracción del tiempo. Y la herramienta sigue mejorando con cada release.",3,{"id":38,"title":39,"titles":40,"content":41,"level":36},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#migración-a-tsgo","Migración a tsgo",[6,28],"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: Instalar @typescript\u002Fnative-preview.Cambiar el script de NODE_OPTIONS='--max-old-space-size=8192' tsc a tsgo.Ajustar tsconfig.json (sacar baseUrl, agregar .\u002F como prefijo en paths).Actualizar la caché en CI (de .tsbuildinfo a .tsgo-cache).Corregir tipos en los lugares donde tsgo es más estricto que tsc. El bonus, ya no necesitas la config de memoria. tsgo corre en Go sin las restricciones de Node. Métricatsctsgo (ene 2026)tsgo (mar 2026)Tiempo (cold)25.34s5.32s0.76sUso CPU~132%~430%~430%Requiere config de memoriaSí (8 GB)NoNo 33 veces más rápido en typecheck, sin tocar una sola línea de código TypeScript del proyecto.",{"id":43,"title":44,"titles":45,"content":46,"level":36},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#el-número-final","El número final",[6,28],"FaseAntesMigraciónHoy (abril 2026)Lint3.70s1.04s0.49sTypecheck25.34s5.32s0.76sTotal29.04s6.36s1.25s De 29 segundos a 1.25. 23 veces más rápido. Y lo que terminó de convencer al equipo fueron precisamente esos números. Sin medir antes y después, la migración es solo \"Sergio cambió las herramientas\". Con los números, es \"el feedback de calidad de código es 23X más rápido\". Lección: mide siempre. Es lo que convierte una migración en una historia que el equipo entiende y firma.",{"id":48,"title":49,"titles":50,"content":51,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#caso-2-screaming-architecture-en-un-frontend-grande","Caso 2: screaming architecture en un frontend grande",[6],"Antes de la migración del toolchain, había hecho otra: aplicar screaming architecture al frontend del producto principal. Reorganizar el repo desde carpetas técnicas (components\u002F, services\u002F, store\u002F) a módulos por feature (modules\u002FAuth\u002F, modules\u002FOrders\u002F, etc). 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\". 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 modules\u002FX\u002F\". El agente entiende el patrón al primer ejemplo y lo aplica al resto. 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. Lección: 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.",{"id":53,"title":54,"titles":55,"content":56,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#caso-3-el-rediseño-de-este-sitio","Caso 3: el rediseño de este sitio",[6],"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 (beacon y orbit), tema dark-only, borders translúcidas con color-mix. La diferencia con los casos anteriores es que el contexto del proyecto era todo mío. Tengo un 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 border-(muted) ni dónde está definido. Ya lo sabe. 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. Lección: invertir en un buen CLAUDE.md no es overhead, es la diferencia entre un agente útil y uno que alucina.",{"id":58,"title":59,"titles":60,"content":61,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#el-playbook","El playbook",[6],"Después de varias migraciones, esto es lo que hago siempre: 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.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ó.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.Mantén un CLAUDE.md actualizado. Stack, convenciones, comandos, gotchas del proyecto. Es la diferencia entre un agente que entiende el repo y uno que inventa.Mide antes y después. Sin números no hay historia que contar, ni argumento para venderle al equipo.Pair programming, no autopilot. El agente no reemplaza criterio, lo amplifica. Tú decides qué se hace, él lo ejecuta y trae el feedback.",{"id":63,"title":64,"titles":65,"content":66,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#lo-que-no-hagas","Lo que NO hagas",[6],"No le pidas todo de una vez. \"Migra el repo entero\" es la peor instrucción posible. Divide en fases, una a la vez.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.No dejes que invente paths o APIs. Si cita una función que no reconoces, verifica que existe antes de aceptar el cambio.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.",{"id":68,"title":69,"titles":70,"content":71,"level":15},"\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento#cierre","Cierre",[6],"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. Si tienes una migración estancada hace tiempo, abre Claude Code esta semana. Probablemente la termines antes de que termine el sprint. 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.",{"id":73,"title":74,"titles":75,"content":76,"level":9},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente","Lo que aprendí construyendo Design Systems y que haría diferente",[],"Lecciones reales construyendo Design Systems en Vue y TypeScript: errores comunes, decisiones de arquitectura y qué haría diferente hoy.",{"id":78,"title":79,"titles":80,"content":81,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#qué-es-un-design-system","¿Qué es un Design System?",[74],"En simple, es una colección de reglas, componentes y directrices que actúan como una única fuente de la verdad para diseñar y construir interfaces. Un buen Design System define desde los colores, tipografías y espaciados, hasta cómo deben comportarse los botones, formularios, alertas, y más. Pero va mucho más allá de lo visual: también establece cómo se comunican diseño y desarrollo, cómo se documentan los patrones y cómo evoluciona el producto de forma consistente. La idea es que todo el equipo hable el mismo idioma, diseñe más rápido y construya productos que se sientan coherentes, sin reinventar la rueda en cada pantalla. No es solo una librería de componentes. Es una forma de trabajar que busca alinear diseño, código y experiencia de usuario. Más adelante voy a entrar en detalle sobre cómo lo construí y qué aprendí, pero primero quiero contarte cómo fue enfrentarme a esta idea desde cero.",{"id":83,"title":84,"titles":85,"content":86,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#cómo-terminé-construyendo-design-systems","Cómo terminé construyendo Design Systems",[74],"Después de trabajar en distintos proyectos y equipos, empecé a notar patrones que se repiten constantemente: El mismo botón diseñado de tres formas distintasComportamientos inconsistentes en componentes similaresCódigo duplicado o difícil de mantenerMalentendidos entre diseño y desarrolloInterfaces que se veían bien en Figma, pero no en producción Cada vez que un producto crecía, también lo hacía el caos visual y técnico. Y con él, el tiempo que perdíamos resolviendo los mismos problemas una y otra vez. Ahí fue cuando entendí que necesitábamos algo más que una librería de componentes. Necesitábamos una forma compartida de construir interfaces, con reglas claras, decisiones centralizadas y una fuente de la verdad para todos los equipos.",{"id":88,"title":89,"titles":90,"content":91,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-versus-realidad","Expectativa versus realidad",[74],"¿Te acuerdas cuando en el colegio te tocaba hacer un trabajo en grupo?\nCada uno hacía su parte por separado, lo juntaban todo al final… y el resultado era un verdadero \"Frankenstein\". Bueno, construir un Design System se siente muchas veces así. Al principio crees que va a ordenar todo, que unirá diseño y desarrollo como por arte de magia. Pero en la práctica, aparecen los malentendidos, los atajos, las decisiones cruzadas, y lo que debería ser un sistema coherente empieza a desarmarse. Estas fueron algunas de las diferencias más marcadas que viví entre lo que imaginaba… y lo que realmente pasó:",{"id":93,"title":94,"titles":95,"content":96,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-1","Expectativa 1:",[74,89],"“Voy a crear una librería de componentes reutilizables y el equipo la va a usar feliz.” Realidad:\nAlgunos devs la ignoran, otros la rompen, otros no saben cómo usarla.\nSin documentación, onboarding y soporte interno, nadie la adopta como esperas.",{"id":98,"title":99,"titles":100,"content":101,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-2","Expectativa 2:",[74,89],"“El diseño está en Figma, así que solo tengo que replicarlo.” Realidad:\nEl diseño no contempla estados, errores, focus, interacción, loading...\nTerminé tomando muchas decisiones técnicas que no estaban definidas.",{"id":103,"title":104,"titles":105,"content":106,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-3","Expectativa 3:",[74,89],"“Una vez que los componentes están hechos, no hay que tocarlos más.” Realidad:\nEl diseño evoluciona, aparecen nuevos requerimientos, y cada cambio afecta múltiples partes del sistema.\nUn Design System necesita mantenimiento constante.",{"id":108,"title":109,"titles":110,"content":111,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-4","Expectativa 4:",[74,89],"“Voy a crear componentes genéricos y reutilizables para todo.” Realidad:\nComponentes ultra flexibles terminan siendo difíciles de mantener, testear o entender.\nA veces es mejor tener variantes claras que un solo componente con 15 props.",{"id":113,"title":114,"titles":115,"content":116,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-5","Expectativa 5:",[74,89],"“Diseño y desarrollo van a trabajar como un solo equipo.” Realidad:\nSin procesos claros y un lenguaje común, aparecen malentendidos todo el tiempo.\nColaborar no es automático: hay que construir puentes intencionalmente.",{"id":118,"title":119,"titles":120,"content":121,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-6","Expectativa 6:",[74,89],"“Con Tailwind puedo hacer todo sin un Design System.” Realidad:\nTailwind ayuda, pero sin decisiones de diseño compartidas, tokens y una estructura clara, tu proyecto igual se vuelve inconsistente.",{"id":123,"title":124,"titles":125,"content":126,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-7","Expectativa 7:",[74,89],"“En unas semanas tengo el sistema listo.” Realidad:\nUn Design System no se termina. Es un sistema vivo que crece, se adapta y necesita evolucionar junto a tu producto.",{"id":128,"title":129,"titles":130,"content":131,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#necesidades-de-diseño-y-desarrollo","Necesidades de Diseño y Desarrollo",[74],"Cuando te enfrentas a crear un Design System (ya sea desde cero o a partir de una base existente) siempre aparecen dos caras de la misma moneda: lo que quiere y necesita tanto Diseño cómo Desarrollo. Es ahí donde empiezan todos los problemas desafíos. Para contextualizar un poco analicemos las necesidades de cada equipo.",{"id":133,"title":134,"titles":135,"content":136,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#diseño","Diseño",[74,129],"Consistencia visual: Un lenguaje visual unificado (colores, tipografías, espaciados, iconografía, etc.).Velocidad y eficiencia: Poder diseñar interfaces sin reinventar los mismos componentes cada vez.Escalabilidad de diseños: Que lo que funciona en una pantalla también funcione en otras (web, mobile, responsive...).Tokens de diseño claros y reutilizables: Definición técnica de colores, tamaños, fuentes, z-index, etc.Relación directa con la implementación: Que lo que diseñan se vea igual en producción.",{"id":138,"title":139,"titles":140,"content":141,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#desarrollo","Desarrollo",[74,129],"Reutilización real de componentes: Librería estable, bien documentada y fácil de usar.Consistencia funcional y técnica: Mismos patrones de props, slots, estados, accesibilidad, validación, etc.Facilidad de mantenimiento: Código limpio, con nombres claros, tests, y sin lógica duplicada.Documentación viva: Storybook o similar, con ejemplos reales, no solo teoría.Adaptabilidad a contextos distintos: Que un botón (por ejemplo) funcione igual en cualquiera de las aplicaciones que tenemos. Cada equipo tiene sus propias necesidades y prioridades, pero para que un Design System funcione, deben trabajar en conjunto, tomando decisiones basadas en definiciones compartidas. Ahí es donde deben aprovechar al máximo los puntos de unión entre ambos mundos.",{"id":143,"title":144,"titles":145,"content":146,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#necesidades-mutuas-el-punto-de-equilibrio","Necesidades mutuas, el punto de equilibrio",[74,129],"Lenguaje común: Tokens, nombres de componentes, variantes, estados. Todos deben hablar el mismo idioma.Proceso de diseño–desarrollo fluido: Entregas claras desde Figma (o lo que usen), handoffs eficientes, feedback mutuo.Versionado y control de cambios: Para que los cambios en diseño se puedan implementar y comunicar sin romper el resto del sistema.Adopción transversal del sistema: Que no se quede en un equipo. Tiene que ser fácil de usar y entender por todos. En resumen, un Design System necesita ser lo suficientemente cerrado como para respetar las definiciones de diseño sin permitir cambios visuales arbitrarios, pero también lo suficientemente flexible como para extenderse y adaptarse sin romper nada.",{"id":148,"title":149,"titles":150,"content":151,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#cómo-lograr-la-adopción-del-sistema","Cómo lograr la adopción del sistema",[74],"Tener un Design System técnicamente impecable no sirve de mucho si nadie lo usa. Uno de los mayores desafíos no es construir el sistema, sino lograr que el equipo lo adopte de verdad. Y no me refiero solo a “que lo conozcan”, sino a que lo integren en su día a día, lo respeten, lo cuestionen y lo mantengan vivo. Algunas cosas que me funcionaron (y otras que aprendí a la fuerza): Involucrar desde el inicio: Si el sistema se construye entre unos pocos, alejado del equipo, va a generar rechazo. Mientras más voces participen desde el comienzo, más pertenencia genera.Mostrar valor rápidamente: Un componente útil, bien documentado y fácil de usar vale más que una promesa de “algún día vamos a tener todo ordenado”.Documentar con empatía: No es solo escribir cómo usar un componente, es pensar en cómo lo entenderá alguien que llega por primera vez. Ejemplos reales, capturas, casos de uso, FAQs... todo suma.Dar soporte interno: Si alguien tiene un problema con un componente, tiene que haber una forma rápida de resolverlo. Slack, issues, pair programming... lo que sea, pero que no sientan que están solos y sobre todo, un flujo claro de como reportar los errores.Celebrar el uso: Cuando alguien adopta un componente, lo mejora o reporta un bug, hay que celebrarlo. Pequeños reconocimientos hacen que el sistema se vea como algo vivo, colaborativo y útil. La adopción no es un evento, es un proceso. Y requiere tiempo, paciencia y mucha comunicación.",{"id":153,"title":154,"titles":155,"content":156,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#lecciones-aprendidas-y-recomendaciones","Lecciones aprendidas y recomendaciones",[74],"Después de todo este proceso, me quedo con varias ideas que ojalá hubiese sabido antes: Un Design System no es un proyecto, es un producto. Y como todo producto, necesita investigación, diseño, mantenimiento, comunicación y evolución.Empieza por lo que más duele: No intentes resolver todo desde el día uno. Identifica qué está causando más fricción hoy y empieza por ahí.La perfección no es el objetivo: Siempre va a haber deuda, componentes por mejorar y decisiones que se pueden cuestionar. La clave es que sea útil y evolucione.Sin adopción, no hay sistema: Si no lo usa el equipo, es solo una carpeta más en tu repo.Cada equipo es distinto: No copies un sistema de otro lado esperando que funcione igual. Inspírate, sí, pero adapta todo a tu contexto y necesidades reales.Anticipa los problemas: Tu Design System puede y va a fallar, habrán cambios de definiciones y de diseño. Ten eso en cuenta para que construyas todo de forma modular y flexible.Utiliza Atomic Design: Pero utilizalo bien, cada componente es un mundo por si mismo, mientras más atómico sea cada uno, más fácil es modificarlo o extenderlo sin romperlo todo. Un Design System es una inversión a largo plazo. Puede ser desafiante, incluso frustrante a veces, pero cuando empieza a funcionar, todo el equipo gana en velocidad, consistencia y confianza.",{"id":158,"title":159,"titles":160,"content":161,"level":9},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba","oxlint-tailwindcss: el plugin de linting que Tailwind v4 necesitaba",[],"El plugin de linting más completo para Tailwind CSS, nativo para oxlint. 22 reglas con autofix, zero-config y diseñado desde cero para Tailwind CSS v4.",{"id":163,"title":164,"titles":165,"content":166,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#del-problema-al-open-source-la-oportunidad-perfecta-para-contribuir","Del problema al open-source: la oportunidad perfecta para contribuir",[159],"El día que el equipo de oxc abrió el alpha de plugins nativos para oxlint, fue la excusa perfecta para solucionar un problema que llevaba tiempo teniendo en mi trabajo actual, lintear las clases de Tailwind CSS con oxlint. Si usas Tailwind CSS v4 con oxlint, las opciones de linting existentes no están pensadas para ese combo. eslint-plugin-tailwindcss es sólido pero vive en el mundo de ESLint y su soporte de v4 todavía es parcial. eslint-plugin-better-tailwindcss funciona en oxlint a través de la capa de compatibilidad jsPlugins, y hace el trabajo — pero no es un plugin nativo y sus reglas son más acotadas. Ninguno fue diseñado específicamente para oxlint + Tailwind CSS v4. Así que lo construí.",{"id":168,"title":169,"titles":170,"content":171,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#qué-es-oxlint-tailwindcss","Qué es oxlint-tailwindcss",[159],"Un plugin nativo de oxlint con 22 reglas de linting diseñadas exclusivamente para Tailwind CSS v4. No es un port de ESLint ni un wrapper, usa directamente la API de @oxlint\u002Fplugins. Solo 2 dependencias en runtime. Esto importa porque al ser nativo, comparte el mismo ciclo de parseo que oxlint. No hay overhead de interoperabilidad, no hay capa de traducción. Es tan rápido como oxlint mismo y se nota.",{"id":173,"title":174,"titles":175,"content":176,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#funciona-sin-configuración","Funciona sin configuración",[159,169],"El plugin auto-detecta tu entry point de Tailwind CSS. Si tu archivo se llama app.css, globals.css, main.css, tailwind.css (o cualquiera de los nombres convencionales) y contiene @import \"tailwindcss\", lo encuentra solo. {\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\"\n  }\n} La auto-detección sigue @import statements un nivel de profundidad — incluyendo imports de paquetes como @import '@company\u002Ftheme\u002Ftailwind.config.css'. En monorepos, la búsqueda se detiene en boundaries de package.json para que cada paquete resuelva su propio design system automáticamente. El design system se carga una vez por entry point, con caché en memoria y en disco. En un monorepo con múltiples paquetes que comparten el mismo entry point, el design system se carga solo una vez. Si tienes un entry point distinto a lo convencional, puedes configurarlo en settings.tailwindcss.entryPoint o por regla.",{"id":178,"title":179,"titles":180,"content":181,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#_22-reglas-en-cuatro-categorías","22 reglas en cuatro categorías",[159],"",{"id":183,"title":184,"titles":185,"content":186,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#correctness-evitar-errores-reales","Correctness — Evitar errores reales",[159,179],"Las reglas de correctness atrapan bugs antes de que lleguen al navegador. no-unknown-classes detecta clases que no existen en tu design system y sugiere correcciones para typos: \u003Cdiv className=\"flex itms-center bg-blu-500\" \u002F>\n\u002F\u002F                   ^^^^^^^^^^^\n\u002F\u002F \"itms-center\" is not a valid Tailwind CSS class.\n\u002F\u002F Did you mean \"items-center\"? Soporta allowlist para permitir clases custom que no están en tu design system e ignorePrefixes para saltarse prefijos que no son clases de Tailwind. no-conflicting-classes te dice exactamente qué propiedad CSS está en conflicto y cuál clase gana: \u003Cdiv className=\"text-red-500 text-blue-500\" \u002F>\n\u002F\u002F \"text-red-500\" and \"text-blue-500\" affect \"color\".\n\u002F\u002F \"text-blue-500\" takes precedence (appears later). no-dark-without-light detecta cuando usas dark: sin una clase base, algo que suele causar estilos faltantes en light mode: \u002F\u002F ❌ — ¿qué fondo tiene en light mode?\n\u003Cdiv className=\"dark:bg-gray-900\" \u002F>\n\n\u002F\u002F ✅\n\u003Cdiv className=\"bg-white dark:bg-gray-900\" \u002F> no-dark-without-light chequea dark: por defecto, pero la opción variants permite aplicar el mismo patrón a cualquier variante — útil si tu proyecto usa variantes custom. no-contradicting-variants atrapa variantes redundantes donde la clase base ya aplica incondicionalmente: \u002F\u002F ❌ — dark:flex es redundante, flex ya aplica siempre\n\u003Cdiv className=\"flex dark:flex\" \u002F> no-deprecated-classes reemplaza automáticamente las clases deprecadas en v4: \u002F\u002F ❌ v3\n\u003Cdiv className=\"flex-grow overflow-ellipsis decoration-slice\" \u002F>\n\n\u002F\u002F ✅ v4 (autofix)\n\u003Cdiv className=\"grow text-ellipsis box-decoration-slice\" \u002F> También flex-shrink → shrink y decoration-clone → box-decoration-clone. Y las clásicas no-duplicate-classes (con autofix) y no-unnecessary-whitespace.",{"id":188,"title":189,"titles":190,"content":191,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#style-consistencia-del-equipo","Style — Consistencia del equipo",[159,179],"enforce-sort-order ordena las clases según el orden oficial de Tailwind CSS (con autofix), compatible con oxfmt y prettier-plugin-tailwindcss. Su modo strict agrupa las clases por prefijo de variante, ordena dentro de cada grupo y ordena los grupos por prioridad de variante. enforce-shorthand convierte mt-2 mr-2 mb-2 ml-2 en m-2, w-full h-full en size-full, y muchas más combinaciones. Todo con autofix. enforce-logical convierte propiedades físicas en lógicas para soporte LTR\u002FRTL: ml-4 → ms-4, left-0 → start-0. Su inversa, enforce-physical, hace lo contrario para proyectos que son solo LTR y prefieren consistencia con propiedades físicas. Ambas con autofix. enforce-consistent-variable-syntax normaliza la sintaxis de variables CSS entre bg-[var(--primary)] y la shorthand de v4 bg-(--primary). enforce-canonical convierte valores arbitrarios a clases nativas cuando existen: p-[2px] → p-0.5 (usa rootFontSize de 16px por defecto para la conversión). Funciona directo con la API de canonicalización de Tailwind. enforce-consistent-important-position (default suffix, la forma canónica de v4), enforce-negative-arbitrary-values (-top-[5px] → top-[-5px]) y consistent-variant-order completan las reglas de estilo.",{"id":193,"title":194,"titles":195,"content":196,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#complexity-mantener-el-código-manejable","Complexity — Mantener el código manejable",[159,179],"max-class-count avisa cuando un elemento supera las 20 clases (configurable). Es la señal de que es hora de extraer un componente. enforce-consistent-line-wrapping controla el largo del string de clases por print width o por cantidad de clases por línea.",{"id":198,"title":199,"titles":200,"content":201,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#restrictions-reglas-del-design-system","Restrictions — Reglas del design system",[159,179],"no-hardcoded-colors prohíbe colores hardcodeados como bg-[#ff5733] en brackets arbitrarios — el típico atajo que erosiona tu design system. no-arbitrary-value y no-unnecessary-arbitrary-value (con autofix) controlan el uso de valores arbitrarios. La segunda detecta cuando usas h-[auto] pero existe h-auto. no-restricted-classes permite bloquear clases específicas por nombre o regex, con mensajes custom.",{"id":203,"title":204,"titles":205,"content":206,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#extracción-de-clases","Extracción de clases",[159],"El parser es lo que hace que todo esto funcione de manera confiable. No es un regex que busca className= y reza. Extrae clases de: Atributos JSX (className, class)Atributos JSX con objetos — e.g. el prop classNames de Mantine: \u003CInput classNames={{ root: \"flex\", input: \"border-none\" }} \u002F>Template literals con interpolaciónTernariosFunciones de utilidad: cn(), clsx(), cx(), cva(), twMerge(), twJoin(), y máscva() completo — base, variants, compoundVariantstv() completo — base, slots, variants con objetos de slots, compoundSlotsclassed() (tw-classed) — ignora el tipo de elemento, extrae clases y config estilo cvaTagged templates (tw\\...``)Variables por nombre (className, classes, style, styles)Clases de componentes definidas con @layer components { .btn {} } en tu CSS Maneja nested brackets, calc anidado, arbitrary variants, quoted values, important modifier, negative values y named groups\u002Fpeers. Los edge cases que rompen otros parsers.",{"id":208,"title":209,"titles":210,"content":211,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#detección-customizable","Detección customizable",[159,204],"Por defecto el plugin detecta clases en atributos comunes, 14 funciones de utilidad, tagged templates tw, y variables con nombres como className\u002Fclasses\u002Fstyle. Puedes extender estos defaults vía settings.tailwindcss — todos los valores son aditivos: {\n  \"settings\": {\n    \"tailwindcss\": {\n      \"attributes\": [\"overlayClassName\"],\n      \"callees\": [\"myHelper\"],\n      \"tags\": [\"css\"],\n      \"variablePatterns\": [\"^tw\"],\n    },\n  },\n} Esto aplica a las 22 reglas de una vez. Si necesitas quitar un default built-in, usa exclude en el mismo bloque de settings.",{"id":213,"title":214,"titles":215,"content":216,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#la-historia-detrás","La historia detrás",[159],"Partí planificando qué quería, el stack que iba a usar y cómo quería que funcionara todo. Después de planificar la implementación con Claude Code, arrancó la iteración hasta conseguir las 22 reglas actuales. El repo incluye un CLAUDE.md y skills configuradas que permiten a cualquier contribuidor usar el mismo workflow para escribir reglas nuevas — la misma herramienta con la que se construyó el plugin. Si quieres agregar una regla, Claude Code ya sabe cómo hacerlo en este proyecto. El proyecto corre completamente sobre el ecosistema de herramientas de VoidZero. tsdown para el build, oxfmt para el formateo, vitest para testing, tsgo (TypeScript 7 nativo en Go) para el type checking, y por supuesto oxlint para el linting del propio plugin. Cada herramienta en la cadena está construida sobre Rust u optimizada para velocidad. No fue una decisión cosmética, fue dogfooding deliberado. Si vas a hacer un plugin para oxlint, tiene sentido que todo el toolchain sea del mismo ecosistema. Y si vas a desarrollar con un agente de IA, tiene sentido que el repo esté preparado para ello.",{"id":218,"title":219,"titles":220,"content":221,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#cómo-empezar","Cómo empezar",[159],"pnpm add -D oxlint-tailwindcss Agrega el plugin a tu .oxlintrc.json: {\n  \"jsPlugins\": [\"oxlint-tailwindcss\"],\n  \"rules\": {\n    \"tailwindcss\u002Fno-unknown-classes\": \"error\",\n    \"tailwindcss\u002Fno-duplicate-classes\": \"error\",\n    \"tailwindcss\u002Fno-conflicting-classes\": \"error\",\n    \"tailwindcss\u002Fno-deprecated-classes\": \"error\",\n    \"tailwindcss\u002Fno-unnecessary-whitespace\": \"error\",\n    \"tailwindcss\u002Fenforce-sort-order\": \"warn\",\n    \"tailwindcss\u002Fenforce-shorthand\": \"warn\",\n    \"tailwindcss\u002Fno-hardcoded-colors\": \"warn\"\n    \u002F\u002F...\n  }\n} Ejecuta oxlint. Eso es todo.",{"id":223,"title":224,"titles":225,"content":226,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#pruébalo","Pruébalo",[159],"El plugin es funcional, testeado y usado en producción. Pero un linter se hace mejor con feedback real de proyectos reales.\nSi lo pruebas y encuentras un caso que no maneja bien, abre un issue. Si quieres contribuir una regla, el repo ya está preparado para que iteres con Claude Code desde el primer minuto. Y si simplemente te resultó útil, una estrella en GitHub ayuda a que más gente lo encuentre. GitHub · npm html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}html pre.shiki code .sfyAc, html code.shiki .sfyAc{--shiki-default:#C3E88D}html pre.shiki code .s5Dmg, html code.shiki .s5Dmg{--shiki-default:#FFCB6B}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);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}",{"id":228,"title":229,"titles":230,"content":231,"level":9},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable","Screaming Architecture: la clave para un frontend escalable",[],"Tu proyecto frontend debería gritar lo que hace, no con qué está hecho. Ejemplos concretos de migración, convivencia con frameworks como Nuxt y manejo de shared state.",{"id":233,"title":234,"titles":235,"content":236,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#el-patrón-que-vi-en-todos-lados","El patrón que vi en todos lados",[229],"En varias empresas donde trabajé me encontré con el mismo patrón. Los equipos sabían que organizar por features era importante, la intención estaba ahí. Pero en la práctica, la feature terminaba desparramada por todo el proyecto: components\u002Fauth\u002F, services\u002Fauth\u002F, store\u002Fauth\u002F, views\u002Fauth\u002F. El nombre de la feature aparecía en cada carpeta técnica, pero nunca en un solo lugar. src\u002F\n├── components\u002F\n│   ├── auth\u002F          # LoginButton.vue, RegisterForm.vue\n│   ├── products\u002F      # ProductCard.vue, ProductList.vue\n│   └── orders\u002F        # OrderSummary.vue\n├── views\u002F\n│   ├── auth\u002F          # LoginView.vue, RegisterView.vue\n│   ├── products\u002F      # ProductsView.vue\n│   └── orders\u002F        # OrdersView.vue\n├── services\u002F\n│   ├── auth.service.ts\n│   ├── products.service.ts\n│   └── orders.service.ts\n├── store\u002F\n│   ├── auth.store.ts\n│   ├── products.store.ts\n│   └── orders.store.ts\n└── utils\u002F A primera vista parece ordenado. Pero cuando necesitas cambiar algo de Auth, tocas 4 carpetas. Cuando un dev nuevo llega, tiene que reconstruir mentalmente qué archivos pertenecen a qué feature. Y cuando la app crece, cada carpeta técnica se convierte en un cajón de sastre con 30+ archivos. El problema no es la falta de organización. Es que la organización grita \"tecnología\" en vez de gritar \"negocio\".",{"id":238,"title":239,"titles":240,"content":241,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#la-analogía-que-lo-explica","La analogía que lo explica",[229],"Imagina el plano de una casa. No ves una sección de \"ladrillos\", otra de \"cemento\" y otra de \"ventanas\". Ves \"cocina\", \"dormitorio\", \"baño\". Los planos gritan el propósito de cada espacio, no los materiales con los que está construido. Screaming Architecture busca lo mismo para tu código. Que la estructura de carpetas de alto nivel grite las features de tu aplicación, no las tecnologías que usas.",{"id":243,"title":244,"titles":245,"content":246,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#qué-ganas-con-este-enfoque","Qué ganas con este enfoque",[229],"Entendimiento instantáneo: abres el proyecto y de un vistazo sabes qué hace el negocio.Onboarding veloz: nuevos devs entienden la estructura del proyecto en minutos, no días.Cambios localizados: modificar una feature no te obliga a tocar 4 carpetas. Todo está en un lugar.Escalabilidad real: agregar una nueva feature es crear una carpeta, no insertar archivos en 6 lugares distintos.Testing más fácil: cada módulo es una unidad aislada que se puede testear independiente.Refactorizaciones seguras: mover o eliminar una feature entera es mover o eliminar una carpeta.",{"id":248,"title":249,"titles":250,"content":251,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#aplicando-screaming-architecture","Aplicando Screaming Architecture",[229],"La idea es simple: agrupar por features o dominios de negocio. Cada carpeta contiene todo lo necesario para esa funcionalidad. src\u002F\n├── modules\u002F\n│   ├── Auth\u002F\n│   │   ├── components\u002F   # LoginButton.vue, RegisterForm.vue\n│   │   ├── views\u002F        # LoginView.vue, RegisterView.vue\n│   │   ├── routes\u002F       # Rutas de este módulo\n│   │   ├── store\u002F        # auth.store.ts\n│   │   └── services\u002F     # auth.service.ts\n│   ├── Products\u002F\n│   │   ├── components\u002F   # ProductCard.vue, ProductList.vue\n│   │   ├── views\u002F        # ProductsView.vue, ProductDetailView.vue\n│   │   ├── store\u002F        # products.store.ts\n│   │   └── services\u002F     # products.service.ts\n│   └── Orders\u002F\n│       ├── components\u002F\n│       ├── views\u002F\n│       ├── store\u002F\n│       └── services\u002F\n├── shared\u002F\n│   ├── ui\u002F\n│   │   ├── components\u002F   # BaseButton.vue, ModalBase.vue\n│   │   └── composables\u002F  # useModal.ts, useDarkMode.ts\n│   ├── composables\u002F      # useDebounce.ts, useLocalStorage.ts\n│   ├── utils\u002F            # formatDate.ts, validateEmail.ts\n│   └── assets\u002F           # Imágenes globales, estilos base\n├── layouts\u002F              # DefaultLayout.vue, AuthLayout.vue\n├── router\u002F               # Router general\n├── app.vue\n└── main.ts No todos los módulos necesitan la misma estructura interna. Auth puede tener store\u002F y services\u002F, pero un módulo de Landing puede ser solo components\u002F y views\u002F. Cada módulo define lo que necesita.",{"id":253,"title":254,"titles":255,"content":256,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#de-la-teoría-a-la-práctica-migrando-una-feature","De la teoría a la práctica: migrando una feature",[229],"Tomemos el ejemplo de Auth en la estructura desagregada y veamos cómo queda la migración: Antes (4 carpetas): src\u002Fcomponents\u002Fauth\u002FLoginButton.vue\nsrc\u002Fcomponents\u002Fauth\u002FRegisterForm.vue\nsrc\u002Fviews\u002Fauth\u002FLoginView.vue\nsrc\u002Fviews\u002Fauth\u002FRegisterView.vue\nsrc\u002Fservices\u002Fauth.service.ts\nsrc\u002Fstore\u002Fauth.store.ts Después (1 carpeta): src\u002Fmodules\u002FAuth\u002Fcomponents\u002FLoginButton.vue\nsrc\u002Fmodules\u002FAuth\u002Fcomponents\u002FRegisterForm.vue\nsrc\u002Fmodules\u002FAuth\u002Fviews\u002FLoginView.vue\nsrc\u002Fmodules\u002FAuth\u002Fviews\u002FRegisterView.vue\nsrc\u002Fmodules\u002FAuth\u002Fservices\u002Fauth.service.ts\nsrc\u002Fmodules\u002FAuth\u002Fstore\u002Fauth.store.ts Los imports cambian, el código no. Si tu módulo de Auth necesita exponer algo al resto de la app (un guard, un composable, el estado del usuario), puedes crear un index.ts que haga de API pública del módulo: \u002F\u002F src\u002Fmodules\u002FAuth\u002Findex.ts\nexport { useAuthStore } from '.\u002Fstore\u002Fauth.store'\nexport { useCurrentUser } from '.\u002Fcomposables\u002FuseCurrentUser'\nexport { authGuard } from '.\u002Froutes\u002Fguards' Así el resto de la app importa desde @\u002Fmodules\u002FAuth, no desde las carpetas internas. Si refactorizas la estructura interna del módulo, los imports externos no se rompen.",{"id":258,"title":259,"titles":260,"content":261,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#convivencia-con-frameworks","Convivencia con frameworks",[229],"Si usas Nuxt, Next o cualquier framework con convenciones de carpetas (pages\u002F, composables\u002F, components\u002F), la pregunta obvia es: ¿cómo convive esto con Screaming Architecture? Las convenciones del framework se encargan del routing y el auto-import. Screaming Architecture se encarga de la lógica de negocio. En Nuxt, por ejemplo, pages\u002F define las rutas, pero la página puede ser un wrapper liviano que importa el módulo real: \u003C!-- pages\u002Fauth\u002Flogin.vue -->\n\u003Ctemplate>\n  \u003CLoginView \u002F>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup lang=\"ts\">\nimport LoginView from '~\u002Fmodules\u002FAuth\u002Fviews\u002FLoginView.vue'\n\u003C\u002Fscript> Nuxt se encarga del routing (\u002Fauth\u002Flogin), y tu módulo Auth se encarga de la lógica. pages\u002F queda liviana, solo como punto de entrada. Lo mismo aplica para composables\u002F. Los composables globales (auto-imported por Nuxt) van en la carpeta convencional del framework. Los composables específicos de una feature van dentro del módulo: composables\u002F              # Auto-imported por Nuxt (globales)\n├── useTheme.ts\n└── useBreakpoint.ts\nmodules\u002FAuth\u002Fcomposables\u002F # Específicos de Auth (import manual)\n├── useCurrentUser.ts\n└── useAuthRedirect.ts",{"id":263,"title":264,"titles":265,"content":266,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#cross-cutting-concerns-qué-va-en-shared","Cross-cutting concerns: qué va en shared",[229],"La pregunta más común cuando aplicas esto: ¿qué pasa cuando dos módulos necesitan lo mismo? La regla es simple. Si algo es específico de una feature, va en su módulo. Si lo usan dos o más features, va en shared\u002F. shared\u002Fui\u002F es para componentes de UI genéricos sin lógica de negocio: botones, modales, inputs. Son los building blocks, no las features. shared\u002Fcomposables\u002F es para lógica reutilizable sin estado de negocio: useDebounce, useLocalStorage, useIntersectionObserver. shared\u002Futils\u002F es para funciones puras: formatDate, slugify, validateEmail. ¿Y el estado compartido? Si Auth y Orders necesitan datos del usuario, Auth es dueño del estado. Orders importa useCurrentUser desde @\u002Fmodules\u002FAuth. Si con el tiempo más módulos necesitan el mismo dato, puedes mover ese composable a shared\u002F. Pero no lo hagas preventivamente. Empieza con el módulo dueño y mueve solo cuando hay evidencia real de que es cross-cutting.",{"id":268,"title":269,"titles":270,"content":271,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#cuándo-sí-y-cuándo-no","Cuándo sí y cuándo no",[229],"Este enfoque funciona mejor en proyectos medianos a grandes, con lógica de negocio real y equipos de varios devs. Si tu app tiene 5+ features que crecen de forma independiente, Screaming Architecture te va a ordenar la vida. Para MVPs, landing pages o CRUDs simples, quizás es excesivo. No necesitas una estructura de módulos para 3 vistas y un formulario. Si tu proyecto tiene potencial de crecer, adoptarlo temprano te ahorra la migración dolorosa después. Y si ya tienes un proyecto grande con estructura genérica, puedes migrar de forma progresiva, un módulo a la vez, empezando por la feature que más crece. La próxima vez que arranques un proyecto, piensa en cómo se verá la estructura cuando el equipo crezca. Si tus carpetas solo dicen components\u002F y utils\u002F, ya sabes por dónde empezar. html pre.shiki code .sC9rS, html code.shiki .sC9rS{--shiki-default:#464B5D;--shiki-default-font-style:italic}html pre.shiki code .s6cf3, html code.shiki .s6cf3{--shiki-default:#89DDFF;--shiki-default-font-style:italic}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}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);}html pre.shiki code .s-wAU, html code.shiki .s-wAU{--shiki-default:#F07178}html pre.shiki code .sJ14y, html code.shiki .sJ14y{--shiki-default:#C792EA}",{"post":273,"surround":867,"translatedPost":870},{"id":274,"title":6,"author":275,"body":276,"date":852,"dateModified":852,"description":8,"extension":853,"img":854,"meta":855,"navigation":856,"path":5,"published":856,"readingTime":857,"seo":858,"sitemap":859,"slug":854,"stem":860,"tags":861,"union":865,"__hash__":866},"blog_es\u002Fes\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento.md","Sergio Azócar",{"type":277,"value":278,"toc":837},"minimark",[279,283,287,290,293,296,299,307,310,313,316,344,347,350,353,356,369,372,376,379,410,413,467,470,473,476,522,525,584,587,590,654,657,663,666,692,695,702,705,710,713,727,738,741,749,752,755,796,799,825,828,831,834],[280,281,12],"h2",{"id":282},"migrar-dejó-de-doler",[284,285,286],"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.",[284,288,289],{},"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.",[284,291,292],{},"Ya no.",[280,294,18],{"id":295},"el-dolor-histórico-de-migrar",[284,297,298],{},"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.",[284,300,301,302,306],{},"Migrar un design system era parecido pero peor. Cambias el sistema de tokens, los componentes base, los nombres de las props. De pronto cada ",[303,304,305],"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.",[284,308,309],{},"El patrón siempre era el mismo, alta carga, baja motivación, postergación indefinida.",[280,311,23],{"id":312},"por-qué-las-migraciones-son-el-escenario-perfecto-para-un-agente",[284,314,315],{},"Después de hacer varias con Claude Code, identifiqué tres condiciones dónde son ideales:",[317,318,319,327,333],"ol",{},[320,321,322,326],"li",{},[323,324,325],"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.",[320,328,329,332],{},[323,330,331],{},"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.",[320,334,335,338,339,343],{},[323,336,337],{},"Documentación accesible."," La herramienta de destino casi siempre tiene un ",[340,341,342],"em",{},"migration guide",". Le pasas el guide al agente, le das el contexto del proyecto, y deja de inventar.",[284,345,346],{},"Si tu tarea cumple las tres, deja de procrastinar. Es trabajo para un agente.",[280,348,28],{"id":349},"caso-1-el-toolchain-de-un-monorepo",[284,351,352],{},"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.",[284,354,355],{},"Dos cuellos de botella:",[357,358,359,366],"ul",{},[320,360,361,362,365],{},"Biome y tsc requerían ",[303,363,364],{},"NODE_OPTIONS='--max-old-space-size=8192'"," para no caer por out of memory durante CI.",[320,367,368],{},"tsc, single-threaded, se demoraba 25 segundos en chequear los tipos del monorepo completo.",[284,370,371],{},"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.",[373,374,33],"h3",{"id":375},"migración-a-oxlint-oxfmt",[284,377,378],{},"Los pasos:",[317,380,381,384,391,397,404,407],{},[320,382,383],{},"Reemplazar Biome por oxlint y oxfmt.",[320,385,386,387,390],{},"Configurar ",[303,388,389],{},".oxlintrc.json"," con 129 reglas activas.",[320,392,386,393,396],{},[303,394,395],{},".oxfmtrc.json"," equivalente.",[320,398,399,400,403],{},"Actualizar scripts en ",[303,401,402],{},"package.json",".",[320,405,406],{},"Ajustar el pipeline en GitHub Actions.",[320,408,409],{},"Ajustar la config del editor.",[284,411,412],{},"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.",[414,415,416,435],"table",{},[417,418,419],"thead",{},[420,421,422,426,429,432],"tr",{},[423,424,425],"th",{},"Métrica",[423,427,428],{},"Biome",[423,430,431],{},"oxlint (ene 2026)",[423,433,434],{},"oxlint (mar 2026)",[436,437,438,453],"tbody",{},[420,439,440,444,447,450],{},[441,442,443],"td",{},"Tiempo promedio",[441,445,446],{},"3.70s",[441,448,449],{},"1.04s",[441,451,452],{},"0.49s",[420,454,455,458,461,464],{},[441,456,457],{},"Archivos analizados",[441,459,460],{},"1.227",[441,462,463],{},"1.262",[441,465,466],{},"~1.670",[284,468,469],{},"oxlint procesaba más archivos con más reglas en una fracción del tiempo. Y la herramienta sigue mejorando con cada release.",[373,471,39],{"id":472},"migración-a-tsgo",[284,474,475],{},"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:",[317,477,478,484,494,509,519],{},[320,479,480,481,403],{},"Instalar ",[303,482,483],{},"@typescript\u002Fnative-preview",[320,485,486,487,490,491,403],{},"Cambiar el script de ",[303,488,489],{},"NODE_OPTIONS='--max-old-space-size=8192' tsc"," a ",[303,492,493],{},"tsgo",[320,495,496,497,500,501,504,505,508],{},"Ajustar ",[303,498,499],{},"tsconfig.json"," (sacar ",[303,502,503],{},"baseUrl",", agregar ",[303,506,507],{},".\u002F"," como prefijo en paths).",[320,510,511,512,490,515,518],{},"Actualizar la caché en CI (de ",[303,513,514],{},".tsbuildinfo",[303,516,517],{},".tsgo-cache",").",[320,520,521],{},"Corregir tipos en los lugares donde tsgo es más estricto que tsc.",[284,523,524],{},"El bonus, ya no necesitas la config de memoria. tsgo corre en Go sin las restricciones de Node.",[414,526,527,542],{},[417,528,529],{},[420,530,531,533,536,539],{},[423,532,425],{},[423,534,535],{},"tsc",[423,537,538],{},"tsgo (ene 2026)",[423,540,541],{},"tsgo (mar 2026)",[436,543,544,558,571],{},[420,545,546,549,552,555],{},[441,547,548],{},"Tiempo (cold)",[441,550,551],{},"25.34s",[441,553,554],{},"5.32s",[441,556,557],{},"0.76s",[420,559,560,563,566,569],{},[441,561,562],{},"Uso CPU",[441,564,565],{},"~132%",[441,567,568],{},"~430%",[441,570,568],{},[420,572,573,576,579,582],{},[441,574,575],{},"Requiere config de memoria",[441,577,578],{},"Sí (8 GB)",[441,580,581],{},"No",[441,583,581],{},[284,585,586],{},"33 veces más rápido en typecheck, sin tocar una sola línea de código TypeScript del proyecto.",[373,588,44],{"id":589},"el-número-final",[414,591,592,608],{},[417,593,594],{},[420,595,596,599,602,605],{},[423,597,598],{},"Fase",[423,600,601],{},"Antes",[423,603,604],{},"Migración",[423,606,607],{},"Hoy (abril 2026)",[436,609,610,621,632],{},[420,611,612,615,617,619],{},[441,613,614],{},"Lint",[441,616,446],{},[441,618,449],{},[441,620,452],{},[420,622,623,626,628,630],{},[441,624,625],{},"Typecheck",[441,627,551],{},[441,629,554],{},[441,631,557],{},[420,633,634,639,644,649],{},[441,635,636],{},[323,637,638],{},"Total",[441,640,641],{},[323,642,643],{},"29.04s",[441,645,646],{},[323,647,648],{},"6.36s",[441,650,651],{},[323,652,653],{},"1.25s",[284,655,656],{},"De 29 segundos a 1.25. 23 veces más rápido. Y lo que terminó de convencer al equipo fueron precisamente esos números. Sin medir antes y después, la migración es solo \"Sergio cambió las herramientas\". Con los números, es \"el feedback de calidad de código es 23X más rápido\".",[284,658,659,662],{},[323,660,661],{},"Lección:"," mide siempre. Es lo que convierte una migración en una historia que el equipo entiende y firma.",[280,664,49],{"id":665},"caso-2-screaming-architecture-en-un-frontend-grande",[284,667,668,669,673,674,677,678,677,681,684,685,677,688,691],{},"Antes de la migración del toolchain, había hecho otra: aplicar ",[670,671,672],"a",{"href":228},"screaming architecture"," al frontend del producto principal. Reorganizar el repo desde carpetas técnicas (",[303,675,676],{},"components\u002F",", ",[303,679,680],{},"services\u002F",[303,682,683],{},"store\u002F",") a módulos por feature (",[303,686,687],{},"modules\u002FAuth\u002F",[303,689,690],{},"modules\u002FOrders\u002F",", etc).",[284,693,694],{},"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\".",[284,696,697,698,701],{},"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 ",[303,699,700],{},"modules\u002FX\u002F","\". El agente entiende el patrón al primer ejemplo y lo aplica al resto.",[284,703,704],{},"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.",[284,706,707,709],{},[323,708,661],{}," 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.",[280,711,54],{"id":712},"caso-3-el-rediseño-de-este-sitio",[284,714,715,716,719,720,723,724,403],{},"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 (",[303,717,718],{},"beacon"," y ",[303,721,722],{},"orbit","), tema dark-only, borders translúcidas con ",[303,725,726],{},"color-mix",[284,728,729,730,733,734,737],{},"La diferencia con los casos anteriores es que el contexto del proyecto era todo mío. Tengo un ",[303,731,732],{},"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 ",[303,735,736],{},"border-(muted)"," ni dónde está definido. Ya lo sabe.",[284,739,740],{},"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.",[284,742,743,745,746,748],{},[323,744,661],{}," invertir en un buen ",[303,747,732],{}," no es overhead, es la diferencia entre un agente útil y uno que alucina.",[280,750,59],{"id":751},"el-playbook",[284,753,754],{},"Después de varias migraciones, esto es lo que hago siempre:",[317,756,757,763,769,775,784,790],{},[320,758,759,762],{},[323,760,761],{},"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.",[320,764,765,768],{},[323,766,767],{},"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ó.",[320,770,771,774],{},[323,772,773],{},"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.",[320,776,777,783],{},[323,778,779,780,782],{},"Mantén un ",[303,781,732],{}," actualizado."," Stack, convenciones, comandos, gotchas del proyecto. Es la diferencia entre un agente que entiende el repo y uno que inventa.",[320,785,786,789],{},[323,787,788],{},"Mide antes y después."," Sin números no hay historia que contar, ni argumento para venderle al equipo.",[320,791,792,795],{},[323,793,794],{},"Pair programming, no autopilot."," El agente no reemplaza criterio, lo amplifica. Tú decides qué se hace, él lo ejecuta y trae el feedback.",[280,797,64],{"id":798},"lo-que-no-hagas",[357,800,801,807,813,819],{},[320,802,803,806],{},[323,804,805],{},"No le pidas todo de una vez."," \"Migra el repo entero\" es la peor instrucción posible. Divide en fases, una a la vez.",[320,808,809,812],{},[323,810,811],{},"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.",[320,814,815,818],{},[323,816,817],{},"No dejes que invente paths o APIs."," Si cita una función que no reconoces, verifica que existe antes de aceptar el cambio.",[320,820,821,824],{},[323,822,823],{},"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.",[280,826,69],{"id":827},"cierre",[284,829,830],{},"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.",[284,832,833],{},"Si tienes una migración estancada hace tiempo, abre Claude Code esta semana. Probablemente la termines antes de que termine el sprint.",[284,835,836],{},"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.",{"title":181,"searchDepth":15,"depth":36,"links":838},[839,840,841,842,847,848,849,850,851],{"id":282,"depth":15,"text":12},{"id":295,"depth":15,"text":18},{"id":312,"depth":15,"text":23},{"id":349,"depth":15,"text":28,"children":843},[844,845,846],{"id":375,"depth":36,"text":33},{"id":472,"depth":36,"text":39},{"id":589,"depth":36,"text":44},{"id":665,"depth":15,"text":49},{"id":712,"depth":15,"text":54},{"id":751,"depth":15,"text":59},{"id":798,"depth":15,"text":64},{"id":827,"depth":15,"text":69},"2026-04-28","md",null,{},true,8,{"title":6,"description":8},{"loc":5},"es\u002Fblog\u002Fcomo-hacer-migraciones-con-claude-code-y-no-morir-en-el-intento",[862,863,864],"claude-code","migrations","tooling","how-to-migrate-with-claude-code-and-not-die-trying","EqJQJcQLgyv3npCjPWhlOIz0neVF-RM4z-sWz076hDU",[854,868],{"title":74,"path":73,"stem":869,"description":76,"children":-1},"es\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente",{"id":871,"title":872,"author":275,"body":873,"date":852,"dateModified":852,"description":1398,"extension":853,"img":854,"meta":1399,"navigation":856,"path":1400,"published":856,"readingTime":857,"seo":1401,"sitemap":1402,"slug":854,"stem":1403,"tags":1404,"union":865,"__hash__":1405},"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":277,"value":874,"toc":1383},[875,879,882,885,888,892,895,901,904,908,911,934,937,941,944,947,958,961,965,968,995,998,1042,1045,1049,1052,1089,1092,1144,1147,1151,1209,1212,1218,1222,1240,1243,1249,1252,1257,1261,1272,1281,1284,1292,1296,1299,1340,1344,1370,1374,1377,1380],[280,876,878],{"id":877},"migrating-no-longer-hurts","Migrating no longer hurts",[284,880,881],{},"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.",[284,883,884],{},"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.",[284,886,887],{},"Not anymore.",[280,889,891],{"id":890},"the-historical-pain-of-migrating","The historical pain of migrating",[284,893,894],{},"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.",[284,896,897,898,900],{},"Migrating a design system was similar but worse. You change the token system, the base components, the prop names. Suddenly every ",[303,899,305],{}," in the app needs review, and when you have hundreds of those, no team signs off on a full quarter just for that.",[284,902,903],{},"The pattern was always the same, high load, low motivation, indefinite procrastination.",[280,905,907],{"id":906},"why-migrations-are-the-perfect-scenario-for-an-agent","Why migrations are the perfect scenario for an agent",[284,909,910],{},"After doing several with Claude Code, I've identified three conditions where they're ideal:",[317,912,913,919,925],{},[320,914,915,918],{},[323,916,917],{},"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.",[320,920,921,924],{},[323,922,923],{},"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.",[320,926,927,930,931,933],{},[323,928,929],{},"Accessible documentation."," The target tool almost always has a ",[340,932,342],{},". Pass the guide to the agent, give it the project context, and it stops making things up.",[284,935,936],{},"If your task meets all three, stop procrastinating. It's work for an agent.",[280,938,940],{"id":939},"case-1-a-monorepos-toolchain","Case 1: a monorepo's toolchain",[284,942,943],{},"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.",[284,945,946],{},"Two bottlenecks:",[357,948,949,955],{},[320,950,951,952,954],{},"Biome and tsc required ",[303,953,364],{}," to avoid out of memory crashes during CI.",[320,956,957],{},"tsc, single-threaded, took 25 seconds to typecheck the entire monorepo.",[284,959,960],{},"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.",[373,962,964],{"id":963},"migration-to-oxlint-oxfmt","Migration to oxlint + oxfmt",[284,966,967],{},"The steps:",[317,969,970,973,979,984,989,992],{},[320,971,972],{},"Replace Biome with oxlint and oxfmt.",[320,974,975,976,978],{},"Configure ",[303,977,389],{}," with 129 active rules.",[320,980,981,982,403],{},"Configure an equivalent ",[303,983,395],{},[320,985,986,987,403],{},"Update scripts in ",[303,988,402],{},[320,990,991],{},"Adjust the GitHub Actions pipeline.",[320,993,994],{},"Adjust the editor config.",[284,996,997],{},"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.",[414,999,1000,1015],{},[417,1001,1002],{},[420,1003,1004,1007,1009,1012],{},[423,1005,1006],{},"Metric",[423,1008,428],{},[423,1010,1011],{},"oxlint (Jan 2026)",[423,1013,1014],{},"oxlint (Mar 2026)",[436,1016,1017,1028],{},[420,1018,1019,1022,1024,1026],{},[441,1020,1021],{},"Average time",[441,1023,446],{},[441,1025,449],{},[441,1027,452],{},[420,1029,1030,1033,1036,1039],{},[441,1031,1032],{},"Files analyzed",[441,1034,1035],{},"1,227",[441,1037,1038],{},"1,262",[441,1040,1041],{},"~1,670",[284,1043,1044],{},"oxlint was processing more files with more rules in a fraction of the time. And the tool keeps improving with every release.",[373,1046,1048],{"id":1047},"migration-to-tsgo","Migration to tsgo",[284,1050,1051],{},"TypeScript 7 (tsgo) is the rewrite of tsc (TypeScript Compiler) in Go. Microsoft promised 7x to 10x improvements. The migration was even more straightforward:",[317,1053,1054,1059,1067,1079,1086],{},[320,1055,1056,1057,403],{},"Install ",[303,1058,483],{},[320,1060,1061,1062,1064,1065,403],{},"Change the script from ",[303,1063,489],{}," to ",[303,1066,493],{},[320,1068,1069,1070,1072,1073,1075,1076,1078],{},"Adjust ",[303,1071,499],{}," (drop ",[303,1074,503],{},", add ",[303,1077,507],{}," as prefix in paths).",[320,1080,1081,1082,1064,1084,518],{},"Update the CI cache (from ",[303,1083,514],{},[303,1085,517],{},[320,1087,1088],{},"Fix types in places where tsgo is stricter than tsc.",[284,1090,1091],{},"The bonus, you no longer need the memory config. tsgo runs in Go without Node's constraints.",[414,1093,1094,1108],{},[417,1095,1096],{},[420,1097,1098,1100,1102,1105],{},[423,1099,1006],{},[423,1101,535],{},[423,1103,1104],{},"tsgo (Jan 2026)",[423,1106,1107],{},"tsgo (Mar 2026)",[436,1109,1110,1121,1132],{},[420,1111,1112,1115,1117,1119],{},[441,1113,1114],{},"Time (cold)",[441,1116,551],{},[441,1118,554],{},[441,1120,557],{},[420,1122,1123,1126,1128,1130],{},[441,1124,1125],{},"CPU usage",[441,1127,565],{},[441,1129,568],{},[441,1131,568],{},[420,1133,1134,1137,1140,1142],{},[441,1135,1136],{},"Requires memory config",[441,1138,1139],{},"Yes (8 GB)",[441,1141,581],{},[441,1143,581],{},[284,1145,1146],{},"33 times faster on typecheck, without touching a single line of TypeScript code in the project.",[373,1148,1150],{"id":1149},"the-final-number","The final number",[414,1152,1153,1169],{},[417,1154,1155],{},[420,1156,1157,1160,1163,1166],{},[423,1158,1159],{},"Phase",[423,1161,1162],{},"Before",[423,1164,1165],{},"Migration",[423,1167,1168],{},"Today (Apr 2026)",[436,1170,1171,1181,1191],{},[420,1172,1173,1175,1177,1179],{},[441,1174,614],{},[441,1176,446],{},[441,1178,449],{},[441,1180,452],{},[420,1182,1183,1185,1187,1189],{},[441,1184,625],{},[441,1186,551],{},[441,1188,554],{},[441,1190,557],{},[420,1192,1193,1197,1201,1205],{},[441,1194,1195],{},[323,1196,638],{},[441,1198,1199],{},[323,1200,643],{},[441,1202,1203],{},[323,1204,648],{},[441,1206,1207],{},[323,1208,653],{},[284,1210,1211],{},"From 29 seconds to 1.25. 23 times faster. And what finally convinced the team were precisely those numbers. Without measuring before and after, the migration is just \"Sergio changed the tools\". With the numbers, it's \"code quality feedback is 23X faster\".",[284,1213,1214,1217],{},[323,1215,1216],{},"Lesson:"," always measure. That's what turns a migration into a story the team understands and signs off on.",[280,1219,1221],{"id":1220},"case-2-screaming-architecture-in-a-large-frontend","Case 2: screaming architecture in a large frontend",[284,1223,1224,1225,1228,1229,677,1231,677,1233,1235,1236,677,1238,691],{},"Before the toolchain migration, I'd done another one: applying ",[670,1226,672],{"href":1227},"\u002Fen\u002Fblog\u002Fscreaming-architecture-the-key-to-scalable-frontend"," to the main product's frontend. Reorganizing the repo from technical folders (",[303,1230,676],{},[303,1232,680],{},[303,1234,683],{},") into feature modules (",[303,1237,687],{},[303,1239,690],{},[284,1241,1242],{},"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\".",[284,1244,1245,1246,1248],{},"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 ",[303,1247,700],{},"\". The agent gets the pattern from the first example and applies it everywhere else.",[284,1250,1251],{},"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.",[284,1253,1254,1256],{},[323,1255,1216],{}," structural refactors are ideal when the pattern is clear but the volume is high. Define the rules once, let the agent apply them everywhere.",[280,1258,1260],{"id":1259},"case-3-the-redesign-of-this-site","Case 3: the redesign of this site",[284,1262,1263,1264,1266,1267,1269,1270,403],{},"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 (",[303,1265,718],{}," and ",[303,1268,722],{},"), dark-only theme, translucent borders with ",[303,1271,726],{},[284,1273,1274,1275,1277,1278,1280],{},"The difference with the previous cases is that the project context was entirely mine. I have a well-kept ",[303,1276,732],{},", 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 ",[303,1279,736],{}," is or where it's defined. It already knows.",[284,1282,1283],{},"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.",[284,1285,1286,1288,1289,1291],{},[323,1287,1216],{}," investing in a good ",[303,1290,732],{}," isn't overhead, it's the difference between a useful agent and one that hallucinates.",[280,1293,1295],{"id":1294},"the-playbook","The playbook",[284,1297,1298],{},"After several migrations, this is what I always do:",[317,1300,1301,1307,1313,1319,1328,1334],{},[320,1302,1303,1306],{},[323,1304,1305],{},"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.",[320,1308,1309,1312],{},[323,1310,1311],{},"Atomic PRs per phase."," In the toolchain case, oxlint and tsgo were separate PRs. If something breaks, you know exactly which phase caused it.",[320,1314,1315,1318],{},[323,1316,1317],{},"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.",[320,1320,1321,1327],{},[323,1322,1323,1324,1326],{},"Keep a ",[303,1325,732],{}," up to date."," Stack, conventions, commands, project gotchas. The difference between an agent that understands the repo and one that makes things up.",[320,1329,1330,1333],{},[323,1331,1332],{},"Measure before and after."," Without numbers there's no story to tell, nor argument to pitch to the team.",[320,1335,1336,1339],{},[323,1337,1338],{},"Pair programming, not autopilot."," The agent doesn't replace judgment, it amplifies it. You decide what gets done, it runs and brings the feedback.",[280,1341,1343],{"id":1342},"what-not-to-do","What NOT to do",[357,1345,1346,1352,1358,1364],{},[320,1347,1348,1351],{},[323,1349,1350],{},"Don't ask for everything at once."," \"Migrate the whole repo\" is the worst possible instruction. Split into phases, one at a time.",[320,1353,1354,1357],{},[323,1355,1356],{},"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.",[320,1359,1360,1363],{},[323,1361,1362],{},"Don't let it invent paths or APIs."," If it cites a function you don't recognize, verify it exists before accepting the change.",[320,1365,1366,1369],{},[323,1367,1368],{},"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.",[280,1371,1373],{"id":1372},"closing","Closing",[284,1375,1376],{},"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.",[284,1378,1379],{},"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.",[284,1381,1382],{},"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.",{"title":181,"searchDepth":15,"depth":36,"links":1384},[1385,1386,1387,1388,1393,1394,1395,1396,1397],{"id":877,"depth":15,"text":878},{"id":890,"depth":15,"text":891},{"id":906,"depth":15,"text":907},{"id":939,"depth":15,"text":940,"children":1389},[1390,1391,1392],{"id":963,"depth":36,"text":964},{"id":1047,"depth":36,"text":1048},{"id":1149,"depth":36,"text":1150},{"id":1220,"depth":15,"text":1221},{"id":1259,"depth":15,"text":1260},{"id":1294,"depth":15,"text":1295},{"id":1342,"depth":15,"text":1343},{"id":1372,"depth":15,"text":1373},"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":872,"description":1398},{"loc":1400},"en\u002Fblog\u002Fhow-to-migrate-with-claude-code-and-not-die-trying",[862,863,864],"BW7GisUgzYUjZKxKDnmPzTknqsB9__xrskze_T9Alrw",1777416194700]