Apex Academy
Módulo 8·05·35 min

addError en triggers

En un trigger no lanzas excepciones para rechazar un registro: marcas ese registro con addError. Solo falla él, el usuario ve tu mensaje como el de una regla de validación, y el resto del lote sigue su camino.

En Admin esUna regla de validación que sí ve los demás registros

✓ En esta sub-lección

  1. 01Rechazar un registro en un trigger con addError, a nivel de registro y de campo.
  2. 02Explicar qué pasa con el lote al usar throw o addError, con insert o con Database.insert(list, false).
  3. 03Detectar duplicados dentro del mismo lote, algo que ni una regla de validación ni un flow resuelven bien.

La nutria: ¿Te acuerdas? · Repaso de la lección 4

¿Por qué el bucle de la importación captura solo RenewalImportException y no Exception?

Northwind quiere una norma: una cuenta no puede tener dos renovaciones abiertas a la vez. La primera versión del guardián la escribió alguien del equipo con un throw, y la primera noche que el ERP cargó 200 renovaciones, una sola repetida tumbó las 200. Hoy aprenderás la forma correcta de decir «no» dentro de un trigger.

addError: rechazar un registro, no la transacción

Todo registro de Trigger.new tiene un método addError('mensaje'). Llamarlo no lanza nada: tu código sigue ejecutándose con normalidad, pero ese registro queda marcado como fallido y Salesforce no lo guardará. Si lo pides sobre un campo, o.Amount.addError('…'), el mensaje aparece junto a ese campo en el formulario, igual que una regla de validación con «Error Location: Field».

Apex
for (Opportunity o : newOpps) {
    if (o.Amount == null || o.Amount <= 0) {
        o.Amount.addError('El importe tiene que ser mayor que 0.');   // junto al campo
    } else if (accountsWithRenewal.contains(o.AccountId)) {
        o.addError('Esta cuenta ya tiene una renovación abierta.');   // arriba del registro
    }
}
// El bucle sigue: ningún addError para la ejecución
A diferencia de throw, addError no interrumpe el bucle: puedes marcar varios registros en una sola pasada.

La nutria: Tu regla de validación de siempre… con vista al resto del lote

Para el usuario que guarda desde el formulario, addError es indistinguible de una regla de validación: el mismo cuadro rojo, el mismo mensaje. Para Data Loader también: el registro aparece en el error.csv con tu texto y los demás se guardan. La diferencia está dentro: tu código puede consultar otros registros y ver todo el lote antes de decidir.

throw o addError: qué pasa con el lote

Lo que le pase al resto del lote depende de dos cosas: cómo rechaza el trigger y cómo se hizo el guardado. Una excepción sin capturar dentro de un trigger hace fallar el guardado entero, se haya pedido como se haya pedido. addError, en cambio, marca solo ese registro: con un insert normal (todo o nada, Módulo 4) el guardado lanza una DmlException y no se guarda nada; con Database.insert(list, false), fallan solo los marcados y el resto se guarda. Data Loader y la mayoría de integraciones guardan en modo parcial.

EL TRIGGER RECHAZA CON…

EL ERP GUARDA CON…

  • Acme · Renovación
  • Globex · Renovación
  • Acme · Renovación (2ª)
  • Initech · Renovación

La tercera es una segunda renovación abierta de Acme. Elige las dos cosas y guarda.

Elige cómo rechaza el trigger y cómo se guarda el lote, y mira qué registros sobreviven.

Duplicados dentro del mismo lote

La consulta solo ve lo que ya está guardado. Si el ERP manda en el mismo lote dos renovaciones para la misma cuenta, ninguna de las dos está en la base de datos todavía, así que la consulta dice «no hay ninguna abierta» para ambas. El truco: cada vez que aceptas una renovación, añades su cuenta al Set. La siguiente del mismo lote ya la encontrará.

La nutria: ¿Y por qué no un Flow… ni una regla de validación? Porque no ven el lote

Lo intentamos por las dos vías. Primero, un campo de resumen en la cuenta que contaba las renovaciones abiertas y una regla de validación que miraba ese número. Después, un flow before-save con un Get Records y un elemento Custom Error, que sí existe en los flows desencadenados por registro. Con un usuario guardando a mano, las dos funcionaban. Pero la primera noche el ERP mandó dos renovaciones de Acme en el mismo lote: ni el resumen ni el Get Records ven registros que todavía no se han guardado, cada interview del flow solo conoce su $Record, y las dos pasaron. Probamos a moverlo a un flow after-save, donde ya se ven guardadas… y entonces cada una veía a la otra y se rechazaban las dos. (Simplificación: se puede apañar comparando fechas o Ids, pero ya no es un flow que un Admin mantenga tranquilo.) Con Apex recibes el lote entero en una lista, y el Set decide: la primera se queda, la segunda no.