Apex Academy
Módulo 8·04·35 min

Excepciones personalizadas

IllegalArgumentException dice «argumento no válido», pero no dice «problema de la importación». Crear tu propia excepción te deja separar los errores del negocio de los fallos técnicos, y capturar solo los tuyos.

En Admin esUn valor de picklist propio en lugar de «Otro»

✓ En esta sub-lección

  1. 01Declarar una excepción propia que extienda Exception.
  2. 02Envolver una excepción técnica en una de negocio sin perder la original (getCause).
  3. 03Capturar solo los errores del negocio y dejar que los fallos inesperados se vean.

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

parseAmount lanza una IllegalArgumentException y el bucle que la llama no tiene try. ¿Qué pasa?

Tu bucle de importación tiene ya dos catch, y en la tarea 6 serán más. Pero todos dicen lo mismo: «esta fila no se puede importar». Lo que el bucle necesita no es distinguir TypeException de IllegalArgumentException; necesita distinguir «fila rechazada por el negocio» de «algo se ha roto de verdad». Para eso creas tu propio tipo de excepción.

Una línea basta

Una es una clase que extiende Exception, igual que en el Módulo 5 TaskReminder extendía Notification. Solo hay una regla: su nombre tiene que terminar en Exception. No hace falta escribir nada dentro: al heredar de Exception, ya trae getMessage(), getCause(), getTypeName() y cuatro constructores listos para usar.

Apex
public class RenewalImportException extends Exception {}

// Los cuatro constructores que trae de serie:
new RenewalImportException();                              // sin mensaje
new RenewalImportException('Cuenta desconocida: ERP-999'); // con mensaje
new RenewalImportException(e);                             // envolviendo otra excepción
new RenewalImportException('Importe no numérico', e);      // mensaje + la original
Si la llamaras RenewalImportError, no compilaría: las clases que extienden Exception tienen que terminar en Exception.

La nutria: Un valor de picklist propio en lugar de «Otro»

¿Te acuerdas de aquella picklist Motivo de pérdida con un «Otro» en el que acababa todo? Nadie podía hacer un informe útil. Lo arreglamos creando los valores del negocio: Precio, Competencia, Sin presupuesto. IllegalArgumentException es ese «Otro»; RenewalImportException es tu valor con nombre: cualquiera que lo vea en un log sabe que viene de la importación de renovaciones.

Envolver sin perder la original

El 'doce mil' sigue lanzando una TypeException dentro de parseAmount, porque la lanza Decimal.valueOf, no tú. Para que al bucle le llegue una RenewalImportException, la capturas justo ahí y lanzas la tuya pasándole la original como segundo argumento. Así el bucle solo ve errores del negocio, y quien depure después puede recuperar el fallo técnico con getCause().

Apex
Decimal amount;
try {
    amount = Decimal.valueOf(raw);
} catch (TypeException e) {
    throw new RenewalImportException('Importe no numérico: ' + raw, e);
}

// Más arriba, en el catch del bucle:
// e.getMessage()                  → 'Importe no numérico: doce mil'
// e.getCause().getTypeName()      → 'System.TypeException'
Esto sí es un catch con sentido: no se traga el error, lo traduce al idioma del negocio y conserva la original dentro.

LANZA UN FALLO

Elige un fallo y mira qué catch lo recoge.

Lanza cada fallo y mira qué catch lo recoge: el tuyo o ninguno.

La nutria: ¿Y por qué no un Flow? Porque el fault path no tiene categorías

En la versión con clics quisimos lo mismo: separar «el ERP mandó un dato malo» de «algo se rompió». Pero en Flow no puedes crear un tipo de error propio: todo fallo llega al fault path como el mismo $Flow.FaultMessage. Acabamos con una variable de texto errorType que cada rama rellenaba a mano y una Decision que la comparaba; al tercer subflow, alguien escribió «Negocio» en vez de «negocio» y los errores técnicos empezaron a colarse como filas rechazadas. En Apex, el tipo es el compilador quien lo comprueba: un nombre mal escrito no llega ni a guardarse.