Biblioteca · Asistentes y bots de mensajería

Microsoft 365 + Claude: la integración completa

Creador90 minActualizado: octubre de 2026
59 de 105 en la biblioteca

Tiempo: unos 30 min de teoría + 60 min de práctica


Lo esencial

El cliente corporativo vive en Microsoft. Teams en vez de Slack, SharePoint en vez de Google Drive, Outlook en vez de Gmail, Power Automate en vez de Zapier. Si quieres vender IA a empresas grandes, aprendes el idioma de Microsoft. No es más difícil que lo de siempre, solo es otro dialecto.

🎨 Imagínalo así: hablas muy bien español. Tu cliente vive en Brasil. El producto es el mismo, solo tienes que explicarlo en portugués. Microsoft 365 es el portugués corporativo.


Conceptos clave

  • Microsoft Graph API: el punto de entrada único a todos los datos de Microsoft 365
  • Webhooks de Teams (app Workflows): Claude manda notificaciones a Teams sin bot
  • Integración con Office: Word, Excel y PowerPoint mediante bibliotecas de Python
  • Power Platform: Power Automate + Claude sin código
  • Azure y Microsoft Foundry: infraestructura para un Claude corporativo
  • Microsoft 365 Agents SDK (antes Bot Framework): un bot completo en Teams

Teoría

La arquitectura de Microsoft 365

Código
Ecosistema Microsoft 365
├── Comunicación: Teams, Outlook
├── Documentos: Word, Excel, PowerPoint, OneNote
├── Almacenamiento: SharePoint, OneDrive
├── Automatización: Power Automate, Power Apps
├── Datos: Power BI, Dataverse
└── Infraestructura: Azure, Active Directory

Microsoft Graph API es una sola API que da acceso a todo. Un token, un punto de entrada.

Código
https://graph.microsoft.com/v1.0/
├── /me                        → datos del usuario actual
├── /users/{id}/messages       → correos de Outlook
├── /teams/{id}/channels       → canales de Teams
├── /sites/{id}/drives         → archivos de SharePoint
└── /me/calendar/events        → eventos del calendario

Microsoft Teams + Claude

Opción 1: Incoming Webhook (la más simple)

Un webhook es un enlace. Mandas una solicitud POST a ese enlace y el mensaje aparece en el canal de Teams. Sin bot y sin registrar ninguna aplicación.

⚠️ Microsoft está retirando los webhooks viejos de Office 365 Connectors, y pronto va a bloquear la creación de nuevos. Ahora el webhook se hace con la app Workflows de Teams, que está construida sobre Power Automate.

Obtener el webhook:

  1. Teams → Canal → ... → Workflows
  2. Elige la plantilla "Send webhook alerts to a channel", configura los parámetros y presiona Save
  3. Copia el enlace del webhook (los nombres de los menús de Teams cambian: los pasos actuales están en la documentación de Microsoft Learn)

Límites: el mensaje no puede pasar de 28 KB, y si hay más de cuatro solicitudes por segundo, Teams frena la conexión. El workflow está ligado a su dueño: si esa persona deja la organización, el webhook se queda sin dueño, así que agrega coautores.

Envío básico:

python
import os
import requests

def notify_teams(webhook_url: str, message: str):
    """Mensaje de texto simple a Teams."""
    payload = {"text": message}
    response = requests.post(webhook_url, json=payload)
    return response.status_code in (200, 202)

# Ejemplo
notify_teams(
    webhook_url=os.environ["TEAMS_WEBHOOK_URL"],
    message="✅ El reporte semanal está listo. Claude analizó 1,247 transacciones."
)

Tarjetas adaptativas (formato enriquecido):

El webhook de Workflows acepta Adaptive Cards. El formato viejo MessageCard con botones no lo muestra, así que los botones se hacen con Action.OpenUrl. Revisa cómo se ve la tarjeta en tu canal.

python
import os
import json
import requests
import anthropic
from datetime import datetime

