New app apps/mcp (NestJS + @nestjs-mcp/server 2): Streamable HTTP at POST /mcp, stateless. - 24 tools: projects (mine, public, details with settings, board, stats, analytics, activity), tickets (my work, search, details with comments and history, create, update, move, assign, release, comment, link), releases (list, upcoming, details with progress and burnup, create, publish) and users (whoami, search). Admin operations stay in the web. - No credentials of its own: each call's Authorization header (a Pireon token) is forwarded to the API; without it only public projects are read. - Typed REST client generated from apps/api/openapi.json (openapi-fetch + openapi-typescript via `nx run mcp:openapi-types`); API refusals become readable tool errors; parameters validated with zod. - Tests: the real MCP against a simulated API (token forwarding, errors, validation, writes). - Docker target `mcp` (Node 24) and an optional compose service under the `mcp` profile; zod declared as a root dependency. - Docs: chapter 9 «Servidor MCP», references in 01/06/07 and the index; Word/PDF regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
7.4 KiB
Markdown
98 lines
7.4 KiB
Markdown
# 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 "<token>"
|
|
```
|
|
|
|
## 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 serve mcp` | Servidor MCP en `http://localhost:3010/mcp`, contra la API local ([MCP](09-mcp.md)) |
|
|
| `npx nx run mcp:openapi-types` | Regenera los tipos REST del MCP desde `apps/api/openapi.json` |
|
|
| `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-<contexto>-<capa>`, `web-<contexto>-<capa>`) |
|
|
|
|
### 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) |
|