# Campos personalizados (/es/guides/custom-fields)

Además de sus campos integrados (`email`, `subscribed`, `createdAt`, `updatedAt`), cada contacto de Bitelio lleva un objeto `data` de forma libre. Lo que vive ahí depende enteramente de ti: nombres, niveles de plan, fechas de alta, tus propios ID de usuario — cualquier cosa que más adelante quieras tener disponible en plantillas, segmentos o condiciones de flujos de trabajo.

Esta guía recorre el ciclo de vida completo de los campos personalizados: cómo escribirlos, cómo funciona el tipado, dónde puedes usarlos y cómo retirarlos.

## Establecer campos personalizados

Puedes escribir campos personalizados cada vez que se crea o actualiza un contacto:

```bash
# Vía /v1/track (crea el contacto automáticamente)
curl -X POST https://api.bitelio.com/v1/track \
  -H "Authorization: Bearer pk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "ada@example.com",
    "event": "signed_up",
    "data": {
      "firstName": "Ada",
      "plan": "pro",
      "signupDate": "2026-05-06T12:00:00Z",
      "lifetimeValue": 240
    }
  }'

# Vía PATCH /contacts/:id
curl -X PATCH https://api.bitelio.com/contacts/cnt_abc123 \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "data": { "plan": "enterprise" } }'
```

Las actualizaciones a `data` son fusiones, no reemplazos — solo se tocan las claves presentes en tu payload, y todo lo demás queda como estaba.

### Valores especiales

| Valor                          | Comportamiento                                                                                                                                 |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Primitivo                      | Se guarda. Disponible para plantillas, segmentos, flujos de trabajo.                                                                           |
| `null`                         | Elimina la clave del contacto.                                                                                                                 |
| `""` (vacío)                   | Se ignora — no sobrescribe los datos existentes.                                                                                               |
| `{ value, persistent: false }` | Se usa solo para este envío; no se guarda en el contacto. Útil para códigos de un solo uso (restablecimientos de contraseña, enlaces mágicos). |

<Callout type="warn" title="Cambio de agosto de 2026">
  Un campo no persistente se escribe como `{ value, persistent: false }` y se referencia como
  `{{ campo }}`: la envoltura se resuelve por ti. Eso ya era así en todas partes menos en los
  emails, SMS y webhooks de los flujos de trabajo, donde hasta agosto de 2026 la envoltura llegaba
  cruda a la plantilla y lo único que se pintaba era `{{ campo.value }}`.

  Los tres se comportan ya como el resto de la plataforma. **Si escribiste `{{ campo.value }}` en
  la plantilla de un flujo como apaño, cámbialo a `{{ campo }}`**: la forma antigua se pinta vacía
  y nada te avisa.
</Callout>

## Inferencia de tipos

La primera vez que un campo aparece con un valor no nulo en cualquier parte de tu proyecto, Bitelio le asigna un tipo:

| Detectado como | Ejemplos                                                              |
| -------------- | --------------------------------------------------------------------- |
| Número         | `42`, `3.14`                                                          |
| Booleano       | `true`, `false`                                                       |
| Fecha          | Cadenas que coinciden con `YYYY-MM-DD` o `YYYY-MM-DDTHH:MM:SS[.sss]Z` |
| Texto          | Cualquier otra cosa                                                   |

Ese tipo determina la interfaz del creador de segmentos — los campos de fecha obtienen selectores de fecha, los campos numéricos obtienen entradas numéricas, y así sucesivamente — y una vez establecido se mantiene para el proyecto. Si más adelante envías un tipo distinto para la misma clave, la interfaz sigue respetando la inferencia original, aunque Bitelio seguirá guardando cualquier valor que llegue.

Los operadores sensibles a fechas (`within`, `olderThan`) solo se aplican a campos personalizados guardados como cadenas ISO 8601 completas: `"2026-05-06T12:00:00Z"` califica, `"05/06/2026"` no.

## Claves reservadas

Un puñado de claves pertenecen a Bitelio y se **filtran silenciosamente** de todo payload `data`. Enviarlas no las guarda ni genera un error:

