Apex Academy
Módulo 7·01·30 min

Un trigger por objeto, y por qué sin lógica

Dos triggers sobre el mismo objeto y evento se ejecutan en el orden que a la plataforma le parezca. La regla profesional es uno por objeto, y el primer paso para cumplirla es Trigger.operationType.

En Admin esVarios flows del mismo objeto sin ordenar en Flow Trigger Explorer

✓ En esta sub-lección

  1. 01Explicar por qué dos triggers del mismo objeto y evento son un error aunque «funcionen».
  2. 02Unir varios triggers en uno solo que decide el evento con switch on Trigger.operationType.
  3. 03Escribir el orden de las reglas a propósito, en lugar de dejárselo a la plataforma.
  4. 04Reconocer por qué la lógica dentro del trigger es el siguiente problema a resolver.

La nutria: ¿Te acuerdas? · Repaso del Módulo 6

Si dos triggers sobre Case ponen Priority a valores distintos, ¿cuál gana?

El trigger CaseEscalation del Módulo 6 funcionó: los casos de cuentas Hot suben a 'High' y su propietario recibe una tarea. Un mes después, Soporte pidió otra regla (lo que entra por la web empieza en 'Low') y un compañero, con prisa, creó un segundo trigger sobre Case. Nadie lo revisó, porque «funcionaba». Esta sub-lección va de por qué ese «funcionaba» es una bomba de relojería, y de cómo desactivarla.

El encargo de este módulo

Como en los módulos anteriores, los cinco talleres son un solo encargo. Esta vez no es una funcionalidad nueva, sino algo que vas a hacer muchísimas veces en tu vida profesional: heredar código ajeno y ponerlo en orden sin romper nada. Partimos del trigger del Módulo 6 y del que añadió tu compañero, y al final del módulo habrás construido la arquitectura con la que trabajan las orgs profesionales… y la usarás para entregar una regla nueva de Soporte.

La nutria: El encargo, tal cual lo cuenta el jefe de Soporte

Vengo de hablar con el jefe de Soporte, y esto es lo que me contó, tal cual: «Los casos de Hot a veces salen en 'Low' y nadie sabe por qué. Queremos que eso no vuelva a pasar, que las reglas de casos estén en un solo sitio, que el botón de recalcular prioridad use la misma regla, que no se dupliquen las tareas, que la migración de casos antiguos no dispare nada… y, cuando esté todo en orden, una regla nueva: cuando se cierra un caso urgente, el comercial de la cuenta tiene que enterarse». Cinco tareas; la de hoy es la primera frase.

La nutria: ¿Y por qué no un Flow? Este módulo no compite con Flow

Aquí la decisión ya está tomada: el escalado vive en Apex desde el Módulo 6. Este módulo ordena ese código, y copia ideas que ya conoces de Flow: Flow Trigger Explorer para el orden (tarea 1), los subflows para separar reglas (tareas 2 y 3) y $Permission para el interruptor (tarea 4). Lo único que Flow no tiene es la guardia de la tarea 4, porque necesita memoria static durante toda la transacción.

Dos triggers, dos resultados

En el Módulo 6 viste la frase de pasada: Salesforce no garantiza en qué orden se ejecutan dos triggers del mismo objeto y el mismo evento. Aquí se nota por qué importa. CaseWebDefaults pone 'Low' a lo que viene de la web; CaseEscalation pone 'High' a lo que viene de una cuenta Hot. Un caso que cumple las dos cosas termina con la prioridad del que se ejecute el último. Pulsa guardar varias veces en el diagrama.

Llega un caso por la web de una cuenta Hot. Un trigger lo pone en 'Low' porque viene de la web; el otro en 'High' porque la cuenta es Hot. Gana el que se ejecute el último.

trigger CaseWebDefaults on Case (before insert) { … c.Priority = 'Low'; }
trigger CaseEscalation on Case (before insert) { … c.Priority = 'High'; }
  1. Pulsa «Guardar el caso» varias veces.
Guarda el mismo caso varias veces con dos triggers; luego activa «un solo trigger» y repite.

La nutria: Tú ya tienes esta herramienta… para los flows

Yo ordenaba mis flows con Flow Trigger Explorer y el campo Trigger Order: si dos flows del mismo objeto chocan, les pones número y se acabó. Los triggers de Apex no tienen nada parecido. La única forma de decidir el orden es que no haya dos: un solo trigger por objeto, y dentro, las reglas en el orden que tú escribas.

Trigger.operationType: el evento en una sola variable

Un solo trigger por objeto significa que ese trigger escucha todos los eventos que necesite. En el Módulo 6 los distinguías con Booleans (Trigger.isBefore, Trigger.isInsert…) combinados en if. Hay algo más limpio: Trigger.operationType, que guarda el evento completo (momento y operación) en un único valor. Y como es un valor exacto, encaja con el switch del Módulo 2.

Trigger.operationType vale…Equivale a
BEFORE_INSERTTrigger.isBefore && Trigger.isInsert
BEFORE_UPDATETrigger.isBefore && Trigger.isUpdate
BEFORE_DELETETrigger.isBefore && Trigger.isDelete
AFTER_INSERTTrigger.isAfter && Trigger.isInsert
AFTER_UPDATETrigger.isAfter && Trigger.isUpdate
AFTER_DELETETrigger.isAfter && Trigger.isDelete
AFTER_UNDELETETrigger.isUndelete (solo existe en after)
Apex
trigger OpportunityTrigger on Opportunity (before insert, before update, after update) {
    switch on Trigger.operationType {
        when BEFORE_INSERT, BEFORE_UPDATE {
            // lo mismo para crear y para editar: una sola rama
        }
        when AFTER_UPDATE {
            // solo al editar, y después de guardar
        }
    }
}
Los valores del enum se escriben sin comillas y sin prefijo dentro del when. Y un when admite varios separados por comas, como en el Módulo 2.

Y ahora el problema de verdad: la lógica dentro

Unir los dos triggers arregla el orden, pero el archivo empieza a mezclar reglas que no tienen nada que ver: con diez, son 400 líneas que nadie se atreve a tocar. Y esa lógica solo se ejecuta guardando un caso; un botón con la misma regla obligaría a copiarla. Por eso la norma completa es «un trigger por objeto, y sin lógica». Hoy cumples la primera mitad; la segunda es la tarea 2.

La nutria: El flow gigante que nadie quiere abrir

Yo he heredado alguno: un Record-Triggered Flow con cuarenta elementos, decisiones anidadas y un lienzo que no cabe en la pantalla. Funciona, pero cada cambio da miedo. Un trigger con toda la lógica dentro es exactamente eso, en código. Y la solución también se parece: en Flow sacabas trozos a subflows; en Apex, a clases.