> ## 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 almacenamiento key-value por sesión respaldado por Redis. Está disponible automáticamente cuando la petición viene de una conversación activa (tiene `socketId`) y el company 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 temporales** — idioma, formato, filtros durante la sesión

## 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: "Memoria no disponible fuera de una conversación" };
    }

    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 — sin socketId o API key de workflow");
  return { error: "memory_unavailable" };
}
```

<Warning>
  `ctx.memory.available` es `false` cuando falta el `socketId` (peticiones fuera de una conversación) 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)

Todos los valores expiran automáticamente. El TTL se especifica en segundos.

| Método      | TTL           | Máximo       |
| ----------- | ------------- | ------------ |
| `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                 | 86,400 segundos (24h)   |
| Alcance                    | Por sesión (`socketId`) |

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 sesión (socketId)                  | Global                                |
| **Persistencia**  | Temporal (máx 24h)                     | Permanente                            |
| **Configuración** | Cero — viene incluido                  | Requiere connection string en secrets |
| **Ideal para**    | Estado de conversación, cache temporal | Datos de usuario, historial, config   |

## 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}
handler: async (_input, ctx) => {
  if (!ctx.isCron) 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>
