Arquitectura hexagonal en frontend: cómo organizar tu código React por dominios de negocio
- Qué es la arquitectura hexagonal y por qué funciona en frontend
- La metáfora de la casa: entender las 4 capas sin tecnicismos
- Del salón al código: ejemplo práctico con validaciones
- Cómo se traduce esto a un proyecto React real
- Caso real en Orbitant: así funciona en "Mis facturas"
- Ventajas y desventajas: cuándo usar arquitectura hexagonal en frontend
- Conclusión: arquitectura como inversión, no como gasto
- Preguntas frecuentes
Arrancas un proyecto frontend con las carpetas ordenadas. Tres meses después: ¿Dónde pongo este componente? ¿En qué archivo está esa validación? ¿Por qué este componente tiene 500 líneas mezclando APIs con lógica de negocio?
Los síntomas son siempre los mismos: carpetas desordenadas, lógica de negocio mezclada con UI, APIs esparcidas sin patrón, testing imposible porque necesitas mockear decenas de dependencias solo para probar una validación.
El coste: onboarding que se alarga semanas, cambios con efectos secundarios impredecibles, validaciones duplicadas en cinco sitios diferentes. La solución viene del backend, pero funciona perfectamente en frontend: arquitectura hexagonal.
Qué es la arquitectura hexagonal y por qué funciona en frontend
La arquitectura hexagonal (también Clean Architecture o Puertos y Adaptadores) estructura el código alrededor de reglas de negocio, no de frameworks. Tu lógica de negocio no depende de React, Vue o Angular.
La regla de oro: las dependencias siempre apuntan hacia dentro. Círculos concéntricos donde el centro es tu dominio (reglas de negocio puras), luego aplicación (casos de uso), infraestructura (bases de datos, APIs) y finalmente presentación (UI). Cada capa solo conoce las capas interiores.
¿Por qué funciona en frontend? Separa qué hace tu aplicación (dominio), cómo lo hace (infraestructura) y cómo se muestra al usuario (presentación). Cambias de React a Vue tocando solo presentación. Cambias de REST a GraphQL modificando solo infraestructura. Testeas lógica de negocio sin levantar un navegador.
La metáfora de la casa: entender las 4 capas sin tecnicismos
Para entender cómo funciona esta arquitectura, pensemos en las cuatro fases de construcción de una casa. Cada fase tiene un equivalente directo en nuestro código.
1. Blueprint (Dominio): El plano es lo primero que defines antes de construir nada. Especifica cuántas habitaciones hay, qué características tienen, qué reglas deben cumplir.
- En código, esta es tu capa de dominio: las entidades y las reglas de negocio puras. Un sofá debe tener tres plazas, ser de cuero o tela, y caber en la habitación. Esto es JavaScript o TypeScript puro, sin importar librerías externas.
2. Instructions (Aplicación): Las instrucciones de construcción definen qué debe ocurrir y en qué orden, pero no especifican qué herramienta usar. «Fija el panel a la estructura usando una herramienta que pueda clavar»: no importa si usas un martillo o un taladro.
- En código, estos son tus casos de uso: crear un mueble, validarlo, guardarlo. La capa de aplicación orquesta el flujo pero no sabe si los datos van a MongoDB, PostgreSQL o LocalStorage.
3. Tools & Materials (Infraestructura): Aquí es donde aparecen las herramientas reales: martillos, taladros, madera, tornillos.
- En tu aplicación, esta es la capa que se comunica con el mundo exterior: clientes HTTP para APIs, adaptadores para bases de datos, servicios de almacenamiento. Esta capa implementa las interfaces que la capa de aplicación define, pero puede cambiarse completamente sin afectar la lógica de negocio.
4. Builders (Presentación): Los trabajadores que ejecutan las instrucciones y hacen visible la casa. En código, son tus componentes React, Vue o lo que uses.
- Esta capa interactúa directamente con el usuario, recibe inputs, muestra outputs, y llama a los casos de uso para ejecutar acciones. Es la única capa que «ve» el framework de UI.
Sigue nuestro viaje construyendo agentes IA desde dentro
Del salón al código: ejemplo práctico con validaciones
Diseñamos un salón: sofá, mesa de café y TV. El sofá debe tener 3 plazas, ser de cuero o tela, de color azul o amarillo. La mesa debe caber en el espacio. La TV: mínimo 55 pulgadas y 4K.
Reglas de negocio puras que no dependen de cómo almacenes datos ni de qué UI uses:
// Domain layer - entities.ts
export type Furniture = {
id: string;
type: 'sofa' | 'coffee_table' | 'tv';
brand: string;
model: string;
dimensions: Dimension;
color: string;
material: string;
price?: number;
image: string;
mountingType: 'wall' | 'floor';
createdAt: Date;
updatedAt: Date;
};
Y aquí las validaciones en la capa de servicios del dominio:
// Domain layer - services.ts
export const isValidSofa = (params: SofaValidationParams): boolean => {
const { furniture, seatCount, roomDimensions, doorways } = params;
// Rule 1: Must have 3 seats
if (seatCount !== 3) {
return false;
}
// Rule 2: Material must be leather or fabric
const validMaterials = ['leather', 'fabric'];
if (!validMaterials.includes(furniture.material.toLowerCase())) {
return false;
}
// Rule 3: Color must be blue or yellow
const validColors = ['blue', 'yellow'];
const furnitureColor = furniture.color.toLowerCase();
if (!validColors.some(color => furnitureColor.includes(color))) {
return false;
}
// Rule 4: Must fit inside the room dimensions
if (
furniture.dimensions.width > roomDimensions.width ||
furniture.dimensions.depth > roomDimensions.depth
) {
return false;
}
// Rule 5: Cannot block doorways
for (const doorway of doorways) {
if (
furniture.dimensions.width >= doorway.width &&
furniture.dimensions.depth >= doorway.height
) {
return false;
}
}
return true;
};
¿Ves alguna referencia a React, bases de datos o librerías UI? No. Estas validaciones se ejecutan en navegador, servidor, test unitario o Lambda. Son independientes de la tecnología.
Por eso NO pueden depender de librerías externas. El día que quieras esta lógica en React Native, o migrar a Vue, o crear una CLI, tu código de dominio se mantiene intacto.
Cómo se traduce esto a un proyecto React real
Estructura por dominios de negocio, no por tipo de archivo. En lugar de components/, services/, utils/, tienes living-room/, kitchen/, bedroom/.
Dentro de cada dominio, las cuatro capas tienen su propia subcarpeta:
living-room/
├── domain/
│ ├── entities.ts // Tipos puros
│ ├── repository.ts // Interfaces (contratos)
│ └── services.ts // Validaciones y reglas
├── application/
│ └── use-cases.ts // Crear mueble, validar, etc.
├── infrastructure/
│ └── repositories.ts // Implementación real (API/DB)
└── presentation/
└── component.tsx // Componentes React
La Domain layer contiene solo TypeScript puro. En entities.ts defines los tipos de datos, en repository.ts defines las interfaces (contratos) que especifican qué operaciones deben existir sin decir cómo, y en services.ts escribes las funciones de validación y reglas de negocio.
La Application layer contiene los casos de uso en use-cases.ts. Por ejemplo, createFurniture recibe un repositorio como dependencia y aplica las validaciones del dominio antes de guardar. Esta capa orquesta el flujo pero no sabe si estás usando MongoDB, PostgreSQL o LocalStorage. Solo sabe que existe un repositorio que cumple el contrato definido en el dominio.
La Infrastructure layer tiene las implementaciones reales en repositories.ts. Aquí es donde aparecen las llamadas HTTP, los clientes de bases de datos, el acceso a LocalStorage. Esta capa implementa las interfaces definidas en el dominio.
La Presentation layer contiene tus componentes React en component.tsx. Aquí usas hooks, estados, efectos, todo lo específico de React. Los componentes llaman a los casos de uso para ejecutar acciones y muestran el resultado al usuario.
La ventaja clave: cambias de React a Vue tocando solo presentación. Cambias de REST a GraphQL modificando solo infraestructura. Dominio y casos de uso permanecen intactos.
Caso real en Orbitant: así funciona en «Mis facturas»
En Orbitant lo aplicamos en MisFacturas, un proyecto de facturación electrónica compatible con Verifactu. Antes de escribir código, mapeamos entidades y servicios del dominio.
Dominios identificados: company, concept, contact, document. Beneficio inmediato: cualquier desarrollador abre domain/ y entiende el negocio en cinco minutos.
El caso más interesante: desarrollar sin backend. Empezamos con LocalStorage. Definimos la interfaz del repositorio y creamos una implementación que guardaba en el navegador.
Cuando el backend estuvo listo, creamos una nueva implementación que llamaba a la API. Cambiamos la inyección de dependencias en un punto y toda la aplicación usó el backend real. Ni una línea de lógica de negocio se modificó. Solo infraestructura.
Esto es arquitectura hexagonal en acción.
Ventajas y desventajas: cuándo usar arquitectura hexagonal en frontend
Ventajas reales:
- Testing extremadamente simple: capas aisladas. Testeas validaciones sin React, sin mockear APIs, sin montar componentes. Importas la función y la pruebas con datos puros.
- Cambiar tecnologías es trivial. ¿Preact en lugar de React? Reescribe presentación. ¿Axios en lugar de Fetch? Cambia infraestructura. ¿Cache Redis? Nueva implementación de repositorio.
- Onboarding rápido: abres
domain/y entiendes el negocio en minutos. Estructura clara y predecible. - Mantenibilidad a largo plazo: reglas de negocio en un solo sitio, sin duplicados, cambios quirúrgicos.
Desventajas honestas:
- Requiere planificación inicial. No puedes empezar a codear componentes directamente. Necesitas pensar en dominios, entidades, casos de uso.
- Más archivos y carpetas. En lugar de un componente con todo, tienes cuatro archivos en cuatro carpetas. Puede abrumar al principio.
- Necesita disciplina del equipo. Si alguien importa el cliente HTTP directo en un componente, rompe la arquitectura. Requiere code reviews.
- Puede ser overkill para apps pequeñas. Una landing page de tres pantallas no necesita esto.
¿Cuándo SÍ? Proyectos enterprise. Equipos distribuidos. Aplicaciones con vida larga. Lógica de negocio compleja.
¿Cuándo NO? MVPs rápidos. Landing pages. Prototipos que se reescribirán. Aplicaciones triviales sin lógica relevante.
Conclusión: arquitectura como inversión, no como gasto
La arquitectura hexagonal resuelve problemas reales de producción: código inmanejable, testing imposible, migraciones dolorosas.
El coste inicial se recupera rápido. Las primeras semanas de mantenimiento compensan la inversión. Desarrolladores nuevos contribuyen en días. Los cambios de tecnología dejan de ser proyectos de meses para convertirse en tareas de horas.
Por eso es nuestro estándar en proyectos frontend complejos. No para todo, pero sí para aplicaciones que van a crecer.
Si quieres profundizar: Clean Architecture de Robert C. Martin. Aunque enfocado en backend, los principios se aplican perfectamente cuando entiendes que React es solo un detalle de implementación.
La pregunta no es si tu proyecto necesita arquitectura; es si quieres definirla conscientemente desde el principio o dejar que emerja caóticamente.
Preguntas frecuentes
¿Cómo gestiono páginas que usan múltiples dominios?
Un componente de página (Next.js, por ejemplo) puede combinar varios dominios. La presentación orquesta. Tu página importa casos de uso de company, contact y document simultáneamente. Cada dominio se mantiene aislado, la presentación los compone.
¿Puedo usar un dominio dentro de otro?
Sí. Ejemplo: document podría necesitar conceptos de company. Pero si un concepto es usado por muchos dominios, mejor moverlo a shared/ o common/. Evitas dependencias circulares. Regla: prefiere composición en capas superiores antes que dependencia entre dominios.
¿Cómo uso esto con IA y herramientas como Cursor?
Perfecta para IA: estructura predecible y repetitiva. Crea prompts que generen dominios completos. En nuestro equipo, David creó un script que genera todas las capas de un dominio especificando solo entidades. Con Cursor, define reglas en .cursorrules para respetar estructura y convenciones de dependencias.
¿Qué pasa con herramientas reutilizables como RabbitMQ o clientes FTP?
Los repositorios son intercambiables. Cliente RabbitMQ que varios dominios usan: defines interfaz del repositorio en cada dominio según sus necesidades, implementación en infraestructura reutiliza el mismo cliente. Lo importante: la interfaz se define desde necesidades del dominio, no desde capacidades de la herramienta. La herramienta se adapta al dominio.