# Etiquetas de contactos

Las etiquetas son la forma más rápida de segmentar el CRM: «cliente», «lead-caliente», «Participante». En Met viven en dos lugares, y conviene tenerlos separados:

- **El catálogo** (`met.contactTags`): las etiquetas que existen en el workspace, aunque ningún contacto las tenga todavía. Es lo que ves en Ajustes y en los selectores del panel, de las difusiones y de los flujos.
- **La asignación** (`met.contacts.addTag` y compañía): qué etiquetas tiene cada contacto. Se guarda en el campo de sistema `$tags` del contacto.

Leer pide `contacts:read`; escribir, `contacts:write`. Las dos funcionan con una key de test. Las rutas son planas (`/contact-tags`, `/contacts/…`): operan sobre el workspace de la key.

## El catálogo

```ts
const etiquetas = await met.contactTags.list();
// [{ id: 74, name: 'lead-caliente' }, { id: 75, name: 'Participante' }]

const vip = await met.contactTags.create({ name: 'VIP' });
await met.contactTags.rename(vip.id, 'Cliente VIP');
await met.contactTags.delete(vip.id); // { ok: true }
```
```python
etiquetas = met.contact_tags.list()

vip = met.contact_tags.create({"name": "VIP"})
met.contact_tags.rename(vip["id"], "Cliente VIP")
met.contact_tags.delete(vip["id"])
```
```bash
curl -X POST https://api.met.meteor.com.co/api/v1/contact-tags \
  -H "Authorization: Bearer $METEOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"VIP"}'
```

- `list()` trae **todo** el catálogo, ordenado por nombre, incluidas las etiquetas que nadie usa.
- El nombre es único en el workspace **sin distinguir mayúsculas**: `VIP` y `vip` son la misma etiqueta, y crear la segunda responde `409`. Hasta 60 caracteres; se guardan sin espacios al borde.
- `create()` solo la registra en el catálogo: no se la pone a ningún contacto.
- `rename()` reescribe el nombre **en todos los contactos que la tenían**, para que no queden etiquetas huérfanas.
- `delete()` la borra del catálogo **y se la quita a todos los contactos**. No se puede deshacer.

## Ponerle y quitarle etiquetas a un contacto

```ts
await met.contacts.addTag(4821, 'lead-caliente');
await met.contacts.removeTag(4821, 'frío');

const suyas = await met.contacts.tags(4821); // ['lead-caliente', 'Participante']
```
```python
met.contacts.add_tag(4821, "lead-caliente")
met.contacts.remove_tag(4821, "frío")

suyas = met.contacts.tags(4821)
```

Ponerle a un contacto una etiqueta que no está en el catálogo la registra en el catálogo sola. Repetir una que ya tiene no hace nada.

## A muchos contactos a la vez

Una etiqueta a una lista de contactos, en una sola llamada (hasta 500 por llamada):

```ts
const r = await met.contacts.bulkAddTags({ contact_ids: [4821, 4822, 4830], tag: 'Feria 2026' });
// { updated: 2, requested: 3, tag: 'Feria 2026' }

await met.contacts.bulkRemoveTags({ contact_ids: [4821, 4822], tag: 'Feria 2026' });
```
```python
r = met.contacts.bulk_add_tags({"contact_ids": [4821, 4822, 4830], "tag": "Feria 2026"})
met.contacts.bulk_remove_tags({"contact_ids": [4821, 4822], "tag": "Feria 2026"})
```

`updated` cuenta solo los contactos que de verdad cambiaron: los que ya tenían la etiqueta, o los ids que no son contactos de este workspace, no suman. Va **una** etiqueta por llamada (`tag`, no una lista).

## Filtrar por etiqueta

`contacts.list()` acepta condiciones sobre los campos del contacto en `filters`, un arreglo JSON. Para las etiquetas, el campo es `$tags` y los operadores son `has` y `not_has`:

```ts
const { data, total } = await met.contacts.list({
  filters: JSON.stringify([{ field: '$tags', op: 'has', value: 'lead-caliente' }]),
});
```
```python
import json

pagina = met.contacts.list(filters=json.dumps([{"field": "$tags", "op": "has", "value": "lead-caliente"}]))
```

Una condición mal escrita se **ignora** en vez de dar error: revisa `total` antes de confiar en el filtro. Para saber qué etiquetas existen, sumando las del catálogo y las que tengan los contactos aunque no estén registradas, usa `met.contacts.tagsDistinct()`.

## Enterarte de los cambios

No hay un evento propio de etiquetas. Ponerle o quitarle una etiqueta a un contacto cambia el contacto, y eso sale como `contact.updated` (scope `contacts:read`; ver [Webhooks](webhooks.html)).

## Errores comunes

| Código | Qué pasó |
|---|---|
| `400` `validation_error` | El nombre está vacío o pasa de 60 caracteres; en lote, más de 500 contactos o una lista vacía. |
| `404` `not_found` | La etiqueta (en `rename` o `delete`) o el contacto no existe en este workspace. |
| `409` `conflict` | Ya existe una etiqueta con ese nombre, sin contar mayúsculas. |
| `403` `missing_scope` | La key no trae `contacts:write` (o `contacts:read` para leer). |
