Arquitectura de software: las piezas de una app de verdad
Qué piezas necesita tu app, cómo decidir la arquitectura según tu equipo y tu tráfico, qué hacer cuando algo falla y qué preguntarle a la IA antes de pedirle código.
Descargar el PDF270 KBEn esta página · 9 partes
Si le pides a una IA «hazme una app», te entrega código que funciona en tu computador. Pero el problema aparece con el primer cliente real, cuando algo se cae y no sabes dónde mirar, porque una app no es solo código, es un sistema de piezas que conversan entre sí.
Lo que sigue sale de la base de conocimiento de devground, donde tengo escritas las decisiones de arquitectura que uso en mis proyectos. Aquí está la versión para alguien que recién empieza.
Una app es un sistema
Piénsalo como un restaurante. Cada parte tiene un trabajo distinto y todas tienen que coordinarse.
| Pieza | En el restaurante | Qué hace |
|---|---|---|
| Interfaz | El salón | Lo que el usuario ve y toca: pantallas, formularios, botones. Ahí se hace el pedido. |
| Servidor | La cocina | Recibe los pedidos, aplica las reglas del negocio y decide qué responder. Nunca confía ciegamente en lo que llega de la interfaz. |
| Base de datos | La despensa | Guarda todo lo que tiene que sobrevivir: usuarios, pedidos, pagos, historial. |
| Servicios externos | Los repartidores | Hablan con el mundo de afuera: correos, pagos, mensajes, archivos, inteligencia artificial. |
Cómo decidir la arquitectura
Ninguna arquitectura es buena o mala por sí sola. Lo que decide cuál te conviene son tres cosas de tu proyecto, no lo que está de moda:
- Cuántas personas la van a mantener. Si eres tú solo, o tú y la IA, todo lo que agregues lo vas a operar tú.
- Qué tan enredadas están sus partes. Si un pago, un pedido y el inventario tienen que cambiar juntos en la misma operación, separarlos te complica la vida.
- Cómo llega el tráfico. Parejo todo el día, en picos de vez en cuando, o mucho más en una parte que en el resto.
Por dónde empezar: un monolito modular
Si metes todo en un solo archivo, cada cambio rompe otra cosa, pero si separas todo en microservicios desde el primer día terminas manteniendo muchos servidores para pocos clientes.
Para la mayoría de los proyectos, lo sensato es un monolito modular: una sola aplicación que se despliega de una vez, pero ordenada por dentro en módulos que no se mezclan (usuarios, pagos, notificaciones). Martin Fowler, que escribió bastante sobre esto, recomienda justamente empezar así y separar después.
Dentro de cada módulo conviene ordenar el código en tres capas: la que recibe las peticiones (rutas, formularios), la que tiene las reglas del negocio (quién puede comprar, cuánto se cobra) y la que guarda y lee los datos. Básicamente, las reglas no deberían saber si los datos vienen de Postgres o de otro lado. Con eso solo ya tienes la mayor parte del orden que dan las arquitecturas más sofisticadas, por bastante menos trabajo.
Cuándo separar una parte
Separa un módulo en un servicio aparte solo cuando tengas una razón que puedas medir:
- recibe mucho más tráfico que el resto (la referencia que uso es unas diez veces más) y necesita escalar por su cuenta;
- lo trabaja un equipo distinto que necesita desplegar sin coordinarse con los demás;
- cambia a un ritmo muy distinto del resto;
- necesita una tecnología que no encaja con lo demás.
Lo que todavía no necesitas
Hay patrones que aparecen en todos los videos de arquitectura y que, al empezar, te frenan más de lo que te ayudan.
| Patrón | Para qué sirve | Cuándo todavía no |
|---|---|---|
| Microservicios | Que equipos grandes desplieguen partes por separado | Si son menos de cinco personas o las partes dependen mucho entre sí |
| Clean o hexagonal | Aislar las reglas del negocio de la base de datos y del framework | En un MVP, o si los requisitos cambian cada semana |
| CQRS | Separar cómo se escribe de cómo se lee, para volúmenes enormes | Si tu app guarda y muestra datos de forma normal, que es lo que hacen casi todas |
Si la IA te propone alguno de estos para tu primera versión, pregúntale qué problema concreto resuelve en tu caso. Si no te da uno con números, quédate con el monolito modular.
¿Serverless o servidor propio?
Ojo, serverless no es una arquitectura, es una forma de desplegar. Puedes tener un monolito en serverless o microservicios en un servidor.
Serverless (funciones que se encienden solo cuando llega una petición) conviene cuando el tráfico llega en picos o es esporádico: pagas por uso y no administras máquinas. A cambio, cada arranque en frío agrega una espera, y con tráfico constante sale caro. El equipo de Amazon Prime Video contó en 2023 que movió un sistema de monitoreo de funciones serverless a un servicio en contenedores y que el costo de esa parte bajó en un 90 %.
Servidor propio conviene cuando el tráfico es constante y alto, cuando la latencia es crítica o cuando necesitas conexiones persistentes, como un chat en vivo. Lo normal es combinar: servidor para el grueso y funciones para webhooks y tareas sueltas.
Cada función se diseña por cómo se usa
Antes de pensar en herramientas, la pregunta es cómo van a usar las personas los datos que guardas. Y la respuesta casi nunca es la misma para toda la app: en Netflix, ver un capítulo es casi pura lectura (millones de personas leen el mismo archivo), pero ir guardando en qué minuto quedaste es casi pura escritura.
Entonces, por cada función importante, anota si se lee mucho o se escribe mucho, y cuáles son las consultas que más se van a repetir. Con eso se elige la base de datos, y no al revés. Para pagos, pedidos y todo lo que tiene que cuadrar, una base relacional como Postgres, que para la mayoría de las apps nuevas alcanza para todo al principio.
Cuando una parte crece, las herramientas cambian según el caso: caché y réplicas si se lee mucho, colas y trabajadores si se escribe mucho. Eso lo explico con detalle en la guía Arquitectura pro con Claude Code.
Algo va a fallar
Los servicios externos se caen, y casi nunca de golpe: se ponen lentos. Una llamada que se queda esperando 30 segundos es peor que una que falla en un segundo, porque mientras espera ocupa recursos, y si llegan muchas así se cae tu app entera aunque el problema sea de otro.
Por eso hay tres cosas que se deciden desde el primer día, no cuando ya pasó:
- Toda llamada a un servicio externo tiene un tiempo límite. Si el usuario está esperando, entre uno y cinco segundos. Si es una tarea de fondo, puede ser más. Lo que no puede pasar es que espere para siempre, que es lo que hacen muchas librerías si no les dices nada.
- Los reintentos esperan cada vez más. Un segundo, después dos, después cuatro, y como mucho tres intentos si hay alguien esperando. Reintentar sin pausa contra un servicio caído solo lo hunde más.
- Si tarda más de un segundo, va a una cola. Enviar correos, generar un PDF o llamar a una IA no debería pasar mientras el usuario espera. La app responde al instante y un proceso aparte resuelve la tarea, con reintentos si algo falla.
Dónde vive cada valor
Toda app acumula valores que alguien va a querer cambiar: claves, proveedores, límites, textos, precios. Si no decides dónde va cada uno, terminan todos en el mismo lugar, y cualquiera de los dos extremos sale mal.
La pregunta que sirve no es «¿lo quiero cambiar sin hacer deploy?», porque casi todo es más cómodo sin deploy. La pregunta es quién debería poder cambiarlo y cuánto daño hace si cambia mal.
| Dónde vive | Qué guarda | Ejemplos |
|---|---|---|
| Código | Cómo funciona el sistema | Las reglas del negocio, la integración con un proveedor |
| Variables de entorno | Con qué infraestructura corre | Claves de API, la dirección de la base de datos, qué proveedor de pagos usas |
| Feature flags | Qué función nueva está encendida y para quién | Una pantalla nueva solo para el 10 % de los usuarios |
| Configuración en la base de datos | Cómo opera el negocio hoy | Precios, horarios, límites, textos de los mensajes |
| Datos | Lo que pasó | Usuarios, pedidos, pagos |
Lo que más daño puede hacer, como cambiar el proveedor de pagos, va en código o en variables de entorno y pasa por un deploy, nunca por un botón. Lo que cambia seguido y hace poco daño, como un texto, va en la base de datos. Y los secretos nunca van en el código ni en el repositorio: si una clave se filtra, la rotas de inmediato, porque borrar el commit no alcanza.
A mí me pasa con este mismo sitio. Hoy, para publicar una guía nueva tengo que hacer un deploy, y una guía es contenido, no estructura. Por eso la tengo anotada para pasarla a la base de datos.
Un stack para empezar
No es el único posible, pero cubre las cuatro piezas con poca configuración y te deja crecer sin rehacer todo.
| Herramienta | Pieza | Para qué la usas |
|---|---|---|
| Next.js | Interfaz + servidor | Pantallas y lógica del servidor en una sola base de código. |
| Supabase | Base de datos + usuarios | Postgres con registro, inicio de sesión y permisos por fila incluidos. |
| Resend | Servicio externo | Correos transaccionales: bienvenida, recuperar contraseña, avisos. |
| Railway | Procesos | Lo que tiene que correr solo: tareas programadas, colas y procesos largos. |
Escribe lo que decidiste
Cada decisión que no sea obvia (qué base de datos, por qué un monolito, dónde va un valor) se escribe en un archivo corto dentro del proyecto, con el contexto, lo que decidiste, lo que descartaste y lo que te cuesta. Se llama registro de decisión de arquitectura, o ADR.
Parece burocracia, pero es lo único que evita que en seis meses, tú o la IA, deshagan una decisión sin saber por qué se tomó. La IA no se acuerda de la conversación de hoy, pero sí lee los archivos del proyecto.
Qué preguntarle a la IA antes de empezar
La IA construye bien cada pieza, pero decidir cuáles van y cómo se conectan te toca a ti. Copia este prompt, completa la primera línea y pídele el plano antes que el código.
Quiero construir esta app: [qué hace, para quién y qué problema resuelve]. La vamos a mantener [cuántas personas].
Antes de escribir código, ayúdame a diseñar su arquitectura. Responde:
- Qué piezas necesita (interfaz, servidor, base de datos, servicios externos) y por qué cada una.
- Qué módulos tiene y cuáles dependen mucho entre sí.
- Por cada función importante: si se lee mucho o se escribe mucho, y cuáles son las consultas que más se van a repetir.
- Qué tipo de base de datos conviene según esas consultas.
- Cómo ordeno los módulos dentro de un monolito modular, con sus capas.
- Qué servicios externos uso, qué tiempo límite y qué reintentos lleva cada llamada, y qué pasa si uno se cae.
- Qué tareas pueden tardar más de un segundo y deberían ir a una cola.
- Dónde vive cada valor configurable: código, variables de entorno, feature flags o base de datos, y por qué.
- Dónde conviene alojar cada pieza para empezar (serverless o servidor) y por qué.
- Qué riesgos ves y qué dejarías para una segunda etapa.
Si me propones microservicios, CQRS o arquitectura hexagonal, dime qué problema concreto resuelven en mi caso. No escribas código todavía. Muéstrame primero el plano y escribe las decisiones como registros de decisión en docs/adr/.
Checklist antes del primer cliente
- Las piezas y su función están escritas en un documento.
- El código está ordenado por módulos, y cada módulo separa lo que recibe, las reglas y los datos.
- Los permisos se validan en el servidor, no solo en la interfaz.
- Ningún secreto está en el código ni en el repositorio.
- Cada llamada a un servicio externo tiene un tiempo límite.
- Las tareas lentas pasan por una cola con reintentos.
- Los errores quedan registrados en algún lugar que revisas.
- Hay un entorno de prueba separado de producción.
- La base de datos tiene respaldos automáticos.
- Las decisiones importantes están escritas en el proyecto.
- Sabes cuánto cuesta cada pieza con tu tráfico actual.
Si quieres ver el razonamiento completo detrás de cada punto, está en la base de conocimiento de devground, con un registro por decisión.