From 16350e586290f158412d328d11e71965729dae44 Mon Sep 17 00:00:00 2001 From: Jorge Date: Wed, 6 May 2026 10:01:18 +0200 Subject: [PATCH] =?UTF-8?q?docs(plans):=20a=C3=B1adir=20plan=20de=20implem?= =?UTF-8?q?entaci=C3=B3n=20del=20scaffold=20base-backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../plans/2026-05-06-base-backend-scaffold.md | 1163 +++++++++++++++++ 1 file changed, 1163 insertions(+) create mode 100644 docs/superpowers/plans/2026-05-06-base-backend-scaffold.md diff --git a/docs/superpowers/plans/2026-05-06-base-backend-scaffold.md b/docs/superpowers/plans/2026-05-06-base-backend-scaffold.md new file mode 100644 index 0000000..2bfa2f8 --- /dev/null +++ b/docs/superpowers/plans/2026-05-06-base-backend-scaffold.md @@ -0,0 +1,1163 @@ +# 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** + +```bash +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** + +```bash +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** + +```bash +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** + +```bash +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** + +```bash +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** + +```bash +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): + +```gitignore +.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): + +```yaml +compressionLevel: mixed + +enableGlobalCache: false + +nodeLinker: node-modules + +npmRegistryServer: "https://registry.npmjs.org/" +``` + +Escribir en `~/code/ref/base-backend/.yarnrc.yml`. + +- [ ] **Step 2.5: Verificar** + +```bash +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): + +```json +{ + "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-eventos` → `base-backend`. +- `version`: `1.0.0` → `0.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** + +```bash +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`): + +```json +{ + "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** + +```bash +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** + +```bash +cp ~/code/ref/sf-sim/vitest.config.ts ~/code/ref/base-backend/vitest.config.ts +``` + +- [ ] **Step 4.4: Verificar** + +```bash +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: + +````markdown +# 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//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` 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** + +```bash +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)"): + +````markdown +# 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** + +```bash +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: + +````markdown +# 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//`. +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: + +````markdown +# 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` 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: + +````markdown +# 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** + +```bash +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)** + +```bash +cp -a ~/code/ref/sf-sim/.claude/skills/. ~/code/ref/base-backend/.claude/skills/ +``` + +`cp -a` preserva symlinks. Verificar: + +```bash +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** + +```bash +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** + +```bash +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/`)** + +```bash +cp -a ~/code/ref/sf-sim/.agents/skills/sf-backend-architecture ~/code/ref/base-backend/.agents/skills/ +``` + +Verificar: + +```bash +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`** + +```bash +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: + +```markdown +> **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** + +```bash +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** + +```bash +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`: + +```bash +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): + +```json +{ + "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-template` → `template`. +- 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`** + +```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-eventos` → `template`, añadido `"#application/*"`. + +- [ ] **Step 10.3: Copiar `_template/index.ts` tal cual** + +```bash +cp ~/code/ref/sf-sim/packages/_template/index.ts ~/code/ref/base-backend/packages/_template/index.ts +``` + +Verificar: + +```bash +cat ~/code/ref/base-backend/packages/_template/index.ts +``` + +Expected: + +```ts +console.log(new Date().toISOString()) + +export default {} +``` + +- [ ] **Step 10.4: Escribir `_template/.env` mínimo** + +```env +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** + +```ts +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** + +```bash +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 `aplication` → `application` en docs y skills + +**Files:** +- Modify: cualquier fichero del repo nuevo que mencione `aplication` (typo de sf-sim). + +- [ ] **Step 11.1: Inventariar ocurrencias** + +```bash +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 `aplication` → `application` 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): + +```bash +sed -i 's/aplication/application/g' +``` + +**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** + +```bash +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** + +```bash +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`** + +```bash +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`** + +```bash +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`** + +```bash +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** + +```bash +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** + +```bash +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`** + +```bash +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** + +```bash +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** + +```bash +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** + +```bash +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`): + +```bash +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** + +```bash +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: + +```bash +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 `aplication` → `application` | 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.