Todos los tutoriales de hexagonal usan ejemplos de banco o de e-commerce. Todos son grandes. Todos tienen 10+ equipos. Pero la mayoría de proyectos que yo construyo son de 1-3 personas, y la pregunta honesta es: ¿vale la pena el overhead?
Después de tres años y cuatro proyectos — dos con hexagonal, dos sin — tengo una respuesta.
El trade-off, sin disfraces
Hexagonal te regala tres cosas:
- Tests rápidos. Lógica de dominio sin red, sin DB, sin LLM. Mocks son triviales porque los puertos son interfaces delgadas.
- Reemplazo de adapters sin tocar dominio. Cambiaste de Postgres a Supabase? Un adapter. Cambiaste de Claude a GPT? Un adapter.
- Contratos explícitos. No hay dudas de qué entra y sale de cada capa.
Y te cobra tres cosas:
- Archivos. Muchos archivos. Un caso de uso simple puede explotar en 6 archivos: use-case, port-in, port-out, adapter-in, adapter-out, dto.
- Curva de aprendizaje. Si contratás a alguien que nunca lo vió, perdés una semana.
- Sobre-ingeniería si el dominio es simple. Un CRUD puro sufre más que se beneficia.
"Hexagonal no es una arquitectura, es una disciplina. Y la disciplina tiene costo fijo." — yo mismo, hace dos años, después de refactorear por tercera vez
Cuándo sí, cuándo no
CRM con agente IA → hexagonal, siempre
El caso de uso convertir_lead_a_cliente tiene:
- Políticas: cuándo un lead es qualifiable
- Side-effects: mandar mail, crear Stripe customer, disparar workflow
- Actores externos: el LLM sugiere la acción pero no la ejecuta
Esto no es un form. Tiene reglas que cambian con el negocio, y LLMs que cambian con el vendor. Quiero poder testear la lógica sin ninguno de los dos colgado.
src/
├── domain/
│ ├── lead.ts # entidad, puro TS, cero imports
│ ├── qualification.ts # política de dominio
│ └── events.ts # LeadConverted, LeadRejected
├── app/
│ └── convert-lead/
│ ├── use-case.ts # orquesta la política + puertos
│ └── port.ts # input/output del caso
└── infra/
├── postgres/ # adapter-out
├── stripe/ # adapter-out
└── mcp/ # adapter-in (el LLM entra por acá)
Landing con typeform → React + Supabase, sin ceremonia
Un lead capture form no tiene casos de uso. Tiene endpoints. La abstracción sobra.
El argumento económico
Tres años, cuatro proyectos. Medí dos cosas: horas hasta el primer feature entregado y horas refactorizando en el mes 6.
| Proyecto | Arquitectura | Primer feature | Refactor mes 6 |
|---|---|---|---|
| CRM A | Hexagonal | 42h | 8h |
| CRM B | Plano | 18h | 64h |
| SaaS C | Hexagonal | 38h | 4h |
| Landing | Plano | 6h | 2h |
El dato interesante no es la suma — es la varianza. Los proyectos planos no siempre son más baratos. Cuando el dominio es rico, el costo de no tener capas se paga con intereses.
Mi stack hexagonal en 2026
- Dominio: TypeScript puro, cero dependencias, Zod para value objects.
- Casos de uso: funciones puras que reciben puertos como argumento. Nada de clases.
- Puertos: interfaces mínimas. Si tiene más de 3 métodos, está mal diseñado.
- Adapters: una carpeta por tecnología. Postgres, Stripe, Claude.
- DI: explícita en la composition root. Sin frameworks.
Nunca uso clases para casos de uso. No las necesito. Y evito que el equipo se enganche en discusiones de OOP que no resuelven nada.
La pregunta honesta
Si estás solo y el proyecto es un MVP de un fin de semana — no lo hagas. Vas a perder el fin de semana peleando con la estructura.
Si estás construyendo algo que va a vivir más de un año, con un dominio que decide cosas — hacelo. En el mes 6 te vas a agradecer.
Yo siempre me agradezco.