Saltar al contenido
< samuelsantana.dev />
Volver al Blog

Arquitectura de Carpetas a Escala: Cómo organizar proyectos React, Angular y Node.js con SOLID y Clean Architecture

Samuel Santana
Publicado el 24 de julho de 2026(Editado el 25 de julho de 2026)
AngularReactNodeJS

Si alguna vez necesitaste agregar un simple campo en un formulario y tuviste que navegar por cinco carpetas en la raíz del proyecto (/components, /services, /types, /hooks, /utils), fuiste víctima de la arquitectura por tipo de archivo.

Al inicio de un software, agrupar archivos por lo que son (componentes con componentes, servicios con servicios) parece limpio e intuitivo. Pero a medida que la aplicación escala a cientos de pantallas y reglas de negocio, este enfoque se transforma en una pesadilla cognitiva. El acoplamiento se dispara, la cohesión desaparece y eliminar una funcionalidad antigua sin romper el sistema se convierte en una misión imposible.

Hoy en día, el consenso arquitectónico entre equipos de alto rendimiento en React, Angular y Node.js gira en torno a Vertical Slices (Cortes Verticales) y la Organización por Dominio/Feature.

En este artículo, analizaremos a fondo cómo estructurar carpetas para aplicaciones modernas en el frontend y el backend, demostrando cómo los principios de SOLID, DRY y Separation of Concerns (SoC) moldean esta arquitectura en la práctica.


1. El Principio de Oro: Colocation y Cohesión

Antes de ver frameworks específicos, necesitamos entender la regla fundamental que rige las arquitecturas modernas: el código que cambia junto debe vivir junto.

Esto es lo que llamamos Colocation (Colocación). Si el componente de pantalla de un carrito de compras necesita un hook para calcular el envío, una interfaz TypeScript específica y una llamada a la API, todos esos archivos deben vivir dentro de la carpeta de la feature del Carrito, y no dispersos globalmente por el proyecto.

El Test de Ácido de la Arquitectura: Si tu Product Owner te pidiera eliminar la funcionalidad de "Lista de Deseos" hoy, ¿podrías simplemente borrar la carpeta /features/wishlist y hacer que el proyecto compile sin errores? Si la respuesta es no, tu acoplamiento es demasiado alto.


2. La Fundación Teórica: SOLID y DRY en el Sistema de Archivos

Una buena estructura de carpetas es la manifestación física de los principios de ingeniería de software en tu repositorio. Veamos cómo se aplican a nuestra organización:

El SOLID en la Práctica de las Carpetas

  • S — Single Responsibility Principle (SRP): En el modelo legado, un archivo UserController o GlobalServices acumulaba reglas de negocio, validación HTTP y consultas a bases de datos. En nuestra estructura, aislamos las responsabilidades. En Node.js, un archivo en use-cases/create-order.usecase.ts tiene solo una razón para cambiar: la regla de negocio del pedido. Ignora el protocolo HTTP (responsabilidad del controller) y la base de datos (responsabilidad del repository).
  • O — Open/Closed Principle (OCP): Tu sistema debe estar abierto a la extensión, pero cerrado a la modificación. Si necesitas agregar un nuevo método de pago en el frontend, no editas un componente gigante existente; simplemente creas una nueva carpeta dentro de features/billing/components/ (ej: /pix-checkout/) y lo exportas por el contrato de la feature. El código antiguo permanece intocable.
  • L & I — Liskov Substitution & Interface Segregation: Evitamos la carpeta global /types con un archivo index.ts de 3.000 líneas. Al colocar la carpeta types/ o dto/ dentro de cada módulo específico, garantizamos que un componente consuma únicamente los contratos estrictamente necesarios para su funcionamiento, sin depender de tipados de otras áreas del sistema.
  • D — Dependency Inversion Principle (DIP): Nuestras reglas de negocio dependen de abstracciones (interfaces), no de implementaciones concretas. Esto nos permite cambiar librerías externas o bases de datos simplemente sustituyendo el archivo en la capa de infraestructura, sin reescribir la lógica de dominio.

El DRY Estratégico (Y la Trampa del Acoplamiento)

