> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Crear una regla a partir de una política

> Convierte una frase de política escrita en una regla activa de Tracer: mapéala a campos, créala como borrador, ensáyala en una cuenta de prueba, actívala y cámbiala o retírala después.

export const GMetadata = ({children}) => <Tooltip headline="Metadata" tip="Additional key-value information attached to entities like accounts or transactions — such as external IDs, reference numbers, or department codes." cta="See glossary" href="/en/glossary">
    {children}
  </Tooltip>;

export const GCEL = ({children}) => <Tooltip headline="CEL (Common Expression Language)" tip="A lightweight expression language for writing business rules — for example, 'if transaction amount > 10000 then REVIEW'. Tracer uses CEL for validation rules." cta="See glossary" href="/en/glossary">
    {children}
  </Tooltip>;

Un responsable de compliance entrega una frase: *"rechaza las compras con tarjeta sin presencia física por encima de BRL 5.000 desde un dispositivo que no hemos visto antes."* Esta guía convierte esa frase en una regla que evalúa como lee la política, la ensaya donde no alcanza a nadie y la pone en producción.

**Lo que cambia en tu operación:** la política deja de vivir en un ticket y empieza a vivir en un endpoint. La persona que escribió la frase puede leer la regla de vuelta, el ensayo corre a través de una evaluación real en lugar de una hoja de cálculo, y cada cambio en la regla deja detrás un evento de auditoría.

<Tip>
  **¿Para quién es esta guía?** Analistas de riesgo y fraude que escriben reglas, y los desarrolladores que conectan los campos de la política a la solicitud de validación. Los pasos 1 y 2 tratan de la política; los pasos 3 a 8 son llamadas a la API.
</Tip>

## Antes de empezar

***

* [ ] Tracer en ejecución y alcanzable, con una API key — consulta [Primeros pasos](./getting-started.mdx)
* [ ] Las variables <GCEL>CEL</GCEL> y el modelo de alcance — consulta el [Motor de reglas](./rule-engine.mdx)
* [ ] Un id de cuenta de prueba al que puedas enviar validaciones, que ningún tráfico de clientes use
* [ ] La frase de la política, por escrito, con quien la escribió disponible para una pregunta

Todas las llamadas de abajo envían la API key como `X-API-Key`.

***

## Paso 1: Mapea la frase sobre los campos

***

Una regla lee lo que la solicitud de validación transporta. Descompón la frase cláusula por cláusula y pon cada una en la columna a la que pertenece.

| Cláusula en la política                   | De dónde viene el valor                                  | Se lee como                |
| ----------------------------------------- | -------------------------------------------------------- | -------------------------- |
| "compras ... con tarjeta"                 | `transactionType`, un enum de Tracer                     | `CARD`                     |
| "por encima de BRL 5.000"                 | `amount` y `currency` en la solicitud                    | `amount`, `currency`       |
| "sin presencia física"                    | `subType`, texto libre que fija tu integración           | `subType`                  |
| "un dispositivo que no hemos visto antes" | <GMetadata>metadata</GMetadata>, que fija tu integración | `metadata.deviceFirstSeen` |

Los dos primeros son campos que Tracer define. Los dos últimos son campos que **tu integración tiene que enviar** — Tracer no tiene una opinión sobre qué significan "sin presencia física" o "dispositivo nuevo". Esa es la pregunta que hay que llevar de vuelta a quien escribió la política: *¿qué bandera de nuestro payload dice que el dispositivo es nuevo?*

<Note>
  `subType` llega a las expresiones en minúsculas, así que `"card_not_present"` es la forma contra la que hay que comparar. Si tu integración ya usa `subType` para otra cosa, lleva el modo de entrada en `metadata` y compara contra eso — ambos funcionan igual en una expresión.
</Note>

