Módulo 3: Loops e Iteraciones

Loops Complejos: Paginación y Patrones Avanzados

Descripción de la cápsula

Los patrones de cápsula 04 cubren lo más común. Pero hay casos donde los loops son genuinamente complejos: paginación de APIs con cursors no obvios, retry con backoff sofisticado, loops que mantienen estado entre iteraciones, recursivos. En esta cápsula vas a ver los patrones avanzados que aparecen en integraciones reales con APIs profesionales.

Estos patterns son menos frecuentes pero esenciales cuando aparecen. Saber identificarlos y resolverlos te diferencia de quien "solo sabe SplitInBatches con batch fijo".


Lo que vas a aprender

Al terminar esta cápsula serás capaz de:

  • Implementar paginación real con cursors, offsets, tokens
  • Manejar APIs con estructuras de paginación distintas
  • Usar Code node para loops que SplitInBatches no cubre bien
  • Mantener estado entre iteraciones
  • Combinar SplitInBatches + Code estratégicamente

Paginación: los 3 estilos

APIs paginan de 3 maneras principales:

Estilo 1: Offset-based (más común antiguo)

GET /api/users?offset=0&limit=100
GET /api/users?offset=100&limit=100
GET /api/users?offset=200&limit=100
...

Pasas offset que indica desde qué item empezar. Sigues hasta que la respuesta tenga menos items que limit (significa última página).

Pros: simple Contras: items nuevos pueden cambiar la paginación entre requests

Estilo 2: Page-number based

GET /api/users?page=1&per_page=100
GET /api/users?page=2&per_page=100
...

Pasas page que es el número de página. Algunos APIs te devuelven total_pages para saber cuándo parar.

Pros: intuitivo Contras: mismos issues que offset

Estilo 3: Cursor-based (más moderno)

GET /api/users?cursor=null              → response: { items: [...], next_cursor: "abc123" }
GET /api/users?cursor=abc123            → response: { items: [...], next_cursor: "def456" }
GET /api/users?cursor=def456            → response: { items: [...], next_cursor: null }

Pasas el cursor que la respuesta anterior te devolvió. Cuando next_cursor === null, terminaste.

Pros: estable ante cambios concurrentes, más rápido en backend Contras: no puedes "saltar a página 5" — debes recorrer


Implementación de paginación con SplitInBatches

Para cualquiera de los 3 estilos, el patrón es similar (Loop until condition de cápsula 04):

Cursor-based con SplitInBatches

[Set: cursor = null]
   │
[SplitInBatches batch=1]
   │ (loop)
   ▼
[HTTP Request: /api/users?cursor={{ cursor }}]
   │
[Set: acumular response.items en array global, actualizar cursor = response.next_cursor]
   │
[IF: cursor === null]
   ├─ TRUE → [Done] → fin
   └─ FALSE → loop back a SplitInBatches

El acumulador

El truco: necesitas acumular los items de cada página en un array global. Opciones:

Opción A: Acumular en Sheet (durable)

Después de cada página, append a un Sheet:

[HTTP page X] → [Sheets: append items]

Al final del loop, lees el Sheet completo. Pros: durable, sobrevive a fallos. Contras: I/O extra.

Opción B: Acumular en variable del workflow

Usar Set + expressions para mantener una variable que crece:

[Set: accumulated_items = accumulated_items + response.items]

Pros: simple. Contras: se pierde si el workflow falla.

Opción C: Code node (más control)

Cuando la lógica es compleja, Code node te da JavaScript completo:

// Code node — loop completo en un solo nodo
let allItems = [];
let cursor = null;

do {
  const response = await fetch(`/api/users?cursor=${cursor || ''}`);
  const data = await response.json();
  allItems = allItems.concat(data.items);
  cursor = data.next_cursor;
} while (cursor !== null);

return [{ json: { all_items: allItems } }];

Pros: lógica en un solo lugar, mucho más rápido. Contras: requiere conocer JS, debugging diferente.


Cuándo Code node es mejor que SplitInBatches

A pesar de la promesa "sin código" de n8n, a veces Code es la opción correcta:

Code es mejor para:

  • Paginación donde la lógica de cursor es compleja
  • Loops con condiciones que dependen de cálculos
  • Procesamiento que mezcla loops y transformaciones
  • Cuando SplitInBatches "no cuadra" naturalmente

SplitInBatches es mejor para:

  • Loops con rate limit (Wait integrado)
  • Cuando cada iteración hace algo distinto (HTTP a APIs diferentes)
  • Cuando necesitas observar cada iteración en Executions (debugging)
  • Cuando la lógica del loop es simple

Regla práctica: si tu loop requiere 5+ nodos diferentes en el procesamiento, SplitInBatches. Si es 1-2 nodos + lógica de cursor, considera Code.


Patrón: paginación + procesamiento del batch en stream

A veces no quieres acumular todos los items antes de procesar — quieres procesar cada página apenas la recibes (stream processing).

Estructura

[Set: cursor = null]
   │
[SplitInBatches batch=1]
   │
[HTTP: get page]
   │
[Procesar items de la página] ← procesamiento real aquí
   │
[IF: cursor null?]
   ├─ TRUE → [Done]
   └─ FALSE → loop back

Ventaja: memoria usage bajo (no acumulas), puede empezar a procesar antes de tener todos.

Cuándo: items grandes, listas inmensas, o el procesamiento incluye guardar a DB (no necesitas acumular).


Patrón: retry con exponential backoff sofisticado

Para retries más sofisticados, Code node suele ser más limpio:

// Code node — retry con backoff
let retries = 0;
const maxRetries = 5;

