# Automatizaciones

Una **automatización** une dos cosas: un `trigger_type` —el evento que la despierta— y un `action_kind` —lo que hace cuando eso pasa—. Es el *cuándo* del plano agéntico: [Funciones y flujos](funciones-y-flujos.html) pone la lógica, [Tareas](tareas.html) pone el procedimiento, y esto decide qué los enciende.

Usa los scopes `automations:read`, `automations:write` y, para disparar a mano, `automations:execute`.

## El modelo en una frase

**Un tipo del catálogo, unas condiciones y una acción.** Nada más:

```ts
const auto = await met.automations.create({
  name: 'Publicar cuando el deal se gana',
  trigger_type: 'item.field_changed',
  conditions: { collection_id: 42, field: 'estado', to: 'ganado' },
  action_kind: 'flow',
  flow_id: 'f1e2d3c4-0000-4000-8000-123456789abc',
});

console.log(auto.id, auto.enabled);   // → '…', true
```
```python
auto = met.automations.create(
    name="Publicar cuando el deal se gana",
    trigger_type="item.field_changed",
    conditions={"collection_id": 42, "field": "estado", "to": "ganado"},
    action_kind="flow",
    flow_id="f1e2d3c4-0000-4000-8000-123456789abc",
)

print(auto["id"], auto["enabled"])
```
```bash
curl -X POST https://api.met.meteor.com.co/api/v1/workspaces/7/automations \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "name": "Publicar cuando el deal se gana",
        "trigger_type": "item.field_changed",
        "conditions": { "collection_id": 42, "field": "estado", "to": "ganado" },
        "action_kind": "flow",
        "flow_id": "f1e2d3c4-0000-4000-8000-123456789abc"
      }'
```

Nace encendida salvo que mandes `enabled: false`. Los campos opcionales son `description`, `icon`, `collection_id`, `action_config` y `enabled`.

## El catálogo manda

`trigger_type` no es texto libre: sale del catálogo. Léelo antes de construir nada, porque es también la lista de lo que puedes ofrecerle a tu usuario.

```ts
const catalogo = await met.automations.catalog();
```
```python
catalogo = met.automations.catalog()
```
```bash
curl https://api.met.meteor.com.co/api/v1/trigger-catalog \
  -H "Authorization: Bearer $MET_API_KEY"
```

Cada entrada trae `type` (lo que va en `trigger_type`), `category`, `label`, `description`, `icon`, `conditions_schema` —qué hay que llenarle—, `exposes` —qué variables recibe la acción cuando se dispara— y `enabled`.

> **`enabled: false` no es un permiso que te falte.** Son tipos publicados en el catálogo a los que el despachador todavía no reacciona. Se leen; crearlos responde `400`. Filtra por `enabled` antes de pintar opciones en tu propia interfaz.

Los tipos que hoy disparan, con sus condiciones obligatorias:

| `trigger_type` | Se dispara cuando | Condiciones obligatorias |
|---|---|---|
| `item.created` | Entra un ítem nuevo a una colección. | `collection_id` |
| `item.field_changed` | Un campo del ítem pasa a un valor concreto. | `collection_id`, `field`, `to` |
| `item.updated` | El ítem cambia, sin importar qué campo. | `collection_id` |
| `item.deleted` | Se borra un ítem de la colección. | `collection_id` |
| `contact.created` | Entra un contacto al CRM (manual, por canal o por API). | — |
| `contact.field_updated` | Cambia un campo del contacto. | `field`, `operator` |
| `date_time` | Llega un instante exacto. Una sola vez. | `scheduled_date` (ISO 8601) |
| `recurrent` | Toca el patrón cron. | `recurrence_pattern` |
| `field_datetime` | Un tiempo antes, en, o después de un campo de fecha. | `source`, `field_id`, `direction` |
| `webhook.received` | Llega un `POST` a un webhook entrante tuyo. | `endpoint_slug` |
| `manual` | Solo a pedido. Ver la nota más abajo. | — |

Al crear se verifica que las condiciones **obligatorias** estén presentes; el resto del objeto viaja tal cual y lo interpreta cada tipo. Si te falta una, la respuesta te dice cuál: `Condición "collection_id" requerida para trigger_type "item.created"`.

`recurrence_pattern` acepta un cron de cinco partes (`0 9 * * 1`) o uno de estos alias: `hourly` (en punto, cada hora), `daily` (9:00), `weekly` (lunes 9:00) y `monthly` (día 1, 9:00). El patrón se evalúa en la zona horaria de tu workspace, y si no tiene una configurada, en hora de Colombia.

> **`manual` no tiene disparo por API.** Se puede crear, pero `run-now` cubre únicamente procesos administrados y flujos recurrentes (más abajo). Si necesitas arrancar algo desde tu código cuando tú decidas, ejecuta el flujo directo con `met.flows.run(flowId)` o la tarea con `met.tasks.execute(taskId)`.

## Qué ejecuta: `action_kind`

