> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# Bot de llamadas de Graph

## Descripción general

Este enfoque convierte al agente en una **identidad de Teams a la que se puede llamar**. Un usuario lo busca por nombre y lo llama de forma individual, y el agente responde en tiempo real, sin número de teléfono, PSTN ni créditos de comunicaciones. Es el único enfoque al que se puede llamar por nombre y el más complejo de ejecutar.

Usa un **bot de medios en tiempo real de Microsoft Graph** (la plataforma de llamadas de Cloud Communications). El SDK de medios (`Microsoft.Skype.Bots.Media`) funciona **solo con .NET en Windows Server**; no hay una opción para Linux ni fuera de .NET para el audio sin procesar en llamadas de Teams.

> **Note**
>
> Este es el único enfoque al que se puede llamar **por nombre** dentro de Teams. Te recomendamos la [pestaña de widget](/docs/es/eleven-agents/phone-numbers/microsoft-teams/widget-tab) para una configuración más sencilla, o
> [ACS](/docs/es/eleven-agents/phone-numbers/microsoft-teams/azure-communication-services) si
> específicamente quieres un número de teléfono.

## Cómo funciona

![Un usuario de Teams llama al bot por su nombre; Teams enruta la llamada al bot de medios en una VM de Windows, que conecta audio PCM 16k sin procesar con el agente de ElevenLabs a través de un WebSocket](/docs/_fern-files/elevenlabs.docs.buildwithfern.com/f411ec0b44d4c5ca1a61598fbd1e6162e46a1c74179eebb4b673ee1428823e84/assets/images/conversational-ai/teams-media-bot-architecture.svg)

El bot responde con **medios alojados por la aplicación**, recibe 50 tramas de audio por segundo (PCM de 16 kHz y 20 ms), las conecta con el agente de ElevenLabs mediante un WebSocket y transmite el audio del agente de vuelta a la llamada.

## Requisitos

1. Un registro y una aplicación de **Azure Bot** (registro de aplicación de Entra).
2. **Permisos de aplicación de Graph** con consentimiento de administrador: `Calls.AccessMedia.All` (medios sin procesar) y `Calls.Initiate.All`.
3. Una **VM de Windows Server** (≥ **2 núcleos físicos**; por ejemplo, `Standard_D4s_v3`) con una IP pública y puertos de medios abiertos.
4. Un **certificado TLS firmado por una CA** en un FQDN público para el endpoint de medios/señalización (la plataforma de medios rechaza los certificados autofirmados).
5. Un [agente de ElevenLabs](/docs/es/eleven-agents/quickstart) configurado en **PCM 16000 Hz** en ambos lados: formato de salida de TTS en la pestaña **Voz** y formato de audio de entrada de usuario en la pestaña **Avanzado**.

> **Warning**
>
> Un `D2s_v3` (2 vCPU = **1 núcleo físico**) falla con `MediaPlatform needs a system with at least 2
>   cores`. Usa un tamaño con ≥ 2 núcleos físicos (por ejemplo, `D4s_v3`).

## Permisos y roles