Para la lista completa de variables y los campos que transporta cada mapa de contexto, consulta el [Motor de reglas](./rule-engine.mdx#expresiones).

***

## Paso 2: Divide la regla entre alcance y expresión

***

Dos cosas de esa tabla — el tipo de transacción y la cuenta — son cosas que Tracer puede filtrar antes de que corra una expresión. Esas pertenecen a los `scopes` de la regla. Las comparaciones de valores pertenecen a la `expression`.

**Alcance**, que decide *si la regla se considera siquiera*:

```json theme={null}
"scopes": [{ "transactionType": "CARD" }]
```

**Expresión**, que decide *si la regla dispara*:

```cel theme={null}
subType == "card_not_present" && amount > 5000 && metadata.deviceFirstSeen == true
```

Lee la expresión contra la frase: modo de entrada, umbral y la bandera del dispositivo. `amount > 5000` es estrictamente mayor, así que una transacción de exactamente `5000.00` no la dispara — verifica eso contra la política antes de seguir, porque "por encima de" y "desde" son reglas distintas.

Una regla que lee una clave de metadata que la solicitud no transporta no coincide, y las demás reglas siguen corriendo. Por eso la expresión de arriba no necesita una prueba de presencia para `deviceFirstSeen`; consulta el [Motor de reglas](./rule-engine.mdx#expresiones).

<Warning>
  No repitas las condiciones de alcance dentro de la expresión. `transactionType == "CARD"` en ambos lugares no está mal, pero deja dos lugares que editar cuando la política cambie — y la expresión es la que necesita un viaje de ida y vuelta por el ciclo de vida para editarse.
</Warning>

***

## Paso 3: Crea la regla como borrador

***

`POST /v1/rules` crea la regla en `DRAFT`. Un borrador no se evalúa, así que nada de lo que hagas aquí alcanza al tráfico.

```bash theme={null}
curl -X POST http://localhost:4020/v1/rules \
  -H "X-API-Key: your-secure-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Deny CNP card purchases above BRL 5,000 from a new device",
    "description": "Compliance policy 2026-14, approved 2026-07-20",
    "expression": "subType == \"card_not_present\" && amount > 5000 && metadata.deviceFirstSeen == true",
    "action": "DENY",
    "scopes": [
      { "transactionType": "CARD", "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d" }
    ]
  }'
```

El `accountId` de ese alcance es tu cuenta de prueba. Es lo que mantiene el paso 4 fuera del tráfico de clientes; el paso 5 lo quita.

Un `201` responde con la regla almacenada:

```json theme={null}
{
  "ruleId": "4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68",
  "name": "deny cnp card purchases above brl 5,000 from a new device",
  "description": "Compliance policy 2026-14, approved 2026-07-20",
  "expression": "subType == \"card_not_present\" && amount > 5000 && metadata.deviceFirstSeen == true",
  "action": "DENY",
  "scopes": [
    { "transactionType": "CARD", "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d" }
  ],
  "status": "DRAFT",
  "createdAt": "2026-07-31T11:04:12.318Z",
  "updatedAt": "2026-07-31T11:04:12.318Z"
}
```

Tracer almacena el nombre de la regla en una forma normalizada, así que el `name` que devuelve puede diferir de la cadena que enviaste. Toma el `ruleId` de la respuesta — ese es el identificador que usa cada llamada de abajo. Consulta [Crear una regla](/es/reference/tracer/create-rule).

La expresión se compila en esta llamada, así que una expresión que no puede correr nunca llega a ser un borrador. Un error de sintaxis responde `0340`, una expresión que no devuelve un booleano responde `0341`, y una cuyo costo estimado está por encima de `CEL_COST_LIMIT` responde `0342`.

***

## Paso 4: Ensáyala en una cuenta que nadie más use

***

El alcance que fijaste es lo que mantiene contenido el ensayo: la regla se considera solo para transacciones de esa única cuenta de prueba, así que activarla la pone frente a exactamente el tráfico que le envíes.

<Steps>
  <Step title="Activa la regla con alcance">
    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/activate \
      -H "X-API-Key: your-secure-api-key"
    ```

    La respuesta vuelve con `status: "ACTIVE"` y un `activatedAt`. Consulta [Activar una regla](/es/reference/tracer/activate-rule).
  </Step>

  <Step title="Envía una transacción que la política debería rechazar">
    ```bash theme={null}
    TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)

    curl -X POST http://localhost:4020/v1/validations \
      -H "X-API-Key: your-secure-api-key" \
      -H "Content-Type: application/json" \
      -d '{
        "requestId": "c1a7f402-6b93-4d5e-8f10-2a4c6e8b0d31",
        "transactionType": "CARD",
        "subType": "card_not_present",
        "amount": "7500.00",
        "currency": "BRL",
        "transactionTimestamp": "'"$TS"'",
        "account": {
          "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d",
          "type": "checking",
          "status": "active"
        },
        "metadata": {
          "deviceFirstSeen": true
        }
      }'
    ```

    Consulta [Validar una transacción](/es/reference/tracer/validate-transaction).
  </Step>

  <Step title="Lee la decisión">
    ```json theme={null}
    {
      "validationId": "b7e3d190-4c25-4e8f-9a16-3d5f7b0c2e41",
      "requestId": "c1a7f402-6b93-4d5e-8f10-2a4c6e8b0d31",
      "decision": "DENY",
      "matchedRuleIds": ["4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68"],
      "evaluatedRuleIds": ["4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68"],
      "reason": "Rule matched with DENY action",
      "totalRulesLoaded": 1,
      "truncated": false,
      "limitUsageDetails": [],
      "processingTimeMs": 6.1,
      "evaluatedAt": "2026-07-31T11:09:44.207Z"
    }
    ```

    Tu `ruleId` en `matchedRuleIds` es el ensayo pasando.
  </Step>

  <Step title="Envía los casos que no deberían disparar">
    Cambia un valor a la vez y repite la llamada con un `requestId` nuevo: `"amount": "5000.00"` para el límite exacto, `"deviceFirstSeen": false` para un dispositivo conocido, `"subType": "purchase"` para una venta con presencia física. Cada una debería volver sin tu `ruleId` en `matchedRuleIds`.
  </Step>
</Steps>

<Warning>
  Envía un `requestId` nuevo en cada intento. `requestId` es la clave de idempotencia: repite uno y Tracer responde `200` con la decisión que ya registró para esa clave, así que el cambio que acabas de hacer parecerá no haber hecho nada.
</Warning>

<Note>
  Un ensayo es una validación real. Almacena un registro de decisión y escribe un evento de auditoría, y una decisión `ALLOW` consume los límites de gasto que cubren esa cuenta. Por eso importa la cuenta de prueba.
</Note>

Si tu `ruleId` no está en `matchedRuleIds`, recórrelo en este orden: ¿la regla está `ACTIVE` (`GET /v1/rules/{id}`)?, ¿la transacción coincide con el alcance que fijaste?, ¿los valores que enviaste satisfacen la expresión?

***

## Paso 5: Ponla en producción

***

Pasar a producción significa una edición: quita la cuenta de prueba del alcance para que la regla aplique a la población que nombra la política.

<Steps>
  <Step title="Deja de evaluar la versión de ensayo">
    Editar el alcance no exige `INACTIVE`. Desactivar primero hace que el cambio surta efecto en un momento que tú controlas y deja un hueco visible en el rastro de auditoría. Cada instancia sirve las reglas desde una caché que refresca por sondeo (cada 10 segundos por defecto, `RULE_SYNC_POLL_INTERVAL_SECONDS`), así que espera esa ventana para que la desactivación llegue a todas las instancias; `GET /v1/rules` confirma el estado almacenado, no que cada instancia ya se haya puesto al día.

    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/deactivate \
      -H "X-API-Key: your-secure-api-key"
    ```

    El estado pasa a `INACTIVE`. Consulta [Desactivar una regla](/es/reference/tracer/deactivate-rule).
  </Step>

  <Step title="Reemplaza el alcance">
    ```bash theme={null}
    curl -X PATCH http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68 \
      -H "X-API-Key: your-secure-api-key" \
      -H "Content-Type: application/json" \
      -d '{ "scopes": [{ "transactionType": "CARD" }] }'
    ```

    `scopes` reemplaza el arreglo completo — envía cada objeto de alcance que quieras que la regla conserve. Consulta [Actualizar una regla](/es/reference/tracer/update-rule).
  </Step>

  <Step title="Activa">
    ```bash theme={null}
    curl -X POST http://localhost:4020/v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68/activate \
      -H "X-API-Key: your-secure-api-key"
    ```

    La activación alcanza a la instancia que atendió esta llamada en cuanto se confirma. Cuando corres varias instancias detrás de un balanceador, las demás recogen el cambio en su siguiente sincronización de reglas (`RULE_SYNC_POLL_INTERVAL_SECONDS`, predeterminado `10`). La desactivación viaja del mismo modo. Espera esa misma ventana después de activar antes de dar la regla por aplicada en todas las instancias.
  </Step>

  <Step title="Confirma qué está vivo">
    ```http theme={null}
    GET /v1/rules?status=ACTIVE&transaction_type=CARD&sort_by=updated_at
    X-API-Key: {api_key}
    ```

    El listado responde "qué está haciendo cumplir la política sobre el tráfico de tarjeta ahora mismo" — consulta [Listar reglas](/es/reference/tracer/list-rules). Para una sola regla, `GET /v1/rules/{id}` devuelve la expresión y los alcances tal como están almacenados ([Recuperar una regla](/es/reference/tracer/retrieve-rule)). Ambos reportan el estado almacenado, no lo que guarda la caché de cada instancia.
  </Step>
