# Batch Operations with Individual Retry

Fuente conceptual: MiroFish `backend/app/utils/retry.py` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Funcion a implementar

### call_batch_with_retry

- **Dominio:** core
- **Lang:** Python
- **Purity:** impure (ejecuta funciones, hace sleep)
- **Signature:**
  ```python
  def call_batch_with_retry(
      items: list[T],
      process_func: Callable[[T], R],
      max_retries: int = 3,
      initial_delay: float = 1.0,
      max_delay: float = 30.0,
      backoff_factor: float = 2.0,
      exceptions: tuple[type[Exception], ...] = (Exception,),
      continue_on_failure: bool = True,
  ) -> tuple[list[R], list[dict]]:
  ```
- **Retorno:** `(results, failures)` donde failures es `[{"index": int, "item": T, "error": str}]`
- **Descripcion:** Procesa una lista de items con retry individual por item. Si un item falla despues de todos los retries, se registra como failure y se continua con el siguiente (o se detiene, segun `continue_on_failure`).
- **Algoritmo:**
  1. Para cada (index, item) en items:
     - Intentar `process_func(item)` con retry (exponential backoff)
     - Si exito: append a results
     - Si falla despues de todos los retries:
       - Append a failures con index, item, error
       - Si `continue_on_failure=False`: re-raise
  2. Retornar (results, failures)
- **Diferencia con retry simple:** El retry simple reintenta una sola llamada. Este maneja **listas** donde cada item se reintenta independientemente, y los fallos individuales no bloquean el resto del batch.
- **Deps:** `time`, `random`
- **Tests:**
  - Todos los items exito
  - 1 item falla permanentemente, rest exito (continue_on_failure=True)
  - 1 item falla, abort (continue_on_failure=False)
  - Item que falla 2 veces y luego exito (retry funciona)
  - failures contiene index correcto
