Factuarea APIDevelopers

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:

PiezaCampoQué decide
Cuándotrigger_typeEl evento que escucha la regla, en forma resource.action (invoice.paid)
SiconditionsUn árbol de condiciones sobre los campos de ese evento. Un {} vacío significa sin condición: la regla actúa siempre
HazactionsLas 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ónQué haceParámetros
create_taskCrea una tareaproject_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_statusMueve la tarea del disparadortarget: el id de una columna del proyecto de la tarea, o planned o archived
assign_taskAsigna la tarea del disparadorassignee_user_id: un miembro de tu empresa
add_task_commentComenta la tarea del disparadorbody: 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 eventos task_comment.* y task_time_entry.*. Con cualquier otro disparador, o si la tarea ya no existe, el paso se descarta con task_target_not_found.
  • Fecha de vencimiento de create_task. Envía due_on (una fecha explícita) o due_from_payload_path con due_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_trigger vincula 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. role elige 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.type es automation); nadie puede editarlos y un admin puede borrarlos.
  • Las referencias se comprueban contra tu empresa —el proyecto, la persona asignada, las etiquetas y un target que 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_task cuyo proyecto se archivó después de guardar la regla se descarta con task_project_not_found y se puede relanzar cuando lo desarchives; una empresa que perdió el módulo tasks recibe module_not_accessible, relanzable cuando el módulo vuelva.
  • Los bucles se cortan. Una regla task.created → create_task llega 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 "" en description lo 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}/versions lista 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ónQué significa
pendingEn cola, sin empezar
runningEn curso
completedTodos los pasos aplicaron su efecto
failedTerminó sin producir su efecto, y no es reprocesable
dead_letteredAparcada, a la espera de un relanzamiento manual
blockedDetenida 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_index es 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ímiteQué acotaMotivo y código de error
Automatizaciones activasReglas activas y no eliminadas de tu empresa; se comprueba al activar o reanudarActivación rechazada · 402 automation_active_rules_limit_exceeded
Profundidad de encadenamientoSaltos «acción → evento → regla», tres por defectochain_depth_exceeded · 422 automation_chain_depth_exceeded
Límite de frecuenciaEjecuciones admitidas por minuto y empresa. Un freno de pico, no de volumenrate_limit_exceeded · 429 automation_rate_limit_exceeded
Presupuesto mensualEjecuciones que permite el plan por periodomonthly_budget_exhausted · 402 automation_monthly_budget_exhausted
Fallos consecutivosUna racha de ejecuciones fallidas pausa la regla solaEvento 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

En esta página

¿Te echamos una mano?Contactar con soporte