El principio Don't Repeat Yourself (DRY) es aplicado a ciegas por muchos desarrolladores. En nuestra arquitectura, se reserva para la infraestructura pura y la UI genérica (/shared, /components/ui, /lib). Botones, modales y clientes HTTP se escriben una sola vez.

Sin embargo, debemos tener un cuidado extremo con el DRY cuando se aplica a las reglas de negocio. Si la entidad OrderDTO en el módulo de Pedidos tiene exactamente los mismos cuatro campos que la InvoiceDTO en el módulo de Facturación, no intentes unificarlas en un BaseGlobalDTO en /shared.

La duplicación accidental entre dominios diferentes es infinitamente mejor que el acoplamiento prematuro. Si el departamento fiscal exige un nuevo campo en la factura mañana, el módulo de pedidos no debe verse impactado.


3. Angular: La Era Standalone y DDD Práctico

Con el fin de los NgModules y la consolidación de las APIs Standalone, Angular se liberó de la complejidad burocrática. El patrón moderno se inspira en las convenciones de monorepos de Nx y en conceptos ligeros de Domain-Driven Design (DDD).

Dividimos la aplicación en cuatro macrocategorías: core, shared (o ui), features y data-access.

Estructura Recomendada para Angular

src/
 ├─ app/
 │   ├─ core/                     # Servicios Singleton, interceptors y guards globales
 │   │   ├─ auth/
 │   │   │   ├─ auth.guard.ts
 │   │   │   └─ auth.service.ts
 │   │   └─ http/
 │   │       └─ error.interceptor.ts
 │   │
 │   ├─ shared/                   # UI "tonta" y genérica (Design System)
 │   │   ├─ ui/
 │   │   │   ├─ button/
 │   │   │   └─ modal/
 │   │   └─ utils/
 │   │
 │   ├─ features/                 # Secciones de negocio (Vertical Slices)
 │   │   ├─ checkout/
 │   │   │   ├─ components/       # Componentes internos y exclusivos del checkout
 │   │   │   ├─ services/         # Lógica de presentación del checkout
 │   │   │   ├─ checkout.routes.ts # Ruta con Lazy Loading
 │   │   │   └─ checkout.component.ts
 │   │   │
 │   │   └─ catalog/
 │   │
 │   └─ data-access/              # Comunicación con APIs externas y Estado Global (Signals)
 │       ├─ products/
 │       │   ├─ products.api.ts
 │       │   ├─ products.store.ts # SignalStore / Estado
 │       │   └─ products.types.ts
 │       └─ user/
  • Separation of Concerns (SoC): La carpeta data-access centraliza las llamadas HTTP y la gestión del estado (SignalStore). La capa de features/ consume estos datos sin saber cómo fueron obtenidos o etiquetados en el caché.
  • Aislamiento de UI: Todo lo que está dentro de features/checkout/components es privado. Si features/catalog necesita la misma tarjeta de producto, ese componente se refactoriza y se promueve a shared/ui.

4. React: Feature-Scoped Architecture y Barrel Files

En el ecosistema React (ya sea Next.js con App Router o SPAs con Vite), la libertad arquitectónica suele generar refactorizaciones traumáticas. La estructura más resiliente adopta la metodología Bulletproof React, abandonando carpetas globales saturadas en favor de módulos autosuficientes.

Estructura Recomendada para React

src/
 ├─ app/                          # Enrutamiento (Next.js App Router o configuración de React Router)
 │   ├─ (public)/
 │   └─ (authenticated)/
 │       └─ dashboard/
 │           └─ page.tsx          # ¡La página actúa únicamente como orquestadora de la Feature!
 │
 ├─ components/                   # Componentes globales de UI (Shadcn / Tailwind)
 │   ├─ ui/
 │   │   ├─ button.tsx
 │   │   └─ dialog.tsx
 │   └─ layouts/
 │
 ├─ lib/                          # Configuraciones de clientes externos (Axios, QueryClient)
 │   ├─ axios.ts
 │   └─ react-query.ts
 │
 ├─ features/                     # El corazón de la aplicación
 │   ├─ billing/
 │   │   ├─ api/                  # Hooks de TanStack Query (useInvoice, usePay)
 │   │   ├─ components/           # InvoiceList, PaymentModal
 │   │   ├─ hooks/                # Hooks locales de la feature (useTaxCalculator)
 │   │   ├─ types/                # Interfaces DTOs restringidas a billing
 │   │   └─ index.ts              # BARREL FILE: ¡Exporta únicamente lo que es público!
 │   │
 │   └─ projects/
 │
 └─ stores/                       # Estado global (Zustand, cuando es estrictamente necesario)
     └─ use-theme-store.ts

