> ## 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 oración de política escrita en una regla activa de Tracer: mapéala a los 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="Metadatos" tip="Información adicional de clave-valor asociada a entidades como cuentas o transacciones, como IDs externos, números de referencia o códigos de departamento." cta="Ver glosario" href="/es/start-here/glossary">
    {children}
  </Tooltip>;

export const GCEL = ({children}) => <Tooltip headline="CEL (Common Expression Language)" tip="Un lenguaje de expresiones ligero para escribir reglas de negocio, por ejemplo, 'if transaction amount > 10000 then REVIEW'. Tracer usa CEL para las reglas de validación." cta="Ver glosario" href="/es/start-here/glossary">
    {children}
  </Tooltip>;

Un oficial de cumplimiento entrega una oración: *"rechazar compras sin presencia de tarjeta por encima de BRL 5,000 desde un dispositivo que no hemos visto antes."*

Esta guía convierte esa oración en una regla que evalúa tal como se lee la política. La ensayas donde no alcanza a nadie, y luego la pones en producción.

**Qué cambia en tu operación:** la política deja de vivir en un ticket y pasa a vivir en un endpoint. La persona que escribió la oración puede volver a leer la regla. El ensayo pasa por evaluación real en lugar de una hoja de cálculo. Cada cambio a la regla deja un evento de auditoría detrás.

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

## Antes de empezar

***

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

Todas las llamadas siguientes envían la clave de API como `X-API-Key`.

***

## Paso 1: mapea la oración a los campos

***

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

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

Los dos primeros son campos que define Tracer. Los últimos dos son campos que **tu integración debe enviar**. Tracer no tiene una postura sobre qué significa "sin presencia de tarjeta" o "dispositivo nuevo". Esa es la pregunta que debes llevarle a quien redactó la política: *¿qué indicador en 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 con la que se debe comparar. Si tu integración ya usa `subType` para otra cosa, lleva el modo de entrada en `metadata` en su lugar y compara con eso. Ambas funcionan igual dentro de una expresión.
</Note>

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

***

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

***

Dos cosas en esa tabla (el tipo de transacción y la cuenta) son cosas que Tracer puede filtrar antes de que se ejecute 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 se activa*:

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

Lee la expresión contra la oración: modo de entrada, umbral y el indicador del dispositivo. `amount > 5000` es estrictamente superior, así que una transacción de exactamente `5000.00` no la activa. Verifica esto contra la política antes de continuar, porque "por encima de" y "desde" son reglas distintas.

