Automatizaciones
El modelo cuándo, si y haz del motor de reglas — versiones, el ensayo que no materializa nada, el historial de ejecuciones y sus pasos, el relanzamiento, los límites del motor y las reglas de cartera de una cuenta.
El motor de automatizaciones convierte un evento en trabajo. Una regla
escucha un tipo de evento, una condición decide si ese evento concreto es su
caso, y se ejecuta una lista ordenada de acciones. Todo cuelga de
https://api.factuarea.com/v1/companies/{company}/automations, requiere el módulo automations del
plan y usa cuatro scopes finos: automations:read, automations:write,
automations:delete y automation_runs:read. Consulta
Scopes y permisos.
Cuándo, si y haz
Una regla tiene exactamente tres piezas móviles:
| Pieza | Campo | Qué decide |
|---|---|---|
| Cuándo | trigger_type | El evento que escucha la regla, en forma resource.action (invoice.paid) |
| Si | conditions | Un árbol de condiciones sobre los campos de ese evento. Un {} vacío significa sin condición: la regla actúa siempre |
| Haz | actions | Las acciones que se ejecutan cuando un evento casa, en el orden en que las declares |
Pregunta al catálogo en vez de adivinar. GET /v1/companies/{company}/automations/catalog devuelve
los disparadores a los que tu empresa puede suscribirse —ya filtrados por los
módulos que incluye tu plan, así que un disparador gobernado por un módulo que no
has contratado ni se ofrece—, más el conjunto cerrado de operadores y
combinadores que admite una condición y las acciones con adaptador registrado en
este despliegue. Es determinista: dos llamadas seguidas sin cambios de
configuración devuelven el mismo cuerpo, así que cachéalo y compáralo.
Los campos evaluables de un disparador son una segunda llamada. Están fuera
del catálogo a propósito: alrededor de un centenar de disparadores visibles con
decenas de propiedades cada uno darían un documento de cientos de kilobytes para
acabar usando los campos de uno. Pide
GET /v1/companies/{company}/automations/catalog/triggers/{trigger}/fields
para el disparador que hayas elegido, y construye la condición contra los campos
que enumere.
Hoy existen doce tipos de acción: notify_in_app, notify_channel,
emit_webhook, create_calendar_event, send_document_email,
send_payment_reminder, change_status, tag_entity y, para tareas,
create_task, change_task_status, assign_task y add_task_comment:
consulta Tareas y proyectos. El catálogo anuncia solo los que tienen
adaptador cableado, nunca la lista de tipos declarados — elegir un tipo sin
adaptador produciría una regla cuyo paso se omite al ejecutarse.
create_calendar_event está retirada: el calendario al que alimentaba ya no
existe, así que el nombre se queda en el vocabulario —una versión sellada antes
sigue leyéndose—, pero ningún adaptador la ejecuta y su paso no produce ningún
efecto. Usa create_task en su lugar.
El enumerado type de una acción ha crecido. Si tu integración valida
actions[].type contra una lista cerrada, añade create_task,
change_task_status, assign_task y add_task_comment, y sigue reconociendo
create_calendar_event para que las versiones antiguas de las reglas se sigan
leyendo.
Una regla nace inactiva. Crearla sella la versión 1 y la deja en draft: no
escucha nada hasta que la actives con
POST /v1/companies/{company}/automations/rules/{rule}/activate.
La activación comprueba el tope de automatizaciones activas del plan. No
comprueba de antemano el presupuesto mensual de ejecuciones: se comprueba al
admitir una ejecución. Lee
el endpoint de consumo
para consultar ambos contadores antes de activar.
curl -X POST https://api.factuarea.com/v1/companies/{company}/automations/rules \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Warn me on a large payment",
"scope": "empresa",
"trigger_type": "invoice.paid",
"conditions": { "field": "total", "operator": "gte", "value": 1000 },
"actions": [
{
"type": "notify_in_app",
"order": 0,
"parameters": {
"recipient_type": "role",
"role": "admin",
"category": "invoice",
"title": "Large payment",
"message": "An invoice over 1000 EUR has been paid"
}
}
]
}'Tareas y proyectos
Las automatizaciones llegan al módulo de tareas en los dos
sentidos: sus eventos ponen en marcha reglas, y cuatro acciones crean y cambian
tareas. Ambos necesitan el módulo tasks.
Disparadores
Los 21 eventos de tareas y proyectos
—task.*, task_comment.*, task_time_entry.* y project.*— son disparadores
válidos. Como todo disparador gobernado por un módulo, existen solo para una
empresa con el módulo tasks: si no, el catálogo no los ofrece y guardar una
regla que use uno devuelve 422. Las condiciones leen los campos que publica cada
evento —en una tarea, entre otros, su estado, columna, prioridad, proyecto,
persona asignada, fecha de vencimiento y fecha de completado; consúltalos con
GET /v1/companies/{company}/automations/catalog/triggers/{trigger}/fields—
y la ejecución congela como payload el data.object del evento.
Acciones
| Acción | Qué hace | Parámetros |
|---|---|---|
create_task | Crea una tarea | project_id (obligatorio: un proyecto activo de tu empresa), title (obligatorio, hasta 500 caracteres), description (Markdown), priority, assignee_mode (user, role o trigger_actor) con assignee_user_id o assignee_role (owner, admin o member), label_ids, la fecha de vencimiento y link_to_trigger |
change_task_status | Mueve la tarea del disparador | target: el id de una columna del proyecto de la tarea, o planned o archived |
assign_task | Asigna la tarea del disparador | assignee_user_id: un miembro de tu empresa |
add_task_comment | Comenta la tarea del disparador | body: Markdown, de 1 a 10 000 caracteres |
{
"name": "Follow up approved quotes",
"scope": "empresa",
"trigger_type": "quote.approved",
"conditions": {},
"actions": [
{
"type": "create_task",
"order": 0,
"parameters": {
"project_id": "0193a4f2-7c20-7a11-8b52-4d6e8f0a2c01",
"title": "Prepare the delivery of the approved quote",
"priority": "high",
"assignee_mode": "role",
"assignee_role": "admin",
"link_to_trigger": true
}
}
]
}- La tarea del disparador. Las tres últimas acciones actúan sobre ella: la
propia tarea en los eventos
task.*, y la tarea a la que pertenece un comentario o una imputación de tiempo en los eventostask_comment.*ytask_time_entry.*. Con cualquier otro disparador, o si la tarea ya no existe, el paso se descarta contask_target_not_found. - Fecha de vencimiento de
create_task. Envíadue_on(una fecha explícita) odue_from_payload_pathcondue_offset_days(un campo de fecha del payload congelado más un número de días), nunca las dos. Un campo que falta o que no es una fecha hace fallar el paso con un mensaje que lo nombra: no se inventa ninguna fecha. Sin ninguna de las dos, la tarea no tiene fecha de vencimiento. link_to_triggervincula la tarea nueva con la entidad que disparó la regla —una factura, un presupuesto, un contacto…—, de modo que aparece entre las tareas vinculadas de esa entidad. Si el disparador no tiene una entidad vinculable, la tarea se crea sin vínculo y la traza del paso lo indica.- Persona asignada.
assignee_mode: "trigger_actor"asigna al usuario que causó el evento, y deja la tarea sin asignar cuando el evento no identifica a ningún miembro.roleelige al primer usuario de la empresa con ese rol, en un orden determinista. Un usuario que no es miembro de la empresa hace fallar el paso y no crea nada. - Los comentarios de una automatización no tienen un usuario como autoría (su
author.typeesautomation); nadie puede editarlos y un admin puede borrarlos. - Las referencias se comprueban contra tu empresa —el proyecto, la persona
asignada, las etiquetas y un
targetque sea una columna— al guardar la regla (422, con el mismo mensaje tanto si la referencia es de otra empresa como si no existe) y de nuevo al ejecutarla. - Pasos descartados. Un
create_taskcuyo proyecto se archivó después de guardar la regla se descarta contask_project_not_foundy se puede relanzar cuando lo desarchives; una empresa que perdió el módulotasksrecibemodule_not_accessible, relanzable cuando el módulo vuelva. - Los bucles se cortan. Una regla
task.created→create_taskllega al tope de profundidad de cadena como cualquier otra cadena. - El ensayo valida parámetros, referencias, sujeto y módulo exactamente igual que una ejecución real, y no crea ninguna tarea ni ningún comentario.
Cada edición sella una versión
Editar una regla nunca reescribe su definición anterior. Sella una versión
nueva y sube current_version, y cada ejecución conserva un puntero a la
versión exacta que ejecutó, así que una ejecución de hace semanas se sigue
leyendo después de que la regla haya avanzado.
PATCH /v1/companies/{company}/automations/rules/{rule}actualiza de forma parcial: los campos que omitas conservan su valor, y""endescriptionlo vacía. El estado queda intacto — editar una regla activa la deja activa, ejecutando la versión nueva desde su siguiente evento.- Pausar y activar no tocan la definición, así que no sellan ninguna versión.
GET /v1/companies/{company}/automations/rules/{rule}/versionslista las versiones selladas y.../versions/{version}devuelve una, con el árbol de condiciones y las acciones congelados tal y como estaban.
El rule_version de una ejecución apunta exactamente a ese número. Puede quedar
por detrás de current_version, y esa es la gracia.
El ensayo no materializa nada
POST /v1/companies/{company}/automations/rules/{rule}/dry-run
responde qué haría una regla ante un evento de ejemplo. No ejecuta nada: no
sale ningún correo, no se crea ninguna entrega de webhook, no se muta ninguna
entidad, no se escribe ninguna fila de ejecución ni de paso, y no consume ni el
presupuesto mensual ni el límite de frecuencia del motor. Por eso pide el scope
de lectura aunque sea un POST — el evento de ejemplo tiene que viajar en el
cuerpo.
La respuesta trae matched, una entrada de condition_trace por cada hoja
evaluada en orden de evaluación, y una entrada de steps por acción con lo que
resolvería. Una condición que no se puede evaluar vuelve como condition_error
con un 200, no como un fallo HTTP: ver por qué una regla no se puede evaluar
antes de activarla es justo para lo que sirve el ensayo.
Si la regla actúa sobre documentos — enviar un documento por correo, enviar un
recordatorio de cobro, cambiar un estado, etiquetar una entidad — pasa
event_aggregate_id con el id de un documento real tuyo. Sin él, el ensayo
informa de que no se ejecutaría ningún paso, y una regla perfectamente correcta
parece rota.
curl -X POST https://api.factuarea.com/v1/companies/{company}/automations/rules/{rule}/dry-run \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"event_type": "invoice.paid",
"event_payload": { "total": 1200, "status": "paid" }
}'Ejecuciones y sus pasos
Cada evento admitido produce una ejecución, y cada acción de la definición
congelada produce un paso. Una ejecución lleva la definición que ejecutó
(rule_version + rule_snapshot) y el payload del evento que la disparó
(event_payload), ambos congelados en el momento de ejecutar.
| Estado de la ejecución | Qué significa |
|---|---|
pending | En cola, sin empezar |
running | En curso |
completed | Todos los pasos aplicaron su efecto |
failed | Terminó sin producir su efecto, y no es reprocesable |
dead_lettered | Aparcada, a la espera de un relanzamiento manual |
blocked | Detenida por un límite del motor antes de ejecutarse |
Los pasos vuelven siempre en orden de ejecución, porque step_index
—empezando en cero— es la identidad del paso: es lo que se relanza y lo que el
evento automation_run.step_dead_lettered publica como data.step_index. Cada
paso lleva su action_type, sus parameters congelados, el result que
devolvió su adaptador y una marca replayable.
Dos ejes ortogonales explican un desenlace. discard_reason es el motivo de
descarte clasificado, de un catálogo cerrado —condition_not_matched,
chain_depth_exceeded, rate_limit_exceeded, monthly_budget_exhausted,
step_attempts_exhausted…—, así que ramifica según él. last_error es el
mensaje técnico crudo, pensado para depurar. discard_reason_label es la
etiqueta humana del motivo y va siempre en español: pertenece al vocabulario
del motor y no sigue Accept-Language.
Lee el historial con
GET /v1/companies/{company}/automations/runs
—filtra por automation_rule_id, status o una ventana created[…]—,
GET /v1/companies/{company}/automations/runs/{run}
y GET /v1/companies/{company}/automations/runs/{run}/steps. Los tres paginan por cursor; consulta
Paginación.
Relanzar vuelve a ejecutar el trabajo de verdad
Dos operaciones rearman trabajo aparcado: POST /v1/companies/{company}/automations/runs/{run}/replay
rearma todos los pasos aparcados de una ejecución, y
POST /v1/companies/{company}/automations/runs/{run}/steps/{step_index}/replay
rearma uno solo, dejando a sus hermanos con su estado, su motivo y sus marcas de
tiempo intactos.
Los pasos rearmados se ejecutan de verdad: envían correo, entregan webhooks y llaman a terceros. Esto no es un reintento inerte — confírmalo con el titular de la cuenta antes de llamarlo.
- Solo se rearman los pasos aparcados con un motivo relanzable. Una ejecución
todavía en curso, o una que terminó limpiamente, se rechaza con un 422
automation_replay_not_allowed. - La respuesta es un 202 —aceptado y encolado, no terminado— y su cuerpo
lleva solo identificadores, deliberadamente sin
status, porque «encolado» no es uno de los estados de la ejecución. Comprueba el desenlace con la ejecución, o más barato con sus pasos. - Llamarlo dos veces no duplica el efecto: el motor revisa de nuevo el estado de cada paso dentro de su propio bloqueo.
step_indexes el índice que publica el endpoint de pasos, no la posición de la acción en la definición de la regla. Un índice fuera de rango devuelve un 404, igual que una ejecución que no es tuya.
Los límites del motor
El plan acota cuántas reglas puedes activar. Por separado, cuatro límites
acotan las ejecuciones: una ejecución detenida por uno de ellos queda registrada
como blocked con su motivo tipado — bloqueada no es fallida, y leer el motivo
las distingue. El rechazo por el tope de activas no crea una ejecución.
| Límite | Qué acota | Motivo y código de error |
|---|---|---|
| Automatizaciones activas | Reglas activas y no eliminadas de tu empresa; se comprueba al activar o reanudar | Activación rechazada · 402 automation_active_rules_limit_exceeded |
| Profundidad de encadenamiento | Saltos «acción → evento → regla», tres por defecto | chain_depth_exceeded · 422 automation_chain_depth_exceeded |
| Límite de frecuencia | Ejecuciones admitidas por minuto y empresa. Un freno de pico, no de volumen | rate_limit_exceeded · 429 automation_rate_limit_exceeded |
| Presupuesto mensual | Ejecuciones que permite el plan por periodo | monthly_budget_exhausted · 402 automation_monthly_budget_exhausted |
| Fallos consecutivos | Una racha de ejecuciones fallidas pausa la regla sola | Evento automation_rule.auto_paused con consecutive_failures |
Un evento que produce una persona o una integración entra con profundidad
cero, así que solo consume el presupuesto de encadenamiento lo que provocó una
automatización. Una regla que reacciona a automation_run.failed y vuelve a
fallar no puede, por tanto, repetirse sin fin.
GET /v1/companies/{company}/automations/usage es donde ves cómo vas frente al presupuesto mensual:
consumed, limit (null significa sin techo automático, nunca «desconocido» — trátalo
como que no hay barra de progreso, no como cero), reset_at ya resuelto, el
period en formato YYYY-MM y el threshold_percent a partir del cual la
plataforma avisa al titular de la cuenta. Ese porcentaje viaja para que tu aviso
y el nuestro no se desalineen.
La misma respuesta también incluye active_rules: {count, limit}: count es
el número de reglas activas y no eliminadas de la empresa; los borradores y las
pausadas no cuentan. limit es el tope del plan, incluido cero cuando no se
permite activar, o null cuando no hay un techo automático. La capacidad de
Enterprise se acuerda según contrato. La respuesta pública v1 y MCP
get_automation_usage exponen el mismo par, sin limit_mode.
Fragmento de la respuesta:
{
"data": {
"active_rules": { "count": 7, "limit": 7 }
}
}Cuando count >= limit con un tope fijo, activar o reanudar una regla se rechaza
con automation_active_rules_limit_exceeded: v1 devuelve 402 con
error.type: payment_required_error y error.details: {active_rules, limit}.
Pausa una regla activa con POST /v1/companies/{company}/automations/rules/{rule}/pause o amplía el
plan antes de volver a intentarlo. MCP activate_automation_rule devuelve
JSON-RPC -32008, con el mismo código en data.code, data.http_status: 402 y
el mismo par en data.details; la app responde HTTP 422 con el mismo código.
Las reglas ya activas por encima del tope siguen ejecutándose y se pueden editar o pausar. Este tope no las pausa automáticamente; solo impide nuevas activaciones. Pausar libera una plaza de activas, y reanudar vuelve a comprobar el tope. El presupuesto mensual de ejecuciones sigue separado y se comprueba al ejecutar.
Un paso concreto también tiene tope de intentos. Cuando se agota, el paso queda
aparcado como dead_lettered con step_attempts_exhausted y espera un
relanzamiento — es el único estado que admite reentrada.
Reglas de cartera
Una regla declara qué NIF vigila en su campo opcional scope: empresa
—el valor que toma si lo omites— vigila los eventos de la empresa que la posee, y
cartera vigila los eventos de la cartera de la cuenta que la posee, y
entrega el aviso a esa empresa, el NIF titular de la cuenta. Se diseñaron para
las gestorías, que son cuentas como cualquier otra.
El alcance es inmutable. Lo eliges al crear la regla, y para cambiarlo creas
otra regla. Una edición que envíe un valor distinto se rechaza con
automation_rule_scope_immutable:
manda el alcance que la regla ya tiene, u omite el campo.
Qué vigila una regla de cartera. Los otros NIF reales de la cuenta, más los
NIF que otra cuenta pone a su alcance por una relación vigente con nivel
operate, un acceso cedido o una
cuenta gestionada. Una relación con nivel view, o
una que se revocó o ha terminado, no aporta nada. La regla nunca vigila la
empresa que la posee: si quieres la misma automatización para esa empresa, créala
con alcance empresa.
El universo no es una lista guardada. Se calcula a partir de cada evento y se
comprueba de nuevo en cada paso: al elegir las reglas candidatas de un evento,
antes de escribir la ejecución, dentro de cada paso antes de que se ejecute su
acción, y cuando relanzas una ejecución o un paso. Un NIF que se ha archivado,
desactivado o traspasado a otra cuenta, o cuya relación se revocó, terminó o bajó
a view, no pasa la comprobación: no se escribe ninguna ejecución para él o, si
la ejecución ya existe, el paso se detiene como blocked con el motivo
tenant_mismatch y su acción no se ejecuta nunca. Relanzar no la esquiva.
Quién puede crear una regla de cartera. Solo la empresa titular de una
cuenta que tenga concedido el módulo gestoria, por plan (empresario y
enterprise) o por un override de empresa. Lo primero que se pregunta es si la
empresa es la titular de su cuenta: un NIF adicional —uno que la cuenta compró,
recibió por traspaso o dio de alta— nunca tiene cartera propia, aunque se le haya
concedido el módulo, porque la cartera es de la cuenta y la representa su
titular. No necesitas un segundo NIF para escribir la regla; mientras la cuenta
no lo tenga, simplemente no se dispara.
El catálogo devuelve siempre los dos alcances, cada uno con available y,
cuando no lo está, un unavailable_reason: module_not_granted (ampliar el plan
lo concede) o company_is_managed_child (la empresa no es la titular de su
cuenta, así que ningún plan lo desbloquea; el nombre es heredado y se conserva
porque forma parte del contrato). Así distingues «no contratado» de «no existe».
Crear una regla de cartera sin ese derecho devuelve un 403
automation_portfolio_scope_not_available.
En cartera solo se admiten cinco de los doce tipos de acción.
notify_in_app, notify_channel y emit_webhook avisan a la empresa titular o no tocan
ninguna entidad, y create_task crea la tarea en un proyecto de la propia
empresa titular —link_to_trigger se rechaza en cartera, porque la entidad que
disparó la regla es de otro NIF—. La retirada create_calendar_event
conserva su lugar en este grupo pero no hace nada. Los otros siete
—send_document_email, send_payment_reminder, change_status, tag_entity,
change_task_status, assign_task y add_task_comment— tendrían por sujeto un
documento o una tarea de otro NIF, algo que la frontera multiempresa
no permite. Se rechazan al crear o editar la regla, nunca al ejecutarla, con un
422 automation_portfolio_scope_forbids_action.
Cada acción del catálogo lleva supports_portfolio_scope para que filtres antes
de escribir la regla.
De quién es la ejecución y cuál es su sujeto. Una ejecución de cartera se
guarda bajo la empresa titular de la cuenta: es su regla, sus avisos llegan a
su buzón y sus ejecuciones consumen su presupuesto mensual y su límite de
frecuencia, así que el presupuesto se gasta en proporción a la cartera. Un evento
puede iniciar más de una ejecución cuando su NIF está en la cartera de más de un
titular, por ejemplo un NIF cedido a otra cuenta con nivel operate: cada titular
con una regla que case tiene su propia ejecución, contra su propio presupuesto.
Cada ejecución nombra en subject_company el NIF sobre el que actuó, que es
null en las ejecuciones de alcance empresa. El bloque viaja incluso cuando ese
NIF ya no se puede leer: id y name vuelven a null en vez de desaparecer el
bloque. El propio NIF no ve las ejecuciones de cartera de su titular: son del
titular y llevan la definición congelada de su regla.
Filtra ejecuciones con
GET /v1/companies/{company}/automations/runs?subject_company_id=… y reglas con
GET /v1/companies/{company}/automations/rules?scope=cartera. Filtrar por un NIF
fuera de la cartera devuelve una página vacía, nunca un error, así que el filtro
no sirve para averiguar qué NIF existen.
En modo de prueba nada sale del sandbox
Una clave fact_test_ opera contra una empresa sandbox aislada, y ahí el motor
se comporta igual hasta el último instante: el disparador casa, la ejecución se
admite y se registra, y cada paso queda neutralizado justo antes de producir su
efecto. El paso cierra con sandbox_neutralized y la ejecución termina
completed — un efecto neutralizado es el desenlace buscado, no un fallo. No se
envía ningún correo, no se entrega ningún webhook y ningún documento cambia.
Consulta Modo de prueba y sandbox.
Siguientes pasos
los siete eventos de ciclo de vida que emite el motor, y por qué ellos mismos son disparadores válidos.
la misma superficie como tools para un agente.
el ciclo de vida completo desde la terminal.
todos los códigos que emite el motor, con su causa y qué hacer.
Referencia API.
Referencia API.
Referencia API.