</Steps>

<Note>
  Una validación carga su conjunto de reglas una vez, desde la caché de la instancia que la atiende, cuando empieza la llamada. Una regla que se activa mientras una validación está en vuelo no forma parte de esa decisión, y las decisiones ya registradas no cambian cuando las reglas cambian después.
</Note>

***

## Paso 6: Conoce dónde queda tu regla entre las demás

***

Las reglas no tienen un campo de prioridad ni un orden que configurar. Las reglas cuyo alcance coincide con una transacción se evalúan juntas, y la decisión viene de la acción más estricta que disparó: primero una regla `DENY`, luego un límite de gasto excedido, luego `REVIEW`, luego `ALLOW`, luego el valor predeterminado configurado para cuando no hay coincidencia. `matchedRuleIds` transporta cada regla que coincidió, sea cual sea la acción que cada una tiene.

Dos consecuencias para la regla que acabas de escribir:

* **Una regla `ALLOW` no exime a nadie de una regla `DENY`.** Si la política tiene una excepción — clientes VIP, un comercio asociado — la excepción pertenece dentro de la expresión `DENY`, como una condición más que la hace más estrecha:

  ```cel theme={null}
  subType == "card_not_present" && amount > 5000 && metadata.deviceFirstSeen == true && metadata.customerTier != "vip"
  ```

  Fíjate en lo que eso cuesta: la regla estrechada ahora lee `metadata.customerTier`, y una solicitud que no transporta esa clave no coincide con ella.