def notify_teams_rich(webhook_url: str, title: str, facts: dict, actions: list = None):
    """
    Tarjeta adaptativa con tabla de datos y botones.
    facts = {"Clave": "Valor", ...}
    actions = [{"title": "Abrir", "url": "https://..."}]
    """
    card = {
        "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
        "type": "AdaptiveCard",
        "version": "1.2",
        "body": [
            {"type": "TextBlock", "text": title, "weight": "Bolder", "size": "Medium", "wrap": True},
            {"type": "TextBlock", "text": f"Generado: {datetime.now().strftime('%d.%m.%Y %H:%M')}",
             "isSubtle": True, "wrap": True},
            {"type": "FactSet", "facts": [{"title": k, "value": v} for k, v in facts.items()]}
        ]
    }

    if actions:
        card["actions"] = [
            {"type": "Action.OpenUrl", "title": a["title"], "url": a["url"]} for a in actions
        ]

    payload = {
        "type": "message",
        "attachments": [{
            "contentType": "application/vnd.microsoft.card.adaptive",
            "content": card
        }]
    }
    requests.post(webhook_url, json=payload)

# Claude analiza los datos → los manda a Teams
def weekly_sales_report():
    data = get_weekly_sales()
    
    client = anthropic.Anthropic()
    analysis = "".join(b.text for b in client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=300,
        messages=[{
            "role": "user",
            "content": f"Analiza en breve: ventas {data['total']}, clientes nuevos {data['new_clients']}, producto más vendido: {data['top_product']}"
        }]
    ).content if b.type == "text")
    
    notify_teams_rich(
        webhook_url=os.environ["TEAMS_WEBHOOK_URL"],
        title="📊 Reporte semanal de ventas",
        facts={
            "Ingresos": f"${data['total']:,}",
            "Clientes nuevos": str(data['new_clients']),
            "Producto top": data['top_product'],
            "Análisis de IA": analysis[:200]
        },
        actions=[{"title": "Reporte completo", "url": "https://dashboard.company.com"}]
    )

Opción 2: un bot de IA en Teams (completo)

Un bot de verdad que responde mensajes en Teams. Microsoft está reemplazando el viejo Bot Framework SDK por Microsoft 365 Agents SDK: hay una guía de migración, y lo sensato es empezar los bots nuevos directo en él (también existe Teams SDK). Abajo, una versión con Agents SDK para Python.

python
# bot.py — bot para Teams con Microsoft 365 Agents SDK (requiere Python 3.10+)
# pip install microsoft-agents-hosting-core microsoft-agents-hosting-aiohttp \
#             microsoft-agents-hosting-teams microsoft-agents-authentication-msal anthropic
#
# AGENT_APP (AgentApplication) se crea según la documentación de Agents SDK:
# CloudAdapter, MsalAuth.from_environment(), almacenamiento de estado.
from microsoft_agents.hosting.core import AgentApplication, TurnState, TurnContext
import anthropic

claude = anthropic.AsyncAnthropic()
conversations = {}  # Historial por conversation_id (en producción: una base de datos o Redis)

@AGENT_APP.activity("message")
async def on_message(context: TurnContext, _state: TurnState):
    user_message = context.activity.text
    conversation_id = context.activity.conversation.id

    # Cargar el historial de la conversación
    history = conversations.get(conversation_id, [])
    history.append({"role": "user", "content": user_message})

    # Claude responde
    response = await claude.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=1000,
        system="Eres el asistente de IA corporativo de la empresa. Responde breve y al grano.",
        messages=history
    )

    reply = "".join(b.text for b in response.content if b.type == "text")
    history.append({"role": "assistant", "content": reply})

    # Guardar el historial (solo los últimos 20 mensajes)
    conversations[conversation_id] = history[-20:]

    await context.send_activity(reply)

Registrar el bot en Azure:

  1. portal.azure.com → Bot Services → Create
  2. Elige "Azure Bot"
  3. Obtén el App ID y el App Password
  4. Configura el Teams Channel
  5. Despliégalo con Azure App Service