`id`, `bitelio_id`, `bitelio_email`, `email`, `unsubscribeUrl`, `subscribeUrl`, `manageUrl`

Los campos de contacto principales — `email`, `subscribed`, `createdAt`, `updatedAt` — también viven fuera de `data`. Cambiar `email` o `subscribed` requiere los campos de solicitud dedicados en lugar de `data`.

Una clave queda en un punto intermedio: `locale`. Se guarda dentro de `data` como cualquier otro campo personalizado, pero Bitelio también la lee para gobernar la [localización](/es/guides/localization).

## Usar campos personalizados

### En plantillas

Cualquier campo puede referenciarse por su nombre como variable de Handlebars:

```html
<p>Hi {{firstName ?? "there"}},</p>
<p>You've been on the {{plan}} plan since {{signupDate}}.</p>
```

El operador `?? fallback` es una extensión de Bitelio para suministrar un valor por defecto cuando el campo falta o es nulo. Los datos anidados también funcionan — un valor guardado en `data.profile.tier` está disponible como `{{profile.tier}}`.

### En segmentos

En los filtros de segmento, antepone `data.` al nombre del campo:

```json
{ "field": "data.plan", "operator": "equals", "value": "pro" }
{ "field": "data.lifetimeValue", "operator": "greaterThan", "value": 100 }
{ "field": "data.signupDate", "operator": "within", "value": 30, "unit": "days" }
```

La taxonomía completa de operadores está documentada en la [página de conceptos de Segmentos](/es/concepts/segments).

### En flujos de trabajo

Los pasos `CONDITION` de un flujo de trabajo usan la misma notación `data.`. Y con el paso `UPDATE_CONTACT`, un flujo de trabajo puede escribir campos personalizados mientras se ejecuta — por ejemplo, estableciendo `{ stage: "activated" }` una vez que se ha abierto el correo de bienvenida.

## Inspeccionar tus campos personalizados

Para ver cada campo que tu proyecto ha usado alguna vez — integrados y personalizados por igual — llama a `GET /contacts/fields`. Cada entrada informa el tipo inferido y qué fracción de contactos lleva un valor:

```json
{
  "fields": [
    { "field": "email",        "type": "string",  "isCustom": false, "coverage": 1.0 },
    { "field": "data.plan",    "type": "string",  "isCustom": true,  "coverage": 0.62 },
    { "field": "data.signupDate", "type": "date",  "isCustom": true,  "coverage": 0.95 }
  ]
}
```

Esto es útil para auditar tu esquema, detectar campos que han quedado obsoletos, o poblar menús desplegables en una interfaz propia.

Para cualquier campo individual, `GET /contacts/fields/:field/values` lista los valores distintos que ha tomado — algo natural para un selector tipo "Filtrar por plan" en tu panel.

## Limpiar campos sin usar

Los campos tienden a acumularse con el tiempo. Dos endpoints hacen que la poda sea segura:

* `GET /contacts/fields/:field/usage` — informa cada segmento, campaña y flujo de trabajo que menciona el campo. Revisa esto antes de eliminar, para que nada se rompa.
* `DELETE /contacts/fields/:field` — elimina el campo de todos los contactos del proyecto.

La eliminación solo afecta los datos de contacto. Los segmentos y flujos de trabajo que referenciaban el campo no se eliminan — sus condiciones sobre él simplemente nunca volverán a coincidir. Por eso existe el endpoint de uso: encuentra esas referencias primero y ordénalas.

## Buenas prácticas

* **Elige nombres de campo que no vayas a renombrar.** Un renombramiento repercute en cada segmento, plantilla y flujo de trabajo que mencione el campo.
* **Guarda las fechas como ISO 8601.** Cualquier otro formato te deja sin acceso a los operadores de segmento sensibles a fechas.
* **Mantén los secretos fuera de `data`.** Todo lo que hay ahí es visible en el panel y para cualquiera con acceso a la API. Para códigos de un solo uso que no deban permanecer, usa `{ value, persistent: false }`.
* **Prefiere claves planas cuando un campo tiene muchos valores.** Las rutas anidadas como `data.profile.tier` funcionan bien, pero son un poco más difíciles de detectar en la interfaz de segmentos.
