openapi: 3.1.0

info:
  title: API de Integração MEDX
  version: 1.0.0
  description: |
    API REST para integração de sistemas de clientes ao **MEDX** — agenda, contatos/pacientes e finanças.
    Todas as requisições e respostas usam **JSON**.

    ## Como obter o token de integração
    O **token de integração** (de longa duração, vinculado à sua conta) é obtido **dentro do sistema MEDX**:

    1. Acesse **Ajustes → Integrações** (página "Integrações para links e websites").
    2. No card **"Integre o MEDX com outros sistemas"**, copie o token exibido no campo.
    3. Use **"Gerar novo token"** para regenerá-lo (isso invalida o token anterior).

    > ⚠️ **Não exponha o token de integração no lado cliente.** Faça a troca de token a partir do seu servidor; no navegador use apenas o JWT curto resultante.

    ## Autenticação (2 passos)
    1. `POST /api/integration/token` — envie `{ "IntegrationToken": "<seu token>" }` e receba um `accessToken` (JWT) válido por ~15 minutos.
    2. Nas demais chamadas, envie o cabeçalho `Authorization: Bearer <accessToken>`. Ao expirar, repita o passo 1.

    Cada endpoint exige um **escopo** (ex.: `agenda:read`, `financas:write`); o JWT emitido traz os escopos concedidos à conta.

    ## Limites de requisição (rate limiting)
    | Política | Limite | Partição |
    |---|---|---|
    | Troca de token | 10 req/min | por IP de origem |
    | Chamadas da API | 30 req/min | por conta (token bucket) |

    Ao exceder, a API responde `429 Too Many Requests` com o cabeçalho `Retry-After: 60`.

    ## Migração da integração legada
    A API antiga usava rotas planas (`api/integration/<Ação>`). Equivalências principais:
    - `GetAuthorizedToken` (GET) → `POST /api/integration/token` (retorna JWT ~15 min em vez de string de 180 min).
    - `GetPacientes` → `GET /api/integration/contatos/grid`; `GetContatosById` → `GET /api/integration/contatos/{id}`.
    - `GetTiposAgendamento` → `GET /api/integration/agenda/tipos`; `GetSetor` → `GET /api/integration/agenda/setores`.
    - `GetUsuariosAgenda` → sem equivalente direto; use `GET /api/integration/financas/profissionais`.
    - `InsertProntuario` → **não portado** para esta API.

servers:
  - url: https://integrationmedx.medx.med.br
    description: Produção
  - url: https://localhost:7261
    description: Local (desenvolvimento)

tags:
  - name: Autenticação
    description: Troca do token de integração da conta por um JWT curto de chamada.
  - name: Agenda
    description: Agendamentos, disponibilidade, ocupação, tipos e setores de agenda.
  - name: Contatos
    description: Cadastro, busca e vínculo de pacientes/contatos; consulta de endereço por CEP.
  - name: Finanças
    description: Faturas, lançamentos e tabelas de apoio (convênios, procedimentos, unidades, etc.).

security:
  - bearerAuth: []