* **Tu regla se suma al conjunto que evalúa cada transacción que coincide.** `MAX_RULES_PER_REQUEST` limita cuántas reglas evalúa una validación; cuando el conjunto es mayor, la respuesta reporta `truncated: true` — consulta [variables de entorno](./tracer-environment-variables.mdx).

La tabla de precedencia y el razonamiento detrás de ella están en la página del [Motor de reglas](./rule-engine.mdx#patrón-de-evaluación).

***

## Paso 7: Cambia la regla cuando cambie la política

***

Lo que haces depende del campo, no de cómo le está yendo a la regla.

| Qué cambió                                                                                        | Cómo                                                                                     |
| ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| El umbral, el modo de entrada, la bandera del dispositivo — cualquier cosa dentro de la expresión | Desactiva, luego `POST /v1/rules/{id}/draft`, luego `PATCH` a la expresión, luego activa |
| `DENY` pasa a `REVIEW`                                                                            | `PATCH /v1/rules/{id}` con la nueva `action`                                             |
| Nombre, descripción o alcances                                                                    | `PATCH /v1/rules/{id}`                                                                   |

<Warning>
  La `expression` acepta una edición solo mientras la regla está en `DRAFT`. Enviar una a una regla en otro estado responde `422` con el código de error `0351` — desactivar no basta por sí solo, porque `INACTIVE` no es `DRAFT`. `POST /v1/rules/{id}/draft` es el paso que la gente olvida; consulta [Pasar una regla a borrador](/es/reference/tracer/draft-rule).
</Warning>

Un `PATCH` queda almacenado cuando responde, y llega a la evaluación en la siguiente sincronización de reglas. Cuando el cambio importa al minuto, desactiva primero y activa de nuevo después — esa secuencia además deja un hueco visible en el rastro de auditoría donde la regla no estaba haciendo cumplir la política, que es lo que buscará quien revise.

El ciclo de vida es un conjunto cerrado de movimientos: `DRAFT` se activa o se elimina; `ACTIVE` se desactiva; `INACTIVE` vuelve a `DRAFT`, vuelve a `ACTIVE` o se elimina; `DELETED` es el final. Cualquier otra cosa responde `422` con el código de error `0349` — incluida una solicitud de pasar a borrador una regla que todavía está `ACTIVE`.

***

## Paso 8: Retira o elimina la regla

***

**Para dejar de hacer cumplir la política sin perder nada**, desactiva. La regla conserva su expresión, sus alcances y su historia, deja de evaluarse, y `POST /v1/rules/{id}/activate` la trae de vuelta. Ese es el movimiento para una política suspendida, estacional o bajo revisión.

**Para quitarla**, elimina — y solo después de desactivar, porque una regla en `ACTIVE` no se puede eliminar:

```http theme={null}
DELETE /v1/rules/4d9c2e70-8b1a-4f36-9c07-5e2a1b3d4f68
X-API-Key: {api_key}
```

Un `204` responde en caso de éxito. Consulta [Eliminar una regla](/es/reference/tracer/delete-rule).

Qué quita la eliminación:

* La regla deja de responder en `GET /v1/rules/{id}`, que devuelve `404` con el código de error `0347` a partir de entonces.
* Ya no aparece en `GET /v1/rules`, y `DELETED` no es un valor que acepte el filtro `status`.
* `DELETED` es el final del ciclo de vida. Ningún endpoint saca a una regla de ahí — una regla eliminada vuelve solo como una regla nueva que crees de nuevo.

Qué deja atrás la eliminación:

* El rastro de auditoría conserva el ciclo de vida de la regla, y el evento `RULE_DELETED` transporta la definición — nombre, descripción, expresión, acción, alcances — tal como estaba al eliminarla. Consulta [Auditoría y compliance](./audit-compliance.mdx).
* Las decisiones que produjo la regla conservan su `ruleId` en `matchedRuleIds`. Una denegación de hace seis meses todavía la nombra — consulta [Revisar una transacción denegada](./reviewing-a-denied-transaction.mdx).
* El nombre queda disponible de nuevo para una regla nueva en el mismo contexto.

<Warning>
  Desactiva, luego lee el rastro de auditoría, luego elimina. Desactivar es reversible en una llamada y eliminar no es reversible en absoluto, así que no hay razón para saltarse el estado intermedio.
</Warning>

***

## Errores frecuentes

***

<Warning>
  **Lo que suele salir mal al convertir una política en una regla:**

  * **"La regla está ACTIVE pero nada coincide."** Revisa el campo en el que más se apoya la política. Una regla que lee `metadata.deviceFirstSeen` no coincide con nada si tu integración nunca envía esa clave — la regla es correcta y el payload está incompleto.
  * **"Disparó en una transacción que la política exime."** Una regla `ALLOW` no anula una `DENY`. Pon la exención dentro de la expresión `DENY` (paso 6).
  * **"Mi segundo ensayo devolvió la primera decisión."** `requestId` es la clave de idempotencia. Envía un UUID nuevo en cada intento.
  * **"PATCH rechazó mi expresión con 422."** La regla no estaba en `DRAFT`. Muévela ahí primero (paso 7).
  * **"El nombre que envié no es el nombre que recibo."** Tracer almacena los nombres en una forma normalizada. Referencia la regla por `ruleId`.
</Warning>

### Códigos de error

| Código                   | Estado | Qué cambiar                                                                                                         |
| ------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `0340`                   | 400    | La expresión no se analiza como CEL                                                                                 |
| `0341`                   | 400    | La expresión no devuelve un booleano — `amount > 5000`, no `amount`                                                 |
| `0342`                   | 422    | El costo estimado de la expresión está por encima de `CEL_COST_LIMIT`                                               |
| `0347`                   | 404    | Ninguna regla tiene ese `ruleId`, o fue eliminada                                                                   |
| `0349`                   | 422    | El ciclo de vida no permite ese movimiento — por ejemplo eliminar una regla `ACTIVE`                                |
| `0351`                   | 422    | Una edición de expresión en una regla que no está en `DRAFT`                                                        |
| `0353` / `0355` / `0357` | 400    | Falta `name`, `expression` o una `action` válida                                                                    |
| `0354` / `0356` / `0359` | 400    | `name` por encima de 255, `expression` por encima de 5000, o `description` por encima de 1000 caracteres            |
| `0358`                   | 400    | Un objeto de alcance sin ningún campo fijado — omite `scopes` por completo para una regla global, nunca envíes `{}` |
| `0360`                   | 400    | Más de 100 objetos de alcance en una regla                                                                          |
| `0441`                   | 409    | Otra regla en el mismo contexto ya tiene ese nombre                                                                 |
| `0065`                   | 400    | El id de la ruta no es un UUID                                                                                      |
| `0082`                   | 400    | Un filtro de `GET /v1/rules` lleva un valor que el endpoint no acepta                                               |

La lista completa está en la [Lista de errores de Tracer](/es/reference/tracer/tracer-error-list).

***

## Referencia rápida

***

| Paso                | Método | Endpoint                    |
| ------------------- | ------ | --------------------------- |
| Crear como borrador | POST   | `/v1/rules`                 |
| Empezar a evaluar   | POST   | `/v1/rules/{id}/activate`   |
| Ensayar o verificar | POST   | `/v1/validations`           |
| Dejar de evaluar    | POST   | `/v1/rules/{id}/deactivate` |
| Reabrir para editar | POST   | `/v1/rules/{id}/draft`      |
| Cambiar campos      | PATCH  | `/v1/rules/{id}`            |
| Leer una regla      | GET    | `/v1/rules/{id}`            |
| Ver qué está vivo   | GET    | `/v1/rules`                 |
| Quitar              | DELETE | `/v1/rules/{id}`            |
