Ir al contenido

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.

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…EscribesResultado
Un texto fijo"Hola mundo"El texto, tal cual
Un número fijo30000El número
El valor de una variablenombreClienteLo que tenga la variable
Un texto armado"Hola " + nombreClienteEl texto con el valor dentro
Un cálculosubtotal * 1.19El 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.
  • Reutilizar el resultado de una actividad. Una actividad HTTP GET guarda su respuesta en respuesta; para leer el código de estado escribes respuesta.StatusCode.
  • Construir texto dinámico. "Factura " + numero + " — " + cliente.
  • Hacer cálculos. subtotal * 1.19.
  • Evaluar una condición (en If, While…). intentos < 3 && !exito.

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.

Escribe el nombre de la variable tal cual. Distingue mayúsculas de minúsculas:

nombre
total
respuesta

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 \"
42
3.14 // el separador decimal es el punto
precio * cantidad
(subtotal + envio) * 1.19
total / 12

Usa el punto . para leer una propiedad de un objeto, y los corchetes [...] para acceder por clave o índice:

respuesta.StatusCode // propiedad
respuesta.Headers["Content-Type"] // valor de un diccionario por clave
fila["NombreColumna"] // celda de una fila de DataTable
lista[0] // primer elemento de una lista (índice 0)
fecha.Year // propiedad de una fecha

Sobre 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.

TipoOperadoresEjemplo
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 : valorNototal > 0 ? "Cobrar" : "Gratis"

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ónFirmaDescripción
upper(s)(string) → stringConvierte a mayúsculas
lower(s)(string) → stringConvierte a minúsculas
trim(s)(string) → stringQuita espacios al inicio y al final
length(s)(string) → intLongitud del texto
replace(s, viejo, nuevo)(string, string, string) → stringReemplaza todas las ocurrencias
split(s, separador)(string, string) → List<string>Divide el texto en una lista
contains(s, sub)(string, string) → booltrue si s contiene sub
startsWith(s, prefijo)(string, string) → booltrue si empieza con el prefijo
endsWith(s, sufijo)(string, string) → booltrue si termina con el sufijo
substring(s, inicio, largo)(string, int, int) → stringExtrae una porción desde inicio
padLeft(s, ancho, car)(string, int, char) → stringRellena a la izquierda hasta ancho
padRight(s, ancho, car)(string, int, char) → stringRellena a la derecha hasta ancho
nl()() → stringUn salto de línea
tab()() → stringUn tabulador
upper(nombre) // "ana" → "ANA"
trim(textoConEspacios)
replace(telefono, "-", "") // quita los guiones
contains(asunto, "URGENTE")
padLeft(toString(numero), 6, '0') // 42 → "000042" (nota: '0' es un char)
FunciónFirmaDescripción
abs(x)(double) → doubleValor absoluto
round(x, d)(double, int) → doubleRedondea a d decimales
floor(x)(double) → doubleRedondea hacia abajo
ceil(x)(double) → doubleRedondea hacia arriba
sqrt(x)(double) → doubleRaíz cuadrada
pow(x, exp)(double, double) → doublePotencia
min(a, b)(double, double) → doubleEl menor de dos números
max(a, b)(double, double) → doubleEl mayor de dos números
mod(a, b)(double, double) → doubleResto de la división (módulo)
round(total * 1.19, 2) // IVA con 2 decimales
max(stock, 0) // nunca por debajo de 0
FunciónFirmaDescripción
toString(v)(any) → stringConvierte cualquier valor a texto
toNumber(v)(any) → doubleConvierte a número decimal
toInt(v)(any) → intConvierte a entero
toBoolean(v)(any) → boolConvierte a verdadero/falso
parseDouble(s)(string) → doubleLee un decimal usando el punto como separador
parseInt(s)(string) → intLee un entero
FunciónFirmaDescripción
isNull(v)(any) → booltrue si el valor es nulo
isEmpty(s)(string) → booltrue si es nulo o cadena vacía
isNumber(v)(any) → booltrue si se puede interpretar como número
isEmpty(correo) ? "sin correo" : correo
isNumber(celda) && toNumber(celda) > 0
FunciónFirmaDescripción
now()() → DateTimeFecha y hora local actual
today()() → DateTimeHoy a las 00:00:00 (sin hora)
utcNow()() → DateTimeFecha 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) // ayer
now().AddHours(2)
now().ToString("yyyy-MM-dd") // "2026-06-06"
now().ToString("dd/MM/yyyy HH:mm")
fecha.Year
FunciónFirmaDescripción
count(e)(IEnumerable) → intNúmero de elementos
join(e, sep)(IEnumerable, string) → stringUne los elementos en un texto
first(e)(IEnumerable) → anyPrimer elemento
last(e)(IEnumerable) → anyÚltimo elemento
newList()() → ListCrea una lista vacía
newDict()() → DictionaryCrea un diccionario vacío
arr(a, b, …)(any…) → ListCrea una lista con los valores dados
listAdd(lista, v)(List, any) → ListAgrega un elemento y devuelve la lista
listGet(lista, i)(List, int) → anyElemento en la posición i
listCount(lista)(List) → intNúmero de elementos
dictSet(d, clave, v)(Dict, string, any) → DictAsigna una clave y devuelve el diccionario
dictGet(d, clave)(Dict, string) → anyValor de una clave
count(correos) // ¿cuántos correos llegaron?
join(nombres, ", ") // ["Ana","Luis"] → "Ana, Luis"
first(filas)
arr("pendiente", "en revisión")
FunciónFirmaDescripción
parseJson(s)(string) → objetoConvierte un texto JSON en un objeto que se lee con corchetes
toJson(v)(any) → stringConvierte un valor a texto JSON
toJsonPretty(v)(any) → stringIgual, con sangría
jget(o, clave) / jget(o, i)(any, string|int) → anyValor de una clave o de una posición
jpath(o, ruta)(any, string) → anyValor en una ruta
asDict(o) / asList(o)(any) → Dictionary / ListTrata 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)

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ónFirmaDescripción
rowCount(dt)(DataTable) → intNúmero de filas
colCount(dt)(DataTable) → intNú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) → stringTexto seguro: "" si la celda es nula
num(v)(any) → doubleNú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) → doubleLee el texto con los separadores de esa cultura: "es-CO", "en-US", "invariant"
eq(a, b)(any, any) → boolIgualdad segura entre celdas (maneja nulos)
rowCount(tabla)
str(fila["Cliente"]) // texto de una celda, sin errores si está vacía
num(fila["Total"]) > 1000
selectRows(tabla, r => str(r["Estado"]) == "Pendiente")

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:

Textonum(...)Por qué
"1500,5"1500.5Un solo separador sin tres dígitos detrás: es el decimal
"1500.5"1500.5Igual, con punto
"1.500,5"1500.5Hay punto y coma: el último es el decimal
"1,500.5"1500.5Igual, al revés
"1.500.000"1500000El mismo separador repetido solo puede ser de miles
"1 500,5"1500.5Los espacios siempre son de miles
"1.500,50-"-1500.5Signo al final, como en algunos reportes contables
"1.500"errorAmbiguo: 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.

FunciónFirmaDescripción
fileExists(ruta)(string) → booltrue si el archivo existe
dirExists(ruta)(string) → booltrue si la carpeta existe
createDir(ruta)(string) → stringCrea la carpeta y devuelve su ruta completa
pathJoin(a, b)(string, string) → stringUne dos partes de ruta
fileName(ruta)(string) → stringNombre del archivo de una ruta
dirName(ruta)(string) → stringCarpeta contenedora de una ruta
asset(rel)(string) → stringRuta absoluta de un archivo dentro de tu proyecto
pathJoin(carpetaSalida, "reporte.xlsx")
asset("plantillas/factura.docx") // ruta a un archivo incluido en el proyecto
fileName(rutaCompleta) // "C:\x\datos.csv" → "datos.csv"
FunciónFirmaDescripción
env(nombre)(string) → string?Lee una variable de entorno del sistema
credential(nombre)(string) → CredentialObtiene una credencial de Zoan Cloud o del Administrador de credenciales de Windows
env("USERNAME")
credential("smtp")["password"] // accede a un campo de la credencial

env() devuelve vacío para las variables que empiezan por ZOAN_: así un playbook nunca puede leer los datos internos de la plataforma.

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ónFirmaDescripción
regexMatch(texto, patrón)(string, string) → booltrue si el patrón aparece en el texto
regexFind(texto, patrón)(string, string) → stringPrimera coincidencia, o ""
regexFindAll(texto, patrón)(string, string) → ListTodas las coincidencias
regexExtract(texto, patrón)(string, string) → ListLos grupos capturados de la primera coincidencia
regexGroup(texto, patrón, nombre)(string, string, string) → stringUn grupo de captura con nombre
regexReplace(texto, patrón, repl)(string, string, string) → stringReemplaza cada coincidencia (admite $1, ${nombre})
regexFind(correoTexto, @"\d{4}-\d{2}-\d{2}") // primera fecha aaaa-mm-dd
regexMatch(email, @"^[^@]+@[^@]+\.[^@]+$") // ¿es un correo válido?
regexGroup(texto, @"(?<factura>FAC-\d+)", "factura")

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 lista
numeros.Where(n => n > 100).ToList()
// Ordenar y tomar el primero
clientes.OrderBy(c => c.Saldo).First()
// Filtrar filas de una tabla y contar
asRows(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.

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, Tab o Enter aceptan, Esc la cierra y Ctrl+Espacio la abre cuando quieras.
  • Crear la variable sin salir del campo. Si escribes el nombre de una variable que aún no existe, Ctrl+Shift+V la 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 (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.
SíntomaCausaSolución
«El nombre «Hola» no existe en este contexto»Escribiste un texto fijo sin comillas, o la variable no está declaradaPonlo entre comillas: "Hola"
Upper(...) / FormatDate(...) no existeEstás usando nombres con mayúscula inicialLas funciones van en minúscula: upper, y para formato de fecha usa fecha.ToString("...")
Una comparación de texto siempre da falsoComparaste celdas de DataTable con ==Usa str(celda) == "valor" o eq(a, b)
Un decimal se interpreta malDatos con coma decimalReemplaza la coma por punto antes de parseDouble
\n aparece literal en el textoLa barra invertida se toma literalmenteUsa 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 textoCierra el texto; si es una ruta que termina en barra, escríbela @"C:\carpeta\" o usa pathJoin(carpeta, nombre)
Tipo incompatibleLa expresión devuelve un tipo distinto al que espera el campoConvierte 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 claveSi puede faltar, usa jget(valor, "x"), que devuelve null
  • 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.