Todos los artículos

// Arquitectura que aguanta

Construyendo un ERP Multi-Tenant con Arquitectura Limpia y Next.js

Lecciones aprendidas al diseñar Orbis 8, un ERP SaaS en producción para PYMES en El Salvador. Arquitectura Limpia, patrones hexagonales y agentes de IA.

27 de febrero de 20263 min de lectura

El Problema

Construir un ERP desde cero en 2025 es una decisión audaz. La mayoría de los equipos optan por plataformas existentes — y con razón. Pero cuando tu mercado tiene requisitos de cumplimiento fiscal muy específicos (como el sistema de facturación electrónica DTE de El Salvador), y quieres workflows IA-first desde el día uno, construir te da una precisión que comprar no puede darte.

Esta es la historia de Orbis 8, y lo que aprendí de 6 meses diseñando su arquitectura.

¿Por qué Arquitectura Limpia?

He trabajado en codebases que empezaron siendo "pragmáticos" — llamadas directas a la base de datos en los controladores, lógica de negocio en componentes React, sin separación clara. Funcionan. Hasta que dejan de funcionar.

El momento en que necesitas:

  • Cambiar tu ORM
  • Agregar una cola de mensajes
  • Escribir un test unitario que no requiera una base de datos en ejecución

...te das cuenta de que te has pintado en una esquina.

La Arquitectura Limpia resuelve esto haciendo que las dependencias apunten hacia adentro. Tu lógica de negocio no sabe nada sobre FastAPI, SQLAlchemy ni PostgreSQL. Solo define lo que necesita hacer.

python
# ✅ Entidad de dominio — Python puro, cero dependencias
from dataclasses import dataclass
from decimal import Decimal

@dataclass
class Factura:
    id: str
    total: Decimal
    tasa_impuesto: Decimal = Decimal("0.13")  # IVA 13% El Salvador

    @property
    def monto_impuesto(self) -> Decimal:
        return (self.total * self.tasa_impuesto).quantize(Decimal("0.0001"))

    @property
    def subtotal(self) -> Decimal:
        return self.total - self.monto_impuesto

El Patrón Repository en la Práctica

La interfaz en el dominio, la implementación en la capa de infraestructura:

python
# domain/interfaces/factura_repository.py
from abc import ABC, abstractmethod
from typing import Optional
from domain.entities.factura import Factura

class IFacturaRepository(ABC):
    @abstractmethod
    async def buscar_por_id(self, factura_id: str) -> Optional[Factura]:
        pass

    @abstractmethod
    async def guardar(self, factura: Factura) -> Factura:
        pass

Esto significa que la capa de servicio puede probarse sin una base de datos. Intercambio el repositorio real por un mock en los tests. Esto no es teórico — nos salvó varias veces cuando el esquema de la base de datos estaba cambiando rápidamente.

Lo que Haría Diferente

1. Empezar con el modelo de eventos

Agregamos Pub/Sub más tarde. Añadir comunicación asíncrona a un sistema síncrono es mucho más difícil que diseñarlo desde el principio. Define tus eventos de dominio el día 1, incluso si los implementas después.

2. MDX para la documentación

Usamos Confluence. No deberíamos. Cada decisión arquitectónica que vivió en Confluence murió cuando la persona que la escribió se fue. Mantén las decisiones cerca del código, en archivos .md en el repositorio.

3. Schemas de Postgres para multi-tenancy, no bases de datos separadas

Usamos aislamiento a nivel de schema (access, billing, inventory schemas). Es más sencillo de gestionar que base de datos por tenant y aún proporciona un aislamiento lógico fuerte.

Conclusión

La Arquitectura Limpia requiere inversión inicial. El retorno es un codebase donde:

  • Los desarrolladores senior se incorporan en horas, no días
  • Los agentes de IA (como Claude) pueden razonar sobre la estructura del codebase de forma fiable
  • Las features pueden añadirse sin tocar módulos no relacionados

La arquitectura sirve al equipo. Cuando el equipo crece, la arquitectura debe doblarse con gracia — no romperse.


Próximo post: Arquitectura Hexagonal en Python — cuándo usarla y cuándo es excesiva.

Referencias rápidas

Vista general

Recursos externos

Incluye recursos adicionales en el frontmatter para que aparezcan aquí.

Más en esta serie

Serie: Arquitectura de Software Avanzada

// newsletter

¿Te sirvió este artículo?

Recibe los siguientes en tu inbox. Sin spam, cancela cuando quieras.

Discusión

Escrito por Jorge Ochoa. ¿Encontraste un error?

Abrir en GitHub