---
url: /contracts/MODULE_CONTRACTS.md
---
# Contratos entre Módulos (`MODULE_CONTRACTS.md`)

Estado: **Contrato arquitectónico normativo para ninaku-app-api**.

Este documento define las reglas estrictas de colaboración entre módulos de negocio dentro del monolito modular de Ninaku. Complementa a [ARCHITECTURE.md](../reference/ARCHITECTURE.md) y consolida la especificación definitiva para las fronteras entre módulos en tiempo de compilación.

El objetivo es preservar una propiedad inmutable de los datos y hechos de negocio, manteniendo la colaboración de forma explícita, fuertemente tipada, comprobable y comprensible. Ninaku es una sola aplicación desplegable por defecto; los límites entre módulos no implican llamadas HTTP internas ni una base de datos independiente por módulo.

***

## 1. Regla Principal

Un módulo de negocio puede exponer sus capacidades funcionales **únicamente** a través de contratos públicos explícitos bajo su carpeta `contracts/`. Ningún módulo tiene permitido acceder a repositorios internos, consultas SQL, servicios de infraestructura o manejadores de aplicación de otro módulo.

Cada hecho de negocio tiene un solo módulo propietario. La colaboración entre módulos transfiere información o solicita acciones, pero **nunca transfiere la propiedad del dato**.

```ts
// ❌ INCORRECTO: Violación de fronteras (acceso directo a repositorio ajeno)
class ConfirmSaleUseCase {
  constructor(
    private readonly inventoryRepository: InventoryRepository,
    private readonly paymentRepository: PaymentRepository,
  ) {}
}

// ✅ PREFERIDO: Colaboración explícita mediante Contratos Públicos
class ConfirmSaleUseCase {
  constructor(
    private readonly inventory: InventoryContract,
    private readonly payments: PaymentsContract,
  ) {}
}
```

En este ejemplo, `Sales` solicita a `Inventory` que ejecute una capacidad de reserva. `Sales` nunca escribe ni muta directamente el estado de `Inventory`.

***

## 2. Disposición Interna de un Módulo

Un módulo de negocio evoluciona hacia la siguiente estructura cuando sus responsabilidades realmente existen:

```text
src/modules/inventory/
├── domain/                  <-- Reglas de negocio puras, agregados y eventos de dominio
├── application/             <-- Casos de uso, orquestación y puertos internos
├── infrastructure/          <-- Adaptadores de persistencia, proveedores y servicios
├── presentation/            <-- Controladores HTTP, DTOs y catálogo de fallos públicos
├── contracts/               <-- SUPERFICIE PÚBLICA IN-PROCESS DEL MÓDULO
└── inventory.module.ts      <-- Módulo NestJS que declara providers y exports
```

La carpeta `contracts/` representa la superficie pública que otros módulos tienen permitido consumir. Todo lo demás (`domain/`, `application/`, `infrastructure/`, `presentation/`) es privado e interno del módulo.

* **Módulos pequeños**: Pueden mantener un directorio de contratos plano:
  ```text
  contracts/
  ├── inventory.contract.ts
  └── inventory.contract.types.ts
  ```
* **Módulos grandes**: Pueden categorizar sus archivos de contrato:
  ```text
  contracts/
  ├── commands/
  ├── queries/
  ├── results/
  ├── events/
  └── inventory.contract.ts
  ```

*Nota sobre errores HTTP*: El catálogo de fallos públicos (`PublicFailure`) **no** vive en `contracts/`. Los códigos de error públicos son consumidos por clientes HTTP externos, no por otros módulos de NestJS. Por ende, residen en `presentation/failures/`.

***

## 3. Contrato vs. Puerto

Los términos **Contrato** (`Contract`) y **Puerto** (`Port`) no son intercambiables en la arquitectura de Ninaku Core.

### A. Contrato Público de Módulo (`Contract`)

Un **Contrato** describe lo que un módulo **ofrece** a otros módulos de Ninaku.

