---
title: "Varios agentes en el mismo número de WhatsApp: cómo hacerlo por la API"
description: "Un número, una conversación por cliente y un equipo entero atendiendo — sin que dos personas respondan lo mismo. Cómo distribuir, bloquear la conversación por agente, transferir y pasar del bot al humano sin que el cliente repita todo."
url: "https://wame.api.br/blog/varios-agentes-mismo-numero-whatsapp"
language: "pt-BR"
og:type: "article"
published_time: "2026-09-10"
modified_time: "2026-09-10"
author: "Raphael Serafim"
keywords: "varios agentes mismo número whatsapp, whatsapp multiagente, whatsapp api varios usuarios, distribuir atención whatsapp, transferir conversación whatsapp, bot a humano whatsapp"
reading_time: "9 min de lectura"
---

# Varios agentes en el mismo número de WhatsApp: cómo hacerlo por la API

> Un número, una conversación por cliente y un equipo entero atendiendo — sin que dos personas respondan lo mismo. Cómo distribuir, bloquear la conversación por agente, transferir y pasar del bot al humano sin que el cliente repita todo.

**La aplicación de WhatsApp no fue hecha para equipos.** Tiene límite de dispositivos conectados, no reparte conversaciones, no registra quién respondió qué y no impide que dos personas contesten lo mismo.

Por la API el problema cambia de naturaleza: los mensajes llegan a tu sistema, y **tú** decides quién atiende.

## El diseño

```
Cliente  →  WhatsApp  →  webhook  →  tu sistema
                                        ↓
                                   distribución
                                        ↓
                             agente 1, 2, 3…
                                        ↓
                            respuesta  →  API  →  Cliente
```

Para el cliente existe una sola conversación, con un solo número. Toda la estructura del equipo es invisible — y así debe ser.

## Las tablas mínimas

```sql
CREATE TABLE conversaciones (
  id             BIGSERIAL PRIMARY KEY,
  numero         VARCHAR(20) UNIQUE NOT NULL,
  agente_id      BIGINT,                        -- NULL = en la cola
  estado         VARCHAR(20) DEFAULT 'abierta', -- abierta|pendiente|cerrada
  cola           VARCHAR(30) DEFAULT 'general',
  asignada_en    TIMESTAMPTZ,
  ultima_en      TIMESTAMPTZ
);

CREATE TABLE mensajes (
  id                BIGSERIAL PRIMARY KEY,
  conversacion_id   BIGINT NOT NULL,
  direccion         VARCHAR(10) NOT NULL,    -- entrada|salida
  agente_id         BIGINT,                  -- quién respondió
  texto             TEXT,
  message_id        VARCHAR(80) UNIQUE,      -- idempotencia
  creado_en         TIMESTAMPTZ DEFAULT NOW()
);
```

El `numero UNIQUE` garantiza una conversación por cliente. El `message_id UNIQUE` evita mensajes duplicados cuando el webhook se reentrega.

## Recibir y distribuir

```js
async function alRecibir(msg) {
  const conv = await obtenerOCrearConversacion(msg.from);

  await db.mensajes.crear({
    conversacion_id: conv.id,
    direccion: 'entrada',
    texto: msg.text?.body,
    message_id: msg.id,          // el duplicado falla acá, y está bien
  });

  // Una conversación ya asignada sigue con quien la está atendiendo.
  // Cambiar de agente a la mitad es una pésima experiencia.
  if (conv.agente_id) {
    return notificarAgente(conv.agente_id, conv.id);
  }

  const agente = await elegirAgente(conv.cola);
  if (!agente) {
    await db.conversaciones.actualizar(conv.id, { estado: 'pendiente' });
    return avisarFueraDeHorario(msg.from);
  }

  await asignar(conv.id, agente.id);
}
```

## El bloqueo que evita la respuesta duplicada

Esta es la parte que da problemas en producción si se hace de forma ingenua:

```js
// MAL: dos agentes pasan por el "if" al mismo tiempo
if (!conv.agente_id) {
  await db.conversaciones.actualizar(conv.id, { agente_id: miId });
}

// BIEN: la condición va dentro del propio UPDATE, y lo resuelve la base
async function asignar(conversacionId, agenteId) {
  const r = await db.query(
    `UPDATE conversaciones
        SET agente_id = $1, asignada_en = NOW(), estado = 'abierta'
      WHERE id = $2 AND agente_id IS NULL
      RETURNING id`,
    [agenteId, conversacionId],
  );
  return r.rowCount > 0;   // false = otro agente la tomó primero
}
```

El `WHERE agente_id IS NULL` dentro del `UPDATE` es lo que hace la operación atómica. Sin eso, dos agentes abren la misma conversación y el cliente recibe dos respuestas — el reclamo número uno de quien arma multiagente a las apuradas.

## Repartir con criterio

```js
async function elegirAgente(cola) {
  const disponibles = await db.query(
    `SELECT a.id, COUNT(c.id) AS abiertas
       FROM agentes a
       LEFT JOIN conversaciones c
              ON c.agente_id = a.id AND c.estado = 'abierta'
      WHERE a.online = true AND $1 = ANY(a.colas)
      GROUP BY a.id
     HAVING COUNT(c.id) < a.limite_simultaneo
      ORDER BY abiertas ASC, a.ultima_asignacion ASC NULLS FIRST
      LIMIT 1`,
    [cola],
  );
  return disponibles.rows[0] ?? null;
}
```

Dos cosas importan más que el algoritmo:

