openapi: 3.1.0
info:
  title: IAReview Public API
  version: "1.1.1"
  summary: Partner REST API for feedbacks, tratativas (tickets), compilations, webhooks and CS metrics
  description: |
    API pública para integração B2B com a plataforma IAReview (NPS/CSAT e close-the-loop).

    ## Autenticação
    1. Obtenha o UUID da conta (`X-Customer-Id`) e o `secretKey` no painel (**Configurações → Organização e API**).
    2. `POST /v1/signin-with-credentials` com headers `X-Customer-Id` e `secretKey`.
    3. Use `Authorization: Bearer <access_token>` e o **mesmo** `X-Customer-Id` nas demais rotas.
    4. O header `apiKey` ainda é aceito no login como sinônimo de `X-Customer-Id`. `secretKey` **não** vai no `Authorization`.

    ## Feedbacks
    - `POST /v1/feedbacks` — cria avaliação **pendente** e devolve o link único. Não grava nota.
    - `POST /v1/feedbacks/invites` — o mesmo fluxo, com disparo de e-mail/WhatsApp quando o canal pede.
    - `GET /v1/feedbacks/{id}/{customerId}/form` — payload público da tela de resposta.
    - `PUT /v1/feedbacks/{id}/{customerId}/response` — o respondente envia a nota (público). Sucesso **201**.

    ## Métricas
    `GET /v1/metrics/scores` e `GET /v1/metrics/cs` usam só `from` e `to` (UTC).
    Sem esses params: últimos 30 dias. `productId` **não** é query — o breakdown por produto vem em `byProduct`.

    ## Tickets
    Tickets mapeiam a tratativa (recovery) de uma avaliação. O `{id}` é o `evaluationId`.
    `GET /v1/tickets/{id}` devolve a thread (mensagens, status, responsável, CSAT da tratativa).

    ## Compilações
    Insights em lote gerados pelo cron (`default_analysis` / `monthly_analysis`).
    `compilation.ready` avisa quando o JSON completo já está em `GET /v1/compilations/{id}`.

    ## Webhooks outbound
    Eventos: `feedback.detractor_created`, `feedback.promoter_created`, `feedback.passive_created`,
    `ticket.replied_by_customer`, `ticket.status_changed`, `ticket.resolved`,
    `detractor.insight.ready`, `compilation.ready`. Formatos: `json`, `discord`, `slack`.
  contact:
    name: IAReview
    url: https://iareview.ai
  license:
    name: Proprietary
  x-logo:
    url: /static/landingPage.svg
    altText: IAReview
    href: https://iareview.ai
    backgroundColor: transparent

servers:
  - url: https://api.iareview.ai
    description: Production
  - url: https://api.iareview.online
    description: Staging / alternate

tags:
  - name: Auth
    description: X-Customer-Id + secretKey → JWT; apiKey ainda aceito no login
  - name: Feedbacks
    description: Listagem, ingestão, convite (link único) e resposta do cliente
  - name: Tickets
    description: Tratativa (close-the-loop) via API
  - name: Compilations
    description: Insights em lote (cron). GET lista/detalhe; webhook `compilation.ready` quando grava.
  - name: Webhooks
    description: |
      Outbound POST. O campo `format` define o **corpo**, não o destino.
      URL de Discord/Slack com `format=json` recebe `{type,timestamp,data}`, não embed/Block Kit.

      - `json`: envelope + `X-IAReview-Signature` opcional (n8n, Zapier, API).
      - `discord`: Incoming Webhook (`username` + `embeds`).
      - `slack`: Incoming Webhook (`text` + Block Kit).

      Teste dispara cada evento assinado no formato salvo.
  - name: Metrics
    description: KPIs de Customer Success e índices NPS/CSAT/CES já calculados

