[{"data":1,"prerenderedAt":277},["ShallowReactive",2],{"search-es":3},[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,272],{"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 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\". 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#las-herramientas-que-uso-en-una-migración","Las herramientas que uso en una migración",[6],"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. Un CLAUDE.md específico al contexto actual. Convenciones, comandos, gotchas y la migración en curso. No copies el package.json (Claude Code ya puede leerlo), escribe lo que no se infiere de las deps. Ejemplo mínimo: # 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. \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. Sub-agents para paralelizar. Mientras el agente principal sigue editando, lanza un sub-agent que corra pnpm typecheck o pnpm test 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. Hooks para validación automática. Un hook PostToolUse que corra pnpm lint después de cada 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.",{"id":68,"title":69,"titles":70,"content":71,"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":73,"title":74,"titles":75,"content":76,"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. 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);}",{"id":78,"title":79,"titles":80,"content":81,"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":83,"title":84,"titles":85,"content":86,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#qué-es-un-design-system","¿Qué es un Design System?",[79],"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":88,"title":89,"titles":90,"content":91,"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",[79],"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":93,"title":94,"titles":95,"content":96,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-versus-realidad","Expectativa versus realidad",[79],"¿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":98,"title":99,"titles":100,"content":101,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-1","Expectativa 1:",[79,94],"“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":103,"title":104,"titles":105,"content":106,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-2","Expectativa 2:",[79,94],"“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":108,"title":109,"titles":110,"content":111,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-3","Expectativa 3:",[79,94],"“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":113,"title":114,"titles":115,"content":116,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-4","Expectativa 4:",[79,94],"“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":118,"title":119,"titles":120,"content":121,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-5","Expectativa 5:",[79,94],"“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":123,"title":124,"titles":125,"content":126,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-6","Expectativa 6:",[79,94],"“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":128,"title":129,"titles":130,"content":131,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#expectativa-7","Expectativa 7:",[79,94],"“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":133,"title":134,"titles":135,"content":136,"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",[79],"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":138,"title":139,"titles":140,"content":141,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#diseño","Diseño",[79,134],"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":143,"title":144,"titles":145,"content":146,"level":36},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#desarrollo","Desarrollo",[79,134],"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":148,"title":149,"titles":150,"content":151,"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",[79,134],"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":153,"title":154,"titles":155,"content":156,"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",[79],"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":158,"title":159,"titles":160,"content":161,"level":15},"\u002Fes\u002Fblog\u002Flo-que-aprendi-construyendo-design-systems-y-que-haria-diferente#lecciones-aprendidas-y-recomendaciones","Lecciones aprendidas y recomendaciones",[79],"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":163,"title":164,"titles":165,"content":166,"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":168,"title":169,"titles":170,"content":171,"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",[164],"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":173,"title":174,"titles":175,"content":176,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#qué-es-oxlint-tailwindcss","Qué es oxlint-tailwindcss",[164],"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":178,"title":179,"titles":180,"content":181,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#funciona-sin-configuración","Funciona sin configuración",[164,174],"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":183,"title":184,"titles":185,"content":186,"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",[164],"",{"id":188,"title":189,"titles":190,"content":191,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#correctness-evitar-errores-reales","Correctness — Evitar errores reales",[164,184],"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":193,"title":194,"titles":195,"content":196,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#style-consistencia-del-equipo","Style — Consistencia del equipo",[164,184],"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":198,"title":199,"titles":200,"content":201,"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",[164,184],"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":203,"title":204,"titles":205,"content":206,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#restrictions-reglas-del-design-system","Restrictions — Reglas del design system",[164,184],"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":208,"title":209,"titles":210,"content":211,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#extracción-de-clases","Extracción de clases",[164],"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":213,"title":214,"titles":215,"content":216,"level":36},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#detección-customizable","Detección customizable",[164,209],"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":218,"title":219,"titles":220,"content":221,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#la-historia-detrás","La historia detrás",[164],"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":223,"title":224,"titles":225,"content":226,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#cómo-empezar","Cómo empezar",[164],"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":228,"title":229,"titles":230,"content":231,"level":15},"\u002Fes\u002Fblog\u002Foxlint-tailwindcss-el-plugin-de-linting-que-tailwindcss-necesitaba#pruébalo","Pruébalo",[164],"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":233,"title":234,"titles":235,"content":236,"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":238,"title":239,"titles":240,"content":241,"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",[234],"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":243,"title":244,"titles":245,"content":246,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#la-analogía-que-lo-explica","La analogía que lo explica",[234],"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":248,"title":249,"titles":250,"content":251,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#qué-ganas-con-este-enfoque","Qué ganas con este enfoque",[234],"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":253,"title":254,"titles":255,"content":256,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#aplicando-screaming-architecture","Aplicando Screaming Architecture",[234],"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":258,"title":259,"titles":260,"content":261,"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",[234],"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":263,"title":264,"titles":265,"content":266,"level":15},"\u002Fes\u002Fblog\u002Fscreaming-architecture-la-clave-para-un-frontend-escalable#convivencia-con-frameworks","Convivencia con frameworks",[234],"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":268,"title":269,"titles":270,"content":271,"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",[234],"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":273,"title":274,"titles":275,"content":276,"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",[234],"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}",1777436645539]