> ## Documentation Index
> Fetch the complete documentation index at: https://docs.jelou.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Memoria de sesión

> Guarda y lee estado por conversación con ctx.memory: flujos multi-paso, carritos, contadores y datos temporales sin base de datos externa.

## ¿Qué es `ctx.memory`?

`ctx.memory` es un cliente HTTP al **Memory API** de Jelou. Habla con el mismo almacenamiento key-value del usuario (`$memory` en el builder). Lo que escribas desde una Function es visible en placeholders `{{$memory.key}}` del workflow y viceversa — es una sola memoria por usuario, no una copia paralela.

Disponible automáticamente cuando la petición viene de una conversación activa y la empresa tiene una API key de workflow configurada.

Casos de uso típicos:

* **Flujos multi-paso** — recordar en qué paso está el usuario
* **Carritos de compras** — acumular productos durante la conversación
* **Contadores** — limitar intentos de login, tracking de reintentos
* **Preferencias del usuario** — idioma, formato, filtros que persisten entre conversaciones

## Inicio rápido

```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";

export default define({
  name: "flujo-registro",
  description: "Registro multi-paso con memoria de sesión",
  input: z.object({
    respuesta: z.string().optional(),
  }),
  handler: async (input, ctx) => {
    if (!ctx.memory.available) {
      return { error: "memory_unavailable" };
    }

    const paso = await ctx.memory.get("paso", "inicio");

    if (paso === "inicio") {
      await ctx.memory.set("paso", "nombre", 3600);
      return { pregunta: "¿Cuál es tu nombre?" };
    }

    if (paso === "nombre") {
      await ctx.memory.set("nombre", input.respuesta || "", 3600);
      await ctx.memory.set("paso", "email", 3600);
      return { pregunta: "¿Cuál es tu email?" };
    }

    const nombre = await ctx.memory.get("nombre", "");
    await ctx.memory.delete("paso");
    await ctx.memory.delete("nombre");
    return { completado: true, nombre, email: input.respuesta };
  },
});
```

## Verificar disponibilidad

```typescript theme={null}
if (!ctx.memory.available) {
  ctx.log("Memory no disponible — fuera de una conversación o sin API key de workflow");
  return { error: "memory_unavailable" };
}
```

<Warning>
  `ctx.memory.available` es `false` cuando la petición no viene de una conversación activa o la API key del workflow no está configurada. Llamar a métodos en un cliente no disponible lanza un `Error`.
</Warning>

## Primitivos vs JSON

Usa `set()`/`get()` para valores simples y `setJson()`/`getJson()` para objetos:

```typescript theme={null}
await ctx.memory.set("paso", "confirmacion", 3600);
const paso = await ctx.memory.get("paso", "inicio");

await ctx.memory.setJson("carrito", { items: [], total: 0 }, 86400);
const carrito = await ctx.memory.getJson("carrito", { items: [], total: 0 });
```

El tipo de retorno de `get()` coincide con el tipo del valor por defecto:

```typescript theme={null}
const nombre = await ctx.memory.get("nombre", "anónimo");     // string
const intentos = await ctx.memory.get("intentos", 0);           // number
const verificado = await ctx.memory.get("verificado", false);   // boolean
```

## TTL (tiempo de vida)

Hay **dos capas de expiración** en Memory:

1. **TTL por variable** (el que pasas al `set`) — controla cuándo caduca esa variable individual.
2. **hashTTL de 30 días** (gestionado por la plataforma) — controla cuándo caduca la memoria completa del usuario. Cuando expira, se borra todo su Memory.

<Warning>
  **Renovación del hashTTL desde `ctx.memory`**: las escrituras vía `ctx.memory` solo **inicializan** el hashTTL cuando la memoria del usuario está vacía; escrituras posteriores **no lo extienden**. En cambio, las escrituras vía `$memory` en el builder sí extienden el hashTTL en cada write. Si tu flujo persiste solo desde Functions y necesitas actividad continua, combina con al menos una escritura desde el builder.
</Warning>

El TTL por variable se especifica en segundos:

| Método      | TTL por variable | Máximo por variable |
| ----------- | ---------------- | ------------------- |
| `set()`     | Opcional         | 86,400 (24h)        |
| `setJson()` | **Requerido**    | 86,400 (24h)        |

```typescript theme={null}
await ctx.memory.set("paso", "pago");                 // sin TTL explícito
await ctx.memory.set("paso", "pago", 1800);           // expira en 30 min
await ctx.memory.setJson("carrito", carrito, 86400);   // expira en 24h (máximo)
```

## Límites

