Files
sf-sim/docs/superpowers/plans/2026-05-06-base-backend-scaffold.md
Jorge 16350e5862 docs(plans): añadir plan de implementación del scaffold base-backend
13 tareas con pasos bite-sized para crear el repo, copiar/editar archivos,
yarn install y commit inicial. Incluye self-review con tabla de cobertura
del spec y verificaciones contra strings prohibidos.
2026-05-06 10:01:18 +02:00

40 KiB

Plan de Implementación — Scaffold base-backend

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Crear ~/code/ref/base-backend como repo git independiente con el esqueleto mínimo de un monorepo TypeScript ESM al estilo sf-sim (Yarn 4 workspaces + DDD/Hexagonal + config de Claude Code), genericizado y listo para arrancar servicios.

Architecture: Repo nuevo, generado por copia selectiva desde sf-sim + ediciones. Sin RabbitMQ, sin Postgres, sin Docker. Solo _template en packages/. Verificación estructural (lint, typecheck, ausencia de strings prohibidos), no TDD — no hay código de aplicación que testear.

Tech Stack: TypeScript ESM, Yarn 4 workspaces, vitest, ESLint, Prettier.

Spec: docs/superpowers/specs/2026-05-06-base-backend-scaffold-design.md (commits 3186b43, 609cd2f).

Convención de paths en este plan: las rutas con prefijo ~/code/ref/base-backend/ o target/ se refieren al repo nuevo. Las rutas con ~/code/ref/sf-sim/ o source/ al actual.

Convención de commits: todo el trabajo se hace sin commitear hasta el final. Un único commit inicial con todo el scaffold (Task 13). Esto es coherente con git init + primer commit; no hay historia previa que preservar en el repo nuevo.


Task 1: Inicializar el repo

Files:

  • Create: ~/code/ref/base-backend/ (directorio)

  • Create: ~/code/ref/base-backend/.git/ (vía git init)

  • Step 1.1: Verificar que el directorio destino no existe

ls -d ~/code/ref/base-backend 2>/dev/null && echo "EXISTS — abortar" || echo "OK"

Expected: OK. Si dice EXISTS — abortar, parar y avisar al usuario.

  • Step 1.2: Crear el directorio y subdirectorios
mkdir -p ~/code/ref/base-backend/.claude/{rules,commands,skills}
mkdir -p ~/code/ref/base-backend/.agents/skills
mkdir -p ~/code/ref/base-backend/packages/_template/config/env
mkdir -p ~/code/ref/base-backend/docs/superpowers/specs
mkdir -p ~/code/ref/base-backend/docs/superpowers/plans
  • Step 1.3: Inicializar repo git
cd ~/code/ref/base-backend && git init -b main

Expected: Initialized empty Git repository in /home/jorge/code/ref/base-backend/.git/.

  • Step 1.4: Verificar estructura
ls -a ~/code/ref/base-backend/ && find ~/code/ref/base-backend -type d -not -path '*/.git*'

Expected: ver .git, .claude, .agents, packages, docs y subdirectorios creados.


Task 2: Archivos de configuración raíz simples

Files:

  • Create: ~/code/ref/base-backend/.editorconfig

  • Create: ~/code/ref/base-backend/.gitattributes

  • Create: ~/code/ref/base-backend/.gitignore

  • Create: ~/code/ref/base-backend/.yarnrc.yml

  • Step 2.1: Copiar .editorconfig tal cual

cp ~/code/ref/sf-sim/.editorconfig ~/code/ref/base-backend/.editorconfig

Verificar: diff ~/code/ref/sf-sim/.editorconfig ~/code/ref/base-backend/.editorconfig → sin output.

  • Step 2.2: Copiar .gitattributes tal cual
cp ~/code/ref/sf-sim/.gitattributes ~/code/ref/base-backend/.gitattributes
  • Step 2.3: Crear .gitignore limpio

Contenido (copia de sf-sim, adaptado quitando referencias a understand-anything y skill-creator-workspace específicas):

.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

# Whether you use PnP or not, the node_modules folder is often used to store
# build artifacts that should be gitignored
node_modules

# Swap the comments on the following lines if you wish to use zero-installs
# In that case, don't forget to run `yarn config set enableGlobalCache false`!
# Documentation here: https://yarnpkg.com/features/caching#zero-installs

#!.yarn/cache
.pnp.*

*.pem


