# 6. Desarrollo ## 6.1 Requisitos - Node 22 y npm 10. - Docker (PostgreSQL, MongoDB y Redis). - Acceso al Nexus de Pireon para `@pi/ui`. El scope está en `.npmrc` (sin token); el token va en tu `~/.npmrc`: ```sh npm config set //nexus.pireon.dev/repository/npm-releases/:_authToken "" ``` ## 6.2 Puesta en marcha ```sh npm install docker compose up -d postgres mongo redis cp .env.example .env # ajusta PIREON_* npx nx serve api # http://localhost:3000 (GraphiQL en /graphql) npx nx serve web # http://localhost:4200 ``` `nx serve web` usa `apps/web/proxy.conf.json` para enviar `/api` y `/graphql` (también el WebSocket) a la API, así que la web y la API comparten origen igual que en Docker. ## 6.3 Comandos | Comando | Qué hace | | -------------------------------- | --------------------------------------------------------------------------------- | | `npx nx run-many -t lint` | Lint de todo, incluidas las reglas de capas y contextos | | `npx nx run-many -t test` | Tests unitarios (dominio y aplicación, API y web) | | `npx nx e2e api` | Flujo completo de la API contra Docker, con Pireon simulado | | `npx nx run-many -t build` | Builds de la API y la web | | `npx nx run web:codegen` | Regenera los tipos GraphQL del cliente desde `apps/api/schema.gql` | | `npx nx run api:prune` | Build de la API con `package.json` y lockfile podados (lo usa Docker) | | `npx nx graph` | Grafo de dependencias entre proyectos | | `npx nx test api-tracker-domain` | Tests de una librería concreta (`api--`, `web--`) | ### Tests | Dónde | Qué se prueba | Herramienta | | ----------------------------------- | ---------------------------------------------------------------------- | ------------------------ | | `api/*/domain` | Reglas de los agregados y reconstrucción desde el historial de eventos | Jest | | `api/*/application` | Commands contra un event store en memoria (incluida la concurrencia) | Jest + `@nestjs/testing` | | `apps/api/e2e` | Todo el flujo: GraphQL → Postgres → Redis → Mongo → subscriptions | Jest contra Docker | | `web/*/domain`, `web/*/application` | Sesión y reducers del tablero | Jest / Vitest | | `apps/web` | Rutas y guards | Vitest | El e2e sustituye a Pireon por usuarios fijos (`token-ana`, `token-bob`…) con `overrideProvider(IdentityProvider)`; el resto de la pila es real. ## 6.4 Cómo añadir una funcionalidad Ejemplo: **marcar un ticket como bloqueado con un motivo**. El recorrido es siempre el mismo, de dentro hacia fuera. ### API 1. **Dominio** (`libs/api/tracker/domain`) - Evento `issue-blocked.event.ts` con `@AutowiredEvent`, que extiende `TrackerEvent`. - En `Issue`, el método `block(actorId, reason)` valida y hace `this.apply(new IssueBlockedEvent(…))`; el handler `onIssueBlockedEvent(event)` cambia el estado. - Test en `issue.spec.ts`, incluida la reconstrucción desde el historial (`replay`). 2. **Aplicación** (`libs/api/tracker/application`) - `commands/block-issue.command.ts` y `block-issue.command-handler.ts`: rehidratar (`WriteAccess`), llamar al dominio y `commitAndWait()`. Registrar el handler en `command-handlers.ts`. - Método en `IssuesService`. - Proyección `event-handlers/issue-blocked.event-handler.ts` (`@EventsHandler`) que actualiza el read model y avisa por `ReadModelNotifier`. Añadir el evento al `ActivityEventHandler` y una frase en `activity-summary.ts`. - Si el read model cambia, añadir el campo a `IssueReadModel` (dominio) y al esquema Mongo (infraestructura). 3. **Presenters** (`libs/api/tracker/presenters`): input y mutation `blockIssue` en `IssueResolver`, que llama al service. 4. Arrancar la API para regenerar `apps/api/schema.gql`. ### Web 5. `npx nx run web:codegen` tras añadir la mutation a `issues.graphql`. 6. **Dominio**: método en el puerto `IssuesGateway`. **Infraestructura**: implementarlo en `GraphqlIssuesGateway`. 7. **Aplicación**: evento `blockRequested` en `issuePageEvents`, su efecto en `IssueStore` (`runCommand`) y el método `block(reason)` en `IssueFacade`. 8. **Presenters**: botón y formulario en la página del ticket que llaman a `facade.block(reason)`. El lint impide atajos: si un resolver intenta usar un repositorio o una página intenta usar Apollo, falla. Y como `application` solo exporta facades, una página tampoco puede tocar un store ni despachar eventos por su cuenta. ## 6.5 Problemas conocidos | Síntoma | Causa y solución | | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `npm install` falla con `Cannot read properties of null (reading 'edgesOut')` | Bug de npm con un peer opcional de Vite que arrastra vitest 5. El `overrides` de `package.json` fija vitest; no lo quites | | `401` al instalar `@pi/ui` | Falta el token del Nexus en `~/.npmrc` (o `NEXUS_TOKEN` al construir con Docker) | | `failed to stat /root/.npmrc` al desplegar | Versión antigua del `docker-compose.yml`: ahora el token llega por `NEXUS_TOKEN` (ver [Despliegue](07-despliegue.md)) | | `nx serve web` arranca en el puerto 3000 | Nx carga el `.env` raíz (`PORT=3000`) en todas las tareas; `apps/web/.env` lo fija en 4200 | | La API no ve un evento nuevo al deserializar | La clase del evento no tiene `@AutowiredEvent` o no se importa | | Un componente de `@pi/ui` lanza `Unable to find icon` | Falta registrar ese icono en `libs/web/shared/ui/src/lib/icons.ts` | | Tablas antiguas (`projects`, `issues`, `outbox`) en tu Postgres local | Restos de una versión anterior sin event sourcing; `docker compose down -v` empieza de cero | | El login con Pireon falla (`unauthorized_client`, `client_secret`…) | Configuración de los clientes OAuth: ver [Si el login falla](05-autenticacion.md#si-el-login-falla) |