Skip to main content
Traducción. Autoritativo: ../../slices/create-initiative.md. ⚠️ 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 (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]

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).
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]

En qué difiere de trackEvent

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