dist/*

.env

# Knowledge graph generado localmente por la skill understand-anything
.understand-anything/

# Settings de Claude Code locales por desarrollador
.claude/settings.local.json

# Workspaces de skill-creator (artefactos de iteración, no la skill en sí)
.agents/skills/*-workspace/

Escribir en ~/code/ref/base-backend/.gitignore.

  • Step 2.4: Crear .yarnrc.yml

Contenido (copia de sf-sim, quitando npmScopes.sf-alvar que apunta al registro de SaveFamily):

compressionLevel: mixed

enableGlobalCache: false

nodeLinker: node-modules

npmRegistryServer: "https://registry.npmjs.org/"

Escribir en ~/code/ref/base-backend/.yarnrc.yml.

  • Step 2.5: Verificar
ls -la ~/code/ref/base-backend/.editorconfig ~/code/ref/base-backend/.gitattributes ~/code/ref/base-backend/.gitignore ~/code/ref/base-backend/.yarnrc.yml
grep -i 'sf-alvar\|savefamilygps' ~/code/ref/base-backend/.yarnrc.yml ~/code/ref/base-backend/.gitignore && echo "FAIL — sigue habiendo referencias" || echo "OK"

Expected: cuatro ficheros listados, OK.


Task 3: package.json raíz

Files:

  • Create: ~/code/ref/base-backend/package.json

  • Step 3.1: Escribir package.json genericizado

Contenido completo (sin migrate, sin @sf-alvar/db-migrate, sin setup:runtime específico, name actualizado):

{
  "name": "base-backend",
  "version": "0.1.0",
  "description": "Monorepo backend TypeScript ESM con DDD/Hexagonal — base para servicios nuevos",
  "packageManager": "yarn@4.12.0",
  "workspaces": [
    "packages/*"
  ],
  "scripts": {
    "test": "vitest watch",
    "build": "rm -rf ./dist && yarn workspaces foreach -Api run build",
    "start": "yarn workspaces foreach -Apiv run start",
    "typecheck": "npx tsc --noEmit",
    "dev": "yarn workspaces foreach -Apiv run dev",
    "lint": "eslint .",
    "lint:fix": "eslint --fix .",
    "format": "prettier --write .",
    "format:check": "prettier --check ."
  },
  "dependencies": {
    "@tsconfig/node22": "^22.0.5",
    "dotenv": "^17.2.3",
    "typescript": "^6.0.3",
    "vite": "^7.3.1",
    "vite-tsconfig-paths": "^6.0.5"
  },
  "devDependencies": {
    "@types/node": "^25.0.3",
    "concurrently": "^9.2.1",
    "prettier": "^3.7.4",
    "tsc-alias": "^1.8.16",
    "tsx": "^4.21.0",
    "vitest": "^4.0.16"
  }
}

Escribir en ~/code/ref/base-backend/package.json.

Diferencias con sf-sim (para tu referencia, no escribir esto en el archivo):

  • name: sim-eventosbase-backend.
  • version: 1.0.00.1.0.
  • Añadido description.
  • scripts.build: quitado && yarn setup:runtime y el script setup:runtime entero.
  • Quitado script migrate.
  • Quitadas dependencias de infra: @sf-alvar/db-migrate, amqp-connection-manager, amqplib, axios, cors, express, pg, uuidv7.
  • Quitadas devDependencies de infra: @types/amqplib, @types/cors, @types/express, @types/pg, @types/supertest, supertest.

Las dependencias de infra se añaden cuando se cree un servicio real que las necesite — no son parte del scaffold.

  • Step 3.2: Verificar JSON válido
node -e "JSON.parse(require('fs').readFileSync('/home/jorge/code/ref/base-backend/package.json', 'utf8'))" && echo "JSON OK"
grep -E '"sf-alvar|"sim-|RabbitMQ|migrate' ~/code/ref/base-backend/package.json && echo "FAIL" || echo "OK"

Expected: JSON OK y OK.


Task 4: tsconfig.json raíz + tsconfig.vitest.json + vitest.config.ts

Files:

  • Create: ~/code/ref/base-backend/tsconfig.json

  • Create: ~/code/ref/base-backend/tsconfig.vitest.json

  • Create: ~/code/ref/base-backend/vitest.config.ts

  • Step 4.1: Escribir tsconfig.json raíz

Contenido (copia de sf-sim quitando paths.sim-consumidor-objenious):

{
  "extends": "@tsconfig/node22/tsconfig.json",
  "compilerOptions": {
    "outDir": "dist",
    "rootDir": "./",
    "composite": true,
    "esModuleInterop": true,
    "sourceMap": true,
    "inlineSources": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "module": "nodenext",
    "moduleResolution": "nodenext"
  },
  "include": [
    "**/*.ts",
    "tsconfig.vitest.json",
    "packages/**/*.ts"
  ],
  "exclude": [
    "dist"
  ]
}

Escribir en ~/code/ref/base-backend/tsconfig.json.

  • Step 4.2: Copiar tsconfig.vitest.json tal cual
cp ~/code/ref/sf-sim/tsconfig.vitest.json ~/code/ref/base-backend/tsconfig.vitest.json

Nota: este fichero extends "./tsconfig.app.json" que no existe — heredamos el bug de sf-sim sin corregirlo (decisión spec § 11). No tocar.

  • Step 4.3: Copiar vitest.config.ts tal cual
cp ~/code/ref/sf-sim/vitest.config.ts ~/code/ref/base-backend/vitest.config.ts
  • Step 4.4: Verificar
node -e "JSON.parse(require('fs').readFileSync('/home/jorge/code/ref/base-backend/tsconfig.json','utf8'))" && echo "OK"
grep 'sim-consumidor-objenious' ~/code/ref/base-backend/tsconfig.json && echo "FAIL" || echo "OK"
ls ~/code/ref/base-backend/tsconfig.json ~/code/ref/base-backend/tsconfig.vitest.json ~/code/ref/base-backend/vitest.config.ts

Expected: dos OK, tres ficheros listados.


Task 5: CLAUDE.md (genericizado)

Files:

  • Create: ~/code/ref/base-backend/CLAUDE.md

  • Step 5.1: Escribir CLAUDE.md

Contenido completo:

# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Comandos

Yarn 4 con workspaces. Desde la raíz:

- `yarn dev` — arranca todos los servicios en watch (tsx).
- `yarn build``tsc --build` por workspace.
- `yarn start` — arranca los servicios desde `dist/`.
- `yarn typecheck``tsc --noEmit` global.
- `yarn lint` / `yarn lint:fix` — ESLint.
- `yarn format` / `yarn format:check` — Prettier.
- `yarn test` — vitest en watch (todos los packages).
- `yarn vitest run packages/<pkg>/path/file.test.ts` — un único test, single-shot.

