---
name: forja-panel-mcp-operator
description: Use when creating, fixing, or operating Forja factory apps.
---

# Forja — La fábrica de apps (manual completo)

Forja es la software factory de Essentia HS. Este es el **manual único** para crear apps, iterar sobre ellas, operar el panel y mantener la seguridad. Cargá esta skill SIEMPRE que trabajes con Forja.

## Índice
1. [Conceptos clave](#1-conceptos-clave)
2. [Conexión al panel (MCP)](#2-conexión-al-panel-mcp)
3. [Crear una app NUEVA (ciclo de pedidos)](#3-crear-una-app-nueva-ciclo-de-pedidos)
4. [Iterar sobre una app EXISTENTE (fix directo)](#4-iterar-sobre-una-app-existente-fix-directo)
5. [Seguridad (obligatorio)](#5-seguridad-obligatorio)
6. [Renombrar un tenant/app](#6-renombrar-un-tenantapp)
7. [Onboarding del humano](#7-onboarding-del-humano)
8. [Errores comunes](#8-errores-comunes)

---

## 1. Conceptos clave

- **TENANT = CLIENTE.** Cuando el usuario diga "creá el cliente X", se crea un **tenant** con `crear_tenant`. NO existe `crear_cliente`.
- **APP** = un tipo de aplicación del catálogo (imagen docker).
- **AMBIENTE** = staging (prueba) o production (real) de un tenant.
- **PEDIDO** = solicitud de crear una app NUEVA (queda "pendiente" en el tablero).
- **MCP** = el "teléfono" por el que el gemelo habla con Forja.
- **API key** = la llave para entrar al teléfono (fail-closed: sin key no entra).

**Regla de oro:** NO inventar herramientas ni endpoints. El panel expone 21 tools por MCP; si el usuario pide algo que no coincide exactamente, mapealo al concepto correcto (cliente→tenant, app nueva→solicitar_app, diseño→disenar_app).

---

## 2. Conexión al panel (MCP)

```
URL:        http://100.88.15.6:3020/mcp
Protocolo:  MCP streamable HTTP (POST JSON-RPC / GET SSE)
API Key:    e5b8bb7a4ce8155227936c88d2877d3bd78e6d302d4d48af8d9d0a5ee3d7d743
Header:     X-Api-Key
```

Headers en cada request:
```
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2024-11-05
X-Api-Key: <key>
```

Flujo: `initialize` → guardás `mcp-session-id` (del header de la respuesta) → usás ese id en `tools/list` y `tools/call`.

**IDENTIFICACIÓN (obligatorio):** el body del `initialize` DEBE incluir tu nombre en `params.clientInfo.name` (ej. `hermes-cortex`). Así la auditoría del panel registra `mcp:TU_NOMBRE`. Sin nombre, queda `mcp` genérico y no se puede rastrear.

### Las 21 tools

**Apps:** `listar_apps`, `crear_app`, `eliminar_app`, `solicitar_app`, `solicitar_app_y_esperar`, `listar_pedidos`, `marcar_pedido_lista`, `eliminar_pedido`.
**Gates:** `aprobar_analisis`, `rechazar_analisis`, `aprobar_diseno`, `rechazar_diseno`.
**Diseño:** `disenar_app`.
**Tenants:** `listar_tenants`, `crear_tenant`, `eliminar_tenant`.
**Ambientes:** `listar_ambientes`, `crear_ambiente`, `eliminar_ambiente`.
**Verificación:** `verificar_entrega`.
**Salud:** `listar_estado`.

---

## 3. Crear una app NUEVA (ciclo de pedidos)

**FLUJO AUTOMÁTICO SIN GATES HUMANOS.** La fábrica prepara TODO de punta a punta sin esperar aprobación. El humano solo ve el resultado final y ajusta después con pedidos nuevos. NO te detengas a esperar validación.

1. `listar_apps` → confirmá que NO existe (si existe, es iteración, sección 4).
2. `solicitar_app_y_esperar` con `{ nombre, slug, descripcion, entidades }`.
   - **La `descripcion` es un CONTRATO de datos, no una aspiración.** Escribí: qué hace, para quién, qué pantallas, qué endpoints consume el frontend, qué muestra si falla. Si está vaga, la app sale mal.
3. **AUTO-APROBÁ el análisis** con `aprobar_analisis { id }` (sin feedback). NO esperes al humano.
4. **AUTO-APROBÁ el diseño** con `aprobar_diseno { id }` (sin feedback). NO esperes al humano.
5. Cuando esté lista, corré `verificar_entrega` con el `tenantId`. SOLO si da `ok: true`, marcá el pedido como lista y avisale al humano con URL + credenciales.
6. Si `verificar_entrega` da `ok: false`, NO entregues. Corregí o reportá.

**Nota sobre `disenar_app`:** la llamada puede cortar la conexión (tarda 1-3 min) — NO es un fallo. Verificá `/home/usuario/forja-data/designs/<slug>/` (App.jsx + DESIGN.md) y seguí.

**Nota sobre el monitor:** el cron "Forja procesar pedidos" (cada 1m) procesa los pedidos. Si un pedido queda en `generando` sin proceso de build vivo (docker build / ocd-cli) para ese slug, es un zombie: resetealo a `pendiente` y reprocesalo.

---

## 4. Iterar sobre una app EXISTENTE (fix directo)

**REGLA INEQUÍVOCA — NO SE NEGOCIA:** si la app ya existe (directorio en `/home/usuario/Desarrollos/forja/<slug>-app/` o container `app-<slug>` corriendo), **NUNCA crees un pedido en el tablero** y **NUNCA uses `disenar_app`** (esa tool es SOLO para apps nuevas). Ni fixes, ni mejoras, ni REDISEÑO, ni **ANÁLISIS DE DISEÑO**. Todo lo que toque una app existente se edita **directo sobre el código**, rebuild, recrea el container — en minutos, no en horas. Crear un pedido o llamar `disenar_app` para una app existente es un error grave: queda fantasma y el flujo se tranca.

**Detección (hacela SIEMPRE primero):** ¿existe el directorio/container de la app? Si NO existe en ningún lado → es NUEVA → sección 3. Si existe → EXISTENTE → iteración directa (esta sección).

### ⚠️ ¿ESTÁS CORRIENDO EN EL HOST DE FORJA O EN UN GEMELO REMOTO?

El código de las apps vive en **un solo lugar**: `/home/usuario/Desarrollos/forja/` en el host del panel (ThinkCentre, Tailscale `100.88.15.6`). Solo un proceso puede editarlo: un Hermes que corra **en ese host** (el principal o el gemelo `hermes-cortex` que está en la máquina de Forja).

Si estás en un **gemelo remoto** (otro VPS / otra máquina, ej. HSPro):
- **NO tenés** el directorio `/home/usuario/Desarrollos/forja/` (no existe en tu filesystem).
- **NO intentes SSH** al host de Forja: no hay credenciales/passphrase configuradas y falla.
- **NO uses `disenar_app`** ni crees pedidos — ambos son para apps NUEVAS y no editan el código existente.

**Lo que SÍ podés hacer (y es el canal correcto para un gemelo remoto):**
1. **Análisis / veredicto de diseño:** capturá la UI como la ve el usuario (o la describís) y pasala por Open CoDesign — el análisis se hace en tu máquina, no necesita el filesystem. El entregable es el veredicto + el spec del rediseño.
2. **Delegá la edición al gemelo que SÍ está en el host:** el que corre sobre Forja (el principal de este host o `hermes-cortex`). Pasale el cambio concreto (qué archivo de `public/` tocar, qué CSS/HTML cambiar). Él edita, rebuild, recrea el container y verifica.
3. Si tenés acceso al **MCP** y una vía de ejecución en el host (ej. se comparte el script de deploy), usala — pero el editor del archivo SIEMPRE es el gemelo del host.

En resumen: **el que edita el código es el gemelo que vive en el host de Forja.** Un gemelo remoto hace el análisis/diseño y delega la aplicación a ese gemelo. Ningún gemelo debe inventar su vía (SSH, pedidos, disenar_app) para tocar una app existente.

**Cuándo es iteración directa (todo esto):**
- Fix de un bug (ej. "no reproduce audio")
- Mejora de UX (ej. "mové el botón", "botón arriba")
- Cambio de diseño (REDISEÑO) de una app desplegada
- **Análisis/auditoría de diseño de una app existente** (ej. "analizá con Open CoDesign si el diseño es bueno")
- Cualquier cosa sobre una app que el humano está probando

**Cuándo SÍ es un pedido nuevo (sección 3):** la app NO existe como directorio ni container (es un producto distinto que no está desplegado).

### El flujo directo (5 pasos) + análisis de diseño

Para **fix/mejora/cambio**: editá el código directo y seguí los pasos de abajo.
Para **ANÁLISIS DE DISEÑO** (lo que pidió Enrique): NO se crea pedido. Se toma el HTML/CSS real (`public/` de la app), se le pasa a Open CoDesign junto con el contexto del usuario objetivo y los criterios (usabilidad, jerarquía, consistencia, accesibilidad WCAG, estética), y Open CoDesign devuelve un veredicto. Después se aplican las mejoras directo. El entregable es el veredicto + las mejoras aplicadas, NO un pedido ni un slug fantasma.

### Pasos de la iteración directa

1. **Ubicá la app.** Código en `/home/usuario/Desarrollos/forja/<slug>-app/`. Container `app-<slug>`.
2. **Editá el código.** Cambiá lo que haga falta en `src/` o `public/` (frontend en `public/`).
3. **Rebuild + recrear container.** Sacá el env del container actual (NO lo inventes):
   ```bash
   docker inspect app-<slug> --format '{{range .Config.Env}}{{println .}}{{end}}'
   cd /home/usuario/Desarrollos/forja/<slug>-app
   docker build -t <slug>-app:latest .
   docker rm -f app-<slug>
   docker run -d --name app-<slug> -p <puerto>:3002 \
     -e APP_NAME="..." -e DATA_DIR=/app/data -e ADMIN_EMAIL=... -e ADMIN_PASSWORD=... \
     -e MCP_API_KEY=... -e JWT_SECRET=... -e TRUST_PROXY=true -e SERVE_HTTP=true -e NODE_ENV=production \
     -v /home/usuario/forja-data/tenants/<slug>:/app/data \
     <slug>-app:latest
   ```
4. **Verificá.** Health + proxy + el flujo que arreglaste:
   ```bash
   curl -s -o /dev/null -w '%{http_code}\n' http://localhost:<puerto>/health
   curl -s -o /dev/null -w '%{http_code}\n' http://localhost:3021/<slug>/
   ```
5. **Probá el fix real** (no solo health). Si es frontend, probá en el navegador el flujo completo.

### Pitfalls de la iteración

- **El env del container es crítico.** No lo inventes — sacalo con `docker inspect`. Si cambiás JWT_SECRET, el login se rompe.
- **El volumen es crítico.** Siempre `-v /home/usuario/forja-data/tenants/<slug>:/app/data`. Si lo sacás, perdés los datos.
- **El puerto** sale de `docker ps`. No lo cambies.
- **No toques el pedido del panel** (si existe, ya está `lista`). La iteración es directa sobre el código.

---

## 5. Seguridad (obligatorio)

**Cargá estas reglas SIEMPRE que escribas o revises código de Forja.** Salieron de una auditoría real que encontró bugs explotables.

### 🔴 Reglas críticas
1. **NUNCA descartes `timingSafeEqual`.** `verifyPassword` hacía `timingSafeEqual(a,b); return true;` — descartaba el resultado y cualquier password era válida. Devolvé el resultado.
2. **Fail-closed en secrets.** `JWT_SECRET` (y cualquier secret): el arranque DEBE fallar si falta, nunca fallback a `''` o `'change-me'`. Turnstile: si `TURNSTILE_SECRET` está configurado, login sin token → 400.
3. **Gate de roles en TODA ruta sensible.** `GET /api/tenants/:id/credentials` (passwords en claro) requiere `requireAdmin`. Cualquier GET que exponga datos sensibles necesita gate de rol.
4. **Escapar SIEMPRE la interpolación de DB en HTML.** XSS almacenado: toda interpolación en `innerHTML`/`onclick` pasa por `esc()`.
5. **Nunca passwords por defecto públicas.** Crear usuario sin password → 400, nunca `bcrypt.hash(password || "forja123")`.
6. **No filtrar `err.message` en respuestas 500.** Devolver `{ error: 'Error interno' }` y loguear el detalle.
7. **Comparación timing-safe para secrets.** Nunca `===`/`!==` para passwords. Usar `crypto.timingSafeEqual`.
8. **Rate limiting y topes.** Login: 5/15min. Formularios públicos: rate limit. `limit` de paginación con tope.

### ⚠️ Los fixes de seguridad van al TEMPLATE, no a apps puntuales
Cualquier fix de seguridad se aplica PRIMERO a `/home/usuario/Desarrollos/forja/app-template/` y luego se propaga a las apps desplegadas. Si se arregla solo en una app, la próxima app nace vulnerable.

Fixes que deben estar en TODA app:
1. **JWT_SECRET fail-closed** — `getJwtSecret()` lanza si falta/`change-me`/`''`; `index.ts` hace `process.exit(1)` si falla.
2. **verifyJWT con `algorithms:['HS256']` + `issuer:'forja'`**.
3. **`requireAdmin`** — middleware (403 si `rol !== 'admin'`). Aplicar a rutas de ESCRITURA y GET sensibles.
4. **Rate limits** — `express-rate-limit`: login 5/15min, formularios públicos 10-20/min.
5. **Trust proxy con valor específico** — `app.set("trust proxy", 1)` (NO `true`).

### Las 6 reglas del template (rompen login/proxy si se omiten)
1. **`window.TENANT_BASE` en el frontend.** Todo `fetch()`/redirect usa `const BASE = window.TENANT_BASE || ''` y `fetch(\`${BASE}/api/...\`)`. NUNCA paths absolutos `/api/...`.
2. **Cookie `secure` condicional.** `secure: isProduction && !serveHttp`. Si se fuerza `secure:true` en HTTP, el navegador no envía la cookie.
3. **`/me` y rutas autenticadas DEBEN usar `verifyJWT`.** Si no, `req.user` es undefined → 401 siempre.
4. **helmet con `script-src 'self' 'unsafe-inline'`.** NO usar `helmet()` por defecto (bloquea scripts inline).
5. **Desactivar `upgrade-insecure-requests` cuando `SERVE_HTTP=true`.** Si no, el fetch falla con "Failed to fetch".
6. **Exponer MCP en `/mcp` con API key OBLIGATORIA (fail-closed).** Sin key → 401, NO `next()`.

---

## 6. Renombrar un tenant/app

Cuándo: el cliente pide cambiar el NOMBRE y NO quiere ninguna referencia al slug/nombre viejo. Ej: ConGA → Ganader-IA.

**IMPORTANTE:** un "cambio de nombre" puede ser solo branding (`APP_NAME`, títulos). Un RENAME COMPLETO elimina además el slug (rutas del proxy, containers, DB, imagen, data dir, panel). Preguntá al usuario si quiere mantener el slug o eliminarlo.

### Orden de ejecución (verificado)
1. **Pausar el cron** `Forja procesar pedidos de apps`.
2. **Renombrar refs en código:** `src/db/client.ts` (DB_PATH), `src/mcp/server.ts` (name), `package.json` (name/description), `src/index.ts` (APP_NAME), `public/*.html` (title/h1).
3. **Rebuildear imagen:** `mv <viejo>-app <nuevo>-app`, `docker build -t forja/<nuevo>-app:latest .`.
4. **Migrar datos** (ver WAL abajo).
5. **Registrar en el panel:** `POST /api/apps` (imagen nueva), `POST /api/tenants` (containerUrl fijo), `POST /api/tenants/:id/ambientes` (staging).
6. **Alinear password admin** (ver pitfall).
7. **Verificar:** proxy 200, login por proxy, datos accesibles.
8. **Limpiar artefactos viejos:** containers, imágenes, data dirs, registros del panel (tenant, app, pedidos que mencionen el nombre viejo). Barrido hasta residuales = 0.
9. **Reactivar el cron.**

### Migrar datos (WAL)
Las DB corren en WAL mode y los archivos son de ROOT. Solución:
1. Pará los containers.
2. Copiá los 3 archivos (`<db>.db`, `.db-wal`, `.db-shm`) a `/tmp/`.
3. En la copia, `PRAGMA wal_checkpoint(TRUNCATE)` → consolida el WAL.
4. Hacé las ediciones (ej. `UPDATE usuarios SET email=...`).
5. Renombrá a `<nuevo>.db`, borrá los `.db-wal`/`.db-shm`.
6. Colocá la DB en `/home/usuario/forja-data/tenants/<nuevo>/` ANTES de crear el tenant.

### PITFALL — password admin desincronizada
Al crear el tenant, el panel genera una pass nueva, pero la DB migrada tiene el hash de la pass vieja → login falla. Fix:
1. Detené el container.
2. Generá el hash bcrypt de la pass del panel: `node -e "const b=require('bcryptjs'); console.log(b.hashSync('<PASS>',10))"`.
3. `UPDATE usuarios SET password_hash=<hash> WHERE email='admin@<nuevo>.com'` (columna `password_hash`, con guion bajo).
4. Arrancá y probá login.

---

## 7. Onboarding del humano

Cuando el humano le pide al gemelo **crear una app**, el gemelo actúa como **analista de requerimientos breve y natural**: le saca la idea de la cabeza con preguntas cortas, en su idioma, y detecta si necesita que le enseñe algo del proceso. **Objetivo:** que el onboarding no sea una locura.

### Reglas para no abrumar
- **Nunca jerga técnica.** Hablá de "lo que la app va a manejar" y "cómo se va a ver".
- **Una pregunta a la vez.** No bombardees con 5 preguntas.
- **Proponé, no preguntes en vacío.** En vez de "¿qué entidades querés?", decí "la app va a manejar X, Y y Z — ¿le falta algo?".
- **El humano valida, vos construís.** Él confirma la idea; vos te encargás de que se convierta en producto.

### Las preguntas del analista (breves, en orden)
1. **El problema:** "¿Qué problema resuelve la app?" / "¿Quién la va a usar?" / "¿Qué es lo más importante que tiene que hacer?"
2. **Las entidades (proponé):** "la app va a manejar animales, pesajes y tratamientos — ¿le falta algo?"
3. **El diseño:** "¿La querés oscura y profesional, o clara y amigable?" / "¿Qué pantallas necesita?"
4. **Cierre:** "entonces la app va a hacer X, para Y, y se va a ver Z — ¿va así?"

### Traducción humano → producto
| El humano dice | Vos lo convertís en |
|---|---|
| "Quiero llevar el control de mis animales" | Entidades: animales, pesajes, tratamientos |
| "Que se vea prolijo y moderno" | Diseño: tono oscuro/profesional, KPIs claros |
| "Que el cliente pueda reservar" | Entidad: reservas + flujo de cliente |
| "No quiero que se me complique" | Vos absorbés toda la parte técnica, él solo ve el resultado |

---

## 8. Errores comunes

- **"No hay tool crear_cliente"** → el cliente se crea con `crear_tenant`. No inventes tools.
- **`crear_app` requiere imagen docker** → si la app no está buildeada, usá `solicitar_app` (pedido), no `crear_app`.
- **`crear_tenant` requiere un `appId` válido** → sacalo de `listar_apps`, no lo inventes.
- **La `descripcion` vaga produce app rota** → escribí el contrato de datos completo.
- **`disenar_app` corta la conexión** → no es fallo, verificá el directorio del diseño.
- **Pedido colgado en `generando`** → si no hay build docker vivo para ese slug, es un zombie: resetealo a `pendiente` y reprocesalo.
- **Login REST bloqueado por captcha/rate-limit** → usá MCP con la API key, nunca REST+login.
- **401 / "MCP no configurado"** → falta la `X-Api-Key` o está mal. Es fail-closed.
- **El server no tiene las tools cargadas** → hacer `/new` para que el cliente MCP las levante, o llamar `tools/list` con el session-id.