paths:
  /v1/signin-with-credentials:
    post:
      tags: [Auth]
      summary: Obter JWT com X-Customer-Id + secretKey
      description: |
        `X-Customer-Id` é o UUID do tenant (o mesmo das demais rotas).
        `secretKey` é a senha do client — **só** neste endpoint.
        O header `apiKey` ainda é aceito como sinônimo de `X-Customer-Id`.
      operationId: signInWithCredentials
      security: []
      parameters:
        - name: X-Customer-Id
          in: header
          required: true
          description: UUID do tenant (Keycloak client_id). Mesmo valor das demais rotas.
          schema:
            type: string
            format: uuid
          example: "11111111-1111-1111-1111-111111111111"
        - name: secretKey
          in: header
          required: true
          description: Senha do client. Não usar no Authorization das outras rotas.
          schema:
            type: string
        - name: apiKey
          in: header
          required: false
          description: Aceito como sinônimo de X-Customer-Id (integrações antigas).
          schema:
            type: string
      responses:
        "200":
          description: Token de acesso
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignInResponse"
        "401":
          description: Credenciais inválidas

  /v1/feedbacks:
    get:
      tags: [Feedbacks]
      summary: Listar feedbacks (paginado / filtrável)
      description: |
        Paginação por `limit` + `offset` (não usa `page`).
        Datas: `createdDateStart`/`createdDateEnd` (criação do convite) e
        `respondedDateStart`/`respondedDateEnd` (quando o cliente respondeu).
      operationId: listFeedbacks
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - name: limit
          in: query
          description: Itens por página. Default 15.
          schema:
            type: integer
            minimum: 1
            default: 15
        - name: offset
          in: query
          description: Itens a pular. Default 0.
          schema:
            type: integer
            minimum: 0
            default: 0
        - name: order
          in: query
          description: |
            1 categoria A–Z, 2 categoria Z–A, 3 criação asc, 4 criação desc (default),
            5 resposta asc, 6 resposta desc.
          schema:
            type: string
            enum: ["1", "2", "3", "4", "5", "6"]
            default: "4"
        - name: channel
          in: query
          schema:
            type: string
        - name: category
          in: query
          description: Categoria do produto
          schema:
            type: string
        - name: product
          in: query
          description: Nome do produto (não é o UUID)
          schema:
            type: string
        - name: evaluationType
          in: query
          schema:
            type: string
            enum: [NPS, CSAT, BOTH, CES]
        - name: createdDateStart
          in: query
          description: YYYY-MM-DD ou RFC3339
          schema:
            type: string
        - name: createdDateEnd
          in: query
          description: YYYY-MM-DD ou RFC3339
          schema:
            type: string
        - name: respondedDateStart
          in: query
          description: YYYY-MM-DD ou RFC3339
          schema:
            type: string
        - name: respondedDateEnd
          in: query
          description: YYYY-MM-DD ou RFC3339
          schema:
            type: string
        - name: moderationStatus
          in: query
          schema:
            type: string
            enum: [PENDING, REJECTED, APPROVED, FLAGGED_AUTO]
        - name: npsClassification
          in: query
          schema:
            type: string
            enum: [PROMOTER, NEUTRAL, DETRACTOR]
        - name: csatClassification
          in: query
          schema:
            type: string
            enum: [SATISFIED, NEUTRAL, DISSATISFIED]
        - name: recoveryStatus
          in: query
          schema:
            type: string
            enum: [none, in_progress, waiting_customer, waiting_cs, closed]
      responses:
        "200":
          description: Lista paginada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackList"
    post:
      tags: [Feedbacks]
      summary: Criar avaliação pendente (link único)
      description: |
        Mesmo handler de `POST /v1/evaluation`: cria avaliação **pendente** e devolve
        `link` + `id`. Não persiste NPS/CSAT/CES — a nota entra em
        `PUT /v1/feedbacks/{id}/{customerId}/response`.

        Canal default `api`. Para disparar e-mail/WhatsApp use `POST /v1/feedbacks/invites`.
      operationId: createFeedback
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFeedbackRequest"
      responses:
        "201":
          description: Avaliação pendente criada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackCreated"

  /v1/feedbacks/{id}:
    get:
      tags: [Feedbacks]
      summary: Obter feedback por ID
      description: |
        Inclui `recoveryStatus`, sentimento da moderação (`feelingScore`, `labelFeeling`)
        e o micro-insight persistido (`microInsight`) quando o detrator já teve insight gerado.
      operationId: getFeedback
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Detalhe do feedback
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Feedback"
        "404":
          description: Não encontrado

  /v1/feedbacks/invites:
    post:
      tags: [Feedbacks]
      summary: Criar convite / link único
      description: |
        Cria avaliação pendente, devolve o link público e dispara e-mail/WhatsApp
        quando `channel` for `Email` ou `WhatsApp` e houver destinatário.
      operationId: createFeedbackInvite
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFeedbackInviteRequest"
      responses:
        "201":
          description: Convite criado (inclui `link`)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackCreated"

  /v1/feedbacks/{id}/{customerId}/form:
    get:
      tags: [Feedbacks]
      summary: Payload público da tela de resposta
      description: Sem JWT. Usado pela página `/review/{id}/{customerId}`.
      operationId: getFeedbackForm
      security: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/CustomerIdPath"
      responses:
        "200":
          description: Dados para renderizar o formulário
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FeedbackForm"
        "404":
          description: Não encontrado

  /v1/feedbacks/{id}/{customerId}/response:
    put:
      tags: [Feedbacks]
      summary: Enviar resposta do cliente
      description: Público (sem JWT). O respondente submete NPS/CSAT/CES no link único.
      operationId: submitFeedbackResponse
      security: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
        - $ref: "#/components/parameters/CustomerIdPath"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SubmitFeedbackResponseRequest"
      responses:
        "201":
          description: Resposta gravada (texto `"Resposta de avaliação enviada com sucesso!"`)
          content:
            application/json:
              schema:
                type: string
                example: Resposta de avaliação enviada com sucesso!
        "400":
          description: Já respondido ou payload inválido

  /v1/tickets/{id}:
    get:
      tags: [Tickets]
      summary: Obter thread da tratativa
      description: |
        O `{id}` é o `evaluationId`. Devolve conversa, mensagens, status, responsável
        e CSAT da tratativa. Sem dump de e-mail no CSV — o objeto completo está aqui.
      operationId: getTicket
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Thread da tratativa. Sem conversa ainda, `messages` vem `[]` e `status` é o da avaliação.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TicketThread"
        "404":
          description: Feedback não encontrado

  /v1/tickets/{id}/reply:
    post:
      tags: [Tickets]
      summary: Enviar e-mail de tratativa
      operationId: replyTicket
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TicketReplyRequest"
      responses:
        "200":
          description: E-mail enfileirado
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                  conversationId:
                    type: string
                    format: uuid
                  messageId:
                    type: string
                    format: uuid
                  recoveryStatus:
                    type: string
                  correlationToken:
                    type: string

  /v1/tickets/{id}/status:
    patch:
      tags: [Tickets]
      summary: Atualizar status da tratativa
      description: |
        `RESOLVED` ou `CLOSED` → encerra (close).
        `IN_PROGRESS` ou `OPEN` → reabre.
      operationId: patchTicketStatus
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TicketStatusRequest"
      responses:
        "200":
          description: Status atualizado. `csatUrl` só existe se o close interno gerar token (o alias público não lê `sendCsat`).
          content:
            application/json:
              schema:
                type: object
                properties:
                  recoveryStatus:
                    type: string
                  csatSent:
                    type: boolean
                  csatUrl:
                    type: string
                  closedBy:
                    type: string
                  reopenedBy:
                    type: string

  /v1/tickets/{id}/assign:
    post:
      tags: [Tickets]
      summary: Atribuir tratativa a um atendente
      operationId: assignTicket
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TicketAssignRequest"
      responses:
        "200":
          description: Atribuído
          content:
            application/json:
              schema:
                type: object
                properties:
                  assignedToEmail:
                    type: string
                  assignedToUserId:
                    type: string

  /v1/compilations:
    get:
      tags: [Compilations]
      summary: Listar compilações de insights
      description: |
        Paginação `limit` + `offset`. Datas deste endpoint são `dateStart`/`dateEnd`
        (não `from`/`to`). Sucesso responde **201** (status do handler interno).
      operationId: listCompilations
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            default: 15
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
        - name: analysisType
          in: query
          schema:
            type: string
            enum: [default_analysis, monthly_analysis]
        - name: dateStart
          in: query
          description: Início (YYYY-MM-DD ou RFC3339). Default 12 meses atrás.
          schema:
            type: string
        - name: dateEnd
          in: query
          description: Fim (YYYY-MM-DD ou RFC3339). Default agora.
          schema:
            type: string
      responses:
        "201":
          description: Lista paginada
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompilationList"

  /v1/compilations/{id}:
    get:
      tags: [Compilations]
      summary: Obter compilação por ID
      description: Sucesso responde **201** (status do handler interno).
      operationId: getCompilation
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "201":
          description: Compilação completa
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Compilation"
        "400":
          description: ID inválido

  /v1/webhooks:
    get:
      tags: [Webhooks]
      summary: Listar webhooks outbound
      operationId: listWebhooks
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      responses:
        "200":
          description: Lista
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Webhook"
    post:
      tags: [Webhooks]
      summary: Criar webhook outbound
      operationId: createWebhook
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookInput"
      responses:
        "201":
          description: Criado
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Webhook"

  /v1/webhooks/{id}:
    delete:
      tags: [Webhooks]
      summary: Remover webhook
      operationId: deleteWebhook
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/ResourceId"
      responses:
        "200":
          description: Removido
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: deleted

  /v1/metrics/scores:
    get:
      tags: [Metrics]
      summary: NPS, CSAT e CES do período
      description: |
        Índices já calculados (mesma conta do painel V2). Só avaliações `APPROVED`.
        O recorte temporal usa `created_at`.

        A resposta traz o **geral da conta** em `nps` / `csat` / `ces` e, no mesmo
        objeto, `byProduct`: um item por produto (`productId` + `productName` + as
        mesmas métricas). Não existe query de produto — o breakdown vem sempre.

        Sem `from`/`to`: últimos 30 dias até agora (UTC).
      operationId: getScoreMetrics
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/DateFrom"
        - $ref: "#/components/parameters/DateTo"
      responses:
        "200":
          description: Índices do período
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ScoreMetrics"
              example:
                from: "2026-09-01T00:00:00Z"
                to: "2026-10-01T00:00:00Z"
                nps:
                  score: 40
                  promoters: 7
                  neutrals: 1
                  detractors: 2
                  total: 10
                  byProduct:
                    - productId: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
                      productName: Loja A
                      score: 50
                      promoters: 3
                      neutrals: 0
                      detractors: 1
                      total: 4
                csat:
                  score: 4.2
                  satisfied: 12
                  neutrals: 3
                  dissatisfied: 2
                  total: 17
                  byProduct: []
                ces:
                  score: 5.8
                  lowEffort: 8
                  neutrals: 2
                  highEffort: 1
                  total: 11
                  byProduct: []

  /v1/metrics/cs:
    get:
      tags: [Metrics]
      summary: Métricas de CS (FRT, MTTR, Churn Saved, CSAT tratativa)
      description: |
        Sem `from`/`to`: últimos 30 dias até agora (UTC).
      operationId: getCSMetrics
      security:
        - BearerAuth: []
          CustomerIdHeader: []
      parameters:
        - $ref: "#/components/parameters/DateFrom"
        - $ref: "#/components/parameters/DateTo"
      responses:
        "200":
          description: Agregados do período
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CSMetrics"
              example:
                from: "2026-09-01T00:00:00Z"
                to: "2026-10-01T00:00:00Z"
                avgFrtSeconds: 3600
                avgMttrSeconds: 86400
                closedCount: 10
                resolutionConfirmedCount: 7
                churnSavedRate: 0.7
                avgTratativaCsat: 4.2
                conversationCount: 12
                byAttendant:
                  - attendantEmail: cs@acme.com
                    assignedCount: 8
                    closedCount: 6
                    avgFrtSeconds: 1800
                    avgMttrSeconds: 72000

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access token (`access_token`) retornado por `/v1/signin-with-credentials`. Não é o secretKey.
      x-scalar-secret-token: YOUR_ACCESS_TOKEN
    CustomerIdHeader:
      type: apiKey
      in: header
      name: X-Customer-Id
      description: UUID do tenant (o mesmo usado no login). Não é o secretKey.
      x-scalar-secret-token: YOUR_CUSTOMER_ID

  parameters:
    ResourceId:
      name: id
      in: path
      required: true
      description: UUID do recurso (`evaluationId` em tickets/feedbacks; ID da compilação em compilations).
      schema:
        type: string
        format: uuid
    CustomerIdPath:
      name: customerId
      in: path
      required: true
      schema:
        type: string
        format: uuid
    DateFrom:
      name: from
      in: query
      required: false
      description: |
        Início do período em UTC. `YYYY-MM-DD` ou RFC3339 (`2026-09-01T00:00:00Z`).
        Omitido: 30 dias atrás.
      schema:
        type: string
        example: "2026-09-01"
    DateTo:
      name: to
      in: query
      required: false
      description: |
        Fim do período em UTC. `YYYY-MM-DD` inclui o dia inteiro (vira meia-noite do dia seguinte).
        RFC3339 usa o instante informado. Omitido: agora.
      schema:
        type: string
        example: "2026-10-01"

  schemas:
    SignInResponse:
      type: object
      properties:
        access_token:
          type: string
        expires_in:
          type: integer
        token_type:
          type: string
          example: Bearer
      additionalProperties: true

    FeedbackList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Feedback"
        total:
          type: integer

    Feedback:
      type: object
      description: Avaliação com sentimento da moderação e micro-insight persistido.
      properties:
        id:
          type: string
          format: uuid
        productId:
          type: string
          format: uuid
        productName:
          type: string
        categoryProduct:
          type: string
        evaluationType:
          type: string
          enum: [NPS, CSAT, BOTH, CES]
        npsScore:
          type: number
        evaluationNote:
          type: number
          description: Nota CSAT 1–5
        cesScore:
          type: number
          description: Nota CES 1–7
        npsClassification:
          type: string
          enum: [PROMOTER, NEUTRAL, DETRACTOR]
        csatClassification:
          type: string
          enum: [SATISFIED, NEUTRAL, DISSATISFIED]
        cesClassification:
          type: string
          enum: [LOW_EFFORT, NEUTRAL, HIGH_EFFORT]
        textAvaluation:
          type: string
        channelEvaluationSended:
          type: string
        customerId:
          type: string
          format: uuid
        responderName:
          type: string
        responderEmail:
          type: string
        responderPhoneNumber:
          type: string
        dateTimeAvaluation:
          type: string
          format: date-time
          description: Quando o cliente respondeu
        createdAt:
          type: string
          format: date-time
        recoveryStatus:
          type: string
          enum: [none, in_progress, waiting_customer, waiting_cs, closed]
        moderationStatus:
          type: string
        feelingScore:
          type: string
          description: Sentimento da resposta (pipeline de moderação).
        labelFeeling:
          type: string
        microInsight:
          $ref: "#/components/schemas/MicroInsight"
        evaluationUrl:
          type: string
          format: uri
      additionalProperties: true

    MicroInsight:
      type: object
      description: Insight pontual do detrator (não inclui sentimento; esse vem de feelingScore).
      properties:
        version:
          type: integer
        status:
          type: string
          enum: [ready, failed]
        summary:
          type: string
        why:
          type: string
        causes:
          type: array
          items:
            type: string
        suggestedAction:
          type: string
        confidence:
          type: number
        generatedAt:
          type: string
          format: date-time

    TicketThread:
      type: object
      properties:
        conversationId:
          type: string
          format: uuid
        status:
          type: string
        primaryChannel:
          type: string
        messageCount:
          type: integer
        whatsappEligible:
          type: boolean
        canSendEmail:
          type: boolean
        assignedToEmail:
          type: string
        closedBy:
          type: string
        reopenedBy:
          type: string
        firstResponseAt:
          type: string
          format: date-time
        resolvedAt:
          type: string
          format: date-time
        resolutionConfirmed:
          type: boolean
        tratativaCsatScore:
          type: integer
        messages:
          type: array
          items:
            $ref: "#/components/schemas/TicketMessage"

    TicketMessage:
      type: object
      properties:
        id:
          type: string
          format: uuid
        channel:
          type: string
        direction:
          type: string
        bodyText:
          type: string
        subject:
          type: string
        status:
          type: string
        sentByUserId:
          type: string
        createdAt:
          type: string
          format: date-time

    Compilation:
      type: object
      properties:
        id:
          type: string
          format: uuid
        customerId:
          type: string
          format: uuid
        batchAnalysisType:
          type: string
          enum: [default_analysis, monthly_analysis]
        dateTimeBatch:
          type: string
          format: date-time
        totalReviews:
          type: integer
        positiveReviews:
          type: integer
        negativeReviews:
          type: integer
        neutralReviews:
          type: integer
        averageFeelingScore:
          type: number
        generalSentiment:
          type: string
        sentimentalTrends:
          type: string
        businessInsights:
          type: string
        businessTrends:
          type: string
        improvementSuggestions:
          type: string
        commonPositiveAspects:
          type: string
        commonNegativeAspects:
          type: string
        commonRelevantKeywords:
          type: string
        commonIdentifiedTopics:
          type: string
        analysisComments:
          type: string
        monthlyComparisonSummary:
          type: string
        trendAnalysisSummary:
          type: string
        createdAt:
          type: string
          format: date-time
      additionalProperties: true

    CompilationList:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Compilation"
        total:
          type: integer

    CreateFeedbackRequest:
      type: object
      required: [productId, productName, categoryProduct, evaluationType]
      properties:
        productId:
          type: string
          format: uuid
        productName:
          type: string
        categoryProduct:
          type: string
        evaluationType:
          type: string
          enum: [NPS, CSAT, BOTH, CES]
        responderName:
          type: string
        responderEmail:
          type: string
          format: email
        responderPhoneNumber:
          type: string
        channel:
          type: string
          description: Default `api`. Não dispara e-mail/WhatsApp.
          default: api
          example: api

    FeedbackCreated:
      type: object
      properties:
        link:
          type: string
          format: uri
        id:
          type: string
          format: uuid
        evaluationType:
          type: string
        channelAvaluationSended:
          type: string
        productId:
          type: string
          format: uuid
        productName:
          type: string
        customerId:
          type: string
          format: uuid
        createdAt:
          type: string
          format: date-time
        categoryProduct:
          type: string

    FeedbackForm:
      type: object
      properties:
        productDescription:
          type: string
        productName:
          type: string
        productImageUrl:
          type: string
        customerName:
          type: string
        customerLogoUrl:
          type: string
        feedbackConfig:
          type: object
        evaluation:
          type: object
          description: Avaliação pendente (campos internos do formulário público)

    CreateFeedbackInviteRequest:
      type: object
      required: [productId, productName, categoryProduct, evaluationType, channel]
      properties:
        productId:
          type: string
          format: uuid
        productName:
          type: string
        categoryProduct:
          type: string
        evaluationType:
          type: string
          enum: [NPS, CSAT, BOTH, CES]
        channel:
          type: string
          description: Preferido. `Email` ou `WhatsApp` dispara o envio. Outros valores só geram o link.
          example: Email
        channelEvaluationSended:
          type: string
          description: Aceito como sinônimo de `channel`
        name:
          type: string
        responderName:
          type: string
        email:
          type: string
          format: email
        responderEmail:
          type: string
          format: email
        phoneNumber:
          type: string
          example: "+5511999881234"
        responderPhoneNumber:
          type: string
        comments:
          type: string
        templateName:
          type: string

    SubmitFeedbackResponseRequest:
      type: object
      properties:
        npsScore:
          type: number
          minimum: 0
          maximum: 10
        evaluationNote:
          type: number
          description: CSAT 1–5
          minimum: 1
          maximum: 5
        cesScore:
          type: number
          description: CES 1–7
          minimum: 1
          maximum: 7
        textAvaluation:
          type: string
        responderName:
          type: string
        responderEmail:
          type: string
          format: email
        languageAvaluation:
          type: string

    TicketReplyRequest:
      type: object
      required: [subject, body]
      properties:
        subject:
          type: string
        body:
          type: string
        channel:
          type: string
          enum: [email]
          default: email

    TicketStatusRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [RESOLVED, CLOSED, IN_PROGRESS, OPEN]

    TicketAssignRequest:
      type: object
      required: [email]
      properties:
        email:
          type: string
          format: email
        userId:
          type: string

    WebhookInput:
      type: object
      required: [url]
      properties:
        name:
          type: string
        url:
          type: string
          format: uri
          description: Deve ser HTTPS
        format:
          type: string
          enum: [json, discord, slack]
          default: json
          description: |
            Corpo do POST. `json` sempre envia o envelope, mesmo se a URL for Discord/Slack.
        authType:
          type: string
          enum: [none, bearer, basic, header]
          default: none
        authHeaderName:
          type: string
        authUsername:
          type: string
        authSecret:
          type: string
        signingSecret:
          type: string
          description: HMAC para format=json
        events:
          type: array
          description: Vazio → default `detractor.insight.ready`.
          items:
            type: string
            enum:
              - feedback.detractor_created
              - feedback.promoter_created
              - feedback.passive_created
              - ticket.replied_by_customer
              - ticket.status_changed
              - ticket.resolved
              - detractor.insight.ready
              - compilation.ready
              - "*"
        enabled:
          type: boolean
          default: true

    Webhook:
      type: object
      description: Resposta sanitizada. Segredos nunca voltam; só `hasAuthSecret` / `hasSigningSecret`.
      properties:
        id:
          type: string
          format: uuid
        customerId:
          type: string
          format: uuid
        name:
          type: string
        url:
          type: string
          format: uri
        format:
          type: string
          enum: [json, discord, slack]
        authType:
          type: string
        authHeaderName:
          type: string
        authUsername:
          type: string
        events:
          type: array
          items:
            type: string
        enabled:
          type: boolean
        hasAuthSecret:
          type: boolean
        hasSigningSecret:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    WebhookDeliveryJson:
      type: object
      required: [type, timestamp, data]
      description: |
        Corpo quando format=json (também se a URL for Discord/Slack).
        `compilation.ready` traz `compilationId`, `type`, `createdAt`, `totalReviews`,
        `generalSentiment` e `insightsUrl`; o JSON completo vem em GET `/v1/compilations/{id}`.
      properties:
        type:
          type: string
        timestamp:
          type: string
          format: date-time
        data:
          type: object
      example:
        type: feedback.promoter_created
        timestamp: "2026-10-20T16:00:00Z"
        data:
          customerId: "11111111-1111-1111-1111-111111111111"
          evaluationId: "22222222-2222-2222-2222-222222222222"
          productName: "Atendimento"
          evaluationType: NPS
          npsScore: 10
          classification: PROMOTER
          textPreview: ótimo
          reviewUrl: https://iareview.ai/app/reviews/item/22222222-2222-2222-2222-222222222222

    WebhookDeliveryDiscord:
      type: object
      description: Corpo quando format=discord (Incoming Webhook).
      example:
        username: IAReview
        embeds:
          - title: Promotor
            description: ótimo
            color: 1483361
            url: https://iareview.ai/app/reviews/item/22222222-2222-2222-2222-222222222222

    WebhookDeliverySlack:
      type: object
      description: Corpo quando format=slack (Incoming Webhook + Block Kit).
      example:
        text: Promotor — ótimo
        blocks:
          - type: header
            text:
              type: plain_text
              text: Promotor

    ScoreMetrics:
      type: object
      required: [from, to, nps, csat, ces]
      properties:
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        nps:
          $ref: "#/components/schemas/NpsMetric"
        csat:
          $ref: "#/components/schemas/CsatMetric"
        ces:
          $ref: "#/components/schemas/CesMetric"

    NpsMetric:
      type: object
      properties:
        score:
          type: number
          description: (promotores% − detratores%), escala −100 a 100
        promoters:
          type: integer
        neutrals:
          type: integer
        detractors:
          type: integer
        total:
          type: integer
        byProduct:
          type: array
          items:
            type: object
            properties:
              productId:
                type: string
                format: uuid
              productName:
                type: string
              score:
                type: number
              promoters:
                type: integer
              neutrals:
                type: integer
              detractors:
                type: integer
              total:
                type: integer

    CsatMetric:
      type: object
      properties:
        score:
          type: number
          description: Média 1–5
        satisfied:
          type: integer
        neutrals:
          type: integer
        dissatisfied:
          type: integer
        total:
          type: integer
        byProduct:
          type: array
          items:
            type: object
            properties:
              productId:
                type: string
                format: uuid
              productName:
                type: string
              score:
                type: number
              satisfied:
                type: integer
              neutrals:
                type: integer
              dissatisfied:
                type: integer
              total:
                type: integer

    CesMetric:
      type: object
      properties:
        score:
          type: number
          description: Média CES 2.0 (1–7, maior = mais fácil)
        lowEffort:
          type: integer
        neutrals:
          type: integer
        highEffort:
          type: integer
        total:
          type: integer
        byProduct:
          type: array
          items:
            type: object
            properties:
              productId:
                type: string
                format: uuid
              productName:
                type: string
              score:
                type: number
              lowEffort:
                type: integer
              neutrals:
                type: integer
              highEffort:
                type: integer
              total:
                type: integer

    CSMetrics:
      type: object
      properties:
        from:
          type: string
          format: date-time
        to:
          type: string
          format: date-time
        avgFrtSeconds:
          type: number
        avgMttrSeconds:
          type: number
        closedCount:
          type: integer
        resolutionConfirmedCount:
          type: integer
        churnSavedRate:
          type: number
          description: resolutionConfirmedCount / closedCount
        avgTratativaCsat:
          type: number
        conversationCount:
          type: integer
        byAttendant:
          type: array
          items:
            type: object
            properties:
              attendantEmail:
                type: string
              assignedCount:
                type: integer
              closedCount:
                type: integer
              avgFrtSeconds:
                type: number
              avgMttrSeconds:
                type: number
