Saltar al contenido principal

Arquitectura Frontend — Atomic Design + Feature-Sliced

La aplicación de Finnova se construye en React Native con un MVP orientado a móvil y la expectativa de extenderse a web en el corto-mediano plazo. Para sostener ese crecimiento sin acumular deuda técnica, el frontend combina dos patrones complementarios más una capa de abstracción de plataforma:

  • Feature-Sliced Design (FSD) organiza la lógica de negocio por dominio.
  • Atomic Design organiza la UI reutilizable dentro de shared/ui.
  • Platform Abstraction Layer aísla las diferencias entre móvil y web.

La decisión y sus alternativas descartadas están documentadas en el ADR: Patrón de diseño — Atomic Design + Feature-Sliced. Este documento describe cómo se aplica esa decisión en la práctica.


Stack del frontend

CapaTecnologíaRol
FrameworkReact NativeBase multiplataforma (móvil → web)
NavegaciónExpo RouterRouting basado en archivos, compatible con web
Estado globalZustandEstado de UI y de dominio en memoria
Data fetchingTanStack QueryCache, sincronización y estado de servidor
FormulariosReact Hook FormManejo y validación de formularios
LenguajeTypeScript (strict: true)Tipado estático en todo el código

El estado de servidor (datos que vienen del backend) vive en TanStack Query; el estado de cliente (UI, sesión, preferencias) vive en Zustand. No duplicar datos del servidor en Zustand.


Capas y reglas de importación

FSD define capas con una regla de dependencia unidireccional: una capa solo puede importar de capas inferiores. Esto evita dependencias circulares y acoplamiento implícito entre módulos.

pages → features → shared

platform (transversal)
  • pages/ puede importar de features/ y shared/.
  • features/ puede importar de shared/ (pero nunca de otra feature).
  • shared/ no importa de ninguna otra capa del proyecto.
  • platform/ es transversal: provee adaptadores de infraestructura (navegación, storage) y puede ser usado por cualquier capa.

Regla clave: features aisladas

Una feature nunca importa directamente de otra feature. Si dos features necesitan compartir algo (un tipo, un componente, un helper), ese código sube a shared/. Esto mantiene cada dominio como un módulo independiente y testeable de forma aislada.


Estructura de carpetas

src/
├── shared/ # Código reutilizable SIN lógica de negocio
│ ├── ui/ # Componentes genéricos (Atomic Design)
│ │ ├── atoms/
│ │ ├── molecules/
│ │ └── organisms/
│ ├── lib/ # Utilidades, helpers, hooks genéricos
│ └── api/ # Cliente HTTP base y configuración

├── features/ # Módulos por dominio de negocio
│ ├── auth/
│ │ ├── ui/ # Componentes y pantallas del módulo
│ │ ├── model/ # Estado (Zustand), lógica de negocio, tipos
│ │ └── api/ # Llamadas al backend de este módulo
│ ├── leaderboard/
│ ├── fdc/ # Financial Data Connection
│ ├── investments/
│ ├── accounting/
│ ├── rewards/
│ ├── courses/
│ └── subscription/
│ ├── ui/
│ ├── model/
│ └── api/

├── pages/ # Pantallas raíz (entry points de navegación)
│ ├── HomeScreen.tsx
│ └── ...Screen.tsx

└── platform/ # Adaptadores de infraestructura nativa
├── navigation/ # Configuración de Expo Router / React Navigation
└── storage/ # Persistencia local (AsyncStorage, etc.)

Estructura interna de una feature

Cada feature replica las tres mismas sub-capas (FSD a nivel de slice):

Sub-capaResponsabilidadEjemplos
ui/Componentes y pantallas propias del dominioLoginForm, BalanceCard
model/Estado (Zustand), lógica de negocio, tipos del dominiouseAuthStore, IUser, validaciones
api/Llamadas al backend de este módulo (hooks de TanStack Query)useLogin, fetchTransactions

Atomic Design en shared/ui

Los componentes genéricos y sin lógica de negocio viven en shared/ui y siguen la jerarquía de Atomic Design. Un componente que conoce reglas de negocio no va aquí: va en la ui/ de la feature correspondiente.

NivelDefiniciónEjemplos
AtomsComponente mínimo, sin lógica de negocio.Button, Input, Text, Icon
MoleculesCombinación de atoms con lógica simple de presentación.LabeledInput, SearchField
OrganismsBloques complejos reutilizables compuestos de molecules/atoms.AppHeader, FormCard

Criterio de ubicación: si el componente es agnóstico de dominio y reutilizable en cualquier pantalla → shared/ui. Si conoce reglas de negocio (p. ej. un TransactionRow que formatea según el tipo de movimiento) → features/<dominio>/ui.


Platform Abstraction Layer

El objetivo es que más del 80% del código (lógica de negocio, estado y UI base) sea reutilizable entre móvil y web sin modificación. Para lograrlo, las diferencias de plataforma no se dispersan con if (Platform.OS === ...) por toda la base de código: se concentran en platform/.

  • platform/navigation/ — configuración de navegación (Expo Router) por plataforma.
  • platform/storage/ — persistencia local: AsyncStorage en móvil, equivalentes en web.

Cuando se agregue soporte web, solo habrá que implementar las interfaces de platform/, sin tocar las features ni shared/.


Flujo de datos típico

Un flujo de lectura de datos (p. ej. mostrar el dashboard financiero) atraviesa las capas así:

  • TanStack Query maneja cache, reintentos y estado de carga/error.
  • shared/api/httpClient centraliza base URL, headers y la inyección del JWT (ver Control de Sesiones).
  • El componente de UI solo consume data / isLoading / error; no conoce detalles de transporte.

Convenciones aplicadas

  • Nomenclatura: componentes en PascalCase (BalanceCard.tsx); el resto de archivos/carpetas en camelCase. Ver Convenciones de Código.
  • Código en inglés, comentarios en español.
  • TypeScript strict, prohibido any salvo excepción documentada.
  • Sin enforcement automático, las reglas de importación entre capas dependen de la disciplina del equipo (idealmente reforzadas con eslint-plugin-import).

Recursos relacionados