```text
Sales Application Use Case
    ↓ (consume)
PaymentsContract (Clase Abstracta bajo src/modules/payments/contracts/)
    ↓ (implementado por)
PaymentsApplicationService
```

```ts
// src/modules/payments/contracts/payments.contract.ts
export abstract class PaymentsContract {
  abstract authorize(input: AuthorizePaymentInput): Promise<AuthorizePaymentResult>;
}
```

### B. Puerto de Aplicación / Dominio (`Port`)

Un **Puerto** describe una dependencia que el propio módulo **requiere** de la infraestructura técnica o de un proveedor externo.

```text
Payments Application
    ↓ (requiere)
PaymentGatewayPort (Interfaz bajo src/modules/payments/application/ports/)
    ↓ (implementado por)
DatafastAdapter (Servicio bajo src/modules/payments/infrastructure/)
```

```ts
// src/modules/payments/application/ports/payment-gateway.port.ts
export interface PaymentGatewayPort {
  authorize(input: GatewayAuthorizationInput): Promise<GatewayAuthorizationResult>;
}
```

**Regla nemotécnica**:

* **Contrato** = Lo que el módulo **OFRECE** hacia afuera (`contracts/`).
* **Puerto** = Lo que el módulo **NECESITA** desde adentro (`application/ports/`).

***

## 4. Modos de Colaboración Permitidos

Ninaku utiliza tres modos principales de colaboración en proceso. Resuelven problemas distintos y no se deben colapsar en un bus genérico de mensajería.

### 4.1 Contrato Síncrono en Proceso

Se utiliza cuando el módulo llamador necesita el resultado inmediato para completar su caso de uso.

```text
Confirm Sale Use Case
    ↓
InventoryContract.reserveStock(...)
    ↓
Inventory Application
```

* La ejecución se realiza dentro del mismo proceso de NestJS (sin llamadas HTTP internas).
* Cuando la invariante de negocio exige atomicidad entre varios propietarios, la llamada puede participar en la misma transacción de PostgreSQL compartiendo un objeto opaco `TransactionContext` provisto por `Foundation`.

### 4.2 Contrato de Lectura (`Read Contract`)

Se utiliza cuando un módulo o un compositor de consultas de pantalla necesita información perteneciente a otro módulo.

```text
POS Workspace Query Composer
   ├── CatalogReadContract
   ├── PricingReadContract
   ├── InventoryReadContract
   └── CustomerReadContract
```

* Los contratos de lectura retornan DTOs de proyección orientados a tareas.
* No exponen entidades del ORM, firmas de repositorios ni SQL builders.

### 4.3 Evento Durable / Outbox

Se utiliza para consecuencias que no necesitan completarse en forma síncrona con el caso de uso iniciador, o que cruzan un límite externo/recuperable.

```text
Sales Transaction
    ├── Actualiza estado propiedad de Sales
    └── Inserta evento de integración SaleConfirmed en la tabla outbox
             ↓ COMMIT
      Outbox Dispatcher (BullMQ)
             ↓
      Procesadores de eventos interesados (Notifications, Analytics, etc.)
```

* Las llamadas a redes o servicios externos **nunca** se ejecutan dentro de la transacción SQL de PostgreSQL. Utilizan el patrón Outbox/Inbox para garantizar reintentos e idempotencia.

***

## 5. Diseño de Contratos Públicos

Un contrato público debe:

* Usar lenguaje e intenciones de negocio, no nombres de tablas SQL.
* Exponer una cantidad acotada y significativa de operaciones.
* Utilizar tipos explícitos para entradas (`Input`), comandos y resultados (`Result`).
* Hacer explícito el contexto de tenant (`organizationId`, `businessUnitId`) cuando sea necesario.
* Evitar la fuga de detalles de tecnología de persistencia o SDKs de terceros.

```ts
// ✅ PREFERIDO
inventory.reserveStock(input)
payments.authorize(input)
pricing.resolvePrice(input)

// ❌ RECHAZADO
inventory.save(entity)
payments.execute('action', payload)
repository.findAll(table, filters)
```

***

## 6. Lo Que Los Contratos NUNCA Deben Exponer

