{
  "schemaVersion": "1.0.0",
  "endpoint": "/api/mcp",
  "transport": "streamable-http",
  "source": "src/mcp/createFusionMcpServer.ts",
  "sources": [
    "src/mcp/createFusionMcpServer.ts",
    "src/mcp/tools/activities.ts",
    "src/mcp/tools/automations.ts",
    "src/mcp/tools/calendar-events.ts",
    "src/mcp/tools/campaigns.ts",
    "src/mcp/tools/catalogs.ts",
    "src/mcp/tools/company-properties.ts",
    "src/mcp/tools/contact-properties.ts",
    "src/mcp/tools/conversation-pipelines.ts",
    "src/mcp/tools/deal-checklists.ts",
    "src/mcp/tools/deal-fields.ts",
    "src/mcp/tools/deal-pipelines.ts",
    "src/mcp/tools/document-templates.ts",
    "src/mcp/tools/email-templates.ts",
    "src/mcp/tools/exports.ts",
    "src/mcp/tools/import.ts",
    "src/mcp/tools/integrations.ts",
    "src/mcp/tools/invoice-config.ts",
    "src/mcp/tools/landing-pages.ts",
    "src/mcp/tools/mail-accounts.ts",
    "src/mcp/tools/me.ts",
    "src/mcp/tools/notifications.ts",
    "src/mcp/tools/organization.ts",
    "src/mcp/tools/product-variants.ts",
    "src/mcp/tools/products.ts",
    "src/mcp/tools/scoring-rules.ts",
    "src/mcp/tools/shipments.ts",
    "src/mcp/tools/users.ts"
  ],
  "toolCount": 225,
  "tools": [
    {
      "name": "add_contacts_to_list",
      "description": "Agregar contactos a una lista ESTÁTICA existente por sus IDs. Solo funciona en listas de tipo static.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista"
          },
          "contactIds": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Array de IDs de contactos a agregar"
          }
        },
        "required": [
          "id",
          "contactIds"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "add_conversation_message",
      "description": "Añadir mensaje a una conversación (POST .../messages).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de conversación"
          },
          "payload": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "Cuerpo del mensaje"
          }
        },
        "required": [
          "id",
          "payload"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "apply_invoice_withholdings",
      "description": "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.\nEs 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.\nCada í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.\nConceptos 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).\nREEMPLAZA 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.\nSolo facturas de venta (direction outgoing, tipo factura): notas crédito/débito y facturas de proveedor responden 422 NOT_A_SALES_INVOICE.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "description": "_id de la factura (preferido)",
            "type": "string"
          },
          "prefix": {
            "description": "Prefijo de la factura, ej. FS (si no tienes el id)",
            "type": "string"
          },
          "number": {
            "description": "Número de la factura (si no tienes el id)",
            "type": "number"
          },
          "items": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "compras25",
                    "compras35",
                    "serviciosGeneral4",
                    "serviciosGeneral6",
                    "honorarios10",
                    "honorarios11",
                    "arrendamientoBienesMuebles",
                    "arrendamientoBienesRaices",
                    "serviciosAseoVigilancia",
                    "serviciosHotelesRestaurantes",
                    "transporteCarga",
                    "reteIva",
                    "reteIca"
                  ],
                  "description": "Concepto de la retención"
                },
                "amount": {
                  "description": "Monto retenido en COP (manda si lo sabes)",
                  "type": "number"
                },
                "pct": {
                  "description": "Porcentaje sobre la base (sobre el IVA si es reteIva)",
                  "type": "number"
                }
              },
              "required": [
                "key"
              ]
            },
            "description": "Retenciones a registrar; reemplazan las que hubiera"
          },
          "dryRun": {
            "description": "true = calcular sin guardar",
            "type": "boolean"
          }
        },
        "required": [
          "items"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "change_conversation_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ObjectId de la conversación"
          },
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline destino"
          },
          "initialStageId": {
            "description": "ObjectId del stage inicial en el nuevo pipeline. Opcional; default = primer stage por order.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "pipelineId"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "conversations_summary",
      "description": "Summary of conversations in a date range: counts and by source (sample up to 1000 per page).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "dateFrom": {
            "type": "string",
            "description": "dd/mm/yyyy"
          },
          "dateTo": {
            "type": "string",
            "description": "dd/mm/yyyy"
          },
          "dateField": {
            "description": "Date field to filter on (default: createdAt)",
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "lastMessageTimestamp"
            ]
          },
          "timezone": {
            "description": "UTC offset, default -5 (Colombia)",
            "type": "number"
          }
        },
        "required": [
          "dateFrom",
          "dateTo"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "create_activity",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "activityType": {
            "type": "string",
            "enum": [
              "Reunion",
              "Llamada",
              "Correo",
              "Nota",
              "Whatsapp"
            ],
            "description": "Tipo de seguimiento"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Título corto, p. ej. 'Llamar para confirmar pedido'"
          },
          "date": {
            "type": "string",
            "description": "Fecha: YYYY-MM-DD (todo el día) o fecha-hora ISO 8601"
          },
          "notes": {
            "description": "Notas libres",
            "type": "string",
            "maxLength": 10000
          },
          "status": {
            "description": "Default incomplete (pendiente)",
            "type": "string",
            "enum": [
              "incomplete",
              "complete"
            ]
          },
          "contactId": {
            "description": "Contacto de la ficha",
            "type": "string"
          },
          "companyId": {
            "description": "Empresa de la ficha",
            "type": "string"
          },
          "advisorId": {
            "description": "Asesor responsable (userId de un empleado)",
            "type": "string"
          },
          "reminder": {
            "description": "Fecha-hora ISO 8601 del aviso al asesor",
            "type": "string"
          }
        },
        "required": [
          "activityType",
          "title",
          "date"
        ]
      },
      "group": "activities"
    },
    {
      "name": "create_agent",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre visible del agente (máx. 100)"
          },
          "systemPrompt": {
            "type": "string",
            "description": "Instrucciones del agente (máx. 30000 caracteres)"
          },
          "description": {
            "type": "string"
          },
          "integrationId": {
            "description": "Integración de IA de la organización a usar",
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "openai",
              "anthropic",
              "google"
            ]
          },
          "aiModel": {
            "type": "string"
          },
          "temperature": {
            "description": "0 a 2",
            "type": "number"
          },
          "maxTokens": {
            "description": "50 a 12000",
            "type": "number"
          },
          "enabledTools": {
            "description": "Nombres de list_agent_tools (máx. 30)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "maxToolCallsPerRun": {
            "description": "0 a 15",
            "type": "number"
          },
          "handoffKeywords": {
            "description": "Palabras que traspasan la conversación a un humano",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "handoffMessage": {
            "type": "string"
          },
          "autoAssignNewConversations": {
            "type": "boolean"
          },
          "triggers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "message_received",
                    "stage_entered",
                    "no_response_timeout",
                    "manual"
                  ]
                },
                "isActive": {
                  "type": "boolean"
                },
                "priority": {
                  "type": "number"
                },
                "sourceFilters": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "facebook",
                      "instagram"
                    ]
                  }
                },
                "stageIds": {
                  "description": "IDs de etapas del pipeline de conversaciones",
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "pipelineIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "keywords": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "excludeIfAssignedToHuman": {
                  "type": "boolean"
                },
                "excludeIfTagsPresent": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "requireTagsPresent": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "noResponseMinutes": {
                  "description": "Solo para no_response_timeout (5 a 10080)",
                  "type": "number"
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "contextSettings": {
            "type": "object",
            "properties": {
              "messagesLimit": {
                "type": "number"
              },
              "patternWindow": {
                "type": "number"
              },
              "dedupeSimilarityThreshold": {
                "type": "number"
              },
              "batchWindowSeconds": {
                "type": "number"
              },
              "batchMaxWaitSeconds": {
                "type": "number"
              }
            }
          },
          "leadStateSchema": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "label"
              ]
            }
          }
        },
        "required": [
          "name",
          "systemPrompt"
        ]
      },
      "group": "agents"
    },
    {
      "name": "create_automation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Nombre de la automatización"
          },
          "triggers": {
            "minItems": 1,
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Nombre del disparador"
                },
                "type": {
                  "type": "string",
                  "description": "Tipo de disparador (ver list_automation_variables)"
                },
                "description": {
                  "type": "string"
                },
                "filters": {
                  "description": "Filtros del disparador según su tipo",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                },
                "schedule": {
                  "description": "Solo para type 'scheduled'",
                  "type": "object",
                  "properties": {
                    "cronExpression": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    }
                  }
                }
              },
              "required": [
                "name",
                "type"
              ]
            },
            "description": "Disparadores (quedan inactivos en el borrador)"
          },
          "steps": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            },
            "description": "Pasos: { stepId?, type, name?, enabled?, onError?, config }; 'condition' lleva yes/no y 'wait' su config"
          },
          "reEntry": {
            "description": "Evitar ejecuciones paralelas para el mismo contacto",
            "type": "object",
            "properties": {
              "policy": {
                "type": "string",
                "enum": [
                  "one_active_per_contact",
                  "allow"
                ]
              },
              "cooldownMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "policy"
            ]
          }
        },
        "required": [
          "name",
          "triggers",
          "steps"
        ]
      },
      "group": "automations"
    },
    {
      "name": "create_calendar_event",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "start": {
            "type": "string",
            "description": "Inicio, fecha-hora ISO 8601"
          },
          "end": {
            "type": "string",
            "description": "Fin, fecha-hora ISO 8601 (no anterior a start)"
          },
          "description": {
            "type": "string",
            "maxLength": 5000
          },
          "allDay": {
            "description": "Evento de todo el día",
            "type": "boolean"
          },
          "timezone": {
            "description": "Zona IANA; default America/Bogota (manda en las recurrencias)",
            "type": "string"
          },
          "type": {
            "description": "Default tarea",
            "type": "string",
            "enum": [
              "reunion",
              "tarea",
              "recordatorio"
            ]
          },
          "location": {
            "type": "string",
            "maxLength": 500
          },
          "tags": {
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "color": {
                  "type": "string"
                }
              },
              "required": [
                "label",
                "color"
              ]
            }
          },
          "attendees": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string",
                  "description": "Correo del invitado"
                },
                "name": {
                  "type": "string"
                },
                "contactId": {
                  "description": "Contacto del CRM (debe ser de esta organización)",
                  "type": "string"
                },
                "userId": {
                  "description": "Empleado de la organización",
                  "type": "string"
                }
              },
              "required": [
                "email"
              ]
            }
          },
          "sendInvitations": {
            "description": "true = ENVÍA el correo de invitación a los invitados. Default false",
            "type": "boolean"
          },
          "assignedTo": {
            "description": "Asesor responsable (userId de un empleado)",
            "type": "string"
          },
          "contactId": {
            "description": "Contacto del CRM al que pertenece la cita",
            "type": "string"
          },
          "reminderEnabled": {
            "type": "boolean"
          },
          "reminderMinutesBefore": {
            "description": "Minutos antes del inicio, p. ej. [15, 1440]",
            "minItems": 1,
            "maxItems": 5,
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10080
            }
          },
          "recurrenceRule": {
            "description": "RRULE, p. ej. FREQ=WEEKLY;BYDAY=TU",
            "type": "string"
          },
          "externalId": {
            "description": "Id en un sistema externo",
            "type": "string",
            "maxLength": 255
          }
        },
        "required": [
          "title",
          "start",
          "end"
        ]
      },
      "group": "calendar"
    },
    {
      "name": "create_campaign_draft",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Nombre de la campaña, p. ej. 'Hoteles Santander oct-2026'"
          },
          "description": {
            "description": "Descripción interna",
            "type": "string",
            "maxLength": 2000
          },
          "contactListId": {
            "type": "string",
            "description": "Id de la lista de contactos (de list_contact_lists); tiene que ser de esta organización"
          },
          "template": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "description": "Nombre exacto de la plantilla aprobada en Meta (minúsculas y _), de list_whatsapp_templates"
              },
              "language": {
                "description": "Código de idioma de la plantilla, p. ej. 'es' o 'es_CO' (default 'es')",
                "type": "string"
              },
              "variableMapping": {
                "description": "Una entrada por variable del cuerpo: { \"1\": { source: 'field', key: 'firstName', fallback: 'cliente' } }",
                "type": "object",
                "propertyNames": {
                  "type": "string"
                },
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string",
                      "enum": [
                        "field",
                        "text"
                      ],
                      "description": "'field' = propiedad del contacto; 'text' = texto fijo igual para todos"
                    },
                    "key": {
                      "description": "Con source=field: propiedad del contacto, p. ej. 'firstName' o 'companyName'",
                      "type": "string"
                    },
                    "value": {
                      "description": "Con source=text: el texto fijo",
                      "type": "string"
                    },
                    "fallback": {
                      "description": "Valor si el contacto no tiene la propiedad (Meta rechaza variables vacías)",
                      "type": "string"
                    }
                  },
                  "required": [
                    "source"
                  ]
                }
              },
              "headerMediaUrl": {
                "description": "URL https de la imagen/video/documento de cabecera, si la plantilla la tiene",
                "type": "string"
              }
            },
            "required": [
              "name"
            ],
            "description": "Plantilla de WhatsApp que enviará la campaña"
          }
        },
        "required": [
          "name",
          "contactListId",
          "template"
        ]
      },
      "group": "campaigns"
    },
    {
      "name": "create_company",
      "description": "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. IDENTIFICACIÓN (modo estricto): idType = código DIAN (13 cédula, 31 NIT, 22 CE, 41 pasaporte, 42 doc. extranjero, 50 NIT de otro país…) o un texto inequívoco ('CC', 'NIT'); idNumber sin puntos (para NIT el DV va en dv; si lo mandas pegado o con guion se separa solo; si falta se calcula). idType ambiguo o desconocido ('1', '11', 'Documento') → 422 ID_TYPE_UNRESOLVED con candidates: pregunta al usuario y reintenta con el código; idNumber inválido para el tipo → 400 INVALID_ID_NUMBER. Nunca elijas un candidato por el usuario. ",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "description": "Nombre de la empresa (requerido si no hay idNumber)",
            "type": "string"
          },
          "idType": {
            "description": "Código DIAN (31 NIT, 13 cédula, 50 NIT de otro país…) o texto inequívoco",
            "type": "string"
          },
          "idNumber": {
            "description": "NIT / número de documento sin puntos ni DV (clave de upsert preferida)",
            "type": "string"
          },
          "dv": {
            "description": "DV del NIT (idType 31); si falta se calcula",
            "type": "string"
          },
          "city": {
            "description": "Ciudad (Colombia): nombre inequívoco del catálogo DANE",
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio, 5 dígitos. Deriva city, state y stateCode.",
            "type": "string",
            "minLength": 5,
            "maxLength": 5
          },
          "state": {
            "description": "Departamento (texto). Solo sirve como pista para desambiguar city; se deriva de cityCode.",
            "type": "string"
          }
        },
        "additionalProperties": {}
      },
      "group": "companies"
    },
    {
      "name": "create_company_property",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clave interna, ej. 'sector'"
          },
          "label": {
            "type": "string",
            "description": "Nombre visible, ej. 'Sector económico'"
          },
          "type": {
            "type": "string",
            "enum": [
              "input",
              "select",
              "textarea",
              "number",
              "date",
              "email",
              "phone"
            ]
          },
          "options": {
            "description": "Opciones para type=select",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isVisible": {
            "description": "Default true. Deprecado: NO significa obligatorio",
            "type": "boolean"
          },
          "required": {
            "description": "Default false. Obligatorio al crear/editar desde la web y la app",
            "type": "boolean"
          },
          "groupId": {
            "description": "_id del grupo (sección) donde se muestra",
            "type": "string"
          }
        },
        "required": [
          "key",
          "label"
        ]
      },
      "group": "fields"
    },
    {
      "name": "create_contact",
      "description": "Create (or update if exists) a contact. At least one of email, mobile, whatsapp_phone or linkedConversationId required. CONVERSACIÓN ENLAZADA: linkedConversationId (_id de una conversación de la org) cuenta como medio — un lead de Instagram/Messenger sin teléfono se crea con first_name + linkedConversationId — y enlaza la conversación al contacto; de otra org o inexistente → 404 CONVERSATION_NOT_FOUND, con otro contacto ya enlazado → 409 CONVERSATION_ALREADY_LINKED (trae contactId). Sin first_name, al crear se usa el nombre del perfil de la conversación, luego el número. 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. IDENTIFICACIÓN (modo estricto): idType = código DIAN (13 cédula, 31 NIT, 22 CE, 41 pasaporte, 42 doc. extranjero, 50 NIT de otro país…) o un texto inequívoco ('CC', 'NIT'); idNumber sin puntos (para NIT el DV va en dv; si lo mandas pegado o con guion se separa solo; si falta se calcula). idType ambiguo o desconocido ('1', '11', 'Documento') → 422 ID_TYPE_UNRESOLVED con candidates: pregunta al usuario y reintenta con el código; idNumber inválido para el tipo → 400 INVALID_ID_NUMBER. Nunca elijas un candidato por el usuario. ",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "email": {
            "type": "string"
          },
          "mobile": {
            "type": "string"
          },
          "whatsapp_phone": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "companyName": {
            "type": "string"
          },
          "source": {
            "description": "Origen del lead, ej. 'whatsapp', 'referido', 'feria'",
            "type": "string"
          },
          "companyType": {
            "description": "Tipo de empresa (select de la org, default: Personal/Empresa/Proveedor/Cliente/Otro)",
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "city": {
            "description": "Ciudad (Colombia): nombre inequívoco del catálogo DANE. Se deriva de cityCode si lo mandas.",
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio, 5 dígitos (ej. 05001 = Medellín, 76001 = Cali). Fuente de verdad: deriva city, state y stateCode.",
            "type": "string",
            "minLength": 5,
            "maxLength": 5
          },
          "state": {
            "description": "Departamento (texto). Solo sirve como pista para desambiguar city; se deriva de cityCode.",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "position": {
            "description": "Cargo del contacto",
            "type": "string"
          },
          "idType": {
            "description": "Tipo de documento: código DIAN (13 cédula, 31 NIT, 41 pasaporte…) o texto inequívoco ('CC', 'NIT')",
            "type": "string"
          },
          "idNumber": {
            "description": "Número de documento sin puntos (NIT sin DV: el DV va en dv)",
            "type": "string"
          },
          "dv": {
            "description": "Dígito de verificación, solo para NIT (idType 31); si falta se calcula",
            "type": "string"
          },
          "phone": {
            "description": "Teléfono fijo (mobile es el principal)",
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "linkedConversationId": {
            "description": "_id de la conversación de la org a enlazar con el contacto; cuenta como medio de contacto",
            "type": "string"
          }
        }
      },
      "group": "contacts"
    },
    {
      "name": "create_contact_property",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clave interna, ej. 'talla'"
          },
          "label": {
            "type": "string",
            "description": "Nombre visible, ej. 'Talla de camiseta'"
          },
          "type": {
            "type": "string",
            "enum": [
              "input",
              "select",
              "textarea",
              "number",
              "date",
              "email",
              "phone"
            ]
          },
          "options": {
            "description": "Opciones para type=select",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isVisible": {
            "description": "Default true. Deprecado: NO significa obligatorio",
            "type": "boolean"
          },
          "required": {
            "description": "Default false. Obligatorio al crear/editar desde la web y la app",
            "type": "boolean"
          },
          "groupId": {
            "description": "_id del grupo (sección) donde se muestra",
            "type": "string"
          }
        },
        "required": [
          "key",
          "label"
        ]
      },
      "group": "fields"
    },
    {
      "name": "create_conversation",
      "description": "Crear conversación. Para descubrir los stageId disponibles usá list_pipeline_stages.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Título de la conversación (requerido)"
          },
          "pipeline": {
            "description": "ID del pipeline (usa default si se omite)",
            "type": "string"
          },
          "stage": {
            "description": "ObjectId del stage inicial (usa el primero por order si se omite)",
            "type": "string"
          },
          "assignedTo": {
            "description": "ID del usuario asignado",
            "type": "string"
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          },
          "tags": {
            "description": "Array de tags, ej: [\"venta\", \"soporte\"]",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "whatsapp",
              "facebook",
              "instagram"
            ]
          },
          "metadata": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "value"
              ]
            }
          }
        },
        "required": [
          "title"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "create_conversation_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre del pipeline, p. ej. 'Pipeline Principal'."
          },
          "isDefault": {
            "description": "Marcarlo como predeterminado (los mensajes entrantes caen ahí).",
            "type": "boolean"
          },
          "stages": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Nombre de la etapa."
                },
                "order": {
                  "description": "Posición (0 = primera). Si falta, se usa el índice.",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 9007199254740991
                },
                "color": {
                  "description": "Color, p. ej. #3b82f6.",
                  "type": "string"
                },
                "autoAssign": {
                  "description": "Al entrar a la etapa, asignar la conversación a assignToTeam.",
                  "type": "boolean"
                },
                "assignToTeam": {
                  "description": "userId de un empleado de la organización.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "webhookEnabled": {
                  "description": "Llamar webhookUrl cuando una conversación entra a la etapa.",
                  "type": "boolean"
                },
                "webhookUrl": {
                  "description": "URL https del webhook de la etapa.",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "reopenOnNewMessage": {
                  "description": "Si llega un mensaje estando aquí, volver a la primera etapa.",
                  "type": "boolean"
                }
              },
              "required": [
                "name"
              ]
            },
            "description": "Etapas en orden; al menos una."
          }
        },
        "required": [
          "name",
          "stages"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "create_conversation_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline."
          },
          "name": {
            "type": "string",
            "description": "Nombre de la etapa."
          },
          "order": {
            "description": "Posición; si falta va al final.",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "color": {
            "description": "Color, p. ej. #22c55e.",
            "type": "string"
          },
          "autoAssign": {
            "type": "boolean"
          },
          "assignToTeam": {
            "description": "userId de un empleado de la organización.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "webhookEnabled": {
            "description": "Requiere webhookUrl.",
            "type": "boolean"
          },
          "webhookUrl": {
            "description": "URL https.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "reopenOnNewMessage": {
            "type": "boolean"
          }
        },
        "required": [
          "pipelineId",
          "name"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "create_deal",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "companyId": {
            "description": "ID de la empresa (Company) a asociar al deal. Opcional: si se omite se hereda la del contacto principal.",
            "type": "string"
          },
          "customFields": {
            "description": "Campos personalizados: [{ \"field\": \"<_id del campo>\", \"value\": \"...\" }]. El `field` es el _id de la definición del campo (el que vive en dealsfields), NO su nombre ni su key: un nombre no se resuelve y la escritura se pierde.",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "value": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "field"
                  ]
                }
              },
              {
                "type": "string"
              }
            ]
          }
        },
        "additionalProperties": {}
      },
      "group": "deals"
    },
    {
      "name": "create_deal_field",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "_id del pipeline de negocios"
          },
          "name": {
            "type": "string",
            "description": "Nombre visible, ej. 'Cantidad de manillas'"
          },
          "key": {
            "type": "string",
            "description": "Clave interna única en el pipeline, ej. 'cantidad'"
          },
          "type": {
            "description": "Default text",
            "type": "string",
            "enum": [
              "text",
              "number",
              "date",
              "select",
              "checkbox"
            ]
          },
          "options": {
            "description": "Opciones para type=select",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "required": {
            "type": "boolean"
          },
          "requiredForStage": {
            "description": "_id de la etapa (del mismo pipeline) para la que es requisito; null = ninguna",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "enforcement": {
            "type": "string",
            "enum": [
              "block",
              "warn"
            ]
          }
        },
        "required": [
          "pipelineId",
          "name",
          "key"
        ]
      },
      "group": "fields"
    },
    {
      "name": "create_deal_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Nombre del pipeline, p. ej. 'Pedidos Medellín'"
          },
          "stages": {
            "description": "Etapas en orden; se puede crear sin etapas y agregarlas después",
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120,
                  "description": "Nombre de la etapa"
                },
                "color": {
                  "description": "Color hexadecimal, p. ej. '#22c55e'",
                  "type": "string"
                },
                "isWon": {
                  "description": "true si es la etapa de negocio ganado",
                  "type": "boolean"
                },
                "isLost": {
                  "description": "true si es la etapa de negocio perdido",
                  "type": "boolean"
                },
                "probability": {
                  "description": "Probabilidad de cierre 0-100",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "maxDaysInStage": {
                  "description": "Días máximos antes de marcar el negocio como estancado",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991
                }
              },
              "required": [
                "name"
              ]
            }
          }
        },
        "required": [
          "title"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "create_deal_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Nombre de la etapa"
          },
          "color": {
            "description": "Color hexadecimal, p. ej. '#3b82f6'",
            "type": "string"
          },
          "isWon": {
            "description": "true si es la etapa de negocio ganado",
            "type": "boolean"
          },
          "isLost": {
            "description": "true si es la etapa de negocio perdido",
            "type": "boolean"
          },
          "probability": {
            "description": "Probabilidad de cierre 0-100",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "maxDaysInStage": {
            "description": "Días máximos antes de marcar el negocio como estancado",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          }
        },
        "required": [
          "pipelineId",
          "name"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "create_dynamic_contact_list",
      "description": "Crear una lista DINÁMICA de contactos.\nA diferencia de la estática, los contactos que pertenecen a la lista se recalculan en tiempo real\ncada vez que se consultan, aplicando los filtros almacenados contra la base de datos actual.\n\nSe REQUIERE al menos un filtro.\nEstructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' }\nOperadores: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than.\nClaves de ejemplo: firstName, lastName, email, mobile, company, city, cityCode, stateCode, country, source, companyType, leadScore.\nCiudades (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).\nFiltro especial: { key: 'hasDeals', operator: 'equals', value: true } para contactos con/sin negocios.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre de la lista"
          },
          "description": {
            "description": "Descripción opcional",
            "type": "string"
          },
          "filters": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Campo del contacto"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "contains",
                    "equals",
                    "starts with",
                    "ends with",
                    "is empty",
                    "is not empty",
                    "greater_than",
                    "less_than"
                  ],
                  "description": "Operador del filtro"
                },
                "value": {
                  "description": "Valor a comparar",
                  "type": "string"
                }
              },
              "required": [
                "key",
                "operator"
              ]
            },
            "description": "Filtros requeridos para la lista dinámica"
          }
        },
        "required": [
          "name",
          "filters"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "create_email_template",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre de la plantilla (máx 200 caracteres)."
          },
          "emailJson": {
            "description": "Bloques del editor, en orden. Cada bloque es un objeto con `type` y sus campos, p. ej. { id: 'b1', type: 'text', content: 'Hola {{firstName}}' }.",
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            }
          },
          "emailHtml": {
            "description": "HTML crudo (solo si no mandas emailJson). Se sanitiza.",
            "type": "string"
          },
          "previewText": {
            "description": "Texto de vista previa que muestran los clientes de correo (solo aplica con emailJson).",
            "type": "string"
          },
          "saved": {
            "description": "true = aparece en la galería de plantillas guardadas.",
            "type": "boolean"
          }
        },
        "required": [
          "name"
        ]
      },
      "group": "templates"
    },
    {
      "name": "create_export",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "import-export"
    },
    {
      "name": "create_fragment",
      "description": "Crear un fragment. El 'atajo' debe ser único globalmente (índice único en BD).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre del fragment"
          },
          "text": {
            "type": "string",
            "description": "Contenido del fragment"
          },
          "atajo": {
            "type": "string",
            "description": "Atajo/shortcut único"
          },
          "format": {
            "description": "Formato del contenido: \"text\" (texto plano / WhatsApp) o \"html\" (correo con formato). Por defecto \"text\".",
            "type": "string",
            "enum": [
              "text",
              "html"
            ]
          },
          "asunto": {
            "description": "Asunto del correo (solo aplica a fragmentos de formato \"html\").",
            "type": "string"
          }
        },
        "required": [
          "name",
          "text",
          "atajo"
        ]
      },
      "group": "fragments"
    },
    {
      "name": "create_invoice",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "customer": {
            "type": "object",
            "properties": {
              "identification_number": {
                "type": "string",
                "description": "NIT o cédula del cliente"
              },
              "name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            },
            "required": [
              "identification_number"
            ],
            "additionalProperties": {}
          },
          "invoice_lines": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {},
              "additionalProperties": {}
            },
            "description": "Líneas de la factura en formato DIAN"
          },
          "legal_monetary_totals": {
            "type": "object",
            "properties": {
              "payable_amount": {
                "type": "number",
                "description": "Total a pagar (> 0)"
              }
            },
            "required": [
              "payable_amount"
            ],
            "additionalProperties": {}
          },
          "date": {
            "description": "Fecha de emisión (YYYY-MM-DD)",
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "quotationId": {
            "description": "_id de la cotización de la que sale la factura (opcional). Debe ser de esta organización y no estar anulada ni rechazada (si no, 400 sin emitir). Con la factura válida ante la DIAN la cotización pasa a Facturada.",
            "type": "string"
          }
        },
        "required": [
          "customer",
          "invoice_lines",
          "legal_monetary_totals"
        ],
        "additionalProperties": {}
      },
      "group": "invoices"
    },
    {
      "name": "create_landing_page",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Nombre interno de la página; también es el título SEO inicial"
          },
          "templateId": {
            "description": "Plantilla de partida (de list_landing_page_templates). Default blank",
            "type": "string",
            "enum": [
              "captacion",
              "cotizacion",
              "evento",
              "descarga",
              "blank"
            ]
          },
          "formId": {
            "description": "Id de un formulario de la organización para el bloque form",
            "type": "string"
          },
          "sections": {
            "description": "Secciones en orden (máximo 12, un solo bloque form). Si no se envía, se usan las de la plantilla",
            "maxItems": 12,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "hero",
                    "benefits",
                    "text",
                    "gallery",
                    "testimonials",
                    "faq",
                    "whatsapp_cta",
                    "form"
                  ],
                  "description": "Tipo de bloque"
                },
                "enabled": {
                  "description": "false oculta el bloque sin borrarlo (default true)",
                  "type": "boolean"
                },
                "data": {
                  "description": "Contenido del bloque según su tipo (ver example en list_landing_page_templates). Texto plano; **negrita** es el único formato",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "theme": {
            "type": "object",
            "properties": {
              "primaryColor": {
                "description": "Color principal #rrggbb",
                "type": "string"
              },
              "accentColor": {
                "description": "Color de acento #rrggbb",
                "type": "string"
              },
              "logoUrl": {
                "description": "URL https del logo",
                "type": "string"
              },
              "fontPreset": {
                "description": "Tipografía",
                "type": "string",
                "enum": [
                  "system",
                  "serif",
                  "rounded"
                ]
              },
              "showBranding": {
                "description": "Mostrar 'Hecho con FusionCRM' (en plan Free siempre queda true)",
                "type": "boolean"
              }
            }
          },
          "seo": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string",
                "maxLength": 120
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "ogImageUrl": {
                "description": "URL https de la imagen al compartir",
                "type": "string"
              },
              "faviconUrl": {
                "description": "URL https del favicon",
                "type": "string"
              },
              "indexable": {
                "description": "false = noindex para buscadores",
                "type": "boolean"
              }
            }
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "phone": {
                "description": "Número con indicativo, p. ej. 573001112233; vacío = el de la organización",
                "type": "string"
              },
              "message": {
                "description": "Mensaje prellenado del chat",
                "type": "string",
                "maxLength": 300
              },
              "sticky": {
                "description": "Botón flotante de WhatsApp",
                "type": "boolean"
              }
            }
          },
          "tracking": {
            "type": "object",
            "properties": {
              "metaPixelId": {
                "description": "ID del Pixel de Meta (15-16 dígitos)",
                "type": "string"
              },
              "ga4MeasurementId": {
                "description": "ID de GA4 G-XXXXXXXXXX",
                "type": "string"
              }
            }
          },
          "legal": {
            "type": "object",
            "properties": {
              "privacyUrl": {
                "description": "URL https de la política de datos propia",
                "type": "string"
              }
            }
          }
        },
        "required": [
          "name"
        ]
      },
      "group": "landings"
    },
    {
      "name": "create_product",
      "description": "Crear producto (POST /api/products).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre del producto"
          },
          "unitPrice": {
            "type": "number",
            "description": "Precio unitario"
          },
          "description": {
            "type": "string"
          },
          "taxes": {
            "description": "Porcentaje de impuesto, ej. 19",
            "type": "number"
          },
          "cost": {
            "type": "number"
          },
          "sku": {
            "type": "string"
          },
          "categoryId": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "unitPrice"
        ],
        "additionalProperties": {}
      },
      "group": "products"
    },
    {
      "name": "create_product_variant",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "description": "Id del producto padre"
          },
          "sku": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Código único de la variante, p. ej. 'MAN-TYV-ROJ'"
          },
          "price": {
            "type": "number",
            "minimum": 0,
            "description": "Precio de la variante"
          },
          "stock": {
            "description": "Unidades disponibles",
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "imageUrl": {
            "description": "URL de la imagen",
            "type": "string",
            "maxLength": 200
          },
          "isActive": {
            "description": "false para crearla inactiva (default true)",
            "type": "boolean"
          },
          "attributeValues": {
            "description": "Atributos que definen la variante",
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "attributeName": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Atributo, p. ej. 'Color'"
                },
                "value": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Valor, p. ej. 'Rojo'"
                },
                "displayName": {
                  "description": "Texto a mostrar (default = value)",
                  "type": "string"
                },
                "valueId": {
                  "type": "string"
                },
                "price": {
                  "type": "number",
                  "minimum": 0
                },
                "imageUrl": {
                  "type": "string"
                }
              },
              "required": [
                "attributeName",
                "value"
              ]
            }
          }
        },
        "required": [
          "productId",
          "sku",
          "price"
        ]
      },
      "group": "products"
    },
    {
      "name": "create_project",
      "description": "Crear proyecto (POST /api/projects).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre del proyecto"
          },
          "description": {
            "type": "string"
          },
          "startDate": {
            "description": "Fecha de inicio (ISO)",
            "type": "string"
          },
          "endDate": {
            "description": "Fecha de fin (ISO), no anterior a startDate",
            "type": "string"
          },
          "budget": {
            "description": "Presupuesto (número positivo)",
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": {}
      },
      "group": "projects"
    },
    {
      "name": "create_project_task",
      "description": "Crear tarea dentro de un proyecto (POST /projects/:id/tasks).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string"
          },
          "payload": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "Campos: title, dueDate, responsibleId, etc."
          }
        },
        "required": [
          "projectId",
          "payload"
        ]
      },
      "group": "projects"
    },
    {
      "name": "create_purchase",
      "description": "Registrar una compra a un proveedor (POST /api/purchases). Requiere purchases:write.\nkind=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.\nkind=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.\nEl proveedor puede ir como supplierId (contacto de la MISMA organización) o simplemente como supplierName + supplierNIT.\nEn cada ítem, si no se envía total se calcula como quantity × unitPrice, y tax vacío se toma como 0.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "kind": {
            "description": "Por defecto invoice. order = orden de compra con consecutivo OC-n",
            "type": "string",
            "enum": [
              "order",
              "invoice"
            ]
          },
          "orderNumber": {
            "description": "Número de la factura del proveedor. Obligatorio si kind=invoice; ignorado si kind=order",
            "type": "string"
          },
          "orderRef": {
            "description": "Solo con kind=invoice: id de la OC abierta que esta factura cubre",
            "type": "string"
          },
          "total": {
            "type": "number",
            "description": "Total de la compra"
          },
          "paymentMethod": {
            "type": "string",
            "description": "Forma de pago, ej. 'Transferencia', 'Efectivo'"
          },
          "tax": {
            "description": "Impuesto total. Por defecto 0",
            "type": "number"
          },
          "discounts": {
            "type": "number"
          },
          "supplierId": {
            "description": "_id de un contacto de la organización",
            "type": "string"
          },
          "supplierName": {
            "type": "string"
          },
          "supplierNIT": {
            "type": "string"
          },
          "purchaseDate": {
            "description": "Fecha de compra (YYYY-MM-DD). Por defecto hoy",
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "voucherType": {
            "type": "string",
            "enum": [
              "Factura electrónica",
              "Nota de crédito electrónica",
              "Documento soporte con no obligados",
              "Application response",
              "Otro"
            ]
          },
          "status": {
            "description": "Por defecto pending",
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "canceled"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "quantity": {
                  "type": "number"
                },
                "unitPrice": {
                  "type": "number"
                },
                "tax": {
                  "type": "number"
                },
                "total": {
                  "type": "number"
                }
              },
              "required": [
                "name",
                "quantity",
                "unitPrice"
              ]
            }
          }
        },
        "required": [
          "total",
          "paymentMethod"
        ]
      },
      "group": "purchases"
    },
    {
      "name": "create_quotation",
      "description": "Crear cotización (POST /api/quotations). El número lo asigna el sistema, solo después de validar el body (un 400 no consume número). Si el contacto no existe por mobile, se crea; si existe, su nombre NO se cambia. Términos: paymentTerms y shippingTerms; si no los mandas se usan los primeros de Ajustes > Cotizaciones y, si la org no tiene, responde 400 MISSING_QUOTATION_TERMS (pregunta al usuario). expirationDate opcional (yyyy-MM-dd); sin ella, la vigencia de la org. Nace en estado ready (Lista: terminada, falta enviarla) salvo que pidas status draft (borrador a medias). Ningún otro estado se admite al crear.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "Cliente": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "Nombre del cliente"
              },
              "mobile": {
                "type": "string",
                "description": "Celular del cliente (busca/crea el contacto por este dato)"
              },
              "email": {
                "type": "string"
              },
              "company": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "mobile"
            ],
            "additionalProperties": {},
            "description": "Datos del cliente. Acepta también Nombre/Mobile en mayúscula"
          },
          "cotizacion": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Nombre del ítem"
                },
                "quantity": {
                  "type": "number"
                },
                "unitPrice": {
                  "type": "number"
                },
                "taxes": {
                  "description": "Impuesto del ítem (valor)",
                  "type": "number"
                },
                "total": {
                  "type": "number"
                },
                "description": {
                  "type": "string"
                },
                "productId": {
                  "type": "string"
                }
              },
              "required": [
                "name",
                "quantity",
                "unitPrice",
                "total"
              ],
              "additionalProperties": {}
            },
            "description": "Ítems de la cotización (mínimo 1)"
          },
          "totals": {
            "type": "object",
            "properties": {
              "subtotal": {
                "type": "number"
              },
              "iva": {
                "type": "number"
              },
              "total": {
                "type": "number"
              }
            },
            "required": [
              "subtotal",
              "total"
            ],
            "additionalProperties": {}
          },
          "name": {
            "description": "Título de la cotización",
            "type": "string"
          },
          "paymentTerms": {
            "description": "Forma de pago (p. ej. 100% Anticipado); default: la primera de la org",
            "type": "string"
          },
          "shippingTerms": {
            "description": "Tiempo de entrega (p. ej. 25 días hábiles); default: el primero de la org",
            "type": "string"
          },
          "expirationDate": {
            "description": "Fecha de vencimiento (yyyy-MM-dd o ISO)",
            "type": "string"
          },
          "observaciones": {
            "type": "string"
          },
          "status": {
            "description": "ready (default) = terminada; draft = borrador a medias, no dispara automatizaciones",
            "type": "string",
            "enum": [
              "draft",
              "ready"
            ]
          }
        },
        "required": [
          "Cliente",
          "cotizacion",
          "totals"
        ],
        "additionalProperties": {}
      },
      "group": "quotations"
    },
    {
      "name": "create_scoring_rule",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Nombre de la regla, p. ej. 'Clientes de Medellín'"
          },
          "description": {
            "description": "Descripción opcional",
            "type": "string",
            "maxLength": 1000
          },
          "isActive": {
            "description": "false para crearla desactivada (default true)",
            "type": "boolean"
          },
          "rules": {
            "minItems": 1,
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "propertyName": {
                  "description": "Key de la propiedad de contacto (obligatorio en condiciones de contacto)",
                  "type": "string"
                },
                "condition": {
                  "type": "string",
                  "enum": [
                    "exists",
                    "equals",
                    "not_equals",
                    "contains",
                    "greater_than",
                    "less_than",
                    "in_list",
                    "last_deal_older_than",
                    "last_deal_newer_than",
                    "deal_amount_greater_than",
                    "deal_amount_less_than",
                    "deal_status_is",
                    "deal_pipeline_is",
                    "total_deals_count_greater_than",
                    "total_deals_amount_greater_than",
                    "has_purchased_product",
                    "purchase_frequency_less_than",
                    "deal_field_value_is"
                  ],
                  "description": "Condición a evaluar"
                },
                "value": {
                  "description": "Valor según la condición (omitir en exists)",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "array",
                      "items": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          },
                          {
                            "type": "boolean"
                          }
                        ]
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "fieldId": {
                          "type": "string"
                        },
                        "value": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "fieldId",
                        "value"
                      ]
                    }
                  ]
                },
                "points": {
                  "type": "number",
                  "description": "Puntos que suma (o resta, si es negativo) al cumplirse; entre -10000 y 10000"
                }
              },
              "required": [
                "condition",
                "points"
              ]
            },
            "description": "Condiciones de la regla (1 a 50); cada una suma sus puntos por separado"
          }
        },
        "required": [
          "name",
          "rules"
        ]
      },
      "group": "settings"
    },
    {
      "name": "create_shipment",
      "description": "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.\nPrimero cotiza con quote_shipment y pasa aquí el carrier y el service de la tarifa elegida, con los MISMOS paquetes y destino.\nPasa 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.\n🚨 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.\nEl 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).\n201 = 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.\nEstados 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "description": "_id del contacto destinatario (obligatorio)"
          },
          "carrier": {
            "type": "string",
            "minLength": 1,
            "description": "carrier de la tarifa elegida en quote_shipment (ej. serviEntrega)"
          },
          "service": {
            "type": "string",
            "minLength": 1,
            "description": "service de la tarifa elegida en quote_shipment (ej. premier)"
          },
          "packages": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "content": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Descripción del contenido"
                },
                "amount": {
                  "description": "Bultos iguales. Por defecto 1",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "weight": {
                  "type": "number",
                  "minimum": 0.01,
                  "description": "Peso en kg (mínimo 0.01)"
                },
                "dimensions": {
                  "type": "object",
                  "properties": {
                    "length": {
                      "type": "number",
                      "minimum": 1
                    },
                    "width": {
                      "type": "number",
                      "minimum": 1
                    },
                    "height": {
                      "type": "number",
                      "minimum": 1
                    }
                  },
                  "required": [
                    "length",
                    "width",
                    "height"
                  ],
                  "description": "Largo, ancho y alto en cm (mínimo 1)"
                },
                "declaredValue": {
                  "description": "Valor declarado en COP",
                  "type": "number",
                  "minimum": 0
                },
                "presetId": {
                  "description": "Preset de paquete de la organización",
                  "type": "string"
                }
              },
              "required": [
                "content",
                "weight",
                "dimensions"
              ]
            },
            "description": "Los mismos paquetes que se cotizaron"
          },
          "idempotencyKey": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "OBLIGATORIA. Clave por INTENTO de envío (ej. pedido-4521-1), única por organización: un reintento con la misma clave no genera (ni cobra) otra guía y devuelve el envío existente ignorando el body. Tras cancelar o ante SHIPMENT_NEEDS_REVIEW, usa una clave nueva"
          },
          "destination": {
            "description": "Dirección confirmada; pisa la del contacto. Úsala igual que en quote_shipment",
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "company": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "street": {
                "description": "Calle y número (\"Calle 5 # 3-20\")",
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "district": {
                "description": "Barrio",
                "type": "string"
              },
              "reference": {
                "description": "Indicaciones para el mensajero",
                "type": "string"
              },
              "identificationNumber": {
                "description": "NIT o cédula del destinatario",
                "type": "string"
              },
              "city": {
                "description": "Ciudad en texto libre (\"Lérida\")",
                "type": "string"
              },
              "cityCode": {
                "description": "DANE de municipio, 5 dígitos (\"73408\"). Gana sobre city",
                "type": "string"
              },
              "state": {
                "description": "Departamento en texto libre (\"Tolima\")",
                "type": "string"
              },
              "stateCode": {
                "description": "DANE de departamento, 2 dígitos (\"73\")",
                "type": "string"
              },
              "country": {
                "description": "Por defecto CO (único soportado)",
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              }
            }
          },
          "insurance": {
            "description": "Seguro. true equivale a { enabled: true }",
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "amount": {
                    "description": "Valor asegurado; si falta se usa el declarado",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "enabled"
                ]
              }
            ]
          },
          "cod": {
            "description": "Pago contra entrega (solo si el servicio lo soporta)",
            "type": "object",
            "properties": {
              "amount": {
                "type": "number",
                "exclusiveMinimum": 0
              }
            },
            "required": [
              "amount"
            ]
          },
          "pickup": {
            "description": "Pedir recogida al generar la guía",
            "type": "object",
            "properties": {
              "date": {
                "type": "string",
                "description": "YYYY-MM-DD"
              },
              "timeFrom": {
                "type": "string",
                "description": "HH:MM, anterior a timeTo"
              },
              "timeTo": {
                "type": "string",
                "description": "HH:MM"
              },
              "instructions": {
                "type": "string",
                "maxLength": 300
              }
            },
            "required": [
              "date",
              "timeFrom",
              "timeTo"
            ]
          },
          "dealId": {
            "description": "Negocio asociado (de la organización)",
            "type": "string"
          },
          "quotationId": {
            "description": "Cotización asociada (de la organización)",
            "type": "string"
          },
          "invoiceId": {
            "description": "Factura asociada (de la organización)",
            "type": "string"
          }
        },
        "required": [
          "contactId",
          "carrier",
          "service",
          "packages",
          "idempotencyKey"
        ]
      },
      "group": "shipments"
    },
    {
      "name": "create_static_contact_list",
      "description": "Crear una lista ESTÁTICA de contactos. Existen tres modos:\n\n1. Lista vacía: pasar solo name (y opcionalmente description). Se puede poblar después con add_contacts_to_list.\n2. Por IDs directos: pasar contactIds (array de IDs de contacto). Máximo 2000 contactos.\n3. Por filtros de propiedades: pasar filters para buscar y capturar los contactos que coincidan en el momento de creación.\n   Los filtros son snapshots — el grupo no cambia automáticamente después (para eso usar create_dynamic_contact_list).\n\nEstructura de un filtro: { key: 'city', operator: 'contains', value: 'Bogotá' }\nOperadores soportados: contains, equals, 'starts with', 'ends with', 'is empty', 'is not empty', greater_than, less_than.\nClaves de ejemplo: firstName, lastName, email, mobile, company, city, country, source, companyType, leadScore.\nLímite: 5000 coincidencias al filtrar (usar filtros más específicos si se supera).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre de la lista"
          },
          "description": {
            "description": "Descripción opcional",
            "type": "string"
          },
          "contactIds": {
            "description": "IDs de contactos a incluir directamente (modo 2)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "filters": {
            "description": "Filtros para poblar la lista automáticamente (modo 3)",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Campo del contacto (ej. city, company, email)"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "contains",
                    "equals",
                    "starts with",
                    "ends with",
                    "is empty",
                    "is not empty",
                    "greater_than",
                    "less_than"
                  ],
                  "description": "Operador del filtro"
                },
                "value": {
                  "description": "Valor a comparar (no requerido para is empty / is not empty)",
                  "type": "string"
                }
              },
              "required": [
                "key",
                "operator"
              ]
            }
          }
        },
        "required": [
          "name"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "create_task",
      "description": "Crear tarea (POST /api/tasks). Incluir projectId en el cuerpo si aplica.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Título de la tarea"
          },
          "dueDate": {
            "type": "string",
            "description": "Fecha límite (ISO)"
          },
          "responsibleId": {
            "type": "string",
            "description": "_id del usuario responsable"
          },
          "projectId": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "budget": {
            "type": "number"
          }
        },
        "required": [
          "title",
          "dueDate",
          "responsibleId"
        ],
        "additionalProperties": {}
      },
      "group": "tasks"
    },
    {
      "name": "deals_summary",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "closingFrom": {
            "type": "string",
            "description": "Start of closing date range (YYYY-MM-DD)"
          },
          "closingTo": {
            "type": "string",
            "description": "End of closing date range (YYYY-MM-DD)"
          }
        },
        "required": [
          "closingFrom",
          "closingTo"
        ]
      },
      "group": "deals"
    },
    {
      "name": "delete_activity",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del seguimiento a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "activities"
    },
    {
      "name": "delete_agent",
      "description": "Borrar un agente IA (no se puede deshacer). Las conversaciones que tenía asignadas quedan sin agente. Confirma con el usuario antes de llamarlo.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del agente"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "agents"
    },
    {
      "name": "delete_calendar_event",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del evento (de la serie si es recurrente)"
          },
          "occurrenceStart": {
            "description": "ISO de la ocurrencia a quitar sola; sin él se borra la serie completa",
            "type": "string"
          },
          "sendCancellations": {
            "description": "true = envía el correo de cancelación a los invitados. Default false",
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "calendar"
    },
    {
      "name": "delete_company",
      "description": "Eliminar empresa por ID (permiso companies:delete). Los contactos y deals vinculados NO se borran: solo se desvinculan de la empresa.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "companies"
    },
    {
      "name": "delete_contact",
      "description": "Eliminar contacto por ID (permiso contacts:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "delete_contact_list",
      "description": "Eliminar una lista de contactos por ID. No elimina los contactos, solo la lista.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "delete_conversation",
      "description": "Eliminar conversación por ID (permiso conversations:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "delete_conversation_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline a borrar."
          }
        },
        "required": [
          "pipelineId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "delete_conversation_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline."
          },
          "stageId": {
            "type": "string",
            "description": "ObjectId de la etapa a borrar."
          },
          "targetStageId": {
            "description": "Etapa del mismo pipeline que recibe las conversaciones.",
            "type": "string"
          }
        },
        "required": [
          "pipelineId",
          "stageId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "delete_deal",
      "description": "Eliminar negocio por ID (permiso deals:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "deals"
    },
    {
      "name": "delete_deal_field",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del campo a borrar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fields"
    },
    {
      "name": "delete_deal_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del pipeline a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "delete_deal_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline de la etapa"
          },
          "stageId": {
            "type": "string",
            "description": "Id de la etapa a eliminar"
          }
        },
        "required": [
          "pipelineId",
          "stageId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "delete_email_template",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la plantilla a borrar."
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "templates"
    },
    {
      "name": "delete_fragment",
      "description": "Eliminar un fragment por ID (permiso fragments:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fragments"
    },
    {
      "name": "delete_invoice_payment",
      "description": "Eliminar un abono mal registrado de una factura de venta (DELETE /api/invoices/:id/payments/:paymentId). Requiere invoices:write. El saldo se recalcula. El paymentId sale de get_invoice_payments. No toca las retenciones (para eso remove_invoice_withholdings).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la factura"
          },
          "paymentId": {
            "type": "string",
            "description": "_id del abono"
          }
        },
        "required": [
          "id",
          "paymentId"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "delete_knowledge_base_document",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID del documento (de list_knowledge_base_documents)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "knowledge-base"
    },
    {
      "name": "delete_product",
      "description": "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, lista 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del producto a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "products"
    },
    {
      "name": "delete_product_variant",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la variante a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "products"
    },
    {
      "name": "delete_project",
      "description": "Eliminar proyecto por ID (permiso projects:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "projects"
    },
    {
      "name": "delete_quotation",
      "description": "Eliminar cotización por ID (permiso quotations:delete). Solo se borra lo que NUNCA se envió (draft o ready sin sentAt); lo demás responde 409 QUOTATION_NOT_DELETABLE: en ese caso anúlala con update_quotation status canceled.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "quotations"
    },
    {
      "name": "delete_scoring_rule",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la regla a eliminar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "settings"
    },
    {
      "name": "delete_task",
      "description": "Eliminar tarea por ID (permiso tasks:delete).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "tasks"
    },
    {
      "name": "download_quotation_pdf",
      "description": "Descargar PDF de cotización por número (quotationNumber). Devuelve base64; puede ser pesado.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "number": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              }
            ],
            "description": "Número de cotización"
          }
        },
        "required": [
          "number"
        ]
      },
      "group": "quotations"
    },
    {
      "name": "duplicate_landing_page",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la landing page a copiar"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "landings"
    },
    {
      "name": "filter_contacts",
      "description": "Filtro avanzado cruzado: combina condiciones sobre propiedades de contactos Y condiciones sobre sus deals para retornar una lista paginada.\n\nCondiciones para contactFilters: equals | not_equals | contains | not_contains | is_empty | is_not_empty\nCondiciones para dealFilters:    equals | not_equals | contains | not_contains | greater_than | less_than | between | last_deal_before | last_deal_after\n\nCampos especiales en dealFilters:\n  - field \"hasDeals\" (sin condition): filtra si el contacto tiene (true) o no (false) deals que cumplan los demás filtros\n  - field \"lastDeal\" con condition \"last_deal_before\"/\"last_deal_after\": filtra según la fecha del deal más reciente del contacto\n  - field \"amount\" con greater_than/less_than/between: filtra por monto del deal (between requiere también \"valueTo\")\n  - cualquier campo real del deal: title, closingDate, createdAt, etc.\n\nEjemplos de uso:\n  - 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\"}]\n  - 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\"}]\n  - Contactos sin categoría: contactFilters=[{field:\"companyType\",condition:\"is_empty\"}]\n  - 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\"}]",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactFilters": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string",
                  "description": "Clave de propiedad del contacto: companyType, city, cityCode (código DANE de 5 dígitos), stateCode (departamento, 2 dígitos), lifeCycle, source, country, etc."
                },
                "condition": {
                  "type": "string",
                  "enum": [
                    "equals",
                    "not_equals",
                    "contains",
                    "not_contains",
                    "is_empty",
                    "is_not_empty"
                  ]
                },
                "value": {
                  "description": "Valor a comparar (no requerido para is_empty/is_not_empty)",
                  "type": "string"
                }
              },
              "required": [
                "field",
                "condition"
              ]
            },
            "description": "Filtros sobre propiedades del contacto, combinados con AND"
          },
          "dealFilters": {
            "description": "Filtros sobre los deals asociados al contacto",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string",
                  "description": "Campo del deal o especial: hasDeals | lastDeal | amount | title | createdAt | closingDate"
                },
                "condition": {
                  "description": "Operador de comparación (no requerido para field=hasDeals)",
                  "type": "string",
                  "enum": [
                    "equals",
                    "not_equals",
                    "contains",
                    "not_contains",
                    "greater_than",
                    "less_than",
                    "between",
                    "last_deal_before",
                    "last_deal_after"
                  ]
                },
                "value": {
                  "description": "Valor principal del filtro",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    }
                  ]
                },
                "valueTo": {
                  "description": "Valor superior para condición between",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              },
              "required": [
                "field"
              ]
            }
          },
          "page": {
            "description": "Página (default 1)",
            "type": "number"
          },
          "limit": {
            "description": "Resultados por página (default 50, máx 500)",
            "type": "number"
          }
        },
        "required": [
          "contactFilters"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "find_conversation_by_phone",
      "description": "Find conversation by contact phone. Includes messages.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "phone": {
            "type": "string"
          },
          "messagesLimit": {
            "description": "Messages to include (default 50, max 200)",
            "type": "number"
          }
        },
        "required": [
          "phone"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "fusion_request",
      "description": "Petición cruda a rutas FusionCRM bajo /api (quotations, tasks, products, etc.).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "DELETE"
            ]
          },
          "path": {
            "type": "string",
            "description": "Ej. /quotations?page=1 — se añade prefijo /api si no viene"
          },
          "body": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {}
          }
        },
        "required": [
          "method",
          "path"
        ]
      },
      "group": "general"
    },
    {
      "name": "generate_document_template_pdf",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la plantilla."
          },
          "values": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "null"
                }
              ]
            },
            "description": "Valores por campo: { \"cliente.firstName\": \"Ana\", \"cliente.address?\": \"Cra 1 #2-3\" }."
          },
          "flatten": {
            "description": "true = aplana el formulario (el PDF queda no editable).",
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "values"
        ]
      },
      "group": "templates"
    },
    {
      "name": "get_activity",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del seguimiento (de list_activities)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "activities"
    },
    {
      "name": "get_agent",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del agente (de list_agents)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "agents"
    },
    {
      "name": "get_automation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la automatización (de list_automations)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "automations"
    },
    {
      "name": "get_automation_trigger",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del disparador"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "automations"
    },
    {
      "name": "get_calendar_event",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del evento (de list_calendar_events) o activity:<id>"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "calendar"
    },
    {
      "name": "get_campaign_preflight",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la campaña"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "campaigns"
    },
    {
      "name": "get_campaign_stats",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la campaña (de list_whatsapp_campaigns)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "campaigns"
    },
    {
      "name": "get_company",
      "description": "Obtener una empresa por ID, incluyendo sus contactos vinculados, sus deals y un resumen (totalRevenue, totalDeals, totalContacts, lastDeal). Permiso companies:read.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "companies"
    },
    {
      "name": "get_contact",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID del contacto (ObjectId de 24 hex)."
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "get_contact_list",
      "description": "Obtener una lista de contactos por su ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "get_contact_list_contacts",
      "description": "Obtener los contactos de una lista (estática o dinámica) con paginación.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "number"
          },
          "limit": {
            "description": "Contactos por página (default 10)",
            "type": "number"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "get_conversation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "messagesLimit": {
            "description": "Messages to include (default 50, max 200)",
            "type": "number"
          },
          "messagesBefore": {
            "description": "Cursor: ISO 8601 timestamp or message _id. Returns messages strictly older than it.",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "get_conversation_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline de conversaciones."
          }
        },
        "required": [
          "pipelineId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "get_conversation_stage_history",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ObjectId de la conversación"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "number"
          },
          "limit": {
            "description": "Items por página (default 100, máx 1000)",
            "type": "number"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "get_current_organization",
      "description": "¿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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "account"
    },
    {
      "name": "get_deal",
      "description": "Get a deal by ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "deals"
    },
    {
      "name": "get_deal_checklist",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline de negocios"
          }
        },
        "required": [
          "pipelineId"
        ]
      },
      "group": "settings"
    },
    {
      "name": "get_deal_field",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del campo"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fields"
    },
    {
      "name": "get_deal_pipeline",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del pipeline (de list_deal_pipelines)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "get_document_template",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la plantilla (de list_document_templates)."
          },
          "includePdfFields": {
            "description": "true = incluye los campos AcroForm reales del PDF (nombre y tipo).",
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "templates"
    },
    {
      "name": "get_email_template",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la plantilla (de list_email_templates)."
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "templates"
    },
    {
      "name": "get_export",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "import-export"
    },
    {
      "name": "get_form",
      "description": "Obtener un formulario por ID, incluyendo el link público, el snippet de embed y la URL del webhook (panel Compartir).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID del formulario"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "forms"
    },
    {
      "name": "get_form_responses",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID del formulario"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "not_created"
            ]
          },
          "since": {
            "description": "ISO date: solo envíos desde esa fecha",
            "type": "string"
          },
          "includeSuspicious": {
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "forms"
    },
    {
      "name": "get_fragment",
      "description": "Obtener un fragment por ID. La respuesta incluye su \"format\" (\"text\" o \"html\").",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fragments"
    },
    {
      "name": "get_import",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la importación (importId de import_contacts o de list_imports)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "import-export"
    },
    {
      "name": "get_integration",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la integración (de list_integrations)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "settings"
    },
    {
      "name": "get_invoice",
      "description": "Obtener factura por ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "get_invoice_config",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "settings"
    },
    {
      "name": "get_invoice_payments",
      "description": "Estado de cobro y abonos de UNA factura de venta (GET /api/invoices/:id/payments). Requiere invoices:read. Devuelve receivable (total, withheld, netTotal, paid, balance, status), collectible y payments[{_id, amount, paidAt, method, reference, note}]. El _id de cada abono es el que pide delete_invoice_payment.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la factura"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "get_invoice_withholdings",
      "description": "Retenciones que el cliente le practicó a una factura de venta emitida (GET /api/invoices/:id/withholdings). Requiere invoices:read.\nDevuelve 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).\nÚ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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la factura"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "get_landing_page_stats",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la landing page"
          },
          "from": {
            "description": "YYYY-MM-DD (hora de Bogotá)",
            "type": "string"
          },
          "to": {
            "description": "YYYY-MM-DD (hora de Bogotá)",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "landings"
    },
    {
      "name": "get_library_file",
      "description": "Obtener un archivo de la Biblioteca por ID, con su mediaURL, tags y metadatos.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "library"
    },
    {
      "name": "get_mail_account",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la cuenta (de list_mail_accounts)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "settings"
    },
    {
      "name": "get_organization_settings",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "settings"
    },
    {
      "name": "get_product",
      "description": "Obtener producto por ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "products"
    },
    {
      "name": "get_product_variant",
      "description": "Devuelve una variante de producto por id. Un id de otra organización o inexistente responde 404.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la variante"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "products"
    },
    {
      "name": "get_project",
      "description": "Obtener proyecto por ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "projects"
    },
    {
      "name": "get_purchase",
      "description": "Obtener una orden de compra por ID, con el proveedor resuelto. Requiere purchases:read.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "purchases"
    },
    {
      "name": "get_purchases_overview",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "fromDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "toDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          }
        },
        "required": [
          "fromDate",
          "toDate"
        ]
      },
      "group": "reports"
    },
    {
      "name": "get_quotation",
      "description": "Obtener cotización por ID (Mongo).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "quotations"
    },
    {
      "name": "get_sales_forecast",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "fromDate": {
            "type": "string",
            "description": "YYYY-MM-DD (normalmente hoy)"
          },
          "toDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "pipelineId": {
            "description": "Limitar a un pipeline de negocios (de list_deals_config)",
            "type": "string"
          }
        },
        "required": [
          "fromDate",
          "toDate"
        ]
      },
      "group": "reports"
    },
    {
      "name": "get_sales_overview",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "fromDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          },
          "toDate": {
            "type": "string",
            "description": "YYYY-MM-DD"
          }
        },
        "required": [
          "fromDate",
          "toDate"
        ]
      },
      "group": "reports"
    },
    {
      "name": "get_scoring_rule",
      "description": "Devuelve una regla de puntuación de leads por id con todas sus condiciones. Un id de otra organización o inexistente responde 404.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la regla (de list_scoring_rules)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "settings"
    },
    {
      "name": "get_scoring_rules_recalculation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "jobId": {
            "type": "string",
            "description": "jobId devuelto por recalculate_scoring_rules"
          }
        },
        "required": [
          "jobId"
        ]
      },
      "group": "settings"
    },
    {
      "name": "get_task",
      "description": "Obtener tarea por ID.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "tasks"
    },
    {
      "name": "get_user",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del usuario (de list_users)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "users"
    },
    {
      "name": "import_contacts",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "rows": {
            "minItems": 1,
            "maxItems": 500,
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "description": "Filas a importar: [{ firstName, email, mobile, company, owner, ...otras claves de propiedades de contacto }]"
          },
          "createMissingCompanies": {
            "description": "true = crea las empresas de la columna 'company' que no existan (default false: solo enlaza las existentes)",
            "type": "boolean"
          },
          "dryRun": {
            "description": "true = solo simula y devuelve el resumen (wouldCreate, wouldUpdate, rejectedRows) sin escribir nada",
            "type": "boolean"
          }
        },
        "required": [
          "rows"
        ]
      },
      "group": "import-export"
    },
    {
      "name": "import_rollback",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la importación a revertir"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "import-export"
    },
    {
      "name": "link_contact_to_company",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "description": "ID del contacto"
          },
          "companyId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "ID de la empresa; null o \"\" desvincula"
          }
        },
        "required": [
          "contactId",
          "companyId"
        ]
      },
      "group": "companies"
    },
    {
      "name": "list_activities",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactId": {
            "description": "Solo los seguimientos de este contacto",
            "type": "string"
          },
          "companyId": {
            "description": "Solo los seguimientos de esta empresa",
            "type": "string"
          },
          "advisorId": {
            "description": "Solo los del asesor (userId) responsable",
            "type": "string"
          },
          "status": {
            "description": "incomplete = pendiente, complete = hecho",
            "type": "string",
            "enum": [
              "incomplete",
              "complete"
            ]
          },
          "activityType": {
            "type": "string",
            "enum": [
              "Reunion",
              "Llamada",
              "Correo",
              "Nota",
              "Whatsapp"
            ]
          },
          "from": {
            "description": "Fecha mínima (YYYY-MM-DD o ISO), inclusive",
            "type": "string"
          },
          "to": {
            "description": "Fecha máxima (YYYY-MM-DD = todo ese día, o ISO), inclusive",
            "type": "string"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Resultados por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "activities"
    },
    {
      "name": "list_agent_tools",
      "description": "Catálogo de tools que un agente IA puede tener habilitadas (valores válidos para enabledTools en create_agent / update_agent).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "agents"
    },
    {
      "name": "list_agents",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "agents"
    },
    {
      "name": "list_automation_runs",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la automatización"
          },
          "status": {
            "description": "Filtra por estado de la ejecución",
            "type": "string",
            "enum": [
              "running",
              "success",
              "partial",
              "failed",
              "waiting",
              "cancelled"
            ]
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Ejecuciones por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "automations"
    },
    {
      "name": "list_automation_triggers",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "automationId": {
            "description": "Solo los de esta automatización",
            "type": "string"
          },
          "type": {
            "description": "Tipo de disparador, p. ej. 'inbound_message'",
            "type": "string"
          },
          "isActive": {
            "description": "true = solo activos, false = solo inactivos",
            "type": "boolean"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Disparadores por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "automations"
    },
    {
      "name": "list_automation_variables",
      "description": "Catálogo para armar automatizaciones: las variables {{...}} disponibles según los tipos de disparador dados (con ejemplo y, en las de valores cerrados como trigger.quotation_status, sus `options` clave → etiqueta: se compara contra la clave), 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "triggers": {
            "description": "Tipos de disparador separados por coma, p. ej. 'quotation_created,deal_status_changed'",
            "type": "string"
          }
        }
      },
      "group": "automations"
    },
    {
      "name": "list_automations",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "status": {
            "description": "Filtra por estado",
            "type": "string",
            "enum": [
              "draft",
              "published",
              "paused"
            ]
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Automatizaciones por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "automations"
    },
    {
      "name": "list_calendar_events",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "description": "Inicio del rango, ISO 8601 (p. ej. 2026-09-01T00:00:00-05:00)"
          },
          "to": {
            "type": "string",
            "description": "Fin del rango, ISO 8601"
          },
          "assignedTo": {
            "description": "Solo los eventos de este asesor (userId)",
            "type": "string"
          },
          "includeCreatedBy": {
            "description": "Con assignedTo: sumar los eventos que esa persona creó",
            "type": "boolean"
          },
          "contactId": {
            "description": "Solo las citas de este contacto",
            "type": "string"
          },
          "includeActivities": {
            "description": "Incluir seguimientos de contactos (default false)",
            "type": "boolean"
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Default 100",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        },
        "required": [
          "from",
          "to"
        ]
      },
      "group": "calendar"
    },
    {
      "name": "list_companies",
      "description": "Listar empresas (companies) con búsqueda, rango de fechas y filtros por propiedades (permiso companies:read).\n\nFiltros de propiedades (se combinan con AND, case-insensitive por defecto):\n- 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).\n- extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { size: '50-100' }).\n- matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta).\n\nsearch: texto libre sobre name, industry, email, phone, website, city, idNumber.\nRango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD).\nOrdenable por createdAt/updatedAt asc|desc. Paginación con page/limit.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "search": {
            "description": "Texto libre; busca en name, industry, email, phone, website, city, idNumber.",
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "createdFrom": {
            "description": "ISO date (YYYY-MM-DD)",
            "type": "string"
          },
          "createdTo": {
            "description": "ISO date (YYYY-MM-DD)",
            "type": "string"
          },
          "name": {
            "description": "Nombre de la empresa",
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio (5 dígitos, ej. 05001 = Medellín)",
            "type": "string"
          },
          "stateCode": {
            "description": "Código DANE del departamento (2 dígitos, ej. 05 = Antioquia)",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "idNumber": {
            "description": "NIT / número de documento",
            "type": "string"
          },
          "extraFilters": {
            "description": "Filtros adicionales por propiedades custom, ej. { size: '50-100' }.",
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            }
          },
          "matchMode": {
            "description": "'contains' (default) usa regex case-insensitive; 'exact' exige igualdad estricta.",
            "type": "string",
            "enum": [
              "contains",
              "exact"
            ]
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "companies"
    },
    {
      "name": "list_company_contacts",
      "description": "Listar los contactos vinculados a una empresa (GET /api/companies/:id/contacts). Permiso companies:read.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la empresa"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "companies"
    },
    {
      "name": "list_company_properties",
      "description": "Lista las propiedades (campos) de las empresas de la organización con sus grupos: key, label, type, required (obligatorio en la web y la app), isVisible (deprecado: solo visibilidad), 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "fields"
    },
    {
      "name": "list_contact_lists",
      "description": "Obtener todas las listas de contactos (estáticas y dinámicas) de la organización.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "contact-lists"
    },
    {
      "name": "list_contact_properties",
      "description": "Lista las propiedades (campos) de los contactos de la organización con sus grupos: key, label, type, required (obligatorio en la web y la app), isVisible (deprecado: solo visibilidad), 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "fields"
    },
    {
      "name": "list_contact_shipments",
      "description": "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.\nCada 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 }.\nstatus filtra por estado: draft, created, picked_up, in_transit, out_for_delivery, delivered, delivery_failed, returned, lost, address_error, at_office, canceled, exception.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "description": "_id del contacto (obligatorio)"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "created",
              "picked_up",
              "in_transit",
              "out_for_delivery",
              "delivered",
              "delivery_failed",
              "returned",
              "lost",
              "address_error",
              "at_office",
              "canceled",
              "exception"
            ]
          },
          "page": {
            "description": "Por defecto 1",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Por defecto 20, máximo 100 (un valor mayor da error de validación)",
            "type": "integer",
            "exclusiveMinimum": 0,
            "maximum": 100
          }
        },
        "required": [
          "contactId"
        ]
      },
      "group": "shipments"
    },
    {
      "name": "list_contacts",
      "description": "Listar contactos con búsqueda, rango de fechas y filtros por propiedades.\n\nFiltros de propiedades (se combinan con AND, case-insensitive por defecto):\n- Campos canónicos: firstName, lastName, email, mobile, phone, company, position, city, cityCode, stateCode, country, source, idNumber, companyType.\n- 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.\n- extraFilters: objeto libre { key: value } para filtrar por CUALQUIER propiedad custom no listada (ej. { industry: 'retail' }).\n- matchMode: 'contains' (default, regex case-insensitive con escape) o 'exact' (igualdad exacta).\n\nFiltros de presencia (ideales para auditar calidad de datos y organizar contactos):\n- missing: lista separada por comas de claves que DEBEN estar ausentes o vacías. Ej: 'companyType,city' trae contactos sin companyType o con companyType=''.\n- exists: lista separada por comas de claves que DEBEN existir con valor no vacío. Ej: 'email,mobile' trae contactos con ambos campos.\n\nRango de fechas (createdAt) vía createdFrom/createdTo en ISO (YYYY-MM-DD).\nOrdenable por createdAt/updatedAt asc|desc. Paginación con page/limit.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "search": {
            "description": "Texto libre; busca en firstName, lastName, email, mobile, phone, company, companyName.",
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "createdFrom": {
            "description": "ISO date (YYYY-MM-DD)",
            "type": "string"
          },
          "createdTo": {
            "description": "ISO date (YYYY-MM-DD)",
            "type": "string"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "mobile": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "company": {
            "description": "Clave canónica para nombre de empresa.",
            "type": "string"
          },
          "companyType": {
            "description": "Solo útil si tus contactos tienen la propiedad 'companyType'.",
            "type": "string"
          },
          "position": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio (5 dígitos, ej. 05001 = Medellín). Combinar con matchMode 'exact'.",
            "type": "string"
          },
          "stateCode": {
            "description": "Código DANE del departamento (2 dígitos, ej. 05 = Antioquia). Trae todos los municipios del departamento.",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "idNumber": {
            "type": "string"
          },
          "extraFilters": {
            "description": "Filtros adicionales por propiedades custom, ej. { industry: 'retail' }.",
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {
              "type": "string"
            }
          },
          "matchMode": {
            "description": "'contains' (default) usa regex case-insensitive; 'exact' exige igualdad estricta.",
            "type": "string",
            "enum": [
              "contains",
              "exact"
            ]
          },
          "missing": {
            "description": "Claves separadas por coma que deben estar ausentes o vacías. Ej: 'companyType,city'.",
            "type": "string"
          },
          "exists": {
            "description": "Claves separadas por coma que deben existir con valor no vacío. Ej: 'email,mobile'.",
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "contacts"
    },
    {
      "name": "list_conversation_pipelines",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "conversations"
    },
    {
      "name": "list_conversations",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipeline": {
            "description": "ObjectId del pipeline para filtrar conversaciones",
            "type": "string"
          },
          "stage": {
            "description": "ObjectId del stage (currentStage) para filtrar conversaciones",
            "type": "string"
          },
          "assignedTo": {
            "description": "ObjectId del usuario/agente asignado",
            "type": "string"
          },
          "tags": {
            "description": "Tags separados por coma (ej. 'vip,urgente')",
            "type": "string"
          },
          "dateFrom": {
            "type": "string"
          },
          "dateTo": {
            "type": "string"
          },
          "dateField": {
            "description": "Date field to filter on (default: createdAt)",
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "lastMessageTimestamp"
            ]
          },
          "timezone": {
            "type": "number"
          },
          "source": {
            "type": "string",
            "enum": [
              "whatsapp",
              "facebook",
              "instagram"
            ]
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          },
          "search": {
            "type": "string"
          },
          "mobile": {
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "messagesLimit": {
            "description": "Messages per conversation (default 50, max 200)",
            "type": "number"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "lastMessageTimestamp",
              "createdAt",
              "updatedAt"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "conversations"
    },
    {
      "name": "list_conversations_config",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "conversations"
    },
    {
      "name": "list_deal_checklists",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Checklists por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "settings"
    },
    {
      "name": "list_deal_fields",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "description": "_id del pipeline de negocios (ver list_deals_config). Omitir = todos.",
            "type": "string"
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "fields"
    },
    {
      "name": "list_deal_pipelines",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Pipelines por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "pipelines"
    },
    {
      "name": "list_deals",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipeline": {
            "description": "ObjectId del pipeline para filtrar deals",
            "type": "string"
          },
          "status": {
            "description": "'won', 'lost' u 'open' (abierto = ni ganado ni perdido), o el _id de una etapa",
            "type": "string"
          },
          "contactId": {
            "description": "ObjectId del contacto asociado al deal",
            "type": "string"
          },
          "amountMin": {
            "description": "Monto mínimo del deal",
            "type": "number"
          },
          "amountMax": {
            "description": "Monto máximo del deal",
            "type": "number"
          },
          "createdFrom": {
            "description": "Filter by creation date start (YYYY-MM-DD)",
            "type": "string"
          },
          "createdTo": {
            "description": "Filter by creation date end (YYYY-MM-DD)",
            "type": "string"
          },
          "closingFrom": {
            "description": "Filter by closing date start (YYYY-MM-DD) — use this for sales reports",
            "type": "string"
          },
          "closingTo": {
            "description": "Filter by closing date end (YYYY-MM-DD) — use this for sales reports",
            "type": "string"
          },
          "wonFrom": {
            "description": "Filter by won date start (YYYY-MM-DD, wonAt)",
            "type": "string"
          },
          "wonTo": {
            "description": "Filter by won date end (YYYY-MM-DD, wonAt)",
            "type": "string"
          },
          "lostFrom": {
            "description": "Filter by lost date start (YYYY-MM-DD, lostAt)",
            "type": "string"
          },
          "lostTo": {
            "description": "Filter by lost date end (YYYY-MM-DD, lostAt)",
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "amount",
              "closingDate"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "deals"
    },
    {
      "name": "list_deals_config",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "deals"
    },
    {
      "name": "list_document_templates",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (desde 1).",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Resultados por página (default 100, máx 1000).",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          },
          "includeInactive": {
            "description": "true = incluye las desactivadas.",
            "type": "boolean"
          }
        }
      },
      "group": "templates"
    },
    {
      "name": "list_email_templates",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (desde 1).",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Resultados por página (default 100, máx 1000).",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          },
          "saved": {
            "description": "true = solo guardadas en la galería; false = solo borradores; omitido = todas.",
            "type": "boolean"
          },
          "search": {
            "description": "Texto a buscar en el nombre (sin distinguir mayúsculas).",
            "type": "string"
          }
        }
      },
      "group": "templates"
    },
    {
      "name": "list_forms",
      "description": "Listar los formularios de captación de la organización (nombre, slug público, campos, si crea negocio, total de envíos).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "active": {
            "description": "true = solo activos, false = solo pausados",
            "type": "boolean"
          }
        }
      },
      "group": "forms"
    },
    {
      "name": "list_fragments",
      "description": "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\").",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "search": {
            "description": "Busca en name, text y atajo (case-insensitive)",
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "name",
              "atajo"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "fragments"
    },
    {
      "name": "list_imports",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "type": {
            "description": "Filtra por tipo de importación",
            "type": "string",
            "enum": [
              "contacts",
              "companies",
              "deals",
              "purchases"
            ]
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Registros por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "import-export"
    },
    {
      "name": "list_integrations",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Integraciones por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "settings"
    },
    {
      "name": "list_invoices",
      "description": "Listar facturas (direction outgoing/incoming, etc.).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "direction": {
            "type": "string",
            "enum": [
              "outgoing",
              "incoming"
            ]
          },
          "status": {
            "type": "string"
          },
          "type_document_id": {
            "type": "number"
          },
          "contactId": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "dateFrom": {
            "type": "string"
          },
          "dateTo": {
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "date",
              "createdAt",
              "number"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "invoices"
    },
    {
      "name": "list_knowledge_base_documents",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "status": {
            "description": "Filtrar por estado de indexación",
            "type": "string",
            "enum": [
              "processing",
              "ready",
              "failed"
            ]
          },
          "tag": {
            "type": "string"
          }
        }
      },
      "group": "knowledge-base"
    },
    {
      "name": "list_landing_page_templates",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "landings"
    },
    {
      "name": "list_landing_pages",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "status": {
            "description": "Filtra por estado; sin él devuelve todas",
            "type": "string",
            "enum": [
              "draft",
              "published"
            ]
          }
        }
      },
      "group": "landings"
    },
    {
      "name": "list_library_files",
      "description": "Listar archivos de la Biblioteca (library) de la organización: imágenes, PDFs, videos, etc. ya subidos.\nDevuelve id, name, fileType, mediaURL (URL pública del archivo), tags y fechas.\n\nIMPORTANTE: esta tool NO sube archivos. El MCP solo puede consultar y editar metadatos\nde archivos que ya existen; para subir uno nuevo hay que usar la app o la web de FusionCRM.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "search": {
            "description": "Busca en el nombre del archivo (case-insensitive)",
            "type": "string"
          },
          "tag": {
            "description": "Filtrar por un tag (se normaliza a minúsculas)",
            "type": "string"
          },
          "fileType": {
            "description": "Filtrar por tipo: \"image\", \"video\", \"application\", \"audio\", \"text\"",
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "name"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "library"
    },
    {
      "name": "list_library_tags",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "library"
    },
    {
      "name": "list_mail_accounts",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Cuentas por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "settings"
    },
    {
      "name": "list_notifications",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "unread": {
            "description": "true = solo las no leídas",
            "type": "boolean"
          },
          "page": {
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Por página, máximo 100 (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 100
          }
        }
      },
      "group": "notifications"
    },
    {
      "name": "list_open_purchase_orders",
      "description": "Ó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.\nEl 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "nit": {
            "description": "NIT del proveedor, en cualquier formato",
            "type": "string"
          },
          "total": {
            "description": "Total de la factura, para ordenar por cercanía y calcular totalDiff",
            "type": "number"
          }
        }
      },
      "group": "purchases"
    },
    {
      "name": "list_pipeline_stages",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "description": "ObjectId del pipeline. Opcional; default = pipeline default de la org.",
            "type": "string"
          }
        }
      },
      "group": "conversations"
    },
    {
      "name": "list_product_variants",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "productId": {
            "description": "Id del producto padre (de list_products)",
            "type": "string"
          },
          "isActive": {
            "description": "true = solo activas; false = solo inactivas",
            "type": "boolean"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Variantes por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "products"
    },
    {
      "name": "list_products",
      "description": "Listar productos con filtros.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "search": {
            "type": "string"
          },
          "categoryId": {
            "type": "string"
          },
          "hasVariants": {
            "type": "boolean"
          },
          "priceMin": {
            "type": "number"
          },
          "priceMax": {
            "type": "number"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "name",
              "unitPrice"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "products"
    },
    {
      "name": "list_project_tasks",
      "description": "Listar tareas de un proyecto (GET /projects/:id/tasks).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "projectId": {
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "createdFrom": {
            "type": "string"
          },
          "createdTo": {
            "type": "string"
          },
          "sortBy": {
            "type": "string"
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        },
        "required": [
          "projectId"
        ]
      },
      "group": "projects"
    },
    {
      "name": "list_projects",
      "description": "Listar proyectos.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "createdFrom": {
            "type": "string"
          },
          "createdTo": {
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "createdAt",
              "updatedAt",
              "name",
              "startDate",
              "endDate"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "projects"
    },
    {
      "name": "list_purchases",
      "description": "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.\nCada 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "kind": {
            "description": "order = órdenes de compra; invoice = facturas de proveedor. Sin él, todo",
            "type": "string",
            "enum": [
              "order",
              "invoice"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "canceled"
            ]
          },
          "supplierId": {
            "description": "_id del contacto proveedor",
            "type": "string"
          },
          "search": {
            "description": "Coincidencia parcial en orderNumber, supplierName, supplierNIT o description",
            "type": "string"
          },
          "dateFrom": {
            "description": "Fecha de compra desde (YYYY-MM-DD)",
            "type": "string"
          },
          "dateTo": {
            "description": "Fecha de compra hasta (YYYY-MM-DD)",
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "purchaseDate",
              "createdAt",
              "total",
              "orderNumber"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "purchases"
    },
    {
      "name": "list_quotations",
      "description": "Listar cotizaciones con filtros y paginación. status admite varios estados (OR); sent_unviewed = enviadas sin ver (sent con viewCount 0). Las viejas sin estado cuentan como draft. La respuesta trae statusCounts: cuántas hay en cada estado con los mismos filtros pero sin el de status.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "status": {
            "description": "Estados a incluir, p. ej. [\"sent\", \"accepted\"] o [\"sent_unviewed\"]",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "draft",
                    "ready",
                    "sent",
                    "accepted",
                    "rejected",
                    "expired",
                    "invoiced",
                    "canceled",
                    "sent_unviewed"
                  ]
                }
              },
              {
                "type": "string",
                "enum": [
                  "draft",
                  "ready",
                  "sent",
                  "accepted",
                  "rejected",
                  "expired",
                  "invoiced",
                  "canceled",
                  "sent_unviewed"
                ]
              }
            ]
          },
          "contactId": {
            "type": "string"
          },
          "createdFrom": {
            "type": "string"
          },
          "createdTo": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "sortBy": {
            "type": "string",
            "enum": [
              "creationDate",
              "lastModified",
              "total",
              "quotationNumber"
            ]
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "quotations"
    },
    {
      "name": "list_receivables",
      "description": "Cartera: facturas de venta cobrables con su saldo (GET /api/invoices/receivables). Requiere invoices:read. Una fila COMPACTA por factura (sin XML ni líneas): úsala para conciliar pagos en vez de list_invoices.\nCada fila: invoiceId, fullNumber (FS-702), date, customerName, customerNit, total, withheld (retenciones), credited y creditNotes (notas crédito que la anulan), netTotal (lo que el cliente consigna), paid, balance, status (pending|partial|overdue|paid|credited = anulada por NC, no es un cobro), dueDate, daysOverdue.\nstatus: open (default, con saldo) | pending | partial | overdue | paid | credited | all. Filtros: dateFrom/dateTo (emisión, YYYY-MM-DD), nit (exacto, sin DV), search (nombre del cliente), contactId. Devuelve además aging (tramos de antigüedad) y totals de todo lo filtrado.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "open",
              "pending",
              "partial",
              "overdue",
              "paid",
              "credited",
              "all"
            ]
          },
          "dateFrom": {
            "description": "Fecha de emisión desde (YYYY-MM-DD)",
            "type": "string"
          },
          "dateTo": {
            "description": "Fecha de emisión hasta (YYYY-MM-DD)",
            "type": "string"
          },
          "nit": {
            "description": "NIT o cédula del cliente, sin DV",
            "type": "string"
          },
          "search": {
            "description": "Texto en el nombre del cliente",
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "limit": {
            "description": "Filas a devolver (default 500, máx 1000); los totales son de todo",
            "type": "number"
          }
        }
      },
      "group": "invoices"
    },
    {
      "name": "list_scoring_rules",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "isActive": {
            "description": "true = solo activas; false = solo inactivas; omitir = todas",
            "type": "boolean"
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Reglas por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "settings"
    },
    {
      "name": "list_states",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "contacts"
    },
    {
      "name": "list_tasks",
      "description": "Listar tareas (opcional projectId en query para filtrar).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "projectId": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          },
          "search": {
            "type": "string"
          },
          "createdFrom": {
            "type": "string"
          },
          "createdTo": {
            "type": "string"
          },
          "sortBy": {
            "type": "string"
          },
          "sortOrder": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ]
          }
        }
      },
      "group": "tasks"
    },
    {
      "name": "list_users",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "role": {
            "description": "Filtrar por rol",
            "type": "string",
            "enum": [
              "propietario",
              "empleado",
              "lector"
            ]
          },
          "page": {
            "description": "Página (default 1)",
            "type": "integer",
            "minimum": 1,
            "maximum": 9007199254740991
          },
          "limit": {
            "description": "Usuarios por página (default 100)",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000
          }
        }
      },
      "group": "users"
    },
    {
      "name": "list_whatsapp_campaigns",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "page": {
            "type": "number"
          },
          "limit": {
            "description": "Default 50, máximo 200",
            "type": "number"
          }
        }
      },
      "group": "whatsapp"
    },
    {
      "name": "list_whatsapp_templates",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "whatsapp"
    },
    {
      "name": "mark_notifications_read",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "ids": {
            "description": "Ids de notificación a marcar",
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "all": {
            "description": "true = marcar todas las no leídas (ignora ids)",
            "type": "boolean"
          }
        }
      },
      "group": "notifications"
    },
    {
      "name": "move_conversation_stage",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ObjectId de la conversación"
          },
          "stageId": {
            "type": "string",
            "description": "ObjectId del stage destino"
          }
        },
        "required": [
          "id",
          "stageId"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "publish_landing_page",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la landing page"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "landings"
    },
    {
      "name": "quote_shipment",
      "description": "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.\nDestino: 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.\nEl 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.\nRespuesta: data.rates ordenadas por totalPrice ascendente (COP), cada una con carrier + service (lo que pide create_shipment), deliveryEstimate y desglose; data.failed = transportadoras sin tarifa (no tumban la cotización): error.code NO_COVERAGE = no cubren la ruta, TIMEOUT/NETWORK = no respondieron. Consulta cada transportadora hasta 20 s (con un reintento): puede tardar.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "contactId": {
            "description": "_id del contacto destinatario; su dirección es el destino",
            "type": "string"
          },
          "destination": {
            "description": "Dirección de destino. Todo opcional: lo enviado pisa lo del contacto",
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "company": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "street": {
                "description": "Calle y número (\"Calle 5 # 3-20\")",
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "district": {
                "description": "Barrio",
                "type": "string"
              },
              "reference": {
                "description": "Indicaciones para el mensajero",
                "type": "string"
              },
              "identificationNumber": {
                "description": "NIT o cédula del destinatario",
                "type": "string"
              },
              "city": {
                "description": "Ciudad en texto libre (\"Lérida\")",
                "type": "string"
              },
              "cityCode": {
                "description": "DANE de municipio, 5 dígitos (\"73408\"). Gana sobre city",
                "type": "string"
              },
              "state": {
                "description": "Departamento en texto libre (\"Tolima\")",
                "type": "string"
              },
              "stateCode": {
                "description": "DANE de departamento, 2 dígitos (\"73\")",
                "type": "string"
              },
              "country": {
                "description": "Por defecto CO (único soportado)",
                "type": "string"
              },
              "postalCode": {
                "type": "string"
              }
            }
          },
          "packages": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "content": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Descripción del contenido"
                },
                "amount": {
                  "description": "Bultos iguales. Por defecto 1",
                  "type": "integer",
                  "exclusiveMinimum": 0,
                  "maximum": 9007199254740991
                },
                "weight": {
                  "type": "number",
                  "minimum": 0.01,
                  "description": "Peso en kg (mínimo 0.01)"
                },
                "dimensions": {
                  "type": "object",
                  "properties": {
                    "length": {
                      "type": "number",
                      "minimum": 1
                    },
                    "width": {
                      "type": "number",
                      "minimum": 1
                    },
                    "height": {
                      "type": "number",
                      "minimum": 1
                    }
                  },
                  "required": [
                    "length",
                    "width",
                    "height"
                  ],
                  "description": "Largo, ancho y alto en cm (mínimo 1)"
                },
                "declaredValue": {
                  "description": "Valor declarado en COP",
                  "type": "number",
                  "minimum": 0
                },
                "presetId": {
                  "description": "Preset de paquete de la organización",
                  "type": "string"
                }
              },
              "required": [
                "content",
                "weight",
                "dimensions"
              ]
            },
            "description": "Al menos un paquete"
          },
          "insurance": {
            "description": "Seguro. true equivale a { enabled: true }",
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "amount": {
                    "description": "Valor asegurado; si falta se usa el declarado",
                    "type": "number",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "enabled"
                ]
              }
            ]
          },
          "cod": {
            "description": "Pago contra entrega (solo si la transportadora lo soporta)",
            "type": "object",
            "properties": {
              "amount": {
                "type": "number",
                "exclusiveMinimum": 0
              }
            },
            "required": [
              "amount"
            ]
          },
          "carriers": {
            "description": "Códigos de transportadora a cotizar (ej. serviEntrega, coordinadora). Sin él, todas las de la cuenta",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "packages"
        ]
      },
      "group": "shipments"
    },
    {
      "name": "recalculate_scoring_rules",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {}
      },
      "group": "settings"
    },
    {
      "name": "register_invoice_payments",
      "description": "Registrar abonos (pagos recibidos) a facturas de venta, en LOTE (POST /api/invoices/payments). Requiere invoices:write. Es lo mismo que 'Registrar pago' de la columna Cobro de /invoices: no emite nada ante la DIAN ni cambia el estado DIAN.\nFlujo recomendado: primero con dryRun: true y mostrarle a la persona la tabla (result por ítem y summary); aplicar sin dryRun solo con su aprobación.\nCada ítem identifica la factura con id (preferido) o prefix+number. amount = lo recibido (número, sin separadores de miles). Un abono NO puede pasar del saldo: si el cliente pagó unos pesos de más (redondeo), manda full: true + amount y se registra el saldo exacto (lo recibido queda en la nota). full se rechaza si amount se aleja del saldo más de $1.000 o 1 %: un pago parcial va sin full.\nSi el pago llegó corto por retenciones, manda withholdings en el mismo ítem (mismos conceptos y reglas que apply_invoice_withholdings: amount o pct; reteIva es sobre el IVA). Se aplican ANTES del abono y REEMPLAZAN las que tuviera la factura; así el neto baja y el abono la deja en cero. Ojo con facturas pagadas en cuotas: si cada cuota trae retención, manda en la última el set COMPLETO (las de todas las cuotas), o la segunda borra la primera.\npaidAt = fecha del pago (YYYY-MM-DD, se guarda en hora de Bogotá; no se aceptan fechas futuras ni inexistentes). reference = referencia o descripción del banco (queda en el abono). Antiduplicados por factura: mismo monto + mismo día => result 'skipped' aunque la referencia sea otra (el mismo pago registrado antes desde la web). Repetir la conciliación es seguro.\nCada ítem es independiente (no es atómico): uno con error (result 'error' + code: INVOICE_NOT_FOUND, NOT_COLLECTIBLE, ALREADY_PAID, PAYMENT_INVALID, CONFLICT, INTERNAL_ERROR…) no frena a los demás. En dryRun, varios ítems sobre la misma factura se simulan en orden (el segundo ve el saldo que dejó el primero). Máximo 100 ítems por llamada.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "items": {
            "minItems": 1,
            "maxItems": 100,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "description": "_id de la factura (preferido)",
                  "type": "string"
                },
                "prefix": {
                  "description": "Prefijo, ej. FS (si no tienes el id)",
                  "type": "string"
                },
                "number": {
                  "description": "Número de la factura (si no tienes el id)",
                  "type": "number"
                },
                "amount": {
                  "description": "Monto recibido en COP",
                  "type": "number"
                },
                "full": {
                  "description": "true = abonar exactamente el saldo",
                  "type": "boolean"
                },
                "paidAt": {
                  "description": "Fecha del pago YYYY-MM-DD (default hoy)",
                  "type": "string"
                },
                "method": {
                  "description": "Ej. transferencia, wompi, efectivo",
                  "type": "string"
                },
                "reference": {
                  "description": "Referencia/descripción del movimiento bancario",
                  "type": "string"
                },
                "note": {
                  "type": "string"
                },
                "withholdings": {
                  "description": "Retenciones que el cliente practicó en este pago; reemplazan las de la factura",
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key": {
                        "type": "string",
                        "enum": [
                          "compras25",
                          "compras35",
                          "serviciosGeneral4",
                          "serviciosGeneral6",
                          "honorarios10",
                          "honorarios11",
                          "arrendamientoBienesMuebles",
                          "arrendamientoBienesRaices",
                          "serviciosAseoVigilancia",
                          "serviciosHotelesRestaurantes",
                          "transporteCarga",
                          "reteIva",
                          "reteIca"
                        ],
                        "description": "Concepto de la retención"
                      },
                      "amount": {
                        "description": "Monto retenido en COP",
                        "type": "number"
                      },
                      "pct": {
                        "description": "Porcentaje (sobre el IVA si es reteIva)",
                        "type": "number"
                      }
                    },
                    "required": [
                      "key"
                    ]
                  }
                }
              }
            },
            "description": "Abonos a registrar"
          },
          "dryRun": {
            "description": "true = calcular sin guardar",
            "type": "boolean"
          },
          "skipDuplicates": {
            "description": "Default true. false solo si de verdad hubo dos pagos del mismo monto el mismo día a la misma factura",
            "type": "boolean"
          }
        },
        "required": [
          "items"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "remove_contacts_from_list",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista"
          },
          "contactIds": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Array de IDs de contactos a quitar"
          }
        },
        "required": [
          "id",
          "contactIds"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "remove_invoice_withholdings",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la factura"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "invoices"
    },
    {
      "name": "reorder_conversation_stages",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline."
          },
          "stageIds": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids de TODAS las etapas en el orden deseado."
          }
        },
        "required": [
          "pipelineId",
          "stageIds"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "reorder_deal_stages",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline"
          },
          "stageIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids de todas las etapas en el nuevo orden"
          }
        },
        "required": [
          "pipelineId",
          "stageIds"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "search_contact_by_phone",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "phone": {
            "type": "string",
            "description": "Teléfono a buscar; se normaliza a solo dígitos (mínimo 3). Ej: '573132925094' o '313 292 5094'."
          }
        },
        "required": [
          "phone"
        ]
      },
      "group": "contacts"
    },
    {
      "name": "search_conversations",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "q": {
            "type": "string"
          },
          "page": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "messagesLimit": {
            "description": "Messages per conversation (default 50, max 200)",
            "type": "number"
          }
        },
        "required": [
          "q"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "search_knowledge_base",
      "description": "Busca en la base de conocimiento PROPIA de la organización (RAG multi-tenant):\ndocumentos que la organización subió a FusionCRM (catálogos, información de\nproductos, políticas, precios propios, manuales internos, etc.).\nDevuelve los fragmentos más relevantes con score de similitud y documento de origen.\nSi no hay resultados, la organización quizá no ha subido documentos aún\n(la subida se hace desde la app en /api/v1/rag/documents).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "Pregunta o tema a buscar en los documentos de la organización"
          },
          "top_k": {
            "description": "Número de fragmentos a retornar (default: 5, máx: 20)",
            "type": "number"
          },
          "min_score": {
            "description": "Similitud coseno mínima entre 0 y 1 (default: 0.25)",
            "type": "number"
          },
          "tag": {
            "description": "Limitar la búsqueda a documentos con este tag",
            "type": "string"
          }
        },
        "required": [
          "query"
        ]
      },
      "group": "knowledge-base"
    },
    {
      "name": "search_municipalities",
      "description": "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.\nÚ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.\nq 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).\nCada 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "q": {
            "description": "Texto a buscar (\"medel\", \"rionegro\", \"santa rosa\")",
            "type": "string",
            "minLength": 1
          },
          "state": {
            "description": "Código DANE del departamento para acotar (\"05\" Antioquia, \"76\" Valle del Cauca)",
            "type": "string",
            "minLength": 1,
            "maxLength": 2
          },
          "limit": {
            "description": "Máximo de filas (1-1200). Por defecto 20 con q",
            "type": "integer",
            "minimum": 1,
            "maximum": 1200
          }
        }
      },
      "group": "contacts"
    },
    {
      "name": "send_email",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "to": {
            "minItems": 1,
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Destinatarios (emails)."
          },
          "subject": {
            "type": "string",
            "description": "Asunto del correo."
          },
          "content": {
            "type": "string",
            "description": "Cuerpo del correo (HTML o texto plano segun contentFormat)."
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "contentFormat": {
            "description": "'html' (default) o 'text'.",
            "type": "string",
            "enum": [
              "html",
              "text"
            ]
          }
        },
        "required": [
          "to",
          "subject",
          "content"
        ]
      },
      "group": "email"
    },
    {
      "name": "send_quotation",
      "description": "Enviar una cotización al cliente: el PDF + el link público de la cotización. channel=email: por la tubería de correo de la plataforma a `to` (obligatorio). channel=whatsapp: como documento por WhatsApp a la conversación `conversationId` o, sin ella, a la del contacto de la cotización; SOLO dentro de la ventana de 24 h de Meta (si el cliente no ha escrito en 24 h → 409 WHATSAPP_WINDOW_CLOSED y no se envía nada). Regla de envío: draft/ready/rejected → pasa a sent; sent/accepted/invoiced no cambian de estado (solo se registra el envío); canceled no se puede enviar (409 QUOTATION_CANCELED); expired solo con fecha de vencimiento futura (409 EXPIRATION_REQUIRED), y draft/ready/rejected con la fecha de vencimiento ya pasada en la zona de la organización tampoco salen (409 EXPIRATION_REQUIRED). 403 WHATSAPP_OPTED_OUT si el contacto pidió no ser contactado por WhatsApp. ⚠️ Es un envío REAL al cliente: confírmalo con el usuario antes.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la cotización"
          },
          "channel": {
            "type": "string",
            "enum": [
              "email",
              "whatsapp"
            ],
            "description": "Canal de envío"
          },
          "to": {
            "description": "Email del destinatario (obligatorio con channel=email)",
            "type": "string"
          },
          "subject": {
            "description": "Asunto (email). Default: \"Cotización <número> — <empresa>\"",
            "type": "string"
          },
          "message": {
            "description": "Texto del correo o del pie del documento de WhatsApp (el link se agrega solo)",
            "type": "string"
          },
          "conversationId": {
            "description": "Conversación de WhatsApp (channel=whatsapp). Sin ella, la del contacto",
            "type": "string"
          }
        },
        "required": [
          "id",
          "channel"
        ]
      },
      "group": "quotations"
    },
    {
      "name": "send_whatsapp_message",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "to": {
            "type": "string",
            "description": "Número destino en formato internacional sin '+', ej. 573001234567"
          },
          "type": {
            "description": "Tipo de mensaje. Default: 'text'",
            "type": "string",
            "enum": [
              "text",
              "image",
              "document",
              "audio",
              "video"
            ]
          },
          "message": {
            "description": "Texto del mensaje. Obligatorio cuando type es 'text' (o ausente)",
            "type": "string"
          },
          "mediaUrl": {
            "description": "URL pública del archivo cuando type es image/document/audio/video",
            "type": "string"
          },
          "caption": {
            "description": "Pie del media (image/document/video)",
            "type": "string"
          },
          "replyToMessageId": {
            "description": "_id del mensaje (en FusionCRM) al que se responde, para citarlo",
            "type": "string"
          }
        },
        "required": [
          "to"
        ]
      },
      "group": "whatsapp"
    },
    {
      "name": "send_whatsapp_template",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "to": {
            "type": "string",
            "description": "Número destino en formato internacional sin '+', ej. 573001234567"
          },
          "templateName": {
            "type": "string",
            "description": "Nombre exacto de la plantilla aprobada en Meta"
          },
          "language": {
            "description": "Código de idioma de la plantilla, ej. 'es', 'en_US'. Default: 'es'",
            "type": "string"
          },
          "components": {
            "description": "Componentes con parámetros según el formato de Meta (body/header/button)",
            "type": "array",
            "items": {}
          },
          "confirm": {
            "description": "false/ausente = solo PREVIEW (no envía). true = enviar de verdad, después de que el usuario confirmó el plan.",
            "type": "boolean"
          },
          "stageId": {
            "description": "Etapa inicial si se CREA la conversación (del preview.availableStages). Sin él cae en la primera etapa del pipeline.",
            "type": "string"
          },
          "assignTo": {
            "description": "Quién atiende la conversación NUEVA: 'human' (default, dueño del token) o 'agent' (agente IA auto-asignable — responderá a todos los entrantes).",
            "type": "string",
            "enum": [
              "human",
              "agent"
            ]
          },
          "contact": {
            "description": "Segmentación del contacto: se crea si no existe (siempre, mínimo con el mobile) y estos campos se fijan/actualizan en el mismo envío.",
            "type": "object",
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "company": {
                "type": "string"
              },
              "companyType": {
                "type": "string"
              },
              "source": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "position": {
                "type": "string"
              },
              "idNumber": {
                "type": "string"
              },
              "notes": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "to",
          "templateName"
        ]
      },
      "group": "whatsapp"
    },
    {
      "name": "set_agent_status",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del agente"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "draft"
            ]
          }
        },
        "required": [
          "id",
          "status"
        ]
      },
      "group": "agents"
    },
    {
      "name": "set_quotation_status",
      "description": "Cambiar el estado de una cotización (PATCH /quotations/:id/status), validado contra la matriz y atómico. Destinos que puede pedir un asesor: draft↔ready; draft/ready→sent (\"marcar como enviada\" a mano: pasa channel); sent→accepted|rejected|canceled; accepted→sent (reabrir)|canceled; rejected→sent|canceled; expired→sent (exige una fecha de vencimiento futura, si no 409 EXPIRATION_REQUIRED)|canceled. Marcar sent desde draft/ready/rejected con la fecha de vencimiento ya pasada (en la zona de la organización) también responde 409 EXPIRATION_REQUIRED; marcar ready no. invoiced y expired los escribe solo el sistema. 409 INVALID_TRANSITION trae `allowed`; 409 STATUS_CONFLICT si cambió mientras tanto (o si expectedFrom no coincide). Para ENVIAR de verdad usa send_quotation: este tool no manda nada al cliente.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la cotización"
          },
          "to": {
            "type": "string",
            "enum": [
              "draft",
              "ready",
              "sent",
              "accepted",
              "rejected",
              "canceled"
            ],
            "description": "Estado destino"
          },
          "reason": {
            "description": "Motivo (texto libre), sobre todo al rechazar o anular",
            "type": "string"
          },
          "reasonCode": {
            "description": "Motivo de rechazo de lista corta (solo con to=rejected)",
            "type": "string",
            "enum": [
              "price",
              "timing",
              "competitor",
              "no_response",
              "other"
            ]
          },
          "channel": {
            "description": "Por dónde salió, al marcarla como enviada a mano",
            "type": "string",
            "enum": [
              "email",
              "whatsapp",
              "in_person",
              "other",
              "mcp"
            ]
          },
          "expectedFrom": {
            "description": "Estado que viste: si no coincide con el actual, 409 sin tocar nada",
            "type": "string"
          }
        },
        "required": [
          "id",
          "to"
        ]
      },
      "group": "quotations"
    },
    {
      "name": "submit_form",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID del formulario"
          },
          "values": {
            "type": "object",
            "propertyNames": {
              "type": "string"
            },
            "additionalProperties": {},
            "description": "Respuestas por clave de campo, p. ej. { email, mobile, firstName }"
          }
        },
        "required": [
          "id",
          "values"
        ]
      },
      "group": "forms"
    },
    {
      "name": "track_shipment",
      "description": "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.\nUn 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.\nEstados: 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "id del envío (campo id de create_shipment o list_contact_shipments)"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "shipments"
    },
    {
      "name": "unpublish_landing_page",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la landing page"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "landings"
    },
    {
      "name": "update_activity",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del seguimiento"
          },
          "activityType": {
            "type": "string",
            "enum": [
              "Reunion",
              "Llamada",
              "Correo",
              "Nota",
              "Whatsapp"
            ]
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "date": {
            "description": "YYYY-MM-DD o ISO 8601",
            "type": "string"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 10000
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "incomplete",
              "complete"
            ]
          },
          "contactId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "companyId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "advisorId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "reminder": {
            "description": "Fecha-hora ISO 8601, o null para quitarlo",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "activities"
    },
    {
      "name": "update_agent",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del agente"
          },
          "name": {
            "type": "string"
          },
          "systemPrompt": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "integrationId": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "openai",
              "anthropic",
              "google"
            ]
          },
          "aiModel": {
            "type": "string"
          },
          "temperature": {
            "type": "number"
          },
          "maxTokens": {
            "type": "number"
          },
          "enabledTools": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "maxToolCallsPerRun": {
            "type": "number"
          },
          "handoffKeywords": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "handoffMessage": {
            "type": "string"
          },
          "autoAssignNewConversations": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "draft"
            ]
          },
          "triggers": {
            "description": "Reemplaza la lista completa",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "message_received",
                    "stage_entered",
                    "no_response_timeout",
                    "manual"
                  ]
                },
                "isActive": {
                  "type": "boolean"
                },
                "priority": {
                  "type": "number"
                },
                "sourceFilters": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "facebook",
                      "instagram"
                    ]
                  }
                },
                "stageIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "pipelineIds": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "keywords": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "excludeIfAssignedToHuman": {
                  "type": "boolean"
                },
                "excludeIfTagsPresent": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "requireTagsPresent": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "noResponseMinutes": {
                  "type": "number"
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "contextSettings": {
            "description": "Se reemplaza completo: las claves no enviadas vuelven a su valor por defecto",
            "type": "object",
            "properties": {
              "messagesLimit": {
                "type": "number"
              },
              "patternWindow": {
                "type": "number"
              },
              "dedupeSimilarityThreshold": {
                "type": "number"
              },
              "batchWindowSeconds": {
                "type": "number"
              },
              "batchMaxWaitSeconds": {
                "type": "number"
              }
            }
          },
          "leadStateSchema": {
            "description": "Reemplaza la lista completa",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "label"
              ]
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "agents"
    },
    {
      "name": "update_automation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la automatización"
          },
          "name": {
            "description": "Nuevo nombre",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "triggers": {
            "description": "Reemplaza TODOS los disparadores del borrador",
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "triggerId": {
                  "description": "Id del disparador existente (de get_automation) para conservarlo",
                  "type": "string"
                },
                "name": {
                  "type": "string",
                  "description": "Nombre del disparador"
                },
                "type": {
                  "type": "string",
                  "description": "Tipo de disparador"
                },
                "description": {
                  "type": "string"
                },
                "filters": {
                  "description": "Filtros del disparador según su tipo",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                },
                "schedule": {
                  "description": "Solo para type 'scheduled'",
                  "type": "object",
                  "properties": {
                    "cronExpression": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    }
                  }
                }
              },
              "required": [
                "name",
                "type"
              ]
            }
          },
          "steps": {
            "description": "Reemplaza TODOS los pasos del borrador",
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            }
          },
          "reEntry": {
            "description": "Solo en automatizaciones que nunca se han publicado",
            "type": "object",
            "properties": {
              "policy": {
                "type": "string",
                "enum": [
                  "one_active_per_contact",
                  "allow"
                ]
              },
              "cooldownMinutes": {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              }
            },
            "required": [
              "policy"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "automations"
    },
    {
      "name": "update_calendar_event",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del evento (de la serie si es una ocurrencia)"
          },
          "occurrenceStart": {
            "description": "ISO de la ocurrencia a editar sola (de list_calendar_events)",
            "type": "string"
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "start": {
            "type": "string"
          },
          "end": {
            "type": "string"
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 5000
              },
              {
                "type": "null"
              }
            ]
          },
          "allDay": {
            "type": "boolean"
          },
          "timezone": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "reunion",
              "tarea",
              "recordatorio"
            ]
          },
          "location": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 500
              },
              {
                "type": "null"
              }
            ]
          },
          "tags": {
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "color": {
                  "type": "string"
                }
              },
              "required": [
                "label",
                "color"
              ]
            }
          },
          "attendees": {
            "description": "Reemplaza la lista completa de invitados",
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "email": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "contactId": {
                  "type": "string"
                },
                "userId": {
                  "type": "string"
                }
              },
              "required": [
                "email"
              ]
            }
          },
          "sendInvitations": {
            "description": "true = ENVÍA correos a los invitados. Default false",
            "type": "boolean"
          },
          "assignedTo": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "contactId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "reminderEnabled": {
            "type": "boolean"
          },
          "reminderMinutesBefore": {
            "minItems": 1,
            "maxItems": 5,
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10080
            }
          },
          "recurrenceRule": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "externalId": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 255
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "calendar"
    },
    {
      "name": "update_campaign_draft",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la campaña (de list_whatsapp_campaigns o create_campaign_draft)"
          },
          "name": {
            "description": "Nuevo nombre",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "description": "Nueva descripción; null la borra",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2000
              },
              {
                "type": "null"
              }
            ]
          },
          "contactListId": {
            "description": "Otra lista de contactos de la organización",
            "type": "string"
          },
          "template": {
            "description": "Cambios de la plantilla",
            "type": "object",
            "properties": {
              "name": {
                "description": "Otra plantilla aprobada en Meta",
                "type": "string"
              },
              "language": {
                "description": "Código de idioma, p. ej. 'es'",
                "type": "string"
              },
              "variableMapping": {
                "description": "Reemplaza el mapeo completo de variables; null lo quita",
                "anyOf": [
                  {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "field",
                            "text"
                          ]
                        },
                        "key": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string"
                        },
                        "fallback": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "source"
                      ]
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "headerMediaUrl": {
                "description": "URL https de la cabecera; null la quita",
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "campaigns"
    },
    {
      "name": "update_company",
      "description": "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. IDENTIFICACIÓN (modo estricto): idType = código DIAN (13 cédula, 31 NIT, 22 CE, 41 pasaporte, 42 doc. extranjero, 50 NIT de otro país…) o un texto inequívoco ('CC', 'NIT'); idNumber sin puntos (para NIT el DV va en dv; si lo mandas pegado o con guion se separa solo; si falta se calcula). idType ambiguo o desconocido ('1', '11', 'Documento') → 422 ID_TYPE_UNRESOLVED con candidates: pregunta al usuario y reintenta con el código; idNumber inválido para el tipo → 400 INVALID_ID_NUMBER. Nunca elijas un candidato por el usuario. ",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la empresa"
          },
          "name": {
            "type": "string"
          },
          "idType": {
            "description": "Código DIAN (31 NIT, 13 cédula…) o texto inequívoco",
            "type": "string"
          },
          "idNumber": {
            "description": "NIT / número de documento sin puntos ni DV",
            "type": "string"
          },
          "dv": {
            "description": "DV del NIT (idType 31); si falta se calcula",
            "type": "string"
          },
          "industry": {
            "type": "string"
          },
          "website": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "city": {
            "description": "Ciudad (Colombia): nombre inequívoco del catálogo DANE",
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio, 5 dígitos (ej. 05001 = Medellín). Deriva city, state y stateCode.",
            "type": "string",
            "minLength": 5,
            "maxLength": 5
          },
          "state": {
            "description": "Departamento (texto); solo pista para desambiguar city",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "size": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "companies"
    },
    {
      "name": "update_company_property",
      "description": "Actualiza una propiedad de empresa por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. required = obligatorio al crear/editar desde la web y la app (isVisible ya NO significa obligatorio): required=false en firstName responde 400 SYSTEM_REQUIRED y required=true en dv o en una de catálogo (cityCode/stateCode) 400 REQUIRED_NOT_ALLOWED. 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clave de la propiedad a actualizar"
          },
          "label": {
            "type": "string"
          },
          "isVisible": {
            "description": "Deprecado: false = ocultar. NO significa obligatorio",
            "type": "boolean"
          },
          "required": {
            "description": "Obligatorio al crear/editar desde la web y la app",
            "type": "boolean"
          },
          "type": {
            "type": "string",
            "enum": [
              "input",
              "select",
              "textarea",
              "number",
              "date",
              "email",
              "phone"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "groupId": {
            "type": "string"
          }
        },
        "required": [
          "key"
        ]
      },
      "group": "fields"
    },
    {
      "name": "update_contact",
      "description": "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. IDENTIFICACIÓN (modo estricto): idType = código DIAN (13 cédula, 31 NIT, 22 CE, 41 pasaporte, 42 doc. extranjero, 50 NIT de otro país…) o un texto inequívoco ('CC', 'NIT'); idNumber sin puntos (para NIT el DV va en dv; si lo mandas pegado o con guion se separa solo; si falta se calcula). idType ambiguo o desconocido ('1', '11', 'Documento') → 422 ID_TYPE_UNRESOLVED con candidates: pregunta al usuario y reintenta con el código; idNumber inválido para el tipo → 400 INVALID_ID_NUMBER. Nunca elijas un candidato por el usuario. Sin idType/idNumber/dv no se toca la identificación.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del contacto"
          },
          "email": {
            "type": "string"
          },
          "mobile": {
            "type": "string"
          },
          "whatsapp_phone": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "companyName": {
            "type": "string"
          },
          "companyId": {
            "description": "_id de la empresa; null o '' desvincula",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "source": {
            "type": "string"
          },
          "companyType": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "city": {
            "description": "Ciudad (Colombia): nombre inequívoco del catálogo DANE",
            "type": "string"
          },
          "cityCode": {
            "description": "Código DANE del municipio, 5 dígitos (ej. 05001 = Medellín). Deriva city, state y stateCode.",
            "type": "string",
            "minLength": 5,
            "maxLength": 5
          },
          "state": {
            "description": "Departamento (texto); solo pista para desambiguar city",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "position": {
            "type": "string"
          },
          "idType": {
            "description": "Código DIAN (13 cédula, 31 NIT…) o texto inequívoco",
            "type": "string"
          },
          "idNumber": {
            "description": "Sin puntos; para NIT sin DV",
            "type": "string"
          },
          "dv": {
            "description": "DV del NIT (idType 31)",
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "address": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "contacts"
    },
    {
      "name": "update_contact_list",
      "description": "Actualizar una lista existente (PUT /lists/:id).\n\n- name / description: siempre editables.\n- filters: solo aplican a listas DINÁMICAS. Reemplazan los filtros existentes. Formato idéntico a create_dynamic_contact_list.\n- contactIds: solo aplican a listas ESTÁTICAS. Reemplazan la lista completa de miembros (útil para eliminar contactos).\n  Si quieres AGREGAR miembros sin reemplazar, usar add_contacts_to_list.\nNo está permitido cambiar el tipo de lista.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la lista a actualizar"
          },
          "name": {
            "description": "Nuevo nombre",
            "type": "string"
          },
          "description": {
            "description": "Nueva descripción",
            "type": "string"
          },
          "filters": {
            "description": "Nuevos filtros para listas dinámicas (reemplaza los existentes)",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "operator": {
                  "type": "string",
                  "enum": [
                    "contains",
                    "equals",
                    "starts with",
                    "ends with",
                    "is empty",
                    "is not empty",
                    "greater_than",
                    "less_than"
                  ]
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "operator"
              ]
            }
          },
          "contactIds": {
            "description": "Nueva lista completa de miembros para listas estáticas (reemplaza). Máx 2000.",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "contact-lists"
    },
    {
      "name": "update_contact_property",
      "description": "Actualiza una propiedad de contacto por su key: label, isVisible (false = ocultarla, true = mostrarla), options, groupId y type. required = obligatorio al crear/editar desde la web y la app (isVisible ya NO significa obligatorio): required=false en firstName responde 400 SYSTEM_REQUIRED y required=true en dv o en una de catálogo (cityCode/stateCode) 400 REQUIRED_NOT_ALLOWED. 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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Clave de la propiedad a actualizar"
          },
          "label": {
            "type": "string"
          },
          "isVisible": {
            "description": "Deprecado: false = ocultar. NO significa obligatorio",
            "type": "boolean"
          },
          "required": {
            "description": "Obligatorio al crear/editar desde la web y la app",
            "type": "boolean"
          },
          "type": {
            "type": "string",
            "enum": [
              "input",
              "select",
              "textarea",
              "number",
              "date",
              "email",
              "phone"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "groupId": {
            "type": "string"
          }
        },
        "required": [
          "key"
        ]
      },
      "group": "fields"
    },
    {
      "name": "update_conversation",
      "description": "Actualizar conversación por ID. Para mover entre etapas usá move_conversation_stage (no pases currentStage aquí).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "ID de la conversación"
          },
          "title": {
            "type": "string"
          },
          "assignedTo": {
            "description": "ID del usuario asignado",
            "type": "string"
          },
          "priority": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          },
          "tags": {
            "description": "Array de tags, ej: [\"venta\", \"soporte\"]",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "metadata": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "value"
              ]
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "conversations"
    },
    {
      "name": "update_conversation_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline."
          },
          "name": {
            "description": "Nuevo nombre (máx. 100 caracteres).",
            "type": "string"
          },
          "isDefault": {
            "description": "true para marcarlo como predeterminado.",
            "type": "boolean"
          }
        },
        "required": [
          "pipelineId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "update_conversation_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "ObjectId del pipeline."
          },
          "stageId": {
            "type": "string",
            "description": "ObjectId de la etapa."
          },
          "name": {
            "type": "string"
          },
          "order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "color": {
            "type": "string"
          },
          "autoAssign": {
            "type": "boolean"
          },
          "assignToTeam": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "webhookEnabled": {
            "type": "boolean"
          },
          "webhookUrl": {
            "description": "URL https, o null para quitarla.",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "reopenOnNewMessage": {
            "type": "boolean"
          }
        },
        "required": [
          "pipelineId",
          "stageId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "update_deal",
      "description": "Update a deal by ID. Campos editables: title, amount, pipeline, status, closingDate, customFields, contact y companyId.\n\nPara asociar un contacto a un deal existente enviar `contact`:\n  - { \"id\": \"<contactId>\" } para usar un contacto ya existente de la organización\n  - { \"mobile\": \"573001234567\", \"name\": \"...\", \"company\": \"...\" } para buscarlo por teléfono y crearlo si no existe\n  - `null` para desasociar el contacto actual.\n\nPara asociar una empresa enviar `companyId` (debe pertenecer a la organización); `null` o \"\" desasocia la empresa actual.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "contact": {
            "description": "Contacto a asociar: { id } o { mobile, ... }; null desasocia",
            "anyOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  }
                },
                "additionalProperties": {}
              },
              {
                "type": "null"
              }
            ]
          },
          "companyId": {
            "description": "Empresa a asociar al deal; null o \"\" desasocia",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "customFields": {
            "description": "Campos personalizados: [{ \"field\": \"<_id del campo>\", \"value\": \"...\" }]. El `field` es el _id de la definición del campo (el que vive en dealsfields), NO su nombre ni su key: un nombre no se resuelve y la escritura se pierde.",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "value": {
                      "anyOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "required": [
                    "field"
                  ]
                }
              },
              {
                "type": "string"
              }
            ]
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "deals"
    },
    {
      "name": "update_deal_checklist",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline de negocios"
          },
          "items": {
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "description": "Id de un ítem existente para conservarlo",
                  "type": "string"
                },
                "name": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120,
                  "description": "Acción, p. ej. 'Enviar cotización'"
                },
                "weight": {
                  "description": "Peso relativo (default 1; se redondea a entero)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1000
                },
                "activityType": {
                  "description": "Tipo de actividad que marca el ítem automáticamente",
                  "anyOf": [
                    {
                      "type": "string",
                      "enum": [
                        "llamada",
                        "mensaje",
                        "reunion",
                        "visita",
                        "correo",
                        "whatsapp",
                        "nota"
                      ]
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "requiredForStage": {
                  "description": "Id de la etapa de este pipeline que exige el ítem (null = informativo)",
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "enforcement": {
                  "description": "block (default) impide mover el negocio; warn solo avisa",
                  "type": "string",
                  "enum": [
                    "block",
                    "warn"
                  ]
                }
              },
              "required": [
                "name"
              ]
            },
            "description": "Lista COMPLETA de ítems"
          }
        },
        "required": [
          "pipelineId",
          "items"
        ]
      },
      "group": "settings"
    },
    {
      "name": "update_deal_field",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del campo"
          },
          "pipelineId": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "date",
              "select",
              "checkbox"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "required": {
            "type": "boolean"
          },
          "requiredForStage": {
            "description": "_id de etapa del pipeline, o null para quitar el requisito",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "enforcement": {
            "type": "string",
            "enum": [
              "block",
              "warn"
            ]
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fields"
    },
    {
      "name": "update_deal_pipeline",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id del pipeline"
          },
          "title": {
            "description": "Nuevo nombre",
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "stageGateEnabled": {
            "description": "Requisitos por etapa activos",
            "type": "boolean"
          },
          "stageGateCumulative": {
            "description": "Exigir también los requisitos de las etapas anteriores",
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "update_deal_stage",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "pipelineId": {
            "type": "string",
            "description": "Id del pipeline de la etapa"
          },
          "stageId": {
            "type": "string",
            "description": "Id de la etapa"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "color": {
            "description": "Color hexadecimal",
            "type": "string"
          },
          "isWon": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ]
          },
          "isLost": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ]
          },
          "probability": {
            "anyOf": [
              {
                "type": "number",
                "minimum": 0,
                "maximum": 100
              },
              {
                "type": "null"
              }
            ]
          },
          "maxDaysInStage": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "pipelineId",
          "stageId"
        ]
      },
      "group": "pipelines"
    },
    {
      "name": "update_email_template",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la plantilla."
          },
          "name": {
            "description": "Nuevo nombre (máx 200 caracteres).",
            "type": "string"
          },
          "emailJson": {
            "description": "Lista COMPLETA de bloques (reemplaza la anterior).",
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            }
          },
          "emailHtml": {
            "description": "HTML crudo (solo si no mandas emailJson). Se sanitiza.",
            "type": "string"
          },
          "previewText": {
            "description": "Texto de vista previa (solo aplica con emailJson).",
            "type": "string"
          },
          "saved": {
            "description": "true = en la galería de guardadas; false = borrador.",
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "templates"
    },
    {
      "name": "update_form",
      "description": "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).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del formulario (de list_forms)"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "fields": {
            "description": "Reemplaza la lista de campos",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Clave de la propiedad de contacto (email, mobile, firstName…)"
                },
                "label": {
                  "type": "string"
                },
                "placeholder": {
                  "type": "string"
                },
                "type": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "order": {
                  "type": "number"
                }
              },
              "required": [
                "key"
              ]
            }
          },
          "hiddenFields": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "utm_source",
                "utm_medium",
                "utm_campaign",
                "utm_term",
                "utm_content",
                "gclid",
                "fbclid",
                "referrer",
                "page_url"
              ]
            }
          },
          "theme": {
            "type": "object",
            "properties": {
              "primaryColor": {
                "description": "Hex de 6 dígitos, ej. #E11D48",
                "type": "string"
              },
              "logoUrl": {
                "type": "string"
              },
              "buttonText": {
                "type": "string"
              },
              "showBranding": {
                "type": "boolean"
              },
              "title": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "appearance": {
                "type": "string",
                "enum": [
                  "light",
                  "dark"
                ]
              }
            }
          },
          "afterSubmit": {
            "type": "object",
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "message",
                  "redirect",
                  "whatsapp"
                ]
              },
              "message": {
                "type": "string"
              },
              "redirectUrl": {
                "type": "string"
              }
            }
          },
          "createContact": {
            "type": "boolean"
          },
          "defaultSource": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "listId": {
            "description": "Lista estática donde se agrega el contacto; null la quita",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "ownerId": {
            "description": "Usuario dueño de los contactos creados; null lo quita",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "deal": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean"
              },
              "pipelineId": {
                "type": "string"
              },
              "stageId": {
                "type": "string"
              },
              "titleTemplate": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              }
            }
          },
          "notify": {
            "type": "object",
            "properties": {
              "emails": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "inApp": {
                "type": "boolean"
              }
            }
          },
          "allowedDomains": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "forms"
    },
    {
      "name": "update_fragment",
      "description": "Actualizar un fragment por ID. Campos editables: name, text, atajo, format, asunto.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "atajo": {
            "type": "string"
          },
          "format": {
            "description": "Formato del contenido: \"text\" (texto plano / WhatsApp) o \"html\" (correo con formato). Por defecto \"text\".",
            "type": "string",
            "enum": [
              "text",
              "html"
            ]
          },
          "asunto": {
            "description": "Asunto del correo (solo aplica a fragmentos de formato \"html\").",
            "type": "string"
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "fragments"
    },
    {
      "name": "update_landing_page",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la landing page (de list_landing_pages)"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "slug": {
            "description": "Nuevo slug (letras, números y guiones). Cambia la URL pública y rompe los enlaces viejos",
            "type": "string",
            "minLength": 3
          },
          "formId": {
            "description": "Id de formulario de la organización; null lo desasocia",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "sections": {
            "description": "Lista COMPLETA de secciones en el orden final",
            "maxItems": 12,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "description": "Id de la sección existente (consérvalo al editar)",
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "hero",
                    "benefits",
                    "text",
                    "gallery",
                    "testimonials",
                    "faq",
                    "whatsapp_cta",
                    "form"
                  ]
                },
                "enabled": {
                  "type": "boolean"
                },
                "data": {
                  "description": "Contenido del bloque según su tipo",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "theme": {
            "type": "object",
            "properties": {
              "primaryColor": {
                "type": "string"
              },
              "accentColor": {
                "type": "string"
              },
              "logoUrl": {
                "type": "string"
              },
              "fontPreset": {
                "type": "string",
                "enum": [
                  "system",
                  "serif",
                  "rounded"
                ]
              },
              "showBranding": {
                "type": "boolean"
              }
            }
          },
          "seo": {
            "type": "object",
            "properties": {
              "title": {
                "type": "string",
                "maxLength": 120
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "ogImageUrl": {
                "type": "string"
              },
              "faviconUrl": {
                "type": "string"
              },
              "indexable": {
                "type": "boolean"
              }
            }
          },
          "tracking": {
            "type": "object",
            "properties": {
              "metaPixelId": {
                "description": "ID del Pixel de Meta (15-16 dígitos); vacío lo quita",
                "type": "string"
              },
              "ga4MeasurementId": {
                "description": "ID de GA4 G-XXXXXXXXXX; vacío lo quita",
                "type": "string"
              }
            }
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "phone": {
                "type": "string"
              },
              "message": {
                "type": "string",
                "maxLength": 300
              },
              "sticky": {
                "type": "boolean"
              }
            }
          },
          "legal": {
            "type": "object",
            "properties": {
              "privacyUrl": {
                "description": "URL https de la política de datos propia",
                "type": "string"
              }
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "landings"
    },
    {
      "name": "update_library_file",
      "description": "Editar los METADATOS de un archivo YA subido a la Biblioteca. Campos editables: name y tags.\n\nNO sube archivos, NO reemplaza el contenido del archivo y NO cambia su mediaURL:\núnicamente renombra y reetiqueta algo que ya está en la Biblioteca.\nOjo: 'tags' REEMPLAZA la lista completa de tags (no agrega). Para añadir uno,\nlee los tags actuales con get_library_file y envía la lista completa ya con el nuevo.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "description": "Nuevo nombre visible del archivo",
            "type": "string"
          },
          "tags": {
            "description": "Lista COMPLETA de tags que reemplaza a la actual (se normalizan a minúsculas)",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "library"
    },
    {
      "name": "update_organization_settings",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "companyName": {
            "description": "Nombre de la empresa",
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "idType": {
            "description": "Tipo de identificación, p. ej. 'Nit' o 'CC'",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "idNumber": {
            "description": "Número de identificación (NIT con dígito de verificación si aplica)",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "email": {
            "description": "Correo de contacto de la empresa",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "phone": {
            "description": "Teléfono de la empresa",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "whatsapp": {
            "description": "WhatsApp de contacto que se muestra (no cambia el número conectado a la API de WhatsApp)",
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "address": {
            "description": "Dirección; solo se cambian las partes enviadas",
            "type": "object",
            "properties": {
              "address": {
                "description": "Dirección",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 200
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "city": {
                "description": "Ciudad en texto. En Colombia se resuelve contra el catálogo DANE: si coincide asigna cityCode; si no, queda sin confirmar (cityCode vacío)",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 200
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "cityCode": {
                "description": "Código DANE del municipio (5 dígitos, ej. '05001' Medellín; búscalo con GET /catalogs/municipalities?q=). Si cambia, MANDA: city y state se derivan del catálogo. Código inexistente → 400",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 5
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "state": {
                "description": "Departamento o estado",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 200
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "zipCode": {
                "description": "Código postal",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 200
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "country": {
                "description": "País",
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 200
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            }
          },
          "settings": {
            "description": "Ajustes generales; solo se cambian las claves enviadas",
            "type": "object",
            "properties": {
              "timezone": {
                "description": "Zona horaria IANA, p. ej. 'America/Bogota'",
                "type": "string"
              },
              "locale": {
                "description": "Idioma/región BCP-47, p. ej. 'es-CO' o 'en-US'",
                "type": "string"
              },
              "currency": {
                "description": "Moneda ISO-4217, p. ej. 'COP' o 'USD'",
                "type": "string",
                "minLength": 3,
                "maxLength": 3
              },
              "automationSendWindow": {
                "description": "Ventana de envío de automatizaciones; se reemplaza completa, null la quita",
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "description": "true activa la ventana"
                      },
                      "days": {
                        "description": "Días en que SÍ se envía: 0=domingo … 6=sábado (obligatorio si enabled)",
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 6
                        }
                      },
                      "startHour": {
                        "description": "Desde qué hora se envía, HH:mm (obligatorio si enabled)",
                        "type": "string"
                      },
                      "endHour": {
                        "description": "Hasta qué hora, HH:mm (igual a startHour = día completo)",
                        "type": "string"
                      },
                      "timezone": {
                        "description": "Zona IANA de la ventana; sin ella usa la de la organización",
                        "type": "string"
                      },
                      "channels": {
                        "description": "Canales afectados (default whatsapp)",
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "whatsapp",
                            "email"
                          ]
                        }
                      }
                    },
                    "required": [
                      "enabled"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "quotations": {
                "description": "Valores por defecto de cotizaciones nuevas (el consecutivo no se edita)",
                "type": "object",
                "properties": {
                  "paymentTerms": {
                    "description": "Condiciones de pago por defecto",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "shippingTerms": {
                    "description": "Condiciones de envío por defecto",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "notes": {
                    "description": "Notas por defecto",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 5000
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "footerText": {
                    "description": "Texto del pie del PDF",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 5000
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "validityDays": {
                    "description": "Días de validez por defecto",
                    "anyOf": [
                      {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 365
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "numberPrefix": {
                    "description": "Prefijo visual del número, p. ej. 'COT-'",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 200
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "taxLabel": {
                    "description": "Rótulo del impuesto en el PDF, p. ej. 'IVA'",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 200
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              "purchases": {
                "description": "Valores por defecto de órdenes de compra nuevas (el consecutivo no se edita)",
                "type": "object",
                "properties": {
                  "paymentTerms": {
                    "description": "Condiciones de pago por defecto",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "shippingTerms": {
                    "description": "Condiciones de envío por defecto",
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "notes": {
                    "description": "Notas por defecto",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 5000
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              "reportsSettings": {
                "description": "Ajustes de reportes",
                "type": "object",
                "properties": {
                  "productQuantityFieldKey": {
                    "description": "Key del campo personalizado de negocios que se suma en el KPI 'Productos vendidos'",
                    "anyOf": [
                      {
                        "type": "string",
                        "maxLength": 100
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "group": "settings"
    },
    {
      "name": "update_product",
      "description": "Actualizar producto por ID (PUT /products/:id). Solo se tocan los campos enviados.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del producto"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "unitPrice": {
            "type": "number"
          },
          "taxes": {
            "description": "Porcentaje de impuesto, ej. 19",
            "type": "number"
          },
          "cost": {
            "type": "number"
          },
          "sku": {
            "type": "string"
          },
          "categoryId": {
            "type": "string"
          },
          "imageUrl": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "products"
    },
    {
      "name": "update_product_variant",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la variante"
          },
          "sku": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "price": {
            "type": "number",
            "minimum": 0
          },
          "stock": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 0,
                "maximum": 9007199254740991
              },
              {
                "type": "null"
              }
            ]
          },
          "imageUrl": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "attributeValues": {
            "description": "Lista COMPLETA de atributos (reemplaza la actual)",
            "maxItems": 20,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "attributeName": {
                  "type": "string",
                  "minLength": 1
                },
                "value": {
                  "type": "string",
                  "minLength": 1
                },
                "displayName": {
                  "type": "string"
                },
                "valueId": {
                  "type": "string"
                },
                "price": {
                  "type": "number",
                  "minimum": 0
                },
                "imageUrl": {
                  "type": "string"
                }
              },
              "required": [
                "attributeName",
                "value"
              ]
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "products"
    },
    {
      "name": "update_project",
      "description": "Actualizar proyecto por ID (PUT /projects/:id). Solo se tocan los campos enviados.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id del proyecto"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "startDate": {
            "description": "ISO",
            "type": "string"
          },
          "endDate": {
            "description": "ISO, no anterior a startDate",
            "type": "string"
          },
          "budget": {
            "type": "number"
          },
          "status": {
            "type": "string"
          },
          "color": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "projects"
    },
    {
      "name": "update_purchase",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la orden de compra"
          },
          "orderNumber": {
            "type": "string"
          },
          "total": {
            "type": "number"
          },
          "tax": {
            "type": "number"
          },
          "discounts": {
            "type": "number"
          },
          "paymentMethod": {
            "type": "string"
          },
          "supplierId": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ]
          },
          "supplierName": {
            "type": "string"
          },
          "supplierNIT": {
            "type": "string"
          },
          "purchaseDate": {
            "description": "YYYY-MM-DD",
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "voucherType": {
            "type": "string",
            "enum": [
              "Factura electrónica",
              "Nota de crédito electrónica",
              "Documento soporte con no obligados",
              "Application response",
              "Otro"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "completed",
              "canceled"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "quantity": {
                  "type": "number"
                },
                "unitPrice": {
                  "type": "number"
                },
                "tax": {
                  "type": "number"
                },
                "total": {
                  "type": "number"
                }
              },
              "required": [
                "name",
                "quantity",
                "unitPrice"
              ]
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "purchases"
    },
    {
      "name": "update_quotation",
      "description": "Actualizar cotización por ID (PUT /quotations/:id). El número de cotización no se puede cambiar. status NO es libre: sigue la matriz de estados (draft↔ready; draft/ready→sent; sent→accepted|rejected|canceled; accepted→sent|canceled; rejected→sent|canceled; expired→sent|canceled). Una transición inválida responde 409 INVALID_TRANSITION con `allowed`. Facturada (invoiced) y Vencida (expired) las escribe solo el sistema (un cron diario vence las Enviadas al terminar el día de expirationDate en la zona de la org). Ponerle a una Vencida una expirationDate futura la devuelve sola a Enviada. Una cotización invoiced o canceled no se puede editar (409 QUOTATION_LOCKED).",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la cotización"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "description": "Nuevo estado; debe ser una transición válida desde el actual",
            "type": "string",
            "enum": [
              "draft",
              "ready",
              "sent",
              "accepted",
              "rejected",
              "canceled"
            ]
          },
          "reason": {
            "description": "Motivo (rechazo o anulación)",
            "type": "string"
          },
          "reasonCode": {
            "description": "Motivo de rechazo de lista corta (solo con status rejected)",
            "type": "string",
            "enum": [
              "price",
              "timing",
              "competitor",
              "no_response",
              "other"
            ]
          },
          "channel": {
            "description": "Por dónde se envió (solo con status sent; marcarla enviada NO manda nada)",
            "type": "string",
            "enum": [
              "email",
              "whatsapp",
              "in_person",
              "other",
              "mcp"
            ]
          },
          "expirationDate": {
            "description": "Fecha de vencimiento (ISO)",
            "type": "string"
          },
          "paymentTerms": {
            "type": "string"
          },
          "shippingTerms": {
            "type": "string"
          },
          "observaciones": {
            "type": "string"
          },
          "additionalNotes": {
            "type": "string"
          },
          "items": {
            "description": "Reemplaza TODOS los ítems (name, quantity, unitPrice, taxes, total…)",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {},
              "additionalProperties": {}
            }
          },
          "subtotal": {
            "type": "number"
          },
          "taxes": {
            "type": "number"
          },
          "discounts": {
            "type": "number"
          },
          "total": {
            "type": "number"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "quotations"
    },
    {
      "name": "update_scoring_rule",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id de la regla"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 1000
              },
              {
                "type": "null"
              }
            ]
          },
          "isActive": {
            "type": "boolean"
          },
          "rules": {
            "description": "Lista COMPLETA de condiciones (reemplaza las actuales)",
            "minItems": 1,
            "maxItems": 50,
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "propertyName": {
                  "type": "string"
                },
                "condition": {
                  "type": "string",
                  "description": "Una de las condiciones de create_scoring_rule"
                },
                "value": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    },
                    {
                      "type": "array",
                      "items": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          },
                          {
                            "type": "boolean"
                          }
                        ]
                      }
                    },
                    {
                      "type": "object",
                      "properties": {
                        "fieldId": {
                          "type": "string"
                        },
                        "value": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      },
                      "required": [
                        "fieldId",
                        "value"
                      ]
                    }
                  ]
                },
                "points": {
                  "type": "number",
                  "description": "Entre -10000 y 10000"
                }
              },
              "required": [
                "condition",
                "points"
              ]
            }
          }
        },
        "required": [
          "id"
        ]
      },
      "group": "settings"
    },
    {
      "name": "update_task",
      "description": "Actualizar tarea por ID (PUT /tasks/:id). Solo se tocan los campos enviados.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "_id de la tarea"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "dueDate": {
            "description": "ISO",
            "type": "string"
          },
          "priority": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "notes": {
            "type": "string"
          },
          "budget": {
            "type": "number"
          },
          "timeline": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "responsibleId": {
            "type": "string"
          },
          "projectId": {
            "type": "string"
          },
          "contactId": {
            "type": "string"
          }
        },
        "required": [
          "id"
        ],
        "additionalProperties": {}
      },
      "group": "tasks"
    },
    {
      "name": "upload_knowledge_base_text",
      "description": "Sube un documento de TEXTO PLANO a la base de conocimiento (RAG) de la organización.\nLa indexación (chunking + embeddings) corre en segundo plano: el documento queda en\nstatus 'processing' y pasa a 'ready' en segundos (verificable con\nlist_knowledge_base_documents). Solo texto plano; archivos binarios (PDF, DOCX) se\nsuben desde la app. Requiere permiso rag:write.\nPara REEMPLAZAR un documento existente (p. ej. una lista de precios), sube primero la\nversión nueva y luego elimina la vieja con delete_knowledge_base_document — si quedan\nlas dos, la búsqueda puede mezclar información desactualizada.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Título del documento (visible en el listado)"
          },
          "text": {
            "type": "string",
            "description": "Contenido en texto plano (máx 2 MB)"
          },
          "tags": {
            "description": "Tags para filtrar búsquedas (p. ej. ['precios'])",
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "title",
          "text"
        ]
      },
      "group": "knowledge-base"
    },
    {
      "name": "validate_automation",
      "description": "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.",
      "inputSchema": {
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre de la automatización"
          },
          "triggers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Nombre del disparador"
                },
                "type": {
                  "type": "string",
                  "description": "Tipo de disparador (ver list_automation_variables)"
                },
                "description": {
                  "type": "string"
                },
                "filters": {
                  "description": "Filtros del disparador según su tipo",
                  "type": "object",
                  "propertyNames": {
                    "type": "string"
                  },
                  "additionalProperties": {}
                },
                "schedule": {
                  "description": "Solo para type 'scheduled'",
                  "type": "object",
                  "properties": {
                    "cronExpression": {
                      "type": "string"
                    },
                    "timezone": {
                      "type": "string"
                    }
                  }
                }
              },
              "required": [
                "name",
                "type"
              ]
            },
            "description": "Disparadores"
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "propertyNames": {
                "type": "string"
              },
              "additionalProperties": {}
            },
            "description": "Pasos: { stepId?, type, name?, enabled?, onError?, config }; 'condition' lleva yes/no y 'wait' su config"
          }
        },
        "required": [
          "name",
          "triggers",
          "steps"
        ]
      },
      "group": "automations"
    }
  ]
}