El Poder del Barrel File (index.ts)

Dentro de cada feature React, mantenemos un archivo index.ts en la raíz que actúa como la API pública del módulo. Si la página de Dashboard necesita renderizar la lista de facturas, importa:

import { InvoiceList } from '@/features/billing';

A la página se le impide físicamente importar archivos internos profundos (como un hook privado useTaxCalculator), manteniendo el encapsulamiento y el principio de ocultación de información.


5. Node.js (NestJS / Express): El Monolito Modular

En el backend, el error fundamental es estructurar el proyecto por capas técnicas en la raíz (/controllers, /models, /repositories). Esto viola el Principio de Responsabilidad Única a nivel de módulo.

Ya sea utilizando NestJS (que nos guía nativamente por este camino) o una configuración limpia con Express/Fastify, debemos diseñar el backend como un Monolito Modular basado en Clean Architecture.

Estructura Recomendada para Node.js

src/
 ├─ config/                       # Variables de entorno, conexión a BD, loggers
 ├─ shared/                       # Middlewares globales, excepciones personalizadas, decorators
 │   ├─ errors/
 │   └─ middlewares/
 │
 ├─ modules/                      # Dominios de negocio (Bounded Contexts)
 │   ├─ orders/
 │   │   ├─ dto/                  # Data Transfer Objects (Zod / Class-Validator)
 │   │   │   ├─ create-order.dto.ts
 │   │   │   └─ order-response.dto.ts
 │   │   │
 │   │   ├─ entities/             # Modelos de Dominio o Schemas del ORM
 │   │   │   └─ order.entity.ts
 │   │   │
 │   │   ├─ repositories/         # Aislamiento de la base de datos (Repository Pattern)
 │   │   │   ├─ order.repository.interface.ts
 │   │   │   └─ prisma-order.repository.ts
 │   │   │
 │   │   ├─ use-cases/            # Lógica de negocio pura (O Services en NestJS)
 │   │   │   ├─ create-order.usecase.ts
 │   │   │   └─ calculate-discount.usecase.ts
 │   │   │
 │   │   ├─ orders.controller.ts  # Capa HTTP / Entrada y salida de datos
 │   │   └─ orders.module.ts      # Cableado de dependencias (Inyección de Dependencia)
 │   │
 │   └─ users/
 │
 └─ main.ts                       # Punto de entrada (Entrypoint) de la aplicación

Ventajas Estratégicas del Monolito Modular

  • Independencia de Infraestructura (DIP): Al aislar los use-cases (regla de negocio) de los repositories (acceso a datos), si la empresa decide migrar de MongoDB a PostgreSQL en el futuro, solo cambiarás los archivos dentro de /repositories. La regla de negocio permanece intacta.
  • Preparado para Microservicios: Si el módulo de orders sufre un cuello de botella severo de procesamiento y necesita escalar de forma aislada, ya tiene sus límites definidos. Extraerlo a un microservicio independiente en un contenedor Docker será una tarea de cortar y pegar, no una reescritura completa.

Conclusión

La arquitectura de software no existe para burocratizar el trabajo de la ingeniería con reglas rígidas; su función principal es la gestión de la carga cognitiva.

Cuando organizamos proyectos en React, Angular o Node.js por Features e implementamos los principios SOLID y DRY en la raíz de nuestra estructura, permitimos que cualquier desarrollador pueda entender, modificar y probar una sección específica del sistema sin necesidad de cargar la aplicación entera en su memoria mental a corto plazo.

La próxima vez que inicies un proyecto o refactorices un sistema legado, abandona la agrupación por sintaxis o tipo de archivo. Empieza a estructurar tu código por los problemas de negocio que resuelve.

Comentarios

Cargando comentarios...

Únete a la conversación

Inicia sesión con tu cuenta para comentar este artículo.