ESM puro: los imports llevan `.js` aunque el archivo sea `.ts`. Las rutas usan path aliases por servicio (`#config/*`, `#adapters/*`, `#application/*`, `#domain/*`, `#ports/*`).

## Arquitectura

Monorepo de microservicios TypeScript ESM. Cada servicio sigue capas DDD/Hexagonal.

Packages en `packages/`:

- **`_template/`** — plantilla oficial para servicios nuevos. Cópiala, renómbrala y desarrolla desde ahí.

Capas por servicio:

- `domain/` — entidades, eventos, ports (`*.port.ts`).
- `application/` — usecases (`X.usecases.ts`), controllers, validators.
- `infrastructure/` — adapters (repositorios, clientes HTTP, routers Express `*Routes.http.ts`).
- `config/` — composition root + env.

**Errores**: `Result<E, D>` para fallos esperables (negocio, I/O). `throw` solo para invariantes rotas.

**Inyección de dependencias**: manual, por constructor con objeto `args: { dep1, dep2, ... }` (NO posicional). Cableado en `index.ts` o `config/*.config.ts` — nunca dentro de un usecase, controller o adapter. Los tipos del constructor apuntan al **port**, nunca al adapter concreto.

## Skill experta y referencias

Para cualquier trabajo no trivial de diseño, revisión o auditoría arquitectónica, invoca la skill **`sf-backend-architecture`** (modos: asesor de diseño / auditor de servicios — preguntar al usuario si ambiguo). Su auto-trigger tiene recall bajo, invócala explícitamente. Referencias en `.agents/skills/sf-backend-architecture/references/`:

- `HOUSE-STYLE.md` — carpetas, naming de ficheros, DI, Result, ESM.
- `CODE-STYLE.md` — naming a nivel código, idioma, `interface`/`type`, `any`, async, tests.
- `ANTI-PATTERNS.md` — olores conocidos.
- `AUDIT-CHECKLIST.md` — checklist exhaustivo del modo auditor.
- `EVENTS-RABBITMQ.md` — publish/consume, routing keys, retries, DLX, outbox, idempotencia. Aplica si añades RabbitMQ al proyecto; si no, ignorable.

Lee solo la referencia que necesites; no las cargues todas por defecto.

> **Nota**: la skill y sus referencias están escritas con ejemplos del repo origen sf-sim (dominio SIM, RabbitMQ). Adapta los ejemplos al dominio e infraestructura concretos de este proyecto cuando los uses.

Convenciones de contribución en [`CONTRIBUTING.md`](CONTRIBUTING.md).

@.claude/rules/code-style.md
@.claude/rules/git-conventions.md

Escribir en ~/code/ref/base-backend/CLAUDE.md.

  • Step 5.2: Verificar ausencia de strings prohibidos
grep -iE '\b(sim|iccid|objenious|nos|rabbitmq|postgres|sf-alvar|savefamilygps|aplication)\b' ~/code/ref/base-backend/CLAUDE.md

Expected: solo dos resultados — la línea EVENTS-RABBITMQ.md (mención al fichero de referencia, intencional) y la línea con sf-sim en la nota (intencional, advierte el origen). Ningún otro hit. Si hay más, ajustar.


Task 6: CONTRIBUTING.md (genericizado)

Files:

  • Create: ~/code/ref/base-backend/CONTRIBUTING.md

  • Step 6.1: Escribir CONTRIBUTING.md

Contenido completo (copia de sf-sim quitando sección 1.3 Pull Requests con reviewer concreto, y la sección "Excepción para este repo (sf-sim)"):

# Guía de Contribución

¡Gracias por tu interés en contribuir a este proyecto! Este documento establece las pautas para asegurar que nuestro código se mantenga de alta calidad y que nuestra historia de git sea limpia y legible.

## 1. Flujo de Trabajo con Git

### Ramas (Branches)

Utilizamos un modelo de ramas basado en el propósito del cambio.

- **Rama Principal**: `main`. Esta rama siempre debe ser estable y desplegable.
- **Creación de Ramas**: Crea una nueva rama para cada tarea, feature o bugfix. Nunca trabajes directamente sobre `main`.

#### Convención de nombres de ramas

- **Con ticket:** `TICKET-XXX_descripcion-breve` — el ticket actúa como tipo.
- **Sin ticket:** `tipo/descripcion-breve`

Donde `tipo` puede ser:

- `feat`: Nuevas características o funcionalidades.
- `fix`: Corrección de errores.
- `docs`: Cambios solo en documentación.
- `style`: Cambios que no afectan el significado del código (espacios, formato, etc).
- `refactor`: Cambio de código que no arregla un bug ni añade una característica.
- `test`: Añadir tests faltantes o corregir existentes.
- `chore`: Cambios en el proceso de construcción o herramientas auxiliares.

Ejemplos:

- `TICKET-338_descripcion-breve`
- `feat/nuevo-gateway`
- `fix/correlation-id`

### Commits

Seguimos la convención de **Conventional Commits** para nuestros mensajes de commit. Esto ayuda a generar changelogs automáticos y facilita la lectura del historial.

#### Formato

```text
tipo(alcance): descripción breve en imperativo

[Cuerpo opcional: descripción más detallada del cambio]

[Pie opcional: referencias a issues, breaking changes]
```

#### Tipos comunes

- `feat`: Una nueva característica.
- `fix`: Una corrección de un bug.
- `docs`: Cambios en la documentación.
- `style`: Formato, puntos y comas faltantes, etc. (no cambios en la lógica).
- `refactor`: Refactorización de código en producción.
- `perf`: Cambio de código que mejora el rendimiento.
- `test`: Añadir o corregir tests.
- `chore`: Tareas rutinarias, actualizaciones de dependencias, etc.

