> ## Documentation Index
> Fetch the complete documentation index at: https://botmesh.afaya.com.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks (middleware)

> Los hooks son funciones que se ejecutan antes o después de un evento. Como el middleware de Express: interceptan, modifican, deciden si continuar.

## Los 3 tipos

```mermaid theme={null}
flowchart LR
    A[Evento] --> B[hooks before]
    B --> C[Handler / Flow / Send]
    C --> D[hooks after]
    C -.->|error| E[hooks onError]
```

### before — antes de procesar

```ts theme={null}
bot.before('message', async (ctx, next) => {
  console.log('📥', ctx.type)
  await next()  // ← importante: llama next() para continuar
})
```

> [!WARNING]
>
> **Olvidar next() bloquea el evento**
>
> Un hook `before` que vuelve **sin llamar a `next()`** detiene la cadena y el
> evento, igual que `ctx.abort()`: el mensaje no se enruta (un `'send'` se
> omite, un evento del provider no se emite). Un hook que solo observa o
> reescribe debe terminar siempre con `await next()`. Para bloquear a
> propósito (rate limiting, filtros), vuelve sin `next()` o llama a
> `ctx.abort()` (ver `abort()` más abajo); `abort()` se lee más explícito.
>
> En `'message'`, `next()` ejecuta los hooks `before` restantes; el enrutado (el
> comando, la conversación o el handler) empieza cuando todos los hooks `before`
> han vuelto. Para actuar después del handler, usa un hook `after`.
>
> Llama a `next()` **una sola vez**. Un hook que vuelve a llamarlo conserva el
> primer despacho (el resto de la cadena corre una vez y cada `next()` posterior
> devuelve la misma promesa) y el mal uso se informa en el evento `'error'`.

### after — después de procesar

```ts theme={null}
bot.after('message', async (ctx) => {
  console.log('📤 procesado:', ctx.type)
})
```

### onError — cuando algo falla

```ts theme={null}
bot.onError('message', async (ctx, error) => {
  console.error('💥 error:', error)
  if (ctx) await ctx.reply('😅 Ocurrió un error. Inténtalo de nuevo.')
})
```

También existe un `bot.hook(event, phase, handler, priority?)` genérico si
necesitas registrar una fase dinámicamente en lugar de llamar directamente a
`before`/`after`/`onError`.

El error de un handler se emite **siempre** en `bot.on('error', ({ error, ctx }) => ...)`,
haya o no un hook `onError('message')` registrado.

## Casos de uso

### Logging

```ts theme={null}
const startedAt = new WeakMap<object, number>()

bot.before('message', async (ctx, next) => {
  startedAt.set(ctx, Date.now())
  await next()          // resuelve cuando corrieron los demás hooks before, no el handler
})

bot.after('message', (ctx) => {
  bot.logger.info({ type: ctx.type, ms: Date.now() - (startedAt.get(ctx) ?? Date.now()) }, 'mensaje procesado')
})
```