Los nombres de los menús del portal de Azure cambian; los pasos actuales están en la documentación de Microsoft 365 Agents SDK.

Microsoft Office + Claude

Word: generar documentos

python
import json
from datetime import datetime
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
import anthropic

def generate_word_report(topic: str, data: dict, output_path: str):
    """Claude escribe el contenido → python-docx crea el documento."""
    client = anthropic.Anthropic()
    
    # Claude genera contenido estructurado
    content_response = "".join(b.text for b in client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=3000,
        messages=[{
            "role": "user",
            "content": f"""
            Escribe un reporte de negocios sobre el tema: {topic}
            
            Datos que debe incluir:
            {json.dumps(data, ensure_ascii=False, indent=2)}
            
            Formato (estricto):
            TÍTULO: [nombre]
            RESUMEN: [3-4 oraciones]
            SECCIÓN1: [nombre]
            [texto de la sección]
            SECCIÓN2: [nombre]  
            [texto de la sección]
            CONCLUSIONES: [2-3 puntos]
            """
        }]
    ).content if b.type == "text")
    
    # Lo procesamos y creamos el documento de Word
    doc = Document()
    
    # Estilos
    style = doc.styles['Normal']
    style.font.name = 'Arial'
    style.font.size = Pt(11)
    
    lines = content_response.split('\n')
    for line in lines:
        if line.startswith('TÍTULO:'):
            title = doc.add_heading(line.replace('TÍTULO:', '').strip(), 0)
            title.alignment = WD_ALIGN_PARAGRAPH.CENTER
        
        elif line.startswith('RESUMEN:'):
            p = doc.add_paragraph()
            run = p.add_run(line.replace('RESUMEN:', '').strip())
            run.italic = True
            p.paragraph_format.space_after = Pt(12)
        
        elif line.startswith('SECCIÓN'):
            section_title = line.split(':', 1)[1].strip() if ':' in line else line
            doc.add_heading(section_title, level=1)
        
        elif line.startswith('CONCLUSIONES:'):
            doc.add_heading('Conclusiones', level=1)
        
        elif line.strip() and not any(line.startswith(k) for k in ['TÍTULO', 'RESUMEN', 'SECCIÓN', 'CONCLUSIONES']):
            doc.add_paragraph(line.strip())
    
    # Agregar fecha y firma
    doc.add_page_break()
    doc.add_paragraph(f"Generado: {datetime.now().strftime('%d.%m.%Y')}")
    doc.add_paragraph("Preparado con Claude AI")
    
    doc.save(output_path)
    return output_path

# Uso
generate_word_report(
    topic="Reporte trimestral de ventas",
    data={"ingresos": "100 000", "crecimiento": "8%", "regiones_top": ["Norte", "Sur"]},  # datos de ejemplo
    output_path="sales_report.docx"
)

Excel: análisis de datos con Claude

python
import json
import anthropic
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.chart import BarChart, Reference

def analyze_excel_with_claude(excel_path: str) -> str:
    """Claude analiza los datos de un Excel y devuelve insights."""
    wb = openpyxl.load_workbook(excel_path)
    ws = wb.active
    
    # Extraer los datos como texto
    data_rows = []
    headers = [cell.value for cell in ws[1]]
    
    for row in ws.iter_rows(min_row=2, values_only=True):
        if any(cell is not None for cell in row):
            data_rows.append(dict(zip(headers, row)))
    
    # Las primeras 50 filas para el análisis
    sample = data_rows[:50]
    
    client = anthropic.Anthropic()
    analysis = "".join(b.text for b in client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=1500,
        messages=[{
            "role": "user",
            "content": f"""
            Analiza los datos de este Excel:
            Columnas: {headers}
            Total de filas: {len(data_rows)}
            Primeros registros: {json.dumps(sample[:10], ensure_ascii=False, default=str)}
            
            Dame:
            1. Una descripción breve de qué hay en la tabla
            2. Observaciones clave (3-5 puntos)
            3. Anomalías, si las hay
            4. Recomendaciones
            """
        }]
    ).content if b.type == "text")
    
    return analysis

