Bot de Microsoft Teams con IA local
Qué cubre este artículo
- Cómo construir un bot de Teams con Bot Framework y Ollama.
- Azure Bot Service versus alternativas locales, qué debe estar obligatoriamente en Azure.
- Adaptive Cards, menciones, canales de Teams y mensajería proactiva.
- Extensiones: RAG sobre datos de SharePoint/M365, resúmenes de reuniones.
- Por qué los bots de Teams son la bestia empresarial entre las plataformas de chat.
Introducción
Microsoft Teams es el estándar en empresas con entornos Microsoft 365 y el único chat donde no registras el bot simplemente “en cualquier lado”. Los bots de Teams requieren obligatoriamente un Azure Bot Resource como intermediario: Teams nunca habla directamente con tu servidor, siempre va a través de Azure Bot Service.
Esto hace el punto de entrada más complejo: registro de aplicación, Azure Bot, manifiesto de Teams, configuración de canales. A cambio, obtienes características empresariales: Adaptive Cards, integración con Graph API, SSO con Entra ID. La IA en sí puede ejecutarse localmente, solo el transporte de mensajes pasa por Azure.
Casos de uso típicos
- Bot de helpdesk interno: responder preguntas de TI desde la wiki corporativa usando RAG.
- Asistente de RRHH: reglas de vacaciones, beneficios, formularios, los empleados preguntan al bot.
- Asistente de DevOps: estado de compilaciones, preguntas sobre despliegues, soporte ante incidentes.
- Asistente de reuniones: resumir transcripciones en combinación con Graph API.
- Chat de IA seguro para compliance: IA local, solo el canal es Microsoft.
Requisitos previos
- Tenant de Microsoft 365 con Teams
- Suscripción a Azure (Bot Service es gratuito en el nivel Free)
- Endpoint HTTPS públicamente accesible para el bot (Azure llama a tu servidor) o un túnel como devtunnel/cloudflared para desarrollo
- Python o C# (Bot Framework SDK está disponible para ambos)
- Ollama con un modelo
Paso 1: Crear Azure Bot Resource
- Portal de Azure → crear “Azure Bot” → Tipo “Single Tenant” o “Multi Tenant”
- Se genera Microsoft App ID + crear Client Secret
- Channels → activar “Microsoft Teams”
- Establecer Messaging Endpoint:
https://bots.tu-dominio.de/api/messages
.env:
APP_ID=tu-app-id
APP_PASSWORD=tu-client-secret
APP_TYPE=MultiTenant
OLLAMA_URL=http://localhost:11434
OLLAMA_MODEL=llama3.1:8b
Paso 2: Bot con Bot Framework (Python)
pip install botbuilder-core botbuilder-schema aiohttp ollama python-dotenv
bot.py:
import os
import ollama
from aiohttp import web
from dotenv import load_dotenv
from botbuilder.core import (
BotFrameworkAdapterSettings, BotFrameworkAdapter, TurnContext, ActivityHandler
)
from botbuilder.schema import Activity, ActivityTypes
load_dotenv()
SETTINGS = BotFrameworkAdapterSettings(
os.environ["APP_ID"], os.environ["APP_PASSWORD"]
)
ADAPTER = BotFrameworkAdapter(SETTINGS)
ollama_client = ollama.Client(host=os.environ["OLLAMA_URL"])
MODEL = os.environ.get("OLLAMA_MODEL", "llama3.1:8b")
SYSTEM = ("Eres el asistente de IA interno de la empresa. "
"Responde brevemente, en español, útil y directo.")
# Contexto de conversación por conversación de Teams
conversations = {}
class KIBot(ActivityHandler):
async def on_message_activity(self, turn_context: TurnContext):
conv_id = turn_context.activity.conversation.id
text = turn_context.activity.text or ""
# Teams incluye <at>Bot-Name</at> en menciones, eliminarlas
text = text.replace("<at>", "").replace("</at>", "")
for entity in turn_context.activity.entities or []:
if entity.type == "mention":
text = text.replace(entity.text, "")
text = text.strip()
if not text:
return
history = conversations.setdefault(conv_id, [])
history.append({"role": "user", "content": text})
history = history[-10:]
# Indicador de escritura
await turn_context.send_activity(Activity(type=ActivityTypes.typing))
try:
resp = ollama_client.chat(model=MODEL, messages=[
{"role": "system", "content": SYSTEM}] + history)
answer = resp["message"]["content"]
history.append({"role": "assistant", "content": answer})
conversations[conv_id] = history
await turn_context.send_activity(answer[:28000])
except Exception as e:
await turn_context.send_activity("Error: backend de IA no disponible.")
BOT = KIBot()
async def messages(request):
body = await request.json()
auth_header = request.headers.get("Authorization", "")
activity = Activity().deserialize(body)
response = await ADAPTER.process_activity(
activity, auth_header, BOT.on_turn
)
return web.Response(status=200)
app = web.Application()
app.router.add_post("/api/messages", messages)
web.run_app(app, host="0.0.0.0", port=3978)
Paso 3: Manifiesto de Teams y paquete de aplicación
Teams necesita un paquete de aplicación (ZIP con manifest.json + iconos):
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
"manifestVersion": "1.16",
"version": "1.0.0",
"id": "tu-app-id-del-azure-bot",
"developer": {
"name": "Tu Empresa",
"websiteUrl": "https://tu-dominio.de",
"privacyUrl": "https://tu-dominio.de/privacy",
"termsOfUseUrl": "https://tu-dominio.de/terms"
},
"name": {"short": "Asistente de IA"},
"description": {"short": "Bot de IA interno", "full": "Asistente de IA con modelo local"},
"icons": {"color": "color.png", "outline": "outline.png"},
"accentColor": "#FFFFFF",
"bots": [{
"botId": "tu-app-id-del-azure-bot",
"scopes": ["personal", "team", "groupChat"],
"supportsFiles": false,
"isNotificationOnly": false
}],
"permissions": ["identity", "messageTeamMembers"],
"validDomains": []
}
Empaquetar en ZIP → Teams → Apps → “Manage your apps” → “Upload a custom app”. En tenants corporativos, el administrador debe permitir sideloading o liberar la app en el Admin Center.
Extensiones
Adaptive Cards (botones, formularios)
card = {
"type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.4",
"body": [{"type": "TextBlock", "text": "¿Fue útil la respuesta?"}],
"actions": [
{"type": "Action.Submit", "title": "👍", "data": {"vote": "up"}},
{"type": "Action.Submit", "title": "👎", "data": {"vote": "down"}},
{"type": "Action.Submit", "title": "Escalar a una persona",
"data": {"escalate": True}},
]
}
await turn_context.send_activity(Activity(
type=ActivityTypes.message,
attachments=[{"contentType": "application/vnd.microsoft.card.adaptive",
"content": card}]
))
Los clics en botones llegan como on_message_activity con value en lugar de text, procésalos allí.
RAG sobre SharePoint y documentación interna
Exporta documentos de SharePoint mediante Graph API, fragmenta, carga en Qdrant, y el bot responde desde el conocimiento corporativo. Alternativamente, usa documentos locales como se describe en RAG local.
Mensajería proactiva
El bot puede escribir a usuarios (por ejemplo, “tu ticket se resolvió”), necesita la conversationReference guardada en el primer contacto:
async def on_members_added_activity(self, members_added, turn_context):
# Guardar referencia de conversación para después
ref = TurnContext.get_conversation_reference(turn_context.activity)
save_ref(turn_context.activity.from_property.id, ref)
# después, de forma proactiva:
await ADAPTER.continue_conversation(ref, callback)
Entra ID / SSO
Dado que el bot se ejecuta de todos modos en el ecosistema Microsoft, puedes obtener la identidad del usuario a través de Entra ID: el bot verá quién pregunta y podrá personalizar las respuestas o validar permisos.
Implementación
Teams invoca tu endpoint, el servidor del bot necesita HTTPS público:
services:
teams-bot:
build: ./bot
restart: always
env_file: .env
ports: ["127.0.0.1:3978:3978"]
# Reverse Proxy (Caddy/Nginx) maneja TLS + redirección
Consejo para desarrollo: utiliza devtunnel (Microsoft) o cloudflared como túnel temporal, sin necesidad de configurar DNS o proxy.
Seguridad y cumplimiento normativo
- Protección de datos híbrida: Los mensajes van Teams → Azure Bot Service → Tu servidor. El procesamiento de IA permanece local, pero Microsoft ve el tráfico.
- App Secret: Guarda el Client Secret en Azure Key Vault o
.env, nunca en el manifiesto. - Validación de tenant: Configura
APP_TYPEcorrectamente (SingleTenant solo para tu empresa). - Permisos: El bot solo ve las conversaciones en las que fue invitado, lo cual es correcto.
- RGPD: Teams se puede usar de manera conforme en la UE (EU Data Boundary); Azure Bot Service actúa como intermediario. Para máxima confidencialidad, considera una solución completamente on-premise como Mattermost.
Errores comunes
- 401 Unauthorized: La App ID o Secret son incorrectos, o
APP_TYPEno coincide con el tipo de tenant. - El bot no responde en canales: El bot debe ser invitado al canal o al equipo (@mention es suficiente para activarlo en el equipo).
- Markup de menciones: Elimina las etiquetas
<at>del texto, de lo contrario aparecerán en el prompt. - La app no es instalable: El sideloading está desactivado en el tenant: el administrador debe habilitarlo.
- El endpoint debe ser HTTPS: Azure solo acepta TLS con certificado válido, sin autofirmados.
Enlaces de referencia
- IRC-Coding.de: Tutoriales de programación en profundidad: Bot Framework, Adaptive Cards, integración con Azure.
- Slack-Bot: La alternativa empresarial más simple.
- Mattermost-Bot: Alternativa completamente autohospedada.
- Comparación de plataformas: Todas las plataformas lado a lado.
- Ollama API: Interfaz del servidor de modelos.
Puntos clave:
- Los bots de Teams requieren obligatoriamente Azure Bot Service como intermediario, no hay ruta directa.
- Configuración: Azure Bot + App Registration + manifiesto de Teams, más trabajo que Slack/Telegram.
- El transporte de mensajes va por Azure, el procesamiento de IA puede mantenerse local (híbrido).
- Adaptive Cards y Graph API son las funciones empresariales destacadas.
- Para empresas M365 es el único camino sensato; para otros casos, demasiado complejo.
FAQ
¿Realmente el bot debe ejecutarse a través de Azure?
¿Cuánto cuesta un Teams-Bot?
¿Python o C# para el bot?
¿Cómo agrego la app al tenant de la empresa?
¿Teams-Bot o Slack-Bot?
¿Ve Microsoft los mensajes del bot?
¿Puede el bot enviar mensajes a los usuarios?
Fuentes y referencias adicionales
- Bot Framework SDK: SDK de Python.
- Teams App Manifest: Referencia del manifiesto.
- Azure Bot Service: Servicio de bot.
- IRC-Coding.de: Tutoriales de programación.


