¿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.
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.tsxfeatures 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:
| Capa | Descripción | Ejemplos |
|---|---|---|
| app/ | Configuración global, providers, routing | App.tsx, providers.tsx, routes.tsx |
| pages/ | Rutas principales de la aplicación | DashboardPage, ProfilePage |
| widgets/ | Bloques de UI compuestos independientes | UserCardWidget, StatsWidget |
| features/ | Acciones de usuario específicas | AddToCart, SearchUsers, SendMessage |
| entities/ | Entidades de negocio (modelos) | User, Product, Order |
| shared/ | Código sin dependencia de negocio | Button, Modal, api.ts |
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:
- Archivos de componentes: PascalCase (
UserCard.tsx) - Archivos de hooks: camelCase con prefijo use (
useUsers.ts) - Archivos de estilos: Mismo nombre + extensión (.styles.ts, .module.css)
- Archivos de tests: Mismo nombre + .test.tsx
- Constantes: SCREAMING_SNAKE_CASE
- 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';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:
| Concern | Ubicación | Ejemplo |
|---|---|---|
| Lógica de UI | components/ | UserCard.tsx renderiza props |
| Lógica de negocio | features/*/services/ | calculateDiscount() |
| Acceso a datos | features/*/api/ | fetchUsers() |
| Estado global | store/ | authSlice, userSlice |
| Estado local | hooks/ | useToggle, useLocalStorage |
Migrando un proyecto existente
Si tienes un proyecto con estructura plana o caótica, la migración gradual es posible:
- Identifica dominios: Agrupa archivos por funcionalidad real.
- Crea la estructura: Añade las carpetas vacías.
- Mueve incrementalmente: Un módulo a la vez, verificando tests.
- Actualiza imports: Usa herramientas como
change-caseo scripts de migración. - Refactoriza al mover: No solo mover, también mejora.
- Documenta: Crea un README con la guía de estructura.
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/ocomponents/? - ¿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?
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/
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.