Introducción: La Arquitectura como Cimiento de un Proyecto Exitoso
Antes de escribir una sola línea de código para nuestra aplicación de comercio electrónico, debemos dedicar tiempo a una de las fases más críticas y, a menudo, subestimadas: el diseño de la arquitectura y la planificación del proyecto. Imagina que estás construyendo un centro comercial. No comenzarías a colocar ladrillos sin un plano arquitectónico detallado, sin definir dónde irán las tiendas, los pasillos, los sistemas eléctricos y de fontanería. De la misma manera, en el desarrollo de software, una arquitectura bien pensada es el plano maestro que guiará todo el proceso de construcción, asegurando que el resultado final sea robusto, escalable y mantenible.
En esta lección, nos enfocaremos en definir la estructura de nuestra aplicación móvil, seleccionar las tecnologías clave que complementarán a React Native y Expo, y establecer un plan de trabajo claro. Para un proyecto de nivel intermedio a avanzado como una app de e-commerce, saltarse esta etapa es una receta para el desastre: conducirá a un código desorganizado, dificultades para integrar nuevas funcionalidades, y una base débil que puede colapsar bajo la presión de un entorno de producción real. Aquí sentaremos las bases para un proyecto que no solo funcione, sino que también pueda crecer y evolucionar.
Abordaremos conceptos como la separación de responsabilidades, la gestión de estado global, la estructura de navegación, y la integración con servicios backend. El objetivo es que al final de esta lección, tengas un esquema claro y un conjunto de herramientas listas para comenzar la implementación con confianza y dirección.
Concepto Clave: Arquitectura de Capas y Separación de Responsabilidades
La Arquitectura de Capas es un principio de diseño que organiza el código en grupos distintos, cada uno con una responsabilidad específica y bien definida. Piensa en un restaurante de alta cocina. Tienes la sala (la interfaz de usuario), donde los comensales interactúan y reciben el plato final. Tienes la cocina (la lógica de negocio), donde los chefs transforman los ingredientes según recetas complejas. Y tienes la despensa y proveedores (la capa de datos y servicios), que se encargan de almacenar y conseguir los ingredientes. Estos mundos interactúan, pero están separados: un mesero no cocina, y un chef no toma pedidos directamente.
En nuestro contexto de React Native, esto se traduce en dividir nuestra aplicación en capas claras: la capa de presentación (componentes UI), la capa de lógica de negocio (gestores de estado, hooks personalizados), y la capa de servicios/datos (llamadas a APIs, almacenamiento local). La separación de responsabilidades hace que el código sea más fácil de testear, depurar y mantener. Si necesitas cambiar la fuente de datos (por ejemplo, de una API REST a GraphQL), solo debes modificar la capa de servicios, sin tocar la lógica de negocio o los componentes visuales.
Esta separación también facilita el trabajo en equipo. Un desarrollador puede enfocarse en crear componentes visuales hermosos y responsivos, mientras otro se concentra en la lógica compleja del carrito de compras o la autenticación, sin pisarse los pies. Adoptar esta mentalidad desde el inicio es fundamental para la salud a largo plazo de nuestro proyecto de e-commerce.
Tip del Instructor: No busques una arquitectura "perfecta" teórica. Busca una arquitectura "práctica" que se adapte a tu equipo y proyecto. Comienza con una separación clara y simple, y refina las capas a medida que la aplicación crece en complejidad. La sobre-ingeniería inicial puede ser tan perjudicial como la falta de planificación.
Cómo Funciona en la Práctica: Definiendo Nuestra Estructura de Carpetas
Vamos a materializar el concepto de capas en una estructura de carpetas concreta para nuestro proyecto. Crearemos un directorio raíz organizado que refleje nuestras responsabilidades. Usaremos Expo como plataforma base, por lo que iniciaremos un proyecto con npx create-expo-app y luego reorganizaremos. La estructura propuesta no es la única posible, pero es una excelente base para aplicaciones de mediana a gran escala.
El primer paso es crear las carpetas principales dentro del directorio src o app. Esto aleja nuestro código del "ruido" de configuración de Expo/React Native que vive en la raíz. Las carpetas clave serán: components (para componentes UI reutilizables), screens (para las pantallas completas de la app), navigation (configuración de navegadores como Stack o Tab), store o state (gestión de estado global), services (llamadas a API), utils (funciones helper), y assets (imágenes, fuentes).
A continuación, un ejemplo paso a paso de cómo se vería la creación inicial y la organización de un componente dentro de esta estructura. Notarás cómo la ubicación del código ya sugiere su propósito.
# 1. Crear el proyecto Expo
npx create-expo-app AwesomeStore --template blank
cd AwesomeStore
# 2. Crear la estructura de carpetas dentro de /src
mkdir -p src/{components,screens,navigation,store,services,utils,assets,constants,hooks}
# 3. Ejemplo: Crear un componente de botón reutilizable
touch src/components/common/PrimaryButton.jsx
Código en Acción: Configuración Inicial de Navegación y Estado
Dos de los pilares arquitectónicos más importantes son la navegación y la gestión de estado. Para la navegación, usaremos React Navigation, la librería estándar de la comunidad. Configuraremos un navegador tipo Stack para el flujo de autenticación y compra, y un navegador tipo Tab para las secciones principales de la app (Inicio, Categorías, Carrito, Perfil). Para el estado, elegiremos Redux Toolkit (RTK) por su eficiencia, convenciones claras y excelente integración con TypeScript. Es la opción recomendada para aplicaciones con estado complejo como un e-commerce.
El siguiente código muestra la configuración inicial de nuestro almacén de Redux (store) y la estructura raíz de navegación. Este es el "esqueleto" central de la aplicación. Definimos slices (porciones) de estado, como el carrito y los productos, y configuramos un navegador que condiciona las pantallas visibles según si el usuario está autenticado o no.
// Archivo: src/store/store.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './slices/cartSlice';
import productsReducer from './slices/productsSlice';
import authReducer from './slices/authSlice';
export const store = configureStore({
reducer: {
cart: cartReducer,
products: productsReducer,
auth: authReducer,
},
});
// Archivo: src/navigation/AppNavigator.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useSelector } from 'react-redux';
import HomeScreen from '../screens/HomeScreen';
import ProductDetailScreen from '../screens/ProductDetailScreen';
import CartScreen from '../screens/CartScreen';
import AuthScreen from '../screens/AuthScreen';
const Stack = createNativeStackNavigator();
const Tab = createBottomTabNavigator();
function MainTabs() {
return (
<Tab.Navigator screenOptions={{ headerShown: false }}>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Cart" component={CartScreen} />
{/* Otras pestañas... */}
</Tab.Navigator>
);
}
export default function AppNavigator() {
const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
return (
<NavigationContainer>
<Stack.Navigator>
{!isAuthenticated ? (
<Stack.Screen name="Auth" component={AuthScreen} options={{ headerShown: false }} />
) : (
<>
<Stack.Screen name="MainTabs" component={MainTabs} options={{ headerShown: false }} />
<Stack.Screen name="ProductDetail" component={ProductDetailScreen} />
{/* Otros stacks dentro del flujo autenticado... */}
</>
)}
</Stack.Navigator>
</NavigationContainer>
);
}
Para completar el ejemplo, aquí está el slice básico del carrito usando Redux Toolkit. Muestra cómo definimos el estado inicial, los reducers (funciones que modifican el estado) y las acciones que despacharemos desde nuestros componentes.
// Archivo: src/store/slices/cartSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
items: [],
totalAmount: 0,
};
const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem: (state, action) => {
const newItem = action.payload;
const existingItem = state.items.find(item => item.id === newItem.id);
if (existingItem) {
existingItem.quantity += newItem.quantity;
} else {
state.items.push(newItem);
}
state.totalAmount = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
removeItem: (state, action) => {
const id = action.payload;
state.items = state.items.filter(item => item.id !== id);
state.totalAmount = state.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
},
clearCart: (state) => {
state.items = [];
state.totalAmount = 0;
},
},
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
export default cartSlice.reducer;
Planificación del Proyecto: Historias de Usuario y Roadmap
Con la arquitectura técnica definida, debemos trazar el plan de desarrollo. La metodología ágil, específicamente el uso de Historias de Usuario, es ideal para esto. Una historia de usuario describe una funcionalidad desde la perspectiva del usuario final, siguiendo el formato: "Como [rol], quiero [objetivo] para [beneficio]". Para nuestro e-commerce, podríamos tener: "Como cliente, quiero filtrar productos por categoría para encontrar rápidamente lo que necesito".
Crearemos un backlog (lista de pendientes) priorizado con estas historias. Las agruparemos en sprints (iteraciones de desarrollo de 1-2 semanas) para crear un roadmap tangible. El primer sprint, o MVP (Producto Mínimo Viable), debe entregar el flujo central: ver productos, añadir al carrito, y un proceso de checkout simplificado. Prioriza siempre las funcionalidades que aporten el mayor valor con el menor esfuerzo. Usa herramientas como Trello, Jira, o simplemente un documento compartido para mantener este backlog visible y actualizado.
Esta planificación no es estática. Debe revisarse al final de cada sprint. ¿Completamos todo lo planeado? ¿Surgen nuevas historias? ¿Necesitamos refactorizar algo basado en lo aprendido? Esta retroalimentación continua es lo que permite que un proyecto evolucione de manera saludable y se mantenga alineado con las expectativas del negocio y los usuarios.
Errores Comunes y Cómo Evitarlos
En esta fase de arquitectura y planificación, ciertos errores pueden comprometer todo el proyecto. Identificarlos a tiempo es crucial.
1. No Definir Límites Claros entre Módulos: Dejar que la lógica de la API se cuele en los componentes o que el estado global maneje detalles de UI. Cómo evitarlo: Sigue estrictamente la arquitectura de capas. Si una función en services/ empieza a usar hooks de React o a manipular el DOM, es una señal de alarma. Revisa y refactoriza.
2. Sobre-ingeniería Prematura (Over-engineering): Implementar una solución extremadamente compleja para un problema simple que aún no existe. Por ejemplo, configurar un sistema de caché distribuido para una app que aún no tiene usuarios. Cómo evitarlo: Sigue el principio YAGNI ("You Ain't Gonna Need It"). Implementa lo necesario para el MVP y escala la arquitectura solo cuando las limitaciones sean evidentes.
3. Subestimar la Gestión de Estado: Usar solo el estado local de componentes (useState) para datos que deben ser compartidos a lo largo de toda la app (como el carrito o el perfil del usuario). Cómo evitarlo: Mapea desde el inicio qué datos son locales (ej: estado de un formulario) y cuáles son globales. Implementa una solución de estado global (Redux Toolkit, Zustand, Context + useReducer) desde el primer sprint para los datos globales.
4. Ignorar la Planificación del Manejo de Errores y Estados de Carga: Asumir que todas las llamadas a la API serán exitosas y instantáneas. Cómo evitarlo: Diseña un sistema centralizado de manejo de errores (por ejemplo, un slice en Redux para errores de la app) y define estados de UI para "cargando", "éxito", "error" y "vacío" para cada pantalla que consuma datos. Implementa esto desde el primer componente que haga fetching.
5. No Documentar las Decisiones Arquitectónicas: Tomar decisiones clave sobre librerías o patrones sin escribirlas en ningún lado, lo que lleva a confusión cuando nuevos desarrolladores se unen al proyecto o cuando se olvidan los razonamientos. Cómo evitarlo: Mantén un archivo ARCHITECTURE.md o DECISIONS.md en la raíz del proyecto. Documenta por qué elegiste Redux Toolkit sobre Context API, o la estructura de carpetas específica. Esto es invaluable para la cohesión del equipo.
Checklist de Dominio
Antes de proceder a la siguiente lección, asegúrate de poder verificar los siguientes puntos. Esto indica que has comprendido y puedes aplicar los conceptos clave de la planificación arquitectónica.
- He definido y creado una estructura de carpetas basada en capas (presentación, lógica, datos) dentro de mi proyecto Expo.
- He seleccionado e integrado una librería de navegación (React Navigation) configurando al menos un Stack y un Tab navigator con lógica de autenticación condicional.
- He configurado un sistema de gestión de estado global (ej: Redux Toolkit) con al menos un slice funcional (como el carrito) que incluye estado, reducers y acciones.
- He escrito al menos 5 historias de usuario prioritarias para el MVP de mi app de e-commerce y las he organizado en un backlog sencillo.
- Puedo explicar la diferencia entre estado local y estado global, y dar un ejemplo de cada uno en el contexto de mi aplicación.
- He identificado y documentado al menos una decisión arquitectónica clave (elección de librería, patrón de diseño) en un archivo del proyecto.
- He creado un componente de presentación "puro" (dumb component) que recibe toda su data y comportamientos por props, ubicándolo en la carpeta correspondiente.
- He configurado un servicio básico (en services/) con una función que realiza una llamada fetch a una API externa simulada.
Conclusión Práctica: El tiempo invertido en diseñar una arquitectura sólida y un plan claro no es tiempo perdido; es tiempo multiplicado. Te ahorrará incontables horas de refactorización, debugging y discusiones en el futuro. Ahora que tienes tu plano arquitectónico, estás listo para comenzar a "construir" con código, sabiendo exactamente dónde debe ir cada pieza y cómo se conectará con las demás. En la siguiente lección, nos sumergiremos en la implementación de la capa de presentación: los componentes UI de nuestro catálogo de productos.Falar no WhatsApp