> ## Documentation Index
> Fetch the complete documentation index at: https://internal.softcrum.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Slice canónico #1 (RECONSTRUCCIÓN) — createInitiative: arquetipo de comando síncrono

> El arquetipo de comando síncrono: llega un request, el sistema lo valida, cambia estado dentro de una transacción y responde con el resultado. Reconstruido para validación, porque la mitad de los task specs lo declaran.

> Traducción. Autoritativo: [`../../slices/create-initiative.md`](/slices/create-initiative).
>
> ⚠️ **Reconstruido para validar, no recuperado.** El slice original no está en este repositorio, pero
> cada task spec declara un arquetipo y la mitad declara este, así que los agentes necesitan que
> exista. Etiquetas: **\[derived]** tiene respaldo en los estándares y ADRs que tenemos ·
> **\[inferred]** es deducido · **\[proposed]** es un hueco llenado con criterio.
>
> La *forma* está bien evidenciada — `standards/api.md`, ADR-017 y el slice `trackEvent` la describen
> desde afuera. Lo genuinamente incierto es la disposición de archivos y los nombres, porque todavía
> no existe código de aplicación al que apuntar.

Estado: **RECONSTRUCCIÓN PROPUESTA** · Convive con [`track-event.md`](/es/slices/track-event) (arquetipo de
pipeline asíncrono). Cada task spec declara cuál sigue (DEC-I5).

***

## Para qué sirve este arquetipo

Un **comando síncrono**: llega un request, el sistema valida, cambia estado y responde con el
resultado. El llamador espera, y la respuesta es autoritativa — a diferencia del pipeline asíncrono,
donde la respuesta es "recibido" y el trabajo ocurre detrás de una cola.

Se usa cuando el llamador necesita saber el resultado antes de seguir: crear un registro, publicar una
regla, canjear puntos en un mostrador. Se usa `trackEvent` en cambio cuando al llamador solo le basta
saber que el sistema aceptó la entrada.

La mayor parte de la plataforma es este arquetipo. Es el default, y el pipeline es la excepción.

## Las seis capas

### 1. Ruta — `backend/api` \[derived]

```
POST /v1/{module}/{resource}
```

La ruta hace exactamente cinco cosas y ninguna lógica de negocio:

1. Autenticar — sesión, API key o token de member.
2. Autorizar — **exactamente un** permiso declarado, `{module}.{resource}.{action}` (R16).
3. Validar — un esquema Zod sobre el cuerpo, produciendo un comando tipado.
4. Resolver contexto — tenant, programa u organización, según cómo acote el módulo.
5. Llamar al command handler, y mapear su resultado o error tipado a la respuesta.

Presupuesto: clase Management, p95 \<1 s. Clase Runtime cuando el llamador es una máquina en un camino
caliente, p95 \<300 ms (R18, DEC-D7).

### 2. Command handler — la capa de aplicación del módulo \[derived]

El handler es donde vive la transacción. Es el único lugar del arquetipo que abre una, y abre
exactamente una (R12).

```
handler(command, deps) {
  // deps vienen de makeDeps(ctx) — solo puertos, nunca un SDK de vendor  (R1, R3)
  return db.transaction(async (tx) => {
    // 3. dominio
    // 4. persistencia
    // 5. outbox + auditoría
  })
}
```

Las dependencias llegan por `makeDeps(ctx)`. Un handler que importa un SDK de vendor es un defecto.

### 3. Dominio \[derived]

Funciones puras y servicios de dominio: invariantes, transiciones de estado, cálculos. Sin I/O, sin
conocimiento de Drizzle, sin conocimiento de HTTP.

Esta es la capa donde vale la pena ser estricto. Todo lo de arriba es plomería que se puede regenerar;
las reglas de acá son el producto. **\[inferred]**

### 4. Persistencia — `database/postgres` por un repositorio \[inferred]

Drizzle dentro de la transacción. Toda escritura lleva `tenant_id` y `cell_id` (R6), llega a la base
por `getDb(tenantCtx)` (R8), y obedece el estándar de datos — códigos paramétricos en vez de enums,
montos con su moneda.