Ejemplos:

- `feat(auth): implementar login con Google`
- `fix(db): corregir error de conexión en timeout`
- `docs(readme): actualizar instrucciones de instalación`

### Pull Requests

Adapta este apartado al hosting y proceso de revisión de tu proyecto (GitHub, GitLab, Gitea, etc.). Como mínimo:

1. Asegúrate de que tu rama está actualizada con `main`.
2. Abre un Pull Request con descripción clara de los cambios.
3. Enlaza issues relacionados.
4. Asigna revisor(es) y espera aprobación antes de fusionar.

## 2. Estilo de Código

Las convenciones de código (naming, idioma, TypeScript, tests) están en:

- [`.claude/rules/code-style.md`](.claude/rules/code-style.md) — reglas resumidas que aplican a cualquier cambio.
- [`.agents/skills/sf-backend-architecture/references/CODE-STYLE.md`](.agents/skills/sf-backend-architecture/references/CODE-STYLE.md) — detalle completo con ejemplos y justificaciones.
- [`.agents/skills/sf-backend-architecture/references/HOUSE-STYLE.md`](.agents/skills/sf-backend-architecture/references/HOUSE-STYLE.md) — convenciones de arquitectura (capas DDD, ports, Result, ESM).

## 3. Tests

Estrategia detallada en `HOUSE-STYLE.md` § Tests (vitest, tres niveles: domain puro / application con mocks de ports / infrastructure contra BD o testcontainers).

### Política por defecto (TDD)

- **Código nuevo:** se escribe con TDD (test primero) e incluye tests del nivel apropiado (domain puro si aplica; application con mocks; infrastructure si se añade un adapter).
- **Cobertura mínima del repo:** 70%. Cada PR debe mantener o aumentar la cobertura.
- **Bugs corregidos:** requieren al menos un test de regresión que reproduzca el fallo.
- **Antes de abrir un PR:** los tests existentes deben pasar (`yarn vitest run` o `/check`).

---

Equipo de Desarrollo.

Escribir en ~/code/ref/base-backend/CONTRIBUTING.md.

  • Step 6.2: Verificar ausencia de referencias específicas
grep -iE 'savefamilygps|alvarsanmartin|alvar san martin|webint|sf-sim' ~/code/ref/base-backend/CONTRIBUTING.md && echo "FAIL" || echo "OK"

Expected: OK.


Task 7: README.md (nuevo)

Files:

  • Create: ~/code/ref/base-backend/README.md

  • Step 7.1: Escribir README.md

Contenido completo:

# base-backend

Monorepo backend TypeScript ESM con DDD/Hexagonal. Punto de partida para servicios nuevos del mismo perfil arquitectónico.

## Stack

- **Yarn 4** workspaces (`packages/*`).
- **TypeScript** ESM puro (imports con extensión `.js` aunque el archivo sea `.ts`).
- **Path aliases** por servicio (`#config/*`, `#adapters/*`, `#application/*`, `#domain/*`, `#ports/*`).
- **vitest** para tests, **ESLint** + **Prettier** para estilo.

## Arrancar

```bash
yarn install
yarn typecheck
yarn lint
```

## Crear un servicio

1. Copia `packages/_template/` a `packages/<nombre-servicio>/`.
2. Renombra `name` en `package.json` y actualiza `outDir` en `tsconfig.json`.
3. Crea las carpetas `domain/`, `application/`, `infrastructure/` según el patrón hexagonal.
4. Cablea dependencias en `index.ts` o `config/*.config.ts`.

## Documentación

- [`CLAUDE.md`](CLAUDE.md) — contexto de arquitectura para Claude Code.
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — flujo de git, commits, código y tests.
- [`.agents/skills/sf-backend-architecture/`](.agents/skills/sf-backend-architecture/) — skill experta con referencias detalladas (HOUSE-STYLE, CODE-STYLE, ANTI-PATTERNS, AUDIT-CHECKLIST, EVENTS-RABBITMQ).

## Origen