Los siguientes elementos son detalles internos de implementación y tienen prohibido formar parte de la superficie de un contrato público:

1. Instancias de `pg.Pool` o `pg.PoolClient`.
2. Constructores de consultas SQL (`Kysely`, `Knex`) o fragmentos de SQL crudo.
3. Repositorios de persistencia pertenecientes al módulo.
4. Entidades o modelos del ORM/base de datos.
5. Tipos de peticiones/respuestas directos de SDKs de terceros.
6. Operaciones CRUD genéricas que permitan a otro módulo mutar libremente tablas ajenas.

***

## 7. Convención de Composición en NestJS

Dado que las interfaces de TypeScript no existen en tiempo de ejecución, un contrato inyectable requiere un token de runtime. Ninaku utiliza **clases abstractas** como tipo de contrato y token de inyección de dependencias (DI):

```ts
// src/modules/inventory/contracts/inventory.contract.ts
export abstract class InventoryContract {
  abstract reserveStock(input: ReserveStockInput): Promise<ReserveStockResult>;
}
```

El módulo propietario vincula el contrato público con su servicio de aplicación interno:

```ts
// src/modules/inventory/inventory.module.ts
@Module({
  providers: [
    InventoryApplicationService,
    {
      provide: InventoryContract,
      useExisting: InventoryApplicationService,
    },
  ],
  exports: [InventoryContract],
})
export class InventoryModule {}
```

Los módulos consumidores importan `InventoryModule` e inyectan únicamente la clase abstracta `InventoryContract`.

***

## 8. Regla de Transacciones Compartidas Entre Módulos

El caso de uso que inicia la operación es el dueño del límite de la transacción. Cuando la atomicidad exige que varios módulos participen:

```text
Application Use Case (Sales)
    ↓
TenantTransactionRunnerService (Foundation)
    ├── SalesContract/Application escribe en tablas de Sales
    ├── InventoryContract/Application escribe en tablas de Inventory
    └── Outbox escribe el evento de integración
    ↓
COMMIT / ROLLBACK
```

**Reglas obligatorias**:

* La transacción la abre y gestiona `Foundation` mediante `TenantTransactionRunnerService`.
* Los módulos participantes reciben un contexto de transacción opaco (`TransactionContext`).
* Un contrato público **nunca** recibe ni expone `pg.PoolClient`.
* Un módulo convierte internamente el `TransactionContext` opaco en una sesión de consulta mediante la función guardián `asDatabaseSession(context)` dentro de sus propios repositorios en `infrastructure/`.
* Los repositorios no crean transacciones anidadas ocultas.

***

## 9. Eventos de Dominio vs. Eventos de Integración

* **Evento de Dominio**: Mensaje interno del módulo que notifica un cambio de estado dentro del mismo límite del agregado. No se expone a otros módulos.
* **Evento de Integración**: Mensaje público, durable e inmutable que se emite a través de la tabla `outbox` para ser consumido por otros módulos o procesos asíncronos.

Los eventos de integración deben ser minimalistas, versionados, serializables en JSON y libres de clases del dominio interno o tipos de drivers de base de datos.

***

## 10. Fronteras de Importación y Reglas de Linter R1–R9

El script de verificación estática **`tooling/repository/check-module-boundaries.ts`** (ejecutado por `npm run lint:module-boundaries` y por `npm run verify`) evalúa y hace cumplir **nueve reglas innegociables (R1–R9)** en cada compilación:

| Regla | Regla Enforzada | Qué Prohíbe Concretamente | Por Qué Existe | Error Concreto que Evita |
| :---: | :--- | :--- | :--- | :--- |
| **R1** | Un archivo bajo `src/modules/<a>/**` solo puede importar de `src/modules/<b>/**` a través de `src/modules/<b>/contracts/**`. | Importar repositorios, entidades, servicios de aplicación o controladores internos de otro módulo. | Preserva el aislamiento modular dentro del monolito. | Evita que refactorizaciones internas en el módulo B rompan la compilación o lógica del módulo A. |
| **R2** | Los archivos bajo `src/modules/*/{domain,application,contracts,presentation}/**` solo pueden importar el TIPO `TransactionContext` desde `foundation/database/transaction-context.ts`; no pueden importar `pg`, `database-session.ts`, ni los nombres `DatabaseSession`, `asDatabaseSession` o `TRANSACTION_CONTEXT_BRAND`. | Importar el driver de PostgreSQL (`pg`) o funciones de ejecución SQL directa en capas que no sean `infrastructure/`. | Mantiene la abstracción transaccional opaca en el dominio y la aplicación. | Evita que casos de uso o controladores ejecuten consultas SQL crudas o administren transacciones directamente. |
| **R3** | Los archivos bajo `src/modules/*/domain/**` no pueden importar `@nestjs/*`, `zod`, `pg`, `foundation/http/**`, `foundation/database/**` ni `foundation/transactions/**`. | Importar frameworks web, ORMs, librerías de validación o infraestructura dentro del Dominio. | Preserva la pureza y portabilidad del Dominio en Clean Architecture / DDD. | Evita que las reglas puras de negocio queden acopladas a NestJS o PostgreSQL, imposibilitando tests unitarios puros. |
| **R4** | Los archivos bajo `src/foundation/**` no pueden importar `src/modules/**` ni `src/verticals/**`. | Importar cualquier código de negocio o vertical desde la capa base de infraestructura. | `foundation/` provee capacidades genéricas y debe ser completamente agnóstico al dominio. | Evita acoplamientos circulares donde la infraestructura base depende de módulos comerciales. |
| **R5** | Un archivo bajo `src/verticals/<v>/**` solo puede importar de otro dueño (`src/modules/<m>/**` o `src/verticals/<other>/**`) únicamente a través de su `contracts/**`. | Que una vertical acceda a capas internas de un módulo o de otra vertical. | Garantiza que los productos verticales compongan capacidades públicas estándar. | Evita mutaciones directas en tablas de módulos compartidos sin pasar por las validaciones del módulo. |
| **R6** | `TenantTransactionRunnerService` y `SystemTransactionRunnerService` solo se importan desde `src/modules/*/application/**` y `src/verticals/*/application/**`. | Importar los ejecutores de transacciones desde `domain/`, `presentation/` o `infrastructure/`. | La capa de aplicación es la única responsable de coordinar la unidad de trabajo transaccional. | Evita transacciones abiertas desde controladores HTTP o repositorios administrando su propio ciclo de vida. |
| **R7** | Los archivos bajo `src/modules/<m>/{domain,application,contracts,presentation}/**` no pueden importar `src/modules/<m>/infrastructure/**`. | Importaciones apuntando hacia capas exteriores de infraestructura dentro del mismo módulo. | Aplica el principio de Inversión de Dependencias (Inward Dependency Rule). | Evita el acoplamiento rígido de casos de uso a implementaciones técnicas concretas o adaptadores de base de datos. |
| **R8** | Los archivos bajo `src/modules/**` no pueden importar `src/verticals/**`, ni siquiera su `contracts/**`. | Que un módulo de negocio compartido conozca o importe una vertical específica. | Los módulos del Core son agnósticos y reutilizables por cualquier vertical. | Evita contaminar el Core con condicionales por vertical (ej. `if (vertical === 'restaurant')`). |
| **R9** | `SystemTransactionRunnerService` solo se importa desde los directorios permitidos explícitamente (`SYSTEM_TRANSACTION_RUNNER_ALLOWED_DIRECTORIES`). | Usar transacciones sin contexto de tenant en flujos ordinarios de la aplicación. | Obliga al uso de `TenantTransactionRunnerService` para proteger el aislamiento RLS por defecto. | Evita fugas de aislamiento tenant donde una operación de negocio olvida aplicar el contexto de la organización. |

### Excepción Permitida en R9 (`SYSTEM_TRANSACTION_RUNNER_ALLOWED_DIRECTORIES`)