*(La indirección por repositorio es **\[inferred]**. R8 exige `getDb(tenantCtx)`, que igual se podría
llamar desde el handler directamente. Propuse un repositorio porque mantiene el handler testeable sin
base de datos, pero es un punto donde el original puede diferir.)*

### 5. Outbox y auditoría — misma transacción \[derived]

No negociable y la razón por la que existe la transacción:

* Uno o más eventos de dominio escritos en el **outbox** (R13), nunca publicados directamente.
* Una fila de `core.audit_log` con actor tipado, el diff completo old→new, y el `correlation_id`
  heredado del request (R15).

Si cualquiera de las tres falla, las tres revierten. Un evento que describe un cambio de estado que no
ocurrió es peor que ningún evento.

### 6. Respuesta \[derived]

El recurso creado o actualizado, o un error tipado como RFC 9457 `problem+json` con un `code` estable.
El handler devuelve un tipo resultado; la ruta lo mapea. Un handler que formatea HTTP es un handler
que no se puede llamar desde un worker.

## Qué copian los agentes de acá

* **La ruta de cinco pasos** y la disciplina de que no contiene lógica de negocio.
* **Una transacción por comando**, abierta en el handler y en ningún otro lugar.
* **Outbox y auditoría dentro de ella**, siempre, con el correlation id atravesándola.
* **Puertos por `makeDeps`**, nunca un import de vendor.
* **Errores tipados** en vez de strings lanzados, para que la ruta los mapee sin inspeccionar mensajes.
* **La capa de dominio se mantiene pura**, que es lo que hace los tests lo bastante rápidos como para
  que la gente los escriba.

## Tests que exige el arquetipo \[inferred]

| Nivel       | Qué demuestra                                                                                                                                          |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Unitario    | Invariantes de dominio y cada rama de falla, sin base de datos                                                                                         |
| Integración | El handler completo contra una base real: el cambio de estado, la fila del outbox y la de auditoría confirman — y una falla inducida revierte las tres |
| RLS         | El comando no puede tocar las filas de otro tenant, con ninguna entrada                                                                                |
| Permiso     | La ruta rechaza a un llamador sin su único permiso declarado                                                                                           |

## En qué difiere de `trackEvent`

|              | createInitiative                        | trackEvent                               |
| ------------ | --------------------------------------- | ---------------------------------------- |
| Respuesta    | El resultado, autoritativo              | `202 Accepted`, trabajo pendiente        |
| Transacción  | Una, en el handler                      | Una en el procesador, después de la cola |
| Falla        | Devuelta al llamador                    | Reintentada, luego a dead letter         |
| Idempotencia | `Idempotency-Key` donde la doc lo marca | Siempre, sobre dos vallas                |
| Presupuesto  | p95 \<1 s Management, \<300 ms Runtime  | \<100 ms para acusar recibo              |

Copiar este arquetipo dentro de un pipeline produce una ruta que hace trabajo pesado en línea y revienta
el presupuesto de ingesta. Copiar el pipeline dentro de un comando produce un llamador que nunca se
entera de si su acción tuvo éxito. Ambos son errores que bloquean la revisión, y por eso cada task spec
declara su arquetipo.

## Lo que hay que validar

1. **La disposición de archivos y los nombres.** Ruta → handler → dominio → repositorio es la forma que
   inferí; el original puede nombrarlas o anidarlas distinto. **\[inferred]**
2. **Si existe una indirección por repositorio** o los handlers llaman a `getDb` directamente.
   **\[inferred]**
3. **El nombre.** "Initiative" sugiere el dominio del módulo Tracker, lo que significa que el slice
   original probablemente vive en un módulo que este repositorio no contiene. Si es así, esta
   reconstrucción debería eventualmente **reemplazarse** por el real y no quedar al lado.
   **\[proposed]**

## Changelog

| Versión | Fecha      | Cambio                              | Por qué                                                        | Autor                  |
| ------- | ---------- | ----------------------------------- | -------------------------------------------------------------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Reconstrucción inicial para validar | La mitad de las task specs declara este arquetipo y no existía | daniel + claude-opus-5 |
