Ir al contenido

Expresiones

Una expresión es una fórmula que el Designer evalúa en el momento de ejecutar el playbook, en lugar de usar un valor fijo. Es lo que te 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 la misma: una celda puede contener el texto Total o la fórmula =A1+A2. En el Designer, un parámetro puede contener el texto Hola o la expresión = "Hola " + nombre.

Cada campo de parámetro en el panel de propiedades funciona en uno de dos modos. Lo que decide el modo es el primer carácter del campo.

ModoCuándo se activaQué hace el Designer
LiteralEl campo no empieza con =Usa el texto exactamente como lo escribiste
ExpresiónEl campo empieza con =Evalúa el contenido como una fórmula y usa el resultado
Modo literal Modo expresión
───────────── ──────────────
Hola mundo → = "Hola mundo"
https://acme.com → = "https://" + dominio
30000 → = timeoutBase * 2
  • Reutilizar el resultado de una actividad. Una actividad HTTP GET guarda su respuesta en una variable 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, Retry…). = intentos < 3 && !exito.

Si el valor nunca cambia (una URL fija, una ruta fija, un número fijo), usa modo literal: es más simple y más rápido de leer.

El motor de expresiones de Zoan usa sintaxis de C#. 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:

= nombre
= total
= respuesta

El texto literal va siempre entre comillas dobles "...". Las comillas simples se reservan para un solo carácter (ver padLeft más abajo).

= "Hola mundo"
= "El cliente es " + cliente // concatenación con +
= "Total: " + total + " COP"
= 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
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 : valorNo= total > 0 ? "Cobrar" : "Gratis"

El Designer trae un conjunto de funciones listas para usar. Todas se escriben en minúscula (upper, no Upper). Al escribir = y empezar a teclear, el autocompletado del Designer te las sugiere con su firma.

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) → ListDivide 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
= 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
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)

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
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")
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 Nexus o del Administrador de credenciales de Windows
= env("USERNAME")
= credential("smtp")["password"] // accede a un campo de la credencial

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) → List<string>Todas las coincidencias
regexExtract(texto, patrón)(string, string) → List<string>Los 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, la sintaxis de consultas de .NET, encadenando métodos con punto. Recuerda terminar 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 dos formas:

  • Autocompletado. Al escribir = y empezar a teclear, aparece una lista con las variables del playbook y las funciones integradas, cada una con su firma. Tras un punto (variable.), sugiere los miembros válidos para el tipo de esa variable.
  • Validación en vivo. El Designer comprueba la sintaxis y el tipo del resultado. Si una expresión devuelve un tipo distinto al que el parámetro espera (por ejemplo, un texto donde se necesita un número), el campo se marca en naranja como advertencia.

Si una expresión referencia un nombre que no corresponde a ninguna variable, en ejecución se evalúa como nulo en lugar de detener el playbook. Esto evita caídas por variables de iteración aún no creadas, pero también significa que un error de tipeo en el nombre de una variable puede pasar silenciosamente. Revisa el panel de variables si una expresión devuelve nulo inesperadamente.

SíntomaCausaSolución
El campo guarda el texto =a+b sin calcularEl = no es el primer carácter, o el campo está en modo literalAsegúrate de que = esté al inicio del campo
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 textoLos escapes de C# no funcionan en expresionesGenera el carácter desde una variable/actividad
La expresión devuelve nulo sin razónNombre de variable mal escrito (identificador desconocido)Verifica el nombre exacto en el panel de variables
  • 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 localmente.