Scaffold extraído de [sf-sim](https://git.savefamilygps.net/SaveFamily/sf-sim). Mantiene el estilo arquitectónico (DDD/Hexagonal + EDA), genericizado y sin la infra específica de SIM (RabbitMQ, Postgres, Docker).

Escribir en ~/code/ref/base-backend/README.md.


Task 8: .claude/ — rules, commands, skills, settings

Files:

  • Create: ~/code/ref/base-backend/.claude/rules/code-style.md

  • Create: ~/code/ref/base-backend/.claude/rules/git-conventions.md

  • Copy: ~/code/ref/base-backend/.claude/commands/audit.md (desde sf-sim)

  • Copy: ~/code/ref/base-backend/.claude/commands/check.md (desde sf-sim)

  • Copy: ~/code/ref/base-backend/.claude/commands/md-lint.md (desde sf-sim)

  • Copy: ~/code/ref/base-backend/.claude/skills/ (skills custom + symlinks)

  • Copy: ~/code/ref/base-backend/.claude/settings.local.json (desde sf-sim)

  • Step 8.1: Escribir .claude/rules/code-style.md (sin "Excepciones de este repo")

Contenido completo:

# Code style — reglas mínimas

Subset siempre cargado. Para detalle completo (ejemplos, casos borde, justificaciones) ver [`.agents/skills/sf-backend-architecture/references/CODE-STYLE.md`](../../.agents/skills/sf-backend-architecture/references/CODE-STYLE.md) — invoca la skill `sf-backend-architecture` para cargarlo bajo demanda.

## Reglas que aplican siempre

- **Comentarios en español.** Inline, JSDoc, TODO. Sin excepciones.
- **Identificadores:** español para dominio, inglés para infraestructura técnica (`httpClient`, `eventBus`).
- **Funciones y métodos:** `camelCase`, verbo en infinitivo (`getUsuario`, `publishEvent`).
- **Variables locales y parámetros:** `camelCase` (`activationDate`, `msgData`).
- **Constantes literales de módulo:** `UPPER_SNAKE_CASE` (`DEFAULT_LIMIT = 1000`). Instancias configuradas (aunque sean `const`) van en `camelCase` (`const eventBus = new RabbitMQEventBus(...)`).
- **Clases, interfaces y tipos:** `PascalCase`.
- **Sin prefijo `I`** en interfaces (`EventBus`, no `IEventBus`).
- **`type` por defecto, `interface` solo para ports** (contratos implementados por adapters).
- **Sin `enum`** — usar uniones de strings literales (`type X = 'a' | 'b'`).
- **Sin `any` en código nuevo** salvo en boundaries de I/O externo con comentario justificando. Preferencia: `unknown` + type guard.
- **`async/await` siempre.** Nada de `.then().catch()` salvo fire-and-forget consciente con comentario.
- **Tests en español:** `describe` e `it` ambos (`it('debería ...')`).
- **Política de tests (TDD por defecto):** todo código nuevo se escribe con TDD y el repo mantiene ≥70% de cobertura. Tests del nivel apropiado (domain puro / application con mocks de ports / infrastructure cuando añade adapter). Bugs corregidos requieren test de regresión que reproduzca el fallo. Estrategia detallada en `HOUSE-STYLE.md` § Tests.
- **Errores:** `Result<E, D>` para fallos esperables, `throw` solo para invariantes rotas.
- **Aplicación:** estricto en código nuevo, oportunista al tocar código existente. No PRs masivos de retrofit.

Escribir en ~/code/ref/base-backend/.claude/rules/code-style.md.

Nota: la sección "Excepciones de este repo" del fichero de sf-sim queda eliminada por completo — esas excepciones eran específicas de sf-sim (cobertura legacy, sim-objenious-cron, typo aplication).

  • Step 8.2: Escribir .claude/rules/git-conventions.md (sin reviewer específico)

Contenido completo:

# Git conventions

Subset siempre cargado. Para guía completa (PRs, flujo de revisión, propósito) ver [`CONTRIBUTING.md`](../../CONTRIBUTING.md).

## Branches

- **Con ticket:** `TICKET-XXX_descripcion-breve` — el ticket actúa como tipo.
- **Sin ticket:** `tipo/descripcion-breve` con `tipo``feat` | `fix` | `docs` | `style` | `refactor` | `test` | `chore`.

Ejemplos: `TICKET-338_descripcion-breve`, `feat/nuevo-gateway`, `fix/correlation-id`.

Nunca trabajar directo sobre `main`.

## Commits — Conventional Commits

Formato:

```text
tipo(alcance): descripción breve en imperativo

[Cuerpo opcional]

[Pie opcional: refs a issues, breaking changes]
```

`tipo``feat` | `fix` | `docs` | `style` | `refactor` | `perf` | `test` | `chore`.

Ejemplos:

- `feat(auth): implementar login con Google`
- `fix(db): corregir error de conexión en timeout`
- `docs(readme): actualizar instrucciones de instalación`

Descripción en español, imperativo, sin punto final, minúscula tras los dos puntos.

## Pull Requests

Adapta este apartado al hosting y revisor de tu proyecto (GitHub, GitLab, Gitea, etc.).

Escribir en ~/code/ref/base-backend/.claude/rules/git-conventions.md.

  • Step 8.3: Copiar comandos /audit, /check, /md-lint tal cual
cp ~/code/ref/sf-sim/.claude/commands/audit.md ~/code/ref/base-backend/.claude/commands/audit.md
cp ~/code/ref/sf-sim/.claude/commands/check.md ~/code/ref/base-backend/.claude/commands/check.md
cp ~/code/ref/sf-sim/.claude/commands/md-lint.md ~/code/ref/base-backend/.claude/commands/md-lint.md
  • Step 8.4: Copiar .claude/skills/ (incluye symlinks)
cp -a ~/code/ref/sf-sim/.claude/skills/. ~/code/ref/base-backend/.claude/skills/

cp -a preserva symlinks. Verificar:

ls -la ~/code/ref/base-backend/.claude/skills/

Expected: ver clean-ddd-hexagonal -> ../../.agents/skills/clean-ddd-hexagonal (el symlink apunta relativo, así que seguirá funcionando una vez .agents/skills/ esté poblado en Task 9).

  • Step 8.5: Copiar settings.local.json tal cual
cp ~/code/ref/sf-sim/.claude/settings.local.json ~/code/ref/base-backend/.claude/settings.local.json

Nota: este fichero no se commitea (está en .gitignore). Se copia para que el usuario tenga las allow-rules de rtk listas en local.

  • Step 8.6: Verificar
ls ~/code/ref/base-backend/.claude/rules/ ~/code/ref/base-backend/.claude/commands/ ~/code/ref/base-backend/.claude/skills/
grep -iE 'savefamilygps|alvarsanmartin|sim-objenious-cron|aplication' ~/code/ref/base-backend/.claude/rules/*.md && echo "FAIL" || echo "OK"

Expected: ficheros listados, OK.


Task 9: .agents/skills/sf-backend-architecture/ con nota inicial

Files:

  • Copy: ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/ (entero desde sf-sim)

  • Modify: ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/SKILL.md (añadir nota tras frontmatter)

  • Step 9.1: Copiar la skill entera (incluye references/ y evals/)

cp -a ~/code/ref/sf-sim/.agents/skills/sf-backend-architecture ~/code/ref/base-backend/.agents/skills/

Verificar:

ls ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/
ls ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/references/

Expected: SKILL.md, references/, evals/ (y los .md dentro de references/).

  • Step 9.2: Localizar el final del frontmatter en SKILL.md
grep -n '^---$' ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/SKILL.md | head -2

Expected: dos líneas. La segunda es el final del frontmatter; la nota se inserta justo después.

  • Step 9.3: Insertar nota inicial tras el frontmatter

Edición: localizar la segunda línea --- del fichero (cierre del frontmatter) e insertar inmediatamente debajo un párrafo en blanco + el siguiente bloque:

> **Nota**: esta skill describe el estilo arquitectónico originado en el repo sf-sim (RabbitMQ + Postgres + DDD/Hexagonal + EDA). Los ejemplos están escritos contra ese dominio (SIM/ICCID/Objenious). Cuando la apliques en un repo distinto, **adapta los ejemplos** al dominio e infraestructura concretos — la arquitectura subyacente sigue valiendo.

Si el contenido inmediatamente después del frontmatter es un encabezado (p. ej. # ...), inserta la nota antes del encabezado, separada por una línea en blanco.

  • Step 9.4: Verificar nota presente y única
grep -c 'esta skill describe el estilo arquitectónico originado' ~/code/ref/base-backend/.agents/skills/sf-backend-architecture/SKILL.md

Expected: 1.

  • Step 9.5: Verificar symlink .claude/skills/clean-ddd-hexagonal resuelve
readlink -f ~/code/ref/base-backend/.claude/skills/clean-ddd-hexagonal
ls ~/code/ref/base-backend/.agents/skills/clean-ddd-hexagonal/ 2>/dev/null && echo "OK" || echo "FAIL — symlink roto, falta copiar clean-ddd-hexagonal"

Si FAIL: copiar también clean-ddd-hexagonal:

cp -a ~/code/ref/sf-sim/.agents/skills/clean-ddd-hexagonal ~/code/ref/base-backend/.agents/skills/

Y reverificar.


Task 10: packages/_template/ adaptado

Files:

  • Create: ~/code/ref/base-backend/packages/_template/package.json

  • Create: ~/code/ref/base-backend/packages/_template/tsconfig.json

  • Create: ~/code/ref/base-backend/packages/_template/index.ts

  • Create: ~/code/ref/base-backend/packages/_template/.env

  • Create: ~/code/ref/base-backend/packages/_template/config/env/index.ts

  • Step 10.1: Escribir _template/package.json

Contenido (name: "template", sin amqplib, imports consolidados con #application/* añadido y #config/* añadido):

{
  "name": "template",
  "version": "1.0.0",
  "description": "Plantilla base para servicios del monorepo",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "tsx index.ts"
  },
  "author": "",
  "license": "ISC",
  "packageManager": "yarn@4.12.0",
  "imports": {
    "#config/*.js": {
      "types": "./src/config/*.ts",
      "default": "./src/config/*.js"
    },
    "#config/*": {
      "types": "./src/config/*.ts",
      "default": "./src/config/*.js"
    },
    "#adapters/*.js": {
      "types": "./src/adapters/*.ts",
      "default": "./src/adapters/*.js"
    },
    "#adapters/*": {
      "types": "./src/adapters/*.ts",
      "default": "./src/adapters/*.js"
    },
    "#application/*.js": {
      "types": "./src/application/*.ts",
      "default": "./src/application/*.js"
    },
    "#application/*": {
      "types": "./src/application/*.ts",
      "default": "./src/application/*.js"
    },
    "#domain/*.js": {
      "types": "./src/domain/*.ts",
      "default": "./src/domain/*.js"
    },
    "#domain/*": {
      "types": "./src/domain/*.ts",
      "default": "./src/domain/*.js"
    },
    "#ports/*.js": {
      "types": "./src/ports/*.ts",
      "default": "./src/ports/*.js"
    },
    "#ports/*": {
      "types": "./src/ports/*.ts",
      "default": "./src/ports/*.js"
    },
    "#tests/*.js": {
      "types": "./__tests__/*.ts",
      "default": "./__tests__/*.js"
    },
    "#tests/*": {
      "types": "./__tests__/*.ts",
      "default": "./__tests__/*.js"
    }
  },
  "dependencies": {
    "@tsconfig/node22": "*",
    "dotenv": "*",
    "typescript": "*"
  },
  "devDependencies": {
    "@types/node": "*",
    "prettier": "*",
    "tsx": "*",
    "vitest": "*"
  }
}

Escribir en ~/code/ref/base-backend/packages/_template/package.json.

Diferencias con sf-sim:

  • name: sim-templatetemplate.

  • Quitado amqplib, @types/amqplib, cors, @types/cors, express, @types/express, supertest, @types/supertest.

  • Añadidos #config/* y #application/* a imports (consolidación con tsconfig paths).

  • Step 10.2: Escribir _template/tsconfig.json

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "outDir": "../../dist/template",
    "baseUrl": ".",
    "paths": {
      "#config/*": [
        "config/*"
      ],
      "#adapters/*": [
        "adapters/*"
      ],
      "#application/*": [
        "application/*"
      ],
      "#domain/*": [
        "domain/*"
      ],
      "#ports/*": [
        "ports/*"
      ],
      "#tests/*": [
        "__tests__/*"
      ]
    }
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "**/*.ts",
    "src/**/*.d.ts"
  ],
  "files": [
    "index.ts"
  ]
}

