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.
Los dos modos de un campo
Sección titulada «Los dos modos de un campo»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.
| Modo | Cuándo se activa | Qué hace el Designer |
|---|---|---|
| Literal | El campo no empieza con = | Usa el texto exactamente como lo escribiste |
| Expresión | El 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://" + dominio30000 → = timeoutBase * 2¿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 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.
Sintaxis básica
Sección titulada «Sintaxis básica»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.
Referenciar variables
Sección titulada «Referenciar variables»Escribe el nombre de la variable tal cual:
= nombre= total= respuestaTexto (strings)
Sección titulada «Texto (strings)»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"Números y operaciones aritméticas
Sección titulada «Números y operaciones aritméticas»= 42= 3.14 // el separador decimal es el punto= precio * cantidad= (subtotal + envio) * 1.19= total / 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 // 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 fechaOperadores
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). Al escribir = y empezar a teclear, el autocompletado del Designer te las sugiere con su firma.
| 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 | 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 |
= 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)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 decimales= max(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" : correo= isNumber(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) // ayer= now().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 |
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)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 |
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ía= num(fila["Total"]) > 1000= selectRows(tabla, r => str(r["Estado"]) == "Pendiente")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 proyecto= fileName(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 Nexus o del Administrador de credenciales de Windows |
= env("USERNAME")= credential("smtp")["password"] // accede a un campo de la credencialExpresiones 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<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) → 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-dd= regexMatch(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, 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.
Autocompletado y validación de tipos
Sección titulada «Autocompletado y validación de tipos»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.
Identificadores desconocidos
Sección titulada «Identificadores desconocidos»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.
Errores comunes
Sección titulada «Errores comunes»| Síntoma | Causa | Solución |
|---|---|---|
El campo guarda el texto =a+b sin calcular | El = no es el primer carácter, o el campo está en modo literal | Asegúrate de que = esté al inicio del campo |
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 | Los escapes de C# no funcionan en expresiones | Genera el carácter desde una variable/actividad |
| La expresión devuelve nulo sin razón | Nombre de variable mal escrito (identificador desconocido) | Verifica el nombre exacto en el panel de variables |
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 localmente.