Procesos y paquetes
En NORA, lo que ejecuta un robot se modela en tres niveles. Entenderlos evita confusiones al subir código y al lanzar jobs.

| Concepto | Qué es | Cambia con |
|---|---|---|
Paquete (Package) | El robot como producto: un nombre y una descripción. Agrupa todas sus versiones. | Casi nunca (es la identidad del robot). |
| Release (versión) | El código del robot empaquetado en un ZIP, con su versión, su entry point y el hash del archivo. | Cada vez que cambias el código (subes una versión nueva). |
Proceso (Process) | Lo que se ejecuta: apunta a un Release y añade la configuración de ejecución (parámetros, timeout, reintentos, assets requeridos, carpeta…). | Cuando ajustas configuración o promueves/haces rollback a otra versión. |
flowchart TD
P[Paquete: facturacion] --> R1[Release 1.0.0]
P --> R2[Release 1.0.1]
P --> R3[Release 1.1.0]
PR[Proceso: Facturacion mensual] -->|release_id activo| R3
PR -.->|rollback| R2
Un Paquete tiene muchos Releases (la versión es única por paquete). Un Proceso fija un Release como activo mediante release_id; cambiarlo permite promover una versión nueva o volver a una anterior sin tocar el robot.
Estructura de un robot
Sección titulada «Estructura de un robot»Un robot es una carpeta con, como mínimo, un entry point (por defecto main.py) y, normalmente, un requirements.txt. Ejemplo real del directorio examples/rpa-challenge/:
rpa-challenge/├── main.py # entry point (orquesta el flujo)├── requirements.txt # dependencias (playwright>=1.40)├── rpa_challenge/ # módulos del robot├── data/└── nora.json # manifiesto (lo crea/actualiza `nora package`)El entry point usa el SDK nora_agent para registrar logs, leer assets, consumir colas y reportar progreso. Si falta requirements.txt, el agente no instalará dependencias (la CLI te avisa).
Empaquetar con la CLI
Sección titulada «Empaquetar con la CLI»La CLI nora (paquete nora_agent) construye el ZIP de forma segura. Desde la carpeta del robot:
# Vista previa: lista qué se incluiría, sin escribir nadanora package --list
# Construye el ZIP en dist/<nombre>-<version>.zipnora packagenora package hace tres cosas:
- Resuelve la versión desde el manifiesto
nora.json. El primer paquete usa1.0.0; los siguientes auto-incrementan (--bump patch|minor|major|none, por defectopatch). Puedes fijarla con--version 2.0.0. - Escribe
nora.jsonconname,versionyentry_point(se incluye en el ZIP y sirve de fuente de la versión para la próxima vez). - Escanea en busca de secretos (claves privadas,
nora_ak_…, tokens, claves AWS). Si encuentra algo, aborta antes de escribir el ZIP; usa--allow-secretssolo si es un falso positivo.
Opciones útiles: --entry workflow.py (otro entry point), --exclude '*.csv' (excluir más archivos, repetible), --gitignore (honrar también .gitignore).
Publicar una versión (Release)
Sección titulada «Publicar una versión (Release)»Con la sesión iniciada (nora login), sube el ZIP como Release. release push es solo para administradores y crea el paquete si no existe:
nora loginnora packagenora release push # sube dist/<nombre>-<version>.zipOtros subcomandos: nora release list, nora release delete <version> y nora release download <version>.

Bajo el capó: la API de Releases
Sección titulada «Bajo el capó: la API de Releases»La CLI llama a estos endpoints (base de producción https://nora.valisoftconsulting.com, prefijo /api/v1, autenticación con sesión Bearer). La subida es multipart/form-data:
POST /api/v1/releasesContent-Type: multipart/form-data
package_id=<uuid>&version=1.0.1&entry_point=main.pyfile=@rpa-challenge-1.0.1.zipEl backend valida que el ZIP sea válido, sin rutas con .. ni absolutas, con tope de 50 MB subidos y 500 MB descomprimidos, y calcula file_hash (SHA-256). La respuesta va envuelta en {"success": true, "data": …}:
{ "success": true, "data": { "id": "…", "package_id": "…", "version": "1.0.1", "file_hash": "…", "file_size": 20480, "entry_point": "main.py", "is_active": true, "uploaded_by": "…" }}La descarga (GET /api/v1/releases/{release_id}/download) devuelve el ZIP y expone la cabecera X-Release-Hash para verificar integridad.
Crear el Proceso
Sección titulada «Crear el Proceso»Un Release es solo código; para ejecutarlo, crea un Proceso que lo apunte. POST /api/v1/processes (rol admin) recibe, entre otros campos:
name,description,release_id(obligatorio),folder_id.input_schema: esquema de los parámetros de entrada del job.timeout_seconds(0 = sin límite),max_retries,auto_retry,retry_delay_seconds.sla_deadline_minutes,on_success_trigger_process_id(encadenar procesos),tags.required_assets: lista de assets que el robot puede leer en tiempo de ejecución. Si no está vacía, el token por job se limita exactamente a esos nombres (un robot comprometido no puede leer el resto de la bóveda del tenant).
Para cambiar la versión activa (promover o hacer rollback) sin recrear el proceso, usa PATCH /api/v1/processes/{process_id}/active-release con {"release_id": "…"}. Activar/desactivar un proceso: PATCH /api/v1/processes/{process_id}/toggle.
Listar procesos desde la API pública
Sección titulada «Listar procesos desde la API pública»Para integraciones externas, NORA expone un endpoint con API key (cabecera X-API-Key: nora_ak_…), que requiere el scope processes:read y está limitado a 60 peticiones/minuto:
curl https://nora-api.valisoftconsulting.com/api/v1/processes/list \ -H "X-API-Key: nora_ak_tu_clave_aqui"Devuelve solo procesos activos, paginado (page, limit, folder_id opcionales) y con la envoltura habitual:
{ "success": true, "data": [ { "id": "…", "name": "Facturacion mensual", "release_id": "…", "is_active": true } ], "meta": { "page": 1, "limit": 20, "total": 1, "pages": 1 }}Con el id del proceso ya puedes dispararlo desde la API. Consulta jobs y autenticación.