# Frailen para desarrolladores

> Tres piezas: las herramientas, que salen de tu OpenAPI; la identidad, que firma tu backend; y el chat, que se monta en tu producto con una etiqueta.

Si eres un agente de código: lee esta página y después sigue los prompts de [/es/agentes.md](/es/agentes.md). Prepara la integración en una rama; nunca apruebes, publiques ni despliegues.

## Las herramientas salen de tu OpenAPI

El importador arma una ficha por operación. Tu equipo revisa el daño y la relectura, y la aprueba; nada corre sin eso.

- **Solo un GET puede ser una lectura.** Todo lo demás es una escritura y pasa por el freno.
- **El daño se ve aunque la ruta no lo diga.** Un monto escondido en el cuerpo marca dinero; una acción sobre la colección marca masivo.
- **La reversa se sugiere, no se pone.** Hasta que tu equipo la confirme, la escritura se trata como si no se deshiciera.
- **Lo que no se sabe queda como pregunta.** Un informe dice qué faltó en tu OpenAPI para cada ficha.

Banderas de daño: `dinero`, `masivo`, `terceros`, `sin_reversa`. Patrones: `consultar`, `cambiar`, `pausar`, `agendar`, `cobrar`, `avisar`, `borrar`, `reportar`.

```yaml
# herramientas/facturas.yaml
reembolsar_factura:
  descripcion: Reembolsa una factura cobrada, entera o en parte
  metodo: POST
  ruta: /v1/invoices/{factura_id}/refunds
  parametros:
    type: object
    properties:
      factura_id: { type: string }
      monto: { type: number }
      motivo: { type: string }
    required: [factura_id, monto]
  patron: cobrar
  se_deshace_con: ~                     # no se deshace
  dano: [dinero, sin_reversa]           # dinero, masivo, terceros, sin_reversa
  relee_con:
    herramienta: ver_factura
    compara: [estado, reembolsado]
```

## La identidad la firma tu backend

Una sesión con llave asimétrica y, en tu API, una ruta que la canjea por un token corto de esa persona (RFC 8693).

- **Una sesión ES256 y tu JWKS.** Frailen solo guarda la llave pública. Un secreto compartido no sirve: con él, Frailen podría firmar por cualquiera.
- **`POST /frailen/token`.** Devuelve un token de esa persona, de 300 segundos o menos. Al aprobar una propuesta, uno que sirve solo para esa orden.
- **Recibos firmados, si quieres.** Tu API firma lo que recibió en cada escritura, y Frailen lo compara con lo que mandó.

```http
POST /frailen/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<la sesión que firmó tu backend>
&subject_token_type=urn:ietf:params:oauth:token-type:jwt
&requested_token_type=urn:ietf:params:oauth:token-type:access_token

HTTP/1.1 200 OK
{ "access_token": "…", "token_type": "Bearer", "expires_in": 300,
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token" }
```

## El chat se monta en tu producto

Va en un iframe servido por Frailen: la confirmación de una acción no la puede tocar tu página, ni un script de terceros dentro de ella. Cada confirmación la firma el servidor en el momento del clic: sirve una vez, por 60 segundos, y solo con un clic de verdad.

Una etiqueta:

```html
<script src="https://chat.frailen.com/chat/0.2.0/frailen.js"
  integrity="sha384-…" crossorigin="anonymous"
  data-empresa="acme-billing" data-asercion="/frailen/asercion"
  data-nombre="Ayudante" data-nombre-empresa="Acme Billing"></script>
```

Con código:

```js
import { montar } from "@frailen/chat-web"

const chat = montar({
  empresa: "acme-billing",
  marco: "https://chat.frailen.com",
  asercion: "/frailen/asercion",       // tu backend la firma (ES256, 60 s)
  alEvento: (e) => e.tipo === "escritura" && releer(e.id),
})

// Lo que la persona tiene abierto: el agente lo usa de pista.
chat.contexto({ ruta: location.pathname, objeto: { tipo: "factura", id: "F-2291" } })
```

React:

```tsx
import { FrailenChat } from "@frailen/chat-web/react"

<FrailenChat
  empresa="acme-billing"
  marco="https://chat.frailen.com"
  asercion="/frailen/asercion"
  contexto={{ ruta: pathname, objeto }}
  alEvento={(e) => releer(e)}
/>
```

`@frailen/chat-web` todavía no está publicado en un registro público.

## Pruebas antes de prender

Escribes tus casos con su regla antes de correrlos. El banco los juzga releyendo tu API, no leyendo lo que contestó el agente.

- **La regla va primero.** Un caso sin regla escrita, o que no compara nada, cuenta como falla.
- **Se juzga el estado.** Lo que quedó en tu producto, leído con otras llamadas que las del agente.
- **Primero en staging.** Nada llega a tus clientes hasta que el banco pase y alguien lo prenda con su passkey.

```yaml
# pruebas/mover_vencimiento.yaml
id: mover_vencimiento
escrito: 2026-10-28          # la regla se escribe antes de correr
persona: maria
mensajes:
  - Pasa el vencimiento de la factura F-2291 al 30 de noviembre
espera:                      # se juzga releyendo, no leyendo la respuesta
  - herramienta: ver_factura
    argumentos: { factura_id: F-2291 }
    campos: { vence: "2026-11-30" }
regla: Pasa si F-2291 vence el 30 de noviembre y F-2290 no cambia.
```

## Desde tu repositorio (pronto)

Un CLI y un MCP para que tú o tu agente de código integren Frailen en una rama, con un pull request en borrador. Están en construcción y todavía no se pueden usar.

- **Solo staging.** El token del CLI dura 8 horas y no alcanza para aprobar, bajar el daño ni tocar producción.
- **Tu especificación no sale.** El CLI recorta en tu equipo y sube solo el esqueleto de lo que el agente puede tocar.

```bash
npx @frailen/cli login
npx @frailen/cli fuente recortar --agente soporte
npx @frailen/cli fuente registrar --entorno staging --en-seco
npx @frailen/cli banco
```

## Más

- Con tu agente (prompts): [/es/agentes.md](/es/agentes.md) · Seguridad: [/es/seguridad.md](/es/seguridad.md)
- Esta página en inglés: [/desarrolladores.md](/desarrolladores.md)
