Apex Academy
Módulo 8·Checkpoint·50 min

Checkpoint del Módulo 8

La entrega: el importador nocturno completo. Una consulta para todas las filas, un try por fila, guardado parcial y un informe para el ERP que sale pase lo que pase.

En Admin esUn Data Loader con su error.csv, escrito por ti

✓ En esta sub-lección

  1. 01Combinar try por fila, excepción propia y guardado parcial en un mismo proceso.
  2. 02Leer los fallos de cada registro con SaveResult: isSuccess() y getErrors().
  3. 03Traducir cada herramienta de errores de Flow a su equivalente en Apex.

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

El guardián marca una renovación duplicada con addError y el ERP guardó con Database.insert(list, false). ¿Qué pasa?

Cinco tareas preparando piezas: un try que no deja caer la importación, los tipos, throw, tu excepción y el guardián con addError. Esta es la tarea en la que se montan. El proceso nocturno llama a RenewalImporter.run con las filas del ERP, y por la mañana el ERP espera dos cosas: las renovaciones buenas creadas y una lista de las malas con su motivo.

Lo que has construido, pieza por pieza

PiezaQué resuelveTarea
try / catch / finallyUna fila mala no tumba las demás, y lo del finally se ejecuta siempre.1
Varios catch por tipoCada fallo tratado según lo que es, del más concreto al más general.2
throw y propagaciónEl método valida y avisa; decide quien lo llama.3
RenewalImportExceptionLos errores del negocio con nombre propio; los bugs no se esconden.4
addErrorEl trigger rechaza una fila sin tumbar el lote.5
Database.insert(list, false) + SaveResultGuardar lo que se pueda y saber qué falló y por qué, registro a registro.6

EN FLOW USABAS… ·aciertos 0/0

El fault path de un elemento

Paso 1 de 7

Elige su pieza en Apex.

Tu diccionario de errores: para cada herramienta de Flow, ¿cuál es su pieza en Apex?

La entrega: el importador completo

run recibe un Map de código ERP → importe en texto. Primero, una sola consulta trae todas las cuentas cuyo ERP_Code__c está entre las claves, y un Map<String, Account> las deja a mano (Módulo 4). Después, cada fila en su propio try: si el código no está en el mapa o el importe no sirve, salta una RenewalImportException, se apunta en el informe y el bucle sigue. Las filas que pasan van a una lista que se guarda de una vez con Database.insert(toInsert, false).

Ese guardado todavía puede rechazar filas que el bucle no podía prever: el guardián de la tarea 5 con su addError, o una cuenta cuyo propietario es un usuario inactivo (INACTIVE_OWNER_OR_USER). Database.insert devuelve una lista de SaveResult en el mismo orden que toInsert: results[i] habla de toInsert[i]. isSuccess() dice si se guardó y getErrors() trae el motivo.

Apex
List<Database.SaveResult> results = Database.insert(toInsert, false);
for (Integer i = 0; i < results.size(); i++) {
    if (!results[i].isSuccess()) {
        Database.Error err = results[i].getErrors()[0];
        report.add(toInsert[i].Name + ' · ' + err.getStatusCode() + ' · ' + err.getMessage());
    }
}
Tu error.csv, fila a fila: el registro, el código de estado y el mensaje, como los que ves en Data Loader.

La nutria: ¿Y por qué no un Flow? Porque el ERP necesita la lista

Recapitulo lo que nos pasó con el flow programado. Con un Create Records dentro del Loop, la noche de 300 filas reventó en «Too many DML statements: 151», y eso no lo captura ningún fault path. Lo pasamos a un Create Records de colección fuera del Loop: todo o nada, una fila mala y no se creaba ninguna. Desde Winter '25 Create Records tiene una opción para guardar parcialmente, y la probamos: se guardaban las buenas… pero el flow no recibe qué registros fallaron ni por qué, y eso era justo lo que el ERP pedía cada mañana. Con Database.insert(list, false), cada SaveResult te dice cuál y por qué.

Lo que viene: el Módulo 9

El importador ya aguanta 300 filas. ¿Y 300.000? No caben en una transacción, por mucho que bulkifiques. Para eso Apex tiene trabajo asíncrono: procesos que se trocean y se ejecutan en segundo plano. Es el Módulo 9.