while (retries < maxRetries) {
  try {
    const response = await fetch(url);
    if (response.ok) return [{ json: await response.json() }];
    if (response.status === 429) {
      const waitSec = parseInt(response.headers.get('Retry-After') || '60');
      await new Promise(r => setTimeout(r, waitSec * 1000));
    } else if (response.status >= 500) {
      const waitSec = Math.min(Math.pow(2, retries), 60);
      await new Promise(r => setTimeout(r, waitSec * 1000));
    } else {
      throw new Error(`Non-retryable error: ${response.status}`);
    }
  } catch (e) {
    if (retries === maxRetries - 1) throw e;
  }
  retries++;
}

Hace retry inteligente: respeta Retry-After, exponential backoff para 5xx, no retry para 4xx no recuperables.


Patrón: dynamic batch size

A veces el batch size óptimo depende del tiempo de día, carga, response del API.

Implementación

[Set: batch_size inicial = 10]
   │
[SplitInBatches]
   │
[procesar batch]
   │
[Set: si último batch tardó <2s, batch_size += 5; si >10s, batch_size -= 5]
   │
   └─→ loop back

Adaptativo: aumenta batch cuando la API responde rápido, lo reduce cuando se ralentiza.

(Requiere SplitInBatches con Reset = ON para que respete el cambio de batch_size — no siempre disponible. Alternativa: Code node con loop manual.)


Patrón: parallel batches (con control)

Por default SplitInBatches procesa batches secuencialmente (el batch 2 espera al 1). A veces quieres N batches en paralelo.

Solución 1: Webhook fan-out

[Master workflow]
   │
[Split items into N chunks]
   │
[N webhook calls a sub-workflows (paralelo)]
   │
[Aggregate when all return]

Cada chunk se procesa en su propio workflow, paralelo.

Solución 2: Workflow con concurrency configurada

En n8n self-hosted, puedes configurar concurrencia. Ver docs de tu versión.

Cuándo: procesamientos muy grandes (10,000+ items) donde la paralelización vale la complejidad.


Trampas comunes

Trampa 1: Loop sin condición de salida real

Qué pasa: Loop con IF cursor !== null pero el cursor de la API nunca llega a null (bug del API o tu interpretación). Loop infinito.

Cómo evitar:

  • Siempre incluir contador de seguridad (max 1000 iteraciones, lanza error)
  • Test con escenarios donde el cursor sí termina

Trampa 2: Acumular en memoria sin límite

Qué pasa: Acumular 100,000 items en una variable. Memoria de n8n se queda corta.

Cómo evitar:

  • Para volumen masivo, stream processing (procesa cada página y descarta)
  • O usar DB como acumulador

Trampa 3: Code node con await fetch que falla

Qué pasa: Code node con await fetch(). Funciona en tu local pero falla en n8n cloud porque fetch global no está disponible siempre.

Cómo evitar:

  • Usar HTTP Request node dentro del workflow (no fetch en Code)
  • Si necesitas fetch en Code, verificar disponibilidad en tu versión de n8n

Trampa 4: Cursor "reset" entre páginas

Qué pasa: Cada llamada al SplitInBatches re-evalúa cursor desde el inicio. Loop infinito procesando la primera página.

Cómo evitar:

  • Asegurar que cursor se persiste en items que vuelven al SplitInBatches
  • Set node que mantiene cursor actualizado en el item

Trampa 5: API que cambia formato entre páginas

Qué pasa: Primera página devuelve { items, next_cursor }. Última página devuelve { items, next_cursor: "" } (string vacío, no null). Tu IF compara con null, sigue infinitamente.

Cómo evitar: Inspeccionar la respuesta del API en cada caso edge. Tratar tanto null como "" como "fin".


Ejercicio: implementar paginación

Objetivo: practicar con un API real (o mock).

Tu tarea

Usa un API de prueba con paginación como JSONPlaceholder (no tiene paginación real) o un mock que controles.

Implementa:

  1. Workflow que hace GET a /api/items?page=N
  2. Si la respuesta tiene 100 items, hay más páginas
  3. Si tiene <100, es la última
  4. Acumula todos los items y al final manda total a Slack

Detalles del API mock:

  • 350 items total
  • 4 páginas (100 + 100 + 100 + 50)

Tu workflow debería hacer 4 requests y reportar 350.


Resumen y siguiente paso

  • 3 estilos de paginación: offset, page-number, cursor — todos manejables con loop until condition
  • Acumulación: Sheet (durable), variable (rápida), Code (control completo)
  • Code node es mejor para lógica de cursor compleja; SplitInBatches para rate limit + observabilidad
  • Stream processing: procesar cada página apenas llega (no acumular todo)
  • Retry sofisticado y batch dinámico suelen mejor en Code
  • Parallel batches requieren arquitectura especial (webhook fan-out)
  • 5 trampas: loop sin condición real, memoria, fetch en Code, cursor reset, formato cambiante

Antes de avanzar deberías poder:

  • Identificar el estilo de paginación de un API
  • Implementar paginación cursor-based con SplitInBatches o Code
  • Decidir entre SplitInBatches y Code según complejidad

Lo que sigue (cápsula 08):

Cierre del módulo: mini-proyecto procesando 1000 items con todos los patrones. Vas a integrar SplitInBatches, Wait, Aggregate, manejo de errores, y reporting final — todo lo que aprendiste en una pieza coherente.


Recursos adicionales

  1. Pagination Patterns Comparison - Comparativa de patrones.
  2. Code Node Documentation - Para loops complejos.

Creado: Mayo 11, 2026 Versión: 1.0