Escribir en ~/code/ref/base-backend/packages/_template/tsconfig.json.

Diferencias con sf-sim: outDir sim-gestor-eventostemplate, añadido "#application/*".

  • Step 10.3: Copiar _template/index.ts tal cual
cp ~/code/ref/sf-sim/packages/_template/index.ts ~/code/ref/base-backend/packages/_template/index.ts

Verificar:

cat ~/code/ref/base-backend/packages/_template/index.ts

Expected:

console.log(new Date().toISOString())

export default {}
  • Step 10.4: Escribir _template/.env mínimo
ENVIRONMENT=development

Escribir en ~/code/ref/base-backend/packages/_template/.env.

Nota: .env está en .gitignore raíz, así que no se commitea. Se incluye como referencia para quien clone el template.

  • Step 10.5: Escribir _template/config/env/index.ts mínimo
export const env = {
  ENVIRONMENT: String(process.env.ENVIRONMENT ?? "development"),
};

Escribir en ~/code/ref/base-backend/packages/_template/config/env/index.ts.

  • Step 10.6: Verificar limpieza del _template
grep -riE 'rabbitmq|postgres|amqplib|sim-' ~/code/ref/base-backend/packages/_template/ && echo "FAIL" || echo "OK"
node -e "JSON.parse(require('fs').readFileSync('/home/jorge/code/ref/base-backend/packages/_template/package.json','utf8'))" && echo "JSON OK"
node -e "JSON.parse(require('fs').readFileSync('/home/jorge/code/ref/base-backend/packages/_template/tsconfig.json','utf8'))" && echo "JSON OK"

