Mantén el código generado mantenible: la regla de la arquitectura aburrida
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.

Por qué la mantenibilidad es más difícil con código generado
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.
La regla de la arquitectura aburrida
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:
- ¿Dónde vive esta función?
- ¿Dónde pongo un archivo nuevo?
- ¿Cómo debo nombrarlo?
- ¿Cuál es el camino más simple desde la UI hasta los datos?
La regla
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:
- Una responsabilidad por carpeta y por archivo. Si un archivo tiene dos motivos para cambiar, sepáralo.
- Previsibilidad antes que flexibilidad. Lo mismo va en el mismo lugar, siempre.
- Prefiere el enfoque estándar de tu stack sobre mini-frameworks personalizados.
- Haz que el camino feliz sea obvio. Los nuevos colaboradores deberían adivinar la ubicación correcta sin preguntar.
- Rechaza la “magia”. Evita comportamientos ocultos, trucos basados en reflexión y abstracciones ingeniosas.
Una prueba mental rápida
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.
Límites de carpetas simples que escalan
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 negocio
Esto 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.
Generado vs escrito a mano
Trata las carpetas generadas como reemplazables.
- Pon la salida generada en
generated/(ogen/) y evita editarla directamente. - Mantén la lógica personalizada en
features/oserver/para que la regeneración no la sobrescriba. - Si debes parchear un comportamiento generado, envuélvelo (archivo adaptador) en lugar de modificar la fuente.
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.
Convenciones de nombres que previenen confusión
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:
- Carpetas y archivos:
kebab-case(user-profile-card.tsx,billing-settings) - Componentes React:
PascalCase(UserProfileCard) - Funciones y variables:
camelCase(getUserProfile) - Constantes:
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.
Convenciones que hacen la navegación rápida
Cuando rutas, handlers y componentes siguen un patrón, puedes encontrar cosas sin buscar:
- Rutas:
routes/users.tscon paths como/users/:userId - Handlers (HTTP):
handlers/users.get.ts,handlers/users.update.ts - Servicios (reglas de negocio):
services/user-profile-service.ts - Acceso a datos:
repositories/user-repository.ts - Componentes UI:
components/user/UserProfileCard.tsx
Si 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.
Predeterminados sin trucos (reglas prácticas)
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:
- Escoge explícito sobre implícito (imports, routing, DI, efectos secundarios).
- Si ahorra 10 líneas pero añade un nuevo concepto, evítalo.
- Mantén una sola forma de hacer las cosas (una herramienta de logging, un módulo de config).
- Prefiere flujo de datos simple sobre observadores ocultos o cadenas de eventos.
- Al depurar, borra primero el código ingenioso.
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ímites entre UI, API y datos
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.
UI: mostrar estado, recopilar entrada
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.
API: formas delgadas y estables
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:
- La UI llama a un cliente API con objetos request/response tipados.
- Los handlers API validan la entrada y llaman a un método del service.
- Los services contienen reglas de negocio y flujos de trabajo.
- Los repositorios ocultan las consultas a la base de datos detrás de métodos pequeños.
Datos: oculta consultas detrás de repositorios
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.
Paso a paso: configurar código generado mantenible
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.
Un flujo de configuración práctico
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:
- Define el mapa y las reglas por escrito. Elige límites (por ejemplo:
ui/,api/,data/,features/) y un puñado de reglas de nombres. - Genera una slice vertical delgada. Elige una pequeña feature que toque UI, API y almacenamiento, como “crear un contacto”. El objetivo es la ruta end-to-end, no la completitud.
- Refactoriza inmediatamente para ajustar los límites. Mueve código a las carpetas planeadas, renombra archivos poco claros y borra duplicados. Separa funciones “que lo hacen todo” en UI, handler y acceso a datos.
- Añade una segunda feature para probar la forma. Elige algo similar, como “listar contactos”. Si te ves forzado a romper tus reglas, probablemente los límites están mal.
- Bloquea las convenciones temprano. Añade un corto
CONVENTIONS.mdy 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.
Escenario de ejemplo: añadir una feature sin crear un desorden
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.tsxyweb/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.gopara reglas como “los nombres de etiquetas deben ser únicos por contacto”server/internal/repo/testdata/para fixtures mínimosweb/src/components/__tests__/ContactForm.test.tsxpara el comportamiento del formulario
Si 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.
Errores comunes que encarecen los cambios futuros
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.
Un checklist rápido antes de enviar
Si alguien nuevo abre el repo, debería poder responder una pregunta rápido: “¿Dónde añado esto?”
Prueba de navegación de 2 minutos
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:
- Los cambios de UI viven en un lugar obvio.
- Las rutas/handlers API son fáciles de encontrar desde la UI.
- El modelo de datos y los cambios de base de datos tienen una ubicación clara.
Reglas que vale la pena hacer cumplir en las reviews
- UI, API y datos tienen cada uno un hogar, y las excepciones son raras.
- Los nombres se leen como etiquetas, no como acertijos.
- Se marcan las rupturas de capas (por ejemplo, UI accediendo a la base de datos).
- Los atajos ingeniosos se rechazan por defecto.
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.
Próximos pasos: manténlo aburrido, manténlo barato
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.
Preguntas frecuentes
¿Qué significa «arquitectura aburrida» para el código generado?
Usa un mapa de carpetas sencillo, nombres literales y una única ruta desde la interfaz hasta la API y los datos. El objetivo es que cualquiera pueda ubicar correctamente un cambio sin tener que aprender antes un patrón personalizado.
¿Por qué es más difícil mantener código generado por IA?
El código generado puede añadir archivos rápidamente, por lo que pequeñas inconsistencias se propagan enseguida. Una estructura predecible evita que el segundo y el tercer cambio se conviertan en una búsqueda entre lógica duplicada y carpetas desconocidas.
¿Qué carpetas debería usar una aplicación web generada?
Un punto de partida práctico es app/ para pantallas y rutas, components/ para la interfaz reutilizable, features/ para código específico de cada funcionalidad, api/ para solicitudes del cliente y server/ para lógica de backend. Mantén el mismo límite para cada funcionalidad.
¿Cómo debo nombrar los archivos y el código generados?
Usa un estilo de forma consistente: kebab-case para archivos y carpetas, PascalCase para componentes de React, camelCase para funciones y SCREAMING_SNAKE_CASE para constantes. Nombra los archivos según su función, como user-repository.ts o money-format.ts.
¿Cómo separo el código de interfaz, API y base de datos?
Mantén el código de interfaz centrado en el renderizado y la entrada de datos, deja que los controladores de API validen las solicitudes y devuelvan respuestas estables, coloca las reglas de negocio en servicios y conserva las llamadas SQL u ORM en repositorios. Cada capa debe llamar a la siguiente en lugar de hacer su trabajo.
¿Debo editar directamente los archivos generados?
Coloca la salida generada en una carpeta dedicada, generated/ o gen/, y evita editarla directamente. Escribe envoltorios o adaptadores personalizados fuera de esa carpeta para poder regenerar el código sin perder tus cambios.
¿Qué patrones «ingeniosos» debería evitar?
Evita los globales ocultos, la conexión automática, la carga dinámica y las abstracciones que introducen un concepto nuevo solo para ahorrar unas líneas. Prefiere importaciones directas, configuración centralizada y una configuración de módulos visible.
¿Cómo configuro un proyecto generado que sea fácil de mantener?
Empieza con una funcionalidad pequeña de extremo a extremo, como crear un contacto. Genérala, mueve todo lo que no esté claro a las carpetas previstas, elimina duplicados y luego crea una segunda funcionalidad similar para comprobar si la estructura sigue resultando natural.
¿Cómo puedo mantener uniforme la salida de Koder.ai?
Da al generador reglas explícitas antes de cada cambio: el mapa de carpetas, el estilo de nombres, los límites entre capas y el patrón de manejo de errores. Con Koder.ai, puedes guardar estas reglas en un prompt de planificación para que el trabajo nuevo siga la misma estructura.
¿Cómo compruebo si es fácil modificar mi proyecto?
Pide a alguien que haga un cambio pequeño, como añadir un campo de registro, sin indicaciones. Si puede encontrar rápidamente la interfaz, el controlador de API, el servicio y la ubicación de los datos, es fácil trabajar con el proyecto. Si no, simplifica el mapa antes de que crezca más.