Estructura de proyecto escalable

Lectura
20 min~6 min lectura
CONCEPTO CLAVE: Una estructura de proyecto escalable en React no es solo organizar archivos, es crear una arquitectura que permita que tu aplicación crezca sin volverse un caos. La forma en que organizas tu código hoy determinará qué tan fácil (o doloroso) será mantenerlo mañana.

¿Por qué importa la estructura de proyecto?

Cuando trabajas en proyectos pequeños, podrías pensar que no necesitas una estructura elaborada. Pero la realidad es que los proyectos crecen, los equipos se expanden, y lo que hoy tiene 5 componentes mañana puede tener 500.

Una buena estructura de proyecto te ofrece:

  • Orientación rápida: Cualquier desarrollador nuevo encuentra lo que busca en segundos.
  • Escalabilidad real: Agregar funcionalidades sin romper existente.
  • Mantenimiento sencillo: Cambios localizados sin efectos secundarios inesperados.
  • Colaboración efectiva: Equipos que trabajan en paralelo sin conflictos.
💡 Regla del 10 segundos: Si un nuevo desarrollador no puede encontrar un archivo en 10 segundos, tu estructura necesita mejorar.

Estructura recomendada para proyectos escalables

No existe una única estructura perfecta, pero existe una que funciona mejor que la mayoría: la estructura por características o feature-based structure.

Estructura base

src/
├── assets/              # Imágenes, fuentes, archivos estáticos
├── components/          # Componentes reutilizables globales
│   ├── common/          # Botones, inputs, cards, etc.
│   ├── layout/          # Header, Footer, Sidebar
│   └── ui/              # Componentes de interfaz específicos
├── features/            # Funcionalidades de dominio
│   ├── auth/
│   │   ├── components/
│   │   ├── hooks/
│   │   ├── services/
│   │   ├── types/
│   │   └── index.ts
│   ├── dashboard/
│   └── users/
├── hooks/               # Hooks globales y personalizados
├── lib/                 # Utilidades y configuraciones
├── services/            # Llamadas API y configuración
├── store/               # Estado global (Redux, Zustand, etc.)
├── styles/              # Estilos globales y temas
├── types/               # Tipos TypeScript globales
├── utils/               # Funciones auxiliares
├── App.tsx
└── main.tsx
📌 Principio fundamental: La carpeta features es el corazón de esta estructura. Cada funcionalidad vive en su propio espacio, conteniendo todo lo que necesita para funcionar de forma independiente.

El patrón de arquitectura Feature-Sliced Design

Una evolución natural de la estructura por características es el patrón Feature-Sliced Design (FSD). Este enfoque organiza el código en capas jerárquicas:

CapaDescripciónEjemplos
app/Configuración global, providers, routingApp.tsx, providers.tsx, routes.tsx
pages/Rutas principales de la aplicaciónDashboardPage, ProfilePage
widgets/Bloques de UI compuestos independientesUserCardWidget, StatsWidget
features/Acciones de usuario específicasAddToCart, SearchUsers, SendMessage
entities/Entidades de negocio (modelos)User, Product, Order
shared/Código sin dependencia de negocioButton, Modal, api.ts
⚠️ Error común: Mezclar componentes de diferentes dominios en la carpeta components/. Un componente UserCard.tsx en components/ viola los principios de separación. Debe estar en entities/user/.

Organizando un módulo de funcionalidad

Veamos cómo se ve un módulo bien estructurado en la práctica:

src/features/users/
├── api/
│   └── usersApi.ts          # Endpoints específicos
├── components/
│   ├── UserList/
│   │   ├── UserList.tsx
│   │   ├── UserList.styles.ts
│   │   └── UserList.test.tsx
│   ├── UserCard/
│   │   ├── UserCard.tsx
│   │   └── UserCard.styles.ts
│   └── UserForm/
│       └── UserForm.tsx
├── hooks/
│   ├── useUsers.ts
│   └── useUserById.ts
├── types/
│   ├── user.types.ts
│   └── user.dto.ts
├── utils/
│   └── userHelpers.ts
├── constants/
│   └── userConstants.ts
├── selectors/
│   └── userSelectors.ts     # Para Zustand/Redux
├── services/
│   └── userService.ts       # Lógica de negocio
├── index.ts                 # Exports públicos
└── users.slice.ts           # Estado local (si aplica)
Ver más: Ejemplo de barrel export (index.ts)
// src/features/users/index.ts
export { UserList } from './components/UserList';
export { UserCard } from './components/UserCard';
export { UserForm } from './components/UserForm';
export { useUsers, useUserById } from './hooks';
export type { User, UserDTO, CreateUserPayload } from './types';
export { userService } from './services/userService';