| Restricción                              | Valor                                                                                                                      |
| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| Longitud máxima de `set()`               | 255 caracteres                                                                                                             |
| TTL máximo por variable                  | 86,400 segundos (24h)                                                                                                      |
| Duración total de la memoria del usuario | 30 días desde la primera escritura; solo las escrituras vía `$memory` del builder renuevan la ventana (ver Warning arriba) |
| Alcance                                  | Por usuario — misma memoria que ve `$memory` del builder                                                                   |

Valores de `set()` que excedan 255 caracteres lanzan un `Error`. Para datos más grandes, usa `setJson()`.

## Patrones comunes

<Tabs>
  <Tab title="Flujo multi-paso">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      const paso = await ctx.memory.get("paso", "inicio");

      if (paso === "inicio") {
        await ctx.memory.set("paso", "datos", 3600);
        return { siguiente: "datos" };
      }

      if (paso === "datos") {
        await ctx.memory.set("nombre", input.nombre, 3600);
        await ctx.memory.set("paso", "confirmar", 3600);
        return { siguiente: "confirmar", nombre: input.nombre };
      }

      const nombre = await ctx.memory.get("nombre", "");
      await ctx.memory.delete("paso");
      await ctx.memory.delete("nombre");
      return { completado: true, nombre };
    }
    ```
  </Tab>

  <Tab title="Carrito de compras">
    ```typescript theme={null}
    interface Carrito {
      items: Array<{ id: string; nombre: string; precio: number; cantidad: number }>;
      total: number;
    }

    handler: async (input, ctx) => {
      const vacio: Carrito = { items: [], total: 0 };
      const carrito = await ctx.memory.getJson<Carrito>("carrito", vacio);

      carrito.items.push({
        id: input.productoId,
        nombre: input.nombre,
        precio: input.precio,
        cantidad: input.cantidad,
      });
      carrito.total = carrito.items.reduce(
        (sum, i) => sum + i.precio * i.cantidad, 0
      );

      await ctx.memory.setJson("carrito", carrito, 86400);
      return { carrito };
    }
    ```
  </Tab>

  <Tab title="Límite de intentos">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      const intentos = await ctx.memory.get("intentos_pin", 0);

      if (intentos >= 3) {
        return { bloqueado: true, mensaje: "Demasiados intentos" };
      }

      const valido = input.pin === "1234";
      if (!valido) {
        await ctx.memory.set("intentos_pin", intentos + 1, 1800);
        return { bloqueado: false, error: "PIN incorrecto" };
      }

      await ctx.memory.delete("intentos_pin");
      return { bloqueado: false, verificado: true };
    }
    ```
  </Tab>
</Tabs>

## Manejo de errores

```typescript theme={null}
import { MemoryApiError } from "@jelou/functions";

try {
  await ctx.memory.set("paso", "pago", 3600);
} catch (err) {
  if (err instanceof MemoryApiError) {
    ctx.log("Memory API falló", { status: err.status, code: err.code });
    if (err.isRateLimit()) {
      return { error: "rate_limit", retryAfter: 2 };
    }
  }
  throw err;
}
```

## ¿Cuándo usar `ctx.memory` vs una base de datos?

|                   | `ctx.memory`                                                                               | Base de datos externa                          |
| ----------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------- |
| **Alcance**       | Por usuario                                                                                | Global                                         |
| **Persistencia**  | Hasta 30 días de inactividad · TTL opcional en `set()`, requerido en `setJson()` (máx 24h) | Permanente                                     |
| **Configuración** | Cero — viene incluido                                                                      | Requiere connection string en secrets          |
| **Ideal para**    | Estado de conversación, preferencias del usuario                                           | Datos históricos, catálogos, config compartida |

## Acceder a memoria de otro usuario

Desde triggers **cron** o **event**, puedes acceder a la memoria de un usuario específico con `ctx.memory.for(userId)`:

```typescript theme={null}
import { define } from "@jelou/functions";

export default define({
  name: "recordatorio-usuario",
  handler: async (_input, ctx) => {
    if (!ctx.isCron && !ctx.isEvent) return { skipped: true };

    // Acceder a la memoria del usuario 12345
    const userMemory = ctx.memory.for("12345");
    const paso = await userMemory.get("paso", "desconocido");

    if (paso === "pendiente") {
      await userMemory.set("paso", "recordado", 3600);
    }

    return { checked: true, paso };
  },
});
```

<Warning>
  `ctx.memory.for()` solo funciona desde triggers cron o event. En requests HTTP normales, `ctx.memory` ya está vinculado a la sesión del usuario actual — llamar `.for()` lanza un error.
</Warning>