Los ids de chat son teléfonos o emails y los cuerpos son mensajes privados:
mantenlos fuera de tus logs, o hashéalos como hace el bot con
[`logIds`](https://botmesh.afaya.com.co/es/guide/configuration#logs).

### Rate limiting

```ts theme={null}
const counts = new Map()

bot.before('message', async (ctx, next) => {
  const now = Date.now()
  let entry = counts.get(ctx.from)

  if (!entry || entry.resetAt < now) {
    entry = { count: 0, resetAt: now + 60000 }
    counts.set(ctx.from, entry)
  }

  entry.count++
  if (entry.count > 10) {
    await ctx.reply('⏳ Demasiados mensajes. Espera un minuto.')
    ctx.abort()
    return
  }

  await next()
})
```

### Filtrar grupos

```ts theme={null}
bot.before('message', async (ctx, next) => {
  if (ctx.isGroup) return  // sin next(): el mensaje se detiene aquí (igual que ctx.abort())
  await next()
})
```

Para mensajes de grupo suele ser más simple la opción integrada
[`groupMode`](https://botmesh.afaya.com.co/es/guide/concepts#el-pipeline-de-entrada), que filtra antes incluso de que corran los hooks.

### Bloquear usuarios

```ts theme={null}
const blocked = new Set(['57300XXXXXXX@s.whatsapp.net'])

bot.before('message', async (ctx, next) => {
  if (blocked.has(ctx.from)) {
    ctx.abort()
    return
  }
  await next()
})
```

Para una lista de bloqueo de verdad usa `bot.blacklist` / `bot.whitelist`:
descartan el mensaje antes incluso de persistirlo y también cubren botones y
llamadas. Ver [Handoff y listas de acceso](https://botmesh.afaya.com.co/es/guide/handoff.md#blacklist-y-whitelist).

## Transformar o bloquear mensajes salientes

`'send'` se dispara en **cada** envío saliente, ya sea que venga de
`ctx.reply`/`ctx.photo`/... o de `bot.send`. El payload es
`{ chatId, content, options }` — `content` es el `OutboundContent` neutral
de transporte (`{ kind: 'text', text }`, `{ kind: 'image', media,
caption }`, ...); mútalo para transformar un mensaje, o aborta para
saltarte el envío por completo:

```ts theme={null}
bot.before('send', async (payload, next) => {
  console.log('📤 enviando', payload.content.kind)
  await next()
})
```

Un envío abortado resuelve `{ skipped: true }` en lugar de rechazar (una
[difusión](https://botmesh.afaya.com.co/es/guide/messages.md#difusiones-broadcast) lo cuenta como `skipped`).

### Ediciones y salida en streaming

Algunos payloads llevan una marca para que un hook los distinga de un envío normal:

| Marca | Lo envía | Hooks que corren |
| - | - | - |
| `edit: ref` | [`ctx.edit()`](https://botmesh.afaya.com.co/es/guide/messages#editar-un-mensaje) al editar un mensaje en el sitio | `before` + `after` |
| `stream: 'message'` | un mensaje nuevo que envía [`ctx.stream()`](https://botmesh.afaya.com.co/es/guide/streaming) (el primero de un stream por ediciones, un trozo por frases, el mensaje final tras los borradores) | `before` + `after` |
| `stream: 'edit'` | una actualización en vivo de un mensaje en streaming | solo `before` |
| `stream: 'draft'` | una vista previa nativa como borrador (Telegram) | solo `before` |

Un hook puede reescribir `payload.content` o hacer `abort()` en cualquiera de ellos.
Una edición abortada resuelve `skipped` y no envía un mensaje nuevo en su lugar.
Una edición degradada (el provider no puede editar) sale como un envío normal, sin `edit`.

```ts theme={null}
let sentCount = 0
bot.before('send', async (payload, next) => {
  // Oculta números de tarjeta en todo, vistas previas incluidas
  if (payload.content.kind === 'text') {
    payload.content.text = payload.content.text.replace(/\b\d{13,16}\b/g, '[tarjeta]')
  }
  // Cuenta solo mensajes nuevos: las vistas previas repiten el mismo texto muchas veces
  if (payload.stream !== 'draft' && payload.stream !== 'edit' && !payload.edit) sentCount++
  await next()
})
```

## abort() — detener el pipeline

`abort()` detiene los hooks `before` restantes y el propio evento. Volver sin
`next()` tiene el mismo efecto; `abort()` sirve además desde código que no
tiene `next` a mano (un helper que solo recibe `ctx`).

```ts theme={null}
bot.before('message', async (ctx, next) => {
  if (someCondition) {
    ctx.abort()
    return
  }
  await next()
})
```

## Prioridad

```ts theme={null}
bot.before('message', handler, 10)  // mayor = se ejecuta primero
bot.before('message', handler, 0)   // valor por defecto
```

## Contextos tipados

El contexto que recibe un hook se tipa por el nombre del evento
(`HookContextMap`): `'message'` da un `MessageContext`, `'send'` un
`SendHookContext` (`{ chatId, content, options?, stream?, edit? }`), `'call'`
un `CallEvent`, `'reaction'` un `InboundReaction`, etc.; los hooks `before`
reciben además `abort()`. No hace falta anotar nada:

```ts theme={null}
bot.before('message', async (ctx, next) => { console.log(ctx.body); await next() })
bot.after('send', (payload) => { console.log(payload.content.kind) })
bot.onError('message', (ctx, error) => { /* ctx es un MessageContext, error es unknown */ })
```

Un evento propio (uno para el que un plugin corre sus propios hooks) es
`unknown` salvo que anotes el handler, pases el argumento de tipo o amplíes
`HookContextMap` (declaration merging):

```ts theme={null}
declare module '@botmesh/core' {
  interface HookContextMap { 'crm.lead': { leadId: string } }
}
bot.after('crm.lead', (lead) => { console.log(lead.leadId) })              // tipado por la ampliación
bot.after('other', (ctx: { n: number }) => { console.log(ctx.n) })         // o anota el handler
bot.after<'other', { n: number }>('other', (ctx) => { console.log(ctx.n) }) // o pasa el argumento de tipo
```

`HookHandler<TCtx = unknown>` y `HookErrorHandler<TCtx = unknown>` nombran el
tipo de un handler; un handler tipado para un evento (`HookHandler<MessageContext>`)
no se puede registrar en otro (`'send'`).

## Eventos disponibles

| Evento | Cuándo |
| - | - |
| `message` | El pipeline principal — un mensaje entrante siendo procesado |
| `send` | Cualquier mensaje saliente, desde un helper de respuesta de `ctx` o `bot.send()` (también ediciones y salida en streaming, ver arriba) |
| `reaction`, `poll`, `group`, `presence`, `status` | Eventos del provider (una reacción, un voto de encuesta, un cambio de grupo, presencia, un acuse de entrega/lectura): corren los hooks `before` y `after` y luego el evento `bot.on(...)` correspondiente. Un hook `before` que aborta (o no llama a `next()`) suprime los hooks `after` y el evento |
| `call` | Una llamada entrante. `before('call', (ev) => ev.abort())` la aparta de los [triggers `CALL`](https://botmesh.afaya.com.co/es/guide/triggers#call), de los hooks `after('call')` y del evento `'call'`. Una llamada bloqueada por las listas de acceso o por un handoff nunca llega a los hooks `'call'` |

Los eventos de conexión a nivel de provider (`qr`, `ready`, `connection`,
...) son listeners `bot.on(...)` normales, no fases de hooks — ver
[Conceptos básicos](https://botmesh.afaya.com.co/es/guide/concepts.md). Lo mismo vale para
`'conversation:started'` / `'conversation:ended'`
(ver [Operación](https://botmesh.afaya.com.co/es/guide/operations.md#eventos-de-conversación)).

## Siguientes pasos

* [Plugins](https://botmesh.afaya.com.co/es/guide/plugins.md) — empaquetar hooks para reutilizar
* [Ejemplo: rate limiter](https://botmesh.afaya.com.co/es/examples/rate-limiter.md) — plugin completo


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.