openapi: 3.0.3
info:
  title: Cliengo Public API
  version: "1.0"
  description: |
    API pública unificada de Cliengo. Punto de entrada único para gestionar contactos,
    conversaciones, canales, chatbots, planes, usuarios y plantillas de WhatsApp.

    ---

    ## Obtener tu Token

    1. Ingresá a [dash.cliengo.com/integrations/connect_api](https://dash.cliengo.com/integrations/connect_api)
    2. Generá un nuevo **Token** seleccionando los permisos que necesites
    3. Copiá tu token (formato `sk_test_...` en stage, `sk_live_...` en producción)

    ---

    ## Autenticación

    Todas las requests requieren el header `Authorization: Bearer <token>`.

    ```bash
    curl -H "Authorization: Bearer TU_TOKEN" https://connect.cliengo.com/v1/plans
    ```

    Si recibís un JSON con planes, tu token funciona.

    ---

    ## Rate Limiting

    **1000 requests por minuto** por token. Cada respuesta incluye:

    | Header | Significado |
    |--------|-------------|
    | `X-RateLimit-Limit` | Máximo por ventana |
    | `X-RateLimit-Remaining` | Restantes |
    | `X-RateLimit-Reset` | Unix timestamp del reset |

    Si excedés el límite → HTTP **429** con header `Retry-After`.

    ---

    ## Errores comunes

    | Código | Causa | Solución |
    |--------|-------|----------|
    | 401 | Falta header de auth o token inválido | Verificá tu `Authorization: Bearer` header |
    | 404 | Endpoint no existe | Revisá la URL |
    | 429 | Rate limit excedido | Esperá `Retry-After` segundos |

    ---

    ## Trazabilidad

    Cada respuesta incluye `X-Request-ID` (UUID). Podés enviar tu propio valor
    en el request para correlacionar logs.

  contact:
    name: Cliengo Engineering
    email: hola@cliengo.com
    url: https://cliengo.com
  license:
    name: Proprietary

servers:
  - url: https://connect.cliengo.com
    description: Producción

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Token de autenticación (formato `sk_live_...`).
        Obtener token en [dash.cliengo.com/integrations/connect_api](https://dash.cliengo.com/integrations/connect_api).

  schemas:
    Contact:
      type: object
      properties:
        id:
          type: string
          example: "60a1b2c3d4e5f6a7b8c9d0e1"
        name:
          type: string
          example: "Juan Pérez"
        email:
          type: string
          format: email
          example: "juan@example.com"
        phone:
          type: string
          example: "+5491155551234"
        internationalPhoneNumber:
          type: string
          example: "5491155551234"
        channelId:
          type: string
          description: ID del canal donde se originó el contacto
        companyId:
          type: string
        conversationId:
          type: string
        status:
          type: string
          enum: [new, active, client, long_term]
        subStatus:
          type: string
        message:
          type: string
          description: Último mensaje del contacto
        assignedTo:
          type: string
          nullable: true
          description: ID del agente asignado
        rating:
          type: integer
        entryMethod:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    Pagination:
      type: object
      properties:
        page:
          type: integer
          example: 1
        limit:
          type: integer
          example: 20
        total:
          type: integer
          example: 42
        totalPages:
          type: integer
          example: 3
        hasNext:
          type: boolean
        hasPrev:
          type: boolean

    ContactSearchResponse:
      type: object
      properties:
        pagination:
          $ref: "#/components/schemas/Pagination"
        data:
          type: array
          items:
            $ref: "#/components/schemas/Contact"

    Conversation:
      type: object
      properties:
        id:
          type: string
          readOnly: true
          example: "626e9cc66ac98128162b2bbc"
        channelId:
          type: string
          example: "5f08e255c9a881002afc06ec"
        companyId:
          type: string
          readOnly: true
          example: "5c7414a4e4b06f6eb79408f5"
        channel:
          $ref: "#/components/schemas/Channel"
        status:
          type: string
          enum: [ACTIVE, CLOSED]
        lastMessage:
          type: string
          readOnly: true
          example: "será un placer asistirte, cuál es tu nombre?"
        lastMessageAt:
          type: string
          format: date-time
          readOnly: true
        visitorName:
          type: string
          example: "Robert"
        visitorEmail:
          type: string
          example: "robert@example.com"
        visitorPhone:
          type: string
          example: "584122233456"
        participants:
          type: array
          readOnly: true
          items:
            $ref: "#/components/schemas/Participant"
        tags:
          type: array
          items:
            type: string
        closed:
          type: boolean
        createdAt:
          type: string
          format: date-time
          readOnly: true

    Channel:
      type: string
      enum: [FACEBOOK, WEB, WHATSAPP, INSTAGRAM]

    Participant:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
          enum: [robot, visitor, user]

    Message:
      type: object
      properties:
        id:
          type: string
          readOnly: true
        user:
          type: string
          readOnly: true
        text:
          type: string
          example: "Hola, necesito ayuda"
        createdAt:
          type: string
          format: date-time
          readOnly: true
        attachments:
          type: array
          items:
            type: object
            properties:
              title:
                type: string
              text:
                type: string
              image_url:
                type: string
                format: uri
        edited:
          type: boolean
          readOnly: true

    ConversationListResponse:
      type: object
      properties:
        pagination:
          $ref: "#/components/schemas/Pagination"
        data:
          type: array
          items:
            $ref: "#/components/schemas/Conversation"

    ChannelSite:
      type: object
      description: |
        Vista de un canal (sitio/integración) en el modelo omnichannel. El campo
        `id` es el identificador del canal (compatible con el `channelId`
        legacy = websiteId). Reemplazó a la respuesta legacy
        `{ id, title, url, company, enabled, integrations }` del backend
        anterior. El chatbot asociado se incluye embebido — usar su `id` para
        operar contra `/v1/chatbots/{chatbotId}`.
      properties:
        id:
          type: string
          description: ID del canal (== websiteId histórico).
        type:
          type: string
          enum: [WEBSITE, WHATSAPP_API, WHATSAPP_BUSINESS, FACEBOOK, INSTAGRAM]
          description: Tipo del canal.
        label:
          type: string
          description: Nombre legible (título del sitio, número de WhatsApp, page de FB, etc.).
        connected:
          type: boolean
          description: Indica si el canal está conectado y recibiendo mensajes.
        testChannelUrl:
          type: string
          format: uri
          description: URL para probar el chatbot en este canal.
        chatbot:
          allOf:
            - $ref: "#/components/schemas/ChatbotSummary"
          nullable: true
          description: Chatbot asignado al canal (resumen). `null` si ninguno.
        whatsappForm:
          type: boolean
          description: Solo aplica a canales WhatsApp — indica si el formulario de configuración está activo.
        disconnectionRequested:
          type: boolean
          description: Solo WhatsApp API — indica que se está procesando la desconexión.

    ChatbotSummary:
      type: object
      description: Resumen del chatbot devuelto cuando se incluye dentro de un canal.
      properties:
        id:
          type: string
        chatbotWithAi:
          type: boolean
          description: True si alguna pregunta del flujo o el `globalFulfillment` apunta a un endpoint de IA (zordon).
        isMuted:
          type: boolean
        title:
          type: string
        name:
          type: string
        connectedChannels:
          type: integer

    Plan:
      type: object
      properties:
        id:
          type: string
          example: "CLIENGO_STARTER"
        name:
          type: string
          example: "Leads Cliengo Starter"
        description:
          type: string
        tier:
          type: string
        annualPlan:
          type: boolean
        leadLimit:
          type: integer
        conversationLimit:
          type: integer
        channelLimit:
          type: integer
        userLimit:
          type: integer
        priceUSD:
          type: number
        priceARS:
          type: number

    Account:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
        plan:
          type: string
        features:
          type: object
        statuses:
          type: array
          items:
            type: string

    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        role:
          type: string
        active:
          type: boolean

    Chatbot:
      type: object
      description: |
        Vista del chatbot en el modelo omnichannel. El `id` ahora es el
        identificador propio del chatbot (no el websiteId/channelId, como
        era en la versión legacy del backend). Para asignarlo a uno o más
        canales usá `PUT /v1/chatbots/{chatbotId}/channels`.
      properties:
        id:
          type: string
          description: ID del chatbot. Diferente al channelId — un chatbot puede asignarse a múltiples canales.
        chatbotWithAi:
          type: boolean
          description: True si alguna pregunta del flujo o el `globalFulfillment` apunta a un endpoint de IA (zordon).
        isMuted:
          type: boolean
          description: Cuando es true, el chatbot no responde mensajes (los visitantes pasan directo a operador).
        title:
          type: string
          description: Título visible del chatbot (mostrado al visitante).
        name:
          type: string
          description: Nombre interno del chatbot.
        channels:
          type: array
          description: Canales actualmente asignados a este chatbot.
          items:
            type: object
            properties:
              id:
                type: string
              type:
                type: string
              label:
                type: string
        testChatbotUrl:
          type: string
          format: uri
          description: URL para probar el chatbot en un widget de prueba.

    Trigger:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
        enabled:
          type: boolean
        conditions:
          type: object
        actions:
          type: object

    WhatsAppTemplate:
      type: object
      properties:
        id:
          type: integer
          description: ID interno de la plantilla
          example: 170
        elementName:
          type: string
          description: Nombre identificador de la plantilla
          example: promo_belleza_personalizada
        status:
          type: string
          enum: [APPROVED, PENDING, REJECTED]
          description: Estado de aprobación en Meta
        category:
          type: string
          enum: [MARKETING, UTILITY, AUTHENTICATION]
          description: Categoría de la plantilla
        text:
          type: string
          description: Contenido del mensaje con variables ({name}, {1}, etc.)
        language:
          type: string
          description: Código de idioma (es, en, pt, etc.)
        components:
          type: array
          items:
            type: object

    WhatsAppCampaign:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        status:
          type: string
          enum: [SCHEDULED, SENDING, SENT, STOPPED, CANCELLED]
        templateId:
          type: string
        scheduledAt:
          type: string
          format: date-time
        sentCount:
          type: integer
        failedCount:
          type: integer

    WhatsAppEvent:
      type: object
      properties:
        id:
          type: integer
        destination:
          type: string
          description: Número de teléfono del destinatario
        status:
          type: string
          enum: [sent, delivered, read, failed, invalid, enqueued, pending]
        failureCode:
          type: string
          nullable: true
        failureReason:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    WhatsAppCampaignFull:
      allOf:
        - $ref: "#/components/schemas/WhatsAppCampaign"
        - type: object
          properties:
            events:
              type: array
              items:
                $ref: "#/components/schemas/WhatsAppEvent"
            eventCounter:
              type: object
              properties:
                pending:
                  type: integer
                enqueued:
                  type: integer
                failed:
                  type: integer
                sent:
                  type: integer
                delivered:
                  type: integer
                read:
                  type: integer
                answered:
                  type: integer
                invalid:
                  type: integer

    Note:
      type: object
      properties:
        id:
          type: string
        text:
          type: string
        createdAt:
          type: string
          format: date-time

    OperatorTag:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        color:
          type: string

    Phase:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        order:
          type: integer

    FeaturesResponse:
      type: object
      properties:
        features:
          type: object
          description: Mapa de features habilitadas y sus valores
          additionalProperties: true
          example:
            livechat: true
            whatsapp: true
            campaigns: false

    Integration:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
        enabled:
          type: boolean
        config:
          type: object

    PhaseCount:
      type: object
      properties:
        phaseId:
          type: string
        phaseName:
          type: string
        count:
          type: integer

    # ── Webhook Schemas ──────────────────────────
    WebhookEventType:
      type: string
      description: |
        Tipo de evento al que se puede suscribir un webhook. Formato: `categoría/acción`.
      enum:
        - conversation/created
        - conversation/updated
        - conversation/deleted
        - conversation/operator_assigned
        - conversation/message
        - conversation/note
        - conversation/archived
        - conversation/contact
        - conversation/phase_changed
        - conversation/tag_added
        - conversation/tag_removed
        - contact/created
        - contact/updated
        - contact/status_changed
        - contact/operator_assigned
        - contact/tag_added
        - contact/deleted

    WebhookSubscription:
      type: object
      properties:
        id:
          type: string
          example: "60a1b2c3d4e5f6a7b8c9d0e1"
        companyId:
          type: string
        websiteId:
          type: string
          description: "ID del canal o `*` para todos los canales"
          example: "*"
        url:
          type: string
          format: uri
          example: "https://tu-servidor.com/webhook"
        eventTypes:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
          example: ["conversation/created", "contact/created"]
        isActive:
          type: boolean
          example: true
        secret:
          type: string
          description: "Token secreto enviado en el header `X-Webhook-Secret` de cada entrega"
          example: "whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456"
        retryCount:
          type: integer
          minimum: 0
          maximum: 10
          description: "Cantidad máxima de reintentos en caso de fallo (backoff exponencial)"
          example: 5
        customHeaders:
          type: object
          additionalProperties:
            type: string
          description: "Headers personalizados enviados con cada entrega"
          example: {"X-Custom-Key": "valor"}
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    CreateWebhookRequest:
      type: object
      required: [url, eventTypes]
      properties:
        url:
          type: string
          format: uri
          description: URL donde Cliengo enviará los eventos via POST
          example: "https://tu-servidor.com/webhook"
        eventTypes:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/WebhookEventType"
          description: "Eventos a los que suscribirse (al menos uno)"
          example: ["conversation/created", "conversation/message"]
        websiteId:
          type: string
          description: "ID del canal. Usar `*` o omitir para recibir eventos de todos los canales"
          default: "*"
        retryCount:
          type: integer
          minimum: 0
          maximum: 10
          default: 5
          description: "Reintentos en caso de fallo (0 = sin reintentos, máximo 10)"
        customHeaders:
          type: object
          additionalProperties:
            type: string
          description: "Headers extra a incluir en cada entrega"

    UpdateWebhookRequest:
      type: object
      properties:
        url:
          type: string
          format: uri
        eventTypes:
          type: array
          items:
            $ref: "#/components/schemas/WebhookEventType"
        isActive:
          type: boolean
        retryCount:
          type: integer
          minimum: 0
          maximum: 10
        customHeaders:
          type: object
          additionalProperties:
            type: string

    WebhookDeliveryLog:
      type: object
      description: Registro de un intento de entrega de un webhook.
      properties:
        id:
          type: string
        webhookId:
          type: string
        url:
          type: string
          format: uri
        event:
          type: string
          description: Tipo de evento entregado
          example: "conversation/created"
        payload:
          type: object
          description: Body JSON enviado al endpoint
        statusCode:
          type: integer
          nullable: true
          description: "HTTP status de la respuesta (null si no hubo respuesta)"
          example: 200
        responseBody:
          type: string
          nullable: true
          description: Body de la respuesta del servidor
        errorMessage:
          type: string
          nullable: true
          description: "Mensaje de error (null si exitoso)"
        success:
          type: boolean
        createdAt:
          type: string
          format: date-time

  parameters:
    LimitParam:
      name: limit
      in: query
      schema:
        type: integer
        default: 20
        maximum: 100
      description: Resultados por página
    OffsetParam:
      name: offset
      in: query
      schema:
        type: integer
        default: 0
      description: Offset de paginación
    PageParam:
      name: page
      in: query
      schema:
        type: integer
        default: 1
      description: Número de página
    webhookId:
      name: webhookId
      in: path
      required: true
      schema:
        type: string
      description: ID del webhook

paths:
  # ── System ──────────────────────────────────────
  /health:
    get:
      summary: Health check
      operationId: healthCheck
      security: []
      tags: [System]
      responses:
        "200":
          description: Gateway operativo
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"

  # ── Contacts ────────────────────────────────────
  /v1/contacts:
    get:
      summary: Buscar contactos
      operationId: searchContacts
      tags: [Contacts]
      description: Búsqueda de contactos con filtros avanzados.
      parameters:
        - name: search
          in: query
          schema:
            type: string
          description: "Búsqueda fuzzy en nombre, email y teléfono"
        - name: email
          in: query
          schema:
            type: string
          description: Filtrar por email (búsqueda parcial)
        - name: name
          in: query
          schema:
            type: string
          description: Filtrar por nombre (búsqueda parcial)
        - name: phone
          in: query
          schema:
            type: string
          description: Filtrar por teléfono (búsqueda parcial)
        - name: status
          in: query
          schema:
            type: string
            enum: [new, active, client, long_term]
          description: Filtrar por estado del contacto
        - name: subStatus
          in: query
          schema:
            type: string
          description: Filtrar por sub-estado
        - name: websiteId
          in: query
          schema:
            type: string
          description: "Filtrar por canal (IDs separados por coma)"
        - name: assignedTo
          in: query
          schema:
            type: string
          description: "Filtrar por agente asignado (IDs separados por coma, o UNASSIGNED)"
        - name: entryMethod
          in: query
          schema:
            type: string
          description: Filtrar por método de entrada (WEB, WHATSAPP, FACEBOOK, etc.)
        - name: conversationTags
          in: query
          schema:
            type: string
          description: "Filtrar por tags de conversación (separados por coma)"
        - name: rating
          in: query
          schema:
            type: string
          description: "Filtrar por calificación (valores separados por coma)"
        - name: dateFrom
          in: query
          schema:
            type: string
            format: date-time
          description: Contactos creados después de esta fecha (ISO 8601)
        - name: dateTo
          in: query
          schema:
            type: string
            format: date-time
          description: Contactos creados antes de esta fecha (ISO 8601)
        - name: lastUpdateDateFrom
          in: query
          schema:
            type: string
            format: date-time
          description: Contactos actualizados después de esta fecha (ISO 8601)
        - name: lastUpdateDateTo
          in: query
          schema:
            type: string
            format: date-time
          description: Contactos actualizados antes de esta fecha (ISO 8601)
        - name: orderBy
          in: query
          schema:
            type: string
            default: _id
          description: "Campo para ordenar (ej: creationDate, name, email)"
        - name: order
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
          description: Dirección de ordenamiento
        - name: page
          in: query
          schema:
            type: integer
            default: 1
          description: Número de página
        - $ref: "#/components/parameters/LimitParam"
      responses:
        "200":
          description: Resultados de la búsqueda
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactSearchResponse"

    post:
      summary: Crear contacto
      operationId: createContact
      tags: [Contacts]
      description: Crea un nuevo contacto.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [websiteId, name]
              properties:
                websiteId:
                  type: string
                  description: ID del canal donde se origina el contacto
                name:
                  type: string
                email:
                  type: string
                phone:
                  type: string
                internationalPhoneNumber:
                  type: string
                message:
                  type: string
                status:
                  type: string
                  enum: [new, active, client, long_term]
                entryMethod:
                  type: string
                assignedTo:
                  type: string
                  description: ID del agente a asignar
                note:
                  type: string
                  description: Nota inicial del contacto
                customFields:
                  type: object
                  description: Campos personalizados (key-value)
      responses:
        "200":
          description: Contacto creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"

  /v1/contacts/bulk-delete:
    post:
      summary: Eliminar contactos en lote
      operationId: bulkDeleteContacts
      tags: [Contacts]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [contacts]
              properties:
                contacts:
                  type: array
                  items:
                    type: string
                  description: Array de IDs de contactos a eliminar
      responses:
        "207":
          description: Resultado por cada contacto (Multi-Status)

  /v1/contacts/{contactId}:
    get:
      summary: Obtener contacto
      operationId: getContact
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle del contacto
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"

    patch:
      summary: Actualizar contacto
      operationId: patchContact
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                phone:
                  type: string
                status:
                  type: string
                assignedTo:
                  type: string
      responses:
        "200":
          description: Contacto actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"

    delete:
      summary: Eliminar contacto
      operationId: deleteContact
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Contacto eliminado

  /v1/contacts/{contactId}/notes:
    get:
      summary: Listar notas del contacto
      operationId: listContactNotes
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Lista de notas

    post:
      summary: Agregar nota al contacto
      operationId: createContactNote
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
      responses:
        "200":
          description: Nota creada

  /v1/contacts/{contactId}/notes/{noteId}:
    patch:
      summary: Actualizar nota
      operationId: patchContactNote
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
        - name: noteId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                message:
                  type: string
      responses:
        "200":
          description: Nota actualizada

    delete:
      summary: Eliminar nota
      operationId: deleteContactNote
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
        - name: noteId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Nota eliminada

  /v1/contacts/{contactId}/tags:
    post:
      summary: Agregar tag al contacto
      operationId: addContactTag
      tags: [Contacts]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tag]
              properties:
                tag:
                  type: string
      responses:
        "200":
          description: Tag agregado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Contact"

  /v1/contacts/{contactId}/history:
    get:
      summary: Historial del contacto
      operationId: getContactHistory
      tags: [Contacts]
      description: Logs y notas del contacto combinados.
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Historial del contacto

  # ── Conversations ───────────────────────────────
  /v1/conversations:
    get:
      summary: Listar conversaciones
      operationId: listConversations
      tags: [Conversations]
      description: Conversaciones de la empresa autenticada, con filtros avanzados y paginación.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [opened, closed, inactive]
          description: Estado de la conversación
        - name: tags
          in: query
          schema:
            type: string
          description: Filtrar por tags (separados por coma)
        - name: fromDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de inicio (ISO 8601)
        - name: toDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de fin (ISO 8601)
        - name: channel
          in: query
          schema:
            type: string
          description: "Filtrar por canal (separados por coma, valores: WEB, WHATSAPP, FACEBOOK, INSTAGRAM)"
        - name: phase
          in: query
          schema:
            type: string
          description: ID de la fase
        - name: agent
          in: query
          schema:
            type: string
          description: ID del usuario/agente asignado
        - name: search
          in: query
          schema:
            type: string
          description: Búsqueda de texto libre
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - $ref: "#/components/parameters/PageParam"
        - name: sort
          in: query
          schema:
            type: string
          description: "Campo y orden de ordenamiento (ej: createdAt:desc)"
      responses:
        "200":
          description: Lista de conversaciones
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationListResponse"

    post:
      summary: Crear conversación
      operationId: createConversation
      tags: [Conversations]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [channelId, companyId, channel, from]
              properties:
                channelId:
                  type: string
                companyId:
                  type: string
                channel:
                  $ref: "#/components/schemas/Channel"
                from:
                  type: string
                  description: Identificador del originante
                url:
                  type: string
      responses:
        "200":
          description: Conversación creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

  /v1/conversations/by-phase:
    get:
      summary: Conversaciones agrupadas por fase
      operationId: listConversationsByPhase
      tags: [Conversations]
      description: Agrupa las conversaciones por fase del pipeline. Soporta los mismos filtros que el listado de conversaciones.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [opened, closed, inactive]
          description: Estado de la conversación
        - name: tags
          in: query
          schema:
            type: string
          description: Filtrar por tags (separados por coma)
        - name: fromDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de inicio (ISO 8601)
        - name: toDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de fin (ISO 8601)
        - name: channel
          in: query
          schema:
            type: string
          description: "Filtrar por canal (separados por coma, valores: WEB, WHATSAPP, FACEBOOK, INSTAGRAM)"
        - name: phase
          in: query
          schema:
            type: string
          description: ID de la fase
        - name: agent
          in: query
          schema:
            type: string
          description: ID del usuario/agente asignado
        - name: search
          in: query
          schema:
            type: string
          description: Búsqueda de texto libre
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
        - $ref: "#/components/parameters/PageParam"
        - name: sort
          in: query
          schema:
            type: string
          description: "Campo y orden de ordenamiento (ej: createdAt:desc)"
      responses:
        "200":
          description: Conversaciones agrupadas por fase
          content:
            application/json:
              schema:
                type: object
                properties:
                  phases:
                    type: array
                    items:
                      type: object
                      properties:
                        phase:
                          $ref: "#/components/schemas/Phase"
                        conversations:
                          $ref: "#/components/schemas/ConversationListResponse"

  /v1/conversations/export:
    get:
      summary: Exportar conversaciones
      operationId: exportConversations
      tags: [Conversations]
      description: Exporta conversaciones en formato descargable.
      responses:
        "200":
          description: Archivo de exportación

  /v1/conversations/channels/list:
    get:
      summary: Listar canales activos
      operationId: listConversationChannels
      tags: [Conversations]
      description: Devuelve los canales con conversaciones activas.
      responses:
        "200":
          description: Lista de canales
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Channel"

  /v1/conversations/contact/{contactId}:
    get:
      summary: Conversaciones de un contacto
      operationId: getConversationsByContact
      tags: [Conversations]
      parameters:
        - name: contactId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Conversaciones del contacto
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Conversation"

  /v1/conversations/{conversationId}:
    get:
      summary: Obtener conversación
      operationId: getConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle de la conversación
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"
        "404":
          description: No encontrada

    put:
      summary: Actualizar conversación
      operationId: updateConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Conversation"
      responses:
        "200":
          description: Conversación actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

    patch:
      summary: Actualizar contexto de conversación
      operationId: patchConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Conversación parcialmente actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

    delete:
      summary: Eliminar conversación
      operationId: deleteConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Conversación eliminada

  /v1/conversations/{conversationId}/messages:
    get:
      summary: Listar mensajes
      operationId: listConversationMessages
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
        - name: includeCommands
          in: query
          schema:
            type: boolean
          description: Incluir mensajes de tipo comando
      responses:
        "200":
          description: Mensajes de la conversación
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Message"

    post:
      summary: Enviar mensaje
      operationId: addConversationMessage
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, fromType, type]
              properties:
                body:
                  type: string
                  example: "Hola, necesito ayuda"
                fromType:
                  type: string
                  enum: [user, robot, visitor]
                from:
                  type: string
                  description: ID del remitente
                type:
                  type: string
                  enum: [image, text/plain, command, note]
                message:
                  type: object
                  description: Para mensajes multimedia
                  properties:
                    url:
                      type: string
                    caption:
                      type: string
      responses:
        "200":
          description: Mensaje enviado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Message"

  /v1/conversations/{conversationId}/assign:
    post:
      summary: Asignar a operador
      operationId: assignConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [assignedTo]
              properties:
                assignedTo:
                  type: string
                  description: ID del operador
      responses:
        "200":
          description: Conversación asignada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

  /v1/conversations/{conversationId}/transfer:
    post:
      summary: Transferir conversación
      operationId: transferConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [transferTo]
              properties:
                transferTo:
                  type: string
                  description: ID del operador destino
      responses:
        "200":
          description: Conversación transferida
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

  /v1/conversations/{conversationId}/close:
    post:
      summary: Cerrar conversación
      operationId: closeConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [isLead]
              properties:
                isLead:
                  type: boolean
                  description: Si la conversación resultó en un lead
      responses:
        "200":
          description: Conversación cerrada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

  /v1/conversations/{conversationId}/open:
    post:
      summary: Reabrir conversación
      operationId: openConversation
      tags: [Conversations]
      description: Reabre una conversación cerrada (status → ACTIVE).
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Conversación reabierta
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Conversation"

  /v1/conversations/{conversationId}/mark:
    post:
      summary: Marcar conversación (leída/no leída)
      operationId: markConversation
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                read:
                  type: boolean
      responses:
        "200":
          description: Conversación marcada

  /v1/conversations/{conversationId}/contact:
    put:
      summary: Actualizar datos del contacto de la conversación
      operationId: updateConversationContact
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                phone:
                  type: string
      responses:
        "200":
          description: Contacto actualizado

  # ── Notes (sub-resource of Conversations) ──────────────────────
  /v1/conversations/{conversationId}/notes:
    get:
      summary: Listar notas de una conversación
      operationId: listConversationNotes
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Lista de notas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Note"

    post:
      summary: Agregar nota a conversación
      operationId: createConversationNote
      tags: [Conversations]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message]
              properties:
                message:
                  type: string
                  description: Contenido de la nota
                tags:
                  type: array
                  items:
                    type: string
                  description: Tags opcionales para la nota
                quote:
                  type: array
                  items:
                    type: string
                  description: IDs de mensajes citados
      responses:
        "201":
          description: Nota creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Note"

  # ── Tags (Operator Tags) ──────────────────────
  /v1/tags:
    get:
      summary: Listar tags
      operationId: listTags
      tags: [Tags]
      description: Devuelve todos los tags de operador de la empresa.
      responses:
        "200":
          description: Lista de tags
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/OperatorTag"

    post:
      summary: Crear tag
      operationId: createTag
      tags: [Tags]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                color:
                  type: string
      responses:
        "201":
          description: Tag creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperatorTag"

  /v1/tags/{tagId}:
    put:
      summary: Actualizar tag
      operationId: updateTag
      tags: [Tags]
      parameters:
        - name: tagId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                color:
                  type: string
      responses:
        "200":
          description: Tag actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OperatorTag"

    delete:
      summary: Eliminar tag
      operationId: deleteTag
      tags: [Tags]
      parameters:
        - name: tagId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Tag eliminado

  # ── Phases ─────────────────────────────────────
  /v1/phases:
    get:
      summary: Listar fases
      operationId: listPhases
      tags: [Phases]
      description: Fases del pipeline de conversaciones.
      responses:
        "200":
          description: Lista de fases
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Phase"

    post:
      summary: Crear fase
      operationId: createPhase
      tags: [Phases]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                order:
                  type: integer
      responses:
        "201":
          description: Fase creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Phase"

  /v1/phases/counts:
    get:
      summary: Conteo por fase
      operationId: getPhaseCounts
      tags: [Phases]
      description: Cantidad de conversaciones en cada fase. Soporta los mismos filtros que el listado de conversaciones.
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [opened, closed, inactive]
          description: Estado de la conversación
        - name: tags
          in: query
          schema:
            type: string
          description: Filtrar por tags (separados por coma)
        - name: fromDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de inicio (ISO 8601)
        - name: toDate
          in: query
          schema:
            type: string
            format: date-time
          description: Fecha de fin (ISO 8601)
        - name: channel
          in: query
          schema:
            type: string
          description: "Filtrar por canal (separados por coma, valores: WEB, WHATSAPP, FACEBOOK, INSTAGRAM)"
        - name: phase
          in: query
          schema:
            type: string
          description: ID de la fase
        - name: agent
          in: query
          schema:
            type: string
          description: ID del usuario/agente asignado
        - name: search
          in: query
          schema:
            type: string
          description: Búsqueda de texto libre
      responses:
        "200":
          description: Conteos por fase
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/PhaseCount"

  /v1/phases/{phaseId}:
    put:
      summary: Actualizar fase
      operationId: updatePhase
      tags: [Phases]
      parameters:
        - name: phaseId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                order:
                  type: integer
      responses:
        "200":
          description: Fase actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Phase"

    delete:
      summary: Eliminar fase
      operationId: deletePhase
      tags: [Phases]
      parameters:
        - name: phaseId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Fase eliminada

  /v1/conversations/{conversationId}/phases/{phaseId}:
    patch:
      summary: Asignar fase a conversación
      operationId: assignPhaseToConversation
      tags: [Phases]
      parameters:
        - name: conversationId
          in: path
          required: true
          schema:
            type: string
        - name: phaseId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Fase asignada

  # ── Accounts ────────────────────────────────────
  /v1/account:
    get:
      summary: Info de la cuenta
      operationId: getAccount
      tags: [Accounts]
      responses:
        "200":
          description: Datos de la cuenta
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"

    put:
      summary: Actualizar cuenta
      operationId: updateAccount
      tags: [Accounts]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
      responses:
        "200":
          description: Cuenta actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Account"

  /v1/account/plan:
    get:
      summary: Plan de la cuenta
      operationId: getAccountPlan
      tags: [Accounts]
      responses:
        "200":
          description: Plan actual de la cuenta
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Plan"

  /v1/account/features:
    get:
      summary: Features de la cuenta
      operationId: getAccountFeatures
      tags: [Accounts]
      responses:
        "200":
          description: Features habilitadas
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeaturesResponse"

  /v1/account/integrations:
    get:
      summary: Integraciones de la cuenta
      operationId: getAccountIntegrations
      tags: [Accounts]
      responses:
        "200":
          description: Lista de integraciones configuradas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Integration"

  # ── Channels ─────────────────────────────────────
  /v1/channels:
    get:
      summary: Listar canales
      operationId: listChannels
      tags: [Channels]
      description: |
        Lista los canales activos de la cuenta. Los canales eliminados no se
        incluyen. La lista NO está paginada — devuelve un array directo.
      responses:
        "200":
          description: Lista de canales
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/ChannelSite"

    post:
      summary: Crear canal
      operationId: createChannel
      tags: [Channels]
      description: Crea un nuevo canal según su tipo.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type]
              properties:
                type:
                  type: string
                  enum: [WEBSITE, WHATSAPP_API, WHATSAPP_BUSINESS, FACEBOOK, INSTAGRAM]
                businessName:
                  type: string
                url:
                  type: string
                  format: uri
                  description: Solo para canales WEBSITE.
                phone:
                  type: string
                  description: Solo para canales WhatsApp.
      responses:
        "201":
          description: Canal creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelSite"

  /v1/channels/{channelId}:
    get:
      summary: Obtener canal
      operationId: getChannel
      tags: [Channels]
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
          description: ID del canal (== websiteId histórico).
      responses:
        "200":
          description: Detalle del canal
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelSite"
        "404":
          description: No encontrado

    patch:
      summary: Actualizar canal parcialmente
      operationId: patchChannel
      tags: [Channels]
      description: |
        Actualiza propiedades del canal (label/title, etc.). Los campos
        protegidos (`_id`, `companyId`, `creationDate`) son ignorados
        silenciosamente.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
                title:
                  type: string
      responses:
        "200":
          description: Canal parcialmente actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelSite"

    put:
      summary: Asignar / desasignar chatbot al canal
      operationId: assignChatbotToChannel
      tags: [Channels]
      description: |
        Asocia (`ADD`) o desasocia (`REMOVE`) un chatbot al canal. Un canal
        puede tener un solo chatbot a la vez — `ADD` desasocia automáticamente
        cualquier otro chatbot previamente asignado.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [chatbotId, action]
              properties:
                chatbotId:
                  type: string
                action:
                  type: string
                  enum: [ADD, REMOVE]
      responses:
        "200":
          description: Asignación actualizada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelSite"

    delete:
      summary: Eliminar canal
      operationId: deleteChannel
      tags: [Channels]
      description: |
        Elimina el canal (soft-delete) y desasocia cualquier chatbot. Para
        canales tipo Meta (FB/IG/WA Business) además dispara la desconexión
        en la plataforma origen.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Canal eliminado

  /v1/channels/{channelId}/disconnect:
    post:
      summary: Solicitar desconexión de un canal WhatsApp
      operationId: disconnectChannel
      tags: [Channels]
      description: |
        Solicita la desconexión del canal en la plataforma origen. Hoy solo
        soportado para canales tipo `WHATSAPP_API` — marca el canal con
        `disconnectionRequested: true`.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Desconexión solicitada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ChannelSite"
        "400":
          description: Tipo de canal no soportado para desconexión

  # ── Users ───────────────────────────────────────
  /v1/users:
    get:
      summary: Listar usuarios
      operationId: listUsers
      tags: [Users]
      parameters:
        - $ref: "#/components/parameters/LimitParam"
        - $ref: "#/components/parameters/OffsetParam"
      responses:
        "200":
          description: Lista de usuarios
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/User"

    post:
      summary: Crear usuario
      operationId: createUser
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, email]
              properties:
                name:
                  type: string
                email:
                  type: string
                  format: email
                role:
                  type: string
      responses:
        "201":
          description: Usuario creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  /v1/users/me:
    get:
      summary: Obtener usuario actual
      operationId: getCurrentUser
      tags: [Users]
      responses:
        "200":
          description: Datos del usuario autenticado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

  /v1/users/roles:
    get:
      summary: Listar roles
      operationId: listUserRoles
      tags: [Users]
      responses:
        "200":
          description: Roles disponibles
          content:
            application/json:
              schema:
                type: array
                items:
                  type: string
                example: ["admin", "operator", "viewer"]

  /v1/users/{userId}:
    get:
      summary: Obtener usuario
      operationId: getUser
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Datos del usuario
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

    put:
      summary: Actualizar usuario
      operationId: updateUser
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                email:
                  type: string
                role:
                  type: string
      responses:
        "200":
          description: Usuario actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

    patch:
      summary: Actualizar usuario parcialmente
      operationId: patchUser
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Usuario parcialmente actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"

    delete:
      summary: Eliminar usuario
      operationId: deleteUser
      tags: [Users]
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Usuario eliminado

  /v1/users/invite:
    post:
      summary: Invitar usuario
      operationId: inviteUser
      tags: [Users]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
                role:
                  type: string
      responses:
        "200":
          description: Invitación enviada

  # ── Plans ───────────────────────────────────────
  /v1/plans:
    get:
      summary: Listar planes
      operationId: listPlans
      tags: [Plans]
      description: Todos los planes disponibles de Cliengo.
      responses:
        "200":
          description: Lista de planes
          content:
            application/json:
              schema:
                type: object
                properties:
                  paging:
                    type: object
                    properties:
                      total:
                        type: integer
                  results:
                    type: array
                    items:
                      $ref: "#/components/schemas/Plan"

  /v1/plans/active:
    get:
      summary: Planes activos
      operationId: getActivePlans
      tags: [Plans]
      description: Solo los planes actualmente disponibles para contratar.
      responses:
        "200":
          description: Lista de planes activos
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Plan"

  /v1/plans/{planId}:
    get:
      summary: Obtener plan
      operationId: getPlan
      tags: [Plans]
      parameters:
        - name: planId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle del plan
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Plan"

  # ── Chatbots ────────────────────────────────────
  /v1/chatbots:
    get:
      summary: Listar chatbots
      operationId: listChatbots
      tags: [Chatbots]
      description: Lista todos los chatbots de la cuenta (no paginado).
      responses:
        "200":
          description: Lista de chatbots
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Chatbot"

    post:
      summary: Crear chatbot
      operationId: createChatbot
      tags: [Chatbots]
      description: |
        Crea un chatbot a partir del template base de la cuenta. Empieza
        sin canales asignados — usá `PUT /v1/chatbots/{chatbotId}/channels`
        para asignarlos.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                title:
                  type: string
                  description: Título visible. Default "Chatbot".
                name:
                  type: string
                  description: Nombre interno. Default "Pilar".
      responses:
        "201":
          description: Chatbot creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chatbot"

  /v1/chatbots/{chatbotId}:
    get:
      summary: Obtener chatbot
      operationId: getChatbot
      tags: [Chatbots]
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
          description: |
            ID propio del chatbot. **Atención**: con la migración omnichannel
            ya no es el `websiteId/channelId` — para encontrar el `chatbotId`
            de un canal, usá `GET /v1/channels/{channelId}` y leé `chatbot.id`
            en la respuesta.
      responses:
        "200":
          description: Configuración del chatbot
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chatbot"
        "404":
          description: Chatbot no encontrado

    patch:
      summary: Actualizar propiedades del chatbot
      operationId: patchChatbot
      tags: [Chatbots]
      description: |
        Actualiza `isMuted`, `title` o `name`. Para asignar/desasignar canales
        usá `PUT /v1/chatbots/{chatbotId}/channels`.
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                isMuted:
                  type: boolean
                  description: Mutear (true) / desmutear (false) el chatbot.
                title:
                  type: string
                name:
                  type: string
      responses:
        "200":
          description: Chatbot actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chatbot"

    delete:
      summary: Eliminar chatbot
      operationId: deleteChatbot
      tags: [Chatbots]
      description: Soft-delete del chatbot. No elimina los canales asociados.
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
      responses:
        "204":
          description: Chatbot eliminado

  /v1/chatbots/{chatbotId}/channels:
    put:
      summary: Asignar / desasignar / limpiar canales del chatbot
      operationId: updateChatbotChannels
      tags: [Chatbots]
      description: |
        Aplica una operación atómica sobre la lista de canales del chatbot:

        - `ADD` — agrega `channelId` a la lista (si ya está, no duplica).
        - `REMOVE` — quita `channelId` de la lista.
        - `CLEAN` — vacía toda la lista. En este caso `channelId` puede omitirse.
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [action]
              properties:
                action:
                  type: string
                  enum: [ADD, REMOVE, CLEAN]
                channelId:
                  type: string
                  description: Requerido cuando `action` es `ADD` o `REMOVE`.
      responses:
        "200":
          description: Canales actualizados
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Chatbot"
        "400":
          description: channelId requerido para ADD/REMOVE

  /v1/chatbots/{chatbotId}/business-hours-config:
    get:
      summary: Obtener configuración de horario de atención del chatbot
      operationId: getChatbotBusinessHoursConfig
      tags: [Chatbots]
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Configuración de horarios
          content:
            application/json:
              schema:
                type: object

    patch:
      summary: Actualizar horario de atención del chatbot
      operationId: patchChatbotBusinessHoursConfig
      tags: [Chatbots]
      parameters:
        - name: chatbotId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [enabled, businessHours, initialMessage, finalSalutation]
              properties:
                enabled:
                  type: boolean
                businessHours:
                  type: array
                  items:
                    type: object
                    required: [range, days]
                    properties:
                      range:
                        type: string
                        description: Rango horario, ej. `09:00-18:00`.
                      days:
                        type: array
                        items:
                          type: integer
                          minimum: 0
                          maximum: 6
                        description: Días de la semana (0 = domingo).
                initialMessage:
                  type: string
                finalSalutation:
                  type: string
      responses:
        "200":
          description: Horarios actualizados
          content:
            application/json:
              schema:
                type: object

  # ── WhatsApp ─────────────────────────────────────
  /v1/whatsapp:
    get:
      summary: Listar canales con WhatsApp
      operationId: listWhatsAppChannels
      tags: [WhatsApp]
      description: Canales vinculados al token con configuración WhatsApp.
      responses:
        "200":
          description: Lista de canales

  /v1/whatsapp/{channelId}/templates:
    get:
      summary: Listar plantillas WhatsApp
      operationId: listWhatsAppTemplates
      tags: [WhatsApp]
      description: Devuelve las plantillas HSM aprobadas, pendientes y rechazadas del canal.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
          description: ID del canal de WhatsApp (websiteId)
      responses:
        "200":
          description: Lista de plantillas
          content:
            application/json:
              schema:
                type: object
                properties:
                  code:
                    type: string
                    example: SUCCESS
                  statusCode:
                    type: integer
                    example: 200
                  result:
                    type: array
                    items:
                      $ref: "#/components/schemas/WhatsAppTemplate"

    post:
      summary: Crear plantilla WhatsApp
      operationId: createWhatsAppTemplate
      tags: [WhatsApp]
      description: |
        Envía una nueva plantilla para aprobación del proveedor (Meta/Gupshup). El resultado inicial queda en estado `pending`; al aprobarse pasa a `approved`.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, languageCode, category, type, text]
              properties:
                name:
                  type: string
                  description: Identificador único de la plantilla dentro del canal.
                  example: registro_cliengo
                languageCode:
                  type: string
                  example: es
                category:
                  type: string
                  enum: [MARKETING, UTILITY, AUTHENTICATION]
                type:
                  type: string
                  enum: [TEXT, IMAGE, VIDEO, DOCUMENT]
                text:
                  type: string
                  description: Cuerpo de la plantilla con placeholders `{name}`, `{code}`, etc.
                description:
                  type: string
                mediaId:
                  type: string
                  description: ID devuelto por `POST /template-media` cuando `type` es IMAGE/VIDEO/DOCUMENT.
                buttons:
                  type: array
                  items:
                    type: object
      responses:
        "201":
          description: Plantilla creada (pendiente aprobación)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppTemplate"

  /v1/whatsapp/{channelId}/templates/{templateId}:
    put:
      summary: Actualizar plantilla WhatsApp
      operationId: updateWhatsAppTemplate
      tags: [WhatsApp]
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: templateId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Plantilla actualizada

  /v1/whatsapp/{channelId}/campaigns:
    post:
      summary: Crear campaña WhatsApp
      operationId: createWhatsAppCampaign
      tags: [WhatsApp]
      description: |
        Crea una campaña de mensajes WhatsApp (inmediata o programada) usando una plantilla aprobada y una fuente de contactos. La fuente puede ser una lista (`contactsSource: "CONTACT_LIST"` + `contactListId`), una google-sheet, o un archivo subido previamente.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [campaignName, templateName, templateText, templateParamCount, contactsSource, scheduled]
              properties:
                campaignName:
                  type: string
                  example: Promoción de navidad
                templateName:
                  type: string
                  example: bienvenida_v2
                templateText:
                  type: string
                  example: Hola {{1}}, tu código es {{2}}
                templateParamCount:
                  type: integer
                  example: 2
                contactsSource:
                  type: string
                  enum: [GOOGLE_SHEET, FILE, CONTACT_LIST]
                contactListId:
                  type: string
                  nullable: true
                  description: Requerido cuando `contactsSource = CONTACT_LIST`.
                scheduled:
                  type: boolean
                scheduleDateTime:
                  type: string
                  nullable: true
                  description: ISO 8601 con timezone. Requerido cuando `scheduled = true`.
                  example: "2026-12-25T14:00:00-03:00"
                mediaUrl:
                  type: string
                  nullable: true
                  description: URL devuelta por `POST /campaign-media` cuando la plantilla tiene header de media.
      responses:
        "201":
          description: Campaña creada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppCampaign"

  /v1/whatsapp/{channelId}/campaigns/report:
    get:
      summary: Reporte de campañas
      operationId: getWhatsAppCampaignReport
      tags: [WhatsApp]
      description: Lista y métricas agregadas de campañas creadas en el rango de fechas indicado.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: from
          in: query
          required: true
          schema:
            type: string
            format: date
          example: "2026-05-01"
        - name: to
          in: query
          required: true
          schema:
            type: string
            format: date
          example: "2026-05-31"
      responses:
        "200":
          description: Métricas de campañas

  /v1/whatsapp/{channelId}/campaigns/{campaignId}/full:
    get:
      summary: Detalle completo de campaña
      operationId: getWhatsAppCampaignFull
      tags: [WhatsApp]
      description: Devuelve el detalle de una campaña incluyendo el estado individual (eventos) de cada mensaje enviado.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle completo de la campaña
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppCampaignFull"

  /v1/whatsapp/{channelId}/campaigns/{campaignId}/simple:
    get:
      summary: Detalle agregado de campaña
      operationId: getWhatsAppCampaignSimple
      tags: [WhatsApp]
      description: Devuelve un resumen de la campaña con conteos agregados (enviados, entregados, leídos, errores) sin el detalle por mensaje.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Resumen de la campaña

  /v1/whatsapp/{channelId}/campaigns/{campaignId}/cancel:
    put:
      summary: Cancelar campaña
      operationId: cancelWhatsAppCampaign
      tags: [WhatsApp]
      description: Cancela una campaña programada (estado pasa a `CANCELLED`). No tiene efecto si la campaña ya empezó a enviar.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                cancellationReason:
                  type: string
                  example: La campaña se creó por error
      responses:
        "200":
          description: Campaña cancelada

  /v1/whatsapp/{channelId}/campaigns/{campaignId}/resend:
    put:
      summary: Reintentar mensajes fallidos
      operationId: resendWhatsAppCampaign
      tags: [WhatsApp]
      description: Reintenta el envío de los mensajes en estado de error de una campaña.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: campaignId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Reintento encolado

  /v1/whatsapp/{channelId}/campaign-media:
    post:
      summary: Subir media para campaña
      operationId: uploadWhatsAppCampaignMedia
      tags: [WhatsApp]
      description: |
        Sube un archivo de media (imagen, video, documento) para usar en una campaña con plantilla con header de media. Devuelve la `mediaUrl` pública (S3) que se pasa al crear la campaña.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "200":
          description: Media subido
          content:
            application/json:
              schema:
                type: object
                properties:
                  mediaUrl:
                    type: string
                    example: https://bucket.s3.us-east-1.amazonaws.com/campaign-media/abc/file.pdf

  /v1/whatsapp/{channelId}/template-media:
    post:
      summary: Subir media para una nueva plantilla
      operationId: uploadWhatsAppTemplateMedia
      tags: [WhatsApp]
      description: |
        Sube un archivo de media para usarlo en la creación de una plantilla con header `IMAGE`, `VIDEO` o `DOCUMENT`. El `mediaId` devuelto se incluye en el payload de creación de la plantilla.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [templateType, file]
              properties:
                templateType:
                  type: string
                  enum: [IMAGE, VIDEO, DOCUMENT]
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Media subido
          content:
            application/json:
              schema:
                type: object
                properties:
                  mediaId:
                    type: string

  /v1/whatsapp/{channelId}/messages:
    post:
      summary: Enviar plantilla HSM
      operationId: sendWhatsAppMessage
      tags: [WhatsApp]
      description: |
        Envía una plantilla HSM por uno de dos modos, según el `to`:

        - **`to.phone`**: envío puntual a un teléfono arbitrario (E.164). No requiere que el número sea un Contacto Cliengo. `to.variables` es un array posicional que reemplaza los placeholders `{{1}}`, `{{2}}`, ... de la plantilla.
        - **`to.contacts`**: envío masivo a Contactos Cliengo existentes. Las propiedades referenciadas por la plantilla (`{name}`, `{email}`, etc.) se resuelven desde el contacto.

        `to.phone` y `to.contacts` son **mutuamente excluyentes**.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [templateId, to]
              properties:
                templateId:
                  type: integer
                  description: ID interno de la plantilla aprobada (no el nombre).
                  example: 1
                to:
                  description: Destinatario(s). `to.phone` y `to.contacts` son mutuamente excluyentes — el schema lo expresa con `oneOf`.
                  oneOf:
                    - type: object
                      title: Envío a un teléfono arbitrario
                      required: [phone]
                      properties:
                        phone:
                          type: string
                          description: Teléfono destino en E.164 (con `+`). No requiere que el número sea un Contacto Cliengo.
                          example: "+5491133261336"
                        variables:
                          type: array
                          description: Valores posicionales que reemplazan los placeholders `{{1}}`, `{{2}}`, ... de la plantilla.
                          items:
                            type: string
                          example: ["Mariano", "123456"]
                        languageCode:
                          type: string
                          description: |
                            Override del locale del template (e.g. `es`, `en_US`, `es_AR`). Solo necesario cuando el template tiene un `languageCode` no persistido en hsm-backend (templates legacy). Por defecto se usa el `languageCode` propagado desde Meta/Gupshup; si tampoco existe, fallback `es`.
                          example: en_US
                    - type: object
                      title: Envío masivo a Contactos Cliengo
                      required: [contacts]
                      properties:
                        contacts:
                          type: array
                          minItems: 1
                          description: Contactos Cliengo. Cada item debe traer al menos `id` y `phone`; las props referenciadas en la plantilla (`{name}`, `{email}`, etc.) se resuelven desde el contacto.
                          items:
                            type: object
                            required: [id, phone]
                            properties:
                              id:
                                type: string
                              phone:
                                type: string
                              name:
                                type: string
                              email:
                                type: string
                        languageCode:
                          type: string
                          description: Mismo override que el modo `phone`, aplicado al template para todos los contactos del envío.
                          example: en_US
                media:
                  type: object
                  description: Media opcional para plantillas con header de imagen/video/documento.
                  properties:
                    link:
                      type: string
                      format: uri
                    filename:
                      type: string
                location:
                  type: object
                  description: Ubicación opcional para plantillas con header de location.
                  properties:
                    latitude:
                      type: string
                      example: "-34.6037"
                    longitude:
                      type: string
                      example: "-58.3816"
            examples:
              singlePhone:
                summary: Envío puntual a un teléfono
                value:
                  templateId: 1
                  to:
                    phone: "+5491133261336"
                    variables: ["Mariano", "123456"]
              bulkContacts:
                summary: Envío masivo a Contactos Cliengo
                value:
                  templateId: 1
                  to:
                    contacts:
                      - id: "642c67a495de1100233eb66a"
                        phone: "+5491133261336"
                        name: "Mariano"
                        email: "mariano@example.com"
      responses:
        "200":
          description: Mensajes encolados

  /v1/whatsapp/{channelId}/events:
    get:
      summary: Historial de eventos HSM
      operationId: listWhatsAppEvents
      tags: [WhatsApp]
      description: |
        Devuelve el historial de eventos (envío, entrega, lectura, error, respuesta) de los mensajes HSM del canal. Filtros por tipo de evento y rango de fechas.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
        - name: type
          in: query
          schema:
            type: string
            enum: [enqueque, sent, delivered, read, answered, failed, pending, invalid]
        - name: from
          in: query
          schema:
            type: string
            format: date
        - name: to
          in: query
          schema:
            type: string
            format: date
      responses:
        "200":
          description: Lista de eventos

  /v1/whatsapp/{channelId}/blacklist:
    get:
      summary: Lista de números bloqueados
      operationId: listWhatsAppBlacklist
      tags: [WhatsApp]
      description: |
        Devuelve los números de teléfono bloqueados (opt-out) para el canal. Estos números no reciben envíos automáticos.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Lista de números bloqueados

  /v1/whatsapp/{channelId}/contact-lists:
    get:
      summary: Listar listas de contactos
      operationId: listWhatsAppContactLists
      tags: [WhatsApp]
      description: Listas de contactos creadas para este canal, usadas como destinatarios de campañas.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Lista de contact-lists

    post:
      summary: Crear lista de contactos
      operationId: createWhatsAppContactList
      tags: [WhatsApp]
      description: Crea una lista de contactos desde un archivo spreadsheet para campañas WhatsApp.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Lista de contactos creada

  /v1/whatsapp/{channelId}/account/limit:
    get:
      summary: Límite diario de WhatsApp
      operationId: getWhatsAppDailyLimit
      tags: [WhatsApp]
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Límite diario de la línea WhatsApp

  /v1/whatsapp/{channelId}/whatsapp-config:
    get:
      summary: Configuración WhatsApp del canal
      operationId: getWhatsAppConfig
      tags: [WhatsApp]
      description: Balance, uso y detalles de la línea WhatsApp.
      parameters:
        - name: channelId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Configuración WhatsApp

  # ── Teams ───────────────────────────────────────
  /v1/teams:
    get:
      summary: Listar equipos
      operationId: listTeams
      tags: [Teams]
      responses:
        "200":
          description: Lista de equipos

    post:
      summary: Crear equipo
      operationId: createTeam
      tags: [Teams]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                members:
                  type: array
                  items:
                    type: string
      responses:
        "201":
          description: Equipo creado

  /v1/teams/{teamId}:
    get:
      summary: Obtener equipo
      operationId: getTeam
      tags: [Teams]
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Detalle del equipo

    patch:
      summary: Actualizar equipo
      operationId: patchTeam
      tags: [Teams]
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                members:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Equipo actualizado

    delete:
      summary: Eliminar equipo
      operationId: deleteTeam
      tags: [Teams]
      parameters:
        - name: teamId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Equipo eliminado

  # ── Webhooks ──────────────────────────────────
  /v1/webhooks:
    get:
      summary: Listar webhooks
      operationId: listWebhooks
      tags: [Webhooks]
      description: |
        Devuelve todos los webhooks configurados para la empresa, ordenados por fecha de creación (más recientes primero).
        Opcionalmente filtrá por `websiteId` para ver solo los webhooks de un canal específico.
      parameters:
        - name: websiteId
          in: query
          schema:
            type: string
          description: Filtrar webhooks por canal. Si se omite, devuelve todos.
      responses:
        "200":
          description: Lista de webhooks
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookSubscription"

    post:
      summary: Crear webhook
      operationId: createWebhook
      tags: [Webhooks]
      description: |
        Crea una nueva suscripción de webhook. Cliengo enviará un `POST` a la URL indicada cada vez que ocurra
        uno de los eventos seleccionados. El webhook se crea activo por defecto y se le asigna un `secret`
        que se enviará en el header `X-Webhook-Secret` de cada entrega para verificar autenticidad.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWebhookRequest"
      responses:
        "201":
          description: Webhook creado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "400":
          description: Datos inválidos (URL vacía, evento desconocido, etc.)

  /v1/webhooks/{webhookId}:
    get:
      summary: Obtener webhook
      operationId: getWebhook
      tags: [Webhooks]
      description: Devuelve los detalles de un webhook específico.
      parameters:
        - $ref: "#/components/parameters/webhookId"
      responses:
        "200":
          description: Detalle del webhook
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "404":
          description: Webhook no encontrado

    put:
      summary: Actualizar webhook
      operationId: updateWebhook
      tags: [Webhooks]
      description: |
        Actualiza la configuración de un webhook existente. Solo se modifican los campos enviados en el body;
        los demás se mantienen sin cambios.
      parameters:
        - $ref: "#/components/parameters/webhookId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWebhookRequest"
      responses:
        "200":
          description: Webhook actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "400":
          description: Datos inválidos
        "404":
          description: Webhook no encontrado

    patch:
      summary: Activar o desactivar webhook
      operationId: toggleWebhook
      tags: [Webhooks]
      description: |
        Activa o desactiva un webhook sin eliminarlo. Un webhook desactivado no recibe entregas
        pero mantiene su configuración y logs.
      parameters:
        - $ref: "#/components/parameters/webhookId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [isActive]
              properties:
                isActive:
                  type: boolean
                  description: "`true` para activar, `false` para desactivar"
      responses:
        "200":
          description: Estado actualizado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookSubscription"
        "404":
          description: Webhook no encontrado

    delete:
      summary: Eliminar webhook
      operationId: deleteWebhook
      tags: [Webhooks]
      description: Elimina un webhook y todo su historial de entregas de forma permanente.
      parameters:
        - $ref: "#/components/parameters/webhookId"
      responses:
        "204":
          description: Webhook eliminado
        "404":
          description: Webhook no encontrado

  /v1/webhooks/{webhookId}/logs:
    get:
      summary: Historial de entregas
      operationId: getWebhookLogs
      tags: [Webhooks]
      description: |
        Devuelve el historial de entregas (delivery log) de un webhook. Cada entrada registra
        el resultado de un intento de entrega: URL, evento, payload enviado, status HTTP de respuesta,
        y si fue exitoso o no. Útil para debugging.
      parameters:
        - $ref: "#/components/parameters/webhookId"
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Cantidad de registros a saltar (paginación)
        - name: limit
          in: query
          schema:
            type: integer
            default: 100
            maximum: 100
          description: Cantidad máxima de registros a devolver
      responses:
        "200":
          description: Lista de entregas
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/WebhookDeliveryLog"
        "404":
          description: Webhook no encontrado

  /v1/webhooks/ping:
    post:
      summary: Probar conectividad
      operationId: pingWebhook
      tags: [Webhooks]
      description: |
        Envía un POST de prueba a una URL para verificar que es accesible antes de crear un webhook.
        No requiere que el webhook exista. Timeout: 10 segundos.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url:
                  type: string
                  format: uri
                  example: "https://tu-servidor.com/webhook"
      responses:
        "200":
          description: Resultado de la prueba
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  status:
                    type: integer
                    description: HTTP status code de la respuesta (solo si exitoso)
                  error:
                    type: string
                    description: Mensaje de error (solo si falló)
              example:
                success: true
                status: 200

  /v1/webhooks/{webhookId}/test:
    post:
      summary: Enviar evento de prueba
      operationId: testWebhook
      tags: [Webhooks]
      description: |
        Envía un evento de prueba real al webhook. El payload incluye `"test": true` para que
        tu servidor pueda distinguirlo de eventos reales. La entrega se registra en el log.
      parameters:
        - $ref: "#/components/parameters/webhookId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [eventType]
              properties:
                eventType:
                  $ref: "#/components/schemas/WebhookEventType"
      responses:
        "200":
          description: Resultado del envío
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
              example:
                success: true
                message: "Test event delivered (200)"
        "404":
          description: Webhook no encontrado

tags:
  - name: System
    description: Estado del gateway
  - name: Accounts
    description: Información y configuración de la cuenta
  - name: Channels
    description: Canales configurados
  - name: Chatbots
    description: Configuración de chatbots y triggers
  - name: Contacts
    description: Buscar y gestionar contactos (Elasticsearch)
  - name: Conversations
    description: Conversaciones multicanal (Web, WhatsApp, Facebook, Instagram)
  - name: Phases
    description: Fases del pipeline de conversaciones
  - name: Plans
    description: Planes de suscripción
  - name: Tags
    description: Tags de operador para clasificar conversaciones
  - name: Teams
    description: Equipos de operadores
  - name: Users
    description: Usuarios y roles de la empresa
  - name: Webhooks
    description: |
      Suscribite a eventos en tiempo real. Cliengo envía un `POST` a tu URL cada vez que ocurre
      un evento en tus conversaciones o contactos. Los webhooks incluyen reintentos automáticos
      con backoff exponencial, headers de seguridad, y un log de entregas para debugging.

      **[Ver guia completa de Webhooks →](guides.html#webhooks)** — setup, eventos, payload, seguridad y buenas practicas.
  - name: WhatsApp
    description: Plantillas y campañas de WhatsApp
