MCP: el CRM desde tu IA
IAFusionCRM expone un servidor MCP: conectas Claude, ChatGPT, Cursor o cualquier cliente compatible y la IA consulta y opera el CRM con 219 herramientas, las mismas capacidades de la API REST sin escribir una línea de código.
Qué es MCP
El Model Context Protocol es el estándar abierto con el que los asistentes de IA se conectan a herramientas externas. En vez de copiar y pegar datos en un chat, el asistente llama directamente a FusionCRM: busca un contacto, resume las conversaciones de la semana, crea una cotización o mueve un negocio de etapa, y te muestra el resultado.
Un solo servidor
https://api.fusioncol.com/api/mcp — transporte Streamable HTTP, sin instalar nada en tu equipo.
Con tus permisos
La IA solo puede hacer lo que el conector o el token que uses le permita.
Mismas reglas
Lo que la IA crea pasa por las mismas validaciones, límites del plan y automatizaciones que la app.
Qué puedes pedirle
Conectar tu cliente
Hay dos formas de autenticarse; ambas terminan en un token con la organización ya dentro, así que no hay nada más que configurar:
- • Conector MCP (OAuth): en Configuración → Desarrollador → Conectores MCP creas un conector y obtienes un Client ID y un Secreto. El cliente hace el flujo OAuth solo.
- • Token de API: el mismo token de la API REST, enviado como
Authorization: Bearer. Para clientes que aceptan headers fijos.
Qué credencial usar
| Si usas | Usa | Por qué |
|---|---|---|
| Claude web, escritorio o móvil | Conector MCP: Client ID + Secreto | El cliente hace el flujo OAuth solo y renueva el acceso sin que hagas nada. |
| Claude Code, Cursor, scripts o n8n | Token de API como Authorization: Bearer | Crea el token con expiración «Sin expiración» y solo los permisos que necesites. |
| Otros clientes MCP con OAuth | Conector MCP: Client ID + Secreto | Si el cliente admite servidores remotos con OAuth; si solo acepta headers fijos, usa un token de API. |
Los tokens de API funcionan en /api/* (la API REST) y en /api/mcp. No sirven en /api/v1/*, que es la API interna de la app web: ahí responden 401 con el código API_TOKEN_NOT_ACCEPTED.
Claude (web y escritorio)
- 1. En FusionCRM: Configuración → Desarrollador → Conectores MCP → Crear conector MCP. Guarda el Client ID y el Secreto (el secreto se muestra una sola vez).
- 2. En Claude: Configuración → Conectores → Agregar conector personalizado.
- 3. Pega la URL del servidor y, en Configuración avanzada, el Client ID y el Secreto.
- 4. Autoriza el acceso cuando Claude lo pida. Listo: en un chat nuevo ya verás las herramientas de FusionCRM.
Claude Code
claude mcp add --transport http fusioncrm https://api.fusioncol.com/api/mcp \ --header "Authorization: Bearer <token de API>"
Cursor y otros clientes
Cualquier cliente que soporte servidores MCP remotos (Streamable HTTP) funciona. En Cursor, el archivo mcp.json queda así:
{
"mcpServers": {
"fusioncrm": {
"url": "https://api.fusioncol.com/api/mcp",
"headers": { "Authorization": "Bearer <token de API>" }
}
}
}Cursor también acepta el flujo OAuth con un conector (redirige a cursor://anysphere.cursor-mcp/oauth/callback); los metadatos del servidor están en /.well-known/oauth-authorization-server.
Verifica a qué cuenta apunta
Es el primer paso recomendado después de conectar, y es obligatorio si tienes varias organizaciones conectadas: equivocarse de conexión es enviar un WhatsApp desde el número de otra empresa. Pregúntale a la IA «¿a qué organización estoy conectado?»; usará la herramienta get_current_organization. Con un token de API puedes comprobarlo tú mismo:
curl https://api.fusioncol.com/api/me \ -H "Authorization: Bearer <token de API>"
Respuesta (recortada)
{
"success": true,
"data": {
"organization": { "id": "665f…", "name": "Mi Empresa SAS" },
"user": { "email": "ana@example.com", "role": "admin" },
"token": {
"name": "claude-code",
"permissions": ["contacts:read", "whatsapp:send"],
"expiresAt": null
},
"plan": { "name": "pro", "modules": { "api": true } }
}
}expiresAt: null significa que el token no vence.Permisos y seguridad
- • Un conector OAuth actúa con los permisos del usuario que lo autorizó, dentro de su organización.
- • Con un token de API, la IA solo ve las herramientas cuyos permisos tenga el token (por ejemplo, sin
whatsapp:sendno puede enviar mensajes). - • Todo queda acotado a tu organización: no hay forma de leer datos de otra.
- • Los conectores y tokens se revocan desde la misma pantalla donde se crearon; el cliente pierde acceso al instante.
- • MCP hace parte del módulo API del plan, igual que la API REST.
- • El header
X-Organization-Id(o el queryorganizationId) es opcional: sin él se usa la organización del token. Si lo envías y no coincide, la respuesta es 403ORGANIZATION_ID_MISMATCH(en MCP llega como error JSON-RPC con ese código endata.code).
Problemas frecuentes
Cada rechazo trae un code que dice exactamente qué pasó:
| HTTP | code | Causa | Solución |
|---|---|---|---|
| 401 | API_TOKEN_MISSING | La petición llegó sin token. | Revisa el header Authorization. Con un conector OAuth la respuesta trae WWW-Authenticate y el cliente vuelve a pedir el login. |
| 401 | API_TOKEN_INVALID / API_TOKEN_EXPIRED / API_TOKEN_NOT_FOUND | El token está mal copiado, venció o fue revocado. | Genera un token nuevo (o vuelve a autorizar el conector) y reemplázalo en el cliente. |
| 401 | API_TOKEN_NOT_ACCEPTED | Usaste un token de API contra /api/v1/*, que es la API interna de la app web. | Llama a /api/* (la API REST) o a /api/mcp. |
| 403 | INSUFFICIENT_API_PERMISSIONS | Al token le falta el permiso de esa operación. | La respuesta indica requiredPermission: crea o edita el token con ese permiso. |
| 403 | ORGANIZATION_ID_MISMATCH | Enviaste X-Organization-Id (u organizationId) de una organización distinta a la del token. | Quita el header o usa el token de esa organización. |
| 403 | PLAN_MODULE_REQUIRED | El plan de la organización no incluye el módulo API. | Actívalo desde Configuración → Plan. |
Herramientas disponibles
219 herramientas, generadas desde el servidor en producción. El catálogo completo con los parámetros de cada una está en mcp-tools.json.
Contactos (11)
| add_contacts_to_list | Agregar contactos a una lista ESTÁTICA existente por sus IDs. Solo funciona en listas de tipo static. |
| create_contact | Create (or update if exists) a contact. At least one of email, mobile, or whatsapp_phone required. Buenas prácticas: llena SIEMPRE que se pueda source (de dónde salió el lead), companyType (Personal/Empresa/Proveedor/Cliente/Otro u opciones de la org) y city — un contacto sin segmentación no sirve para filtrar ni para campañas. Si no tienes esos datos, pregúntale al usuario. CIUDAD (catálogo DANE, modo estricto): manda cityCode (5 dígitos, ej. 05001 = Medellín) o un city inequívoco; si city es ambiguo (ej. 'Rionegro': Antioquia 05615 / Santander 68615), está mal escrito o no existe, la API responde 422 CITY_UNRESOLVED con `candidates` [{code, shortName, stateName}] y NO crea el contacto: pregunta el departamento y reintenta con cityCode. Con candidates: [] (mal escrita o desconocida) busca el código con search_municipalities. Un cityCode inexistente responde 400 INVALID_CITY_CODE. Nunca elijas un candidato por el usuario. Sin city/cityCode no se toca nada; con country distinto de Colombia, city es texto libre. |
| delete_contact | Eliminar contacto por ID (permiso contacts:delete). |
| filter_contacts | Filtro avanzado cruzado: combina condiciones sobre propiedades de contactos Y condiciones sobre sus deals para retornar una lista paginada. Condiciones para contactFilters: equals | not_equals | contains | not_contains | is_empty | is_not_empty Condiciones para dealFilters: equals | not_equals | contains | not_contains | greater_than | less_than | between | last_deal_before | last_deal_after Campos especiales en dealFilters: - field "hasDeals" (sin condition): filtra si el contacto tiene (true) o no (false) deals que cumplan los demás filtros - field "lastDeal" con condition "last_deal_before"/"last_deal_after": filtra según la fecha del deal más reciente del contacto - field "amount" con greater_than/less_than/between: filtra por monto del deal (between requiere también "valueTo") - cualquier campo real del deal: title, closingDate, createdAt, etc. Ejemplos de uso: - Hoteles sin pedidos en 60 días: contactFilters=[{field:"companyType",condition:"equals",value:"Hoteles y Piscinas"}], dealFilters=[{field:"hasDeals",value:true},{field:"lastDeal",condition:"last_deal_before",value:"2026-02-09"}] - Clientes de Bogotá con deals > $500k: contactFilters=[{field:"lifeCycle",condition:"equals",value:"Cliente"},{field:"city",condition:"equals",value:"Bogota"}], dealFilters=[{field:"amount",condition:"greater_than",value:"500000"}] - Contactos sin categoría: contactFilters=[{field:"companyType",condition:"is_empty"}] - Ciudad por código DANE (Épica Y): contactFilters=[{field:"cityCode",condition:"equals",value:"05001"}] (05001 = Medellín); todo un departamento: {field:"stateCode",condition:"equals",value:"05"} (05 = Antioquia). No filtres cityCode por prefijo: contains escapa el texto (un "^05" se busca literal y nunca casa) y aquí no existe starts_with. Códigos con search_municipalities / list_states. Contactos con ciudad sin confirmar: [{field:"cityCode",condition:"is_empty"},{field:"city",condition:"is_not_empty"}] |
| get_contact | Get a single contact by ID. Devuelve el contacto aplanado (firstName, lastName, email, mobile, company, properties custom…) más `tags` (string[]) y `attribution`: la atribución first-touch (se fija UNA vez, cuando el contacto entra por un formulario/widget/landing) con firstTouchAt (ISO), channel, utmSource/utmMedium/utmCampaign/utmTerm/utmContent, referrer, landingPage (URL), gclid, fbclid, formId, landingPageId y landingPageName; es null si el contacto no tiene atribución (p. ej. creado a mano o por WhatsApp). |
| list_contacts | Listar contactos con búsqueda, rango de fechas y filtros por propiedades. Filtros de propiedades (se combinan con AND, case-insensitive por defecto): - Campos canónicos: firstName, lastName, email, mobile, phone, company, position, city, cityCode, stateCode, country, source, idNumber, companyType. - Ciudades (catálogo DANE, Épica Y): cityCode = código DANE del municipio (5 dígitos, ej. 05001 = Medellín; usar matchMode exact); stateCode = código del departamento (2 dígitos, ej. 05 = Antioquia). Para todo un departamento filtra por stateCode (matchMode exact; en filter_contacts: stateCode equals '05'). No filtres cityCode por prefijo: contains escapa el texto ('^05' nunca casa) y filter_contacts no tiene starts_with. city sigue siendo texto (puede estar sin confirmar: cityCode vacío). ¿No sabes el código? search_municipalities. - extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { industry: 'retail' }). - matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta). Filtros de presencia (ideales para auditar calidad de datos y organizar contactos): - missing: lista separada por comas de claves que DEBEN estar ausentes o vacías. Ej: 'companyType,city' trae contactos sin companyType o con companyType=''. - exists: lista separada por comas de claves que DEBEN existir con valor no vacío. Ej: 'email,mobile' trae contactos con ambos campos. Rango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD). Ordenable por createdAt/updatedAt asc|desc. Paginación con page/limit. |
| list_states | Listar los 33 departamentos de Colombia (GET /api/catalogs/states; Bogotá D.C. cuenta como uno, código 11) con su código DANE de 2 dígitos (el stateCode de contactos y empresas y el state de search_municipalities), nombre oficial, nombre corto e ISO 3166-2. Solo lectura; cualquier token, sin permiso especial. |
| remove_contacts_from_list | Quitar contactos de una lista ESTÁTICA existente por sus IDs (DELETE /lists/:id/contacts). No borra los contactos, solo su pertenencia a la lista. Los IDs que no estaban se cuentan en skipped. |
| search_contact_by_phone | Search contact by phone number for this organization (coincidencia exacta por dígitos y luego parcial, en mobile o phone). Responde `contact` con properties aplanadas, `tags` y `attribution` first-touch (mismo formato que get_contact; null si no tiene). |
| search_municipalities | Buscar municipios de Colombia en el catálogo DANE/DIVIPOLA (GET /api/catalogs/municipalities) para obtener su cityCode (código DANE de 5 dígitos). Solo lectura; cualquier token, sin permiso especial. Úsala cuando create_contact, update_contact, create_company, update_company, import_contacts o submit_form respondan CITY_UNRESOLVED (sobre todo con candidates: [] = ciudad mal escrita o desconocida), y antes de quote_shipment/create_shipment si la ciudad del destino no es inequívoca. Luego reintenta con cityCode. q busca por prefijo o inclusión sobre el nombre oficial y el corto, sin tildes ni mayúsculas, capitales primero (20 resultados por defecto). state acota a un departamento (código DANE de 2 dígitos, ver list_states); un state desconocido → 400 STATE_UNKNOWN. Sin q devuelve el departamento entero (o el catálogo completo de 1.122 municipios: evítalo, manda q). Cada fila trae code (el cityCode), name (oficial), shortName (lo que el CRM guarda en city), stateCode, stateName, stateIso2 y dane8. Hay 66 nombres repetidos entre departamentos (Rionegro, La Unión, Armenia…): mira stateName y, si hay más de un resultado plausible, pregúntale al usuario cuál es. Nunca elijas por él. |
| update_contact | Actualizar un contacto por ID (PUT /contacts/:id): merge de properties por key, los campos que no se envían se conservan. companyId null o '' desvincula la empresa. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. stateCode siempre se deriva del cityCode (el del body se ignora). Sin city/cityCode no se toca la ubicación. |
Listas de contactos (7)
| create_dynamic_contact_list | Crear una lista DINÁMICA de contactos. A diferencia de la estática, los contactos que pertenecen a la lista se recalculan en tiempo real cada vez que se consultan, aplicando los filtros almacenados contra la base de datos actual. Se REQUIERE al menos un filtro. Estructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' } Operadores: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than. Claves de ejemplo: firstName, lastName, email, mobile, company, city, cityCode, stateCode, country, source, companyType, leadScore. Ciudades (catálogo DANE): { key: 'cityCode', operator: 'equals', value: '05001' } (05001 = Medellín); todo un departamento: { key: 'stateCode', operator: 'equals', value: '05' } o { key: 'cityCode', operator: 'starts with', value: '05' }. city es texto y puede estar sin confirmar (cityCode vacío). Filtro especial: { key: 'hasDeals', operator: 'equals', value: true } para contactos con/sin negocios. |
| create_static_contact_list | Crear una lista ESTÁTICA de contactos. Existen tres modos: 1. Lista vacía: pasar solo name (y opcionalmente description). Se puede poblar después con add_contacts_to_list. 2. Por IDs directos: pasar contactIds (array de IDs de contacto). Máximo 2000 contactos. 3. Por filtros de propiedades: pasar filters para buscar y capturar los contactos que coincidan en el momento de creación. Los filtros son snapshots — el grupo no cambia automáticamente después (para eso usar create_dynamic_contact_list). Estructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' } Operadores soportados: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than. Claves de ejemplo: firstName, lastName, email, mobile, company, city, country, source, companyType, leadScore. Límite: 5000 coincidencias al filtrar (usar filtros más específicos si se supera). |
| delete_contact_list | Eliminar una lista de contactos por ID. No elimina los contactos, solo la lista. |
| get_contact_list | Obtener una lista de contactos por su ID. |
| get_contact_list_contacts | Obtener los contactos de una lista (estática o dinámica) con paginación. |
| list_contact_lists | Obtener todas las listas de contactos (estáticas y dinámicas) de la organización. |
| update_contact_list | Actualizar una lista existente (PUT /lists/:id). - name / description: siempre editables. - filters: solo aplican a listas DINÁMICAS. Reemplazan los filtros existentes. Formato idéntico a create_dynamic_contact_list. - contactIds: solo aplican a listas ESTÁTICAS. Reemplazan la lista completa de miembros (útil para eliminar contactos). Si quieres AGREGAR miembros sin reemplazar, usar add_contacts_to_list. No está permitido cambiar el tipo de lista. |
Empresas (7)
| create_company | Crear una empresa (o actualizarla si ya existe: upsert por idNumber/NIT primero y por name después). Body plano → properties: name, industry, website, email, phone, address, city, cityCode, country, idType, idNumber, size, notes y cualquier propiedad custom. Requiere name o idNumber. Permiso companies:write. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos, ej. 05001 = Medellín) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. state (departamento en texto) sirve de pista para homónimos; stateCode siempre se deriva del cityCode. Nunca elijas un candidato por el usuario. |
| delete_company | Eliminar empresa por ID (permiso companies:delete). Los contactos y deals vinculados NO se borran: solo se desvinculan de la empresa. |
| get_company | Obtener una empresa por ID, incluyendo sus contactos vinculados, sus deals y un resumen (totalRevenue, totalDeals, totalContacts, lastDeal). Permiso companies:read. |
| link_contact_to_company | Vincular un contacto a una empresa (setea companyId en el contacto y sincroniza su propiedad de texto 'company' con el nombre de la empresa). Enviar companyId null o "" DESVINCULA al contacto de su empresa actual. Para vinculación masiva usar fusion_request con POST /api/companies/:id/contacts { contactIds: [...] }. Permisos contacts:write (+ companies:read para validar). |
| list_companies | Listar empresas (companies) con búsqueda, rango de fechas y filtros por propiedades (permiso companies:read). Filtros de propiedades (se combinan con AND, case-insensitive por defecto): - Campos canónicos: name, industry, city, cityCode (código DANE de 5 dígitos, usar matchMode exact), stateCode (departamento, 2 dígitos), country, idNumber (NIT). - extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { size: '50-100' }). - matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta). search: texto libre sobre name, industry, email, phone, website, city, idNumber. Rango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD). Ordenable por createdAt/updatedAt asc|desc. Paginación con page/limit. |
| list_company_contacts | Listar los contactos vinculados a una empresa (GET /api/companies/:id/contacts). Permiso companies:read. |
| update_company | Actualizar una empresa por ID (merge de properties por key: los campos enviados se actualizan o agregan, el resto se conserva). Si cambia el name se sincroniza en los contactos vinculados. Permiso companies:write. CIUDAD (catálogo DANE, modo estricto): cityCode (5 dígitos) o city inequívoco; ambiguo/desconocido → 422 CITY_UNRESOLVED con candidates (reintenta con cityCode; si candidates viene vacío, búscalo con search_municipalities); cityCode inexistente → 400 INVALID_CITY_CODE. state (texto) es pista para homónimos; stateCode siempre se deriva. |
Negocios (7)
| create_deal | Create a deal (cuerpo igual que POST /api/deals). Acepta companyId opcional para asociar una empresa (debe pertenecer a la organización); si no se envía y el contacto principal tiene empresa vinculada, el deal la HEREDA automáticamente. |
| deals_summary | Sales summary for a date range filtered by closingDate (fecha de cierre real: al ganar o perder, closingDate pasa a ser el día en que se cerró; wonAt/lostAt llevan la hora). Total deals, won deals, total sold, avg ticket, top 5 by amount. Use this for monthly/period sales reports. |
| delete_deal | Eliminar negocio por ID (permiso deals:delete). |
| get_deal | Get a deal by ID. |
| list_deals | List deals with optional date range, search, and pagination. Dates YYYY-MM-DD. Use closingFrom/closingTo to filter by closing date (for sales reports: closingDate is the REAL close date once a deal is won/lost, the estimate while open). wonFrom/wonTo and lostFrom/lostTo filter by the exact won/lost timestamp (wonAt/lostAt). Use createdFrom/createdTo to filter by creation date. Each deal exposes wonAt, lostAt and expectedCloseDate (the original estimate). |
| list_deals_config | Devuelve todos los pipelines de deals con sus etapas embebidas en una sola llamada. Úsalo para conocer los stageId disponibles antes de crear o mover deals. Evita llamadas separadas. |
| update_deal | Update a deal by ID. Campos editables: title, amount, pipeline, status, closingDate, customFields, contact y companyId. Para asociar un contacto a un deal existente enviar `contact`: - { "id": "<contactId>" } para usar un contacto ya existente de la organización - { "mobile": "573001234567", "name": "...", "company": "..." } para buscarlo por teléfono y crearlo si no existe - `null` para desasociar el contacto actual. Para asociar una empresa enviar `companyId` (debe pertenecer a la organización); `null` o "" desasocia la empresa actual. |
Conversaciones y WhatsApp entrante (15)
| add_conversation_message | Añadir mensaje a una conversación (POST .../messages). |
| change_conversation_pipeline | Cambia una conversación a otro pipeline, opcionalmente a una etapa inicial específica. Si no se pasa initialStageId, se usa el primer stage (por order) del pipeline destino. Escribe un log de cambio y dispara el webhook del stage destino si está habilitado. |
| conversations_summary | Summary of conversations in a date range: counts and by source (sample up to 1000 per page). |
| create_conversation | Crear conversación. Para descubrir los stageId disponibles usá list_pipeline_stages. |
| delete_conversation | Eliminar conversación por ID (permiso conversations:delete). |
| find_conversation_by_phone | Find conversation by contact phone. Includes messages. |
| get_conversation | Get a conversation by ID with messages, lastMessage populated, and contact data. Returns the most recent `messagesLimit` messages. To read older history, pass `messagesBefore` = `messagesPagination.nextBefore` from the previous response (or the timestamp/_id of the oldest message you already have) and repeat while `messagesPagination.hasMore` is true. |
| get_conversation_stage_history | Devuelve el historial de cambios de etapa de una conversación (logs CONVERSATION_STAGE_CHANGE) ordenado por timestamp descendente. Cada item incluye fromStage, toStage, pipeline, user y triggeredBy (user/automation/system). |
| list_conversation_pipelines | Lista los pipelines de conversación de la organización (id, nombre, isDefault). Úsalo antes de list_pipeline_stages si querés un pipeline específico que no sea el default. |
| list_conversations | List conversations with filters, messages included. Dates dd/mm/yyyy; timezone = UTC offset (e.g. -5 Colombia). dateField controls which date column is filtered (createdAt, updatedAt, lastMessageTimestamp). |
| list_conversations_config | Devuelve todos los pipelines de conversación con sus stages embebidos en una sola llamada. Úsalo para conocer los stageId disponibles antes de crear o mover conversaciones. Evita tener que llamar list_conversation_pipelines y list_pipeline_stages por separado. |
| list_pipeline_stages | Lista las etapas (stages) de un pipeline de conversación, ordenadas por order. Devuelve _id, name, order, color, autoAssign, webhookEnabled. Si no pasas pipelineId se usa el pipeline default de la organización. Necesario para conocer los stageId antes de mover o crear conversaciones. |
| move_conversation_stage | Mueve una conversación a otra etapa dentro del mismo pipeline. Escribe un log de cambio y dispara el webhook configurado en la etapa destino si está habilitado. El stageId debe pertenecer al pipeline actual de la conversación (usar list_pipeline_stages para obtenerlo). |
| search_conversations | Buscar conversaciones por título, nombre/apellido del contacto o teléfono (query q, substring sin distinguir mayúsculas). NO busca dentro del contenido de los mensajes — para localizar una conversación por lo que se dijo en ella, usar list_conversations y revisar mensajes, o buscar por el contacto. |
| update_conversation | Actualizar conversación por ID. Para mover entre etapas usá move_conversation_stage (no pases currentStage aquí). |
Envío de WhatsApp (4)
| list_whatsapp_campaigns | Listar las campañas masivas de WhatsApp de la organización (las de /mass-whatsapp), más recientes primero, con un resumen por contacto único según el ÚLTIMO estado que reportó Meta: accepted (Meta aceptó el envío — NO significa entregado), delivered, read, failed y lastSentAt. Para el detalle (quién respondió, motivo de cada fallo, excluidos) usa get_campaign_stats con el id. |
| list_whatsapp_templates | Listar las plantillas de WhatsApp (Meta/Facebook Marketing) aprobadas para la organización. Devuelve nombre, idioma, categoría, estado y componentes (BODY/HEADER/FOOTER con su texto) para que la IA pueda elegir la plantilla adecuada antes de enviarla con send_whatsapp_template. Requiere permiso whatsapp:send en el token. |
| send_whatsapp_message | Enviar un mensaje de WhatsApp de sesión (texto o media) a un número. Solo llega si el cliente escribió en las últimas 24 h (ventana de Meta); fuera de la ventana usa send_whatsapp_template. organizationId se rellena automáticamente. |
| send_whatsapp_template | Enviar una plantilla de WhatsApp (Meta/Facebook Marketing) a un número. FLUJO EN 2 PASOS: (1) llama SIN confirm (o confirm=false) → NO envía nada, devuelve un PREVIEW del estado (si el contacto existe y qué campos de segmentación le faltan; si la conversación existe, su etapa y si un agente IA la atiende; las etapas disponibles del pipeline). Muestra ese plan al usuario y pregúntale: a qué etapa debe ir la conversación (stageId), si el agente IA debe atenderla (assignTo) y qué datos del contacto completar (bloque contact). (2) re-llama con confirm=true y los parámetros elegidos → envía de verdad. Usa list_whatsapp_templates primero para obtener templateName, language y components. 'to' sin '+'. 'components' en formato oficial de Meta. Requiere permiso whatsapp:send. Nota: la conversación nueva NO se asigna al agente IA por defecto (el primer entrante suele ser un autorespondedor del cliente y el bot conversaría con otra máquina). |
Correo (1)
| send_email | Enviar un correo por SMTP del usuario (mismo envío que la app web). Solo texto/HTML, sin adjuntos. Requiere que el usuario tenga SMTP configurado. Registra una actividad 'Correo' en los contactos cuyo email coincida con los destinatarios. Si un destinatario está desuscrito del email marketing, el resultado incluye un campo 'warning' (el envío 1:1 no se bloquea) — tenlo en cuenta antes de enviar contenido comercial. |
Cotizaciones (6)
| create_quotation | Crear cotización (POST /api/quotations). El número lo asigna el sistema. Si el contacto no existe por mobile, se crea. |
| delete_quotation | Eliminar cotización por ID (permiso quotations:delete). |
| download_quotation_pdf | Descargar PDF de cotización por número (quotationNumber). Devuelve base64; puede ser pesado. |
| get_quotation | Obtener cotización por ID (Mongo). |
| list_quotations | Listar cotizaciones con filtros y paginación. |
| update_quotation | Actualizar cotización por ID (PUT /quotations/:id). El número de cotización no se puede cambiar. |
Facturas (6)
| apply_invoice_withholdings | Registrar las retenciones que el cliente practicó al pagar una factura de venta (PUT /api/invoices/:id/withholdings o, con prefix+number, PUT /api/invoices/withholdings). Requiere invoices:write. No emite nada ante la DIAN. Es lo mismo que el modal 'Aplicar retenciones' de la app: queda pegado a la factura, netea la cartera (saldo = total − retenciones − abonos) y sale en el Excel del contador y en el informe mensual. Cada ítem lleva key (concepto) y amount O pct. Con pct el servidor calcula sobre la base sin IVA (tax_exclusive_amount); para reteIva calcula sobre el IVA de la factura. Si no mandas ni amount ni pct usa el porcentaje sugerido del concepto. El monto que manda es el que el cliente retuvo de verdad (el comprobante): si difiere del cálculo, manda amount. Conceptos y % sugerido: compras25 (2.5), compras35 (3.5), serviciosGeneral4 (4), serviciosGeneral6 (6), honorarios10 (10), honorarios11 (11), arrendamientoBienesMuebles (4), arrendamientoBienesRaices (3.5), serviciosAseoVigilancia (2), serviciosHotelesRestaurantes (3.5), transporteCarga (1), reteIva (15, sobre el IVA), reteIca (sin % sugerido: manda amount). REEMPLAZA el set completo de la factura (idempotente): para editar, manda todos los ítems otra vez. Con dryRun: true devuelve el cálculo sin guardar; úsalo para mostrarle a la persona el total y el neto antes de aplicar. Solo facturas de venta (direction outgoing, tipo factura): notas crédito/débito y facturas de proveedor responden 422 NOT_A_SALES_INVOICE. |
| create_invoice | Crear factura electrónica (POST /api/invoices, formato DIAN). Requiere customer con identification_number, invoice_lines no vacío y legal_monetary_totals.payable_amount > 0. |
| get_invoice | Obtener factura por ID. |
| get_invoice_withholdings | Retenciones que el cliente le practicó a una factura de venta emitida (GET /api/invoices/:id/withholdings). Requiere invoices:read. Devuelve withholdings (null si no tiene): base, iva, items[{key, kind, label, pct, amount}], byKind{rteFuente, rteIva, rteIca}, total, netPayable, appliedAt, source; y netPayable (= total de la factura si no hay retenciones). Úsala antes de apply_invoice_withholdings para saber si la factura ya las tiene. El id se obtiene con list_invoices (search por prefijo/número). |
| list_invoices | Listar facturas (direction outgoing/incoming, etc.). |
| remove_invoice_withholdings | Quitar las retenciones registradas en una factura de venta (DELETE /api/invoices/:id/withholdings). Requiere invoices:write. Idempotente: si no tenía, responde igual con removed: false. La cartera vuelve a cobrar el total completo. |
Productos (10)
| create_product | Crear producto (POST /api/products). |
| create_product_variant | Crea una variante de un producto de la organización. El sku debe ser único en TODA la organización: uno repetido responde 409. No marca el producto como 'con variantes' (hasVariants); si hace falta, usa update_product. Requiere products:write. |
| delete_product | Eliminar un producto del catálogo por su ID (DELETE /products/:id, permiso products:delete). BORRA el producto Y TODAS sus variantes (tallas, colores, SKUs de ProductVariant). Es IRREVERSIBLE: no hay papelera ni forma de recuperarlo. Si el producto aparece en una cotización abierta (borrador o enviada) responde 409 PRODUCT_IN_OPEN_QUOTATIONS y NO borra nada: cierra o edita esas cotizaciones primero. No toca cotizaciones ni facturas ya emitidas (guardan su propia copia del ítem). Confirma con el usuario el nombre del producto (get_product) antes de llamarla. Para solo cambiar precio o datos usa update_product. |
| delete_product_variant | ELIMINA una variante de producto. Es IRREVERSIBLE y libera su sku. Si solo quieres dejar de ofrecerla usa update_product_variant con isActive=false. Confirma con el usuario antes de llamarlo. Requiere products:delete. |
| get_product | Obtener producto por ID. |
| get_product_variant | Devuelve una variante de producto por id. Un id de otra organización o inexistente responde 404. |
| list_product_variants | Lista las variantes de producto de la organización (SKU, precio, stock, atributos como talla o color, activa o no). Filtra por productId para ver las de un producto (404 si el producto no es de la organización). Para el catálogo de productos usa list_products. |
| list_products | Listar productos con filtros. |
| update_product | Actualizar producto por ID (PUT /products/:id). Solo se tocan los campos enviados. |
| update_product_variant | Edita una variante de producto: sku (único en la organización, 409 si se repite), price, stock, imageUrl, isActive (false la oculta sin borrarla) o attributeValues (REEMPLAZA la lista completa). Parcial: solo cambia lo que envíes; null en stock o imageUrl los limpia. No cambia de producto padre. Requiere products:write. |
Formularios web (5)
| get_form | Obtener un formulario por ID, incluyendo el link público, el snippet de embed y la URL del webhook (panel Compartir). |
| get_form_responses | Respuestas (envíos) de un formulario, más recientes primero. Cada una trae el contacto creado/actualizado, el negocio si se creó, las respuestas y el origen (UTMs, página). Los sospechosos (honeypot) se excluyen salvo includeSuspicious. |
| list_forms | Listar los formularios de captación de la organización (nombre, slug público, campos, si crea negocio, total de envíos). |
| submit_form | Enviar un formulario en nombre de un contacto (server-to-server). Pasa por el mismo camino que la página pública: busca el contacto por email/celular y lo crea o actualiza sin duplicar, registra el origen, crea el negocio si el form lo tiene configurado y avisa al dueño. `values` = { claveDeCampo: valor }; opcionales utm_source/utm_medium/utm_campaign/page_url. Solo cuentan las claves que son campos del formulario (get_form). CIUDAD (catálogo DANE, modo estricto): si el envío trae ciudad y no resuelve sin ambigüedad, responde 400 con code CITY_UNRESOLVED (ambigua, mal escrita o desconocida) o INVALID_CITY_CODE (cityCode inexistente), más input y candidates [{code, shortName, stateName, stateCode}], y NO guarda nada. Reintenta con cityCode (si el formulario tiene ese campo) o con la ciudad + el departamento (campo state/departamento/depto/dpto) para homónimos como Rionegro; con candidates vacío, búscala con search_municipalities. Nunca elijas un candidato por el usuario. |
| update_form | Editar la configuración de un formulario de captación (PUT /forms/:id): campos, tema, qué pasa después de enviar, negocio automático, avisos, consentimiento y dominios permitidos. Solo cambian las claves enviadas; nunca toca slug, publicKey ni estadísticas. Los campos (fields[].key) son claves de propiedades de contacto de la organización. active: false pausa el formulario (los envíos responden 410). |
Proyectos (7)
| create_project | Crear proyecto (POST /api/projects). |
| create_project_task | Crear tarea dentro de un proyecto (POST /projects/:id/tasks). |
| delete_project | Eliminar proyecto por ID (permiso projects:delete). |
| get_project | Obtener proyecto por ID. |
| list_project_tasks | Listar tareas de un proyecto (GET /projects/:id/tasks). |
| list_projects | Listar proyectos. |
| update_project | Actualizar proyecto por ID (PUT /projects/:id). Solo se tocan los campos enviados. |
Tareas (5)
| create_task | Crear tarea (POST /api/tasks). Incluir projectId en el cuerpo si aplica. |
| delete_task | Eliminar tarea por ID (permiso tasks:delete). |
| get_task | Obtener tarea por ID. |
| list_tasks | Listar tareas (opcional projectId en query para filtrar). |
| update_task | Actualizar tarea por ID (PUT /tasks/:id). Solo se tocan los campos enviados. |
Respuestas rápidas (5)
| create_fragment | Crear un fragment. El 'atajo' debe ser único globalmente (índice único en BD). |
| delete_fragment | Eliminar un fragment por ID (permiso fragments:delete). |
| get_fragment | Obtener un fragment por ID. La respuesta incluye su "format" ("text" o "html"). |
| list_fragments | Listar fragments (snippets de texto reutilizables) de la organización. Soporta paginación y búsqueda por nombre, texto o atajo. Cada fragment incluye su "format" ("text" o "html"). |
| update_fragment | Actualizar un fragment por ID. Campos editables: name, text, atajo, format, asunto. |
Biblioteca (4)
| get_library_file | Obtener un archivo de la Biblioteca por ID, con su mediaURL, tags y metadatos. |
| list_library_files | Listar archivos de la Biblioteca (library) de la organización: imágenes, PDFs, videos, etc. ya subidos. Devuelve id, name, fileType, mediaURL (URL pública del archivo), tags y fechas. IMPORTANTE: esta tool NO sube archivos. El MCP solo puede consultar y editar metadatos de archivos que ya existen; para subir uno nuevo hay que usar la app o la web de FusionCRM. |
| list_library_tags | Listar los tags únicos en uso en la Biblioteca de la organización. Útil para saber qué tags existen antes de reetiquetar un archivo con update_library_file. |
| update_library_file | Editar los METADATOS de un archivo YA subido a la Biblioteca. Campos editables: name y tags. NO sube archivos, NO reemplaza el contenido del archivo y NO cambia su mediaURL: únicamente renombra y reetiqueta algo que ya está en la Biblioteca. Ojo: 'tags' REEMPLAZA la lista completa de tags (no agrega). Para añadir uno, lee los tags actuales con get_library_file y envía la lista completa ya con el nuevo. |
Base de conocimiento (4)
| delete_knowledge_base_document | Elimina un documento de la base de conocimiento (RAG) por ID, incluyendo sus chunks/embeddings. IRREVERSIBLE. Usa list_knowledge_base_documents para obtener el ID. Requiere permiso rag:delete. |
| list_knowledge_base_documents | Lista los documentos de la base de conocimiento (RAG) de la organización: título, estado de indexación (processing/ready/failed), tags y cantidad de chunks. Útil para saber qué información hay disponible antes de buscar con search_knowledge_base. |
| search_knowledge_base | Busca en la base de conocimiento PROPIA de la organización (RAG multi-tenant): documentos que la organización subió a FusionCRM (catálogos, información de productos, políticas, precios propios, manuales internos, etc.). Devuelve los fragmentos más relevantes con score de similitud y documento de origen. Si no hay resultados, la organización quizá no ha subido documentos aún (la subida se hace desde la app en /api/v1/rag/documents). |
| upload_knowledge_base_text | Sube un documento de TEXTO PLANO a la base de conocimiento (RAG) de la organización. La indexación (chunking + embeddings) corre en segundo plano: el documento queda en status 'processing' y pasa a 'ready' en segundos (verificable con list_knowledge_base_documents). Solo texto plano; archivos binarios (PDF, DOCX) se suben desde la app. Requiere permiso rag:write. Para REEMPLAZAR un documento existente (p. ej. una lista de precios), sube primero la versión nueva y luego elimina la vieja con delete_knowledge_base_document — si quedan las dos, la búsqueda puede mezclar información desactualizada. |
Tu cuenta (1)
| get_current_organization | ¿A qué organización apunta esta conexión? Devuelve la organización (id y nombre), el usuario a cuyo nombre se emitió el token, sus permisos, su vencimiento y el plan efectivo con sus módulos. Llámalo al empezar y ANTES de cualquier acción de salida (WhatsApp, correo, campañas) si el usuario tiene varias cuentas conectadas: equivocarse de conexión es enviar desde el número de otra empresa. |
Configuración: pipelines y etapas (17)
| create_conversation_pipeline | Crea el pipeline de conversaciones de la organización con sus etapas. La app trabaja con UN solo pipeline de conversaciones por organización: si ya existe uno, NO crea otro y devuelve el existente con alreadyExists: true (status 200). Para cambiar las etapas de un pipeline existente usa create_conversation_stage / update_conversation_stage / delete_conversation_stage. webhookUrl debe ser https. Requiere pipelines:write. |
| create_conversation_stage | Agrega una etapa a un pipeline de conversaciones (por defecto al final). Campos opcionales: color, autoAssign + assignToTeam (userId de un empleado de la org), webhookEnabled + webhookUrl (https obligatorio), reopenOnNewMessage. No mueve conversaciones. Requiere pipelines:write. |
| create_deal_pipeline | Crea un pipeline de negocios nuevo con sus etapas en el orden dado (la primera queda a la izquierda del tablero). Máximo 50 etapas. Marca con isWon la etapa de negocio ganado y con isLost la de perdido (una etapa no puede ser las dos). No mueve ni crea negocios. Requiere pipelines:write. |
| create_deal_stage | Agrega una etapa al FINAL de un pipeline de negocios (para cambiar su posición llama luego reorder_deal_stages). Máximo 50 etapas por pipeline. No mueve negocios a la etapa nueva. Requiere pipelines:write. |
| delete_conversation_pipeline | BORRA un pipeline de conversaciones y TODAS sus etapas (incluida la configuración de webhooks y asignación). Es IRREVERSIBLE. Se niega (409) si es el único pipeline de la organización o si todavía tiene conversaciones: primero hay que moverlas con change_conversation_pipeline. Si era el predeterminado, otro pasa a serlo. No borra conversaciones. Confirma con el usuario antes de llamarlo. Requiere pipelines:write. |
| delete_conversation_stage | BORRA una etapa de un pipeline de conversaciones, con su configuración (webhook, asignación). Es IRREVERSIBLE. Si la etapa tiene conversaciones, hay que indicar targetStageId (otra etapa del MISMO pipeline): las conversaciones se mueven ahí antes de borrar; sin él responde 409 con el número de conversaciones afectadas. No borra conversaciones ni registra historial de cambio de etapa. La última etapa del pipeline no se puede borrar. Confirma con el usuario antes de llamarlo. Requiere pipelines:write. |
| delete_deal_pipeline | ELIMINA un pipeline de negocios y TODAS sus etapas. Es IRREVERSIBLE (no hay papelera). Solo funciona si el pipeline no tiene negocios: si tiene, responde 409 con dealsCount y no borra nada; en ese caso mueve o elimina esos negocios primero. Confirma con el usuario antes de llamarlo. Requiere pipelines:write. |
| delete_deal_stage | ELIMINA una etapa de un pipeline de negocios. Es IRREVERSIBLE. Solo funciona si la etapa no tiene negocios: si tiene, responde 409 con dealsCount y no borra nada; mueve antes esos negocios a otra etapa con update_deal. Nunca borra negocios. Confirma con el usuario antes de llamarlo. Requiere pipelines:write. |
| get_conversation_pipeline | Devuelve UN pipeline de conversaciones con todas sus etapas ordenadas (id, name, order, color, autoAssign, assignToTeam, webhookEnabled, webhookUrl, último resultado del webhook, reopenOnNewMessage). Úsalo antes de editar etapas para conocer sus ids. No lista pipelines (para eso list_conversation_pipelines) ni devuelve conversaciones. Requiere pipelines:read. |
| get_deal_pipeline | Devuelve un pipeline de negocios por id con sus etapas ordenadas, la configuración de cada etapa y el número de negocios por etapa. Un id de otra organización o inexistente responde 404. No devuelve los negocios (para eso list_deals con el pipeline). |
| list_deal_pipelines | Lista los pipelines de NEGOCIOS (deals) de la organización con todas sus etapas en orden: id, nombre, color, probabilidad de cierre, marcas de ganada/perdida (null = se infiere del nombre), días máximos en la etapa (maxDaysInStage, null = sin límite), requisitos por etapa (stageGateEnabled/stageGateCumulative) y cuántos negocios hay en cada etapa. Úsalo antes de crear, mover o configurar etapas para tener los ids. NO sirve para los pipelines de conversaciones de WhatsApp (esos son list_conversation_pipelines) ni lista los negocios en sí (list_deals). |
| reorder_conversation_stages | Reordena las etapas de un pipeline de conversaciones: stageIds debe traer TODAS las etapas del pipeline, sin repetir, en el orden nuevo (la posición en el arreglo pasa a ser su order). No crea ni borra etapas ni mueve conversaciones. Requiere pipelines:write. |
| reorder_deal_stages | Cambia el orden de las etapas (columnas del tablero) de un pipeline de negocios. stageIds debe traer TODAS las etapas del pipeline, cada una una sola vez, en el orden deseado; si falta, sobra o se repite alguna responde 400 y no cambia nada. Obtén los ids con get_deal_pipeline. No mueve negocios. Requiere pipelines:write. |
| update_conversation_pipeline | Renombra un pipeline de conversaciones o lo marca como predeterminado (isDefault: true apaga el predeterminado anterior). NO edita etapas: para eso create_/update_/delete_conversation_stage y reorder_conversation_stages. isDefault solo acepta true (para quitarlo, marca otro). Requiere pipelines:write. |
| update_conversation_stage | Edita una etapa de un pipeline de conversaciones: solo cambia los campos enviados. webhookUrl null la quita; cambiar la URL o apagar el webhook limpia su último resultado. assignToTeam debe ser un empleado de la organización (null lo quita). Para cambiar el orden de varias usa reorder_conversation_stages. No mueve conversaciones. Requiere pipelines:write. |
| update_deal_pipeline | Renombra un pipeline de negocios o cambia sus requisitos por etapa: stageGateEnabled activa/desactiva los campos obligatorios y el checklist al mover negocios; stageGateCumulative exige también los requisitos de las etapas anteriores (apágalo en pipelines con etapas paralelas). NO edita etapas (usa update_deal_stage, create_deal_stage, reorder_deal_stages). Envía al menos un campo. Requiere pipelines:write. |
| update_deal_stage | Edita una etapa de un pipeline de negocios: nombre, color, ganada/perdida, probabilidad de cierre y días máximos. Parcial: solo cambia lo que envíes. null en isWon/isLost vuelve a 'automático por nombre'; null en probability o maxDaysInStage deja el campo sin configurar. La etapa debe pertenecer a ese pipeline. NO cambia el orden (usa reorder_deal_stages) ni mueve negocios. Requiere pipelines:write. |
Configuración: campos personalizados (11)
| create_company_property | Crea una propiedad nueva para las empresas. key: letra inicial y luego letras, dígitos o _ (máx. 64), única y no puede ser una clave núcleo (409). type default 'input'; un 'select' exige options. groupId opcional (default: el primer grupo); ver los grupos con list_company_properties. NO llena el valor en empresas existentes. Las propiedades NO se pueden borrar: solo ocultar con update_company_property isVisible=false. Requiere fields:write. |
| create_contact_property | Crea una propiedad nueva para los contactos. key: letra inicial y luego letras, dígitos o _ (máx. 64), única y no puede ser una clave núcleo (409). type default 'input'; un 'select' exige options. groupId opcional (default: el primer grupo); ver los grupos con list_contact_properties. NO llena el valor en contactos existentes. Las propiedades NO se pueden borrar: solo ocultar con update_contact_property isVisible=false. Requiere fields:write. |
| create_deal_field | Crea un campo personalizado en un pipeline de negocios. La clave (key) debe ser única dentro del pipeline (si ya existe responde 400 DUPLICATE_KEY). Un campo select necesita al menos una opción. requiredForStage (opcional) hace el campo obligatorio para LLEGAR a esa etapa, que debe ser del mismo pipeline; enforcement 'block' (default) impide mover el negocio y 'warn' solo avisa. required=true lo exige además al cerrar ganado. NO llena valores en negocios existentes. Requiere fields:write. |
| delete_deal_field | BORRA un campo personalizado de negocios Y TODOS los valores que ese campo tenga guardados en TODOS los negocios de la organización. Es IRREVERSIBLE: no hay papelera ni forma de recuperar los datos. Confirma con el usuario antes de llamarla. Devuelve cuántos valores se borraron (deletedValues). Requiere fields:write. |
| get_deal_field | Devuelve un campo personalizado de negocios por su _id (definición del campo, no sus valores). 404 si no existe o es de otra organización. Requiere fields:read. |
| list_company_properties | Lista las propiedades (campos) de las empresas de la organización con sus grupos: key, label, type, isVisible, options, groupId y reserved (true = propiedad núcleo del CRM como name, idNumber, email o phone). type: catalog (con catalog: co_municipalities | co_states) = cityCode y stateCode, códigos DANE del catálogo nacional: cityCode se escribe con un código de search_municipalities y stateCode se deriva solo; no se pueden crear propiedades catalog. Las keys son las que usan create_company/update_company. NO devuelve empresas. Requiere fields:read. |
| list_contact_properties | Lista las propiedades (campos) de los contactos de la organización con sus grupos: key, label, type, isVisible, options, groupId y reserved (true = propiedad núcleo del CRM como email, mobile, firstName o companyType). type: catalog (con catalog: co_municipalities | co_states) = cityCode y stateCode, códigos DANE del catálogo nacional: cityCode se escribe con un código de search_municipalities y stateCode se deriva solo; no se pueden crear propiedades catalog. Las keys son las que usan create_contact/update_contact y los filtros de list_contacts. NO devuelve contactos. Requiere fields:read. |
| list_deal_fields | Lista los campos personalizados de negocios (deals) de la organización: nombre, clave (key), tipo, opciones, si es obligatorio y si es requisito para llegar a una etapa (requiredForStage + enforcement block|warn). Con pipelineId devuelve solo los de ese pipeline; sin él, los de todos los pipelines. NO devuelve los valores que cada negocio tiene guardados (eso viene en get_deal). Requiere fields:read. |
| update_company_property | Actualiza una propiedad de empresa por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. La key NUNCA cambia. En propiedades núcleo (reserved) el type no se puede cambiar, pero sí label, visibilidad y opciones. No existe borrado de propiedades: ocultar es la única forma de retirarla, y los valores guardados en las empresas se conservan. Requiere fields:write. |
| update_contact_property | Actualiza una propiedad de contacto por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. La key NUNCA cambia. En propiedades núcleo (reserved) el type no se puede cambiar, pero sí label, visibilidad y opciones (ej. las de companyType). No existe borrado de propiedades: ocultar es la única forma de retirarla, y los valores guardados en los contactos se conservan. Requiere fields:write. |
| update_deal_field | Actualiza un campo personalizado de negocios. Solo cambia lo que se envía. Cambiar la key no migra los valores ya guardados (siguen ligados al campo por su _id, no por la key). Si se cambia pipelineId, la etapa requisito debe existir en el nuevo pipeline (o enviar requiredForStage: null). NO borra valores. Requiere fields:write. |
Plantillas de correo y documentos (8)
| create_email_template | Crea una plantilla de correo. Manda emailJson (preferido) o emailHtml. Si mandas emailJson, el servidor genera el HTML desde los bloques con el logo de la organización y emailHtml se IGNORA. Si solo mandas emailHtml, se guarda sanitizado (sin scripts ni javascript:, máx 500 KB) y el editor visual de la app no podrá mostrar sus bloques. Bloques admitidos (type): header {text}, text {content}, image {src, alt}, button {label, url}, divider, spacer {height}, logo, social {networks[]}, footer {text} (enlace de baja {{unsubscribe_url}}), twoCols {leftBlocks, rightBlocks}. Solo crea la plantilla: NO envía correos ni crea campañas. |
| delete_email_template | BORRA una plantilla de correo de la organización. Es IRREVERSIBLE: no hay papelera ni forma de recuperarla. Las campañas y automatizaciones que la referencian se quedan sin plantilla. No borra correos ya enviados. Confirma con el usuario antes de llamarla. |
| generate_document_template_pdf | Genera el PDF de una plantilla rellenado con los valores que mandes y lo devuelve en base64 (body.base64, contentType application/pdf; puede ser pesado). values es un mapa plano { "etapa.campo": valor } con las claves pdfFieldName de get_document_template; una clave que termina en "?" es opcional (si el PDF no tiene ese campo no falla). Si falta un campo requerido responde 400 con missingFields. No guarda nada en el CRM, no envía el documento a nadie ni lo adjunta al contacto. |
| get_document_template | Devuelve una plantilla PDF con sus etapas y campos. Cada campo trae pdfFieldName ("etapa.campo"), que es la clave que espera generate_document_template_pdf en values, además de label, type, si es requerido y valueTemplate (cómo se compone desde el contacto, p. ej. '{firstName} {lastName}'). Con includePdfFields=true también lista los campos reales del PDF (más lento: descarga el archivo). Solo lectura. |
| get_email_template | Devuelve una plantilla de correo completa: nombre, bloques del editor visual (emailJson) y el HTML final (emailHtml) que se envía. Úsalo antes de update_email_template para no perder bloques. Un id de otra organización o inexistente responde 404. No envía nada. |
| list_document_templates | Lista las plantillas de documentos PDF de la organización (PDF con campos de formulario que el CRM rellena: contratos, cartas, remisiones). Devuelve id, nombre, categoría, archivo, versión y cuántas etapas/campos tiene. Por defecto solo las activas. Solo lectura: no crea ni sube plantillas (eso se hace desde la app). |
| list_email_templates | Lista las plantillas de correo de la organización (las que se usan en campañas de email y en la acción 'Enviar correo' de las automatizaciones), más recientes primero. Devuelve id, nombre, si está guardada (saved), número de bloques y fechas; NO incluye el HTML ni los bloques (pídelos con get_email_template). No envía ningún correo. |
| update_email_template | Edita una plantilla de correo existente. Solo cambia lo que mandes: name, saved, y el contenido con emailJson (preferido; regenera el HTML y emailHtml se ignora) o emailHtml (se sanitiza y deja emailJson vacío, así que el editor visual ya no podrá editarla por bloques). emailJson REEMPLAZA todos los bloques: lee primero la plantilla con get_email_template. No envía correos. Las campañas o automatizaciones que usan esta plantilla enviarán la versión nueva. |
Configuración de la cuenta (17)
| create_scoring_rule | Crea una regla de puntuación de leads (queda activa salvo isActive=false). Condiciones de CONTACTO (propertyName obligatorio = key de una propiedad de contacto; consulta list_contact_properties): exists (sin value), equals / not_equals (texto, número o booleano), contains (texto), greater_than / less_than (número), in_list (arreglo). Condiciones de NEGOCIOS (propertyName no se usa): last_deal_older_than / last_deal_newer_than / purchase_frequency_less_than (días), deal_amount_greater_than / deal_amount_less_than / total_deals_amount_greater_than (monto), total_deals_count_greater_than (cantidad), deal_status_is (id de etapa), deal_pipeline_is (id de pipeline), has_purchased_product (id de producto), deal_field_value_is ({ fieldId, value }). points puede ser negativo. NO recalcula los puntajes existentes: para aplicarla a todos los contactos llama después recalculate_scoring_rules. Requiere settings:write. |
| delete_scoring_rule | ELIMINA una regla de puntuación de leads. Es IRREVERSIBLE. Los puntajes ya calculados de los contactos NO cambian hasta que se llame recalculate_scoring_rules. Si solo quieres dejar de aplicarla, usa update_scoring_rule con isActive=false. Confirma con el usuario antes de llamarlo. Requiere settings:write. |
| get_deal_checklist | Devuelve el checklist de cierre de un pipeline de negocios (items vacío e id null si aún no tiene). Un pipeline de otra organización o inexistente responde 404. Los ids de pipeline salen de list_deal_pipelines. |
| get_integration | Devuelve una integración por id (de list_integrations): servicio, nombre, si está activa, fechas y configuredKeys (solo los nombres de las credenciales guardadas). Nunca devuelve valores de credenciales ni settings. Un id de otra organización o inexistente responde 404. |
| get_invoice_config | Devuelve la configuración de FACTURACIÓN ELECTRÓNICA (DIAN) de la organización, en solo lectura: empresa emisora (razón social, NIT, DV, correo, teléfono, dirección, municipio y códigos DIAN), resolución de facturación (prefijo, número, rango desde/hasta, vigencia y siguiente consecutivo), numeración de notas crédito, notas débito y documento soporte, ambiente (environment: 'production' o 'test'), si el certificado digital está cargado y cuándo vence, correo SMTP de envío y buzón IMAP de recepción (host, puerto, usuario y si hay contraseña guardada) y los textos por defecto de la factura. NUNCA devuelve contraseñas, la clave técnica, el token del proveedor, el PIN del software ni el certificado: solo booleanos que dicen si existen (hasPassword, hasTechnicalKey, provider.hasToken, certificate.uploaded). Útil para saber qué prefijo y consecutivo saldrá en la próxima factura o si la resolución está por vencer. 404 si la organización no ha configurado facturación. No emite nada ni cambia la configuración (eso se hace desde la app). |
| get_mail_account | Devuelve una cuenta de correo IMAP por id (de list_mail_accounts): correo, proveedor, estado, último error, última sincronización y servidor IMAP (host, puerto, usuario, TLS). Nunca devuelve la contraseña. Un id de otra organización o inexistente responde 404. |
| get_organization_settings | Devuelve los datos de la EMPRESA dueña de la cuenta y sus ajustes generales: nombre, tipo y número de identificación (NIT), correo, teléfono, WhatsApp, dirección (con cityCode = código DANE del municipio, vacío si la ciudad no está confirmada), logo e icono; y en settings la zona horaria, idioma/región (locale BCP-47), moneda (ISO-4217), la ventana de envío de automatizaciones (automationSendWindow: días 0=domingo..6=sábado y franja HH:mm en que SÍ se escribe a clientes; null = sin restricción), si se exige 2FA, los textos por defecto de cotizaciones (condiciones de pago y envío, notas, pie, días de validez, prefijo del número, rótulo del impuesto) y de órdenes de compra, y el campo de negocios que alimenta el KPI de productos vendidos. NO incluye usuarios (list_users), plan, configuración de facturación electrónica, integraciones ni credenciales (tienen sus propias tools). Requiere settings:read. |
| get_scoring_rule | Devuelve una regla de puntuación de leads por id con todas sus condiciones. Un id de otra organización o inexistente responde 404. |
| get_scoring_rules_recalculation | Estado de un recálculo de puntajes lanzado con recalculate_scoring_rules: processing, completed o failed (con el error), más inicio, fin y duración. Los jobs viven en memoria del servidor: tras un reinicio o una hora después de terminar responden 404; en ese caso lanza otro si hace falta. |
| list_deal_checklists | Lista los checklists de cierre configurados en los pipelines de NEGOCIOS de la organización (uno por pipeline): cada ítem con nombre, peso (el % de avance de un negocio es sobre la suma de pesos), activityType (registrar una actividad de ese tipo en el negocio marca el ítem sola) y requiredForStage (etapa a la que el negocio no puede llegar sin el ítem marcado; enforcement block = impide, warn = solo avisa). Los pipelines sin checklist no aparecen. |
| list_integrations | Lista las integraciones que la organización tiene conectadas (WhatsApp, Messenger, Instagram, OpenAI, Claude, Brevo, n8n, Google Analytics…): id, servicio, nombre, si está activa, fechas y configuredKeys (solo los NOMBRES de las credenciales guardadas, p. ej. ['accessToken','phoneNumberId']). Solo lectura: NUNCA devuelve valores de credenciales ni settings. Sirve para responder '¿está conectado WhatsApp?' o diagnosticar por qué algo no envía (integración inactiva o sin la llave esperada). Conectar, desconectar o cambiar llaves se hace desde la app. |
| list_mail_accounts | Lista las cuentas de correo IMAP que la organización conectó a la bandeja compartida del CRM: id, correo, proveedor, estado (status 'active' | 'error' | 'inactive', isActive), último error de conexión y su código (lastError, lastErrorCode: auth_failed, oauth_required, timeout…), última verificación y última sincronización, si está sincronizando, y el servidor IMAP (host, puerto, usuario, TLS). Solo lectura: NUNCA devuelve la contraseña. Útil para diagnosticar por qué no llegan correos a la bandeja. No sirve para leer correos ni para el correo de facturación (ese es get_invoice_config). Crear, probar o sincronizar cuentas se hace desde la app. |
| list_scoring_rules | Lista las reglas de puntuación de leads (lead scoring) de la organización. Cada regla tiene un nombre, si está activa y sus condiciones: propertyName (propiedad del contacto), condition, value y points. El puntaje de un contacto (leadScore) es la suma de los points de TODAS las condiciones que cumple en las reglas activas. Úsalo antes de crear o editar reglas para no duplicarlas. |
| recalculate_scoring_rules | Recalcula en segundo plano el puntaje (leadScore) de TODOS los contactos de la organización con las reglas activas actuales, SOBRESCRIBIENDO el puntaje que tengan (si no hay reglas activas no cambia nada). Responde enseguida con un jobId; consulta el avance con get_scoring_rules_recalculation. Si ya hay un recálculo en curso devuelve ese mismo job (alreadyRunning=true) en vez de lanzar otro. Úsalo después de crear, editar o borrar reglas. Requiere settings:write. |
| update_deal_checklist | REEMPLAZA el checklist de cierre completo de un pipeline de negocios (lo crea si no existe). Envía TODOS los ítems en el orden deseado: los que no envíes se eliminan, y un items vacío deja el pipeline sin checklist. Para conservar un ítem existente (y las marcas que ya tienen los negocios) incluye su id; un ítem sin id es nuevo y ningún negocio lo tiene marcado. requiredForStage debe ser una etapa de ESE pipeline: con enforcement block los negocios no podrán pasar a esa etapa sin el ítem marcado (afecta a todo el equipo). Máximo 50 ítems. Requiere pipelines:write. |
| update_organization_settings | Edita los datos de la empresa y sus ajustes generales. PARCIAL: envía SOLO los campos a cambiar; lo que no mandes queda igual (null borra un campo opcional). Cambia lo que ven los clientes: nombre, NIT, dirección y los textos por defecto salen en las cotizaciones y órdenes de compra NUEVAS, y la moneda/idioma cambian el formato de dinero y fechas en toda la cuenta. automationSendWindow se reemplaza COMPLETA y con enabled=true retiene los envíos automáticos de WhatsApp/correo fuera de la franja (null la quita). Solo funciona si el token es del PROPIETARIO (si no, 403 OWNER_REQUIRED). Campos no editables por aquí (employees, plan, consecutivos de cotización/compra, logo, 2FA, credenciales) responden 400. Confirma los cambios con el usuario antes de llamarlo. Requiere settings:write. |
| update_scoring_rule | Edita una regla de puntuación de leads: nombre, descripción (null la borra), isActive (false la desactiva sin borrarla) o rules. OJO: rules REEMPLAZA todas las condiciones de la regla (envía la lista completa, no solo la nueva). Mismas condiciones y validación que create_scoring_rule. NO recalcula los puntajes existentes: llama después recalculate_scoring_rules. Envía al menos un campo. Requiere settings:write. |
Usuarios (2)
| get_user | Devuelve un usuario de la organización por id (nombre, correo, celular, rol, avatar, lastSeenAt). Un id de otra organización o inexistente responde 404. Solo lectura. Requiere users:read. |
| list_users | Lista los usuarios (miembros del equipo) de la organización: id, nombre, correo, celular, rol (propietario, empleado o lector = solo lectura), avatar y lastSeenAt (última actividad en la app; null si no hay sesión reciente). Úsalo para obtener el id de un asesor antes de asignarle negocios, conversaciones o tareas. Solo lectura: NO crea, invita, edita ni borra usuarios (eso cambia asientos cobrados y se hace desde la app). No devuelve contraseñas ni credenciales. Requiere users:read. |
Automatizaciones (borrador) (9)
| create_automation | Crea una automatización NUEVA que nace SIEMPRE en BORRADOR (status draft) con sus disparadores inactivos: NO se dispara, NO envía mensajes ni ejecuta nada. Un humano la revisa y la publica desde la web (Automatizaciones → Publicar); por MCP no se puede publicar, activar, pausar ni probar. Valida el grafo y responde 400 con la lista de errores si no es válido (usa validate_automation y list_automation_variables antes). Efecto externo: ninguno hasta que alguien la publique. Requiere automations:write. |
| get_automation | Devuelve una automatización por id con lo PUBLICADO (pasos y disparadores reales, lo que corre hoy) y el BORRADOR (pasos y disparadores pendientes de publicar), más la última ejecución. Los secretos de las acciones http_request (headers de autorización, apiKey, tokens) salen como ***REDACTED***: si editas el borrador con update_automation puedes dejar ese marcador tal cual y se conserva el valor guardado. Un id de otra organización o inexistente responde 404. Solo lectura. |
| get_automation_trigger | Devuelve un disparador real por id: tipo, activo, filtros, programación, estadísticas y su automatización. Un id de otra organización o inexistente responde 404. Solo lectura. |
| list_automation_runs | Historial paginado de ejecuciones de una automatización (más recientes primero): estado de la ejecución, fechas, conversación/contacto y, por paso, tipo, estado, duración, intentos y error. NO incluye los datos de entrada/salida de los pasos ni el evento que la disparó (traen datos del cliente y secretos). No reintenta ni ejecuta nada. Los registros se conservan ~30 días. |
| list_automation_triggers | Lista los disparadores REALES (publicados) de las automatizaciones de la organización: tipo, si está activo, filtros, programación (cron) y estadísticas de ejecución, con el nombre y estado de su automatización. Filtros opcionales por automatización, tipo y activo. Solo lectura: por MCP no se crean, editan ni activan disparadores (los de un borrador se editan con update_automation). |
| list_automation_variables | Catálogo para armar automatizaciones: las variables {{...}} disponibles según los tipos de disparador dados (con ejemplo), los tipos de disparador válidos y los tipos de acción que existen (send_whatsapp, send_whatsapp_template, send_email, create_task, http_request…). Úsalo antes de create_automation. Solo lectura. |
| list_automations | Lista las automatizaciones de la organización: nombre, estado (draft = borrador nunca publicado, published = activa, paused = pausada), si tiene cambios sin publicar, sus disparadores reales (tipo y si están activos), los disparadores del borrador, la última ejecución y cuántas ejecuciones hubo por estado en los últimos ~30 días. Solo lectura. Para ver los pasos usa get_automation. |
| update_automation | Edita SOLO el BORRADOR de una automatización (nombre, disparadores y/o pasos; lo que no envíes se conserva del borrador actual o, si no hay, de lo publicado). Si la automatización está publicada, lo que corre hoy NO cambia ni su estado: los cambios quedan pendientes hasta que un humano pulse Publicar en la web. Por MCP no se publica, pausa, activa ni prueba. Los disparadores del borrador quedan inactivos. Un ***REDACTED*** de get_automation conserva el secreto guardado en ese mismo paso. Valida el grafo (400 con errores). reEntry solo se acepta en automatizaciones nunca publicadas. Requiere automations:write. |
| validate_automation | Valida un grafo de automatización (nombre, disparadores y pasos) SIN guardarlo: devuelve valid, errors (bloquean) y warnings. Usa las mismas reglas que create_automation y que el botón Publicar de la web (tipos de acción, variables disponibles para los disparadores, cron, filtros que el disparador sí guarda). No crea ni ejecuta nada. |
Campañas (4)
| create_campaign_draft | Crea el BORRADOR de una campaña masiva de WhatsApp (la de /mass-whatsapp): nombre, lista de contactos de la organización y plantilla aprobada en Meta (nombre, idioma, de dónde sale cada variable {{n}} y la imagen/video de cabecera si la plantilla la lleva). Queda en estado 'planeacion' y SIN fecha de envío: NO envía ningún mensaje, NO la programa y NO la ejecuta — eso lo hace una persona desde la app con 'Enviar ahora'. No verifica que la plantilla exista en Meta (un nombre mal escrito falla recién al ejecutar): toma el nombre de list_whatsapp_templates. Después usa get_campaign_preflight para ver a cuántos les llegaría. Requiere automations:write. |
| get_campaign_preflight | Antes de enviar: calcula, con la lista TAL COMO ESTÁ HOY, a cuántos contactos les llegaría la campaña de WhatsApp y cuántos quedan fuera por motivo (no_mobile = sin celular, landline = número fijo, invalid_phone = formato inválido, opted_out = pidió no ser contactado, whatsapp_invalid = marcado sin WhatsApp), con hasta 10 ejemplos (teléfono enmascarado). Es la misma selección que usa el envío real. Solo lectura: NO envía nada ni modifica contactos. Responde 409 si la campaña no tiene lista o la lista ya no existe. Requiere contacts:read. |
| get_campaign_stats | Resultados reales de una campaña masiva de WhatsApp: embudo (listSize → attempted → accepted → delivered → read → replied, y failed/skipped), tasas (entrega, lectura, respuesta, fallo), motivos de fallo agrupados (número sin WhatsApp, plantilla pausada…), tabla por contacto con estado, fechas y conversación del CRM, y las últimas ejecuciones de la cola. 'accepted' es lo que Meta aceptó (HTTP 200); delivered/read llegan por webhook de Meta; 'replied' = contacto con mensaje entrante después del envío; 'skipped' = lo que la lista dejaría fuera hoy (sin celular, fijo, opt-out, marcado sin WhatsApp). Úsalo para responder '¿se enviaron todos?', '¿cuántos respondieron?' o '¿a quién no le llegó y por qué?' sin mirar la base de datos. |
| update_campaign_draft | Edita el borrador de una campaña masiva de WhatsApp: nombre, descripción, lista de contactos o la plantilla (nombre, idioma, variables, cabecera). Solo funciona si la campaña NUNCA se ha ejecutado y no se está ejecutando: si ya salió, está en cola o su fecha programada ya llegó responde 409 y no cambia nada (crea otra con create_campaign_draft). NO envía, NO programa y NO cambia el estado ni la fecha de la campaña. En template solo se cambian los campos enviados; variableMapping o headerMediaUrl en null los quitan. Requiere automations:write. |
Landing pages (8)
| create_landing_page | Crea una landing page nueva SIEMPRE EN BORRADOR (no queda pública hasta publish_landing_page). Parte de una plantilla (templateId; default blank) o de las secciones que envíes (sections reemplaza las de la plantilla). El slug se genera del nombre y es único global; se cambia después con update_landing_page. Si la página tiene bloque form, asocia formId (un formulario de la organización, de list_forms). Las imágenes van como URLs https (no se suben archivos). Cuenta contra el límite de páginas del plan: si está lleno responde 403 PLAN_LIMIT_REACHED. Requiere landings:write. |
| duplicate_landing_page | Duplica una landing page: crea una copia EN BORRADOR con '(copia)' en el nombre, slug nuevo terminado en -copia, las mismas secciones y configuración, y estadísticas en cero. La original no cambia. Cuenta contra el límite de páginas del plan (403 PLAN_LIMIT_REACHED si está lleno). Requiere landings:write. |
| get_landing_page_stats | Estadísticas de una landing page en un rango de fechas (default: últimos 30 días): totales (vistas, únicos, sesiones, clics CTA, clics WhatsApp, envíos, negocios ganados y monto, tiempo medio), las dos conversiones (envíos/únicos y WhatsApp/únicos), serie diaria, embudo por sesión (vista → scroll 50% → interacción → conversión → ganado), top utm_source, clics por sección, dispositivo y últimas respuestas. |
| list_landing_page_templates | Lista las plantillas de landing page (captacion, cotizacion, evento, descarga, blank) con su objetivo (whatsapp/form), si necesitan un formulario y qué secciones traen, más el catálogo de tipos de sección (hero, benefits, text, gallery, testimonials, faq, whatsapp_cta, form) con un ejemplo del objeto data de cada uno y las claves de ícono permitidas. Úsalo ANTES de create_landing_page o update_landing_page para armar secciones válidas. Solo lectura. |
| list_landing_pages | Listar las landing pages (páginas de aterrizaje) de la organización: nombre, slug, estado (draft/published), plantilla, URL pública, formulario asociado y stats acumuladas (vistas, únicos, envíos, clics a WhatsApp). Cada página se sirve en forms.fusioncol.com/p/<slug> y convierte a WhatsApp o a un formulario de captación. |
| publish_landing_page | PUBLICA una landing page: queda PÚBLICA EN INTERNET en su publicUrl (forms.fusioncol.com/p/<slug>) para cualquiera que tenga el enlace, indexable por buscadores salvo seo.indexable=false, y empieza a recibir visitas y leads reales. Confirma con el usuario antes de publicar. Exige al menos una sección activa y, si hay bloque form, un formulario activo de la organización; si no cumple responde 400 con el motivo. Requiere landings:write. |
| unpublish_landing_page | Despublica una landing page: vuelve a borrador y su URL pública deja de responder (404) de inmediato; los anuncios, QR o enlaces que apunten a ella quedan rotos hasta volver a publicarla. No borra la página, sus secciones ni sus estadísticas. Requiere landings:write. |
| update_landing_page | Edita una landing page: nombre, slug (cambia la URL pública; 409 si ya existe), formulario, secciones, tema, SEO, tracking (Pixel de Meta / GA4 por ID) y WhatsApp. Solo cambia lo que envías; sections REEMPLAZA la lista completa (lee antes el detalle con fusion_request GET /landing-pages/{id} para conservar las demás). OJO: si la página está PUBLICADA los cambios salen EN VIVO en internet de inmediato (no hace falta republicar); si el cambio la deja sin poder publicarse (sin secciones activas o con bloque form sin formulario activo) vuelve a borrador y la respuesta trae unpublished: true con el motivo. No cambia el estado: usa publish_landing_page / unpublish_landing_page. Requiere landings:write. |
Actividades (5)
| create_activity | Registra un seguimiento en la ficha de un contacto y/o empresa (debe venir al menos contactId o companyId, de esta organización). Queda a nombre del dueño del token. Si pones 'reminder' (fecha-hora), el asesor (advisorId) o, sin asesor, quien lo registró recibe un aviso por push y correo a esa hora. Aparece también en el calendario como seguimiento. No envía nada al cliente. Requiere activities:write. |
| delete_activity | ELIMINA un seguimiento de la ficha del contacto/empresa. Es IRREVERSIBLE (no hay papelera); si era el recordatorio ligado a un paso de un negocio, el negocio conserva su paso pero sin recordatorio. Confirma con el usuario antes de llamarlo. Requiere activities:write. |
| get_activity | Devuelve un seguimiento (actividad de contacto/empresa) por id: tipo, título, fecha, notas, estado, recordatorio, contacto, empresa, asesor y quién lo registró. Un id de otra organización o inexistente responde 404. |
| list_activities | Lista los SEGUIMIENTOS (actividades) de contactos y empresas: llamadas, reuniones, correos, notas y WhatsApp registrados en la ficha, con su fecha, estado (incomplete = pendiente, complete = hecho), recordatorio y asesor. Filtra por contacto, empresa, asesor, estado, tipo o rango de fechas (sobre 'date'). Más recientes primero, paginado. NO lista las actividades del timeline de un NEGOCIO (esas son de list_deals/get_deal) ni las citas del calendario (list_calendar_events). |
| update_activity | Edita un seguimiento; parcial: solo cambia lo que envíes. status 'complete' lo marca hecho (y cierra el próximo paso del negocio ligado, si lo hay); 'incomplete' lo reabre. reminder null quita el recordatorio; cambiarlo re-arma el aviso. contactId/companyId/advisorId null desvincula (siempre debe quedar contacto o empresa). Requiere activities:write. |
Calendario (5)
| create_calendar_event | Crea un evento en el calendario de la organización. Para repetirlo usa recurrenceRule (RRULE RFC 5545 sin prefijo, p. ej. 'FREQ=WEEKLY;BYDAY=TU'; mínimo diario). ⚠️ CORREOS: si hay attendees y sendInvitations es true, cada invitado RECIBE UN CORREO con la invitación (.ics) enviado a nombre de la organización; por defecto (false) NO se envía nada. Pide confirmación al usuario antes de poner sendInvitations: true. reminderEnabled avisa al asesor (push y correo interno) antes del inicio. Requiere calendar:write. |
| delete_calendar_event | ELIMINA un evento del calendario; IRREVERSIBLE. Si es recurrente y NO pasas occurrenceStart se borra la SERIE ENTERA con todas sus ocurrencias (incluidas las editadas aparte); con occurrenceStart se quita solo esa fecha. ⚠️ Con sendCancellations true los invitados que ya recibieron la invitación reciben un CORREO de cancelación; por defecto no se envía nada. Confirma con el usuario antes de llamarlo. No sirve para ids 'activity:' (usa delete_activity). Requiere calendar:write. |
| get_calendar_event | Devuelve un evento del calendario por id (la serie completa si es recurrente: recurrenceRule, excludedDates) con invitados, contacto, asesor y recordatorios. Acepta también 'activity:<id>' para ver un seguimiento como evento. Un id de otra organización o inexistente responde 404. |
| list_calendar_events | Lista los eventos del calendario (citas, reuniones, tareas, recordatorios) entre 'from' y 'to' (ambos obligatorios, ISO 8601; rango máximo ~3 años, se recorta si es mayor). Las series recurrentes salen expandidas: cada ocurrencia trae el id de la serie y 'occurrenceStart' (úsalo para editar o borrar SOLO esa ocurrencia). Con includeActivities=true suma los seguimientos de contactos como eventos de solo lectura (id 'activity:<id>'). Filtros: asesor (assignedTo, con includeCreatedBy para sumar los que creó) y contacto. Paginado, en orden cronológico. |
| update_calendar_event | Edita un evento del calendario; parcial. Sin occurrenceStart edita la SERIE completa si es recurrente; con occurrenceStart (el valor que trae list_calendar_events) edita SOLO esa ocurrencia, que se separa de la serie como evento propio. null en assignedTo/contactId los quita; recurrenceRule '' o null deja de repetir. ⚠️ CORREOS: con sendInvitations true los invitados reciben la invitación actualizada y los que quitaste reciben una cancelación; por defecto (false) no se envía nada. Confirma con el usuario antes de enviar. No sirve para ids 'activity:' (usa update_activity). Requiere calendar:write. |
Notificaciones (2)
| list_notifications | Listar las notificaciones de la campana (menciones, asignaciones, recordatorios, formularios recibidos, cotización vista, errores de automatización, avisos de cuenta) del USUARIO a cuyo nombre está emitido el token — no las de otros empleados. Más recientes primero. Devuelve data[] (id, type, title, body, targetUrl, entity, actor, createdAt, readAt: null = sin leer), unreadCount y pagination. Solo lectura: NO las marca como leídas (para eso mark_notifications_read). |
| mark_notifications_read | Marcar como leídas notificaciones del usuario del token. Pasa ids (lista de ids de list_notifications, máximo 50) O all: true para marcar TODAS las no leídas. Solo cambia el estado leído (baja el contador de la campana en la web y la app); no borra nada. Una ya leída cuenta como éxito (alreadyRead); un id inexistente o de otro usuario responde 404 en su fila. |
Importar y exportar (6)
| create_export | Pedir una copia COMPLETA de los datos de la organización (ZIP con CSV de contactos, empresas, negocios, conversaciones y mensajes, cotizaciones, facturas, tareas, eventos y usuarios, más organization.json sin secretos). Solo funciona si el token es del PROPIETARIO (si no, 403 OWNER_REQUIRED). Se genera en segundo plano (202): el enlace llega por correo al propietario y se consulta con get_export. Si ya hay uno en curso no arranca otro (alreadyRunning: true). No borra ni modifica datos. Es pesado: no la llames en bucle; para leer registros sueltos usa las tools de cada módulo. |
| get_export | Consultar el ÚLTIMO export de datos de la organización (solo el propietario). status: none (no hay o ya caducó el ZIP), queued (generándose) o ready, con downloadUrl = URL firmada que caduca en 24 h (expiresAt) y createdAt del ZIP. Cada llamada firma una URL nueva; no existe URL pública permanente. El enlace da acceso a TODOS los datos: no lo compartas fuera de la conversación con el propietario. No genera un export nuevo (para eso create_export). |
| get_import | Devuelve una importación del historial por id con sus conteos, avisos y las filas rechazadas (rejectedRows: número de fila, motivo y valores) para corregirlas y reimportarlas. Un id de otra organización o inexistente responde 404. Solo lectura. |
| import_contacts | Importa contactos EN LOTE (hasta 500 filas por llamada) con el mismo motor que el importador de Excel de la app. Cada fila es un objeto { clave: valor } con las claves de las propiedades de contacto de la organización (firstName, lastName, email, mobile, phone, company, city… — consulta las claves reales con la configuración de propiedades de contacto) más 'owner' (correo del asesor dueño). DEDUPE: busca cada fila contra los contactos existentes por email (sin distinguir mayúsculas), luego mobile y luego phone (comparando los últimos 10 dígitos, con o sin indicativo), y también fusiona filas repetidas dentro del mismo lote. Si el contacto YA EXISTE lo ACTUALIZA: escribe los valores no vacíos de la fila (una celda vacía nunca borra un dato existente) y conserva lo demás; si no existe lo crea. Una clave que no es propiedad de contacto de la organización RECHAZA esa fila (sale en rejectedRows, no se inventan propiedades). CIUDADES (catálogo DANE, modo estricto): 'cityCode' (código DANE de 5 dígitos, ej. 05001 = Medellín) es la fuente de verdad y deriva city/state/stateCode; si solo mandas 'city' debe ser un nombre inequívoco del catálogo (opcionalmente con 'state' para desambiguar). Una fila con ciudad ambigua (ej. 'Rionegro': Antioquia 05615 o Santander 68615), mal escrita o desconocida se RECHAZA y sale en rejectedRows con los candidatos (sin candidatos = mal escrita o desconocida: busca el código con search_municipalities): corrige con cityCode y reimporta. stateCode siempre se deriva del cityCode. Nunca se asigna un 'mejor candidato'. Un contacto de otro país (country distinto de Colombia) guarda city como texto libre. 'company' enlaza la empresa por nombre; con createMissingCompanies=true crea las que falten. Respeta el límite de contactos del plan (los que no caben salen en skippedByPlan). NO dispara automatizaciones ni eventos de 'contacto creado', NO envía mensajes ni correos. RECOMENDADO: llama primero con dryRun=true para ver cuántos crearía, actualizaría y rechazaría sin escribir nada, y confirma con el usuario antes de importar de verdad. La importación real queda en el historial (list_imports) y se puede deshacer con import_rollback. |
| import_rollback | REVIERTE una importación (contactos, empresas o negocios): BORRA los registros que esa importación creó y RESTAURA los que actualizó a como estaban antes (en contactos también borra las empresas creadas por createMissingCompanies y restaura el enlace a empresa). Los cambios hechos a mano después sobre esos registros se pierden. Solo puede el propietario de la organización o quien hizo la importación (si no, 403). Una importación ya revertida responde 409. No emite eventos ni envía nada. Confirma con el usuario antes de llamarlo. |
| list_imports | Lista el historial de importaciones de la organización (más reciente primero): tipo (contacts, companies, deals, purchases), origen (web = Excel de la app, api = JSON por API/MCP), estado (completed, failed, rolled_back), autor y conteos (importados, actualizados, rechazados, omitidos por plan, empresas creadas). No incluye el detalle de filas rechazadas (usa get_import). Solo lectura. |
Compras (5)
| create_purchase | Registrar una compra a un proveedor (POST /api/purchases). Requiere purchases:write. kind=invoice (default): factura del proveedor. Obligatorios: orderNumber (el número de la factura del proveedor), total y paymentMethod. Si cubre una OC abierta, manda orderRef con el id de la OC (ver list_open_purchase_orders): la OC queda completed con invoiceRef. 409 si la OC ya está facturada o cancelada. kind=order: orden de compra. El servidor genera el consecutivo OC-n (settings.purchases.numberPrefix + contador): NO mandes orderNumber, se ignora. Obligatorios: total y paymentMethod. Sus retenciones son estimadas y no entran en informes ni certificados. El proveedor puede ir como supplierId (contacto de la MISMA organización) o simplemente como supplierName + supplierNIT. En cada ítem, si no se envía total se calcula como quantity × unitPrice, y tax vacío se toma como 0. |
| get_purchase | Obtener una orden de compra por ID, con el proveedor resuelto. Requiere purchases:read. |
| list_open_purchase_orders | Órdenes de compra ABIERTAS (kind=order, pending, sin factura enlazada) de un proveedor (GET /api/purchases/open-orders). Sirve para conciliar: antes de registrar la factura de un proveedor, buscar si ya había una OC que la cubra y pasar su id como orderRef en create_purchase. El NIT se compara normalizado (900.480.268-1 = 900480268). Con total, la lista viene ordenada por cercanía y cada OC trae totalDiff (|OC.total − total|). Sin nit devuelve todas las abiertas. Requiere purchases:read. |
| list_purchases | Listar compras a proveedores (GET /api/purchases): facturas de proveedor (kind=invoice) y órdenes de compra (kind=order). Sin `kind` devuelve ambas. Filtros por estado, proveedor, texto y rango de fechas de compra. Requiere purchases:read y el módulo de facturación en el plan. Cada compra trae kind, orderRef (en la factura: la OC que cubre) e invoiceRef (en la OC: la factura que la cerró). En una OC, status pending = abierta y completed = facturada. |
| update_purchase | Actualizar una compra por ID (PUT /api/purchases/:id). Solo se tocan los campos enviados; enviar items los REEMPLAZA. supplierId vacío o null desvincula al proveedor. kind no se cambia después de crear (400 si difiere); orderNumber de una OC lo asigna el servidor (400 si difiere); cancelar una OC ya facturada responde 409. Requiere purchases:write. |
Agentes IA (7)
| create_agent | Crear un agente IA. Nace en status draft (no responde a nadie hasta activarlo con set_agent_status). name y systemPrompt son obligatorios (systemPrompt ≤ 30000 caracteres); el resto toma valores por defecto (openai / gpt-4o-mini, temperature 0.4, maxTokens 400). Confirma el contenido del prompt con el usuario antes de crearlo. |
| delete_agent | Borrar un agente IA (no se puede deshacer). Las conversaciones que tenía asignadas quedan sin agente. Confirma con el usuario antes de llamarlo. |
| get_agent | Detalle completo de un agente IA: systemPrompt, modelo, temperatura, tools habilitadas, palabras de traspaso a humano, triggers, contextSettings y esquema de lead. Úsalo antes de update_agent: triggers, contextSettings y leadStateSchema se reemplazan completos al actualizar. |
| list_agent_tools | Catálogo de tools que un agente IA puede tener habilitadas (valores válidos para enabledTools en create_agent / update_agent). |
| list_agents | Listar los agentes IA de WhatsApp/Messenger de la organización (nombre, estado draft/active/paused, proveedor y modelo, estadísticas de uso) y los límites del plan. Sin paginación. |
| set_agent_status | Activar (active), pausar (paused) o volver a borrador (draft) un agente IA. Activar hace que empiece a responder a clientes reales según sus triggers: confírmalo con el usuario. |
| update_agent | Actualizar un agente IA (solo cambian las claves enviadas). OJO: triggers, contextSettings y leadStateSchema se reemplazan COMPLETOS — lee el agente con get_agent y envía la lista entera. Si el agente está activo, el cambio afecta de inmediato las respuestas a clientes: confirma con el usuario. |
Reportes (3)
| get_purchases_overview | Resumen de compras de un período: total bruto, retenciones practicadas, neto pagado y desglose. Requiere el módulo de facturación electrónica del plan y el permiso purchases:read. |
| get_sales_forecast | Previsión ponderada de ventas: suma de (monto × probabilidad de la etapa) de los negocios abiertos con cierre esperado en el rango, por mes, por etapa y por vendedor. Avisa aparte lo que no entra al número: etapas sin probabilidad configurada, abiertos sin fecha de cierre y abiertos con la fecha vencida. |
| get_sales_overview | Resumen de ventas de un período (los mismos números de la pantalla Informes): total vendido, negocios ganados, ticket promedio, comparación con el mismo rango del año anterior, serie de 13 meses y mejores clientes. Fechas YYYY-MM-DD en la zona horaria de la organización. |
General (1)
| fusion_request | Petición cruda a rutas FusionCRM bajo /api (quotations, tasks, products, etc.). |
Otros (4)
| create_shipment | Generar un envío (guía) en Envia.com (POST /api/shipments). 🚨 COBRA SALDO REAL de la cuenta de Envia de la organización en esta misma llamada: confirma con el usuario la transportadora, el servicio y el precio antes de llamarla. Requiere shipments:write y el módulo envios. Primero cotiza con quote_shipment y pasa aquí el carrier y el service de la tarifa elegida, con los MISMOS paquetes y destino. Pasa SIEMPRE idempotencyKey, una por INTENTO de envío y no por pedido (ej. "pedido-4521-1"; 1-100 caracteres, única por organización). Si reintentas con la misma clave y el envío ya tiene guía, responde 200 con reused:true sin volver a cobrar: en ese caso el body se IGNORA (no se aplican cambios de destino, paquetes ni transportadora) y hay que revisar data.status, que puede ser cualquier estado, incluso canceled. Para generar otra guía del mismo pedido (p. ej. tras cancelar la anterior) usa una clave NUEVA ("pedido-4521-2"). Si el intento anterior falló (draft), la misma clave reintenta generar sobre el mismo envío (201 reused:false, o 422 si falla otra vez). Mientras el generate de esa clave sigue en curso (candado de hasta 120 s), la misma clave responde 409 SHIPMENT_IN_PROGRESS sin volver a cobrar: espera unos segundos y reintenta. La misma clave con OTRO contactId → 400 IDEMPOTENCY_KEY_REUSED. NO reintentes en bucle; consulta con list_contact_shipments. 🚨 409 SHIPMENT_NEEDS_REVIEW (con shipmentId): Envia no respondió a tiempo, falló la red o respondió sin guía, así que NO sabemos si generó (y cobró) la guía. Ese envío NUNCA se regenera solo, ni con la misma clave. Dile al usuario que revise su panel de Envia; solo si allí no aparece la guía, reintenta con una clave NUEVA. El destino sale del contacto y destination lo pisa (ciudad ambigua o desconocida → 422 INVALID_CITY con candidates; el cityCode se obtiene con search_municipalities). dealId/quotationId/invoiceId y presetId deben ser de la organización (400 VALIDATION_ERROR con field si no). 201 = guía generada: data.status created con trackingNumber, trackUrl, labelUrl (etiqueta PDF/ZPL), pricing.total y balanceAfter. Si Envia la rechaza (saldo insuficiente, transportadora caída…) → 422 ENVIA_GENERATE_FAILED con error y shipmentId del borrador (queda en draft; se puede reintentar con la misma clave). Si error.code es CARRIER_ERROR, pide al usuario revisar su panel de Envia antes de reintentar: la transportadora pudo haber creado la guía. Estados posibles de un envío: draft (falló la generación o está en revisión si generateOutcome=unknown), created (guía generada; único cancelable — cancelar se hace desde la app: la API pública expone POST /api/shipments/{id}/cancel, pero este MCP no tiene tool de cancelar hasta verificar con una guía real cómo responde Envia y si devuelve el saldo), picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception. Terminales: delivered, returned, lost, canceled. |
| list_contact_shipments | Listar los envíos de UN contacto (GET /api/shipments?contactId=…), más recientes primero, con el último estado guardado (no consulta a Envia; para refrescar usa track_shipment). Requiere shipments:read y el módulo envios. Cada envío trae id, carrier, service, trackingNumber, trackUrl, labelUrl, status, events, destination, packages y pricing. Paginación: limit 20 por defecto y máximo 100 (un valor mayor da error de validación); la respuesta trae pagination { total, page, limit, pages }. status filtra por estado: draft, created, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception. |
| quote_shipment | Cotizar un envío con Envia.com (POST /api/shipments/rates). Es una CONSULTA: no crea nada ni cobra saldo. Requiere shipments:read y el módulo envios en el plan. Destino: manda contactId (se usa la dirección del contacto) o destination; con ambos, lo que venga en destination pisa lo del contacto. Sin ninguno → 400. La ciudad se resuelve contra el catálogo DANE: usa cityCode (DANE de 5 dígitos) o city + state inequívocos; si es ambigua o no se reconoce → 422 INVALID_CITY con candidates (pregúntale al usuario cuál es y reintenta con su cityCode; si no hay candidatos, búscalo con search_municipalities). Ojo: este código es INVALID_CITY, no el CITY_UNRESOLVED de contactos. Solo Colombia. El origen es el de Ajustes → Envíos de la app (no se manda; 422 ORIGIN_NOT_CONFIGURED si falta). Sin la API key de Envia → 422 ENVIA_NOT_CONFIGURED. Respuesta: data.rates ordenadas por totalPrice ascendente (COP), cada una con carrier + service (lo que pide create_shipment), deliveryEstimate y desglose; data.failed = transportadoras que no respondieron (no tumban la cotización). Consulta cada transportadora con timeout de 12 s: puede tardar. |
| track_shipment | Refrescar el rastreo de un envío consultando a Envia.com (POST /api/shipments/:id/track): agrega los eventos nuevos sin duplicar y actualiza status/statusRaw. Devuelve el envío actualizado (events con at, status, raw, description, location). No cobra. Requiere shipments:write y el módulo envios. Un envío sin guía (draft) → 409 SHIPMENT_NOT_TRACKABLE; inexistente o de otra organización → 404; Envia caído → 502 ENVIA_TRACK_FAILED. Para ver el último estado guardado sin consultar a Envia usa list_contact_shipments. Estados: draft, created, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception. Terminales: delivered, returned, lost, canceled. |