Aprende a mantener el código generado mantenible con la regla de la arquitectura aburrida: límites de carpetas claros, nombres consistentes y predeterminados simples que reducen trabajo futuro.

El código generado cambia la rutina diaria. No solo estás construyendo funciones, también guías un sistema que puede crear muchos archivos rápidamente. La velocidad es real, pero las pequeñas inconsistencias se multiplican con rapidez.
La salida generada a menudo parece correcta aislada. Los costes aparecen en el segundo y tercer cambio: no sabes dónde pertenece una pieza, arreglas el mismo comportamiento en dos sitios o evitas tocar un archivo porque no sabes qué más afectará.
La estructura “ingeniosa” se vuelve costosa porque es difícil de predecir. Patrones personalizados, magia oculta y abstracciones pesadas tienen sentido el primer día. En la sexta semana, el siguiente cambio se ralentiza porque tienes que reaprender el truco antes de poder actualizarlo con seguridad. Con la generación asistida por IA, esa inclinación a lo ingenioso también puede confundir generaciones futuras y llevar a lógica duplicada o a nuevas capas apiladas encima.
La arquitectura aburrida es lo opuesto: límites claros, nombres sencillos y predeterminados obvios. No se trata de perfección. Se trata de elegir una estructura que un compañero cansado (o tu versión futura) pueda entender en 30 segundos.
Un objetivo simple: facilitar el próximo cambio, no impresionar. Eso suele significar un lugar claro para cada tipo de código (UI, API, datos, utilidades compartidas), nombres previsibles que reflejen lo que hace un archivo y mínima “magia” como auto-wiring, globals ocultos o metaprogramación.
Ejemplo: si le pides a Koder.ai que añada “invitaciones de equipo”, quieres que coloque la UI en la zona de UI, añada una ruta API en el área de API y guarde los datos de invitación en la capa de datos, sin inventar una nueva carpeta o patrón solo para esa función. Esa consistencia aburrida es lo que mantiene baratos los edits futuros.
El código generado se vuelve caro cuando te da muchas formas de hacer lo mismo. La regla de la arquitectura aburrida es simple: hacer que el próximo cambio sea predecible, aunque la primera versión parezca menos ingeniosa.
Deberías poder responder a estas preguntas rápidamente:
Elige una estructura simple y aplícala en todas partes. Cuando una herramienta (o un compañero) sugiere un patrón sofisticado, la respuesta por defecto es “no” a menos que elimine un dolor real.
Predeterminados prácticos que resisten el paso del tiempo:
Imagina que un desarrollador nuevo abre tu repo y necesita añadir un botón “Cancelar suscripción”. No debería tener que aprender una arquitectura personalizada primero. Debería encontrar un área de feature clara, un componente UI evidente, una única ubicación de cliente API y una única vía de acceso a datos.
Esta regla funciona especialmente bien con herramientas de vibe-coding como Koder.ai: puedes generar rápido, pero sigues guiando la salida hacia los mismos límites aburridos cada vez.
El código generado tiende a crecer rápido. La forma más segura de mantenerlo mantenible es un mapa de carpetas aburrido donde cualquiera pueda adivinar dónde pertenece un cambio.
Un pequeño layout de primer nivel que encaja en muchas apps web:
app/ pantallas, enrutamiento y estado a nivel de páginacomponents/ piezas UI reutilizablesfeatures/ una carpeta por feature (billing, projects, settings)api/ código del cliente API y helpers de peticiónserver/ handlers del backend, servicios y reglas de negocioEsto hace que los límites sean obvios: la UI vive en app/ y components/, las llamadas API en api/ y la lógica del backend en server/.
El acceso a datos también debe ser aburrido. Mantén las consultas SQL y el código de repositorios cerca del backend, no esparcidos por archivos de UI. En un setup Go + PostgreSQL, una regla simple es: los handlers HTTP llaman a servicios, los servicios llaman a repositorios y los repositorios hablan con la base de datos.
Los tipos y utilidades compartidas merecen un hogar claro, pero mantenlo pequeño. Pon tipos cross-cutting en types/ (DTOs, enums, interfaces compartidas) y helpers pequeños en utils/ (formateo de fechas, validadores simples). Si utils/ empieza a sentirse como una segunda app, probablemente el código pertenece al folder de una feature.
Trata las carpetas generadas como reemplazables.
generated/ (o gen/) y evita editarla directamente.features/ o server/ para que la regeneración no la sobrescriba.Ejemplo: si Koder.ai genera un cliente API, guárdalo bajo generated/api/, luego escribe wrappers delgados en api/ donde puedas añadir reintentos, logging o mensajes de error más claros sin tocar los archivos generados.
El código generado es fácil de crear y fácil de acumular. El nombrado es lo que lo mantiene legible un mes después.
Elige un estilo de nombres y no lo mezcles:
kebab-case (user-profile-card.tsx, billing-settings)PascalCase (UserProfileCard)camelCase (getUserProfile)SCREAMING_SNAKE_CASE (MAX_RETRY_COUNT)Nombra por rol, no por cómo funciona hoy. user-repository.ts es un rol. postgres-user-repository.ts es un detalle de implementación que puede cambiar. Usa sufijos de implementación solo cuando realmente tengas múltiples implementaciones.
Evita cajones de basura como misc, helpers o un gigante utils. Si una función solo se usa en una feature, mantenla cerca de esa feature. Si es compartida, que el nombre describa la capacidad (date-format.ts, money-format.ts, id-generator.ts) y mantén el módulo pequeño.
Cuando rutas, handlers y componentes siguen un patrón, puedes encontrar cosas sin buscar:
routes/users.ts con paths como /users/:userIdhandlers/users.get.ts, handlers/users.update.tsservices/user-profile-service.tsrepositories/user-repository.tscomponents/user/UserProfileCard.tsxSi usas Koder.ai (o cualquier generador), incluye estas reglas en el prompt y manténlas consistentes durante las ediciones. La idea es previsibilidad: si puedes adivinar el nombre del archivo, los cambios futuros siguen siendo más baratos.
El código generado puede parecer impresionante el primer día y doloroso al día treinta. Elige predeterminados que hagan el código obvio, aunque sea un poco repetitivo.
Empieza por reducir la magia. Omite carga dinámica, trucos de estilo reflexión y auto-wiring a menos que haya una necesidad medida. Estas características ocultan de dónde vienen las cosas, lo que hace que depurar y refactorizar sea más lento.
Prefiere imports explícitos y dependencias claras. Si un archivo necesita algo, impórtalo directamente. Si los módulos necesitan wiring, hazlo en un solo lugar visible (por ejemplo, un archivo de composición). Un lector no debería tener que adivinar qué se ejecuta primero.
Mantén la configuración aburrida y centralizada. Pon variables de entorno, flags de features y ajustes de app en un módulo con un esquema de nombres único. No disperses la configuración por archivos al azar porque fue conveniente.
Reglas prácticas que mantienen la consistencia del equipo:
El manejo de errores es donde la ingeniosidad más duele. Escoge un patrón y úsalo en todas partes: devuelve errores estructurados desde la capa de datos, mapea esos errores a respuestas HTTP en un solo lugar y tradúcelos a mensajes para el usuario en la frontera de la UI. No lances tres tipos de error distintos según el archivo.
Si generas una app con Koder.ai, pide estos predeterminados desde el inicio: wiring de módulos explícito, config centralizada y un patrón único de errores.
Líneas claras entre UI, API y datos mantienen los cambios contenidos. La mayoría de los bugs misteriosos ocurren cuando una capa empieza a hacer el trabajo de otra.
Trata la UI (a menudo React) como un lugar para renderizar pantallas y gestionar estado solo de UI: qué pestaña está abierta, errores de formulario, spinners de carga y manejo básico de entradas.
Mantén el estado del servidor separado: listas obtenidas, perfiles cacheados y cualquier cosa que deba coincidir con el backend. Cuando los componentes UI empiezan a calcular totales, validar reglas complejas o decidir permisos, la lógica se reparte por las pantallas y se vuelve costosa de cambiar.
Mantén la capa API predecible. Debe traducir peticiones HTTP en llamadas al código de negocio y luego traducir resultados a formas request/response estables. Evita enviar modelos de base de datos directamente por la red. Respuestas estables te permiten refactorizar internals sin romper la UI.
Un camino simple que funciona bien:
Pon SQL (o lógica ORM) detrás de una frontera de repositorio para que el resto de la app no “sepa” cómo se almacenan los datos. En Go + PostgreSQL, eso normalmente significa repositorios como UserRepo o InvoiceRepo con métodos pequeños y claros (GetByID, ListByAccount, Save).
Ejemplo concreto: añadir códigos de descuento. La UI renderiza un campo y muestra el precio actualizado. La API acepta code y devuelve {total, discount}. El service decide si el código es válido y cómo se acumulan los descuentos. El repositorio recupera y persiste las filas necesarias.
Las apps generadas pueden parecer “listas” rápido, pero la estructura es lo que mantiene baratos los cambios más tarde. Decide reglas aburridas primero, luego genera solo el código suficiente para probarlas.
Empieza con un breve pase de planificación. Si usas Koder.ai, el Modo Planning es un buen lugar para escribir un mapa de carpetas y unas pocas reglas de nombres antes de generar nada.
Luego sigue esta secuencia:
ui/, api/, data/, features/) y un puñado de reglas de nombres.CONVENTIONS.md y trátalo como un contrato. Una vez que la base de código crezca, cambiar nombres y patrones de carpetas se vuelve caro.Chequeo de realidad: si una persona nueva no puede adivinar dónde poner “editar contacto” sin preguntar, la arquitectura aún no es lo bastante aburrida.
Imagina un CRM simple: una página lista de contactos y un formulario de edición de contacto. Construyes la primera versión rápido, luego una semana después necesitas añadir “etiquetas” a los contactos.
Trata la app como tres cajas aburridas: UI, API y datos. Cada caja obtiene límites claros y nombres literales para que el cambio de “etiquetas” siga siendo pequeño.
Un layout limpio podría verse así:
web/src/pages/ContactsPage.tsx y web/src/components/ContactForm.tsxserver/internal/http/contacts_handlers.goserver/internal/service/contacts_service.goserver/internal/repo/contacts_repo.goserver/migrations/Ahora “etiquetas” se vuelve predecible. Actualiza el esquema (nueva tabla contact_tags o una columna tags), luego toca una capa a la vez: el repo lee/escribe etiquetas, el service valida, el handler expone el campo y la UI lo renderiza y edita. No metas SQL en handlers ni reglas de negocio en componentes React.
Si el producto luego pide “filtrar por etiqueta”, trabajarás principalmente en ContactsPage.tsx (estado UI y query params) y en el handler HTTP (parsing de la petición), mientras el repo maneja la consulta.
Para tests y fixtures, mantenlos pequeños y cerca del código:
server/internal/service/contacts_service_test.go para reglas como “los nombres de etiquetas deben ser únicos por contacto”server/internal/repo/testdata/ para fixtures mínimosweb/src/components/__tests__/ContactForm.test.tsx para el comportamiento del formularioSi generas esto con Koder.ai, la misma regla aplica después de exportar: mantén carpetas aburridas, nombres literales y las ediciones dejarán de sentirse como arqueología.
El código generado puede parecer limpio el primer día y seguir siendo caro más adelante. El culpable habitual no es “mal código”, es la inconsistencia.
Un hábito caro es dejar que el generador invente estructura cada vez. Una feature llega con sus propias carpetas, estilo de nombres y funciones helper, y acabas con tres formas de hacer lo mismo. Elige un patrón, escríbelo y trata cualquier nuevo patrón como un cambio consciente, no como el default.
Otra trampa es mezclar capas. Cuando un componente UI habla con la base de datos, o un handler API construye SQL, los cambios pequeños se convierten en ediciones riesgosas por toda la app. Mantén la frontera: UI llama a una API, la API llama a un service y el service llama al acceso a datos.
Abusar de abstracciones genéricas demasiado pronto también añade coste. Un “BaseService” universal o un framework de Repository se siente elegante, pero las abstracciones tempranas son conjeturas. Cuando la realidad cambia, luchas contra tu propio framework en lugar de enviar funcionalidad.
Renombrar y reorganizar constantemente es una forma más silenciosa de deuda. Si los archivos se mueven cada semana, la gente deja de confiar en el layout y los fixes rápidos caen en lugares aleatorios. Estabiliza el mapa de carpetas primero y luego refactoriza en trozos planificados.
Finalmente, ten cuidado con el “código de plataforma” que no tiene usuario real. Bibliotecas compartidas y herramientas caseras solo rinden cuando tienes necesidades repetidas y demostradas. Hasta entonces, mantén los defaults directos.
Si alguien nuevo abre el repo, debería poder responder una pregunta rápido: “¿Dónde añado esto?”
Entrega el proyecto a un compañero (o a tu yo futuro) y pídele que añada una pequeña feature, como “añadir un campo al formulario de signup”. Si no puede encontrar el lugar correcto rápidamente, la estructura no está haciendo su trabajo.
Comprueba tres hogares claros:
Si tu plataforma lo soporta, mantén un camino de rollback. Snapshots y rollback son especialmente útiles cuando experimentas con la estructura y quieres una forma segura de volver atrás.
La mantenibilidad mejora más rápido cuando dejas de debatir el estilo y empiezas a tomar unas pocas decisiones que se mantengan.
Escribe un pequeño conjunto de convenciones que eliminen las dudas diarias: dónde van los archivos, cómo se nombran y cómo se manejan errores y config. Manténlo lo suficientemente corto como para leerlo en un minuto.
Luego haz una pasada de limpieza para alinear el código con esas reglas y deja de reorganizar semanalmente. Reordenar con frecuencia hace que el siguiente cambio sea más lento, incluso si el código luce mejor.
Si construyes con Koder.ai (koder.ai), ayuda guardar estas convenciones como prompt inicial para que cada nueva generación caiga en la misma estructura. La herramienta puede moverse rápido, pero los límites aburridos son lo que mantienen el código fácil de cambiar.