**`limite_simultaneo`** — un agente con 15 conversaciones abiertas no atiende bien ninguna. Un techo por persona protege la calidad.

**Desempate por `ultima_asignacion`** — sin él, quien cierra rápido recibe todo y quien se demora queda libre.

## Transferir sin que el cliente repita

```js
async function transferir(conversacionId, deId, paraId, motivo) {
  await db.conversaciones.actualizar(conversacionId, {
    agente_id: paraId,
    asignada_en: new Date(),
  });

  // Lo que evita la peor frase de la atención: "¿me lo cuentas otra vez?"
  const resumen = await db.mensajes.ultimos(conversacionId, 20);
  await notificarAgente(paraId, { conversacionId, resumen, motivo });

  await enviar(conv.numero,
    'Te paso con quien lleva este tema, ya con todo lo que hablamos acá. 👍');
}
```

Avisarle al cliente **y** entregarle el historial al nuevo agente. Si falta cualquiera de las dos, la transferencia se vuelve la experiencia que todos detestan.

## Del bot al humano

El diseño más eficiente combina ambos: el bot hace el triaje y resuelve lo repetitivo; la persona entra cuando hace falta.

```js
async function alRecibir(msg) {
  const conv = await obtenerOCrearConversacion(msg.from);

  // ¿Ya está con un humano? El bot no vuelve a intervenir. Punto.
  if (conv.agente_id) return derivarAlAgente(conv, msg);

  const r = await bot.responder(conv.id, msg.text.body);

  if (r.quiereHumano) {
    const agente = await elegirAgente(r.cola ?? 'general');
    if (agente) {
      await asignar(conv.id, agente.id);
      await notificarAgente(agente.id, {
        conversacionId: conv.id,
        resumen: r.resumenDeLaConversacion,   // el bot entrega el contexto
      });
      return enviar(msg.from, 'Ya te comunico con alguien del equipo. 👋');
    }
    await db.conversaciones.actualizar(conv.id, { estado: 'pendiente' });
    return enviar(msg.from, 'Están todos ocupados ahora. Apenas se libere alguien te escribo.');
  }

  await enviar(msg.from, r.texto);
}
```

**La primera línea es la regla más importante:** una vez que la conversación pasó a un humano, el bot no vuelve a responder. Un bot que interrumpe una atención humana es peor que no tener bot.

Cómo armar el bot está en [chatbot de IA con la API de OpenAI](/blog/chatbot-ia-openai-whatsapp) — y el `[HUMANO]` de ese artículo es exactamente el `quiereHumano` de acá.

## Horario y cola de espera

```js
async function avisarFueraDeHorario(numero) {
  if (dentroDelHorario()) {
    return enviar(numero, '¡Recibimos tu mensaje! Ya te respondo. 😊');
  }
  return enviar(numero,
    '¡Recibimos tu mensaje! Atendemos de lunes a viernes, de 8 a 18 h. ' +
    'Te respondo apenas abramos.');
}
```

Una expectativa clara vale más que una respuesta rápida. Un cliente que sabe cuándo lo van a atender no manda cinco mensajes más preguntando si hay alguien.

## Métricas que dicen algo

| Número | Qué revela |
|---|---|
| Tiempo hasta la primera respuesta | Lo que siente el cliente |
| Tiempo hasta resolver | La eficiencia real |
| Conversaciones por agente | Si el reparto es justo |
| Tasa de transferencia | Si el triaje está fallando |
| Pendientes en el pico | Si falta gente |

Una tasa de transferencia alta casi siempre significa colas mal diseñadas, no agentes malos.

## Conclusión

El multiagente no es una función de WhatsApp: es tu sistema decidiendo quién responde. Las piezas son pocas — una conversación por número, asignación atómica, transferencia con historial y un bot que se retira cuando entra el humano.

La que más retrabajo ahorra es el bloqueo atómico. Es una línea de SQL, y sin ella el cliente recibe dos respuestas distintas de la misma empresa.

## Perguntas frequentes

### ¿Cómo pongo varios agentes en el mismo número de WhatsApp?

Por la API. Los mensajes llegan a un webhook y tu sistema decide quién atiende, repartiendo las conversaciones entre los agentes conectados. Todos responden por el mismo número y, para el cliente, existe una sola conversación — algo que la app común no permite.

### ¿Cuál es el límite de agentes por número?

Por la API no aplica el límite de dispositivos de la aplicación, porque quien conversa con WhatsApp es tu sistema y no los aparatos. El límite práctico pasa a ser el volumen de mensajes que soporta el número y la capacidad de tu equipo.

### ¿Cómo evito que dos agentes respondan la misma conversación?

Con un bloqueo de asignación en la base de datos. La conversación se asigna a un agente en una operación atómica, y la interfaz solo permite responder a quien la tiene. Sin eso, dos personas abren la misma conversación a la vez y el cliente recibe dos respuestas distintas.

### ¿Cómo transfiero una conversación entre agentes?

Cambiando la asignación y preservando el historial. Lo importante es pasar también un resumen de lo ya tratado, para que el cliente no tenga que repetir. Una transferencia que obliga a contar todo de nuevo es el reclamo más común de la atención en cualquier canal.

### ¿Puede el bot atender primero y pasar al humano?

Sí, y es el diseño más eficiente. El bot hace el triaje y resuelve lo repetitivo; cuando detecta que hace falta una persona, marca la conversación para atención humana y le entrega el historial al agente. El error es que el bot insista después de que la persona ya pidió un humano.