| `action_kind` | Qué hace | Qué le tienes que dar |
|---|---|---|
| `task` | Ejecuta una tarea del workspace. | `task_id` |
| `flow` | Ejecuta un flujo **publicado**. | `flow_id` |
| `ai_field` | Rellena un campo de la colección con IA. | `collection_id` y `action_config.field` |

Si omites `action_kind`, se asume `task` — y entonces `task_id` pasa a ser obligatorio.

> El flujo tiene que estar **publicado**. Conectar uno que solo tiene borrador responde `400`: publícalo antes con `met.flows.publish(flowId)` — ver [Funciones y flujos](funciones-y-flujos.html). La tarea o el flujo, además, tienen que ser de tu mismo workspace.

## Leer, encender, apagar y borrar

```ts
const todas = await met.automations.list();
const deUnaTarea = await met.automations.list({ taskId: 'd4c3b2a1-…' });

const una = await met.automations.retrieve(auto.id);

await met.automations.setEnabled(auto.id, false);              // apagar
await met.automations.update(auto.id, { conditions: { collection_id: 42, field: 'estado', to: 'perdido' } });

await met.automations.remove(auto.id);
```
```python
todas = met.automations.list()
de_una_tarea = met.automations.list(task_id="d4c3b2a1-…")

una = met.automations.retrieve(auto["id"])

met.automations.set_enabled(auto["id"], False)
met.automations.update(auto["id"], conditions={"collection_id": 42, "field": "estado", "to": "perdido"})

met.automations.remove(auto["id"])
```
```bash
curl https://api.met.meteor.com.co/api/v1/workspaces/7/automations \
  -H "Authorization: Bearer $MET_API_KEY"

curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":false}'
```

`setEnabled()` es un atajo de `update()`: manda solo `enabled` y no toca el resto de la configuración.

Dos cosas que el listado devuelve y conviene mirar antes de creer que algo no anda:

- **`name` e `icon` siempre vienen resueltos.** Si no le pusiste nombre, cae al de la tarea o el flujo destino, y en último caso a `(sin nombre)`; el ícono cae al del tipo de disparador.
- **`task_name` y `flow_name`** te ahorran la segunda llamada para saber qué ejecuta cada fila.

## Cuando la pausa no la pusiste tú

Una automatización puede volver apagada sin que nadie la haya tocado. Pasa con las que ejecutan un flujo: si una sola se desboca y consume por sí misma el techo de corridas del día del workspace, Meteor **pausa esa automatización** y deja el motivo escrito.

```ts
const a = await met.automations.retrieve(id);
if (!a.enabled && a.paused_reason) {
  // la frenó el sistema; a.paused_reason dice por qué
}
```

`paused_reason: null` con `enabled: false` significa lo contrario: la apagó una persona. Volver a encenderla con `enabled: true` limpia el motivo y le da cuenta nueva.

Lo importante para tu integración: se pausa **la automatización culpable, no el workspace**. Las demás siguen corriendo.

## Procesos administrados

Algunas filas del listado llegan con `is_system_managed: true`. Son procesos que **publica y versiona Meteor** —reportes, recordatorios, herramientas que un Met usa dentro de una conversación—: su definición no se edita por `PATCH`, y `update()` con cualquier campo que no sea `enabled` responde `400`. Tampoco se borran.

A cambio, tienen una superficie propia. Antes de usarla, mira lo que la propia fila declara:

```ts
const p = await met.automations.retrieve(id);

p.is_system_managed;              // true
p.capabilities.editable_fields;   // ['schedule', 'flow']
p.capabilities.locked_fields;     // ['handler', 'tool_name', 'templates', 'recipients']
p.capabilities.can_restore;       // true
p.managed_revision;               // 2
p.manual_run;                     // ¿admite ejecución a pedido?
p.last_run;                       // la última corrida, ya resuelta
```

> Los seis métodos que siguen son **solo** para estas filas. Sobre una automatización que creaste tú responden `400`, y eso es el contrato, no un problema de tu key.

### Historial de corridas

```ts
const corridas = await met.automations.runs(id, { limit: 50 });
// [{ id, source: 'scheduled' | 'manual', status, run_key, result, error_message, started_at, finished_at }, …]
```
```python
corridas = met.automations.runs(automation_id, limit=50)
```
```bash
curl "https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/runs?limit=50" \
  -H "Authorization: Bearer $MET_API_KEY"
```

De la más reciente hacia atrás. `limit` va entre 1 y 100; sin él, 20. `run_key` es la llave de la ocurrencia (por ejemplo `2026-08-14`): es lo que hace que el horario no corra dos veces lo mismo.

Para el historial de un flujo tuyo, la ruta es otra: `met.flows.listRuns(flowId)`.

### Ejecutar a pedido

```ts
const { ok, already_processed, run } = await met.automations.runNow(id, {
  input: { periodo: '2026-08-14' },
});
```
```python
res = met.automations.run_now(automation_id, input={"periodo": "2026-08-14"})
```
```bash
curl -X POST https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/run-now \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":{"periodo":"2026-08-14"}}'
```

`already_processed: true` significa que esa ocurrencia ya estaba corrida y tu pedido no duplicó nada.

**La respuesta tiene dos formas y confundirlas cuesta una tarde:**