def generate_excel_report(data: list, output_path: str, title: str = "AI Report"):
    """Crear un reporte de Excel bien presentado con datos y gráfica."""
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Datos"
    
    # Título
    ws.merge_cells('A1:E1')
    title_cell = ws['A1']
    title_cell.value = title
    title_cell.font = Font(bold=True, size=14, color="FFFFFF")
    title_cell.fill = PatternFill(start_color="0076D7", fill_type="solid")
    title_cell.alignment = Alignment(horizontal='center')
    
    # Encabezados de columnas
    if data:
        headers = list(data[0].keys())
        for col, header in enumerate(headers, 1):
            cell = ws.cell(row=2, column=col, value=header)
            cell.font = Font(bold=True)
            cell.fill = PatternFill(start_color="E0E7FF", fill_type="solid")
    
    # Datos
    for row_idx, row_data in enumerate(data, 3):
        for col_idx, value in enumerate(row_data.values(), 1):
            ws.cell(row=row_idx, column=col_idx, value=value)
    
    wb.save(output_path)
    return output_path

PowerPoint: presentaciones automáticas

python
from datetime import datetime
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
import anthropic

def generate_pptx_from_claude(topic: str, key_points: list, output_path: str):
    """Claude crea el contenido → python-pptx arma la presentación."""
    client = anthropic.Anthropic()
    
    # Generamos las diapositivas
    slides_content = "".join(b.text for b in client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": f"""
            Crea la estructura de una presentación de 5 diapositivas sobre el tema: {topic}
            
            Ideas clave: {', '.join(key_points)}
            
            Formato para cada diapositiva:
            DIAPOSITIVA N:
            TÍTULO: [nombre]
            PUNTOS:
            - [punto 1]
            - [punto 2]
            - [punto 3]
            NOTAS: [para quien presenta]
            ---
            """
        }]
    ).content if b.type == "text")
    
    prs = Presentation()
    prs.slide_width = Inches(13.33)
    prs.slide_height = Inches(7.5)
    
    # Diapositiva de portada
    slide = prs.slides.add_slide(prs.slide_layouts[0])
    slide.shapes.title.text = topic
    slide.placeholders[1].text = f"Preparado: {datetime.now().strftime('%d.%m.%Y')}"
    
    # Procesamos y agregamos las diapositivas
    current_slide_data = {}
    for line in slides_content.split('\n'):
        if line.startswith('DIAPOSITIVA'):
            if current_slide_data.get('title'):
                _add_content_slide(prs, current_slide_data)
            current_slide_data = {'title': '', 'bullets': [], 'notes': ''}
        elif line.startswith('TÍTULO:'):
            current_slide_data['title'] = line.replace('TÍTULO:', '').strip()
        elif line.startswith('- '):
            current_slide_data['bullets'].append(line[2:].strip())
        elif line.startswith('NOTAS:'):
            current_slide_data['notes'] = line.replace('NOTAS:', '').strip()
    
    if current_slide_data.get('title'):
        _add_content_slide(prs, current_slide_data)
    
    prs.save(output_path)
    return output_path

def _add_content_slide(prs, data):
    slide = prs.slides.add_slide(prs.slide_layouts[1])
    slide.shapes.title.text = data['title']
    tf = slide.placeholders[1].text_frame
    tf.text = ''
    for bullet in data['bullets']:
        p = tf.add_paragraph()
        p.text = bullet
        p.level = 0

Power Platform + Claude

Power Automate: el conector HTTP

En Power Automate, crea un Flow:

Código
Trigger: Manual / Schedule / Email received
    ↓
Action: HTTP
    Method: POST
    URL: https://api.anthropic.com/v1/messages
    Headers:
        x-api-key: [tu clave]
        anthropic-version: 2023-06-01
        content-type: application/json
    Body: {
        "model": "claude-haiku-4-5",
        "max_tokens": 500,
        "messages": [{"role": "user", "content": "@{triggerBody()?['text']}"}]
    }
    ↓
Action: Send email / Post to Teams / Update SharePoint

Es la integración no-code más simple: Claude sin una sola línea de código. La acción HTTP es uno de los conectores premium de Power Automate, así que necesitas la licencia correspondiente. No pegues la clave de API en el flujo como texto plano: guárdala en un almacén seguro de secretos (por ejemplo, Azure Key Vault).

Power Apps: un conector personalizado

  1. Power Apps → Custom Connectors → New
  2. Agrega la API de Anthropic como conector
  3. Configura las actions: sendMessage, etc.
  4. Úsalo en cualquier Power App como componente

Microsoft Graph API: el punto único de datos

python
from msal import ConfidentialClientApplication
import requests

class MSGraphClient:
    def __init__(self):
        app = ConfidentialClientApplication(
            client_id=os.environ["AZURE_CLIENT_ID"],
            client_credential=os.environ["AZURE_CLIENT_SECRET"],
            authority=f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}"
        )
        token_result = app.acquire_token_for_client(
            scopes=["https://graph.microsoft.com/.default"]
        )
        self.headers = {
            "Authorization": f"Bearer {token_result['access_token']}",
            "Content-Type": "application/json"
        }
        self.base = "https://graph.microsoft.com/v1.0"
    
    def get_recent_emails(self, user_id: str, count: int = 10) -> list:
        url = f"{self.base}/users/{user_id}/messages"
        params = {"$top": count, "$orderby": "receivedDateTime desc"}
        r = requests.get(url, headers=self.headers, params=params)
        return r.json().get("value", [])
    
    def send_teams_message(self, team_id: str, channel_id: str, message: str):
        # Enviar a un canal vía Graph solo funciona con un token delegado (a nombre de un usuario).
        # Con un token de aplicación, como en esta clase, Graph devuelve 403: para notificaciones usa el webhook de Workflows.
        url = f"{self.base}/teams/{team_id}/channels/{channel_id}/messages"
        payload = {"body": {"content": message}}
        requests.post(url, headers=self.headers, json=payload)
    
    def get_sharepoint_files(self, site_id: str, folder: str = "root") -> list:
        url = f"{self.base}/sites/{site_id}/drive/root:/{folder}:/children"
        r = requests.get(url, headers=self.headers)
        return r.json().get("value", [])

# Claude procesa datos corporativos
def process_emails_with_claude(user_id: str):
    graph = MSGraphClient()
    emails = graph.get_recent_emails(user_id, count=20)
    
    email_texts = [
        f"De: {e['from']['emailAddress']['address']}\nAsunto: {e['subject']}\n"
        f"Fecha: {e['receivedDateTime'][:10]}"
        for e in emails
    ]
    
    client = anthropic.Anthropic()
    summary = "".join(b.text for b in client.messages.create(
        model="claude-haiku-4-5",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": f"Haz un resumen breve de estos correos y señala los urgentes:\n" + "\n---\n".join(email_texts)
        }]
    ).content if b.type == "text")
    
    return summary

Azure + Claude: infraestructura corporativa

Dos enfoques:

1. La API de Claude directo desde la infraestructura de Azure:

Código
Azure App Service → Claude API (api.anthropic.com)
Azure Functions → Claude API
Azure Container Apps → Claude API

Tu código corre en Azure y llama directo a Anthropic. Simple y confiable.

2. Claude en Microsoft Foundry (antes Azure AI Foundry):

Si el cliente dice "no podemos sacar datos de Azure", Claude está disponible en Microsoft Foundry. Hay dos opciones de alojamiento: en la infraestructura de Azure ("Hosted on Azure") y en la infraestructura de Anthropic ("Hosted on Anthropic infrastructure"); no todos los modelos están en ambas. Necesitas una suscripción de pago de Azure en un país soportado y suscribirte al modelo a través de Azure Marketplace. Dónde se procesan exactamente los datos y qué regiones están disponibles, revísalo en la documentación de Microsoft Learn y en el contrato: la palabra "Azure" por sí sola no cubre los requisitos de cumplimiento (compliance).

python
# Claude vía Microsoft Foundry: el mismo SDK de Anthropic, otro cliente y otra dirección
import os
from anthropic import AnthropicFoundry

client = AnthropicFoundry(
    api_key=os.environ["AZURE_AI_KEY"],
    base_url=os.environ["AZURE_AI_ENDPOINT"],  # https://<nombre-del-recurso>.services.ai.azure.com/anthropic
)

response = client.messages.create(
    model=os.environ["AZURE_DEPLOYMENT_NAME"],  # el nombre del despliegue que elegiste al desplegar
    max_tokens=500,
    messages=[{"role": "user", "content": "Hola"}]
)
print("".join(b.text for b in response.content if b.type == "text"))

En vez de una clave, es mejor usar Microsoft Entra ID (DefaultAzureCredential): mira el ejemplo en la documentación de Microsoft Learn ("Deploy and use Claude models in Microsoft Foundry").


Práctica

Paso 1: Webhook de Teams (15 min)

  1. Crea un canal de prueba en Teams
  2. Crea un webhook con la app Workflows y copia el enlace
  3. Corre el ejemplo básico de la lección
  4. Verifica que apareció el mensaje

Paso 2: Word con python-docx (20 min)

bash
pip install python-docx anthropic

Genera un documento de Word sobre cualquier tema con el código de la lección. Abre el archivo y revisa el formato.

Paso 3: Análisis de Excel (15 min)

bash
pip install openpyxl

Toma cualquier archivo de Excel con datos. Corre analyze_excel_with_claude(). Mira qué dice Claude de los datos.

Paso 4: Power Automate (opcional, necesitas M365)

Si tienes acceso a Microsoft 365:

  1. Crea un Flow en Power Automate
  2. Trigger: manual
  3. HTTP action → API de Claude (conector premium)
  4. Manda el resultado a Teams

Paso 5: Ejercicio de práctica

Elige un escenario real:

Opción A: Reporte semanal en Teams. Un script junta datos de cualquier fuente, Claude los analiza y el resultado es una tarjeta en un canal de Teams.

Opción B: Reporte de Word con plantilla. Claude genera el contenido para una tarea concreta y python-docx lo convierte en un documento corporativo.

Opción C: Resumen de correos. Graph API lee el correo, Claude hace el resumen y el resultado llega a Outlook o a Teams.


Herramientas y recursos

  • python-docx: pip install python-docx
  • openpyxl: pip install openpyxl
  • python-pptx: pip install python-pptx
  • MSAL (autenticación de Microsoft): pip install msal
  • Documentación de Microsoft Graph: learn.microsoft.com/graph
  • Documentación de webhooks de Teams: learn.microsoft.com/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
  • Microsoft Foundry (antes Azure AI Foundry): ai.azure.com
  • Microsoft 365 Agents SDK: learn.microsoft.com/microsoft-365/agents-sdk
  • Power Automate: make.powerautomate.com
  • Adaptive Cards Designer: adaptivecards.io/designer

Ideas clave

El mercado corporativo vive en Microsoft. Saber integrar Claude con Teams, Office y Power Platform es una habilidad útil para trabajar con clientes corporativos. Empieza por lo más simple: un webhook de Workflows para Teams son unas cuantas decenas de líneas de código. Después, Graph API abre los datos de la organización (dentro de los permisos otorgados). Microsoft Foundry ayuda a responder la pregunta de dónde se alojan los datos para clientes enterprise, pero los requisitos de cumplimiento revísalos en la documentación y en el contrato.


Lecciones relacionadas

La marca se guarda solo en este navegador y no se envía a ningún sitio. Mi progreso