---
name: etiquetas-y-campos
description: "Create, change, fill and read fields («etiquetas y campos») on the contact, the conversation or the message with one JSON document (frepi.field/v1), and query them in SQL. Use for a tag, a funnel, a stage, an amount, a date, a reason for contact or counts per value. Spanish triggers: etiqueta, campo, embudo, etapa, motivo, valor, contactos con, conversaciones con."
license: "Proprietary. Free to use with a Frepi account. See LICENSE."
metadata:
  author: "frepi-ai"
  version: "1"
---

# Etiquetas y campos

Un **campo** guarda lo que sabes de cada contacto. Un campo **de opciones** guarda etiquetas; un campo **con
valor** guarda un número, una fecha, un texto o un sí/no. Personas y agentes los llenan con las mismas reglas.

Los nombres de las herramientas son los de MCP (`bi_…`). En el copiloto de Frepi son los mismos sin `bi_`.
Referencias: [esquema](references/esquema.md) (el JSON Schema) y [ejemplos](references/ejemplos.md). Cárgalas
con `bi_get_skill(name="etiquetas-y-campos", reference="esquema")`.

## 1. Elige el tipo y el nivel

| Pregunta | Respuesta | En el documento |
| --- | --- | --- |
| ¿Qué guarda? | Etiquetas de una lista | `type: "options"` |
| | Un número, una fecha, un texto, un sí/no | `type: "number" \| "date" \| "text" \| "bool"` |
| ¿Cuántas a la vez? | Una (poner una quita la anterior) | `options.mode: "one"` |
| | Varias (se suman) | `options.mode: "many"` |
| ¿Van en orden, como un embudo? | Sí | `options.ordered: true` |
| ¿Dónde se guarda? | Al contacto (un valor actual) | `level: "contact"` |
| | A la conversación (cada una guarda el suyo) | `level: "conversation"` |
| | Al mensaje (cada mensaje del cliente) | `level: "message"` |

El tipo, el modo y el nivel **no cambian** cuando el campo ya tiene valores.

## 2. Crea o cambia un campo

1. `bi_field_config_get(key=…)` devuelve el documento con `meta.etag` (sin `key`: las opciones para crear uno).
2. Cambia el documento. Una etiqueta conserva su `key`: si cambias el `label`, los valores la siguen.
3. `bi_field_config_apply(document=…)` la primera vez muestra qué cambia y a cuántos contactos toca. Con el
   `confirmation_token` de la tarjeta, lo guarda.

Antes de `bi_field_config_apply` sobre un campo que ya existe, léelo con `bi_field_config_get` y manda su
`meta.etag`: un documento sin etag para una clave que ya existe responde `field_key_taken`.

Una etiqueta con valores no se quita (`field_option_in_use`). Un `meta.etag` viejo es `field_etag_stale`:
vuelve a leer el campo.

Después de crear un campo, ofrece enlazarlo en los tableros y reportes (`bi_help conversation-links`).

## 3. Quién lo llena

- **Personas y agentes** (`fill.by = "agents"`): las personas siempre ganan; entre agentes gana el último. Un
  agente siempre trae la prueba (el mensaje).
- **Una consulta** (`bi_field_fill_from_query`): un SELECT que devuelve `contact_key` (o `phone`) y `value`, una
  fila por contacto. Sin modelo y sin créditos. Desde entonces solo la consulta cambia el campo.
- **Un conector**: lo llena la conexión. Nadie lo cambia aquí.

Reglas de opciones (`rules`): `onlyForward` (los agentes solo avanzan), `finalsLocked` (no salen de una etapa
final), `expiresDays` (se quita si el contacto no la vuelve a mencionar). `personsOnly` en una etiqueta: ningún
agente la pone ni la quita.

## 4. Pon valores

`bi_set_field_values(key, op, value, …)`: `op` = `set` | `add` | `remove` | `clear`. Al contacto: `contacts`
(teléfonos) o `audience_id`. A la conversación: `conversation_id`. Al mensaje: `message_id` (el `m=` de la url).
Un solo destino se aplica de una vez; más de uno pide confirmación. Un campo de consulta o de conector responde
`field_source_locked`.

## 5. Lee y cuenta

- `bi_get_contact_fields(contact)`: los valores de un contacto, de sus conversaciones y de sus mensajes, con
  quién, cuándo, la prueba y la url.
- `bi_field_report(key)`: conteos en el tiempo por valor; el embudo de un campo en orden.
- SQL (`sql_query`): `public_marts.contact_fields_v1`, `conversations_v1`, `conversation_fields_v1`,
  `message_fields_v1`, `field_history_v1` (cada cambio, con su procedencia) y `ai_field_values_v1` (lo que
  pusieron los agentes). Cada fila trae su `url`.

```sql
SELECT value, count(*) FROM public_marts.conversation_fields_v1
WHERE field_key = 'motivo_contacto' AND ended_at > now() - interval '30 days' GROUP BY value;
```

Las urls solo llevan ids: un contacto es `k<n>`, nunca un teléfono.

## 6. Si algo falla

`contact_fields_not_ready`: el workspace todavía no tiene su base de datos. Díselo al usuario y no reintentes.