paths:
  # ─────────────────────────── Autenticação ───────────────────────────
  /api/integration/token:
    post:
      tags: [Autenticação]
      summary: Trocar o token de integração por um JWT de chamada
      description: |
        Recebe o **IntegrationToken** da conta (obtido em Ajustes → Integrações) e devolve um JWT curto (~15 min).
        Endpoint público, porém limitado a 10 req/min por IP. Token inválido retorna `401` genérico.
      operationId: exchangeToken
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/IntegrationTokenRequest' }
      responses:
        '200':
          description: JWT emitido com sucesso.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IntegrationTokenResponse' }
        '401':
          description: Token de integração inválido.
          content:
            application/json:
              schema:
                type: object
                properties:
                  error: { type: string, example: invalid_client }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  # ─────────────────────────── Agenda ───────────────────────────
  /api/integration/agenda/agendamentos:
    get:
      tags: [Agenda]
      summary: Listar agendamentos por período
      description: Filtra por profissional (`idUsuario`) ou por setor, se informados.
      operationId: getAgenda
      parameters:
        - { name: inicio, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-01' }
        - { name: fim, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-31' }
        - { name: idUsuario, in: query, required: false, schema: { type: integer }, description: Filtra pelos agendamentos de um profissional. }
        - { name: setor, in: query, required: false, schema: { type: string }, description: Filtra pelo nome do setor. }
      responses:
        '200':
          description: Lista de agendamentos do período.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaResumo' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
    post:
      tags: [Agenda]
      summary: Criar agendamento
      description: Use `Id_do_Usuario` obtido em `GET /agenda` ou `GET /financas/profissionais`. `Vinculado_a` é o id do paciente.
      operationId: insertAgenda
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AgendaDia' }
            example:
              Id_do_Usuario: 12345
              Inicio: '2026-07-10T14:00:00'
              Final: '2026-07-10T14:30:00'
              Descricao: 'Consulta — Maria Silva'
              Status: 1
              Vinculado_a: 67890
              Id_do_TipoConsulta: 111
      responses:
        '200':
          description: Id do agendamento criado.
          content:
            application/json:
              schema: { type: string }
              example: '1275002478'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
    put:
      tags: [Agenda]
      summary: Atualizar agendamento
      description: Para deixá-lo confirmado, envie `Confirmacao` exatamente como `"Mensagem Salva com Sucesso"`.
      operationId: updateAgendamento
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AgendaDia' }
            example:
              Id_do_Agendamento: 1275002478
              Id_do_Usuario: 43013640
              Inicio: '2026-07-10T10:00:00'
              Final: '2026-07-10T13:30:00'
              Descricao: 'Rodrigo Souto, Convênio: Saúde'
              Status: 3
              Vinculado_a: 30094605
              Confirmacao: 'Mensagem Salva com Sucesso'
              Id_do_TipoConsulta: 111
      responses:
        '200':
          description: Mensagem de confirmação.
          content:
            application/json:
              schema: { type: string }
              example: 'Agendamento atualizado com sucesso.'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/agendamentos/{id}:
    get:
      tags: [Agenda]
      summary: Obter agendamento por ID
      operationId: getAgendaById
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer }, description: Id do agendamento. }
      responses:
        '200':
          description: Agendamento (lista, normalmente com 1 item).
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaDia' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/agendamentos/paciente/{idPaciente}:
    get:
      tags: [Agenda]
      summary: Listar agendamentos de um paciente
      operationId: getAgendaByPaciente
      parameters:
        - { name: idPaciente, in: path, required: true, schema: { type: integer }, description: Id do paciente. }
      responses:
        '200':
          description: Agendamentos do paciente.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaDia' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/disponibilidade:
    get:
      tags: [Agenda]
      summary: Disponibilidade (slots livres) de um profissional
      operationId: getDisponibilidade
      parameters:
        - { name: inicio, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-10' }
        - { name: fim, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-15' }
        - { name: proId, in: query, required: false, schema: { type: integer, default: 0 }, description: Id do profissional. }
        - { name: intervalo, in: query, required: false, schema: { type: integer, default: 60 }, description: Duração de cada slot em minutos. }
      responses:
        '200':
          description: Horários livres no período.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaDisponibilidade' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/ocupacao:
    get:
      tags: [Agenda]
      summary: Ocupação da agenda por período
      description: Use `idSetor = -1` para todos os setores.
      operationId: getOcupacaoPeriodo
      parameters:
        - { name: inicio, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-01' }
        - { name: fim, in: query, required: true, schema: { type: string, format: date-time }, example: '2026-07-31' }
        - { name: idSetor, in: query, required: false, schema: { type: integer, default: -1 }, description: Id do setor; -1 para todos. }
      responses:
        '200':
          description: Ocupação no período.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaOcupacao' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/integracao:
    get:
      tags: [Agenda]
      summary: Agendamentos do dia (dados de paciente/médico)
      description: Se `dia` for omitido, usa o dia atual.
      operationId: getAgendamentosIntegration
      parameters:
        - { name: dia, in: query, required: false, schema: { type: string, format: date-time }, description: 'Dia consultado. Padrão: hoje.' }
      responses:
        '200':
          description: Agendamentos do dia enriquecidos.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaIntegracao' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/status:
    get:
      tags: [Agenda]
      summary: Nomes dos status da agenda
      description: Retorna um array de rótulos; o índice corresponde ao valor de `Status` do agendamento.
      operationId: getStatusNomeAgenda
      responses:
        '200':
          description: Lista de nomes de status.
          content:
            application/json:
              schema: { type: array, items: { type: string } }
              example: ['Agendado', 'Confirmado', 'Chegou', 'Cancelado']
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/tipos:
    get:
      tags: [Agenda]
      summary: Listar tipos de agendamento
      operationId: getAllAgendaTipos
      responses:
        '200':
          description: Tipos de agendamento.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendamentosTipos' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
    post:
      tags: [Agenda]
      summary: Criar tipo de agendamento
      description: Define a duração padrão (em minutos) e o diagnóstico relacionado.
      operationId: insertAgendaTipos
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AgendamentosTipos' }
            example: { DiagnosticoQP: 'Consulta de Rotina', Tempo: 30 }
      responses:
        '200':
          description: Mensagem de confirmação.
          content:
            application/json:
              schema: { type: string }
              example: 'Registro inserido com sucesso.'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/tipos/{id}:
    get:
      tags: [Agenda]
      summary: Obter tipo de agendamento por ID
      operationId: getByIdAgendaTipos
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
      responses:
        '200':
          description: Tipo de agendamento.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AgendamentosTipos' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/setores:
    get:
      tags: [Agenda]
      summary: Listar setores de agenda
      operationId: getAllAgendaSetores
      responses:
        '200':
          description: Setores de agenda.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/AgendaSetores' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
    post:
      tags: [Agenda]
      summary: Criar setor de agenda
      description: '`SMS_DDD` e `SMS_Telefone` definem o remetente de SMS do setor (opcionais).'
      operationId: insertAgendaSetores
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AgendaSetores' }
            example: { Setor: 'Clínica Geral', SMS_DDD: '11', SMS_Telefone: '999998888' }
      responses:
        '200':
          description: Mensagem de confirmação.
          content:
            application/json:
              schema: { type: string }
              example: 'Registro inserido com sucesso.'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/agenda/setores/{id}:
    get:
      tags: [Agenda]
      summary: Obter setor de agenda por ID
      operationId: getByIdAgendaSetores
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
      responses:
        '200':
          description: Setor de agenda.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AgendaSetores' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  # ─────────────────────────── Contatos ───────────────────────────
  /api/integration/contatos/search:
    get:
      tags: [Contatos]
      summary: Buscar contatos com filtros e paginação
      operationId: getContatosBySearch
      parameters:
        - { name: group, in: query, required: true, schema: { type: integer }, description: Código do grupo/critério de filtro. }
        - { name: groupValue, in: query, required: true, schema: { type: string }, description: Valor do grupo/critério. }
        - { name: name, in: query, required: false, schema: { type: string }, description: Filtra pelo nome do contato. }
        - { name: take, in: query, required: false, schema: { type: integer, default: 0 }, description: Quantidade a retornar (0 = todos). }
        - { name: skip, in: query, required: false, schema: { type: integer, default: 0 }, description: Quantidade a pular (paginação). }
      responses:
        '200':
          description: Contatos encontrados.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/ContatosFicha' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/contatos/grid:
    get:
      tags: [Contatos]
      summary: Grid de contatos (pacientes) paginado
      operationId: getContatosGrid
      parameters:
        - { name: take, in: query, required: false, schema: { type: integer, default: 0 }, description: Quantidade a retornar (0 = todos). }
        - { name: skip, in: query, required: false, schema: { type: integer, default: 0 }, description: Quantidade a pular (paginação). }
      responses:
        '200':
          description: Linhas do grid de contatos.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/ContatosGrid' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/contatos/{id}:
    get:
      tags: [Contatos]
      summary: Obter ficha completa do contato por ID
      operationId: getContatoById
      parameters:
        - { name: id, in: path, required: true, schema: { type: integer, format: int64 }, description: Id do cliente/contato. }
      responses:
        '200':
          description: Ficha do contato.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ContatosFicha' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/contatos/cep/{cep}:
    get:
      tags: [Contatos]
      summary: Buscar endereço por CEP
      operationId: getEnderecoByCEP
      parameters:
        - { name: cep, in: path, required: true, schema: { type: string }, description: CEP (somente dígitos). }
      responses:
        '200':
          description: Endereço(s) correspondente(s) ao CEP.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/EnderecoCEP' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/contatos:
    post:
      tags: [Contatos]
      summary: Criar contato/paciente
      description: '`Sexo`: "M" masculino, "F" feminino.'
      operationId: insertContato
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ContatosFicha' }
            example:
              Nome: 'Maria Silva'
              Endereco_Residencial: 'Rua Teste, 450'
              Bairro_Residencial: 'Centro'
              Cidade_Residencial: 'São Paulo'
              Estado_Residencial: 'SP'
              Cep_Residencial: '01310100'
              Telefone_Residencial: '1123213123'
              Celular: '11999999999'
              Email: 'maria@email.com'
              Sexo: 'F'
              Nascimento: '1990-05-20T00:00:00'
              CPF_CGC: '12345678910'
              RG: '123456789'
      responses:
        '200':
          description: Resultado da operação ('Success' em caso de sucesso).
          content:
            application/json:
              schema: { type: string }
              example: 'Success'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }
    put:
      tags: [Contatos]
      summary: Atualizar contato
      description: Informe `Id_do_Cliente`.
      operationId: updateContato
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ContatosFicha' }
            example:
              Id_do_Cliente: 67890
              Nome: 'Maria Silva'
              Celular: '11988887777'
              Email: 'maria.novo@email.com'
      responses:
        '200':
          description: Resultado da operação ('Success' em caso de sucesso).
          content:
            application/json:
              schema: { type: string }
              example: 'Success'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/contatos/vinculo:
    post:
      tags: [Contatos]
      summary: Buscar/criar vínculo do paciente
      description: |
        Localiza o paciente por telefone/e-mail/nome e o cria se não existir.
        Retorna o id do cliente e se o match é confiável. Telefone é o critério principal.
      operationId: buscarVinculoPaciente
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/RequestBuscarVinculoDadosObrigatorios' }
            example:
              Name: 'Maria Silva'
              Email: 'maria@email.com'
              DateOfBirth: '1990-05-20'
              Gender: 'F'
              Phone: '11987654321'
      responses:
        '200':
          description: Resultado do vínculo.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/VinculoPacienteResult' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  # ─────────────────────────── Finanças ───────────────────────────
  /api/integration/financas/faturamentos:
    get:
      tags: [Finanças]
      summary: Listar faturamentos/atendimentos
      operationId: getFaturamentos
      parameters:
        - { name: filterstring, in: query, required: false, schema: { type: string, default: '' }, description: Coluna/campo de filtro. }
        - { name: filter, in: query, required: false, schema: { type: string, default: '' }, description: Valor do filtro. }
      responses:
        '200':
          description: Atendimentos/faturamentos.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/Atendimentos' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/faturas:
    post:
      tags: [Finanças]
      summary: Criar fatura/atendimento
      description: 'Cria uma fatura com seus procedimentos e pagamentos. Use `Acao: "insert"` nos itens que estão sendo criados.'
      operationId: insertFatura
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/FaturaGeral' }
            example:
              Iddocliente: 67890
              Iddousuario: 43013640
              NomeCliente: 'Maria Silva'
              Data: '2026-07-10T00:00:00'
              IddoConvenio: 12
              IddaUnidadedeNegocios: 1
              Observacoes: 'Consulta clínica'
              Fechado: 1
              Desconto: 0
              Acao: 'insert'
              Procedimentos:
                - { Acao: 'insert', Iddoprocedimento: 305, Procedimento: 'Consulta clínica', Qtd: 1, ValorUnitario: 250.00 }
              Pagamentos:
                - { Acao: 'insert', IddaConta: 1, Tipo: 'Dinheiro', Descricao: 'Pagamento consulta', Valor: 250.00, ValorPago: 250.00, Vencimento: '2026-07-10T00:00:00', Data_Pgto: '2026-07-10T00:00:00' }
      responses:
        '200':
          description: Mensagem de confirmação.
          content:
            application/json:
              schema: { type: string }
              example: 'Fatura inserida com sucesso'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/lancamentos:
    post:
      tags: [Finanças]
      summary: Criar lançamento financeiro
      description: Conta a pagar/receber. `Iddosubgrupo`, `Iddaconta` e `IddaUnidadedeNegocios` vêm das tabelas de apoio.
      operationId: insertLancamentos
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/Lancamentos' }
            example:
              DatadeVencimento: '2026-07-31T00:00:00'
              DatadePagamento: '2026-07-31T00:00:00'
              Descricao: 'Aluguel da sala 3'
              Numerododocumento: 'NF-4821'
              Observacao: 'Referente a julho/2026'
              ValorOriginal: 3500.00
              ValorPago: 3500.00
              Iddosubgrupo: 14
              Iddaconta: 2
              IddaUnidadedeNegocios: 1
              IddoContato: 67890
      responses:
        '200':
          description: Mensagem de confirmação.
          content:
            application/json:
              schema: { type: string }
              example: 'Lançamento inserido com sucesso'
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/unidades:
    get:
      tags: [Finanças]
      summary: Listar unidades de negócio
      operationId: getUnidades
      responses:
        '200':
          description: Unidades de negócio.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/UN' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/convenios:
    get:
      tags: [Finanças]
      summary: Listar convênios
      operationId: getConvenios
      responses:
        '200':
          description: Convênios.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/Convenios' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/profissionais:
    get:
      tags: [Finanças]
      summary: Listar profissionais (usuários)
      description: Útil para obter o `Id_do_Usuario` usado ao criar agendamentos.
      operationId: getProfissionais
      responses:
        '200':
          description: Profissionais/usuários.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/Usuario' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/procedimentos:
    get:
      tags: [Finanças]
      summary: Listar procedimentos
      operationId: getProcedimentos
      responses:
        '200':
          description: Procedimentos.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/Procedimentos' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/formas-pagamento:
    get:
      tags: [Finanças]
      summary: Listar formas de pagamento
      operationId: getFormasPagamento
      responses:
        '200':
          description: Formas de pagamento.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/FormasdePagamento' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/contas-correntes:
    get:
      tags: [Finanças]
      summary: Listar contas correntes
      operationId: getContasCorrentes
      responses:
        '200':
          description: Contas correntes.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/ContasCorrentes' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

  /api/integration/financas/subgrupos:
    get:
      tags: [Finanças]
      summary: Listar subgrupos (plano de contas)
      operationId: getSubgrupos
      responses:
        '200':
          description: Subgrupos.
          content:
            application/json:
              schema: { type: array, items: { $ref: '#/components/schemas/Subgrupos' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/TooManyRequests' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'JWT obtido em `POST /api/integration/token`. Envie como `Authorization: Bearer <accessToken>`.'

  responses:
    Unauthorized:
      description: Token ausente, inválido ou expirado.
    TooManyRequests:
      description: 'Limite de requisições excedido (rate limiting). Header `Retry-After: 60`.'
      headers:
        Retry-After:
          schema: { type: integer }
          description: Segundos até nova tentativa.

  schemas:
    IntegrationTokenRequest:
      type: object
      required: [IntegrationToken]
      properties:
        IntegrationToken:
          type: string
          description: Token de longa duração da conta (obtido em Ajustes → Integrações).
          example: 'aB3xY7kQ9mZ2pL5nR8tW1cV4'

    IntegrationTokenResponse:
      type: object
      properties:
        accessToken: { type: string, example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' }
        tokenType: { type: string, example: 'Bearer' }
        expiresInSeconds: { type: integer, example: 900 }
        scope: { type: string, example: 'contatos:read contatos:write agenda:read agenda:write financas:read financas:write' }

    AgendaDia:
      type: object
      description: Agendamento (usado em criação/atualização e no retorno por ID).
      properties:
        Id_da_Assinatura: { type: integer, description: Id da assinatura/tenant. 0 na criação. }
        Id_do_Agendamento: { type: integer, description: Id do agendamento. 0 ao criar; obrigatório ao atualizar. }
        Id_do_Usuario: { type: integer, description: Id do profissional/usuário dono do horário. }
        Inicio: { type: string, format: date-time }
        Final: { type: string, format: date-time }
        Descricao: { type: string, nullable: true }
        Status: { type: integer, description: Índice na lista de GET /agenda/status. }
        Id_do_Procedimento: { type: integer }
        Vinculado_a: { type: integer, nullable: true, description: Id do paciente. }
        Id_do_Diagnostico_QP: { type: integer, description: Id do tipo de agendamento. }
        Id_do_TipoConsulta: { type: integer, description: Alias de entrada para o tipo; se > 0 sobrescreve Id_do_Diagnostico_QP. }
        Confirmacao: { type: string, nullable: true }
        Usuario: { type: string, nullable: true, description: Nome do usuário (retorno). }
        Nome: { type: string, nullable: true, description: Nome do paciente (retorno). }
        Chegada: { type: string, format: date-time, nullable: true }
        SMS: { type: string, nullable: true }
        Atendido_as: { type: string, format: date-time, nullable: true }
        Saiu_as: { type: string, format: date-time, nullable: true }
        LastEditDate: { type: string, format: date-time, nullable: true }
        CreationDate: { type: string, format: date-time, nullable: true }
        Pendente: { type: boolean }

    AgendaResumo:
      type: object
      description: Linha de leitura de agenda por período/usuário/setor.
      properties:
        Id_Usuario: { type: integer }
        Inicio: { type: string, format: date-time }
        Fim: { type: string, format: date-time }
        Descricao: { type: string, nullable: true }
        TipoAgendamento: { type: string, nullable: true }
        Status: { type: integer }
        Id_Paciente: { type: integer }
        Id_Agendamento: { type: integer }
        Id_do_Diagnostico_QP: { type: integer }
        Confirmacao: { type: string, nullable: true }
        Chegada: { type: string, format: date-time, nullable: true }
        SMS: { type: string, nullable: true }
        Atendido_as: { type: string, format: date-time, nullable: true }
        Saiu_as: { type: string, format: date-time, nullable: true }
        LastEditDate: { type: string, format: date-time, nullable: true }
        CreationDate: { type: string, format: date-time, nullable: true }
        Setor: { type: string, nullable: true }

    AgendaDisponibilidade:
      type: object
      properties:
        Data: { type: string, format: date-time }
        De: { type: string, format: date-time }
        Ate: { type: string, format: date-time }

    AgendaOcupacao:
      type: object
      properties:
        Id_do_Agendamento: { type: integer }
        Id_do_Usuario: { type: integer }
        Usuario: { type: string, nullable: true }
        Id_Setor_Usuario: { type: integer, nullable: true }
        Setor_Usuario: { type: string, nullable: true }
        Inicio: { type: string, format: date-time }
        Final: { type: string, format: date-time }
        Descricao: { type: string, nullable: true }
        Id_Status: { type: integer }
        Id_do_Procedimento: { type: integer }
        Vinculado_a: { type: integer, nullable: true }
        Paciente: { type: string, nullable: true }
        CPF_Paciente: { type: string, nullable: true }
        Id_do_Diagnostico_QP: { type: integer }
        Confirmacao: { type: string, nullable: true }

    AgendaIntegracao:
      type: object
      properties:
        Id_do_Agendamento: { type: integer }
        Inicio: { type: string, format: date-time }
        Chegada: { type: string, format: date-time, nullable: true }
        Descricao: { type: string, nullable: true }
        Id_da_Assinatura: { type: integer }
        Id_do_Paciente: { type: integer }
        Paciente: { type: string, nullable: true }
        Nascimento: { type: string, format: date-time, nullable: true }
        Sexo: { type: string, nullable: true }
        Tipo_Agendamento: { type: string, nullable: true }
        Convenio: { type: string, nullable: true }
        Email: { type: string, nullable: true }
        Telefone_Residencial: { type: string, nullable: true }
        Telefone_Residencial_1: { type: string, nullable: true }
        Celular: { type: string, nullable: true }
        Medico: { type: string, nullable: true }
        Conselho: { type: string, nullable: true }
        Numero_Conselho: { type: string, nullable: true }
        UF_Conselho: { type: string, nullable: true }

    AgendamentosTipos:
      type: object
      properties:
        IddaAssinatura: { type: integer, nullable: true }
        IddoDiagnosticoQP: { type: integer }
        DiagnosticoQP: { type: string, description: Descrição/diagnóstico principal. }
        Tempo: { type: integer, nullable: true, description: Duração padrão em minutos. }

    AgendaSetores:
      type: object
      properties:
        IddaAssinatura: { type: integer, nullable: true }
        IddoSetor: { type: integer, format: int64 }
        Setor: { type: string }
        SMS_DDD: { type: string }
        SMS_Telefone: { type: string }

    ContatosFicha:
      type: object
      description: Ficha completa do contato/paciente.
      properties:
        Id_da_Assinatura: { type: integer, nullable: true }
        Id_do_Cliente: { type: integer }
        Nome: { type: string }
        Nome_Social: { type: string }
        Endereco_Residencial: { type: string }
        Endereco_Comercial: { type: string }
        Cidade_Residencial: { type: string }
        Cidade_Comercial: { type: string }
        Estado_Residencial: { type: string }
        Estado_Comercial: { type: string }
        Cep_Residencial: { type: string }
        Cep_Comercial: { type: string }
        Pais_Residencial: { type: string }
        Pais_Comercial: { type: string }
        Telefone_Residencial: { type: string }
        Telefone_Residencial_1: { type: string }
        Celular: { type: string }
        Observacoes: { type: string }
        Sexo: { type: string, description: '"M" ou "F".' }
        Estado_Civil: { type: string }
        Bairro_Residencial: { type: string }
        Bairro_Comercial: { type: string }
        Telefone_Comercial: { type: string }
        Profissao: { type: string }
        Email: { type: string }
        Nascimento: { type: string, format: date-time, nullable: true }
        CPF_CGC: { type: string }
        Tipo: { type: string }
        Mala_Direta: { type: boolean }
        Id_do_Convenio: { type: integer, nullable: true }
        VIP: { type: boolean }
        Numero_da_Matricula: { type: string }
        RG: { type: string }
        Mae: { type: string }
        Pai: { type: string }
        Conjugue: { type: string }
        Acompanhante: { type: string }
        Contato: { type: string }
        Tags: { type: string }
        Como_conheceu: { type: string }
        Indicado_por: { type: integer }
        Pendente: { type: string }
        NumeroCNS: { type: string }

    ContatosGrid:
      type: object
      description: Linha do grid/listagem de contatos.
      properties:
        total: { type: integer }
        Pendente: { type: string }
        Id_da_Assinatura: { type: integer, nullable: true }
        Id_do_Cliente: { type: integer }
        Nome_Social: { type: string }
        Nome: { type: string }
        Telefone_Residencial: { type: string }
        Telefone_Residencial_1: { type: string }
        Celular: { type: string }
        Email: { type: string }
        Nascimento: { type: string, format: date-time, nullable: true }
        CPF_CGC: { type: string }
        IddoConvenio: { type: integer, nullable: true }
        Convenio: { type: string }

    EnderecoCEP:
      type: object
      properties:
        UF: { type: string }
        Cidade: { type: string }
        Bairro: { type: string }
        Endereco: { type: string }

    RequestBuscarVinculoDadosObrigatorios:
      type: object
      properties:
        Name: { type: string, nullable: true, description: Nome completo do paciente. }
        Email: { type: string, nullable: true, description: Critério fraco de match. }
        DateOfBirth: { type: string, nullable: true, description: 'Data de nascimento (ex.: 1990-05-20).' }
        Gender: { type: string, nullable: true, description: '"F" ou "M".' }
        Phone: { type: string, nullable: true, description: Telefone/celular com DDD (critério principal de match). }

    VinculoPacienteResult:
      type: object
      properties:
        Id: { type: string, nullable: true, description: Id do cliente encontrado/criado. }
        Match: { type: boolean, description: 'true = match confiável (telefone) ou paciente criado; false = match fraco (email/nome).' }

    FaturaGeral:
      type: object
      description: Fatura/atendimento com procedimentos e pagamentos.
      properties:
        Acao: { type: string, description: '"insert"/"update".' }
        Iddoatendimento: { type: integer }
        Iddousuario: { type: integer, nullable: true }
        Iddocliente: { type: integer }
        NomeCliente: { type: string }
        Data: { type: string, format: date-time }
        IddoConvenio: { type: integer, nullable: true }
        Tabela: { type: string }
        NumerodaGuia: { type: string }
        Observacoes: { type: string }
        Fechado: { type: integer, nullable: true }
        Desconto: { type: number, nullable: true }
        IddaUnidadedeNegocios: { type: integer, nullable: true }
        Valor_Fatura: { type: number, nullable: true }
        Procedimentos:
          type: array
          items: { $ref: '#/components/schemas/ItensdoatendimentoFatura' }
        Pagamentos:
          type: array
          items: { $ref: '#/components/schemas/PagamentosdoatendimentoFatura' }

    ItensdoatendimentoFatura:
      type: object
      properties:
        Acao: { type: string }
        Iddoprocedimento: { type: integer }
        Procedimento: { type: string }
        Qtd: { type: number }
        ValorUnitario: { type: number }
        Sessoes: { type: integer, nullable: true }

    PagamentosdoatendimentoFatura:
      type: object
      properties:
        Acao: { type: string }
        IddaConta: { type: integer }
        Tipo: { type: string }
        Descricao: { type: string }
        Valor: { type: number, nullable: true }
        ValorPago: { type: number, nullable: true }
        TaxaAdm: { type: number, nullable: true }
        Vencimento: { type: string, format: date-time }
        NumerodeControle: { type: string }
        Data_Pgto: { type: string, format: date-time, nullable: true }

    Lancamentos:
      type: object
      properties:
        IddaAssinatura: { type: integer, nullable: true }
        Iddolancamento: { type: integer }
        DatadeVencimento: { type: string, format: date-time }
        DatadePagamento: { type: string, format: date-time, nullable: true }
        Descricao: { type: string }
        Numerododocumento: { type: string }
        Observacao: { type: string }
        ValorOriginal: { type: number, nullable: true }
        ValorPago: { type: number, nullable: true }
        Iddosubgrupo: { type: integer }
        Iddaconta: { type: integer }
        IddaUnidadedeNegocios: { type: integer, nullable: true }
        NumerodeControle: { type: string }
        IddoContato: { type: integer, nullable: true }
        repetir: { type: integer, nullable: true }

    Atendimentos:
      type: object
      properties:
        IddaAssinatura: { type: string }
        Iddoatendimento: { type: integer }
        Iddousuario: { type: integer }
        Iddocliente: { type: integer }
        Iddoitemdoatendimento: { type: integer }
        Sessoes: { type: integer, nullable: true }
        NomeCliente: { type: string }
        Data: { type: string, format: date-time }
        IddoConvenio: { type: integer, nullable: true }
        Tabela: { type: string }
        NumerodaGuia: { type: string }
        Observacoes: { type: string }
        Fechado: { type: integer, nullable: true }
        Desconto: { type: number, nullable: true }
        IddaUnidadedeNegocios: { type: integer, nullable: true }
        Filial: { type: string }
        NF: { type: string }
        Recibo: { type: string }
        Orcamento: { type: boolean, nullable: true }
        Valor_Fatura: { type: number, nullable: true }

    UN:
      type: object
      properties:
        IddaUnidadedeNegocios: { type: integer, format: int64 }
        UnidadedeNegocios: { type: string }
        CPFCNPJ: { type: string }
        CNES: { type: string }
        NomeRazaoSocial: { type: string }
        Logradouro: { type: string }
        Numero: { type: string }
        Complemento: { type: string }
        Municipio: { type: string }
        UF: { type: string }
        CEP: { type: string }

    Convenios:
      type: object
      properties:
        IddoConvenio: { type: integer }
        Convenio: { type: string }
        Observacoes: { type: string }
        Codigo: { type: string }
        CodigodaTabelaTISS: { type: string }
        Carencia: { type: integer }
        idClinica: { type: integer }
        cnpj: { type: string }
        lote: { type: string }
        guia: { type: string }

    Usuario:
      type: object
      description: Profissional/usuário. Campos sensíveis (senha) não são expostos nesta API.
      properties:
        IdDaAssinatura: { type: integer, nullable: true }
        IdDoUsuario: { type: integer }
        UsuarioNome: { type: string }
        NomeCompleto: { type: string, nullable: true }
        IdDoSetor: { type: integer }
        Ativo: { type: boolean, nullable: true }
        Agenda: { type: boolean, nullable: true }
        IdDaUnidadeDeNegocios: { type: integer, nullable: true }
        ConselhoProfissional: { type: string, nullable: true }
        NumeroDoConselho: { type: string, nullable: true }
        UfConselho: { type: string, nullable: true }
        Endereco: { type: string, nullable: true }
        Telefone: { type: string, nullable: true }
        CPF: { type: string, nullable: true }
        Email: { type: string, nullable: true }
        LinkAgendamentoOnline: { type: string, nullable: true }

    Procedimentos:
      type: object
      properties:
        IddoProcedimento: { type: integer }
        Procedimento: { type: string }
        Custo: { type: number, nullable: true }
        UltimaAtualizacao: { type: string, format: date-time, nullable: true }
        PrecoBase: { type: number, nullable: true }
        Produto: { type: boolean }
        QuantidadeMinima: { type: number, nullable: true }
        Saldo: { type: number, nullable: true }
        Sessoes: { type: integer, nullable: true }
        Comissao: { type: number, nullable: true }
        CodigodoProcedimento: { type: string }
        Ocultar: { type: boolean }

    FormasdePagamento:
      type: object
      description: Forma de pagamento (modelo interno FormasdePagamento1).
      properties:
        Id: { type: integer }
        FormadePagamento: { type: string }
        TaxaAdministrativa: { type: number, nullable: true }
        Parcelas: { type: integer, nullable: true }
        IddaConta: { type: integer, nullable: true }
        Primeiraparcelaem: { type: integer, nullable: true }
        Obrigatorionumero: { type: integer, nullable: true }
        ContaTxt: { type: string }
        ObrigatorioTxt: { type: string }

    ContasCorrentes:
      type: object
      properties:
        IddaAssinatura: { type: integer, nullable: true }
        IddaConta: { type: integer }
        Conta: { type: string }
        Tipo: { type: string }
        Encargos: { type: number, nullable: true }

    Subgrupos:
      type: object
      properties:
        IddaAssinatura: { type: integer, nullable: true }
        Iddosubgrupo: { type: integer }
        Iddogrupo: { type: integer }
        Subgrupo: { type: string }