- Si el proceso ejecuta un trabajo del sistema (`action_kind: 'system_job'`), `run` es la corrida real: el mismo `id` que vas a ver después en `runs()`.
- Si ejecuta un flujo (`action_kind: 'flow'`), `run` viene con `status: 'processing'` y un `id` que es el del **flow run**, no el de una corrida administrada. Ese `id` no aparece nunca en `runs()`. Para seguirlo:

```ts
const flowRun = await met.flows.findRun(run.id);
```

Responde `400` si el proceso está pausado, si no admite ejecución a pedido (`manual_run` en `false`) o si es un flujo que no es recurrente.

### Cambiar el horario

```ts
await met.automations.updateSchedule(id, '0 12 * * 1-6');   // 12:00, de lunes a sábado
```
```python
met.automations.update_schedule(automation_id, "0 12 * * 1-6")
```
```bash
curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/schedule \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recurrence_pattern":"0 12 * * 1-6"}'
```

El cron es **acotado a propósito**: cinco partes, con una hora fija y días de la semana. El día del mes y el mes tienen que ir en `*`; cualquier otra cosa responde `400`. Y solo aplica si `capabilities.editable_fields` incluye `schedule` — un proceso que se activa por un evento no tiene horario que cambiar.

### Editar sin pisar a nadie

```ts
const p = await met.automations.retrieve(id);

await met.automations.updateConfiguration(id, {
  expected_revision: p.managed_revision,
  recurrence_pattern: '0 7 * * 1-5',
  flow_id: null,                 // desconecta el flujo posterior
});
```
```python
p = met.automations.retrieve(automation_id)

met.automations.update_configuration(
    automation_id,
    expected_revision=p["managed_revision"],
    recurrence_pattern="0 7 * * 1-5",
    flow_id=None,
)
```
```bash
curl -X PATCH https://api.met.meteor.com.co/api/v1/workspaces/7/automations/$AUTO_ID/configuration \
  -H "Authorization: Bearer $MET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expected_revision":2,"recurrence_pattern":"0 7 * * 1-5","flow_id":null}'
```

`expected_revision` es la revisión que traía la fila cuando la leíste. Si alguien la cambió en el medio, la llamada falla con **`409`** en vez de pisar el cambio ajeno; en los SDK llega como `MetInvalidRequestError` con `status` 409. Relee el proceso y vuelve a intentar con la revisión nueva.

Manda al menos uno de los dos campos configurables: sin ninguno responde `400`. `flow_id: null` es explícito y sí se envía — desconecta el flujo.

### Deshacer y ver quién tocó qué

```ts
await met.automations.restoreConfiguration(id, { expected_revision: p.managed_revision });

const cambios = await met.automations.configurationHistory(id, { limit: 20 });
// [{ revision, operation: 'update' | 'restore', changed_by_user_id, before_config, after_config, created_at }, …]
```
```python
met.automations.restore_configuration(automation_id, expected_revision=p["managed_revision"])

cambios = met.automations.configuration_history(automation_id, limit=20)
```

`restoreConfiguration()` devuelve el proceso a la configuración que publicó Meteor y solo aplica si `capabilities.can_restore` es `true`. El historial va de lo más reciente hacia atrás, con `limit` entre 1 y 100 (sin él, 20).

## Programar una tarea: usa esta API, no la de la tarea

Existe una ruta anterior para colgarle un disparador a una tarea desde la tarea misma (`POST /tasks/{taskId}/triggers`). Sigue publicada por compatibilidad, pero lo que crea **no queda atado a un workspace**, así que no aparece en `GET /workspaces/{workspaceId}/automations` ni pasa por la validación del catálogo.

Para programar una tarea hoy, crea una automatización que apunte a ella:

```ts
await met.automations.create({
  name: 'Calificar leads cada lunes',
  trigger_type: 'recurrent',
  conditions: { recurrence_pattern: '0 9 * * 1' },
  action_kind: 'task',
  task_id: tarea.id,
});
```

## Errores que vas a ver

| Respuesta | Qué la causa |
|---|---|
| `400` `trigger_type "…" no existe en el catálogo` | El tipo no está en `GET /trigger-catalog`. |
| `400` `…todavía no está habilitado en producción` | El tipo existe pero llegó con `enabled: false`. |
| `400` `Condición "…" requerida para trigger_type "…"` | Falta una condición obligatoria. |
| `400` `El flow no ha sido publicado` | Conectaste un flujo que solo tiene borrador. |
| `400` `La task no pertenece a este workspace` | La tarea o el flujo son de otro workspace. |
| `400` `Esta automatización no tiene historial administrado` | Llamaste a `runs()` sobre una automatización tuya. |
| `400` `Los procesos administrados solo permiten activar o pausar` | Un `update()` con campos que no son `enabled` sobre un proceso administrado. |
| `409` `La configuración cambió en otra sesión` | Tu `expected_revision` quedó viejo. |

El resto —`401` por key o workspace equivocado, `429` por límites— se comporta igual que en toda la API: ver [Errores, idempotencia y paginación](errores-e-idempotencia.html).