Una regla que lee una clave de metadatos que la solicitud no trae no coincide, y las demás reglas siguen ejecutándose. Por eso, la expresión anterior no necesita una prueba de presencia para `deviceFirstSeen`. Consulta [Motor de reglas](./rule-engine.mdx#expressions).

<Warning>
  No repitas condiciones de alcance dentro de la expresión. `transactionType == "CARD"` en ambos lugares no está mal. Deja dos lugares para editar cuando la política cambia, y la expresión es la que necesita un recorrido completo 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 nunca llega a evaluación, así que nada de lo que hagas aquí llega 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 retira.

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 conserva las mayúsculas/minúsculas y los espacios internos del nombre. Recorta los espacios al inicio y al final antes de almacenarlo. La unicidad del nombre de la regla distingue mayúsculas de minúsculas dentro del contexto derivado de los alcances de la regla. Así, `FraudRule` y `fraudrule` son nombres distintos, y el mismo nombre puede coexistir en contextos diferentes.

Toma el `ruleId` de la respuesta. Ese es el identificador que usa cada llamada siguiente. Consulta [Crear una regla](/es/reference/products/tracer/create-rule).

La expresión se compila en esta llamada, así que una expresión que no puede ejecutarse 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 supera `CEL_COST_LIMIT` responde `0342`.

***

## Paso 4: ensáyala en una cuenta que nadie más usa

***

El alcance que definiste es lo que mantiene el ensayo contenido. La regla llega a evaluación solo para transacciones en esa única cuenta de prueba. La activación la pone frente a exactamente el tráfico que le envías.

<Steps>
  <Step title="Activa la regla con el alcance configurado">
    ```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 regresa con `status: "ACTIVE"` y un `activatedAt`. Consulta [Activar una regla](/es/reference/products/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",
        "asset": "BRL",
        "transactionTimestamp": "'"$TS"'",
        "account": {
          "accountId": "8a1f2c34-5d6e-4a7b-8c9d-0e1f2a3b4c5d",
          "type": "checking",
          "status": "active"
        },
        "metadata": {
          "deviceFirstSeen": true
        }
      }'
    ```

    Consulta [Validar una transacción](/es/reference/products/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` significa que el ensayo pasó.
  </Step>

  <Step title="Envía los casos que no deberían activarla">
    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 de tarjeta. Cada una debería regresar sin tu `ruleId` en `matchedRuleIds`.
  </Step>
</Steps>

<Warning>
  Envía un `requestId` nuevo en cada intento. El `requestId` es la clave de idempotencia. Si repites uno, Tracer responde `200` con la decisión que ya registró para esa clave. El cambio que acabas de hacer entonces parece 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`, revísalo en este orden. ¿La regla está `ACTIVE` (`GET /v1/rules/{id}`)? ¿La transacción coincide con el alcance que definiste? ¿Los valores que enviaste satisfacen la expresión?

***

## Paso 5: ponla en producción

***

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

<Steps>
  <Step title="Detén la evaluación de la versión de ensayo">
    Editar el alcance no exige `INACTIVE`. Desactivar primero hace que el cambio surta efecto en un momento que controlas y registra un espacio visible en el registro de auditoría. Cada instancia sirve las reglas desde una caché que actualiza con un sondeo (cada 10 segundos por defecto, `RULE_SYNC_POLL_INTERVAL_SECONDS`). Da esa ventana de tiempo para que la desactivación llegue a todas las instancias. `GET /v1/rules` confirma el estado almacenado, no que todas las instancias ya se pusieron 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/products/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 todo el arreglo. Envía cada objeto de alcance que quieras que la regla conserve. Consulta [Actualizar una regla](/es/reference/products/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 llega a la instancia que atendió esta llamada tan pronto se confirma. Cuando ejecutas varias instancias detrás de un balanceador de carga, las demás recogen el cambio en su siguiente sincronización de reglas (`RULE_SYNC_POLL_INTERVAL_SECONDS`, predeterminado `10`). La desactivación viaja de la misma forma. Da esa misma ventana después de la activación antes de tratar la regla como aplicada en todas las instancias.
  </Step>

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

    El listado responde "qué reglas aplican en el tráfico de tarjetas en este momento". Consulta [Listar reglas](/es/reference/products/tracer/list-rules). Para una sola regla, `GET /v1/rules/{id}` devuelve la expresión y los alcances tal como se almacenaron ([Consultar una regla](/es/reference/products/tracer/retrieve-rule)). Ambos reportan el estado almacenado, no lo que contiene 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 curso no forma parte de esa decisión. Las decisiones ya registradas no cambian cuando las reglas cambian después.
</Note>

***

## Paso 6: entiende dónde se ubica 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 en conjunto. La decisión proviene de la acción más estricta que se activó: primero una regla `DENY`, luego un límite de gasto excedido, después `REVIEW`, luego `ALLOW`, y por último el valor predeterminado configurado para la ausencia de coincidencias. El arreglo `matchedRuleIds` lleva cada regla que coincidió, sin importar qué acción tenga cada una.

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), colócala dentro de la expresión `DENY`. Agrégala como una condición más que hace la regla más estrecha:

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

  Eso cuesta lo siguiente: la regla más estrecha ahora lee `metadata.customerTier`, y una solicitud que no trae esa clave no coincide con ella.

* **Tu regla se une al conjunto que evalúa cada transacción coincidente.** `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 [Motor de reglas](./rule-engine.mdx#evaluation-pattern).

***

## Paso 7: cambia la regla cuando cambia la política

***

Lo que hagas depende del campo, no de cómo se desempeña la regla.

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

<Warning>
  El `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 pasa por alto. Consulta [Poner una regla en borrador](/es/reference/products/tracer/draft-rule).
</Warning>

Un `PATCH` se almacena cuando responde, y llega a evaluación en la siguiente sincronización de reglas. Cuando el cambio importa al minuto, desactiva primero y vuelve a activar después. Esa secuencia también deja un espacio visible en el registro de auditoría donde la regla no se aplicó. Un revisor buscará ese espacio.

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 para poner en borrador una regla que sigue `ACTIVE`.

***

## Paso 8: retira o elimina la regla

***

**Para dejar de aplicarla sin perder nada**, desactívala. La regla conserva su expresión, sus alcances y su historial. Ya no llega a evaluación, y `POST /v1/rules/{id}/activate` la trae de vuelta. Ese es el movimiento para una política suspendida, estacional o en revisión.

**Para eliminarla**, bórrala. Desactívala primero, porque no puedes eliminar una regla en `ACTIVE`:

```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/products/tracer/delete-rule).

Qué elimina el borrado:

* La regla deja de responder en `GET /v1/rules/{id}`, que después devuelve `404` con el código de error `0347`.
* 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 una regla de ahí. Una regla eliminada regresa solo como una regla nueva que vuelves a crear.

Qué deja atrás el borrado:

* El registro de auditoría conserva el ciclo de vida de la regla, y el evento `RULE_DELETED` lleva la definición (name, description, expression, action, scopes) tal como estaba al momento de la eliminación. Consulta [Auditoría y cumplimiento](./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 revisa el registro de auditoría, luego elimina. Desactivar es reversible en una sola llamada y eliminar no es reversible en absoluto, así que no hay razón para saltarse el estado intermedio.
</Warning>

***

## Errores comunes

***

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

  * **"La regla está ACTIVE pero no coincide con nada."** 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.
  * **"Se activó en una transacción que la política exime."** Una regla `ALLOW` no anula una `DENY`. Coloca la excepció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 de vuelta."** Tracer recorta solo los espacios al inicio y al final. Conserva las mayúsculas/minúsculas y los espacios internos. Referencia la regla por su `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, usa `amount > 5000`, no `amount`                                               |
| `0342`                   | 422    | El costo estimado de la expresión supera `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á `DRAFT`                                                            |
| `0353` / `0355` / `0357` | 400    | Falta `name`, `expression`, o una `action` válida                                                                    |
| `0354` / `0356` / `0359` | 400    | `name` supera 255, `expression` supera 5000, o `description` supera 1000 caracteres                                  |
| `0358`                   | 400    | Un objeto de alcance sin ningún campo definido; omite `scopes` por completo para una regla global, nunca envíes `{}` |
| `0360`                   | 400    | Más de 100 objetos de alcance en una sola regla                                                                      |
| `0441`                   | 409    | Otra regla en el mismo contexto ya tiene ese nombre                                                                  |
| `0065`                   | 400    | El id en la ruta no es un UUID                                                                                       |
| `0082`                   | 400    | Un filtro en `GET /v1/rules` lleva un valor que el endpoint no acepta                                                |

La lista completa está en [Lista de errores de Tracer](/es/reference/products/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`           |
| Detener la evaluación      | 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á en producción | GET    | `/v1/rules`                 |
| Eliminar                   | DELETE | `/v1/rules/{id}`            |
