Expresiones
Una expresión es una fórmula que se evalúa en el momento de ejecutar el playbook. Es lo que permite que una actividad use el resultado de otra, construya un texto dinámico, haga un cálculo o tome una decisión.
Si vienes de Excel, la idea es parecida: una celda puede contener el texto Total o una fórmula. En el Designer, un campo puede contener el texto fijo "Hola" o la expresión "Hola " + nombre.
Un campo, una expresión C#
Sección titulada «Un campo, una expresión C#»Los campos de Propiedades que llevan el distintivo ƒx aceptan una expresión con sintaxis de C#. Lo que escribes es exactamente lo que se evalúa, sin prefijos:
| Quieres… | Escribes | Resultado |
|---|---|---|
| Un texto fijo | "Hola mundo" | El texto, tal cual |
| Un número fijo | 30000 | El número |
| El valor de una variable | nombreCliente | Lo que tenga la variable |
| Un texto armado | "Hola " + nombreCliente | El texto con el valor dentro |
| Un cálculo | subtotal * 1.19 | El resultado |
El texto fijo va siempre entre comillas dobles. Sin comillas, Hola se lee como el nombre de una variable y el panel Problemas te avisa de que no existe.
El distintivo ƒx se ve atenuado cuando el campo tiene un texto fijo entre comillas y encendido cuando tiene una expresión, así distingues de un vistazo qué campos dependen de datos de la ejecución.
Algunos campos no son de texto libre:
- Listas de opciones (por ejemplo el nivel de Log): eliges una opción del desplegable. La última, ‹ expresión ›, te deja calcular la opción con una expresión.
- Sí/no: un interruptor.
- Nombres de variable (la salida, el iterador de For Each): escribes solo el nombre, sin comillas.
- Colecciones, condiciones y selectores: tienen su propio editor. Ver Editores de parámetros.
¿Cuándo necesito una expresión?
Sección titulada «¿Cuándo necesito una expresión?»- Reutilizar el resultado de una actividad. Una actividad HTTP GET guarda su respuesta en
respuesta; para leer el código de estado escribesrespuesta.StatusCode. - Construir texto dinámico.
"Factura " + numero + " — " + cliente. - Hacer cálculos.
subtotal * 1.19. - Evaluar una condición (en If, While…).
intentos < 3 && !exito.
Sintaxis básica
Sección titulada «Sintaxis básica»No necesitas saber programar para las fórmulas comunes, pero conviene conocer las reglas que más sorprenden a quien viene de Excel o de otras herramientas.
Referenciar variables
Sección titulada «Referenciar variables»Escribe el nombre de la variable tal cual. Distingue mayúsculas de minúsculas:
nombretotalrespuestaTexto (strings)
Sección titulada «Texto (strings)»El texto va siempre entre comillas dobles "...". Las comillas simples son para un solo carácter (ver padLeft más abajo).
"Hola mundo""El cliente es " + cliente // concatenación con +"Total: " + total + " COP""Dijo \"sí\"" // comillas dentro del texto, con \"Números y operaciones aritméticas
Sección titulada «Números y operaciones aritméticas»423.14 // el separador decimal es el puntoprecio * cantidad(subtotal + envio) * 1.19total / 12Acceso a propiedades y elementos
Sección titulada «Acceso a propiedades y elementos»Usa el punto . para leer una propiedad de un objeto, y los corchetes [...] para acceder por clave o índice:
respuesta.StatusCode // propiedadrespuesta.Headers["Content-Type"] // valor de un diccionario por clavefila["NombreColumna"] // celda de una fila de DataTablelista[0] // primer elemento de una lista (índice 0)fecha.Year // propiedad de una fechaSobre datos dinámicos (variables con diccionarios o listas, parseJson(...), asDict(...), asList(...)), una clave que no existe o un índice fuera de rango dan null, sin error: pedido["descuento"] y asDict(elemento)["descuento"] se comportan igual. Compáralo con == null o usa str(...), que lo convierte en "".
En cambio, un diccionario o una lista con tipo (Dictionary<string, string>, List<int>…) sí falla si la clave no existe, y el error dice cuál y en qué expresión. Si la clave puede faltar, léela con jget(valor, "clave"), que devuelve null.
Operadores
Sección titulada «Operadores»| Tipo | Operadores | Ejemplo |
|---|---|---|
| Aritméticos | + - * / % | precio * 1.19 |
| Comparación | == != < > <= >= | total > 1000 |
| Lógicos | && (y) || (o) ! (no) | activo && !bloqueado |
| Concatenación de texto | + | "Hola " + nombre |
| Condicional (ternario) | condición ? valorSi : valorNo | total > 0 ? "Cobrar" : "Gratis" |
Funciones integradas
Sección titulada «Funciones integradas»El Designer trae un conjunto de funciones listas para usar. Todas se escriben en minúscula (upper, no Upper). El autocompletado te las sugiere con su firma, y el editor de expresiones grande las lista en su catálogo.
| Función | Firma | Descripción |
|---|---|---|
upper(s) | (string) → string | Convierte a mayúsculas |
lower(s) | (string) → string | Convierte a minúsculas |
trim(s) | (string) → string | Quita espacios al inicio y al final |
length(s) | (string) → int | Longitud del texto |
replace(s, viejo, nuevo) | (string, string, string) → string | Reemplaza todas las ocurrencias |
split(s, separador) | (string, string) → List<string> | Divide el texto en una lista |
contains(s, sub) | (string, string) → bool | true si s contiene sub |
startsWith(s, prefijo) | (string, string) → bool | true si empieza con el prefijo |
endsWith(s, sufijo) | (string, string) → bool | true si termina con el sufijo |
substring(s, inicio, largo) | (string, int, int) → string | Extrae una porción desde inicio |
padLeft(s, ancho, car) | (string, int, char) → string | Rellena a la izquierda hasta ancho |
padRight(s, ancho, car) | (string, int, char) → string | Rellena a la derecha hasta ancho |
nl() | () → string | Un salto de línea |
tab() | () → string | Un tabulador |
upper(nombre) // "ana" → "ANA"trim(textoConEspacios)replace(telefono, "-", "") // quita los guionescontains(asunto, "URGENTE")padLeft(toString(numero), 6, '0') // 42 → "000042" (nota: '0' es un char)Números
Sección titulada «Números»| Función | Firma | Descripción |
|---|---|---|
abs(x) | (double) → double | Valor absoluto |
round(x, d) | (double, int) → double | Redondea a d decimales |
floor(x) | (double) → double | Redondea hacia abajo |
ceil(x) | (double) → double | Redondea hacia arriba |
sqrt(x) | (double) → double | Raíz cuadrada |
pow(x, exp) | (double, double) → double | Potencia |
min(a, b) | (double, double) → double | El menor de dos números |
max(a, b) | (double, double) → double | El mayor de dos números |
mod(a, b) | (double, double) → double | Resto de la división (módulo) |
round(total * 1.19, 2) // IVA con 2 decimalesmax(stock, 0) // nunca por debajo de 0Conversión de tipos
Sección titulada «Conversión de tipos»| Función | Firma | Descripción |
|---|---|---|
toString(v) | (any) → string | Convierte cualquier valor a texto |
toNumber(v) | (any) → double | Convierte a número decimal |
toInt(v) | (any) → int | Convierte a entero |
toBoolean(v) | (any) → bool | Convierte a verdadero/falso |
parseDouble(s) | (string) → double | Lee un decimal usando el punto como separador |
parseInt(s) | (string) → int | Lee un entero |
Validación
Sección titulada «Validación»| Función | Firma | Descripción |
|---|---|---|
isNull(v) | (any) → bool | true si el valor es nulo |
isEmpty(s) | (string) → bool | true si es nulo o cadena vacía |
isNumber(v) | (any) → bool | true si se puede interpretar como número |
isEmpty(correo) ? "sin correo" : correoisNumber(celda) && toNumber(celda) > 0Fecha y hora
Sección titulada «Fecha y hora»| Función | Firma | Descripción |
|---|---|---|
now() | () → DateTime | Fecha y hora local actual |
today() | () → DateTime | Hoy a las 00:00:00 (sin hora) |
utcNow() | () → DateTime | Fecha y hora actual en UTC |
Para sumar tiempo o dar formato, usa los métodos del tipo DateTime (ver Tipos de datos):
now()today().AddDays(-1) // ayernow().AddHours(2)now().ToString("yyyy-MM-dd") // "2026-06-06"now().ToString("dd/MM/yyyy HH:mm")fecha.YearColecciones (listas y diccionarios)
Sección titulada «Colecciones (listas y diccionarios)»| Función | Firma | Descripción |
|---|---|---|
count(e) | (IEnumerable) → int | Número de elementos |
join(e, sep) | (IEnumerable, string) → string | Une los elementos en un texto |
first(e) | (IEnumerable) → any | Primer elemento |
last(e) | (IEnumerable) → any | Último elemento |
newList() | () → List | Crea una lista vacía |
newDict() | () → Dictionary | Crea un diccionario vacío |
arr(a, b, …) | (any…) → List | Crea una lista con los valores dados |
listAdd(lista, v) | (List, any) → List | Agrega un elemento y devuelve la lista |
listGet(lista, i) | (List, int) → any | Elemento en la posición i |
listCount(lista) | (List) → int | Número de elementos |
dictSet(d, clave, v) | (Dict, string, any) → Dict | Asigna una clave y devuelve el diccionario |
dictGet(d, clave) | (Dict, string) → any | Valor de una clave |
count(correos) // ¿cuántos correos llegaron?join(nombres, ", ") // ["Ana","Luis"] → "Ana, Luis"first(filas)arr("pendiente", "en revisión")| Función | Firma | Descripción |
|---|---|---|
parseJson(s) | (string) → objeto | Convierte un texto JSON en un objeto que se lee con corchetes |
toJson(v) | (any) → string | Convierte un valor a texto JSON |
toJsonPretty(v) | (any) → string | Igual, con sangría |
jget(o, clave) / jget(o, i) | (any, string|int) → any | Valor de una clave o de una posición |
jpath(o, ruta) | (any, string) → any | Valor en una ruta |
asDict(o) / asList(o) | (any) → Dictionary / List | Trata el valor como diccionario o como lista. Con [...], una clave que falta o un índice fuera de rango dan null |
parseJson(respuestaTexto)["cliente"]["nombre"]toJson(datos)DataTable y filas
Sección titulada «DataTable y filas»Cuando trabajas con tablas (resultado de Excel, CSV, bases de datos o Google Sheets) tienes funciones específicas. Las funciones str, num y eq son importantes porque manejan de forma segura las celdas vacías (DBNull).
| Función | Firma | Descripción |
|---|---|---|
rowCount(dt) | (DataTable) → int | Número de filas |
colCount(dt) | (DataTable) → int | Número de columnas |
firstRow(dt) | (DataTable) → DataRow? | Primera fila, o nulo si está vacía |
asRows(dt) | (DataTable) → List<DataRow> | Convierte la tabla en lista (para usar con LINQ) |
selectRows(dt, pred) | (DataTable, Func) → List<DataRow> | Filtra filas con una condición |
str(v) | (any) → string | Texto seguro: "" si la celda es nula |
num(v) | (any) → double | Número seguro: 0 si la celda es nula o vacía; lee textos con coma o punto decimal (ver abajo) |
num(v, cultura) | (any, string) → double | Lee el texto con los separadores de esa cultura: "es-CO", "en-US", "invariant" |
eq(a, b) | (any, any) → bool | Igualdad segura entre celdas (maneja nulos) |
rowCount(tabla)str(fila["Cliente"]) // texto de una celda, sin errores si está vacíanum(fila["Total"]) > 1000selectRows(tabla, r => str(r["Estado"]) == "Pendiente")Cómo lee num un número escrito como texto
Sección titulada «Cómo lee num un número escrito como texto»Desde la versión 1.2.29. Un número que llega como texto (de un CSV, una página web o una celda de Excel con formato de texto) puede usar coma o punto como decimal. num deduce el separador decimal por su posición, para que el monto sea siempre el correcto:
| Texto | num(...) | Por qué |
|---|---|---|
"1500,5" | 1500.5 | Un solo separador sin tres dígitos detrás: es el decimal |
"1500.5" | 1500.5 | Igual, con punto |
"1.500,5" | 1500.5 | Hay punto y coma: el último es el decimal |
"1,500.5" | 1500.5 | Igual, al revés |
"1.500.000" | 1500000 | El mismo separador repetido solo puede ser de miles |
"1 500,5" | 1500.5 | Los espacios siempre son de miles |
"1.500,50-" | -1500.5 | Signo al final, como en algunos reportes contables |
"1.500" | error | Ambiguo: puede ser mil quinientos o uno coma cinco |
Cuando el texto es ambiguo (un solo separador seguido de exactamente tres dígitos, como "1.500" o "12,345"), num no adivina: la actividad falla con un mensaje que lo explica. Indica la cultura de origen con el segundo parámetro y se usan sus separadores, sin deducir nada:
num(fila["Total"], "es-CO") // "1.500" → 1500 (coma decimal, punto de miles)num(fila["Total"], "en-US") // "1.500" → 1.5 (punto decimal, coma de miles)Con una cultura, un texto que no encaja con sus separadores (por ejemplo "1500.5" con "es-CO", donde el punto es de miles) también es error, nunca otro número. Si la celda ya es un número (como las que lee Leer hoja de Excel), num lo devuelve tal cual, sin importar la cultura.
Archivos y rutas
Sección titulada «Archivos y rutas»| Función | Firma | Descripción |
|---|---|---|
fileExists(ruta) | (string) → bool | true si el archivo existe |
dirExists(ruta) | (string) → bool | true si la carpeta existe |
createDir(ruta) | (string) → string | Crea la carpeta y devuelve su ruta completa |
pathJoin(a, b) | (string, string) → string | Une dos partes de ruta |
fileName(ruta) | (string) → string | Nombre del archivo de una ruta |
dirName(ruta) | (string) → string | Carpeta contenedora de una ruta |
asset(rel) | (string) → string | Ruta absoluta de un archivo dentro de tu proyecto |
pathJoin(carpetaSalida, "reporte.xlsx")asset("plantillas/factura.docx") // ruta a un archivo incluido en el proyectofileName(rutaCompleta) // "C:\x\datos.csv" → "datos.csv"Variables de entorno y credenciales
Sección titulada «Variables de entorno y credenciales»| Función | Firma | Descripción |
|---|---|---|
env(nombre) | (string) → string? | Lee una variable de entorno del sistema |
credential(nombre) | (string) → Credential | Obtiene una credencial de Zoan Cloud o del Administrador de credenciales de Windows |
env("USERNAME")credential("smtp")["password"] // accede a un campo de la credencialenv() devuelve vacío para las variables que empiezan por ZOAN_: así un playbook nunca puede leer los datos internos de la plataforma.
Expresiones regulares (regex)
Sección titulada «Expresiones regulares (regex)»Para extraer o validar datos no estructurados (un número de factura dentro de un texto, un correo, una fecha). Usan el dialecto de .NET.
| Función | Firma | Descripción |
|---|---|---|
regexMatch(texto, patrón) | (string, string) → bool | true si el patrón aparece en el texto |
regexFind(texto, patrón) | (string, string) → string | Primera coincidencia, o "" |
regexFindAll(texto, patrón) | (string, string) → List | Todas las coincidencias |
regexExtract(texto, patrón) | (string, string) → List | Los grupos capturados de la primera coincidencia |
regexGroup(texto, patrón, nombre) | (string, string, string) → string | Un grupo de captura con nombre |
regexReplace(texto, patrón, repl) | (string, string, string) → string | Reemplaza cada coincidencia (admite $1, ${nombre}) |
regexFind(correoTexto, @"\d{4}-\d{2}-\d{2}") // primera fecha aaaa-mm-ddregexMatch(email, @"^[^@]+@[^@]+\.[^@]+$") // ¿es un correo válido?regexGroup(texto, @"(?<factura>FAC-\d+)", "factura")Trabajar con listas y tablas: LINQ
Sección titulada «Trabajar con listas y tablas: LINQ»Para operaciones más potentes sobre listas y tablas (filtrar, ordenar, transformar) puedes usar LINQ, encadenando métodos con punto. Termina con .ToList() cuando necesites una lista concreta.
// Filtrar una listanumeros.Where(n => n > 100).ToList()
// Ordenar y tomar el primeroclientes.OrderBy(c => c.Saldo).First()
// Filtrar filas de una tabla y contarasRows(tabla).Where(r => str(r["Estado"]) == "Pendiente").ToList()count(asRows(tabla).Where(r => num(r["Total"]) > 1000))Los métodos LINQ disponibles (Where, Select, OrderBy, Any, All, First, ToList…) se listan por tipo en Tipos de datos.
Autocompletado, editor grande y validación
Sección titulada «Autocompletado, editor grande y validación»Mientras escribes una expresión, el Designer te ayuda de varias formas:
- Autocompletado. Al empezar a escribir aparece una lista con las variables del playbook, las funciones integradas y las variables del sistema, cada una con su firma. Tras un punto (
variable.) sugiere los miembros válidos para el tipo de esa variable.↑/↓recorren la lista,TaboEnteraceptan,Escla cierra yCtrl+Espaciola abre cuando quieras. - Crear la variable sin salir del campo. Si escribes el nombre de una variable que aún no existe,
Ctrl+Shift+Vla declara en el playbook con el tipo que espera ese campo. - Editor de expresiones. El botón { } del campo abre un editor grande, con un catálogo de Variables, Funciones y variables del Sistema para insertar con un clic. Abajo te confirma Expresión válida y el tipo que devuelve.
- Validación en vivo. El Designer compila cada expresión mientras editas. Si algo no cuadra, el campo se marca y el error aparece en el panel Problemas, con un enlace a la actividad y al campo.
Errores y avisos
Sección titulada «Errores y avisos»- Errores (rojo): fallos de sintaxis, un tipo incompatible o un nombre que no corresponde a ninguna variable declarada. Impiden ejecutar y publicar, porque el playbook fallaría a mitad de camino en el robot.
- Avisos (ámbar): cosas que conviene revisar pero no bloquean, como un selector frágil o un pendiente de una plantilla.
Errores comunes
Sección titulada «Errores comunes»| Síntoma | Causa | Solución |
|---|---|---|
| «El nombre «Hola» no existe en este contexto» | Escribiste un texto fijo sin comillas, o la variable no está declarada | Ponlo entre comillas: "Hola" |
Upper(...) / FormatDate(...) no existe | Estás usando nombres con mayúscula inicial | Las funciones van en minúscula: upper, y para formato de fecha usa fecha.ToString("...") |
| Una comparación de texto siempre da falso | Comparaste celdas de DataTable con == | Usa str(celda) == "valor" o eq(a, b) |
| Un decimal se interpreta mal | Datos con coma decimal | Reemplaza la coma por punto antes de parseDouble |
\n aparece literal en el texto | La barra invertida se toma literalmente | Usa nl() (y tab() para el tabulador) |
| «Un texto entre comillas no está cerrado» | Falta la comilla de cierre, o una barra justo antes de ella la vuelve parte del texto | Cierra el texto; si es una ruta que termina en barra, escríbela @"C:\carpeta\" o usa pathJoin(carpeta, nombre) |
| Tipo incompatible | La expresión devuelve un tipo distinto al que espera el campo | Convierte el valor (toString, toInt…) o revisa el tipo de la variable |
| «La clave «x» no existe en el diccionario» | Un diccionario con tipo no tiene esa clave | Si puede faltar, usa jget(valor, "x"), que devuelve null |
Siguientes pasos
Sección titulada «Siguientes pasos»- Variables: declarar y capturar datos entre actividades.
- Tipos de datos: qué propiedades y métodos tiene cada tipo.
- Depuración: probar tus expresiones ejecutando el playbook.