Ir al contenido

Tutorial 2: Assets y automatización

En el Tutorial 1 montaste un robot real que resuelve el RPA Challenge leyendo su trabajo de una cola, y lo lanzaste a mano. Aquí lo llevamos al mundo real con dos cosas que todo proceso de producción necesita:

  1. Credenciales fuera del código — leerlas cifradas desde assets.
  2. Disparo automático — que el robot corra solo, por horario (cron) o ante un evento (webhook/API).

El RPA Challenge es público, pero tu proceso real no lo será: tendrá un portal con usuario y contraseña, una URL de ERP, un token de API. La regla de oro:

Dentro de un job gestionado, el robot pide el asset por nombre y NORA se lo entrega descifrado, sin que el robot sepa de claves de API ni de HTTP. El ejemplo ya lo hace para la URL del reto: si existe un asset rpa-challenge-url, lo usa; si no, cae a la URL por defecto.

# rpa_challenge/workflows.py — _resolve_url()
def _resolve_url() -> str:
asset = nora.asset(URL_ASSET) or {} # nora.asset envuelve sdk.get_asset
url = asset.get("value") or RPA_URL # fallback si el asset no existe
if url != RPA_URL:
nora.log("info", f"URL tomada del asset '{URL_ASSET}'.")
return url

nora.asset(name) envuelve la función real del SDK:

from nora_agent import sdk
cred = sdk.get_asset("erp-login") # environment="production" por defecto
usuario = cred.get("username")
secreto = cred["value"] # valor descifrado, solo durante el job

get_asset devuelve un dict con {name, type, environment, value, username?}. No necesitas X-API-Key dentro del job: el agente inyecta un token de ejecución con alcance restringido. Detalle en SDK de robots.

Los assets se crean solo desde la consola: Settings → Assets → Nuevo, con rol admin (la API de escritura POST /api/v1/assets requiere sesión de dashboard admin, no X-API-Key). Por API key los assets solo se leen (scope assets:read, GET /api/v1/assets/by-name/{name}).

Vista de Assets en NORA: la bóveda cifrada de credenciales y configuración por entorno

Para un portal real usarías type: "credential" con username + value. Un asset credential o secret no se puede volver a leer desde la UI (solo actualizar).

En el Proceso, lista los Assets requeridos (p. ej. rpa-challenge-url). Así el agente los precarga y los inyecta en el job; get_asset los sirve sin llamada de red y el token de ejecución solo puede leer esos, no toda la bóveda.

sequenceDiagram
    participant Bot as Robot (job)
    participant Ag as Agente
    participant API as NORA
    Note over Ag: el proceso declara required_assets
    Ag->>API: precarga los assets del job
    API-->>Ag: valores descifrados (scoped)
    Bot->>Ag: sdk.get_asset("rpa-challenge-url")
    Ag-->>Bot: { value: "https://..." }  (sin red)

Hasta ahora lanzabas el job con Run. NORA puede dispararlo solo de dos maneras. Ambas terminan creando un job del proceso (ver jobs).

flowchart LR
    CRON["Programación (cron)<br/>cada día 7:00"] --> J[Job del proceso]
    HOOK["Webhook / API<br/>evento externo"] --> J
    J --> M["Máquina Online<br/>ejecuta el robot"]

Lo más simple es desde el panel: Programaciones → Nueva; eliges proceso, días y hora, y el panel arma el cron por ti.

Vista de Programaciones en NORA: lista de disparos por horario (cron) con su proceso, expresión y próxima ejecución El cron es una expresión de 5 campos (minuto hora día-del-mes mes día-de-la-semana); por ejemplo 0 7 * * 1-5 = de lunes a viernes a las 07:00.

Si prefieres la API, el equivalente es:

POST /api/v1/schedules HTTP/1.1
Host: nora-api.valisoftconsulting.com
Content-Type: application/json
{
"name": "RPA Challenge diario",
"process_id": "3f1c…",
"cron_expression": "0 7 * * 1-5",
"timezone": "America/Lima",
"skip_holidays": true
}

Esto lo corre de lunes a viernes a las 07:00 de Lima. Puntos clave (todos verificados en la guía):

  • Zona horaria IANA (America/Lima, Europe/Madrid, UTC). El disparo se calcula en esa zona y se guarda en UTC.
  • No solapa: si el job anterior sigue corriendo, ese disparo se omite.
  • Sin máquina Online: se omite el ciclo (no hay dónde ejecutar).
  • Feriados: con skip_holidays: true salta los días que definas en Feriados.

Detalle completo y operaciones (toggle, PUT, DELETE) en programaciones y triggers.

Cuando el disparador es un sistema externo (“llegó una factura”, “se creó un cliente”), tienes dos caminos:

B.1 — Trigger de webhook (token por trigger, firma HMAC). Creas un trigger y NORA te da una URL pública con un webhook_token y un webhook_secret. El sistema externo hace POST y NORA crea el job con el payload como input_data:

Vista de Disparadores (triggers/webhooks) en NORA: URLs públicas por proceso con su token y firma HMAC

POST /api/v1/triggers/inbound/AbC123… HTTP/1.1
Host: nora-api.valisoftconsulting.com
Content-Type: application/json
X-Webhook-Signature: sha256=<hmac-sha256(secret, cuerpo_crudo)>
{ "lote": "2026-06-26", "origen": "erp" }

La firma es obligatoria si el trigger tiene secreto; hay anti-replay y límite de 120 req/min. Ver programaciones y triggers → triggers.

B.2 — Webhook con tu API Key (URL fija por proceso). Más simple si el llamador es tu propio backend: una X-API-Key y el process_id en la ruta.

Ventana de terminal
curl -X POST \
"https://nora-api.valisoftconsulting.com/api/v1/webhooks/trigger/5e6f7a8b-…" \
-H "X-API-Key: nora_ak_xxxxxxxxxxxxxxxxxxxx" -H "Content-Type: application/json" \
-d '{ "lote": "2026-06-26" }'

Valida el payload contra el JSON Schema del proceso y está limitado a 60 req/min por key. Comparación webhook vs. /jobs/trigger en webhooks.

Cuando automatizas, recuerda quién llena la cola. Tienes dos patrones:

  • El robot la llena (como en el Tutorial 1): load_queue() carga si está vacía. Ideal cuando los datos los produce el propio robot o un archivo fijo.

  • Un productor externo la llena por API antes (o en vez) de disparar el job, con una key de scope queues:write:

    Ventana de terminal
    curl -X POST \
    "https://nora-api.valisoftconsulting.com/api/v1/queues/by-name/RPA-Challenge/items/bulk" \
    -H "X-API-Key: nora_ak_..." -H "Content-Type: application/json" \
    -d @data/people.json

    Así el webhook que recibe “llegaron 200 facturas” puede cargar la cola y luego disparar el robot, que las consume una a una. Ver colas vía API.

NecesidadSoluciónDónde
Credenciales fuera del códigoAssets (get_asset)assets
Correr cada día/horaProgramación (cron)programaciones
Correr ante un evento externoWebhook/Triggerwebhooks
Llenar la cola desde otro sistemaAPI queues:writecolas vía API