/** * Microsoft Teams por Microsoft Graph — la fuente única de sus operaciones * para el api, hw-nodes, el dashboard y el servidor MCP. * * ## Por qué un nodo aparte (2026-10-05) * * Ariel pidió las integraciones de Microsoft 365 SEPARADAS, un nodo por * producto, cada uno con su action, su trigger y su AI toolkit, y una app de * Entra por servicio («HostWebhook Teams»): Microsoft sólo deja revocar la app * ENTERA, así que quitarle Teams a HostWebhook no le quita el correo ni los * ficheros, ni al revés. Éste es el sexto, después de Outlook, Outlook * Calendar, OneDrive, Excel y Word. Diseño: `plans/microsoft-teams.md`. * * ## Sólo cuentas de trabajo o escuela, y la primera vez un admin * * Graph no ofrece Teams a cuentas personales: todas las tablas de permisos * dicen «Delegated (personal Microsoft account): Not supported». Por eso la * credencial va con la autoridad `organizations` y hw-credentials rechaza un * `tid` de consumidores. * * La credencial pide nueve permisos delegados, y tres de ellos * (`ChannelMessage.Read.All`, `ChannelMessage.ReadWrite`, `Channel.Create`) * piden consentimiento de ADMIN: la primera conexión de cada organización la * aprueba un administrador de Microsoft 365, y después cualquiera de esa * organización conecta sin pedir nada. Sin `ChannelMessage.Read.All` no hay * ni leer canales ni trigger de canal, que es lo que se pidió. * * Todo va como la PERSONA conectada (permisos delegados): los mensajes salen * con su nombre. Mandar como app por Graph sólo existe para migraciones. * * ## Dieciséis operaciones en cinco familias * * - **Teams & channels** — listar equipos y canales, crear un canal. * - **Channel messages** — publicar, contestar en el hilo, leer los * últimos de un canal. * - **Chats** — listar chats, mandar y leer en uno, y el directo a una * persona por su correo (abre o reutiliza el 1:1). * - **Any message** — en un canal o en un chat (`where`, sin defecto): leer * uno, editarlo, borrarlo, poner y quitar una reacción. * - **People** — buscar personas de la organización. * * ## Los tipos de campo * * Sólo los que ya existen (`text`, `string`, `number`, `boolean`, `select`), * para no tocar la clasificación de tipos del dashboard: qué campo pinta un * selector (equipo, canal, chat, personas) lo decide la página por su NOMBRE * (`teamId`, `channelId`, `chatId`, `mentions`, `userEmail`), como hace * Outlook con `folder`. Ningún campo nace elegido: los `select` opcionales * dicen su defecto en el hueco, sin marcarlo. */ export declare const TEAMS_OPERATIONS: readonly ["listTeams", "listChannels", "createChannel", "sendChannelMessage", "replyToChannelMessage", "listChannelMessages", "listChats", "sendChatMessage", "listChatMessages", "sendDirectMessage", "getMessage", "updateMessage", "deleteMessage", "addReaction", "removeReaction", "findUsers"]; export type TeamsOperation = (typeof TEAMS_OPERATIONS)[number]; /** Type guard — for DTOs and AI tool calls, where the input is untrusted. */ export declare function isTeamsOperation(value: unknown): value is TeamsOperation; /** * El tipo de credencial: el OAuth de la app «HostWebhook Teams» de Entra, sólo * cuentas de trabajo o escuela. Un tipo por servicio de Microsoft, porque cada * uno es su propia app: la credencial de Outlook no sirve aquí ni al revés. */ export declare const TEAMS_CREDENTIAL_TYPE = "microsoft_teams_oauth2"; /** Los grupos del desplegable, en su orden. */ export declare const TEAMS_FAMILIAS: readonly ["Teams & channels", "Channel messages", "Chats", "Any message", "People"]; export type TeamsOperationFamily = (typeof TEAMS_FAMILIAS)[number]; /** Un listado: lo más que se puede pedir (el `$top` máximo de Graph en mensajes). */ export declare const TEAMS_LISTADO_MAXIMO = 50; /** Lo que se pide si no se dice. */ export declare const TEAMS_LISTADO_POR_DEFECTO = 20; /** «Find people»: lo más que se puede pedir. */ export declare const TEAMS_PERSONAS_MAXIMAS = 25; /** * Menciones de personas por mensaje (las de `mentions` más las `correo` * del contenido). Sin comprobar el tope real de Teams: con 20 no se cruza. */ export declare const TEAMS_MENCIONES_MAXIMAS = 20; /** * Caracteres de un mensaje. Sin comprobar: el límite que se cita de Teams es * ~28 KB por mensaje; si Graph contesta 413/400 por tamaño, el error lo dice. */ export declare const TEAMS_CONTENIDO_MAXIMO = 28000; /** El nombre de un canal (documentado por Graph): se comprueba antes de llamar. */ export declare const TEAMS_NOMBRE_DE_CANAL_MAXIMO = 50; /** * «Get message» con «Save inline images»: cuántas imágenes y cuántos bytes EN * TOTAL por llamada. Los metadatos de Graph no traen tamaño ni tipo, así que * se cuentan los bytes reales al bajar; lo que no entra sale en `skipped`. */ export declare const TEAMS_IMAGENES_MAXIMAS = 10; export declare const TEAMS_IMAGENES_BYTES_MAX: number; /** `importance` de un mensaje. Vacío = `normal`. `urgent` sigue avisando en los chats. */ export declare const TEAMS_IMPORTANCIAS: readonly ["normal", "high", "urgent"]; /** `body.contentType` de un mensaje. Vacío = `html`. */ export declare const TEAMS_FORMATOS_DE_CONTENIDO: readonly ["html", "text"]; /** `chatType` de un chat («List chats» filtra por él). Vacío = todos. */ export declare const TEAMS_TIPOS_DE_CHAT: readonly ["oneOnOne", "group", "meeting"]; /** * `membershipType` de un canal nuevo. Vacío = `standard`. Sin `shared`: Graph * lo crea en una operación asíncrona (202) que el nodo no sigue. */ export declare const TEAMS_TIPOS_DE_CANAL: readonly ["standard", "private"]; /** * Dónde está el mensaje en la familia «Any message». Obligatorio y SIN defecto * en single operation; en el toolkit, vacío = `chat` si viene `chat_id`, si no * `channel` (lo infiere hw-nodes). */ export declare const TEAMS_DONDE: readonly ["channel", "chat"]; export interface TeamsParamSpec { /** Field key — also the property name on operationConfig. */ name: string; label: string; /** * Param type, so the dashboard renders the right control. Sólo los tipos * que ya existen: el selector de equipo, canal, chat o personas lo pone la * página por el NOMBRE del campo. */ type: 'text' | 'string' | 'number' | 'boolean' | 'select'; required?: boolean; description: string; /** * El hueco vacío. En un `select` opcional dice lo que pasará si se deja sin * elegir («HTML (default)»): lo enseña, no lo marca. */ placeholder?: string; /** For `select`. */ options?: readonly string[]; /** For `number`. */ min?: number; max?: number; } export interface TeamsOperationSpec { label: string; description: string; /** The Graph route this maps to, so the mapping is checkable. */ apiRoute: string; /** Changes something in Teams (as opposed to only reading it). */ writes?: boolean; /** El grupo del desplegable. */ family: TeamsOperationFamily; params: TeamsParamSpec[]; } export declare const TEAMS_OPERATION_SPECS: Record;