Expected: tres OK.


Task 11: Sustituir aplicationapplication en docs y skills

Files:

  • Modify: cualquier fichero del repo nuevo que mencione aplication (typo de sf-sim).

  • Step 11.1: Inventariar ocurrencias

grep -rl 'aplication' ~/code/ref/base-backend/ 2>/dev/null

Expected: lista de ficheros con la palabra. Probablemente:

  • .agents/skills/sf-backend-architecture/SKILL.md o referencias.
  • Posiblemente comandos en .claude/commands/.

Si la lista está vacía, saltar a Step 11.3.

  • Step 11.2: Sustituir aplicationapplication en cada fichero

Para cada fichero del listado del paso anterior, hacer la sustitución. Usa este comando por fichero (más controlado que un find -exec ciego):

sed -i 's/aplication/application/g' <ruta-fichero>

Importante: revisar si la palabra aparece en algún contexto donde es intencional (ej. una nota explicando "el typo aplication en sf-sim..."). Si la encuentras, conservar esa mención. La sustitución debe ser solo donde el typo se usa como nombre de carpeta/path alias real, no donde se discute como fenómeno histórico.

Caso esperado: en sf-sim hay menciones a la carpeta aplication como path alias / nombre de carpeta que en base-backend ya es application. Esas sí se sustituyen. Si hay un ADR / referencia que cite el typo histórico, se conserva.

  • Step 11.3: Verificar limpieza
grep -rn 'aplication' ~/code/ref/base-backend/ 2>/dev/null

Expected: vacío, o solo menciones intencionales documentadas (poco probable).


Task 12: yarn install y verificación estructural

Files:

  • Genera: ~/code/ref/base-backend/yarn.lock

  • Genera: ~/code/ref/base-backend/node_modules/

  • Step 12.1: yarn install desde la raíz

cd ~/code/ref/base-backend && yarn install

Expected: instalación sin errores. Crea yarn.lock y node_modules/.

Si falla: leer el error y corregir el package.json (raíz o _template). No avanzar hasta que yarn install pase limpio.

  • Step 12.2: yarn typecheck
cd ~/code/ref/base-backend && yarn typecheck

Expected: pasa sin errores. El único .ts en el repo es _template/index.ts, _template/config/env/index.ts y vitest.config.ts — los tres deberían compilar limpios.

Si falla: revisar tsconfig.json (raíz o _template) y arreglar.

  • Step 12.3: yarn lint
cd ~/code/ref/base-backend && yarn lint

Expected: pasa, posiblemente con avisos pero sin errores.

Nota: sf-sim no tiene eslint.config.js ni .eslintrc.* que hayamos copiado. Si yarn lint falla por config faltante, no es bloqueante para esta tarea — anótalo en el resumen final como deuda conocida del scaffold (resolver creando una eslint.config.js mínima en una iteración futura, fuera del alcance de este plan).

  • Step 12.4: yarn format:check
cd ~/code/ref/base-backend && yarn format:check

Expected: pasa o reporta diferencias menores. Si reporta diferencias, ejecutar yarn format para normalizar y volver a ejecutar format:check.


Task 13: Verificación final + commit inicial

Files:

  • Modify: ~/code/ref/base-backend/.git/ (commit)

  • Step 13.1: Verificación de aceptación — strings prohibidos en docs raíz

grep -iEn '\b(iccid|objenious|nos|sim-eventos|sim-shared|sim-entrada|sim-consumidor|sim-objenious|sim-gestor)\b' \
  ~/code/ref/base-backend/CLAUDE.md \
  ~/code/ref/base-backend/CONTRIBUTING.md \
  ~/code/ref/base-backend/README.md \
  ~/code/ref/base-backend/.claude/rules/*.md \
  2>/dev/null && echo "FAIL" || echo "OK"

Expected: OK.

  • Step 13.2: Verificación — referencias a hosting/reviewer concretos
grep -iEn 'savefamilygps|alvarsanmartin|webint-' \
  ~/code/ref/base-backend/CLAUDE.md \
  ~/code/ref/base-backend/CONTRIBUTING.md \
  ~/code/ref/base-backend/README.md \
  ~/code/ref/base-backend/.claude/rules/*.md \
  2>/dev/null

Expected: solo el README.md menciona savefamilygps en la sección "Origen" (intencional, link al repo origen). Cualquier otra mención = FAIL → revisar y limpiar.

  • Step 13.3: Verificación — sin typo aplication
grep -rn 'aplication' ~/code/ref/base-backend/ 2>/dev/null

Expected: vacío (o solo menciones intencionales históricas marcadas).

  • Step 13.4: Verificación — estructura completa
find ~/code/ref/base-backend -maxdepth 3 -not -path '*/node_modules*' -not -path '*/.git*' -not -path '*/.yarn*' | sort

