El email que nadie lee
Hay una escena que se repite en casi todos los proyectos de Orchestrator que he visto.
El Orchestrator funciona. Detecta algo, procesa datos, dispara una notificación. Hasta ahí todo bien.
Pero el email que llega al controller, al gerente o al responsable de operaciones es esto: un bloque de texto plano con valores separados por comas o saltos de línea. Sin estructura. Sin jerarquía visual. Sin manera clara de distinguir qué es importante y qué no.
Nadie lo lee con atención. No está diseñado para que lo lean, está diseñado para que el sistema diga que avisó. Eso no es automatización profesional. Es automatización descuidada.
El problema técnico real
Cuando tu Orchestrator procesa un conjunto de registros —facturas pendientes, líneas de inventario, discrepancias de conciliación— el resultado natural es un array JSON. El problema es que ese JSON volcado directamente en el cuerpo de un email es ilegible para cualquier humano que no sea desarrollador. Lo que necesitas es convertirlo en una tabla HTML antes de enviarlo. Y ahí entra Groovy.
La firma que la mayoría omite
Aquí es donde la mayoría de los tutoriales fallan: muestran un script de Groovy genérico que no compila dentro del Orchestrator porque no respeta la firma que JDE espera.
La firma correcta es HashMap<String, Object> main(OrchestrationAttributes orchAttr, HashMap inputMap). Sin eso, el paso no existe para JDE.
Aquí está el script completo, listo para pegar en un paso Groovy de tu Orchestrator:
import com.oracle.e1.common.OrchestrationAttributes;
import groovy.json.JsonSlurper;
import java.text.SimpleDateFormat;
HashMap<String, Object> main(OrchestrationAttributes orchAttr, HashMap inputMap)
{
HashMap<String, Object> returnMap = new HashMap<String, Object>();
try {
// Recibimos el array JSON desde el paso anterior del Orchestrator
String jsonInput = (String)inputMap.get("inputJsonArray");
orchAttr.writeDebug("JSON recibido: " + jsonInput);
// Parseamos el array JSON
def records = new JsonSlurper().parseText(jsonInput);
if (records == null || records.isEmpty()) {
orchAttr.writeWarn("El array JSON llegó vacío. No se generará tabla HTML.");
returnMap.put("outputHtmlTable", "<p>No se encontraron registros.</p>");
return returnMap;
}
// Construimos la tabla HTML con estilos inline
// (inline obligatorio para compatibilidad con clientes de correo)
def html = new StringBuilder();
html.append("<table border='1' cellpadding='8' cellspacing='0' style='border-collapse:collapse; font-family:Arial, sans-serif; font-size:13px; width:100%;'>");
// Cabeceras dinámicas generadas desde las keys del primer objeto
// Si mañana agregas un campo al JSON, la tabla se actualiza sola
def headers = records[0].keySet();
html.append("<tr style='background-color:#003366; color:#ffffff; text-align:left;'>");
headers.each { h ->
html.append("<th style='padding:8px; border:1px solid #cccccc;'>${h}</th>");
}
html.append("</tr>");
// Filas de datos con color alternado para facilitar la lectura
records.eachWithIndex { row, index ->
String rowColor = (index % 2 == 0) ? "#ffffff" : "#f2f2f2";
html.append("<tr style='background-color:${rowColor};'>");
headers.each { h ->
def cellValue = (row[h] != null) ? row[h].toString() : "";
html.append("<td style='padding:8px; border:1px solid #cccccc;'>${cellValue}</td>");
}
html.append("</tr>");
}
html.append("</table>");
orchAttr.writeDebug("Tabla HTML generada correctamente con " + records.size() + " filas.");
returnMap.put("outputHtmlTable", html.toString());
} catch (Exception e) {
orchAttr.writeWarn("Error al generar la tabla HTML: " + e.getMessage());
returnMap.put("outputHtmlTable", "<p>Error al procesar los datos: " + e.getMessage() + "</p>");
}
return returnMap;
}
Los detalles que lo hacen listo para producción
Input y output nombrados. El JSON entra como inputJsonArray desde el paso anterior y la tabla sale como outputHtmlTable. Esos son exactamente los nombres que mapeas en la configuración del paso dentro del Orchestrator.
Manejo de nulos. Si una celda viene null, el script no explota: devuelve celda vacía. Pequeño detalle que evita fallos en producción con datos reales.
Array vacío controlado. Si el paso anterior no devuelve registros, el email no llega roto. Llega con un mensaje limpio. Eso es lo que distingue un script de ejemplo de uno listo para cliente.
Cabeceras dinámicas. Se generan desde las keys del primer objeto JSON. Agrega un campo nuevo mañana y la tabla se actualiza sola, sin tocar el script.
Filas alternadas. Blanco y gris claro. Cero coste extra de implementación, legibilidad duplicada para quien recibe el informe.
Logs de debug y warn. Los orchAttr.writeDebug y orchAttr.writeWarn son tus ojos dentro del Orchestrator Log cuando algo falla en entorno de cliente. Ponlos siempre.
El impacto en negocio que nadie está midiendo
Sé que suena menor. Es un email. ¿Qué tan importante puede ser el formato?
Más de lo que parece.
Cuando el controller recibe una tabla clara, toma decisiones en segundos. Cuando recibe un bloque de texto que tiene que interpretar, tarda minutos y comete errores de lectura. En un proceso de aprobación que se ejecuta tres veces por semana, estás hablando de entre 30 y 60 minutos semanales de fricción cognitiva que desaparecen.
Y algo más difícil de medir pero igual de real: la percepción de calidad del sistema. Un Orchestrator que envía notificaciones estructuradas no parece una automatización casera. Parece un sistema de producción. Eso afecta directamente a la confianza que el equipo directivo deposita en la herramienta, lo que a su vez determina cuánto presupuesto y cuánto alcance le dan en el futuro.
Lo que esto abre
Este patrón completo —array JSON, Groovy con firma nativa JDE, tabla HTML con estilos inline, manejo de edge cases— es exactamente el tipo de detalle que cubro en el curso avanzado de Orchestrator.
Si quieres construir Orchestrators que no solo funcionen sino que se vean como si los hubiera diseñado alguien que sabe lo que está haciendo, escríbeme.