`SystemTransactionRunnerService` abre transacciones sin aplicar contexto de tenant (`organization_id`), por lo que su uso está restringido mediante una lista blanca explícita:

* `src/modules/identity/application`: Permitido porque las operaciones de registro inicial de usuario, autenticación de credenciales y búsqueda global de identidades ocurren **antes** de que exista o se seleccione un contexto de organización activo.

***

## 11. Compatibilidad y Cambios Rompedores

Los contratos in-process residen en la misma unidad de despliegue. Si un contrato público necesita un cambio incompatible:

1. Se refactorizan todas las llamadas en los módulos consumidores dentro de la misma Pull Request.
2. Se ejecutan las pruebas de integración del sistema.
3. No se dejan capas de compatibilidad intermedias ni código deprecado dentro del repositorio.

***

## 12. Colaboración en Verticales

Las verticales (`restaurant`, `retail`, `hospitality`) son consumidoras de módulos del Core compartidos y deben respetar exactamente las mismas reglas de fronteras:

```text
src/verticals/restaurant/
    ├── CatalogContract    (de src/modules/catalog/contracts/)
    ├── SalesContract      (de src/modules/sales/contracts/)
    ├── PaymentsContract   (de src/modules/payments/contracts/)
    └── PrintingContract   (de src/modules/printing/contracts/)
```

Código específico de restaurante (ej. comandas de cocina o asignación de mesas) **nunca** modifica directamente las tablas SQL de `sales` o `catalog`.

***

## 13. Antipatrones Rechazados Explícitamente

1. **Llamadas HTTP internas**: Prohibido realizar peticiones HTTP entre módulos del mismo monolito.
2. **Importar repositorios ajenos**: Un módulo nunca inyecta un repositorio de otro módulo.
3. **Exponer clientes SQL**: Prohibido exportar `pg.PoolClient` o instancias de base de datos en contratos.
4. **Bus genérico no tipado**: Prohibido usar patrones como `eventBus.publish('any_string', payload)` para colaboración síncrona ordinaria.
5. **Condicionales por vertical en el Core**: Prohibido escribir bifurcaciones de código basadas en la vertical dentro de módulos compartidos.

***

## 14. Definición de Hecho (Definition of Done) para una Colaboración entre Módulos

Antes de dar por completada una integración entre módulos, se debe verificar:

1. ¿Se identifica claramente cuál módulo es el **propietario único** del dato?
2. ¿La llamada síncrona utiliza un contrato abstracto bajo `contracts/`?
3. ¿El contrato expone únicamente DTOs y tipos de negocio sin fugas de persistencia?
4. Si la operación requiere atomicidad, ¿se utiliza `TenantTransactionRunnerService` con un `TransactionContext` opaco?
5. Si la operación es asíncrona, ¿se utiliza la tabla `outbox` y un procesador idempotente?
6. ¿El comando `npm run verify` (y en particular `npm run lint:module-boundaries`) pasa en verde sin violaciones de R1–R9?

***

## 15. Índice de Contratos Publicados

Cada contrato real, una vez implementado, se agrega a esta tabla en el mismo cambio que lo publica.

| Contrato | Módulo propietario | Archivo | Consumidores actuales |
| :--- | :--- | :--- | :--- |
| `PlatformAuditContract` | `audit` | `src/modules/audit/contracts/platform-audit.contract.ts` | `identity` (diez transiciones de seguridad de plataforma: cambio y recuperación de contraseña, alta/baja de método de inicio de sesión, bloqueo de cuenta, cierre de sesión y cierre de sesión en todas partes, alta/baja de segundo factor, emisión de códigos de recuperación) |

## 16. Modelo Mental Práctico

```text
Lo que mi módulo OFRECE a otros        → src/modules/<m>/contracts/
Lo que mi módulo NECESITA externamente  → src/modules/<m>/application/ports/
Cómo lo implemento técnicamente        → src/modules/<m>/infrastructure/
Cómo la API HTTP me alcanza            → src/modules/<m>/presentation/
Cómo protejo las reglas de negocio     → src/modules/<m>/domain/ + application/
```