Expected: ver al menos:

  • .editorconfig, .gitattributes, .gitignore, .yarnrc.yml

  • CLAUDE.md, CONTRIBUTING.md, README.md

  • package.json, tsconfig.json, tsconfig.vitest.json, vitest.config.ts, yarn.lock

  • .claude/rules/code-style.md, .claude/rules/git-conventions.md

  • .claude/commands/audit.md, .claude/commands/check.md, .claude/commands/md-lint.md

  • .claude/skills/clean-ddd-hexagonal (symlink)

  • .agents/skills/sf-backend-architecture/SKILL.md

  • .agents/skills/sf-backend-architecture/references/

  • packages/_template/package.json, tsconfig.json, index.ts

  • packages/_template/config/env/index.ts

  • docs/superpowers/specs/, docs/superpowers/plans/

  • Step 13.5: Verificación — no se ha colado infra del stack completo

ls ~/code/ref/base-backend/{deployment,imgs,rabbitmq_plugins,build.local.sh,run.local.sh,stop.local.sh} 2>/dev/null && echo "FAIL" || echo "OK"

Expected: OK.

  • Step 13.6: Stage de todos los archivos
cd ~/code/ref/base-backend && git add -A && git status --short

Expected: lista de ficheros nuevos. Verificar manualmente que .claude/settings.local.json y .env NO aparecen (deberían estar ignorados por .gitignore):

cd ~/code/ref/base-backend && git check-ignore -v .claude/settings.local.json packages/_template/.env

Expected: ambos reportados como ignorados.

  • Step 13.7: Commit inicial
cd ~/code/ref/base-backend && git commit -m "$(cat <<'EOF'
chore: scaffold base-backend monorepo

Esqueleto mínimo de monorepo TypeScript ESM con DDD/Hexagonal extraído
de sf-sim. Genericizado: sin RabbitMQ, sin Postgres, sin Docker. Solo
_template en packages/. Incluye config de Claude Code (CLAUDE.md, rules,
comandos /audit /check /md-lint, skill sf-backend-architecture).

Spec: docs/superpowers/specs/2026-05-06-base-backend-scaffold-design.md
(en repo origen sf-sim).
EOF
)"

Expected: commit creado. Verificar:

cd ~/code/ref/base-backend && git log --oneline

Expected: una línea con el hash del commit inicial.

  • Step 13.8: Resumen final al usuario

Generar mensaje resumen para el usuario con:

  • Path del repo creado: ~/code/ref/base-backend
  • Commit inicial: hash + asunto
  • Resultados de las verificaciones (12.2 typecheck, 12.3 lint, 12.4 format, 13.1-13.5 estructura).
  • Cualquier deuda conocida (ej. si lint falló por config faltante en Step 12.3).
  • Siguientes pasos sugeridos: añadir remote y git push -u, o empezar a clonar _template/ para el primer servicio.

Self-Review

Spec coverage (cada sección del spec → task que la cubre):

Sección spec Task
Lo que incluye / estructura de directorios Task 1 (mkdir) + Tasks 2-10 (contenido)
Lo que NO se copia Task 1 no crea esos dirs; Step 13.5 lo verifica
§ 1 package.json raíz Task 3
§ 2 CLAUDE.md Task 5
§ 3 .claude/rules/code-style.md Task 8 step 8.1
§ 4 .claude/rules/git-conventions.md Task 8 step 8.2
§ 5 CONTRIBUTING.md Task 6
§ 6 README.md Task 7
§ 7 _template/ (package.json, tsconfig, .env, env/index.ts) Task 10
§ 8 sf-backend-architecture/SKILL.md nota Task 9
§ 9 .gitignore Task 2 step 2.3
§ 10 tsconfig.json raíz Task 4 step 4.1
§ 11 tsconfig.vitest.json y vitest.config.ts Task 4 steps 4.2-4.3
§ 12 aplicationapplication Task 11 + verificación 13.3
Verificación de aceptación Task 12 + Task 13 steps 13.1-13.5

Placeholder scan: revisado. Todos los pasos contienen comandos concretos o contenido completo. No hay TBD, TODO, "implement later", "similar to task N", ni descripciones sin código.

Type consistency: el plan no introduce tipos TypeScript propios (es un scaffold). Los nombres de path aliases son consistentes en package.json#imports y tsconfig.json#paths del template (Task 10): #config/*, #adapters/*, #application/*, #domain/*, #ports/*, #tests/*.

Casos edge cubiertos:

  • Symlink .claude/skills/clean-ddd-hexagonal: Task 9 step 9.5 verifica que resuelve y, si no, copia el destino.
  • .gitignore cubre .env y .claude/settings.local.json: Task 13 step 13.6 verifica explícitamente.
  • yarn lint puede fallar por falta de eslint config: Task 12 step 12.3 lo trata como deuda no bloqueante (sf-sim tampoco trae eslint.config.js en lo que copiamos).

Plan listo para ejecutar.