| Ámbito                       | Rol / permiso                                                                                                                                         | Motivo                                                                                                                                               |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entra                        | **Administrador de aplicaciones**                                                                                                                     | crear el registro de aplicación + Azure Bot                                                                                                          |
| Entra                        | **Administrador global / Administrador de roles con privilegios**                                                                                     | conceder el consentimiento de administrador para los permisos de llamadas de Graph; los permisos de aplicación no pueden autorizarse automáticamente |
| Microsoft Graph (aplicación) | `Calls.AccessMedia.All`, `Calls.Initiate.All`                                                                                                         | responder llamadas individuales y acceder a medios sin procesar                                                                                      |
| Azure RBAC                   | **Colaborador** en el grupo de recursos                                                                                                               | crear la VM de Windows + Azure Bot                                                                                                                   |
| Administrador de Teams       | permitir la [**carga de aplicaciones personalizadas**](https://admin.teams.microsoft.com/policies/manage-apps); activar el canal **Llamadas** del bot | cargar la aplicación de forma local y recibir llamadas                                                                                               |

## Paso 1: registrar el bot y los permisos de Graph

Crea un registro de aplicación y un Azure Bot asociado a él; después, concede y autoriza los permisos de llamadas (necesitas ser administrador global o administrador de roles con privilegios para dar el consentimiento):

```bash
APPID=$(az ad app create --display-name "ElevenLabs Teams Agent" \
  --sign-in-audience AzureADMyOrg --query appId -o tsv)
az ad sp create --id "$APPID"
# create a client secret and record it
az ad app credential reset --id "$APPID" --display-name bot --query password -o tsv

# Azure Bot bound to the app
az bot create --resource-group $RG --name el-teams-agent-bot \
  --app-type SingleTenant --appid "$APPID" --tenant-id $TENANT \
  --endpoint "https://YOUR_FQDN/api/messages" --sku S1
```

Concede los dos roles de aplicación de Graph y el **consentimiento de administrador** (requiere administrador global o administrador de roles con privilegios), y confirma después que se hayan asignado:

```bash
# Graph app roles: Calls.AccessMedia.All, Calls.Initiate.All
az ad app permission add --id "$APPID" --api 00000003-0000-0000-c000-000000000000 \
  --api-permissions a7a681dc-756e-4909-b988-f160edc6655f=Role \
    284383ee-7f6e-4e40-a2a8-e85dcb029101=Role
az ad app permission admin-consent --id "$APPID"

# Verify — should print both role ids
az rest --method GET \
  --url "https://graph.microsoft.com/v1.0/servicePrincipals(appId='$APPID')/appRoleAssignments" \
  --query "value[].appRoleId" -o tsv
```

> **Note**
>
> Si `admin-consent` devuelve `Consent validation failed`, concede los roles de aplicación directamente en la entidad de servicio:
>
> ```bash
> GRAPH_SP=$(az ad sp show --id 00000003-0000-0000-c000-000000000000 --query id -o tsv)
> BOT_SP=$(az ad sp show --id "$APPID" --query id -o tsv)
> for ROLE in a7a681dc-756e-4909-b988-f160edc6655f 284383ee-7f6e-4e40-a2a8-e85dcb029101; do
>   az rest --method POST \
>     --url "https://graph.microsoft.com/v1.0/servicePrincipals/$GRAPH_SP/appRoleAssignedTo" \
>     --body "{\"principalId\": \"$BOT_SP\", \"resourceId\": \"$GRAPH_SP\", \"appRoleId\": \"$ROLE\"}"
> done
> ```

En el portal, verifica en el [centro de administración de Entra](https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/RegisteredAppsListBlade), en **Registros de aplicaciones → tu aplicación → Permisos de API**, que ambos permisos muestren **Concedido** con marcas de verificación verdes.

![La sección de permisos de API del registro de aplicación muestra Calls.AccessMedia.All y Calls.Initiate.All
concedidos](/docs/_fern-img/4abc1e220d3fc03845594aa4f835656425cc1c39448dae30dace98e672fd8fb1.webp)

## Paso 2: aprovisionar la VM de Windows, el certificado y los puertos

```bash
az vm create -g $RG -n teams-media-bot --image Win2022Datacenter \
  --size Standard_D4s_v3 --admin-username azureuser --admin-password '<strong-pw>' \
  --public-ip-sku Standard --public-ip-address-dns-name elevenmediabot
az vm open-port -g $RG -n teams-media-bot --port 80,443,8445,9441 --priority 300
```

En la VM (el código nativo de la plataforma de medios los necesita; Windows Server no los incluye de forma predeterminada):

```powershell
# VC++ runtime + Media Foundation feature (required by NativeMedia.dll)
choco install -y vcredist140
Install-WindowsFeature Server-Media-Foundation

# CA cert for the VM's FQDN via win-acme (HTTP-01), then import to LocalMachine\My
& wacs.exe --target manual --host <vm-fqdn>.cloudapp.azure.com `
  --validation selfhosting --store pfxfile --pfxfilepath C:\bot\certs --accepttos
```

Abre los mismos puertos en el **firewall de Windows** y anota la **huella digital** del certificado: el bot vincula Kestrel (443 y un puerto de notificaciones) y la plataforma de medios (8445) a él.

> **Tip**
>
> El FQDN `*.cloudapp.azure.com` de la propia VM funciona con un certificado de Let's Encrypt; no necesitas un dominio independiente.

## Paso 3 — Compila y ejecuta el bot

Empieza con [`microsoft-graph-comms-samples`](https://github.com/microsoftgraph/microsoft-graph-comms-samples) **PublicSamples/EchoBot** de Microsoft: utiliza `net6.0` y se compila con el SDK de .NET (no necesitas Visual Studio Build Tools):

```powershell
git clone --depth 1 https://github.com/microsoftgraph/microsoft-graph-comms-samples.git C:\bot\samples
cd C:\bot\samples\Samples\PublicSamples\EchoBot\src
dotnet build EchoBot.sln -c Release
```

Configura la sección `AppSettings` de `appsettings.json` con tu `AadAppId`, `AadAppSecret`, `ServiceDnsName`/`MediaDnsName` (el FQDN de la VM), `CertificateThumbprint` y los puertos (llamadas `443`, notificaciones `9441`, contenido multimedia `8445`). Añade dos ajustes para el puente de ElevenLabs que aparece a continuación: `ElevenLabsAgentId` y `ElevenLabsOrigin` (`wss://api.elevenlabs.io` o tu host de residencia). Ejecútalo como una tarea programada o servicio de Windows para que sobreviva a los reinicios.

> **Warning**
>
> El **límite de tiempo de ejecución predeterminado (72 horas)** del Programador de tareas finaliza silenciosamente las tareas de larga duración: un bot iniciado al arrancar deja de funcionar tres días después y las llamadas fallan con "we couldn't connect you". Desactiva el límite y añade el reinicio en caso de error:
>
> ```powershell
> $s = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
>   -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable
> Set-ScheduledTask -TaskName EchoBot -Settings $s
> ```

> **Warning**
>
> El EchoBot predeterminado se bloquea en una llamada realizada al puerto estándar 443: `HttpHelpers.SetAbsoluteUri`
> llama a `req.Host.Port.Value`, que es nulo cuando la cabecera Host no tiene un puerto explícito. Cámbialo por
> `req.Host.Port ?? (req.IsHttps ? 443 : 80)`.

### Sustituye el eco por ElevenLabs

La interfaz de audio de EchoBot es sencilla: `SpeechService.AppendAudioBuffer(in)` y un evento `OnSendMediaBufferEventArgs(out)`. Sustituye su cuerpo de Azure Speech por un puente WebSocket de agentes de ElevenLabs que mantenga la misma interfaz:

**`SpeechService.cs — puente de ElevenLabs (núcleo)`**

```csharp title="SpeechService.cs — puente de ElevenLabs (núcleo)"
public class SpeechService
{
    private readonly AppSettings _settings;
    private readonly ILogger _logger;
    private ClientWebSocket _ws;
    private bool _started;
    private bool _connecting;

    public event EventHandler<MediaStreamEventArgs> SendMediaBuffer; // agent audio -> call
    public event EventHandler FlushMedia;                            // barge-in: drop queued audio

    public SpeechService(AppSettings settings, ILogger logger) { _settings = settings; _logger = logger; }

    // Caller audio -> ElevenLabs
    public async Task AppendAudioBuffer(AudioMediaBuffer buffer)
    {
        if (!_started)
        {
            if (_connecting) return;            // a connect attempt is already in flight
            _connecting = true;
            try { await Connect(); _started = true; }
            catch (Exception ex) { _logger.Error(ex, "ElevenLabs connect failed; retry on next frame"); return; }
            finally { _connecting = false; }
        }
        if (_ws?.State != WebSocketState.Open || buffer.Length <= 0) return;
        var pcm = new byte[buffer.Length];
        Marshal.Copy(buffer.Data, pcm, 0, (int)buffer.Length);
        var msg = JsonSerializer.Serialize(new { user_audio_chunk = Convert.ToBase64String(pcm) });
        await _ws.SendAsync(Encoding.UTF8.GetBytes(msg), WebSocketMessageType.Text, true, default);
    }

    private async Task Connect()
    {
        _ws = new ClientWebSocket();
        // ElevenLabsOrigin: wss://api.elevenlabs.io, or a residency host (.eu./.in./.sg.)
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await _ws.ConnectAsync(new Uri(url), default);
        await _ws.SendAsync(Encoding.UTF8.GetBytes(
            JsonSerializer.Serialize(new { type = "conversation_initiation_client_data" })),
            WebSocketMessageType.Text, true, default);
        _ = Task.Run(ReceiveLoop);
    }

    private async Task ReceiveLoop()
    {
        var buf = new byte[32768]; var sb = new StringBuilder();
        while (_ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await _ws.ReceiveAsync(buf, default); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            var type = doc.RootElement.GetProperty("type").GetString();
            if (type == "audio")            // ElevenLabs audio -> call
                Emit(Convert.FromBase64String(doc.RootElement
                    .GetProperty("audio_event").GetProperty("audio_base_64").GetString()));
            else if (type == "ping")
                await _ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new {
                    type = "pong", event_id = doc.RootElement.GetProperty("ping_event").GetProperty("event_id").GetInt32() })),
                    WebSocketMessageType.Text, true, default);
            else if (type == "interruption")  // barge-in: drop any agent audio still queued
                FlushMedia?.Invoke(this, EventArgs.Empty);
        }
    }

    // slice PCM into 20 ms / 640-byte frames the media platform expects
    private void Emit(byte[] pcm)
    {
        var all = new List<AudioMediaBuffer>(); long tick = DateTime.Now.Ticks;
        for (int off = 0; off < pcm.Length; off += 640)
        {
            var frame = new byte[640];
            Array.Copy(pcm, off, frame, 0, Math.Min(640, pcm.Length - off));
            all.AddRange(Utilities.CreateAudioMediaBuffers(frame, tick, _logger));
            tick += 20 * 10000;
        }
        if (all.Count > 0) SendMediaBuffer?.Invoke(this, new MediaStreamEventArgs { AudioMediaBuffers = all });
    }
}
```

Ambos lados utilizan PCM mono de 16 kHz, por lo que es una transferencia directa en base64: configura el agente en `pcm_16000`. Cuando se produce una `interruption` de ElevenLabs (interrupción), el puente genera `FlushMedia`; conéctalo a tu flujo multimedia para que descarte los `AudioMediaBuffer` en cola; de lo contrario, el agente seguirá hablando sobre la persona que llama. Consulta la referencia completa de mensajes en la [documentación de WebSocket](/docs/es/eleven-agents/libraries/web-sockets). La finalización de la llamada y la transferencia asistida se explican en las secciones siguientes.

> **Note**
>
> La URL de `Connect()` accede a un agente **público**. Para un agente privado, solicita en el servidor una URL firmada de corta duración: `GET /v1/convai/conversation/get-signed-url?agent_id=...` con tu clave de API, y conéctate a la URL devuelta. En la [residencia de datos](/docs/es/overview/administration/data-residency), configura `ElevenLabsOrigin` con tu host de residencia (`wss://api.eu.residency.elevenlabs.io`, `.in.` o `.sg.`): las solicitudes de URL firmada usan el host `https://` correspondiente.

## Paso 4 — Haz que se pueda llamar desde Teams

1. **Activa Calling** en el canal de Teams del Azure Bot y configura el **webhook de llamadas** como `https://YOUR_FQDN/api/calling`:

   ```bash
   az bot msteams create -g $RG -n el-teams-agent-bot \
     --enable-calling --calling-web-hook "https://YOUR_FQDN/api/calling"
   ```

   En el portal, se encuentra en tu recurso de Azure Bot → **Channels** → **Microsoft Teams** → pestaña **Calling**:

   ![La sección Channels de Azure Bot muestra el canal de Microsoft Teams en buen estado](/docs/_fern-img/01115d26ab645dd6ec6569a36a918cd4de0214c79dcb0070d7a3c3a5e544c93b.webp)

   ![La pestaña Calling del canal de Teams, con Enable calling activado y el webhook de llamadas configurado](/docs/_fern-img/7c4819f64878bfb152661adc948c1170f9efd48ba3d68401c4a4d4ba075951b0.webp)

2. Crea un **manifiesto de app de Teams** con `bots[0].supportsCalling: true` y el ID de app del bot; después, cárgalo de forma local (**Apps → Manage your apps → Upload a custom app**) o publícalo para toda la organización sin usar la interfaz: `New-TeamsApp -DistributionMethod organization -Path ./bot-app.zip` (módulo PowerShell de MicrosoftTeams).

Busca la app por nombre en Teams y llámala: el bot responde y el agente de ElevenLabs habla.

![Una llamada activa de Teams con el bot agente de ElevenLabs](/docs/_fern-img/0f1fad08a091bcd20ffbbf5c3949b5503f423d8eb6298f464a870b3a0a254726.webp)

> **Note**
>
> No necesitas un número de teléfono ni una cuenta de recurso para las llamadas 1:1 por nombre: solo son necesarios para la marcación PSTN. `Calls.AccessMedia.All` es lo que activa el puente de audio sin procesar.

## Chat de texto (el mismo bot)

El mismo Azure Bot también puede responder **texto** en Teams, así que los usuarios pueden llamar al agente o chatear con él. Las llamadas y la mensajería son canales independientes en el bot: el webhook de llamadas gestiona la voz y un **punto de conexión de mensajería** de Bot Framework (`/api/messages`) gestiona el chat.

![Un chat de Teams con el bot agente de ElevenLabs respondiendo mensajes de texto](/docs/_fern-img/fb75ae17848916790d29c1c0a703051946902893e46ed62884742db36be453b2.webp)

Dirige el punto de conexión de mensajería del bot al host que lo sirva (el bot multimedia o cualquier otro servicio; no tiene que ser la VM de Windows):

```bash
az bot update -g $RG -n el-teams-agent-bot --endpoint "https://YOUR_FQDN/api/messages"
```

Implementa el punto de conexión con el SDK de Bot Framework y retransmite cada mensaje al agente en **modo texto** a través del mismo WebSocket de conversación utilizado para la voz: envía un evento [`user_message`](/docs/es/eleven-agents/customization/events/client-to-server-events) y lee el evento [`agent_response`](/docs/es/eleven-agents/customization/events/client-events). Primero, activa el campo **first message** en los ajustes de [overrides](/docs/es/eleven-agents/customization/personalization/overrides) del agente: el código siguiente lo sobrescribe con un valor vacío para que la respuesta sea la contestación al mensaje del usuario en lugar del saludo del agente:

**`ChatBot.cs — chat de texto de Teams -> ElevenLabs (modo texto)`**

```csharp title="ChatBot.cs — chat de texto de Teams -> ElevenLabs (modo texto)"
public class ChatBot : ActivityHandler
{
    private readonly AppSettings _settings;
    public ChatBot(AppSettings settings) => _settings = settings;

    protected override async Task OnMessageActivityAsync(
        ITurnContext<IMessageActivity> turn, CancellationToken ct)
    {
        var reply = await AskAgent(turn.Activity.Text, ct);
        await turn.SendActivityAsync(MessageFactory.Text(reply), ct);
    }

    private async Task<string> AskAgent(string text, CancellationToken ct)
    {
        using var ws = new ClientWebSocket();
        var url = $"{_settings.ElevenLabsOrigin}/v1/convai/conversation?agent_id={_settings.ElevenLabsAgentId}";
        await ws.ConnectAsync(new Uri(url), ct);
        // Suppress the agent's greeting: with no override, the first agent_response is the
        // configured first message, not the answer to this user_message.
        await Send(ws, new
        {
            type = "conversation_initiation_client_data",
            conversation_config_override = new { agent = new { first_message = "" } },
        }, ct);
        await Send(ws, new { type = "user_message", text }, ct);

        var buf = new byte[16384]; var sb = new StringBuilder();
        while (ws.State == WebSocketState.Open)
        {
            sb.Clear(); WebSocketReceiveResult r;
            do { r = await ws.ReceiveAsync(buf, ct); sb.Append(Encoding.UTF8.GetString(buf, 0, r.Count)); }
            while (!r.EndOfMessage);

            using var doc = JsonDocument.Parse(sb.ToString());
            switch (doc.RootElement.GetProperty("type").GetString())
            {
                case "agent_response":
                    return doc.RootElement.GetProperty("agent_response_event")
                              .GetProperty("agent_response").GetString();
                case "ping":
                    await Send(ws, new { type = "pong", event_id = doc.RootElement
                        .GetProperty("ping_event").GetProperty("event_id").GetInt32() }, ct);
                    break;
            }
        }
        return "Sorry, I couldn't reach the agent.";
    }

    private static Task Send(ClientWebSocket ws, object msg, CancellationToken ct) =>
        ws.SendAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(msg)),
            WebSocketMessageType.Text, true, ct);
}
```

Regístralo de la forma habitual (un `CloudAdapter`, el bot mediante `AddTransient<IBot, ChatBot>()` y un controlador `/api/messages`) y añade ámbitos de chat a la entrada del bot en el manifiesto:

```json
"bots": [
  { "botId": "YOUR_APP_ID", "supportsCalling": true, "scopes": ["personal", "team", "groupChat"] }
]
```

> **Note**
>
> El fragmento abre una conversación nueva por mensaje, por lo que cada turno es independiente. Para tener **memoria** en el chat, mantén un WebSocket abierto por cada `conversation.id` de Teams (reutilízalo entre turnos) y elimina las sesiones inactivas: así, el agente recordará los mensajes anteriores de ese chat. La anulación de `first_message` debe estar activada en los ajustes de [overrides](/docs/es/eleven-agents/customization/personalization/overrides) del agente: el servidor **cierra la conversación** si se envía una anulación no permitida. Si no puedes activarla, omite la anulación y descarta el primer `agent_response` de cada sesión (el saludo); después, devuelve el siguiente.

> **Tip**
>
> Si las respuestas de chat no llegan, activa el [evento de cliente](/docs/es/eleven-agents/customization/events/client-events) **`agent_response`** en los ajustes **Advanced** del agente: las respuestas de texto se envían mediante ese evento.

## Fin de la llamada

Cuando ElevenLabs termina la conversación (su herramienta **End Call** cierra el WebSocket), cuelga el tramo de Teams:

```csharp
await this.Call.DeleteAsync(); // after a short delay so the goodbye audio finishes
```

## Transferencia asistida a una persona

El agente activa una herramienta de cliente personalizada `transfer_to_human`; el bot **invita a un usuario de Teams** a la llamada en curso (incorporación consultiva) y luego se retira:

```csharp
var target = new IdentitySet { User = new Identity { Id = humanObjectId } };
await this.Call.Participants.InviteAsync(target, replacesCallId: null);
// suppress the end-call hangup while transferring, and mute the bot
```

> **Tip**
>
> La transferencia consultiva (`replacesCallId`) requiere que ambas partes sean usuarios de Teams del **mismo tenant**; los destinos de transferencia PSTN requieren una instancia de aplicación. Para informar primero a la persona, pasa un parámetro `reason` desde el agente y reprodúceselo antes de conectarla.

## Resolución de problemas

#### \`MediaPlatform necesita un sistema con al menos 2 núcleos\`

La VM solo tiene un núcleo físico. Redimensiónala a ≥ 2 núcleos físicos (por ejemplo, `D4s_v3`) y reiníciala.

#### \`No se puede cargar la DLL 'NativeMedia'\`

Instala **VC++ Redistributable** (`vcredist140`) y la característica de Windows **Server-Media-Foundation**; después, reinicia el bot.

#### La llamada entrante devuelve 500 / no se puede conectar la llamada

El error de puerto nulo de EchoBot en 443: corrige `HttpHelpers.SetAbsoluteUri` (consulta el paso 3). Confirma también que el certificado esté firmado por una CA y sea accesible por el puerto 443.

#### Al llamar al bot se escucha 'we couldn't connect you'

Confirma que Calling esté activado en el canal de Teams con el webhook `/api/calling` correcto, que se haya concedido el consentimiento para el permiso de Graph `Calls.AccessMedia.All` y que los puertos 443/8445/9441 estén abiertos tanto en el NSG como en el firewall de Windows. Si las llamadas **antes funcionaban y han dejado de hacerlo**, comprueba que el proceso del bot siga ejecutándose en la VM: el límite de ejecución predeterminado de 72 horas del Programador de tareas lo finaliza unos días después del arranque (consulta la advertencia del paso 3).

## Enlaces útiles

* [Descripción general de los bots de llamadas y reuniones](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/calls-meetings-bots-overview)
* [Conceptos de contenido multimedia en tiempo real](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/real-time-media-concepts)
* [Registrar un bot de llamadas](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/calls-and-meetings/registering-calling-bot)
* [microsoft-graph-comms-samples (EchoBot)](https://github.com/microsoftgraph/microsoft-graph-comms-samples)
* [call: transfer (consultiva)](https://learn.microsoft.com/en-us/graph/api/call-transfer)
* [Protocolo WebSocket del agente](/docs/es/eleven-agents/libraries/web-sockets)