Patrones de nomenclatura esenciales

La consistencia en nombres es tan importante como la estructura:

  1. Archivos de componentes: PascalCase (UserCard.tsx)
  2. Archivos de hooks: camelCase con prefijo use (useUsers.ts)
  3. Archivos de estilos: Mismo nombre + extensión (.styles.ts, .module.css)
  4. Archivos de tests: Mismo nombre + .test.tsx
  5. Constantes: SCREAMING_SNAKE_CASE
  6. Tipos/Interfaces: PascalCase, sufijos descriptivos (UserDTO, CreateUserPayload)
«El código se escribe una vez, pero se lee cientos de veces. Nombres claros son documentación implícita.» — Robert C. Martin

Configuración de imports absolutos

Para mejorar la legibilidad, configura imports absolutos en tu proyecto:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["src/components/*"],
      "@features/*": ["src/features/*"],
      "@hooks/*": ["src/hooks/*"],
      "@utils/*": ["src/utils/*"],
      "@lib/*": ["src/lib/*"],
      "@store/*": ["src/store/*"],
      "@types/*": ["src/types/*"]
    }
  }
}

Así en lugar de escribir:

import { Button } from '../../../components/common/Button';

Escribes:

import { Button } from '@components/common/Button';
💡 Tip avanzado: Configura también alias en Vite con el plugin vite-tsconfig-paths para que el servidor de desarrollo reconozca los mismos paths.

Separación de concerns por contexto

No todo debe estar en components/. Aprende a separar:

ConcernUbicaciónEjemplo
Lógica de UIcomponents/UserCard.tsx renderiza props
Lógica de negociofeatures/*/services/calculateDiscount()
Acceso a datosfeatures/*/api/fetchUsers()
Estado globalstore/authSlice, userSlice
Estado localhooks/useToggle, useLocalStorage
📌 Regla del pulgar: Si un archivo necesita imports de más de 3 dominios diferentes, probablemente está en el lugar equivocado o hace demasiadas cosas.

Migrando un proyecto existente

Si tienes un proyecto con estructura plana o caótica, la migración gradual es posible:

  1. Identifica dominios: Agrupa archivos por funcionalidad real.
  2. Crea la estructura: Añade las carpetas vacías.
  3. Mueve incrementalmente: Un módulo a la vez, verificando tests.
  4. Actualiza imports: Usa herramientas como change-case o scripts de migración.
  5. Refactoriza al mover: No solo mover, también mejora.
  6. Documenta: Crea un README con la guía de estructura.
⚠️ Precaución: Nunca hagas refactoring y migración de estructura al mismo tiempo que resuelves bugs. Son diferentes tipos de cambios cognitivos que requieren diferentes enfoques mentales.

Checklist de estructura saludable

Antes de considerar tu estructura completa, verifica:

  • ¿Cada archivo tiene una única responsabilidad clara?
  • ¿Los componentes reutilizables están en shared/ o components/?
  • ¿Las funcionalidades de negocio viven en features/?
  • ¿Los archivos barril (index.ts) exponen solo lo público?
  • ¿Los paths de imports son consistentes (absolutos o relativos)?
  • ¿Los tests están junto al código que prueban?
  • ¿La profundidad de carpetas no excede 4 niveles?
🧠 Quiz: Estructura de Proyecto Escalable

Según el patrón Feature-Sliced Design, ¿en qué capa se ubicaría un componente de botón reutilizable globalmente?

  • A) entities/
  • B) features/
  • C) shared/
  • D) widgets/
✅ Respuesta correcta: C) shared/. Los componentes sin dependencia de negocio específico, como un botón base, pertenecen a la capa shared/ ya que son elementos reutilizables universales.

Conclusión

La estructura de proyecto no es un documento decorativo, es la arquitectura que sostiene el crecimiento de tu aplicación. Invierte tiempo en diseñarla correctamente al inicio y te ahorrará meses de refactoring dolorosos después.

Recuerda: la mejor estructura es aquella que tu equipo entiende, respeta y puede navegar sin esfuerzo. Adáptala a tu contexto, tu equipo y tu aplicación específica.

💡 Próximo paso: Revisa la estructura de tu proyecto actual. Identifica un módulo que podría refactorizarse según estos principios y practica la migración con un componente a la vez.