{
  "openapi": "3.1.0",
  "info": {
    "title": "API do AgendAI (leitura)",
    "version": "1.0.0",
    "description": "Documentação pública, só de leitura, da API do AgendAI: horas dos\nconsultores, comercial (oportunidades, metas, comissões, dashboard) e\ninadimplência. Para agentes, a versão em texto está em `/llms.txt`\n(índice) e `/llms-full.txt` (tudo num arquivo).\n"
  },
  "servers": [
    {
      "url": "https://api-prod.agendai.pro",
      "description": "Produção"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Autenticação",
      "description": "Login com e-mail e senha para obter o JWT usado em todas as outras chamadas."
    },
    {
      "name": "Horas dos consultores",
      "description": "Horas realizadas pelos consultores (BI Produtividade)."
    },
    {
      "name": "Inadimplência",
      "description": "Títulos vencidos e em aberto dos clientes, a partir dos recebíveis do Omie."
    },
    {
      "name": "Oportunidades",
      "description": "Funil comercial (CRM) — oportunidades, etapas, histórico."
    },
    {
      "name": "Metas",
      "description": "Metas comerciais e o realizado contra elas."
    },
    {
      "name": "Comissões",
      "description": "Comissões de venda por vendedor e seu ciclo de liberação."
    },
    {
      "name": "Dashboard e cancelamentos",
      "description": "Números consolidados do mês e da semana e índice de cancelamento."
    }
  ],
  "paths": {
    "/login": {
      "post": {
        "tags": [
          "Autenticação"
        ],
        "operationId": "login",
        "summary": "Obter o token (JWT)",
        "security": [],
        "description": "Troca e-mail e senha por um JWT. Use o token em todas as outras\nchamadas no cabeçalho `Authorization: Bearer <token>`.\n\n- Responde **201** (não 200) com `{ \"token\": \"...\" }`.\n- O e-mail é comparado sem diferenciar maiúsculas.\n- O token vale **7 dias** e não há refresh: quando qualquer chamada\n  responder 401, faça login de novo e repita a chamada.\n- Guarde o token e reutilize-o; não faça login a cada requisição.\n- Usuário inativo (ou com convite pendente) recebe 401 com mensagem\n  própria. Credenciais erradas recebem 401 `Invalid credentials.`\n- Desativar o usuário no AgendAI invalida os tokens já emitidos na\n  próxima chamada.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "password"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "email": "integracao@exemplo.com.br",
                "password": "sua-senha"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Token emitido.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "token"
                  ],
                  "properties": {
                    "token": {
                      "type": "string",
                      "description": "JWT HS256, validade de 7 dias."
                    }
                  }
                },
                "example": {
                  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIuLi4ifQ.assinatura"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "description": "Credenciais inválidas, e-mail ambíguo ou usuário inativo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Erro"
                },
                "example": {
                  "status": 401,
                  "path": "/login",
                  "message": "Invalid credentials."
                }
              }
            }
          }
        }
      }
    },
    "/bi-produtividade/por-consultor": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarHorasPorConsultorPorMes",
        "summary": "Horas realizadas por pessoa e por mês, com meta individual",
        "description": "Responde: \"quantas horas cada pessoa realizou em cada mês do período e qual era a meta dela?\". Devolve **uma linha por (mês, pessoa)**, ordenada pela data da primeira agenda da pessoa no mês (crescente; os meses ficam em ordem cronológica), depois nome e id da pessoa.\n\n**Cálculo.** Soma `duracao` (minutos) das agendas REALIZADAS agrupadas por mês da data de referência e por pessoa (responsável da agenda; se a agenda foi reservada num consultor sem usuário, o próprio consultor). A meta individual é `metaTotalHoraMes x 60 x fator` minutos, onde `metaTotalHoraMes` vem da tabela de horas úteis da organização (horas por pessoa no mês) e o fator é 1 para pessoa ativa, no mês da desativação a fração = horas úteis do dia 1 até a desativação ÷ horas úteis do mês (1 nos meses anteriores, 0 nos posteriores e para inativo sem data de desativação) e 0 para quem está fora da base da meta (nesse caso `minutosMetaConsultor` é nulo, mas as horas realizadas continuam aparecendo). Sem filtro de pessoa, só conta na meta quem está hoje no setor Consultoria; com `usuarioIds`/`setor`, as pessoas filtradas (em ambos os casos restritas pela `situacao`). Só com o filtro `consultor` (nome), toda linha recebe fator 1 (meta cheia) e `quantidadeConsultores` = número de nomes distintos.\n\n**Diferença para as vizinhas:** esta é a única rota por pessoa **x mês** com **meta**. `horas-realizadas-por-consultor` dá só o total do período por pessoa (sem meta); `horas-por-mes` dá o total do mês sem separar pessoas; `grafico-mes-atual` dá só o mês corrente.\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasPorConsultorMesItem"
                  }
                },
                "example": [
                  {
                    "ano": 2026,
                    "mesNumero": 3,
                    "usuarioId": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                    "consultor": "Consultor Exemplo",
                    "cor": "#3B82F6",
                    "mes": "Mar. de 2026",
                    "minutosRealizados": 7200,
                    "horas": "120:00",
                    "metaTotalHoraMes": 160,
                    "quantidadeConsultores": 10,
                    "minutosMetaConsultor": 9600,
                    "metaHorasConsultor": "160:00"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/horas-por-mes": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarHorasPorMes",
        "summary": "Horas realizadas por mês, com meta e produtividade da equipe",
        "description": "Responde: \"quantas horas a equipe (ou o recorte filtrado) realizou em cada mês e quanto isso representa da meta?\". Devolve **uma linha por mês**, em ordem cronológica; lista vazia se não há agenda REALIZADA no período.\n\n**Cálculo.** `minutosRealizados` = soma das durações REALIZADAS do mês. A meta do mês é `metaTotalHoraMes` (horas por pessoa, da tabela de horas úteis) x 60 x `consultoresNaMeta` (pessoas na base, com fração para desativados). `produtividade` = realizado / meta x 100 (percentual 0-100+, 2 casas). Sem filtro de pessoa a base é o setor Consultoria; com `usuarioIds`/`setor` são as pessoas filtradas; com `consultor` (nome), a base é o número de NOMES distintos informados (homônimos contam 1, sem fração de desativação) (`baseMeta` informa qual caso).\n\n**Diferença para as vizinhas:** agrega o mês inteiro, **sem separar pessoas**; é a rota para atingimento de meta da equipe. Para a mesma conta por pessoa use `por-consultor`; para tabela simples sem meta, `tabela-total-horas`.\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasPorMesItem"
                  }
                },
                "example": [
                  {
                    "ano": 2026,
                    "mesNumero": 3,
                    "mes": "Mar. de 2026",
                    "minutosRealizados": 9150,
                    "hoursRealizadas": "152:30",
                    "metaTotalHoraMes": 160,
                    "minutosMeta": 96000,
                    "metaHoras": "1600:00",
                    "produtividade": 9.53,
                    "consultoresNaMeta": 10,
                    "baseMeta": "SETOR_CONSULTORIA"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/horas-por-tipo-servico": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarHorasPorTipoServico",
        "summary": "Horas realizadas por ano e tipo de serviço",
        "description": "Responde: \"quantas horas foram realizadas em cada tipo de serviço (`tipoServico` da agenda)?\". Devolve uma linha por (ano, `tipoServico`), ordenada pelo total de minutos decrescente. `tipoServico` é nulo para agendas sem esse campo.\n\n**Cálculo.** Soma `duracao` (minutos) das agendas REALIZADAS no período e a formata em `HH:MM`. Atenção: `ano` volta como **texto**. A rota irmã `porcentagem-por-tipo-servico` conta AGENDAS, não horas.\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasTipoServicoItem"
                  }
                },
                "example": [
                  {
                    "ano": "2026",
                    "tipoServico": "Implantação",
                    "horas": "210:15"
                  },
                  {
                    "ano": "2026",
                    "tipoServico": null,
                    "horas": "12:00"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/porcentagem-por-tipo-servico": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarPorcentagemPorTipoServico",
        "summary": "Participação de cada tipo de serviço no número de agendas realizadas",
        "description": "Responde: \"qual a fatia (%) de cada tipo de serviço entre as agendas realizadas?\". Devolve uma linha por `tipoServico` da agenda (nulo se vazio), ordenada por porcentagem decrescente.\n\n**Cálculo.** `contagem` = número de agendas REALIZADAS do tipo; `porcentagem` = contagem / total x 100, 2 casas. **A unidade é QUANTIDADE DE AGENDAS, não horas** (para horas use `horas-por-tipo-servico`). `porcentagem` chega como **texto** (`\"66.67\"`). Diferente das demais rotas, o período filtra por `dataInicio` da agenda (não por `COALESCE(dataFim, dataInicio)`).\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasPorcentagemTipoServicoItem"
                  }
                },
                "example": [
                  {
                    "tipoServico": "Implantação",
                    "contagem": 2,
                    "porcentagem": "66.67"
                  },
                  {
                    "tipoServico": "Suporte",
                    "contagem": 1,
                    "porcentagem": "33.33"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/horas-por-tipo-agenda": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarHorasPorTipoAgenda",
        "summary": "Horas realizadas por pessoa e tipo de agenda",
        "description": "Responde: \"como cada pessoa dividiu suas horas realizadas entre tipos de agenda (CLIENTE, INTERNO, FÉRIAS, ESTUDO...)?\". Devolve uma linha por (pessoa, `tipoAgenda`), ordenada por nome, id da pessoa e minutos decrescentes. Agenda sem tipo conta como `CLIENTE`; pessoa sem nome aparece como `Sem consultor`.\n\n**Cálculo.** Soma `duracao` (minutos) das agendas REALIZADAS no período; devolve minutos (inteiro) e `HH:MM`. Como nas demais rotas de BI, não há filtro de tipo por padrão (todos os tipos entram); o filtro `tipoAgenda` restringe. Aqui o filtro `situacao` já se aplica sempre (a consulta sempre junta a pessoa).\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasPorTipoAgendaItem"
                  }
                },
                "example": [
                  {
                    "usuarioId": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                    "consultor": "Consultor Exemplo",
                    "tipoAgenda": "CLIENTE",
                    "minutosRealizados": 4800,
                    "horas": "80:00"
                  },
                  {
                    "usuarioId": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                    "consultor": "Consultor Exemplo",
                    "tipoAgenda": "INTERNO",
                    "minutosRealizados": 600,
                    "horas": "10:00"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/tabela-total-horas": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarTabelaTotalHoras",
        "summary": "Tabela simples de horas realizadas por mês (sem pessoa, sem meta)",
        "description": "Responde: \"quantas horas no total foram realizadas em cada mês?\". Devolve uma linha por (ano, mês), ordenada por **ano decrescente** e depois mês crescente (atenção: diferente de `horas-por-mes`, que é cronológico).\n\n**Cálculo.** Soma `duracao` das agendas REALIZADAS e devolve só `HH:MM`. **Sem minutos, sem meta, sem separar pessoas.** Tipos esquisitos herdados do SQL: `ano` e `mes_numero` são **strings** e `mes` é o nome do mês **em inglês** (`March`). Se precisar de meta ou de números, use `horas-por-mes`.\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasTabelaMesItem"
                  }
                },
                "example": [
                  {
                    "ano": "2026",
                    "mes": "March",
                    "mes_numero": "3",
                    "horas": "152:30"
                  },
                  {
                    "ano": "2026",
                    "mes": "February",
                    "mes_numero": "2",
                    "horas": "140:00"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/horas-realizadas-por-consultor": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "listarHorasRealizadasPorConsultor",
        "summary": "Total de horas realizadas por pessoa no período inteiro",
        "description": "Responde: \"qual o total de horas realizadas por cada pessoa no período?\" (ranking). Devolve **uma linha por pessoa**, ordenada por total decrescente (numérico, correto) e depois nome.\n\n**Cálculo.** Soma `duracao` das agendas REALIZADAS do período (padrão: ano civil atual) por pessoa e devolve só `HH:MM`. **Não separa por mês, não traz minutos nem meta** (para isso use `por-consultor`). Para só o mês corrente use `grafico-mes-atual`. Homônimos sem usuário vinculado aparecem em linhas separadas (`usuarioId` nulo).\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "dataInicio",
            "in": "query",
            "required": false,
            "description": "Início do período (inclusivo). Aceita qualquer texto que `Date.parse` leia; recomendado `AAAA-MM-DD` (vira `T00:00:00Z`). Valor ilegível é IGNORADO em silêncio (sem erro). Use `AAAA-MM-DD` com data válida; texto que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Compara com a data da agenda em hora de parede (ver description da rota).",
            "example": "2026-01-01",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dataFim",
            "in": "query",
            "required": false,
            "description": "Fim do período (inclusivo). Com `AAAA-MM-DD` vira `T23:59:59Z`. Valor ilegível é ignorado em silêncio; data que o JavaScript aceita mas o Postgres não (por exemplo `2026-02-30`) pode dar 500. Se NEM `dataInicio` NEM `dataFim` forem enviados, o período é o ANO CIVIL ATUAL do servidor (01/jan a 31/dez); se só um vier, o intervalo fica aberto do outro lado.",
            "example": "2026-12-31",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HorasConsultorTotalItem"
                  }
                },
                "example": [
                  {
                    "usuarioId": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                    "consultor": "Consultor Exemplo",
                    "horas": "620:30"
                  },
                  {
                    "usuarioId": "9a3c7e10-2b4d-4f68-a1c5-7e8d9f0a1b22",
                    "consultor": "Outra Consultora Exemplo",
                    "horas": "480:00"
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/bi-produtividade/grafico-mes-atual": {
      "get": {
        "tags": [
          "Horas dos consultores"
        ],
        "operationId": "obterGraficoHorasMesAtual",
        "summary": "Horas realizadas por pessoa no mês corrente",
        "description": "Responde: \"quanto cada pessoa já realizou NESTE mês?\". Devolve `{ mesAtual, consultores }`: `mesAtual` é uma lista de um item com o nome do mês corrente em português minúsculo; `consultores` tem uma linha por pessoa.\n\n**Cálculo.** Igual a `horas-realizadas-por-consultor`, mas o período é **sempre o mês corrente do servidor** (do dia 1 ao último dia). **`dataInicio` e `dataFim` são ignorados** mesmo se enviados. Só devolve `HH:MM`. Ordenação por `horas` decrescente **como texto** (um total de `100:00` fica abaixo de `99:00`), depois nome. Sem agenda realizada no mês, `consultores` vem vazio.\n\n**Qual rota de horas chamar (as quatro parecem iguais, mas respondem perguntas diferentes):**\n\n| Rota | Agrupa por | Devolve | Quando usar |\n|---|---|---|---|\n| `GET /bi-produtividade/horas-por-mes` | mês (total da organização/filtro) | minutos, `HH:MM`, **meta e produtividade** | evolução mensal e atingimento da meta |\n| `GET /bi-produtividade/por-consultor` | mês **x** pessoa | minutos, `HH:MM`, **meta individual**, cor | evolução mensal por pessoa com meta |\n| `GET /bi-produtividade/horas-realizadas-por-consultor` | pessoa (período inteiro) | só `HH:MM` (sem minutos, sem meta) | ranking/total de horas por pessoa no período |\n| `GET /bi-produtividade/grafico-mes-atual` | pessoa, **só o mês corrente** | só `HH:MM` | \"quanto cada pessoa já realizou NESTE mês\"; ignora `dataInicio`/`dataFim` |\n| `GET /bi-produtividade/tabela-total-horas` | mês (total, **sem pessoa**) | só `HH:MM`, mês em inglês | tabela simples de horas por mês, sem meta |\n| `GET /bi-produtividade/horas-por-tipo-agenda` | pessoa **x** tipo de agenda | minutos e `HH:MM` | em que tipo de agenda (cliente, interno, férias...) cada pessoa gastou o tempo |\n| `GET /bi-produtividade/horas-por-tipo-servico` | ano **x** tipo de serviço | só `HH:MM` | distribuição de horas por serviço |\n| `GET /bi-produtividade/porcentagem-por-tipo-servico` | tipo de serviço | contagem de agendas e % | participação (rosca) por serviço, em NÚMERO DE AGENDAS, não em horas |\n\n\n**Regras comuns a todas as rotas `/bi-produtividade/*` de horas:**\n\n- Conta **somente agendas com `status = REALIZADO`** da organização do token (isolamento por tenant). Agendas previstas, confirmadas, reservadas, pendentes ou canceladas ficam de fora. Agendas excluídas não entram.\n- A **duração** vem do campo `duracao` da agenda, em **MINUTOS inteiros**. As rotas que devolvem `horas` o fazem como **texto `HH:MM`** (horas totais, pode passar de 24, ex.: `\"152:30\"`), NUNCA como número decimal.\n- A **data de referência** de cada agenda é `COALESCE(dataFim, dataInicio)` (a data de término, ou o início se não houver fim), em **hora de parede gravada como UTC** (agenda digitada às 09:00 está armazenada como `T09:00:00Z`; não há conversão de fuso). Exceção: `porcentagem-por-tipo-servico` usa `dataInicio`.\n- Período padrão: sem `dataInicio` e sem `dataFim`, o **ano civil atual**.\n- **Permissão:** exige apenas token válido (JWT) e organização identificada. Não há `@Roles`, checagem de tela nem escopo de dados por usuário: qualquer usuário autenticado vê as horas de **todas** as pessoas da organização, sem filtro por quem pergunta.\n- Os filtros são lidos direto da query string, sem schema Zod: parâmetros desconhecidos são ignorados. Só falham com 400: `situacao` inválida, `usuarioIds` com UUID inválido (ou mais de 200) e `setor` desconhecido.\n",
        "parameters": [
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Filtra por pessoa (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetindo o parâmetro (`?usuarioIds=a&usuarioIds=b`) ou separados por vírgula. Máximo de 200 ids. Só entram usuários da organização do token; UUID malformado dá 400. Com este filtro (ou `setor`), a base da meta (`horas-por-mes`) passa a ser as pessoas filtradas.",
            "example": [
              "c0a80101-0000-4000-8000-000000000001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Filtra pelo setor efetivo do responsável da agenda. Valores (sem diferenciar maiúsculas): CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS. Repetido ou separado por vírgula. Se vier junto com `usuarioIds`, vale a interseção; um setor sem ninguém devolve lista vazia. Valor desconhecido dá 400 (\"setor inválido.\").",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          },
          {
            "name": "consultor",
            "in": "query",
            "required": false,
            "description": "Filtro legado por NOME: casa o nome do consultor cadastrado OU o nome do usuário responsável (igualdade exata, diferencia maiúsculas). Aceita vários nomes (repetido ou separado por vírgula). Prefira `usuarioIds`.",
            "example": [
              "Consultor Exemplo"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "Situação atual da pessoa responsável: `ativos` (usuário com status diferente de false; sem usuário, o consultor ativo), `inativos` ou `todos`. Ausente ou vazio = `todos`. Outro valor dá 400.",
            "example": "ativos",
            "schema": {
              "type": "string",
              "enum": [
                "ativos",
                "inativos",
                "todos"
              ],
              "default": "todos"
            }
          },
          {
            "name": "tipoServico",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do tipo de projeto/serviço (`tipo_projeto.tipoProjeto`, via projeto da agenda), igualdade exata. Repetido ou separado por vírgula. Atenção: filtra o tipo de projeto, não a coluna `tipoServico` da agenda que as rotas `horas-por-tipo-servico` e `porcentagem-por-tipo-servico` devolvem.",
            "example": [
              "Implantação"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Filtra pelo NOME do produto do projeto da agenda (`produto.produto`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "Produto Alfa"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "documento",
            "in": "query",
            "required": false,
            "description": "Filtra pelo número do documento do registro financeiro do projeto (`registro_financeiro.documento`), igualdade exata. Repetido ou separado por vírgula.",
            "example": [
              "DOC-0001"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "tipoAgenda",
            "in": "query",
            "required": false,
            "description": "Filtra pelo tipo da agenda. `CLIENTE` também casa agendas sem tipo (nulo ou vazio). Sem este parâmetro NÃO há filtro de tipo (entram todos os tipos). Valor fora do enum provavelmente quebra a consulta (500; inferido, não validado).",
            "example": [
              "CLIENTE"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "INTERNO",
                  "CLIENTE",
                  "FERIAS",
                  "ESTUDO",
                  "PESSOAL",
                  "COMERCIAL",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sucesso.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HorasGraficoMesAtual"
                },
                "example": {
                  "mesAtual": [
                    "novembro"
                  ],
                  "consultores": [
                    {
                      "usuarioId": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                      "consultor": "Consultor Exemplo",
                      "horas": "64:30"
                    },
                    {
                      "usuarioId": "9a3c7e10-2b4d-4f68-a1c5-7e8d9f0a1b22",
                      "consultor": "Outra Consultora Exemplo",
                      "horas": "42:00"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/dashboard/resumo-mes": {
      "get": {
        "tags": [
          "Dashboard e cancelamentos"
        ],
        "operationId": "obterResumoMesDashboard",
        "summary": "Resumo do mês da Visão geral (minutos e contagens)",
        "description": "Responde: \"como está o mês: quanto foi realizado, quanto está confirmado, o que aguarda aprovação ou conferência?\". Devolve sete números do mês.\n\n**Cálculo.** Considera as agendas **não excluídas** cujo **`dataInicio`** cai no mês civil (hora de parede gravada como UTC: o mês vai de `AAAA-MM-01T00:00:00Z` inclusive ao dia 1 do mês seguinte exclusivo). Campos em **MINUTOS**: `minutosRealizados` (REALIZADO), `minutosConfirmadosARealizar` (CONFIRMADO), `minutosAgendados` (PRE_RESERVAR + RESERVADO + CONFIRMADO + REALIZADO). Campos em **QUANTIDADE de agendas**: `aguardandoAprovacao` (CONFIRMADO com `statusAprovacao = AGUARDANDO`), `conferenciasPendentes` (REALIZADO com `conferencia = false`), `totalAgendamentos` (todas menos as CANCELADAS; inclui PENDENTE e agendas sem status).\n\n**Permissão e escopo.** Exige token válido (JWT). Sem `@Roles`, mas **é a única rota deste grupo com escopo de dados**: o resultado considera só as agendas que o usuário pode ler (o mesmo escopo da lista de agendas): visão total vê tudo; com o acesso por setor ligado, a pessoa vê os ramos do seu setor (ex.: agendas CS, universo comercial, clientes de que é CS) mais as agendas em que é responsável ou convidada; perfil comercial restrito vê as agendas do seu vendedor mais as em que é responsável ou convidado; quem não tem escopo nenhum (e não é participante) recebe os números zerados (não é 403). Não aceita os filtros `usuarioIds`/`setor`.\n\nErros: `mesAno` ausente ou em formato inválido dá 400.",
        "parameters": [
          {
            "name": "mesAno",
            "in": "query",
            "required": true,
            "description": "Mês de referência. Formatos aceitos: `MM-AAAA` (ex.: `03-2026`, recomendado) ou `Mês AAAA` com o nome do mês em português, capitalizado e com acento (`Março 2026`). Também é lido de `mesAno[mesAno]`. O mês cobre as agendas cujo INÍCIO (`dataInicio`) cai nele.",
            "example": "03-2026",
            "schema": {
              "type": "string",
              "examples": [
                "03-2026",
                "Março 2026"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resumo do mês.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardResumoMes"
                },
                "example": {
                  "mesAno": "03-2026",
                  "minutosRealizados": 7440,
                  "minutosConfirmadosARealizar": 1800,
                  "aguardandoAprovacao": 6,
                  "conferenciasPendentes": 14,
                  "totalAgendamentos": 132,
                  "minutosAgendados": 12600
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/dashboard/cardMensal": {
      "get": {
        "tags": [
          "Dashboard e cancelamentos"
        ],
        "operationId": "obterCardMensalDashboard",
        "summary": "Ocupação do mês por status, em percentual e em horas",
        "description": "Responde: \"que fatia da capacidade do mês está confirmada, pré-reservada, reservada, realizada, cancelada ou livre?\".\n\n**Capacidade (base 100%).** `capacidadeHoras` = horas úteis do mês (calendário de dias úteis, 2023-2025 por tabela fixa, 2026 em diante pela regra de dias úteis; mês desconhecido = 160) x **número de pessoas distintas que têm ao menos uma agenda não excluída no mês** (qualquer status, só agendas com consultor). Ou seja, a capacidade depende de quem tem agenda, não do tamanho cadastrado da equipe.\n\n**Como o status é contado.** Soma as durações de TODAS as agendas não excluídas (`deleted_at` nulo) da organização cujo `dataInicio` cai no período, por status, em **horas** (minutos / 60). Uma agenda CANCELADA com `deduzir = true` conta como REALIZADA. Agendas PENDENTE e agendas sem status NÃO aparecem em nenhum campo da resposta e NÃO reduzem o `livre` (`livre` = capacidade menos as cinco categorias exibidas), mas contam para o número de pessoas da capacidade. `livre` nunca é negativo (sobreposição/overbooking é cortada em 0).\n\n**Unidades.** `porcentagem*`: percentual 0-100 como **TEXTO** com 2 casas (`\"12.50\"`); `horasPorStatus.*` e `capacidadeHoras`: **número em horas**, 2 casas. Os percentuais são horas / capacidade x 100 e podem passar de 100 individualmente, mas `porcentagemLivre` é cortado em 0.\n\n**Permissão:** exige token válido (JWT) e organização identificada. Não há `@Roles` nem checagem de tela. Os filtros `usuarioIds` e `setor` restringem às pessoas informadas (400 se malformados ou `setor` desconhecido). Nenhum escopo por usuário: qualquer usuário autenticado vê as horas de todas as pessoas da organização.\n\nErros: `mesAno` ausente ou inválido (ex.: `foo`, `13-2026`, nome de mês errado) responde **400**.",
        "parameters": [
          {
            "name": "mesAno",
            "in": "query",
            "required": true,
            "description": "Mês de referência. Formatos aceitos: `MM-AAAA` (ex.: `03-2026`, recomendado) ou `Mês AAAA` com o nome do mês em português, capitalizado e com acento (`Março 2026`). Também é lido de `mesAno[mesAno]`. O mês cobre as agendas cujo INÍCIO (`dataInicio`) cai nele.",
            "example": "03-2026",
            "schema": {
              "type": "string",
              "examples": [
                "03-2026",
                "Março 2026"
              ]
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Restringe às pessoas (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetidos ou separados por vírgula; máx. 200; só usuários da organização. UUID malformado dá 400.",
            "example": [
              "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Restringe ao setor efetivo do responsável: CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS (sem diferenciar maiúsculas). Com `usuarioIds`, vale a interseção. Desconhecido dá 400.",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ocupação do mês.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardCard"
                },
                "example": {
                  "porcentagemConfirmadas": "12.50",
                  "porcentagemPreReservadas": "3.10",
                  "porcentagemReservado": "8.00",
                  "porcentagemRealizadas": "40.25",
                  "porcentagemCanceladas": "5.00",
                  "porcentagemLivre": "31.15",
                  "horasPorStatus": {
                    "confirmadas": 20,
                    "preReservadas": 4.96,
                    "reservado": 12.8,
                    "realizadas": 64.4,
                    "canceladas": 8,
                    "livre": 49.84
                  },
                  "capacidadeHoras": 160
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/dashboard/cardSemanal": {
      "get": {
        "tags": [
          "Dashboard e cancelamentos"
        ],
        "operationId": "obterCardSemanalDashboard",
        "summary": "Ocupação da semana ISO por status, em percentual e em horas",
        "description": "Responde: o mesmo que `cardMensal`, mas para uma **semana ISO** (segunda 00:00 a domingo 23:59:59.999).\n\n**Capacidade (base 100%).** `capacidadeHoras` = horas úteis da semana (2023-2025 por tabela fixa, 2026 em diante pela regra de dias úteis; 40 h para semana fora das regras) x número de pessoas distintas com ao menos uma agenda não excluída na semana.\n\n**Como o status é contado.** Soma as durações de TODAS as agendas não excluídas (`deleted_at` nulo) da organização cujo `dataInicio` cai no período, por status, em **horas** (minutos / 60). Uma agenda CANCELADA com `deduzir = true` conta como REALIZADA. Agendas PENDENTE e agendas sem status NÃO aparecem em nenhum campo da resposta e NÃO reduzem o `livre` (`livre` = capacidade menos as cinco categorias exibidas), mas contam para o número de pessoas da capacidade. `livre` nunca é negativo (sobreposição/overbooking é cortada em 0).\n\n**Unidades.** `porcentagem*`: percentual 0-100 como **TEXTO** com 2 casas; `horasPorStatus.*` e `capacidadeHoras`: **número em horas**. `semanaAno` ecoa o texto recebido sem normalizar (`00-2027` volta `00-2027`); só com os literais `null`/`undefined` vem a semana de hoje no formato `\"S AAAA\"` (espaço, sem zero à esquerda, ex.: `\"7 2026\"`).\n\n**Permissão:** exige token válido (JWT) e organização identificada. Não há `@Roles` nem checagem de tela. Os filtros `usuarioIds` e `setor` restringem às pessoas informadas (400 se malformados ou `setor` desconhecido). Nenhum escopo por usuário: qualquer usuário autenticado vê as horas de todas as pessoas da organização.\n\nErros: `semanaAno` ausente ou vazio dá 400; formato inválido dá 400. Detalhe: os textos literais `null` e `undefined` passam pelo controller e o serviço os trata como \"semana de hoje\".",
        "parameters": [
          {
            "name": "semanaAno",
            "in": "query",
            "required": true,
            "description": "Semana ISO 8601 (segunda a domingo) no formato `SS-AAAA` (também `SS AAAA`), onde AAAA é o ANO ISO (29/12/2025 é a semana 01 de 2026) e SS vai de 0 a 53. `00-2027` é normalizado para a semana ISO real (53-2026). Ausente ou malformado dá 400. Também é lido de `semanaAno[semanaAno]`.",
            "example": "11-2026",
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,2}[- ]\\d{4}$"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Restringe às pessoas (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetidos ou separados por vírgula; máx. 200; só usuários da organização. UUID malformado dá 400.",
            "example": [
              "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Restringe ao setor efetivo do responsável: CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS (sem diferenciar maiúsculas). Com `usuarioIds`, vale a interseção. Desconhecido dá 400.",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ocupação da semana.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DashboardCard"
                    },
                    {
                      "type": "object",
                      "required": [
                        "semanaAno"
                      ],
                      "properties": {
                        "semanaAno": {
                          "type": "string",
                          "example": "11-2026"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "semanaAno": "11-2026",
                  "porcentagemConfirmadas": "12.50",
                  "porcentagemPreReservadas": "3.10",
                  "porcentagemReservado": "8.00",
                  "porcentagemRealizadas": "40.25",
                  "porcentagemCanceladas": "5.00",
                  "porcentagemLivre": "31.15",
                  "horasPorStatus": {
                    "confirmadas": 20,
                    "preReservadas": 4.96,
                    "reservado": 12.8,
                    "realizadas": 64.4,
                    "canceladas": 8,
                    "livre": 49.84
                  },
                  "capacidadeHoras": 160
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/dashboard/indice-cancelamento-mensal": {
      "get": {
        "tags": [
          "Dashboard e cancelamentos"
        ],
        "operationId": "obterIndiceCancelamentoMensal",
        "summary": "Horas canceladas e horas agendadas por pessoa no mês",
        "description": "Responde: \"quanto cada pessoa cancelou em um mês e quanto agendou no total?\". É a matéria-prima do índice de cancelamento: **a rota NÃO calcula o percentual**; quem consome divide `Cancelados` por `Total Agendamentos` (ou por Cancelados + Total, conforme a definição de negócio).\n\n**Formato.** `resultadoFinal` tem sempre duas séries (`Cancelados` e `Total Agendamentos`). Cada série traz listas paralelas por pessoa (`ordem` = nome, `usuarioIds`, `data` = horas sem arredondar, `horas` = horas com 2 casas). A mesma pessoa ocupa a mesma posição nas duas séries. Pessoas sem nenhuma agenda no período não aparecem.\n\n**Cálculo (unidade: HORAS decimais).** Considera agendas não excluídas da organização cujo `dataInicio` cai no período e que tenham consultor. `Cancelados` = horas das agendas com status CANCELADO **exceto** as canceladas com `deduzir = true` (essas contam como REALIZADO). `Total Agendamentos` = horas de TODAS as agendas **não canceladas** (PRE_RESERVAR, RESERVADO, CONFIRMADO, REALIZADO, PENDENTE, sem status e canceladas com `deduzir`), ou seja, NÃO inclui as canceladas. Agrupa por pessoa: o responsável da agenda (reserva no usuário do consultor, depois no consultor).\n\n**Permissão:** exige token válido (JWT) e organização identificada. Não há `@Roles` nem checagem de tela. Os filtros `usuarioIds` e `setor` restringem às pessoas informadas (400 se malformados ou `setor` desconhecido). Nenhum escopo por usuário: qualquer usuário autenticado vê as horas de todas as pessoas da organização.\n\nErros: `mesAno` ausente ou com formato inválido responde **400**.",
        "parameters": [
          {
            "name": "mesAno",
            "in": "query",
            "required": true,
            "description": "Mês de referência. Formatos aceitos: `MM-AAAA` (ex.: `03-2026`, recomendado) ou `Mês AAAA` com o nome do mês em português, capitalizado e com acento (`Março 2026`). Só é lido de `mesAno` (a forma `mesAno[mesAno]` NÃO é aceita nesta rota: sem `mesAno`, responde 400). O mês cobre as agendas cujo INÍCIO (`dataInicio`) cai nele.",
            "example": "03-2026",
            "schema": {
              "type": "string",
              "examples": [
                "03-2026",
                "Março 2026"
              ]
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Restringe às pessoas (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetidos ou separados por vírgula; máx. 200; só usuários da organização. UUID malformado dá 400.",
            "example": [
              "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Restringe ao setor efetivo do responsável: CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS (sem diferenciar maiúsculas). Com `usuarioIds`, vale a interseção. Desconhecido dá 400.",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Séries do índice de cancelamento do mês.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardIndiceCancelamento"
                },
                "example": {
                  "resultadoFinal": [
                    {
                      "ordem": [
                        "Consultor Exemplo",
                        "Outra Consultora Exemplo"
                      ],
                      "usuarioIds": [
                        "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                        "9a3c7e10-2b4d-4f68-a1c5-7e8d9f0a1b22"
                      ],
                      "name": "Cancelados",
                      "data": [
                        4,
                        1.5
                      ],
                      "horas": [
                        4,
                        1.5
                      ]
                    },
                    {
                      "ordem": [
                        "Consultor Exemplo",
                        "Outra Consultora Exemplo"
                      ],
                      "usuarioIds": [
                        "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
                        "9a3c7e10-2b4d-4f68-a1c5-7e8d9f0a1b22"
                      ],
                      "name": "Total Agendamentos",
                      "data": [
                        120.5,
                        98.33333333333333
                      ],
                      "horas": [
                        120.5,
                        98.33
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/dashboard/indice-cancelamento-semanal": {
      "get": {
        "tags": [
          "Dashboard e cancelamentos"
        ],
        "operationId": "obterIndiceCancelamentoSemanal",
        "summary": "Horas canceladas e horas agendadas por pessoa na semana ISO",
        "description": "Responde: \"quanto cada pessoa cancelou em uma semana ISO e quanto agendou no total?\". É a matéria-prima do índice de cancelamento: **a rota NÃO calcula o percentual**; quem consome divide `Cancelados` por `Total Agendamentos` (ou por Cancelados + Total, conforme a definição de negócio).\n\n**Formato.** `resultadoFinal` tem sempre duas séries (`Cancelados` e `Total Agendamentos`). Cada série traz listas paralelas por pessoa (`ordem` = nome, `usuarioIds`, `data` = horas sem arredondar, `horas` = horas com 2 casas). A mesma pessoa ocupa a mesma posição nas duas séries. Pessoas sem nenhuma agenda no período não aparecem.\n\n**Cálculo (unidade: HORAS decimais).** Considera agendas não excluídas da organização cujo `dataInicio` cai no período e que tenham consultor. `Cancelados` = horas das agendas com status CANCELADO **exceto** as canceladas com `deduzir = true` (essas contam como REALIZADO). `Total Agendamentos` = horas de TODAS as agendas **não canceladas** (PRE_RESERVAR, RESERVADO, CONFIRMADO, REALIZADO, PENDENTE, sem status e canceladas com `deduzir`), ou seja, NÃO inclui as canceladas. Agrupa por pessoa: o responsável da agenda (reserva no usuário do consultor, depois no consultor).\n\n**Permissão:** exige token válido (JWT) e organização identificada. Não há `@Roles` nem checagem de tela. Os filtros `usuarioIds` e `setor` restringem às pessoas informadas (400 se malformados ou `setor` desconhecido). Nenhum escopo por usuário: qualquer usuário autenticado vê as horas de todas as pessoas da organização.\n\nErros: `semanaAno` ausente ou malformado dá 400.",
        "parameters": [
          {
            "name": "semanaAno",
            "in": "query",
            "required": true,
            "description": "Semana ISO 8601 (segunda a domingo) no formato `SS-AAAA` (também `SS AAAA`), onde AAAA é o ANO ISO (29/12/2025 é a semana 01 de 2026) e SS vai de 0 a 53. `00-2027` é normalizado para a semana ISO real (53-2026). Ausente ou malformado dá 400.",
            "example": "11-2026",
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,2}[- ]\\d{4}$"
            }
          },
          {
            "name": "usuarioIds",
            "in": "query",
            "required": false,
            "description": "Restringe às pessoas (usuário responsável da agenda). UUIDs (maiúsculas são aceitas e convertidas), repetidos ou separados por vírgula; máx. 200; só usuários da organização. UUID malformado dá 400.",
            "example": [
              "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          {
            "name": "setor",
            "in": "query",
            "required": false,
            "description": "Restringe ao setor efetivo do responsável: CONSULTORIA, PROJETOS, COMERCIAL, FINANCEIRO, MIGRACAO, CS (sem diferenciar maiúsculas). Com `usuarioIds`, vale a interseção. Desconhecido dá 400.",
            "example": [
              "CONSULTORIA"
            ],
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "CONSULTORIA",
                  "PROJETOS",
                  "COMERCIAL",
                  "FINANCEIRO",
                  "MIGRACAO",
                  "CS"
                ]
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Séries do índice de cancelamento da semana.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardIndiceCancelamento"
                },
                "example": {
                  "resultadoFinal": [
                    {
                      "ordem": [
                        "Consultor Exemplo"
                      ],
                      "usuarioIds": [
                        "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
                      ],
                      "name": "Cancelados",
                      "data": [
                        2
                      ],
                      "horas": [
                        2
                      ]
                    },
                    {
                      "ordem": [
                        "Consultor Exemplo"
                      ],
                      "usuarioIds": [
                        "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
                      ],
                      "name": "Total Agendamentos",
                      "data": [
                        31.5
                      ],
                      "horas": [
                        31.5
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/financeiro/inadimplencia/resumo": {
      "get": {
        "tags": [
          "Inadimplência"
        ],
        "operationId": "obterResumoInadimplencia",
        "summary": "Resumo (BI) da inadimplência financeira",
        "description": "Devolve os indicadores do BI de inadimplência: cartões de totais e cinco agregações (por departamento, maiores devedores, evolução por mês de vencimento e histórico ano x mês). Todos os números respeitam os mesmos filtros e o mesmo cálculo de `/financeiro/inadimplencia/itens`; não há paginação.\n\nTodos os valores monetários são em **reais** (número JSON com 2 casas). A resposta vem embrulhada em `{ \"data\": ... }`.\n\nAtenção: no resumo da fonte canônica cada **parcela/título** é um item (`quantidadeItensAtrasados`), enquanto em `/itens` a paginação conta **linhas** agrupadas por OS. Os valores monetários totais batem entre as duas rotas; as contagens não.\n\n**Filtros em lista:** valores separados por vírgula ou parâmetro repetido; texto vazio e segmentos vazios (`a,,b`) são ignorados; o texto `null` **não** é ignorado (vira um valor literal no filtro de lista e, em datas, `incluirIgnoradas`, `page` e `pageSize`, responde 400).\n\n### Como a inadimplência é calculada\n\nA pergunta respondida é \"quanto a organização tem a receber, já vencido e não pago, de títulos faturados\".\nOs números saem da fila de **recebíveis do Omie** sincronizados no AgendAI (`omie_receivable`), não de um cálculo sobre a agenda.\nTudo é restrito à organização (tenant) do token.\n\n**Duas fontes de dados (a organização cai em uma delas):**\n\n- **Canônica** (a fonte atual): usada quando a organização tem ao menos um cadastro Omie ativo e **todos** os cadastros ativos terminaram a carga inicial de recebíveis (fase `READY`). Cada título/parcela do Omie é um recebível.\n- **Legada**: usada quando não há cadastro Omie ativo, ou algum cadastro ainda está em carga (`BACKFILL_PENDING`) ou falhou (`BACKFILL_FAILED`). Nesse caso a base é a **ordem de serviço** (OS) em vez do título: OS com status `BOLETO_GERADO` ou `FATURADA`, valor total > 0, **uma única parcela**, vencimento anterior ao início do dia (UTC), de projeto não excluído, com `naoFaturarFinanceiro = false`, de tipo de projeto pós-pago elegível ao financeiro **ou sem tipo de projeto** e com cliente vinculado. O valor em atraso é o valor total da OS.\n- A resposta não diz qual fonte foi usada, exceto pelos itens: na fonte legada `origem = \"Legado\"`, `omieTenantAppId = null`, `rowKey = \"OS:<id>\"` e `detalhe.parcelas = []`.\n\n**O que entra (fonte canônica)** — um recebível só conta se **todas** as condições valem:\n\n1. O cadastro Omie dele (`omie_tenant_app`) está **ativo**. Cadastro Omie inativo (por exemplo empresa baixada) sai da inadimplência; reativar devolve os títulos.\n2. O estado é `OPEN` (em aberto) ou `PARTIALLY_PAID` (parcialmente pago) e o saldo em aberto (`open_amount`) é maior que zero. Títulos liquidados (`SETTLED`) e cancelados não entram.\n3. A **data de vencimento efetiva** é anterior ao início do dia de hoje em Brasília (00:00 BRT). A data efetiva é a mais tardia entre o vencimento (`dDtVenc`) e a previsão de recebimento (`dDtPrevisao`) do Omie: um título vencido cujo recebimento foi reprogramado para o futuro não conta. Os filtros `dataVencimento*` e o campo `vencimento` usam essa mesma data. Título com data efetiva hoje ainda não é inadimplente.\n4. O título tem **número de documento fiscal** preenchido (isto é, a nota foi emitida).\n5. O tipo de documento **não** é `REE`, `DAM` nem `99999` (sem diferenciar maiúsculas de minúsculas).\n6. Salvo `incluirIgnoradas=true`: a OS do título **não** tem regra de \"inadimplência ignorada\" ativa e o contrato (direto ou herdado da OS) também não. Essas regras são cadastradas no módulo de inadimplência (`inadimplencia_ignorada`, alvo `ORDEM_SERVICO` ou `CONTRATO`).\n7. Os filtros da consulta (empresa Omie, cliente, projeto, produto, tipo de projeto, departamento, categoria, tipo de documento, intervalo de vencimento).\n\nOverrides de status e regras de inadimplência por cliente/projeto (módulo `inadimplencia-status`/`inadimplencia-regras`) **não** influenciam estas três rotas; só as regras de \"ignorada\" acima entram no cálculo.\n\n**Valores:** reais (BRL), números JSON com 2 casas (não centavos, não string).\n**Datas:** `vencimento` e demais datas de calendário são `YYYY-MM-DD`.\n**Dias de atraso:** dias inteiros entre o vencimento (o mais antigo, no caso de linha com várias parcelas) e hoje; 0 se não vencido.\n\n**Permissão:** capacidade `VER_BI_INADIMPLENCIA`. Sem ela a resposta é 403. Em geral: administrador global, administrador do tipo Financeiro, ou usuário com a tela `/financeiro/inadimplencia` liberada nas permissões de tela (lida do banco a cada requisição, então revogação vale na hora). Com a regra de acesso por setor ligada na organização, vale somente a tela efetiva do usuário. Não há filtro por registros: quem passa vê todos os dados da organização.\n",
        "parameters": [
          {
            "name": "empresaOmie",
            "in": "query",
            "required": false,
            "description": "Ids dos cadastros Omie (`omie_tenant_app`) da organização. Aceita parâmetro repetido (`?empresaOmie=1&empresaOmie=2`) ou lista separada por vírgula (`empresaOmie=1,2`); valores duplicados são descartados. Só tem efeito na fonte canônica; na fonte legada qualquer valor aqui zera o resultado. Os ids válidos vêm de `filter-options/empresaOmie`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer",
                "minimum": 1
              }
            },
            "example": [
              1
            ]
          },
          {
            "name": "cliente",
            "in": "query",
            "required": false,
            "description": "Clientes. Fonte canônica: ids (UUID) do cliente ou o texto exato do campo `cliente` dos itens (nome fantasia; senão a empresa do projeto; senão o nome no Omie; ou `Sem cliente vinculado`); os dois formatos podem ser misturados. Fonte legada: somente o nome fantasia. Mesmo formato de lista de `empresaOmie` (repetido ou separado por vírgula; por isso um nome com vírgula não pode ser filtrado). Na fonte canônica use o `id` de `filter-options/cliente`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11"
            ]
          },
          {
            "name": "projeto",
            "in": "query",
            "required": false,
            "description": "Projetos. Fonte canônica: ids (UUID) ou nome do projeto; fonte legada: nome. Mesmo formato de lista. Na fonte canônica use o `id` de `filter-options/projeto`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "Implantação Alfa"
            ]
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Produtos (id do produto). Fonte canônica: casa o título pela categoria Omie mapeada ao produto (`omie_tenant_billing_mapping` ativo). Na fonte legada qualquer valor aqui zera o resultado. Ids em `filter-options/produto`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69"
            ]
          },
          {
            "name": "tipoProjeto",
            "in": "query",
            "required": false,
            "description": "Tipos de projeto. Fonte canônica: id do tipo de projeto; fonte legada: nome do tipo (`Sem tipo de projeto` seleciona os projetos sem tipo). Na fonte canônica use o `id` de `filter-options/tipoProjeto`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "a1b2c3d4-0000-4000-8000-000000000001"
            ]
          },
          {
            "name": "departamento",
            "in": "query",
            "required": false,
            "description": "Departamentos. Fonte canônica: código do departamento Omie (`SEM_DEPARTAMENTO` para títulos sem roteamento de departamento); fonte legada: nome/descrição do departamento (`Sem departamento` quando vazio). Na fonte canônica use o `id` de `filter-options/departamento`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "SEM_DEPARTAMENTO"
            ]
          },
          {
            "name": "categoria",
            "in": "query",
            "required": false,
            "description": "Códigos de categoria Omie do título (`category_code`). Só tem efeito na fonte canônica; na legada qualquer valor zera o resultado. Códigos em `filter-options/categoria`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "1.01.02"
            ]
          },
          {
            "name": "tipoDocumento",
            "in": "query",
            "required": false,
            "description": "Tipos de documento Omie do título (`BOL`, `REC`, `TED`...). Só tem efeito na fonte canônica; na legada qualquer valor zera o resultado. Códigos em `filter-options/tipoDocumento`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "BOL"
            ]
          },
          {
            "name": "dataVencimentoInicio",
            "in": "query",
            "required": false,
            "description": "Vencimento mínimo, inclusivo, no formato `YYYY-MM-DD` (data de calendário válida). Fonte canônica: o dia começa à 00:00 de Brasília (03:00Z). Fonte legada: dia em UTC. Texto vazio é ignorado.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-01-01"
          },
          {
            "name": "dataVencimentoFim",
            "in": "query",
            "required": false,
            "description": "Vencimento máximo, inclusivo, `YYYY-MM-DD`. Deve ser maior ou igual a `dataVencimentoInicio` (senão 400). Como só entram parcelas vencidas antes de hoje, valores futuros não ampliam o resultado.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-10-31"
          },
          {
            "name": "incluirIgnoradas",
            "in": "query",
            "required": false,
            "description": "Aceita `true` ou `false`. Padrão (omitido ou `false`): ordens de serviço e contratos com regra de \"ignorada\" ativa ficam fora. Com `true` eles entram nos números.",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "example": false
          }
        ],
        "responses": {
          "200": {
            "description": "Resumo calculado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InadimplenciaResumoResponse"
                },
                "example": {
                  "data": {
                    "cards": {
                      "quantidadeItensAtrasados": 7,
                      "valorTotalEmAtraso": 31250.5,
                      "maiorValorEmAtraso": 12000,
                      "clientesInadimplentes": 4
                    },
                    "graficos": {
                      "porDepartamento": [
                        {
                          "departamento": "Serviços",
                          "quantidadeItensAtrasados": 5,
                          "valorTotalEmAtraso": 26000.5
                        },
                        {
                          "departamento": "Sem departamento",
                          "quantidadeItensAtrasados": 2,
                          "valorTotalEmAtraso": 5250
                        }
                      ],
                      "topDevedores": [
                        {
                          "clienteId": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                          "cliente": "Empresa Alfa Ltda",
                          "quantidadeItensAtrasados": 3,
                          "valorTotalEmAtraso": 16500
                        }
                      ],
                      "evolucaoPorMesVencimento": [
                        {
                          "mes": "2026-09",
                          "label": "09/2026",
                          "quantidadeItensAtrasados": 4,
                          "valorTotalEmAtraso": 20250.5
                        },
                        {
                          "mes": "2026-10",
                          "label": "10/2026",
                          "quantidadeItensAtrasados": 3,
                          "valorTotalEmAtraso": 11000
                        }
                      ],
                      "matrizHistoricaAnoMes": {
                        "meses": [
                          {
                            "mes": 1,
                            "key": "01",
                            "label": "Jan"
                          },
                          {
                            "mes": 2,
                            "key": "02",
                            "label": "Fev"
                          },
                          {
                            "mes": 3,
                            "key": "03",
                            "label": "Mar"
                          },
                          {
                            "mes": 4,
                            "key": "04",
                            "label": "Abr"
                          },
                          {
                            "mes": 5,
                            "key": "05",
                            "label": "Mai"
                          },
                          {
                            "mes": 6,
                            "key": "06",
                            "label": "Jun"
                          },
                          {
                            "mes": 7,
                            "key": "07",
                            "label": "Jul"
                          },
                          {
                            "mes": 8,
                            "key": "08",
                            "label": "Ago"
                          },
                          {
                            "mes": 9,
                            "key": "09",
                            "label": "Set"
                          },
                          {
                            "mes": 10,
                            "key": "10",
                            "label": "Out"
                          },
                          {
                            "mes": 11,
                            "key": "11",
                            "label": "Nov"
                          },
                          {
                            "mes": 12,
                            "key": "12",
                            "label": "Dez"
                          }
                        ],
                        "anos": [
                          {
                            "ano": 2026,
                            "totalAno": 31250.5,
                            "valoresPorMes": {
                              "10": 11000,
                              "11": 0,
                              "12": 0,
                              "01": 0,
                              "02": 0,
                              "03": 0,
                              "04": 0,
                              "05": 0,
                              "06": 0,
                              "07": 0,
                              "08": 0,
                              "09": 20250.5
                            }
                          }
                        ]
                      },
                      "serieHistoricaAnoMes": [
                        {
                          "ano": 2026,
                          "totalAno": 31250.5,
                          "pontos": [
                            {
                              "mes": 9,
                              "key": "09",
                              "label": "Set",
                              "valorTotalEmAtraso": 20250.5
                            },
                            {
                              "mes": 10,
                              "key": "10",
                              "label": "Out",
                              "valorTotalEmAtraso": 11000
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/financeiro/inadimplencia/itens": {
      "get": {
        "tags": [
          "Inadimplência"
        ],
        "operationId": "listarItensInadimplencia",
        "summary": "Itens (linhas) em atraso do BI de inadimplência, paginados",
        "description": "Lista paginada das **linhas** inadimplentes. Na fonte canônica uma linha agrupa todas as parcelas em atraso da mesma OS (`OS:<id>`) ou, sem OS, um título (`TITLE:<app>:<cod>`); o valor da linha é a soma dos saldos e o vencimento é o mais antigo. Cada linha traz o detalhamento das parcelas, da OS, do contrato e do projeto.\n\nValores em **reais**; datas de calendário `YYYY-MM-DD`; resposta em `{ \"data\": { items, pagination } }`.\n\n**Ordenação** (`sortBy`/`sortOrder`, padrão `vencimento` ascendente, ou seja, o mais antigo primeiro). O desempate é sempre vencimento mais antigo e depois a chave da linha. `sortBy=diasAtraso` com `asc` devolve primeiro os com menos dias de atraso, nas duas fontes.\n\n**Filtros em lista:** valores separados por vírgula ou parâmetro repetido; texto vazio e segmentos vazios (`a,,b`) são ignorados; o texto `null` **não** é ignorado (vira um valor literal no filtro de lista e, em datas, `incluirIgnoradas`, `page` e `pageSize`, responde 400).\n\n### Como a inadimplência é calculada\n\nA pergunta respondida é \"quanto a organização tem a receber, já vencido e não pago, de títulos faturados\".\nOs números saem da fila de **recebíveis do Omie** sincronizados no AgendAI (`omie_receivable`), não de um cálculo sobre a agenda.\nTudo é restrito à organização (tenant) do token.\n\n**Duas fontes de dados (a organização cai em uma delas):**\n\n- **Canônica** (a fonte atual): usada quando a organização tem ao menos um cadastro Omie ativo e **todos** os cadastros ativos terminaram a carga inicial de recebíveis (fase `READY`). Cada título/parcela do Omie é um recebível.\n- **Legada**: usada quando não há cadastro Omie ativo, ou algum cadastro ainda está em carga (`BACKFILL_PENDING`) ou falhou (`BACKFILL_FAILED`). Nesse caso a base é a **ordem de serviço** (OS) em vez do título: OS com status `BOLETO_GERADO` ou `FATURADA`, valor total > 0, **uma única parcela**, vencimento anterior ao início do dia (UTC), de projeto não excluído, com `naoFaturarFinanceiro = false`, de tipo de projeto pós-pago elegível ao financeiro **ou sem tipo de projeto** e com cliente vinculado. O valor em atraso é o valor total da OS.\n- A resposta não diz qual fonte foi usada, exceto pelos itens: na fonte legada `origem = \"Legado\"`, `omieTenantAppId = null`, `rowKey = \"OS:<id>\"` e `detalhe.parcelas = []`.\n\n**O que entra (fonte canônica)** — um recebível só conta se **todas** as condições valem:\n\n1. O cadastro Omie dele (`omie_tenant_app`) está **ativo**. Cadastro Omie inativo (por exemplo empresa baixada) sai da inadimplência; reativar devolve os títulos.\n2. O estado é `OPEN` (em aberto) ou `PARTIALLY_PAID` (parcialmente pago) e o saldo em aberto (`open_amount`) é maior que zero. Títulos liquidados (`SETTLED`) e cancelados não entram.\n3. A **data de vencimento efetiva** é anterior ao início do dia de hoje em Brasília (00:00 BRT). A data efetiva é a mais tardia entre o vencimento (`dDtVenc`) e a previsão de recebimento (`dDtPrevisao`) do Omie: um título vencido cujo recebimento foi reprogramado para o futuro não conta. Os filtros `dataVencimento*` e o campo `vencimento` usam essa mesma data. Título com data efetiva hoje ainda não é inadimplente.\n4. O título tem **número de documento fiscal** preenchido (isto é, a nota foi emitida).\n5. O tipo de documento **não** é `REE`, `DAM` nem `99999` (sem diferenciar maiúsculas de minúsculas).\n6. Salvo `incluirIgnoradas=true`: a OS do título **não** tem regra de \"inadimplência ignorada\" ativa e o contrato (direto ou herdado da OS) também não. Essas regras são cadastradas no módulo de inadimplência (`inadimplencia_ignorada`, alvo `ORDEM_SERVICO` ou `CONTRATO`).\n7. Os filtros da consulta (empresa Omie, cliente, projeto, produto, tipo de projeto, departamento, categoria, tipo de documento, intervalo de vencimento).\n\nOverrides de status e regras de inadimplência por cliente/projeto (módulo `inadimplencia-status`/`inadimplencia-regras`) **não** influenciam estas três rotas; só as regras de \"ignorada\" acima entram no cálculo.\n\n**Valores:** reais (BRL), números JSON com 2 casas (não centavos, não string).\n**Datas:** `vencimento` e demais datas de calendário são `YYYY-MM-DD`.\n**Dias de atraso:** dias inteiros entre o vencimento (o mais antigo, no caso de linha com várias parcelas) e hoje; 0 se não vencido.\n\n**Permissão:** capacidade `VER_BI_INADIMPLENCIA`. Sem ela a resposta é 403. Em geral: administrador global, administrador do tipo Financeiro, ou usuário com a tela `/financeiro/inadimplencia` liberada nas permissões de tela (lida do banco a cada requisição, então revogação vale na hora). Com a regra de acesso por setor ligada na organização, vale somente a tela efetiva do usuário. Não há filtro por registros: quem passa vê todos os dados da organização.\n",
        "parameters": [
          {
            "name": "empresaOmie",
            "in": "query",
            "required": false,
            "description": "Ids dos cadastros Omie (`omie_tenant_app`) da organização. Aceita parâmetro repetido (`?empresaOmie=1&empresaOmie=2`) ou lista separada por vírgula (`empresaOmie=1,2`); valores duplicados são descartados. Só tem efeito na fonte canônica; na fonte legada qualquer valor aqui zera o resultado. Os ids válidos vêm de `filter-options/empresaOmie`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer",
                "minimum": 1
              }
            },
            "example": [
              1
            ]
          },
          {
            "name": "cliente",
            "in": "query",
            "required": false,
            "description": "Clientes. Fonte canônica: ids (UUID) do cliente ou o texto exato do campo `cliente` dos itens (nome fantasia; senão a empresa do projeto; senão o nome no Omie; ou `Sem cliente vinculado`); os dois formatos podem ser misturados. Fonte legada: somente o nome fantasia. Mesmo formato de lista de `empresaOmie` (repetido ou separado por vírgula; por isso um nome com vírgula não pode ser filtrado). Na fonte canônica use o `id` de `filter-options/cliente`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11"
            ]
          },
          {
            "name": "projeto",
            "in": "query",
            "required": false,
            "description": "Projetos. Fonte canônica: ids (UUID) ou nome do projeto; fonte legada: nome. Mesmo formato de lista. Na fonte canônica use o `id` de `filter-options/projeto`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "Implantação Alfa"
            ]
          },
          {
            "name": "produto",
            "in": "query",
            "required": false,
            "description": "Produtos (id do produto). Fonte canônica: casa o título pela categoria Omie mapeada ao produto (`omie_tenant_billing_mapping` ativo). Na fonte legada qualquer valor aqui zera o resultado. Ids em `filter-options/produto`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69"
            ]
          },
          {
            "name": "tipoProjeto",
            "in": "query",
            "required": false,
            "description": "Tipos de projeto. Fonte canônica: id do tipo de projeto; fonte legada: nome do tipo (`Sem tipo de projeto` seleciona os projetos sem tipo). Na fonte canônica use o `id` de `filter-options/tipoProjeto`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "a1b2c3d4-0000-4000-8000-000000000001"
            ]
          },
          {
            "name": "departamento",
            "in": "query",
            "required": false,
            "description": "Departamentos. Fonte canônica: código do departamento Omie (`SEM_DEPARTAMENTO` para títulos sem roteamento de departamento); fonte legada: nome/descrição do departamento (`Sem departamento` quando vazio). Na fonte canônica use o `id` de `filter-options/departamento`; na legada, o `label`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "SEM_DEPARTAMENTO"
            ]
          },
          {
            "name": "categoria",
            "in": "query",
            "required": false,
            "description": "Códigos de categoria Omie do título (`category_code`). Só tem efeito na fonte canônica; na legada qualquer valor zera o resultado. Códigos em `filter-options/categoria`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "1.01.02"
            ]
          },
          {
            "name": "tipoDocumento",
            "in": "query",
            "required": false,
            "description": "Tipos de documento Omie do título (`BOL`, `REC`, `TED`...). Só tem efeito na fonte canônica; na legada qualquer valor zera o resultado. Códigos em `filter-options/tipoDocumento`.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1
              }
            },
            "example": [
              "BOL"
            ]
          },
          {
            "name": "dataVencimentoInicio",
            "in": "query",
            "required": false,
            "description": "Vencimento mínimo, inclusivo, no formato `YYYY-MM-DD` (data de calendário válida). Fonte canônica: o dia começa à 00:00 de Brasília (03:00Z). Fonte legada: dia em UTC. Texto vazio é ignorado.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-01-01"
          },
          {
            "name": "dataVencimentoFim",
            "in": "query",
            "required": false,
            "description": "Vencimento máximo, inclusivo, `YYYY-MM-DD`. Deve ser maior ou igual a `dataVencimentoInicio` (senão 400). Como só entram parcelas vencidas antes de hoje, valores futuros não ampliam o resultado.",
            "schema": {
              "type": "string",
              "format": "date",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "example": "2026-10-31"
          },
          {
            "name": "incluirIgnoradas",
            "in": "query",
            "required": false,
            "description": "Aceita `true` ou `false`. Padrão (omitido ou `false`): ordens de serviço e contratos com regra de \"ignorada\" ativa ficam fora. Com `true` eles entram nos números.",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "example": false
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Página, base 1 (mínimo 1; 400 se menor). Texto vazio vale o padrão.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Linhas por página (1 a 100; 400 fora disso).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "example": 20
          },
          {
            "name": "sortBy",
            "in": "query",
            "required": false,
            "description": "Campo de ordenação. `ordemServico` ordena número de OS só de dígitos como número e o resto como texto; `contrato` pelo rótulo do contrato; textos ausentes vão ao fim nos dois sentidos.",
            "schema": {
              "type": "string",
              "enum": [
                "vencimento",
                "diasAtraso",
                "valorEmAtraso",
                "cliente",
                "projeto",
                "departamento",
                "ordemServico",
                "contrato",
                "produto",
                "tipoProjeto"
              ],
              "default": "vencimento"
            },
            "example": "vencimento"
          },
          {
            "name": "sortOrder",
            "in": "query",
            "required": false,
            "description": "Sentido da ordenação.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "example": "asc"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de itens em atraso.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InadimplenciaItensResponse"
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "rowKey": "OS:48213",
                        "omieTenantAppId": 3,
                        "origem": "Empresa Alfa Serviços",
                        "codTituloOmie": null,
                        "cpfCnpjCliente": "12345678000190",
                        "ordemServicoId": 48213,
                        "ordemServicoNumero": "000412",
                        "codOsOmie": "7001234567",
                        "contratoId": 912,
                        "contratoLabel": "CTR-0091",
                        "clienteId": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                        "cliente": "Empresa Alfa Ltda",
                        "projetoId": "7e3d1c20-9a54-4d3b-8f21-0c6b5a4d3e22",
                        "projeto": "Implantação Alfa",
                        "produtoId": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                        "produto": "Plataforma Exemplo",
                        "tipoProjetoId": "a1b2c3d4-0000-4000-8000-000000000001",
                        "tipoProjeto": "Implantação",
                        "departamento": "Serviços",
                        "tipoDocumento": "BOL",
                        "status": "OPEN",
                        "statusDescricao": "Recebível em aberto",
                        "vencimento": "2026-09-10",
                        "diasAtraso": 62,
                        "valorEmAtraso": 4500,
                        "historico": false,
                        "catalogoAusente": false,
                        "detalhe": {
                          "tipoDocumento": "BOL",
                          "parcelas": [
                            {
                              "codTituloOmie": "9988776655",
                              "numero": "1/1",
                              "vencimento": "2026-09-10",
                              "valorOriginal": 4500,
                              "valorEmAberto": 4500,
                              "estado": "OPEN",
                              "ultimaSincronizacao": "2026-11-11T09:30:00.000Z",
                              "compoeTotalFiltrado": true
                            }
                          ],
                          "ordemServico": {
                            "id": 48213,
                            "numero": "000412",
                            "codOsOmie": "7001234567",
                            "status": "OPEN",
                            "statusDescricao": "Recebível em aberto",
                            "vencimento": "2026-09-10",
                            "dataFaturamento": null,
                            "diasAtraso": 62,
                            "valorEmAtraso": 4500,
                            "quantidadeParcelas": 1,
                            "parcelas": [
                              {
                                "codTituloOmie": "9988776655",
                                "numero": "1/1",
                                "vencimento": "2026-09-10",
                                "valorOriginal": 4500,
                                "valorEmAberto": 4500,
                                "estado": "OPEN",
                                "ultimaSincronizacao": "2026-11-11T09:30:00.000Z",
                                "compoeTotalFiltrado": true
                              }
                            ]
                          },
                          "contrato": {
                            "existeVinculo": true,
                            "id": 912,
                            "numero": "CTR-0091",
                            "label": "CTR-0091",
                            "codCtrOmie": "6001122334",
                            "situacao": null,
                            "descStatus": null,
                            "vigenciaInicio": null,
                            "vigenciaFim": null,
                            "valorTotalMensal": null
                          },
                          "projeto": {
                            "id": "7e3d1c20-9a54-4d3b-8f21-0c6b5a4d3e22",
                            "nome": "Implantação Alfa",
                            "clienteId": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                            "cliente": "Empresa Alfa Ltda",
                            "produtoId": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                            "produto": "Plataforma Exemplo",
                            "tipoProjetoId": "a1b2c3d4-0000-4000-8000-000000000001",
                            "tipoProjeto": "Implantação",
                            "departamento": "Serviços"
                          }
                        }
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "pageSize": 20,
                      "total": 1,
                      "totalPages": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/financeiro/inadimplencia/filter-options/{dimension}": {
      "get": {
        "tags": [
          "Inadimplência"
        ],
        "operationId": "listarOpcoesFiltroInadimplencia",
        "summary": "Catálogo paginado de opções para um filtro da inadimplência",
        "description": "Devolve as opções (id + rótulo) que alimentam um filtro das rotas `/resumo` e `/itens`. O catálogo é da organização inteira (não só de quem está inadimplente hoje) e, na fonte canônica, `id` é o valor que os filtros esperam. Na fonte legada (itens com `origem = \"Legado\"`), `cliente`, `projeto`, `tipoProjeto` e `departamento` filtram por nome: envie o `label` em vez do `id`; `empresaOmie`, `produto`, `categoria` e `tipoDocumento` zeram o resultado na fonte legada.\n\n**Dimensões aceitas** (`dimension`): `empresaOmie`, `cliente`, `projeto`, `produto`, `tipoProjeto`, `departamento`, `categoria`, `tipoDocumento`. Outro valor responde 400.\n\nO que cada uma lista:\n- `empresaOmie`: cadastros Omie; id numérico em texto, rótulo = apelido (ou `Omie app <id>`).\n- `cliente`: empresas com relação CLIENTE, não excluídas e com nome fantasia; id UUID.\n- `projeto`: projetos com nome; id UUID.\n- `produto` e `tipoProjeto`: id UUID, rótulo = nome.\n- `departamento`: departamentos do roteamento Omie; id = código do departamento Omie. `SEM_DEPARTAMENTO` (títulos sem roteamento) pode ser enviado no filtro mesmo quando não aparece na lista; ele só é listado quando algum roteamento tem código vazio (e esse caso não casa no filtro).\n- `categoria`: códigos de categoria Omie existentes nos recebíveis; id = código, rótulo = \"código - descrição\" quando mapeada.\n- `tipoDocumento`: tipos de documento existentes nos recebíveis (exceto `REE`, `DAM`, `99999`); id = código, rótulo traduzido (ex.: `BOL - Boleto`).\n\nSó entram opções **ativas**; inativas ou excluídas só aparecem se o id vier em `selectedId` (para mostrar uma seleção que ficou obsoleta; `availability` diz o estado). Ordem: rótulo (sem diferenciar caixa) e depois id.\n\n**Paginação por cursor:** envie `limit` e, para a página seguinte, o `nextCursor` recebido em `cursor`. Há mais páginas quando `hasMore=true`. Cursor adulterado responde 400 (\"Cursor de catálogo inválido.\").\n\n**Busca:** `q` filtra por trecho do rótulo, sem diferenciar maiúsculas (os caracteres `%` e `_` valem como curingas do SQL `LIKE`).\n\n**Permissão:** a mesma das demais rotas de inadimplência (capacidade `VER_BI_INADIMPLENCIA`): administrador global, administrador Financeiro ou tela `/financeiro/inadimplencia` liberada; senão 403. Resposta em `{ \"data\": { items, hasMore, nextCursor } }`.",
        "parameters": [
          {
            "name": "dimension",
            "in": "path",
            "required": true,
            "description": "Dimensão do catálogo.",
            "schema": {
              "type": "string",
              "enum": [
                "empresaOmie",
                "cliente",
                "projeto",
                "produto",
                "tipoProjeto",
                "departamento",
                "categoria",
                "tipoDocumento"
              ]
            },
            "example": "cliente"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Busca por trecho do rótulo (1 a 120 caracteres após aparar espaços). Vazio é ignorado; só espaços responde 400.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "example": "Alfa"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em `nextCursor` (1 a 512 caracteres).",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 512
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 50); vazio, não inteiro ou fora disso responde 400.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 25
            },
            "example": 25
          },
          {
            "name": "selectedId",
            "in": "query",
            "required": false,
            "description": "Ids já selecionados; trazem de volta opções inativas (empresa Omie, cliente, produto, tipo de projeto) ou projetos excluídos (cliente excluído nunca volta). Sem efeito em `departamento`, `categoria` e `tipoDocumento`. Repetido ou separado por vírgula; cada id com até 120 caracteres.",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              }
            },
            "example": [
              "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Página do catálogo.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InadimplenciaOpcoesFiltroResponse"
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                        "label": "Empresa Alfa Ltda",
                        "availability": "ACTIVE"
                      },
                      {
                        "id": "3c9e8f10-1111-4222-8333-444455556666",
                        "label": "Empresa Beta S.A.",
                        "availability": "ACTIVE"
                      }
                    ],
                    "hasMore": true,
                    "nextCursor": "eyJsYWJlbCI6ImVtcHJlc2EgYmV0YSBzLmEuIiwiaWQiOiIzYzllOGYxMC0xMTExLTQyMjItODMzMy00NDQ0NTU1NTY2NjYifQ"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/comissoes": {
      "get": {
        "tags": [
          "Comissões"
        ],
        "operationId": "listarComissoes",
        "summary": "Lista paginada de comissões de venda",
        "description": "Lista as comissões de venda (padrão e especiais, misturadas) visíveis ao usuário, com paginação feita em memória sobre o resultado filtrado.\n\n**Ordenação:** só comissões padrão: `dataElegibilidade` decrescente (as ainda sem data, isto é, pendentes, vêm primeiro) e depois criação decrescente. Com comissões especiais no resultado, tudo é reordenado por data decrescente usando `dataElegibilidade`, senão `dataVendaConclusao`, senão criação.\n**Paginação:** `page` e `pageSize` fora do intervalo não geram erro: são ajustados (page mínimo 1; pageSize entre 1 e 500; valor não numérico vale o padrão).\nResposta: `{ data: [...], pagination: { page, pageSize, total, totalPages } }`. Cada item é do formato padrão ou especial (`tipoComissao`).\n\nValores monetários em **reais** (número JSON), percentuais de 0 a 100, datas ISO 8601 (UTC).\n\n**Sem efeito colateral:** é só leitura. Comissões `PENDENTE_REGRA` passam a `PENDENTE_CONFERENCIA` quando uma regra de comissão vigente é criada ou alterada, não numa consulta.\n\n**Permissão:** capacidade `LER_COMISSOES`. Sem ela, 403 (\"Usuário sem permissão para acessar o módulo de comissões.\"). Em geral: usuário de tipo `ADMIN` ou `COMERCIAL`; com acesso por setor ligado, administrador pleno ou membro/gestor do setor Comercial.\n**Escopo de registros:** modo legado: `type ADMIN` ou usuário com `is_admin` vê todas as comissões da organização; usuário `COMERCIAL` sem `is_admin` vê só as do vendedor vinculado ao seu usuário. Com acesso por setor ligado: Dono/Admin e gestor do Comercial veem todas; membro do Comercial vê só as do vendedor vinculado. Sem o interruptor `ESCOPO_COMERCIAL`, um membro do Comercial que também pertence a outro setor pode ver todas. Sem vínculo de vendedor, ou com mais de um, **não há erro: lista vazia / zeros** (404 no detalhe).\n\n### Ciclo de status da comissão padrão\n\n1. `PENDENTE_REGRA`: o vendedor é comissionado, mas não há regra de comissão ativa e vigente (vendedor + produto + tipo de projeto) na data da venda. Vira `PENDENTE_CONFERENCIA` assim que a regra existe.\n2. `PENDENTE_CONFERENCIA`: regra resolvida; percentuais congelados (`percentualPrimeiraParcelaSnapshot`, `percentualServicoSnapshot`); aguardando a **conferência da venda**, feita manualmente por gestão ou automaticamente na aprovação do formulário.\n3. `PENDENTE_ELEGIBILIDADE`: venda conferida (`conferidaEm` preenchido); aguarda a **primeira OS paga**.\n4. `ELEGIVEL`: liberada para pagamento. Acontece quando a **primeira OS do projeto/contrato muda para `PAGO` no Omie** (`primeiraOsPagaId` e `dataElegibilidade` são gravados e os valores calculados). Atalhos: sem projeto (importada, ou formulário que não gera projeto) ou projeto não faturável pelo financeiro, a conferência já libera a comissão sem depender de OS; o mesmo vale para venda sem comissão de serviço cujo SaaS foi conferido. Se a OS foi paga antes da conferência, a conferência já a encontra. Se a OS vinculada for cancelada/excluída e outra OS for paga, a comissão é revinculada à nova.\n5. `FECHADA`: congelada em um fechamento de período (`fechamentoId`); continua apta a pagamento.\n6. `PAGA_PARCIAL`: um componente pago (`SERVICO` ou `PRIMEIRA_PARCELA`) e o outro não.\n7. `PAGA`: todos os componentes aplicáveis pagos (`pagaEm` = o pagamento mais recente).\n8. `CANCELADA`: a OS ligada à comissão foi cancelada ou excluída no Omie antes da elegibilidade. Se uma OS for paga depois, a comissão é revivida ao estágio em que estava. Não há cancelamento manual.\n\nO pagamento exige conferência final registrada (feita por administrador); esses passos são escritas e não fazem parte desta documentação de leitura.\n\n**Componentes:** `SERVICO` (comissão sobre o serviço/implantação) e `PRIMEIRA_PARCELA` (comissão sobre a primeira parcela do SaaS/mensalidade). Cada um tem conferência e pagamento próprios em `componentes`. `valorComissao` é a soma dos dois; percentuais são de 0 a 100; valores em **reais**, números JSON (a API converte os decimais do banco para número).\n\n**Comissões especiais** (`tipoComissao = ESPECIAL`) são comissões adicionais para outro vendedor beneficiário, derivadas de uma comissão padrão por uma regra especial. Têm ciclo próprio: `ABERTA` -> `CONFERIDA` -> `PAGA` (ou `CANCELADA`), um único componente cada, e não pertencem a fechamento.\n\n**Campos expostos:** a resposta tem forma explícita. Não saem colunas internas (organização, arquivo de importação, conferência final) nem o e-mail do vendedor. Os campos `*PorId` são ids (UUID) de usuários do AgendAI.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Página, base 1. Não numérico ou menor que 1 vira 1; valor fracionário não é arredondado (envie inteiro).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Itens por página; limitado a 1-500. Não numérico ou 0 vale 20.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 20
            },
            "example": 20
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra por status. Aceita parâmetro repetido ou lista separada por vírgula. Valores de comissão padrão: `PENDENTE_REGRA`, `PENDENTE_CONFERENCIA`, `PENDENTE_ELEGIBILIDADE`, `ELEGIVEL`, `FECHADA`, `PAGA_PARCIAL`, `PAGA`, `CANCELADA`; de comissão especial: `ABERTA`, `CONFERIDA`, `PAGA`, `CANCELADA`. Valores desconhecidos são **ignorados sem erro**; cada tipo de comissão só é filtrado pelos valores que reconhece (se nenhum valor da lista é válido para um tipo, esse tipo some do resultado).",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "PENDENTE_REGRA",
                  "PENDENTE_CONFERENCIA",
                  "PENDENTE_ELEGIBILIDADE",
                  "ELEGIVEL",
                  "FECHADA",
                  "PAGA_PARCIAL",
                  "PAGA",
                  "CANCELADA",
                  "ABERTA",
                  "CONFERIDA"
                ]
              }
            },
            "example": [
              "ELEGIVEL",
              "PAGA_PARCIAL"
            ]
          },
          {
            "name": "vendedorId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) do vendedor; para comissão especial, o beneficiário. UUID inválido (inclusive texto vazio) responde 400.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b"
          },
          {
            "name": "fechamentoId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) de um fechamento. Comissões especiais não pertencem a fechamento: com este filtro elas são excluídas.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dataVendaInicio",
            "in": "query",
            "required": false,
            "description": "Início do período, inclusivo. Prefira `YYYY-MM-DD` (vira 00:00:00.000Z do dia, UTC); qualquer texto que `new Date()` entenda é aceito, e texto não interpretável responde 400 (\"Data inválida para o domínio de comissões.\"). **Sem `componente`**, compara com a data da venda/conclusão (`dataVendaConclusao`; nas especiais, `dataReferenciaVenda`). **Com `componente`**, comissões de formulário passam a ser comparadas com a data de assinatura do contrato do componente (serviço ou SaaS) do formulário, e as sem formulário (`formularioId` nulo) com a data da venda.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "dataVendaFim",
            "in": "query",
            "required": false,
            "description": "Fim do período, inclusivo até 23:59:59.999Z do dia. Mesmas regras de `dataVendaInicio`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "componente",
            "in": "query",
            "required": false,
            "description": "Mostra só comissões em que o componente é aplicável (valor > 0): `SERVICO` ou `PRIMEIRA_PARCELA` (SaaS/mensalidade). Comissões ligadas a formulário só entram se o formulário tiver a data de assinatura do contrato desse componente (`contratoDataAssinaturaServico` / `contratoDataAssinaturaSaas`), mesmo sem filtro de data; comissões sem formulário entram sempre. Entre as que passam por esse corte, as `PENDENTE_REGRA` aparecem mesmo sem valor. Nas especiais, filtra pelo `componente` da especial. Texto vazio é ignorado; outro valor responde 400. Também muda a base do filtro de data (ver `dataVendaInicio`).",
            "schema": {
              "type": "string",
              "enum": [
                "SERVICO",
                "PRIMEIRA_PARCELA"
              ]
            },
            "example": "SERVICO"
          },
          {
            "name": "produtoId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) do produto. UUID inválido responde 400.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "classe",
            "in": "query",
            "required": false,
            "description": "`ADITIVO` quando o tipo de projeto se chama \"Aditivo\" (sem diferenciar acento/caixa); `NOVO` para todos os demais. Outro valor responde 400.",
            "schema": {
              "type": "string",
              "enum": [
                "NOVO",
                "ADITIVO"
              ]
            },
            "example": "NOVO"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de comissões.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ComissoesComissao"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer",
                          "description": "Página atual."
                        },
                        "pageSize": {
                          "type": "integer",
                          "description": "Itens por página (após ajuste)."
                        },
                        "total": {
                          "type": "integer",
                          "description": "Total de comissões (padrão + especiais) que passam nos filtros."
                        },
                        "totalPages": {
                          "type": "integer",
                          "description": "Total de páginas (`0` quando `total = 0`)."
                        }
                      },
                      "required": [
                        "page",
                        "pageSize",
                        "total",
                        "totalPages"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "pagination"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f607",
                      "tipoComissao": "PADRAO",
                      "origem": "FORMULARIO",
                      "formularioId": "f0e1d2c3-b4a5-4968-8776-5a4b3c2d1e0f",
                      "projetoId": "7e3d1c20-9a54-4d3b-8f21-0c6b5a4d3e22",
                      "vendedorId": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b",
                      "produtoId": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                      "tipoProjetoId": "a1b2c3d4-0000-4000-8000-000000000001",
                      "regraId": "d9c8b7a6-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
                      "importacaoReferenciaExterna": null,
                      "clienteSnapshot": null,
                      "projetoSnapshot": null,
                      "vendedorNomeSnapshot": null,
                      "produtoSnapshot": null,
                      "tipoProjetoSnapshot": null,
                      "contratoId": 912,
                      "primeiraOsPagaId": 48213,
                      "fechamentoId": null,
                      "status": "ELEGIVEL",
                      "dataVendaConclusao": "2026-09-28T00:00:00.000Z",
                      "dataBaseRegra": "2026-09-28T00:00:00.000Z",
                      "percentualPrimeiraParcelaSnapshot": 50,
                      "percentualServicoSnapshot": 5,
                      "regraVigenciaInicio": "2026-01-01T00:00:00.000Z",
                      "regraVigenciaFim": null,
                      "baseCalculoPrimeiraParcela": 1200,
                      "valorComissaoPrimeiraParcela": 600,
                      "baseCalculoServico": 10000,
                      "valorComissaoServico": 500,
                      "baseCalculo": 11200,
                      "valorComissao": 1100,
                      "dataElegibilidade": "2026-10-20T13:05:00.000Z",
                      "conferidaEm": "2026-10-05T14:20:00.000Z",
                      "conferidaPorId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "observacaoConferencia": null,
                      "primeiraParcelaConferidaEm": "2026-10-05T14:20:00.000Z",
                      "primeiraParcelaConferidaPorId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                      "observacaoConferenciaPrimeiraParcela": null,
                      "primeiraParcelaPagaEm": null,
                      "primeiraParcelaPagaPorId": null,
                      "servicoPagoEm": null,
                      "servicoPagoPorId": null,
                      "observacaoPagamentoServico": null,
                      "observacaoPagamentoPrimeiraParcela": null,
                      "pagaEm": null,
                      "pagaPorId": null,
                      "observacaoOperacional": "Comissão elegível pela primeira OS paga confirmada no Omie.",
                      "createdAt": "2026-09-28T17:00:00.000Z",
                      "updatedAt": "2026-10-20T13:05:00.000Z",
                      "vendedor": {
                        "id": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b",
                        "nome": "Vendedor Exemplo"
                      },
                      "produto": {
                        "id": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                        "produto": "Plataforma Exemplo"
                      },
                      "tipoProjeto": {
                        "id": "a1b2c3d4-0000-4000-8000-000000000001",
                        "tipoProjeto": "Implantação",
                        "status": true
                      },
                      "fechamento": null,
                      "cliente": {
                        "id": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                        "nomeEmpresa": "Empresa Alfa Ltda"
                      },
                      "projeto": {
                        "nomeProjeto": "Implantação Alfa",
                        "quantidadeHoras": 100,
                        "valorHora": 100
                      },
                      "contrato": {
                        "id": 912,
                        "tipoContrato": "SERVICO",
                        "numCtr": "CTR-0091",
                        "codStatus": "ATIVO",
                        "descStatus": "Ativo",
                        "valorTotalMensal": 1200,
                        "diaFaturamento": 10,
                        "vigenciaInicio": "2026-09-01T00:00:00.000Z",
                        "vigenciaFim": "2027-08-31T00:00:00.000Z",
                        "pagamentoStatus": "PAGA",
                        "dataPrimeiroRecebimento": "2026-10-20T00:00:00.000Z"
                      },
                      "primeiraOsPaga": {
                        "id": 48213,
                        "numOs": "000412",
                        "codStatus": "PAGO",
                        "descStatus": "Pago",
                        "valorTotal": 10000,
                        "dataFaturamento": "2026-10-01T00:00:00.000Z",
                        "dataVencimento": "2026-10-10T00:00:00.000Z",
                        "quantidadeParcelas": 1,
                        "pagamentoStatus": "PAGA",
                        "dataPrimeiroRecebimento": "2026-10-20T00:00:00.000Z"
                      },
                      "primeiraOsPendente": null,
                      "componentes": {
                        "SERVICO": {
                          "aplicavel": true,
                          "aptoPagamento": true,
                          "conferencia": {
                            "status": "CONFERIDO",
                            "em": "2026-10-05T14:20:00.000Z",
                            "porId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                            "observacao": null
                          },
                          "pagamento": {
                            "status": "PENDENTE",
                            "em": null,
                            "porId": null,
                            "observacao": null
                          }
                        },
                        "PRIMEIRA_PARCELA": {
                          "aplicavel": true,
                          "aptoPagamento": true,
                          "conferencia": {
                            "status": "CONFERIDO",
                            "em": "2026-10-05T14:20:00.000Z",
                            "porId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                            "observacao": null
                          },
                          "pagamento": {
                            "status": "PENDENTE",
                            "em": null,
                            "porId": null,
                            "observacao": null
                          }
                        }
                      }
                    }
                  ],
                  "pagination": {
                    "page": 1,
                    "pageSize": 20,
                    "total": 1,
                    "totalPages": 1
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/comissoes/resumo": {
      "get": {
        "tags": [
          "Comissões"
        ],
        "operationId": "obterResumoComissoes",
        "summary": "Totais de comissões por etapa do fluxo",
        "description": "Soma quantidade e valor das comissões visíveis por etapa do fluxo, com os mesmos filtros de `GET /comissoes` (sem paginação). Resposta: `{ cards: { pendenteRegra, pendenteConferencia, pendenteElegibilidade, prontaPagamento, paga } }`.\n\nMapeamento dos cartões: `pendenteRegra` = `PENDENTE_REGRA`; `pendenteConferencia` = `PENDENTE_CONFERENCIA` + especiais `ABERTA`; `pendenteElegibilidade` = `PENDENTE_ELEGIBILIDADE`; `prontaPagamento` = `ELEGIVEL` + `FECHADA` + `PAGA_PARCIAL` + especiais `CONFERIDA`; `paga` = `PAGA` + especiais `PAGA`. `CANCELADA` não entra em nenhum. Valores em **reais**.\n\nArmadilhas: em `prontaPagamento`, comissões `PAGA_PARCIAL` contribuem só com o valor ainda não pago. Em `pendenteRegra`, o valor é a base de serviço estimada do projeto (horas x valor da hora), pois ainda não há comissão calculada. O filtro `status` limita o que entra, mas os cartões continuam sendo por etapa (um cartão fora do filtro fica zerado).\n\n**Sem efeito colateral:** é só leitura. Comissões `PENDENTE_REGRA` passam a `PENDENTE_CONFERENCIA` quando uma regra de comissão vigente é criada ou alterada, não numa consulta.\n\n**Permissão:** capacidade `LER_COMISSOES`. Sem ela, 403 (\"Usuário sem permissão para acessar o módulo de comissões.\"). Em geral: usuário de tipo `ADMIN` ou `COMERCIAL`; com acesso por setor ligado, administrador pleno ou membro/gestor do setor Comercial.\n**Escopo de registros:** modo legado: `type ADMIN` ou usuário com `is_admin` vê todas as comissões da organização; usuário `COMERCIAL` sem `is_admin` vê só as do vendedor vinculado ao seu usuário. Com acesso por setor ligado: Dono/Admin e gestor do Comercial veem todas; membro do Comercial vê só as do vendedor vinculado. Sem o interruptor `ESCOPO_COMERCIAL`, um membro do Comercial que também pertence a outro setor pode ver todas. Sem vínculo de vendedor, ou com mais de um, **não há erro: lista vazia / zeros** (404 no detalhe).\n\n### Ciclo de status da comissão padrão\n\n1. `PENDENTE_REGRA`: o vendedor é comissionado, mas não há regra de comissão ativa e vigente (vendedor + produto + tipo de projeto) na data da venda. Vira `PENDENTE_CONFERENCIA` assim que a regra existe.\n2. `PENDENTE_CONFERENCIA`: regra resolvida; percentuais congelados (`percentualPrimeiraParcelaSnapshot`, `percentualServicoSnapshot`); aguardando a **conferência da venda**, feita manualmente por gestão ou automaticamente na aprovação do formulário.\n3. `PENDENTE_ELEGIBILIDADE`: venda conferida (`conferidaEm` preenchido); aguarda a **primeira OS paga**.\n4. `ELEGIVEL`: liberada para pagamento. Acontece quando a **primeira OS do projeto/contrato muda para `PAGO` no Omie** (`primeiraOsPagaId` e `dataElegibilidade` são gravados e os valores calculados). Atalhos: sem projeto (importada, ou formulário que não gera projeto) ou projeto não faturável pelo financeiro, a conferência já libera a comissão sem depender de OS; o mesmo vale para venda sem comissão de serviço cujo SaaS foi conferido. Se a OS foi paga antes da conferência, a conferência já a encontra. Se a OS vinculada for cancelada/excluída e outra OS for paga, a comissão é revinculada à nova.\n5. `FECHADA`: congelada em um fechamento de período (`fechamentoId`); continua apta a pagamento.\n6. `PAGA_PARCIAL`: um componente pago (`SERVICO` ou `PRIMEIRA_PARCELA`) e o outro não.\n7. `PAGA`: todos os componentes aplicáveis pagos (`pagaEm` = o pagamento mais recente).\n8. `CANCELADA`: a OS ligada à comissão foi cancelada ou excluída no Omie antes da elegibilidade. Se uma OS for paga depois, a comissão é revivida ao estágio em que estava. Não há cancelamento manual.\n\nO pagamento exige conferência final registrada (feita por administrador); esses passos são escritas e não fazem parte desta documentação de leitura.\n\n**Componentes:** `SERVICO` (comissão sobre o serviço/implantação) e `PRIMEIRA_PARCELA` (comissão sobre a primeira parcela do SaaS/mensalidade). Cada um tem conferência e pagamento próprios em `componentes`. `valorComissao` é a soma dos dois; percentuais são de 0 a 100; valores em **reais**, números JSON (a API converte os decimais do banco para número).\n\n**Comissões especiais** (`tipoComissao = ESPECIAL`) são comissões adicionais para outro vendedor beneficiário, derivadas de uma comissão padrão por uma regra especial. Têm ciclo próprio: `ABERTA` -> `CONFERIDA` -> `PAGA` (ou `CANCELADA`), um único componente cada, e não pertencem a fechamento.\n",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filtra por status. Aceita parâmetro repetido ou lista separada por vírgula. Valores de comissão padrão: `PENDENTE_REGRA`, `PENDENTE_CONFERENCIA`, `PENDENTE_ELEGIBILIDADE`, `ELEGIVEL`, `FECHADA`, `PAGA_PARCIAL`, `PAGA`, `CANCELADA`; de comissão especial: `ABERTA`, `CONFERIDA`, `PAGA`, `CANCELADA`. Valores desconhecidos são **ignorados sem erro**; cada tipo de comissão só é filtrado pelos valores que reconhece (se nenhum valor da lista é válido para um tipo, esse tipo some do resultado).",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "PENDENTE_REGRA",
                  "PENDENTE_CONFERENCIA",
                  "PENDENTE_ELEGIBILIDADE",
                  "ELEGIVEL",
                  "FECHADA",
                  "PAGA_PARCIAL",
                  "PAGA",
                  "CANCELADA",
                  "ABERTA",
                  "CONFERIDA"
                ]
              }
            },
            "example": [
              "ELEGIVEL",
              "PAGA_PARCIAL"
            ]
          },
          {
            "name": "vendedorId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) do vendedor; para comissão especial, o beneficiário. UUID inválido (inclusive texto vazio) responde 400.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b"
          },
          {
            "name": "fechamentoId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) de um fechamento. Comissões especiais não pertencem a fechamento: com este filtro elas são excluídas.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "dataVendaInicio",
            "in": "query",
            "required": false,
            "description": "Início do período, inclusivo. Prefira `YYYY-MM-DD` (vira 00:00:00.000Z do dia, UTC); qualquer texto que `new Date()` entenda é aceito, e texto não interpretável responde 400 (\"Data inválida para o domínio de comissões.\"). **Sem `componente`**, compara com a data da venda/conclusão (`dataVendaConclusao`; nas especiais, `dataReferenciaVenda`). **Com `componente`**, comissões de formulário passam a ser comparadas com a data de assinatura do contrato do componente (serviço ou SaaS) do formulário, e as sem formulário (`formularioId` nulo) com a data da venda.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "dataVendaFim",
            "in": "query",
            "required": false,
            "description": "Fim do período, inclusivo até 23:59:59.999Z do dia. Mesmas regras de `dataVendaInicio`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "componente",
            "in": "query",
            "required": false,
            "description": "Mostra só comissões em que o componente é aplicável (valor > 0): `SERVICO` ou `PRIMEIRA_PARCELA` (SaaS/mensalidade). Comissões ligadas a formulário só entram se o formulário tiver a data de assinatura do contrato desse componente (`contratoDataAssinaturaServico` / `contratoDataAssinaturaSaas`), mesmo sem filtro de data; comissões sem formulário entram sempre. Entre as que passam por esse corte, as `PENDENTE_REGRA` aparecem mesmo sem valor. Nas especiais, filtra pelo `componente` da especial. Texto vazio é ignorado; outro valor responde 400. Também muda a base do filtro de data (ver `dataVendaInicio`).",
            "schema": {
              "type": "string",
              "enum": [
                "SERVICO",
                "PRIMEIRA_PARCELA"
              ]
            },
            "example": "SERVICO"
          },
          {
            "name": "produtoId",
            "in": "query",
            "required": false,
            "description": "Id (UUID) do produto. UUID inválido responde 400.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "classe",
            "in": "query",
            "required": false,
            "description": "`ADITIVO` quando o tipo de projeto se chama \"Aditivo\" (sem diferenciar acento/caixa); `NOVO` para todos os demais. Outro valor responde 400.",
            "schema": {
              "type": "string",
              "enum": [
                "NOVO",
                "ADITIVO"
              ]
            },
            "example": "NOVO"
          }
        ],
        "responses": {
          "200": {
            "description": "Totais por etapa.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComissoesResumoResponse"
                },
                "example": {
                  "cards": {
                    "pendenteRegra": {
                      "totalComissoes": 1,
                      "valorComissaoTotal": 8000
                    },
                    "pendenteConferencia": {
                      "totalComissoes": 2,
                      "valorComissaoTotal": 2300
                    },
                    "pendenteElegibilidade": {
                      "totalComissoes": 3,
                      "valorComissaoTotal": 4100.5
                    },
                    "prontaPagamento": {
                      "totalComissoes": 4,
                      "valorComissaoTotal": 5200,
                      "valorComissaoPrimeiraParcelaPendente": 1800,
                      "valorComissaoServicoPendente": 3400
                    },
                    "paga": {
                      "totalComissoes": 6,
                      "valorComissaoTotal": 9100
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/comissoes/filter-options": {
      "get": {
        "tags": [
          "Comissões"
        ],
        "operationId": "listarOpcoesFiltroComissoes",
        "summary": "Opções para os filtros de comissões",
        "description": "Devolve os valores que alimentam os filtros de `GET /comissoes`: status existentes, vendedores, produtos e fechamentos, **somente a partir do que o usuário pode ver** (mesmo escopo de registros da listagem). Não recebe parâmetros.\n\nResposta: `{ statuses, vendedores, produtos, fechamentos }`.\n\n**Sem efeito colateral:** é só leitura. Comissões `PENDENTE_REGRA` passam a `PENDENTE_CONFERENCIA` quando uma regra de comissão vigente é criada ou alterada, não numa consulta.\n\n**Permissão:** capacidade `LER_COMISSOES`. Sem ela, 403 (\"Usuário sem permissão para acessar o módulo de comissões.\"). Em geral: usuário de tipo `ADMIN` ou `COMERCIAL`; com acesso por setor ligado, administrador pleno ou membro/gestor do setor Comercial.\n**Escopo de registros:** modo legado: `type ADMIN` ou usuário com `is_admin` vê todas as comissões da organização; usuário `COMERCIAL` sem `is_admin` vê só as do vendedor vinculado ao seu usuário. Com acesso por setor ligado: Dono/Admin e gestor do Comercial veem todas; membro do Comercial vê só as do vendedor vinculado. Sem o interruptor `ESCOPO_COMERCIAL`, um membro do Comercial que também pertence a outro setor pode ver todas. Sem vínculo de vendedor, ou com mais de um, **não há erro: lista vazia / zeros** (404 no detalhe).\n",
        "responses": {
          "200": {
            "description": "Opções de filtro.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComissoesFilterOptionsResponse"
                },
                "example": {
                  "statuses": [
                    "ELEGIVEL",
                    "PAGA",
                    "PENDENTE_CONFERENCIA"
                  ],
                  "vendedores": [
                    {
                      "id": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b",
                      "nome": "Vendedor Exemplo"
                    }
                  ],
                  "produtos": [
                    {
                      "id": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                      "produto": "Plataforma Exemplo"
                    }
                  ],
                  "fechamentos": [
                    {
                      "id": "e1f2a3b4-c5d6-4789-8a9b-0c1d2e3f4a5b",
                      "periodoInicio": "2026-09-01T00:00:00.000Z",
                      "periodoFim": "2026-09-30T23:59:59.999Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/comissoes/{id}": {
      "get": {
        "tags": [
          "Comissões"
        ],
        "operationId": "obterComissao",
        "summary": "Detalhe de uma comissão",
        "description": "Devolve uma comissão pelo id, no mesmo formato dos itens de `GET /comissoes` (sem o envelope `data`), já com `primeiraOsPendente` e a situação de recebimento da OS e do contrato (`pagamentoStatus`, `dataPrimeiroRecebimento`) (só na comissão padrão; a especial não tem esses campos).\n\nPadrão: procura entre as comissões **padrão**. Para uma comissão especial é obrigatório enviar `tipoComissao=ESPECIAL`; sem isso o id de uma especial responde 404. O usuário só enxerga comissões do seu escopo (ver abaixo); fora dele a resposta é 404, não 403. O `id` precisa ser um UUID (senão 400).\n\n**Sem efeito colateral:** é só leitura. Comissões `PENDENTE_REGRA` passam a `PENDENTE_CONFERENCIA` quando uma regra de comissão vigente é criada ou alterada, não numa consulta.\n\n**Permissão:** capacidade `LER_COMISSOES`. Sem ela, 403 (\"Usuário sem permissão para acessar o módulo de comissões.\"). Em geral: usuário de tipo `ADMIN` ou `COMERCIAL`; com acesso por setor ligado, administrador pleno ou membro/gestor do setor Comercial.\n**Escopo de registros:** modo legado: `type ADMIN` ou usuário com `is_admin` vê todas as comissões da organização; usuário `COMERCIAL` sem `is_admin` vê só as do vendedor vinculado ao seu usuário. Com acesso por setor ligado: Dono/Admin e gestor do Comercial veem todas; membro do Comercial vê só as do vendedor vinculado. Sem o interruptor `ESCOPO_COMERCIAL`, um membro do Comercial que também pertence a outro setor pode ver todas. Sem vínculo de vendedor, ou com mais de um, **não há erro: lista vazia / zeros** (404 no detalhe).\n\n### Ciclo de status da comissão padrão\n\n1. `PENDENTE_REGRA`: o vendedor é comissionado, mas não há regra de comissão ativa e vigente (vendedor + produto + tipo de projeto) na data da venda. Vira `PENDENTE_CONFERENCIA` assim que a regra existe.\n2. `PENDENTE_CONFERENCIA`: regra resolvida; percentuais congelados (`percentualPrimeiraParcelaSnapshot`, `percentualServicoSnapshot`); aguardando a **conferência da venda**, feita manualmente por gestão ou automaticamente na aprovação do formulário.\n3. `PENDENTE_ELEGIBILIDADE`: venda conferida (`conferidaEm` preenchido); aguarda a **primeira OS paga**.\n4. `ELEGIVEL`: liberada para pagamento. Acontece quando a **primeira OS do projeto/contrato muda para `PAGO` no Omie** (`primeiraOsPagaId` e `dataElegibilidade` são gravados e os valores calculados). Atalhos: sem projeto (importada, ou formulário que não gera projeto) ou projeto não faturável pelo financeiro, a conferência já libera a comissão sem depender de OS; o mesmo vale para venda sem comissão de serviço cujo SaaS foi conferido. Se a OS foi paga antes da conferência, a conferência já a encontra. Se a OS vinculada for cancelada/excluída e outra OS for paga, a comissão é revinculada à nova.\n5. `FECHADA`: congelada em um fechamento de período (`fechamentoId`); continua apta a pagamento.\n6. `PAGA_PARCIAL`: um componente pago (`SERVICO` ou `PRIMEIRA_PARCELA`) e o outro não.\n7. `PAGA`: todos os componentes aplicáveis pagos (`pagaEm` = o pagamento mais recente).\n8. `CANCELADA`: a OS ligada à comissão foi cancelada ou excluída no Omie antes da elegibilidade. Se uma OS for paga depois, a comissão é revivida ao estágio em que estava. Não há cancelamento manual.\n\nO pagamento exige conferência final registrada (feita por administrador); esses passos são escritas e não fazem parte desta documentação de leitura.\n\n**Componentes:** `SERVICO` (comissão sobre o serviço/implantação) e `PRIMEIRA_PARCELA` (comissão sobre a primeira parcela do SaaS/mensalidade). Cada um tem conferência e pagamento próprios em `componentes`. `valorComissao` é a soma dos dois; percentuais são de 0 a 100; valores em **reais**, números JSON (a API converte os decimais do banco para número).\n\n**Comissões especiais** (`tipoComissao = ESPECIAL`) são comissões adicionais para outro vendedor beneficiário, derivadas de uma comissão padrão por uma regra especial. Têm ciclo próprio: `ABERTA` -> `CONFERIDA` -> `PAGA` (ou `CANCELADA`), um único componente cada, e não pertencem a fechamento.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Id (UUID) da comissão.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f607"
          },
          {
            "name": "tipoComissao",
            "in": "query",
            "required": false,
            "description": "`PADRAO` (padrão quando omitido) ou `ESPECIAL`. Outro valor responde 400.",
            "schema": {
              "type": "string",
              "enum": [
                "PADRAO",
                "ESPECIAL"
              ],
              "default": "PADRAO"
            },
            "example": "PADRAO"
          }
        ],
        "responses": {
          "200": {
            "description": "Comissão encontrada.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComissoesComissao"
                },
                "example": {
                  "id": "c1a2b3c4-d5e6-4f70-8a91-b2c3d4e5f607",
                  "tipoComissao": "PADRAO",
                  "origem": "FORMULARIO",
                  "formularioId": "f0e1d2c3-b4a5-4968-8776-5a4b3c2d1e0f",
                  "projetoId": "7e3d1c20-9a54-4d3b-8f21-0c6b5a4d3e22",
                  "vendedorId": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b",
                  "produtoId": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                  "tipoProjetoId": "a1b2c3d4-0000-4000-8000-000000000001",
                  "regraId": "d9c8b7a6-5f4e-4d3c-8b2a-1f0e9d8c7b6a",
                  "importacaoReferenciaExterna": null,
                  "clienteSnapshot": null,
                  "projetoSnapshot": null,
                  "vendedorNomeSnapshot": null,
                  "produtoSnapshot": null,
                  "tipoProjetoSnapshot": null,
                  "contratoId": 912,
                  "primeiraOsPagaId": 48213,
                  "fechamentoId": null,
                  "status": "ELEGIVEL",
                  "dataVendaConclusao": "2026-09-28T00:00:00.000Z",
                  "dataBaseRegra": "2026-09-28T00:00:00.000Z",
                  "percentualPrimeiraParcelaSnapshot": 50,
                  "percentualServicoSnapshot": 5,
                  "regraVigenciaInicio": "2026-01-01T00:00:00.000Z",
                  "regraVigenciaFim": null,
                  "baseCalculoPrimeiraParcela": 1200,
                  "valorComissaoPrimeiraParcela": 600,
                  "baseCalculoServico": 10000,
                  "valorComissaoServico": 500,
                  "baseCalculo": 11200,
                  "valorComissao": 1100,
                  "dataElegibilidade": "2026-10-20T13:05:00.000Z",
                  "conferidaEm": "2026-10-05T14:20:00.000Z",
                  "conferidaPorId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "observacaoConferencia": null,
                  "primeiraParcelaConferidaEm": "2026-10-05T14:20:00.000Z",
                  "primeiraParcelaConferidaPorId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                  "observacaoConferenciaPrimeiraParcela": null,
                  "primeiraParcelaPagaEm": null,
                  "primeiraParcelaPagaPorId": null,
                  "servicoPagoEm": null,
                  "servicoPagoPorId": null,
                  "observacaoPagamentoServico": null,
                  "observacaoPagamentoPrimeiraParcela": null,
                  "pagaEm": null,
                  "pagaPorId": null,
                  "observacaoOperacional": "Comissão elegível pela primeira OS paga confirmada no Omie.",
                  "createdAt": "2026-09-28T17:00:00.000Z",
                  "updatedAt": "2026-10-20T13:05:00.000Z",
                  "vendedor": {
                    "id": "2d4f6a80-1b3c-4e5f-8a7b-9c0d1e2f3a4b",
                    "nome": "Vendedor Exemplo"
                  },
                  "produto": {
                    "id": "5f0c1e7a-8d2b-4a39-b0c4-1e2d3c4b5a69",
                    "produto": "Plataforma Exemplo"
                  },
                  "tipoProjeto": {
                    "id": "a1b2c3d4-0000-4000-8000-000000000001",
                    "tipoProjeto": "Implantação",
                    "status": true
                  },
                  "fechamento": null,
                  "cliente": {
                    "id": "0b6a1d52-3f0e-4c6a-9d0e-5a1f2f6d7a11",
                    "nomeEmpresa": "Empresa Alfa Ltda"
                  },
                  "projeto": {
                    "nomeProjeto": "Implantação Alfa",
                    "quantidadeHoras": 100,
                    "valorHora": 100
                  },
                  "contrato": {
                    "id": 912,
                    "tipoContrato": "SERVICO",
                    "numCtr": "CTR-0091",
                    "codStatus": "ATIVO",
                    "descStatus": "Ativo",
                    "valorTotalMensal": 1200,
                    "diaFaturamento": 10,
                    "vigenciaInicio": "2026-09-01T00:00:00.000Z",
                    "vigenciaFim": "2027-08-31T00:00:00.000Z",
                    "pagamentoStatus": "PAGA",
                    "dataPrimeiroRecebimento": "2026-10-20T00:00:00.000Z"
                  },
                  "primeiraOsPaga": {
                    "id": 48213,
                    "numOs": "000412",
                    "codStatus": "PAGO",
                    "descStatus": "Pago",
                    "valorTotal": 10000,
                    "dataFaturamento": "2026-10-01T00:00:00.000Z",
                    "dataVencimento": "2026-10-10T00:00:00.000Z",
                    "quantidadeParcelas": 1,
                    "pagamentoStatus": "PAGA",
                    "dataPrimeiroRecebimento": "2026-10-20T00:00:00.000Z"
                  },
                  "primeiraOsPendente": null,
                  "componentes": {
                    "SERVICO": {
                      "aplicavel": true,
                      "aptoPagamento": true,
                      "conferencia": {
                        "status": "CONFERIDO",
                        "em": "2026-10-05T14:20:00.000Z",
                        "porId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                        "observacao": null
                      },
                      "pagamento": {
                        "status": "PENDENTE",
                        "em": null,
                        "porId": null,
                        "observacao": null
                      }
                    },
                    "PRIMEIRA_PARCELA": {
                      "aplicavel": true,
                      "aptoPagamento": true,
                      "conferencia": {
                        "status": "CONFERIDO",
                        "em": "2026-10-05T14:20:00.000Z",
                        "porId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
                        "observacao": null
                      },
                      "pagamento": {
                        "status": "PENDENTE",
                        "em": null,
                        "porId": null,
                        "observacao": null
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/oportunidades": {
      "get": {
        "tags": [
          "Oportunidades"
        ],
        "operationId": "listarOportunidades",
        "summary": "Lista oportunidades (paginada, com filtros e ordenação)",
        "description": "Lista as oportunidades comerciais (CRM) do tenant do usuário, uma linha por oportunidade, com paginação por página\n(`page`/`pageSize`), filtros combináveis (todos opcionais, combinados com E) e ordenação.\n\n## Modelo de dados\n- Uma **oportunidade** pertence a um **pipeline** e está em uma **etapa** dele (`pipeline` → `etapas`, ordenadas por\n  `position`). O `fluxo` do pipeline (`COMERCIAL`, `CS_PARA_COMERCIAL`, `CS_PARA_CS`, `COMERCIAL_PARA_CS`) é herdado\n  pela oportunidade.\n- **`status`** (ciclo de vida): `ABERTA` (em negociação), `ACEITA` (**ganha**; o enum usa \"ACEITA\", não \"ganha\") e\n  `PERDIDA`. As etapas têm `papel` (`NENHUM`, `PROPOSTA_ENVIADA`, `AJUSTE_SOLICITADO`, `ACEITE`, `PERDA`); as etapas de\n  papel `ACEITE` e `PERDA` são as colunas \"fechadas\" do quadro.\n- **`tipo`**: `VENDA_NOVA`, `ADITIVO` ou `SERVICO`.\n- **Pessoas**: `vendedor` é um registro de **vendedor** (tabela de vendedores, não de usuários; seu `id` é o aceito em\n  `vendedorId`). O **CS responsável** (`csResponsavel`, um usuário) só aparece no detalhe (`GET /oportunidades/{id}`).\n- **`empresa`**: a empresa (cliente ou prospect) dona da oportunidade. `contato` é opcional.\n- **Valores**: `valorEstimado` é um **número JSON em reais (BRL)**, com até 2 casas decimais (não é string, não é centavos);\n  `null` = sem valor estimado. Datas de calendário (`dataPrevisaoFechamento`) são `YYYY-MM-DD`; instantes\n  (`createdAt`, `entrouNaEtapaEm`) são ISO 8601 em UTC. Dias \"civis\" (filtros `criadaDe`/`criadaAte`, `atrasada`, `diasNaEtapa`)\n  usam o fuso de São Paulo.\n\n## Quem enxerga o quê (escopo de visibilidade)\nNão há erro 403 por falta de permissão: o escopo **filtra a lista**.\n- Usuário com escopo total vê **todas** as oportunidades do tenant. Legado (acesso por setor desligado): papel manager/admin/owner,\n  `type=ADMIN` ou `is_admin` (gestor de qualquer setor). Acesso por setor ligado: Dono/Admin, gestor do Comercial e, com `ESCOPO_COMERCIAL`\n  desligado, gestor de qualquer setor.\n- Usuário vinculado a **um único vendedor** vê **somente as oportunidades desse vendedor** (`vendedorId` igual ao dele). Com `ESCOPO_COMERCIAL`\n  ligado, o vínculo de vendedor só vale para membro ou gestor do Comercial; os demais caem no perfil restrito.\n- Qualquer outro usuário (perfil \"restrito\") vê **somente as oportunidades em que é responsável por alguma tarefa**; nele,\n  `contrato` vem sempre `null`.\nFiltros como `vendedorId` só reduzem ainda mais o que o escopo já permite.\nSempre há isolamento por tenant.\n\n## Filtros\nTodos opcionais. Combinações sem resultado possível são recusadas com `400` em vez de devolver lista vazia:\n`semPrevisao` com `previsaoDe`/`previsaoAte`/`atrasada`; `semValor` com `valorMin`; `situacao` com `status`;\n`criadaDe` maior que `criadaAte`. Parâmetros desconhecidos também geram `400`. Os booleanos de query são as strings\n`true`/`false`; `false` equivale a \"sem filtro\" (não filtra pelo oposto).\n\n## Ordenação e paginação\n`sort` (`criadaEm` padrão, `valor`, `previsao`, `codigo`) e `dir` (`desc` padrão). Em `valor` e `previsao` os nulos ficam por último\nem qualquer direção; `codigo` ordena pela data de criação/sequência (equivale a `criadaEm`), e o `id` desempata.\n`page` começa em 1; `pageSize` padrão 25, máximo 100. O corpo traz `total` (de todos os registros que casam com o filtro, sem paginar).\n\n## Campos derivados do item\n- `diasNaEtapa`: dias civis desde `entrouNaEtapaEm`.\n- `proximaTarefa`: tarefa aberta de menor prazo (tarefas `ADIADA` não contam; só tarefas que o usuário enxerga); `tarefasVencidas`: abertas com prazo vencido.\n- `proposta`: selo da versão mais recente da proposta que não é rascunho nem substituída; `termometro`/`termometroMotivo` só existem\n  enquanto a proposta espera o cliente (`ENVIADA` ou `AJUSTE_SOLICITADO`) e a oportunidade está aberta.\n- `contrato`: selo do contrato da oportunidade.\n",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Busca textual (sem diferenciar maiúsculas) em código, título, nome fantasia ou razão social da empresa e nome do contato. Máx. 100 caracteres; espaços das pontas são removidos.",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "example": "Alfa"
          },
          {
            "name": "vendedorId",
            "in": "query",
            "required": false,
            "description": "UUID do vendedor (o `vendedor.id` que vem nos itens desta lista).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "0b8f6a52-3c41-4d0e-9a77-5f2d1c6e8a10"
          },
          {
            "name": "empresaId",
            "in": "query",
            "required": false,
            "description": "UUID da empresa.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "7d3c2f10-8a4b-4e65-b1d9-2c9e5a7f0b33"
          },
          {
            "name": "produtoId",
            "in": "query",
            "required": false,
            "description": "UUID do produto da oportunidade (o `produto.id` que vem nos itens desta lista).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1"
          },
          {
            "name": "fluxo",
            "in": "query",
            "required": false,
            "description": "Fluxo do pipeline em que a oportunidade está.",
            "schema": {
              "type": "string",
              "enum": [
                "COMERCIAL",
                "CS_PARA_COMERCIAL",
                "CS_PARA_CS",
                "COMERCIAL_PARA_CS"
              ]
            },
            "example": "COMERCIAL"
          },
          {
            "name": "comTarefaVencida",
            "in": "query",
            "required": false,
            "description": "`true` = só oportunidades com pelo menos uma tarefa vencida (entre as tarefas que o usuário enxerga). `false` = sem filtro.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "example": "true"
          },
          {
            "name": "previsaoDe",
            "in": "query",
            "required": false,
            "description": "Previsão de fechamento a partir desta data (inclusiva), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-11-01"
          },
          {
            "name": "previsaoAte",
            "in": "query",
            "required": false,
            "description": "Previsão de fechamento até esta data (inclusiva), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-12-31"
          },
          {
            "name": "semPrevisao",
            "in": "query",
            "required": false,
            "description": "`true` = só oportunidades sem data de previsão. Não combina com `previsaoDe`, `previsaoAte` nem `atrasada`. `false` = sem filtro.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "example": "true"
          },
          {
            "name": "atrasada",
            "in": "query",
            "required": false,
            "description": "`true` = só `ABERTA` com previsão de fechamento anterior a hoje (dia civil de São Paulo). `false` = sem filtro.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "example": "true"
          },
          {
            "name": "situacao",
            "in": "query",
            "required": false,
            "description": "`abertas` = status `ABERTA`; `fechadas` = `ACEITA` ou `PERDIDA`. Não combina com `status`.",
            "schema": {
              "type": "string",
              "enum": [
                "abertas",
                "fechadas"
              ]
            },
            "example": "abertas"
          },
          {
            "name": "valorMin",
            "in": "query",
            "required": false,
            "description": "Valor estimado mínimo, inclusivo, em reais (0 a 999999999999.99). Oportunidades sem valor ficam de fora. Não combina com `semValor`. Atenção: `valorMin=` vazio vira 0 (sem 400) e exclui as sem valor.",
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 999999999999.99
            },
            "example": 10000
          },
          {
            "name": "semValor",
            "in": "query",
            "required": false,
            "description": "`true` = só oportunidades sem valor estimado. Não combina com `valorMin`. `false` = sem filtro.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "example": "true"
          },
          {
            "name": "criadaDe",
            "in": "query",
            "required": false,
            "description": "Criada a partir deste dia (inclusivo, dia civil de São Paulo), `YYYY-MM-DD`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-01"
          },
          {
            "name": "criadaAte",
            "in": "query",
            "required": false,
            "description": "Criada até este dia (inclusivo, dia civil de São Paulo), `YYYY-MM-DD`. Deve ser maior ou igual a `criadaDe`.",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-10-31"
          },
          {
            "name": "pipelineId",
            "in": "query",
            "required": false,
            "description": "UUID do pipeline (o `pipeline.id` que vem nos itens desta lista).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "9c4e7a12-5d38-4b0f-a6e1-3f8b2d1c0e55"
          },
          {
            "name": "etapaId",
            "in": "query",
            "required": false,
            "description": "UUID da etapa (ids de etapa vêm em `etapas[].id` do quadro ou em `etapasDoPipeline[].id` do detalhe).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Status exato. Não combina com `situacao`.",
            "schema": {
              "type": "string",
              "enum": [
                "ABERTA",
                "ACEITA",
                "PERDIDA"
              ]
            },
            "example": "ABERTA"
          },
          {
            "name": "tipo",
            "in": "query",
            "required": false,
            "description": "Tipo da oportunidade.",
            "schema": {
              "type": "string",
              "enum": [
                "VENDA_NOVA",
                "ADITIVO",
                "SERVICO"
              ]
            },
            "example": "VENDA_NOVA"
          },
          {
            "name": "projetoId",
            "in": "query",
            "required": false,
            "description": "UUID do projeto vinculado.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "4f0a8c36-1e92-4d7b-8c05-9a3e6b1d2f70"
          },
          {
            "name": "sort",
            "in": "query",
            "required": false,
            "description": "Campo de ordenação. `valor` e `previsao` põem nulos por último.",
            "schema": {
              "type": "string",
              "enum": [
                "criadaEm",
                "valor",
                "previsao",
                "codigo"
              ],
              "default": "criadaEm"
            },
            "example": "valor"
          },
          {
            "name": "dir",
            "in": "query",
            "required": false,
            "description": "Direção da ordenação.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "example": "desc"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Número da página, a partir de 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "example": 25
          }
        ],
        "responses": {
          "200": {
            "description": "Página de oportunidades.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OportunidadeLista"
                },
                "example": {
                  "items": [
                    {
                      "id": "3b7e1a90-6c24-4f58-8d0b-e5a2c91f7d46",
                      "codigo": "OP-2026-0042",
                      "titulo": "Implantação ERP - Empresa Alfa Ltda",
                      "tipo": "VENDA_NOVA",
                      "status": "ABERTA",
                      "fluxo": "COMERCIAL",
                      "etapaId": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88",
                      "position": 3,
                      "valorEstimado": 48500.5,
                      "dataPrevisaoFechamento": "2026-12-15",
                      "empresa": {
                        "id": "7d3c2f10-8a4b-4e65-b1d9-2c9e5a7f0b33",
                        "nome": "Empresa Alfa Ltda",
                        "cnpj": "12345678000195",
                        "relacao": "PROSPECT",
                        "ativo": true
                      },
                      "contato": {
                        "id": "c1a4d8e2-0b97-4f36-a5d1-7e2b9c3f6084",
                        "nome": "Contato Exemplo",
                        "cargo": "Diretor Financeiro",
                        "email": "contato@alfa.example",
                        "telefone": "11999990000"
                      },
                      "vendedor": {
                        "id": "0b8f6a52-3c41-4d0e-9a77-5f2d1c6e8a10",
                        "nome": "Vendedor Exemplo",
                        "iniciais": "VE"
                      },
                      "entrouNaEtapaEm": "2026-11-03T14:22:10.000Z",
                      "diasNaEtapa": 8,
                      "proximaTarefa": {
                        "id": "a9d2f4b1-3e60-4c78-9b15-0f8e7d6c5a21",
                        "titulo": "Ligar para o contato",
                        "tipo": "LIGACAO",
                        "prazo": "2026-11-12T17:00:00.000Z",
                        "vencida": false
                      },
                      "tarefasVencidas": 0,
                      "proposta": {
                        "versao": 1,
                        "status": "ENVIADA",
                        "aberturas": 2,
                        "validadeAte": "2026-11-30"
                      },
                      "termometro": "MORNA",
                      "termometroMotivo": "ABERTA",
                      "contrato": {
                        "situacao": "semContrato",
                        "contratoId": null,
                        "categoria": null,
                        "assinados": null,
                        "total": null,
                        "quantidade": 0,
                        "clienteObrigatorio": false,
                        "prospect": true,
                        "podeGerar": false
                      },
                      "pipeline": {
                        "id": "9c4e7a12-5d38-4b0f-a6e1-3f8b2d1c0e55",
                        "nome": "Pipeline Comercial"
                      },
                      "etapa": {
                        "id": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88",
                        "nome": "Proposta enviada",
                        "papel": "PROPOSTA_ENVIADA"
                      },
                      "produto": {
                        "id": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                        "nome": "Produto Exemplo"
                      },
                      "createdAt": "2026-10-20T12:05:00.000Z"
                    }
                  ],
                  "page": 1,
                  "pageSize": 25,
                  "total": 1
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/oportunidades/{id}": {
      "get": {
        "tags": [
          "Oportunidades"
        ],
        "operationId": "obterOportunidade",
        "summary": "Detalha uma oportunidade",
        "description": "Devolve o detalhe completo de uma oportunidade: dados do cartão mais pipeline e todas as suas etapas (`etapasDoPipeline`), empresa, contato,\nprojeto, produto, tipo de projeto, vendedor, CS responsável, briefing, cláusula especial, motivo de perda, escopo (itens com horas), auditoria\nde criação e as `permissoes` que o usuário tem sobre ela.\n\n- `valorEstimado` e `horasEstimadas` (e `escopo[].horas`) são **números JSON** (reais e horas decimais), `null` quando não informados.\n- `fechadaEm` só existe quando `status` é `ACEITA` ou `PERDIDA`; `motivoPerda`/`motivoPerdaDescricao` só quando `PERDIDA`\n  (`PRECO`, `CONCORRENTE`, `SEM_ORCAMENTO`, `SEM_RETORNO`, `ADIADO`, `OUTRO`).\n- `etapa.probabilidade` é um inteiro 0 a 100 (ou `null`), usado no valor ponderado do quadro.\n- `permissoes` é um reflexo informativo (o usuário desta API é somente leitura): `podeEditar`/`podeMover` exigem status `ABERTA` e que o usuário seja\n  escopo total ou dono (vendedor) da oportunidade; `podeTrocarPipeline` exige gestor do CRM e status `ABERTA`; `podeReabrir` exige gestor do CRM e\n  status `PERDIDA`. Gestor = papel manager/admin/owner no legado; Dono/Admin ou gestor do Comercial no acesso por setor.\n\n## Permissão e escopo\nQualquer usuário autenticado, mas só enxerga oportunidades do seu escopo (todas / as do seu vendedor / aquelas em que é responsável por tarefa).\nUma oportunidade que existe mas está **fora do escopo** responde `404` com `code = OPORTUNIDADE_NAO_ENCONTRADA` (nunca 403), para não revelar existência.\nId que não é UUID responde `400`.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID da oportunidade.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3b7e1a90-6c24-4f58-8d0b-e5a2c91f7d46"
          }
        ],
        "responses": {
          "200": {
            "description": "Detalhe da oportunidade.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OportunidadeDetalhe"
                },
                "example": {
                  "id": "3b7e1a90-6c24-4f58-8d0b-e5a2c91f7d46",
                  "codigo": "OP-2026-0042",
                  "numero": 42,
                  "titulo": "Implantação ERP - Empresa Alfa Ltda",
                  "tipo": "VENDA_NOVA",
                  "status": "ABERTA",
                  "pipeline": {
                    "id": "9c4e7a12-5d38-4b0f-a6e1-3f8b2d1c0e55",
                    "nome": "Pipeline Comercial"
                  },
                  "fluxo": "COMERCIAL",
                  "etapa": {
                    "id": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88",
                    "nome": "Proposta enviada",
                    "papel": "PROPOSTA_ENVIADA",
                    "probabilidade": 50
                  },
                  "etapasDoPipeline": [
                    {
                      "id": "1a5c7e90-3b24-4d68-9f01-8e2b4a6c0d13",
                      "nome": "Qualificação",
                      "position": 1,
                      "papel": "NENHUM",
                      "cor": "#9ca3af"
                    },
                    {
                      "id": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88",
                      "nome": "Proposta enviada",
                      "position": 3,
                      "papel": "PROPOSTA_ENVIADA",
                      "cor": "#3b82f6"
                    }
                  ],
                  "entrouNaEtapaEm": "2026-11-03T14:22:10.000Z",
                  "diasNaEtapa": 8,
                  "empresa": {
                    "id": "7d3c2f10-8a4b-4e65-b1d9-2c9e5a7f0b33",
                    "nome": "Empresa Alfa Ltda",
                    "cnpj": "12345678000195",
                    "relacao": "PROSPECT",
                    "ativo": true
                  },
                  "contato": {
                    "id": "c1a4d8e2-0b97-4f36-a5d1-7e2b9c3f6084",
                    "nome": "Contato Exemplo",
                    "cargo": "Diretor Financeiro",
                    "email": "contato@alfa.example",
                    "telefone": "11999990000"
                  },
                  "projeto": null,
                  "produto": {
                    "id": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                    "nome": "Produto Exemplo"
                  },
                  "tipoProjeto": {
                    "id": "8e2d4a60-9b13-4c75-a0f8-1b6c3d7e9f42",
                    "nome": "Implantação"
                  },
                  "vendedor": {
                    "id": "0b8f6a52-3c41-4d0e-9a77-5f2d1c6e8a10",
                    "nome": "Vendedor Exemplo"
                  },
                  "csResponsavel": null,
                  "valorEstimado": 48500.5,
                  "horasEstimadas": 120,
                  "dataPrevisaoFechamento": "2026-12-15",
                  "briefing": "Cliente quer migrar do sistema atual até o fim do ano.",
                  "clausulaEspecial": null,
                  "motivoPerda": null,
                  "motivoPerdaDescricao": null,
                  "fechadaEm": null,
                  "escopo": [
                    {
                      "id": "e7a3c9d1-5b02-4f64-8a1d-3c0b6e9f2d85",
                      "ordem": 1,
                      "descricao": "Treinamento da equipe",
                      "horas": 40,
                      "modulo": {
                        "id": "d3f9b5a7-4c20-4e18-8a6b-0e1c7f2d9b54",
                        "nome": "Módulo Exemplo"
                      },
                      "subModulo": null
                    }
                  ],
                  "createdBy": {
                    "id": "f4c7a1d9-6e32-4b85-9c0a-2d8e5b1f7a63",
                    "nome": "Usuário Exemplo"
                  },
                  "createdAt": "2026-10-20T12:05:00.000Z",
                  "updatedAt": "2026-11-03T14:22:10.000Z",
                  "proposta": {
                    "versao": 1,
                    "status": "ENVIADA",
                    "aberturas": 0,
                    "validadeAte": "2026-11-30"
                  },
                  "termometro": "MORNA",
                  "termometroMotivo": "AGUARDANDO_ABERTURA",
                  "contrato": {
                    "situacao": "semContrato",
                    "contratoId": null,
                    "categoria": null,
                    "assinados": null,
                    "total": null,
                    "quantidade": 0,
                    "clienteObrigatorio": false,
                    "prospect": true,
                    "podeGerar": false
                  },
                  "permissoes": {
                    "podeEditar": true,
                    "podeMover": true,
                    "podeTrocarPipeline": false,
                    "podeReabrir": false
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/oportunidades/{id}/historico": {
      "get": {
        "tags": [
          "Oportunidades"
        ],
        "operationId": "listarHistoricoOportunidade",
        "summary": "Linha do tempo (histórico) da oportunidade, com paginação por cursor",
        "description": "Linha do tempo da oportunidade, do **mais recente para o mais antigo** (`createdAt` desc, `id` desc): criação, edições, mudanças de etapa/pipeline,\nperda/aceite/reabertura, tarefas, propostas (inclusive ações do cliente na proposta pública), notas, contrato e formulário.\n\n- Paginação por **cursor**: envie o `nextCursor` recebido como `cursor` na chamada seguinte, com os mesmos demais parâmetros. `nextCursor = null`\n  indica a última página. O cursor é opaco (base64url); um cursor malformado gera `400` com `message` em string única (`\"Cursor inválido.\"`), não\n  na lista `campo: motivo` dos demais erros de validação. `limit` padrão 30, máximo 100.\n- `origem` filtra pela origem do evento (`OPORTUNIDADE`, `TAREFA`, `PROPOSTA`, `NOTA`, `CONTRATO`, `FORMULARIO`).\n- Notas excluídas não aparecem.\n- Cada item: `acao` é um código textual (ex.: `CRIADA`, `EDITADA`, `ETAPA_ALTERADA`, `PIPELINE_ALTERADO`, `ACEITA`, `PERDIDA`, `REABERTA`,\n  `NOTA_ADICIONADA`, `TAREFA_CRIADA`, `TAREFA_CONCLUIDA`, `PROPOSTA_ENVIADA`, `ANEXO_ADICIONADO`...; a lista não é fechada, trate valores desconhecidos como texto);\n  `dados` é um objeto JSON livre com os detalhes da ação (depende de `acao`; pode ser `null`); `texto` é o conteúdo livre (por exemplo, o texto da nota).\n  `autor` é o usuário que agiu; `null` quando quem agiu foi o cliente na proposta pública (aí `destinatario` traz o contato) ou o próprio\n  sistema (expiração automática, eventos do provedor de assinatura, falha técnica do formulário). Nesse último caso `destinatario` também é `null`.\n  `editadaEm`/`editadaPor` só existem em nota editada. `podeAlterar` indica se o usuário poderia editar/excluir a nota (só `NOTA_ADICIONADA`).\n\n## Permissão e escopo\nQualquer usuário autenticado que enxergue a oportunidade (mesmo escopo do detalhe); fora do escopo ou inexistente = `404` `OPORTUNIDADE_NAO_ENCONTRADA`.\nPara o perfil **restrito** (só vê oportunidades em que tem tarefa), eventos de origem `CONTRATO` e formulários nascidos de contrato são omitidos, e\n`origem=CONTRATO` devolve lista vazia.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID da oportunidade.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "3b7e1a90-6c24-4f58-8d0b-e5a2c91f7d46"
          },
          {
            "name": "origem",
            "in": "query",
            "required": false,
            "description": "Filtra pela origem do evento.",
            "schema": {
              "type": "string",
              "enum": [
                "OPORTUNIDADE",
                "TAREFA",
                "PROPOSTA",
                "NOTA",
                "CONTRATO",
                "FORMULARIO"
              ]
            },
            "example": "NOTA"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor opaco devolvido em `nextCursor` da página anterior.",
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "example": "WyIyMDI2LTExLTAzVDE0OjIyOjEwLjAwMFoiLCI3YzJhOWYxMC00ZDM4LTRiNjUtOGUwMS0zYTVjN2U5ZDJiNDYiXQ"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 100).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 30
            },
            "example": 30
          }
        ],
        "responses": {
          "200": {
            "description": "Página do histórico.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OportunidadeHistoricoPagina"
                },
                "example": {
                  "items": [
                    {
                      "id": "7c2a9f10-4d38-4b65-8e01-3a5c7e9d2b46",
                      "origem": "NOTA",
                      "acao": "NOTA_ADICIONADA",
                      "entidadeTipo": null,
                      "entidadeId": null,
                      "texto": "Cliente pediu desconto no setup.",
                      "dados": null,
                      "autor": {
                        "id": "f4c7a1d9-6e32-4b85-9c0a-2d8e5b1f7a63",
                        "nome": "Usuário Exemplo"
                      },
                      "destinatario": null,
                      "createdAt": "2026-11-03T14:22:10.000Z",
                      "editadaEm": null,
                      "editadaPor": null,
                      "podeAlterar": true
                    },
                    {
                      "id": "1d8b4e70-9a25-4c63-b0f7-5e2a8c1d3f94",
                      "origem": "OPORTUNIDADE",
                      "acao": "ETAPA_ALTERADA",
                      "entidadeTipo": null,
                      "entidadeId": null,
                      "texto": null,
                      "dados": {
                        "de": {
                          "id": "1a5c7e90-3b24-4d68-9f01-8e2b4a6c0d13",
                          "nome": "Qualificação",
                          "papel": "NENHUM"
                        },
                        "para": {
                          "id": "2e6b9d03-7c14-4a8f-9b52-1d7f3a0c6e88",
                          "nome": "Proposta enviada",
                          "papel": "PROPOSTA_ENVIADA"
                        },
                        "origem": "MANUAL",
                        "posicao": 0,
                        "pendencias": []
                      },
                      "autor": {
                        "id": "f4c7a1d9-6e32-4b85-9c0a-2d8e5b1f7a63",
                        "nome": "Usuário Exemplo"
                      },
                      "destinatario": null,
                      "createdAt": "2026-11-01T10:00:00.000Z",
                      "editadaEm": null,
                      "editadaPor": null,
                      "podeAlterar": false
                    }
                  ],
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/metas": {
      "get": {
        "tags": [
          "Metas"
        ],
        "operationId": "listarMetas",
        "summary": "Lista as metas (objetivos) de vendas de um ano",
        "description": "Devolve as **metas cadastradas** do tenant para um ano. Uma meta é um **objetivo mensal por produto e classe**: a combinação\n(`produtoId`, `classe`, `ano`, `mes`) é única no tenant. **Não há meta por vendedor ou por equipe**: as metas são globais ao tenant.\n\n- `classe`: `NOVO` (venda nova) ou `ADITIVO` (aditivo a cliente existente). É a classificação do tipo de projeto da venda: `ADITIVO` = tipo\n  \"Aditivo\"; `NOVO` = \"Implantação e Consultoria\" ou \"CS\" (nomes comparados sem acento, sem diferenciar maiúsculas e sem espaços nas pontas).\n  Qualquer outro tipo (ex.: \"Serviço de base\") nunca entra no realizado.\n- `quantidadeMeta`: número inteiro de vendas desejadas no mês. No dashboard, vale tanto para MRR quanto para serviço.\n- `valorSaasMeta`: meta de **MRR** (receita recorrente mensal do SaaS) do mês, em **reais**, número JSON (não string, não centavos).\n- `valorServicoMeta`: meta de **valor de serviço** (implantação/setup, receita não recorrente) do mês, em **reais**, número JSON.\n- `mes`: 1 a 12. Só existem linhas para os meses efetivamente cadastrados (meses sem meta não aparecem aqui).\n- Ordem: nome do produto, depois `classe`, depois `mes`. O corpo é um **array simples** (sem paginação).\n\nEsta rota traz só os **objetivos**; o **realizado** vem de `GET /metas/dashboard` (agregado) e `GET /metas/realizados` (vendas uma a uma).\nEscopo de dados: metas não são filtradas por vendedor.\n\n## Permissão\nQualquer usuário autenticado do tenant: o código não aplica checagem de capacidade nem de tela nesta rota (a leitura dos *realizados* é que é\nrestrita ao Comercial/Administração). Não há `403` específico.\n",
        "parameters": [
          {
            "name": "ano",
            "in": "query",
            "required": true,
            "description": "Ano das metas (inteiro maior ou igual a 1).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 2026
          },
          {
            "name": "produtoId",
            "in": "query",
            "required": false,
            "description": "Restringe a um produto (UUID; o mesmo `produto.id` dos itens de `GET /oportunidades`).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1"
          },
          {
            "name": "classe",
            "in": "query",
            "required": false,
            "description": "Restringe a uma classe de meta.",
            "schema": {
              "type": "string",
              "enum": [
                "ADITIVO",
                "NOVO"
              ]
            },
            "example": "NOVO"
          }
        ],
        "responses": {
          "200": {
            "description": "Metas do ano (array, possivelmente vazio).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/MetasMeta"
                  }
                },
                "example": [
                  {
                    "id": "a3e5c7b9-1d20-4f46-8b68-0c2e4a6d8f10",
                    "produtoId": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                    "produtoNome": "Produto Exemplo",
                    "classe": "NOVO",
                    "ano": 2026,
                    "mes": 1,
                    "quantidadeMeta": 2,
                    "valorSaasMeta": 3000,
                    "valorServicoMeta": 10000
                  },
                  {
                    "id": "b4f6d8ca-2e31-4057-9c79-1d3f5b7e9a21",
                    "produtoId": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                    "produtoNome": "Produto Exemplo",
                    "classe": "NOVO",
                    "ano": 2026,
                    "mes": 2,
                    "quantidadeMeta": 2,
                    "valorSaasMeta": 3000,
                    "valorServicoMeta": 10000
                  }
                ]
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/metas/realizados": {
      "get": {
        "tags": [
          "Metas"
        ],
        "operationId": "listarMetasRealizados",
        "summary": "Vendas realizadas que contam para as metas (lista paginada)",
        "description": "Lista, uma a uma, as **vendas realizadas** que alimentam o \"realizado\" das metas. É o detalhe do que `GET /metas/dashboard` soma, com os mesmos filtros\ne a mesma regra, de modo que os totais reconciliam.\n\n## De onde vem o realizado\nFonte exclusiva: os **formulários comerciais** (`formulario_comercial`). Um formulário entra quando:\n- está `CONCLUIDO`;\n- é **venda do Comercial** (foto gravada na conclusão; sem foto, vale o cadastro atual do vendedor: vendedor não comissionado fica de fora);\n- tem **produto principal** (o produto marcado como principal; ele define `produtoId`/`produtoNome`);\n- o tipo de projeto o classifica como `ADITIVO` (tipo \"Aditivo\") ou `NOVO` (\"Implantação e Consultoria\" ou \"CS\"; nomes comparados sem acento,\n  sem diferenciar maiúsculas e sem espaços nas pontas). Formulário sem classe (ex.: \"Serviço de base\") nunca entra, mesmo sem filtro de `classe`.\n\nCada formulário pode gerar até **dois eventos**, conforme `metrica`: `MRR` (usa a data de assinatura do SaaS e o valor mensal do SaaS, `valorSaasMensal` > 0)\ne `SERVICO` (usa a data de assinatura do serviço e o valor de serviço, `valorServico` > 0). O evento só existe se o valor correspondente for positivo.\nO ano/mês é o da **data de assinatura de cada evento** (janela UTC `[início, fim)`), não da data de criação.\nNesta lista, um formulário com os dois eventos aparece **uma única vez** (o evento de data mais recente; em empate, `MRR`).\n`total` conta formulários distintos. No dashboard, os mesmos formulários são contados em cada métrica (podem entrar em MRR e em serviço).\n\n## Unidades\n`valorSaasMensal`, `valorServico`, `valorComissao` e `os.valorTotal` são **números JSON em reais**, `null` quando ausentes. As datas são instantes ISO 8601 em UTC\n(datas de assinatura normalmente à meia-noite UTC). `dataVenda` e `dataAssinatura` são iguais e correspondem à data de referência do evento exibido.\n`os` é a primeira ordem de serviço paga ligada à comissão da venda (`null` se não houver); `os.pagamentoStatus`: `PAGA`, `PARCIAL`, `PENDENTE` ou `SEM_RECEBIVEL`.\n\n## Filtros e paginação\n`ano` obrigatório; `mes`, `vendedorId`, `produtoId`, `classe`, `metrica` opcionais. Ordenação fixa: `dataVenda` desc, `id` desc. `page` a partir de 1, `pageSize` padrão 20, máximo 200;\n`pagination.totalPages = ceil(total / pageSize)`.\n\n## Permissão e escopo\nExige a capacidade **`LER_METAS`**: na regra legada, `type` do usuário igual a `ADMIN` ou `COMERCIAL` (papel de dono/admin ou gestor de outro setor não basta); com o acesso por setor ligado, dono/administrador ou membro/gestor do setor Comercial.\nSem ela: `403`. Além disso, o **escopo de dados** (`META`) restringe as linhas: escopo total vê todos os vendedores; usuário vinculado a um vendedor vê **somente as vendas dele**\n(`vendedorId` do filtro só reduz ainda mais); sem escopo, a lista vem vazia.\nEscopo total: administrador, gestor e qualquer usuário fora do Comercial (com `ESCOPO_COMERCIAL` desligado, o padrão); só o usuário do Comercial que não é\ngestor fica restrito ao seu vendedor. Com `ACESSO_POR_SETOR` e `ESCOPO_COMERCIAL` ligados: Dono/Admin e gestor do Comercial veem tudo; membro do Comercial\nvê só o seu vendedor.\n",
        "parameters": [
          {
            "name": "ano",
            "in": "query",
            "required": true,
            "description": "Ano das assinaturas (inteiro maior ou igual a 1).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 2026
          },
          {
            "name": "mes",
            "in": "query",
            "required": false,
            "description": "Mês das assinaturas (1 a 12). Sem ele, o ano inteiro.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            },
            "example": 11
          },
          {
            "name": "vendedorId",
            "in": "query",
            "required": false,
            "description": "UUID do vendedor da venda.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "0b8f6a52-3c41-4d0e-9a77-5f2d1c6e8a10"
          },
          {
            "name": "produtoId",
            "in": "query",
            "required": false,
            "description": "UUID do produto principal da venda.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1"
          },
          {
            "name": "classe",
            "in": "query",
            "required": false,
            "description": "Classe da venda.",
            "schema": {
              "type": "string",
              "enum": [
                "ADITIVO",
                "NOVO"
              ]
            },
            "example": "NOVO"
          },
          {
            "name": "metrica",
            "in": "query",
            "required": false,
            "description": "Só eventos desta métrica: `MRR` (recorrente) ou `SERVICO` (implantação/serviço).",
            "schema": {
              "type": "string",
              "enum": [
                "MRR",
                "SERVICO"
              ]
            },
            "example": "MRR"
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Número da página, a partir de 1.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "example": 1
          },
          {
            "name": "pageSize",
            "in": "query",
            "required": false,
            "description": "Itens por página (1 a 200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 20
            },
            "example": 20
          }
        ],
        "responses": {
          "200": {
            "description": "Página de vendas realizadas.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetasRealizadosLista"
                },
                "example": {
                  "data": [
                    {
                      "id": "c5a7e9d1-3f42-4168-8a8a-2e4c6b8d0f32",
                      "origem": "FORMULARIO",
                      "status": "CONCLUIDO",
                      "classe": "NOVO",
                      "vendedorId": "0b8f6a52-3c41-4d0e-9a77-5f2d1c6e8a10",
                      "vendedorNome": "Vendedor Exemplo",
                      "clienteNome": "Empresa Alfa Ltda",
                      "projetoId": "4f0a8c36-1e92-4d7b-8c05-9a3e6b1d2f70",
                      "projetoNome": "Implantação Alfa",
                      "produtoId": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                      "produtoNome": "Produto Exemplo",
                      "tipoProjetoId": "8e2d4a60-9b13-4c75-a0f8-1b6c3d7e9f42",
                      "tipoProjetoNome": "Implantação",
                      "metrica": "MRR",
                      "dataVenda": "2026-11-05T00:00:00.000Z",
                      "dataAssinatura": "2026-11-05T00:00:00.000Z",
                      "dataAssinaturaSaas": "2026-11-05T00:00:00.000Z",
                      "dataAssinaturaServico": "2026-11-03T00:00:00.000Z",
                      "valorSaasMensal": 1500,
                      "valorServico": 8000,
                      "valorComissao": 450.75,
                      "os": {
                        "id": 1234,
                        "numOs": "000123",
                        "valorTotal": 8000,
                        "dataFaturamento": "2026-11-10T00:00:00.000Z",
                        "dataVencimento": "2026-11-25T00:00:00.000Z",
                        "pagamentoStatus": "PENDENTE",
                        "dataPagamento": null
                      }
                    }
                  ],
                  "pagination": {
                    "page": 1,
                    "pageSize": 20,
                    "total": 1,
                    "totalPages": 1
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/metas/dashboard": {
      "get": {
        "tags": [
          "Metas"
        ],
        "operationId": "obterMetasDashboard",
        "summary": "Painel anual de metas x realizado, por produto e classe",
        "description": "Para um ano, cruza as **metas** cadastradas com o **realizado** e devolve uma linha por (produto, classe), cada uma com os 12 meses e os 4 trimestres. É a\npergunta \"estamos batendo as metas?\".\n\n## Como é calculado\n- **Meta** (por mês): de `GET /metas`. `quantidadeMrr` e `quantidadeServico` recebem a mesma `quantidadeMeta`; `saas` = `valorSaasMeta` (MRR em reais); `servico` = `valorServicoMeta`\n  (reais). Mês sem meta cadastrada = tudo 0.\n- **Realizado** (por mês): mesma fonte e regras de `GET /metas/realizados` (formulários comerciais concluídos, venda do Comercial, produto principal, classe ADITIVO/NOVO; ver aquela rota).\n  `quantidadeMrr` = nº de vendas com assinatura SaaS no mês e MRR positivo; `saas` = soma do MRR mensal dessas vendas (reais); `quantidadeServico` = nº de vendas com assinatura de serviço no mês e valor\n  de serviço positivo; `servico` = soma desses valores (reais). O mês é o da data de assinatura (UTC). Não há ligação com faturamento ou pagamento: venda assinada e não paga também conta.\n- **`atingimento`**: percentual **de 0 a 100 ou mais** (não é fração 0-1) = `realizado / meta × 100`, arredondado a 1 casa decimal, em cada uma das 4 medidas. **Meta 0 resulta em 0** (não nulo, não infinito),\n  mesmo que haja realizado.\n- **Trimestres** (`tri` 1 a 4 = jan-mar, abr-jun, jul-set, out-dez): soma dos 3 meses de meta e de realizado; o `atingimento` é recalculado sobre as somas (não é média dos meses).\n- Só aparecem linhas (produto, classe) que tenham meta **ou** realizado no ano; cada linha sempre traz os 12 meses e 4 trimestres (zerados quando vazios). Ordenação: nome do produto, depois classe.\n  Realizado de formulário sem classe ou sem produto principal não aparece.\n\n## Permissão e escopo\nQualquer usuário autenticado: não há checagem de capacidade nesta rota (diferente de `/metas/realizados`, que dá `403`). As **metas** são globais do tenant e aparecem para todos; o **realizado** respeita\no escopo de dados `META`: escopo total soma todos os vendedores; usuário vinculado a um vendedor soma só as vendas dele; sem escopo, o realizado vem zerado (não há erro). Dois usuários podem, portanto, ver\no mesmo `meta` com `realizado` e `atingimento` diferentes.\n\nEscopo total do realizado: administrador, gestor e qualquer usuário fora do Comercial (com `ESCOPO_COMERCIAL` desligado, o padrão). Só o usuário do\nComercial que não é gestor fica restrito ao seu vendedor. Com `ACESSO_POR_SETOR` e `ESCOPO_COMERCIAL` ligados: Dono/Admin e gestor do Comercial veem\ntudo; membro do Comercial vê só o seu vendedor; os demais, nada.\n",
        "parameters": [
          {
            "name": "ano",
            "in": "query",
            "required": true,
            "description": "Ano do painel (inteiro maior ou igual a 1).",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "example": 2026
          }
        ],
        "responses": {
          "200": {
            "description": "Painel do ano.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetasDashboard"
                },
                "example": {
                  "ano": 2026,
                  "linhas": [
                    {
                      "produtoId": "5a1e9c47-2b60-4f8d-8e13-6d0a4b92c7f1",
                      "produtoNome": "Produto Exemplo",
                      "classe": "NOVO",
                      "meses": [
                        {
                          "mes": 1,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 2,
                            "saas": 3200,
                            "quantidadeServico": 1,
                            "servico": 9000
                          },
                          "atingimento": {
                            "quantidadeMrr": 100,
                            "saas": 106.7,
                            "quantidadeServico": 50,
                            "servico": 90
                          }
                        },
                        {
                          "mes": 2,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 1,
                            "saas": 1500,
                            "quantidadeServico": 2,
                            "servico": 12000
                          },
                          "atingimento": {
                            "quantidadeMrr": 50,
                            "saas": 50,
                            "quantidadeServico": 100,
                            "servico": 120
                          }
                        },
                        {
                          "mes": 3,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 3,
                            "saas": 4500,
                            "quantidadeServico": 3,
                            "servico": 15000
                          },
                          "atingimento": {
                            "quantidadeMrr": 150,
                            "saas": 150,
                            "quantidadeServico": 150,
                            "servico": 150
                          }
                        },
                        {
                          "mes": 4,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 5,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 6,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 7,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 8,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 9,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 10,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 11,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "mes": 12,
                          "meta": {
                            "quantidadeMrr": 2,
                            "saas": 3000,
                            "quantidadeServico": 2,
                            "servico": 10000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        }
                      ],
                      "trimestres": [
                        {
                          "tri": 1,
                          "meta": {
                            "quantidadeMrr": 6,
                            "saas": 9000,
                            "quantidadeServico": 6,
                            "servico": 30000
                          },
                          "realizado": {
                            "quantidadeMrr": 6,
                            "saas": 9200,
                            "quantidadeServico": 6,
                            "servico": 36000
                          },
                          "atingimento": {
                            "quantidadeMrr": 100,
                            "saas": 102.2,
                            "quantidadeServico": 100,
                            "servico": 120
                          }
                        },
                        {
                          "tri": 2,
                          "meta": {
                            "quantidadeMrr": 6,
                            "saas": 9000,
                            "quantidadeServico": 6,
                            "servico": 30000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "tri": 3,
                          "meta": {
                            "quantidadeMrr": 6,
                            "saas": 9000,
                            "quantidadeServico": 6,
                            "servico": 30000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        },
                        {
                          "tri": 4,
                          "meta": {
                            "quantidadeMrr": 6,
                            "saas": 9000,
                            "quantidadeServico": 6,
                            "servico": 30000
                          },
                          "realizado": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          },
                          "atingimento": {
                            "quantidadeMrr": 0,
                            "saas": 0,
                            "quantidadeServico": 0,
                            "servico": 0
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Token obtido em `POST /login`."
      }
    },
    "schemas": {
      "Erro": {
        "type": "object",
        "description": "Corpo padrão de erro de toda a API.",
        "required": [
          "status",
          "path",
          "message"
        ],
        "properties": {
          "status": {
            "type": "integer",
            "description": "Mesmo valor do status HTTP."
          },
          "path": {
            "type": "string",
            "description": "Caminho chamado."
          },
          "message": {
            "description": "Mensagem legível (string ou lista de strings).",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "code": {
            "type": "string",
            "description": "Código estável de erro de domínio, quando houver (use este, não a mensagem, para decidir o que fazer)."
          },
          "details": {
            "description": "Dados extras do erro de domínio, quando houver."
          }
        }
      },
      "HorasPorMesItem": {
        "type": "object",
        "description": "Uma linha por mês com horas realizadas, meta e produtividade.",
        "required": [
          "ano",
          "mesNumero",
          "mes",
          "minutosRealizados",
          "hoursRealizadas",
          "metaTotalHoraMes",
          "minutosMeta",
          "metaHoras",
          "produtividade",
          "consultoresNaMeta",
          "baseMeta"
        ],
        "properties": {
          "ano": {
            "type": "integer",
            "example": 2026
          },
          "mesNumero": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "example": 3
          },
          "mes": {
            "type": "string",
            "description": "Rótulo `Mon. de aaaa` com a abreviatura do mês em INGLÊS (Jan., Feb., Mar., Apr., May., Jun., Jul., Aug., Sep., Oct., Nov., Dec.), ex.: `Mar. de 2026`. Use `ano` e `mesNumero` para lógica; este texto é só rótulo.",
            "example": "Mar. de 2026"
          },
          "minutosRealizados": {
            "type": "integer",
            "description": "Soma das durações das agendas REALIZADAS no mês, em minutos.",
            "example": 9150
          },
          "hoursRealizadas": {
            "type": "string",
            "pattern": "^\\d{2,}:\\d{2}$",
            "description": "Mesmo valor de `minutosRealizados` em `HH:MM` (note o nome do campo: `hoursRealizadas`, com \"s\" e em inglês).",
            "example": "152:30"
          },
          "metaTotalHoraMes": {
            "type": [
              "number",
              "null"
            ],
            "description": "Meta de horas POR PESSOA no mês, cadastrada na tabela de horas úteis da organização (`horas_uteis.meta_total_hora_mes`). Nulo se não há linha cadastrada para o mês.",
            "example": 160
          },
          "minutosMeta": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Meta total do mês em minutos = `metaTotalHoraMes` x 60 x `consultoresNaMeta`, arredondado. Nulo se não há meta ou a base de pessoas é nula/zero.",
            "example": 96000
          },
          "metaHoras": {
            "type": [
              "string",
              "null"
            ],
            "description": "`minutosMeta` em `HH:MM`.",
            "example": "1600:00"
          },
          "produtividade": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentual **0-100+** (pode passar de 100) = `minutosRealizados / minutosMeta x 100`, 2 casas. Nulo sem meta.",
            "example": 9.53
          },
          "consultoresNaMeta": {
            "type": "number",
            "description": "Quantidade de pessoas que entram na meta do mês, com 2 casas (fração = horas úteis do dia 1 até a desativação ÷ horas úteis do mês, só no mês da desativação; 1 nos meses anteriores, 0 nos posteriores e para inativo sem data de desativação). 0 quando não há ninguém na base (aí `minutosMeta` fica nulo).",
            "example": 10
          },
          "baseMeta": {
            "type": "string",
            "enum": [
              "SETOR_CONSULTORIA",
              "PESSOAS_FILTRADAS"
            ],
            "description": "`SETOR_CONSULTORIA`: sem filtro de pessoa, a base é o setor Consultoria. `PESSOAS_FILTRADAS`: a base são as pessoas dos filtros `usuarioIds`/`setor`; com `consultor` (nome), a base é o número de NOMES distintos informados (homônimos contam 1, sem fração de desativação).",
            "example": "SETOR_CONSULTORIA"
          }
        }
      },
      "HorasPorConsultorMesItem": {
        "type": "object",
        "description": "Uma linha por (mês, pessoa).",
        "required": [
          "ano",
          "mesNumero",
          "usuarioId",
          "consultor",
          "cor",
          "mes",
          "minutosRealizados",
          "horas",
          "metaTotalHoraMes",
          "quantidadeConsultores",
          "minutosMetaConsultor",
          "metaHorasConsultor"
        ],
        "properties": {
          "ano": {
            "type": "integer",
            "example": 2026
          },
          "mesNumero": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "example": 3
          },
          "usuarioId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário responsável da linha. Nulo quando a agenda está reservada num consultor que não tem usuário vinculado.",
            "example": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
          },
          "consultor": {
            "type": "string",
            "description": "Nome da pessoa (nome do usuário; reserva no nome do consultor). Pode repetir entre pessoas diferentes homônimas: distinga por `usuarioId`.",
            "example": "Consultor Exemplo"
          },
          "cor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cor hexadecimal da pessoa no calendário.",
            "example": "#3B82F6"
          },
          "mes": {
            "type": "string",
            "description": "Rótulo `Mon. de aaaa` com mês em inglês.",
            "example": "Mar. de 2026"
          },
          "minutosRealizados": {
            "type": "integer",
            "description": "Minutos REALIZADOS da pessoa no mês.",
            "example": 7200
          },
          "horas": {
            "type": "string",
            "description": "`minutosRealizados` em `HH:MM`.",
            "example": "120:00"
          },
          "metaTotalHoraMes": {
            "type": [
              "number",
              "null"
            ],
            "description": "Meta mensal de horas POR PESSOA (tabela de horas úteis). Nulo sem cadastro.",
            "example": 160
          },
          "quantidadeConsultores": {
            "type": "number",
            "description": "Pessoas na meta do mês (mesmo valor de `consultoresNaMeta` de `horas-por-mes`), 2 casas. 0 quando não há ninguém na base (aí `minutosMetaConsultor` fica nulo).",
            "example": 10
          },
          "minutosMetaConsultor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Meta individual do mês em minutos = `metaTotalHoraMes` x 60 x fator da pessoa (1 ativa; fração = horas úteis do dia 1 até a desativação ÷ horas úteis do mês, só no mês da desativação; 1 nos meses anteriores, 0 nos posteriores e para inativo sem data de desativação). Nulo sem meta ou quando a pessoa está fora da meta (fator 0), mesmo que tenha horas realizadas.",
            "example": 9600
          },
          "metaHorasConsultor": {
            "type": [
              "string",
              "null"
            ],
            "description": "`minutosMetaConsultor` em `HH:MM`.",
            "example": "160:00"
          }
        }
      },
      "HorasPorTipoAgendaItem": {
        "type": "object",
        "required": [
          "usuarioId",
          "consultor",
          "tipoAgenda",
          "minutosRealizados",
          "horas"
        ],
        "properties": {
          "usuarioId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Nulo quando a agenda está reservada num consultor sem usuário vinculado.",
            "example": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
          },
          "consultor": {
            "type": "string",
            "description": "Nome da pessoa; `Sem consultor` quando não há nome.",
            "example": "Consultor Exemplo"
          },
          "tipoAgenda": {
            "type": "string",
            "description": "Tipo da agenda. Agenda sem tipo aparece como `CLIENTE`.",
            "enum": [
              "INTERNO",
              "CLIENTE",
              "FERIAS",
              "ESTUDO",
              "PESSOAL",
              "COMERCIAL",
              "CS"
            ],
            "example": "CLIENTE"
          },
          "minutosRealizados": {
            "type": "integer",
            "description": "Minutos REALIZADOS da pessoa nesse tipo, no período inteiro.",
            "example": 4800
          },
          "horas": {
            "type": "string",
            "description": "`minutosRealizados` em `HH:MM`.",
            "example": "80:00"
          }
        }
      },
      "HorasTabelaMesItem": {
        "type": "object",
        "description": "ATENÇÃO aos tipos: `ano` e `mes_numero` chegam como STRING (numérico do Postgres serializado como texto) e `mes` é o nome do mês em INGLÊS.",
        "required": [
          "ano",
          "mes",
          "mes_numero",
          "horas"
        ],
        "properties": {
          "ano": {
            "type": "string",
            "description": "Ano, como texto.",
            "example": "2026"
          },
          "mes": {
            "type": "string",
            "description": "Nome do mês em inglês completo (January...December).",
            "example": "March"
          },
          "mes_numero": {
            "type": "string",
            "description": "Número do mês (1-12), como texto, sem zero à esquerda.",
            "example": "3"
          },
          "horas": {
            "type": "string",
            "description": "Horas realizadas no mês em `HH:MM`.",
            "example": "152:30"
          }
        }
      },
      "HorasConsultorTotalItem": {
        "type": "object",
        "required": [
          "usuarioId",
          "consultor",
          "horas"
        ],
        "properties": {
          "usuarioId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Nulo quando a agenda está reservada num consultor sem usuário vinculado (nesse caso homônimos aparecem em linhas separadas).",
            "example": "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10"
          },
          "consultor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome da pessoa (usuário, com reserva no consultor).",
            "example": "Consultor Exemplo"
          },
          "horas": {
            "type": "string",
            "description": "Horas realizadas em `HH:MM`.",
            "example": "120:00"
          }
        }
      },
      "HorasGraficoMesAtual": {
        "type": "object",
        "required": [
          "mesAtual",
          "consultores"
        ],
        "properties": {
          "mesAtual": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1,
            "items": {
              "type": "string"
            },
            "description": "Lista de um único item: nome do mês corrente em português minúsculo (`novembro`), pelo relógio do servidor.",
            "example": [
              "novembro"
            ]
          },
          "consultores": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HorasConsultorTotalItem"
            },
            "description": "Uma linha por pessoa, ordenada por `horas` DECRESCENTE **comparando como texto** (não numericamente: `\"100:00\"` fica depois de `\"99:00\"`), depois por nome."
          }
        }
      },
      "HorasTipoServicoItem": {
        "type": "object",
        "required": [
          "ano",
          "tipoServico",
          "horas"
        ],
        "properties": {
          "ano": {
            "type": "string",
            "description": "Ano da data da agenda, como TEXTO (numérico do Postgres).",
            "example": "2026"
          },
          "tipoServico": {
            "type": [
              "string",
              "null"
            ],
            "description": "Valor do campo `tipoServico` da agenda; nulo quando não preenchido.",
            "example": "Implantação"
          },
          "horas": {
            "type": "string",
            "description": "Horas realizadas em `HH:MM`.",
            "example": "210:15"
          }
        }
      },
      "HorasPorcentagemTipoServicoItem": {
        "type": "object",
        "required": [
          "tipoServico",
          "contagem",
          "porcentagem"
        ],
        "properties": {
          "tipoServico": {
            "type": [
              "string",
              "null"
            ],
            "description": "Valor do campo `tipoServico` da agenda; nulo quando não preenchido.",
            "example": "Implantação"
          },
          "contagem": {
            "type": "integer",
            "description": "NÚMERO DE AGENDAS realizadas (não horas) nesse tipo.",
            "example": 42
          },
          "porcentagem": {
            "type": "string",
            "description": "Participação percentual **0-100** da `contagem` sobre o total, 2 casas, serializada como TEXTO (numérico do Postgres), sem zeros à direita desnecessários (`\"100\"`, `\"66.67\"`). Use `Number()` para converter.",
            "example": "66.67"
          }
        }
      },
      "DashboardPorcentagens": {
        "type": "object",
        "description": "Percentuais **0-100** como TEXTO com 2 casas decimais (`\"12.50\"`). Base = `capacidadeHoras`; sem capacidade, tudo `\"0.00\"` e `porcentagemLivre` = `\"100.00\"`.",
        "required": [
          "porcentagemConfirmadas",
          "porcentagemPreReservadas",
          "porcentagemReservado",
          "porcentagemRealizadas",
          "porcentagemCanceladas",
          "porcentagemLivre"
        ],
        "properties": {
          "porcentagemConfirmadas": {
            "type": "string",
            "example": "12.50"
          },
          "porcentagemPreReservadas": {
            "type": "string",
            "example": "3.10"
          },
          "porcentagemReservado": {
            "type": "string",
            "example": "8.00"
          },
          "porcentagemRealizadas": {
            "type": "string",
            "example": "40.25"
          },
          "porcentagemCanceladas": {
            "type": "string",
            "example": "5.00"
          },
          "porcentagemLivre": {
            "type": "string",
            "description": "100 menos a soma dos demais, nunca negativa.",
            "example": "31.15"
          }
        }
      },
      "DashboardHorasPorStatus": {
        "type": "object",
        "description": "Horas (NÚMERO, em horas, 2 casas) de cada status; as mesmas que sustentam os percentuais.",
        "required": [
          "confirmadas",
          "preReservadas",
          "reservado",
          "realizadas",
          "canceladas",
          "livre"
        ],
        "properties": {
          "confirmadas": {
            "type": "number",
            "example": 20
          },
          "preReservadas": {
            "type": "number",
            "example": 4.96
          },
          "reservado": {
            "type": "number",
            "example": 12.8
          },
          "realizadas": {
            "type": "number",
            "example": 64.4
          },
          "canceladas": {
            "type": "number",
            "example": 8
          },
          "livre": {
            "type": "number",
            "description": "Capacidade que sobra, nunca negativa; 0 sem capacidade.",
            "example": 49.84
          }
        }
      },
      "DashboardCard": {
        "type": "object",
        "allOf": [
          {
            "$ref": "#/components/schemas/DashboardPorcentagens"
          },
          {
            "type": "object",
            "required": [
              "horasPorStatus",
              "capacidadeHoras"
            ],
            "properties": {
              "horasPorStatus": {
                "$ref": "#/components/schemas/DashboardHorasPorStatus"
              },
              "capacidadeHoras": {
                "type": "number",
                "description": "Capacidade do período em HORAS = horas úteis do período x número de pessoas com alguma agenda no período. É a base (100%) dos percentuais.",
                "example": 160
              }
            }
          }
        ]
      },
      "DashboardResumoMes": {
        "type": "object",
        "required": [
          "mesAno",
          "minutosRealizados",
          "minutosConfirmadosARealizar",
          "aguardandoAprovacao",
          "conferenciasPendentes",
          "totalAgendamentos",
          "minutosAgendados"
        ],
        "properties": {
          "mesAno": {
            "type": "string",
            "description": "Eco do parâmetro `mesAno` recebido, sem normalizar.",
            "example": "03-2026"
          },
          "minutosRealizados": {
            "type": "integer",
            "description": "MINUTOS de agendas com status REALIZADO que começam no mês.",
            "example": 7440
          },
          "minutosConfirmadosARealizar": {
            "type": "integer",
            "description": "MINUTOS de agendas com status CONFIRMADO que começam no mês.",
            "example": 1800
          },
          "aguardandoAprovacao": {
            "type": "integer",
            "description": "QUANTIDADE (não minutos) de agendas CONFIRMADAS com aprovação em `AGUARDANDO`.",
            "example": 6
          },
          "conferenciasPendentes": {
            "type": "integer",
            "description": "QUANTIDADE de agendas REALIZADAS ainda sem conferência (`conferencia = false`).",
            "example": 14
          },
          "totalAgendamentos": {
            "type": "integer",
            "description": "QUANTIDADE de agendas do mês que NÃO estão canceladas (status nulo ou diferente de CANCELADO; inclui PENDENTE, reservadas, etc.).",
            "example": 132
          },
          "minutosAgendados": {
            "type": "integer",
            "description": "MINUTOS somados dos status PRE_RESERVAR, RESERVADO, CONFIRMADO e REALIZADO.",
            "example": 12600
          }
        }
      },
      "DashboardSerie": {
        "type": "object",
        "description": "Uma série do gráfico. As quatro listas (`ordem`, `usuarioIds`, `data`, `horas`) têm o MESMO tamanho e são paralelas por posição: o item `i` de cada uma descreve a mesma pessoa.",
        "required": [
          "ordem",
          "usuarioIds",
          "name",
          "data",
          "horas"
        ],
        "properties": {
          "ordem": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Nome de cada pessoa (coluna), ordenado por nome. Pode repetir entre homônimos.",
            "example": [
              "Consultor Exemplo",
              "Outra Consultora Exemplo"
            ]
          },
          "usuarioIds": {
            "type": "array",
            "items": {
              "type": [
                "string",
                "null"
              ],
              "format": "uuid"
            },
            "description": "Usuário de cada coluna, na mesma ordem de `ordem`; nulo quando a agenda está reservada num consultor sem usuário.",
            "example": [
              "5d1b6c2e-8f3a-4c71-9b0e-2a7d4e6f1a10",
              null
            ]
          },
          "name": {
            "type": "string",
            "description": "Nome da série.",
            "enum": [
              "Cancelados",
              "Total Agendamentos"
            ],
            "example": "Cancelados"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Horas (decimais, SEM arredondar) de cada coluna.",
            "example": [
              12.5,
              3.333333333333333
            ]
          },
          "horas": {
            "type": "array",
            "items": {
              "type": "number"
            },
            "description": "Mesmas horas de `data`, arredondadas a 2 casas.",
            "example": [
              12.5,
              3.33
            ]
          }
        }
      },
      "DashboardIndiceCancelamento": {
        "type": "object",
        "required": [
          "resultadoFinal"
        ],
        "properties": {
          "resultadoFinal": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "description": "Sempre duas séries, nesta ordem: `Cancelados` e `Total Agendamentos`.",
            "items": {
              "$ref": "#/components/schemas/DashboardSerie"
            }
          }
        }
      },
      "InadimplenciaResumoResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "cards": {
                "type": "object",
                "properties": {
                  "quantidadeItensAtrasados": {
                    "type": "integer",
                    "description": "Quantidade de itens em atraso. Fonte canônica: número de **parcelas/títulos** em atraso (não de linhas agrupadas por OS; por isso pode ser maior que `pagination.total` de `/itens`). Fonte legada: número de OS."
                  },
                  "valorTotalEmAtraso": {
                    "type": "number",
                    "description": "Soma do saldo em atraso, em reais."
                  },
                  "maiorValorEmAtraso": {
                    "type": "number",
                    "description": "Maior valor individual entre os itens em atraso (uma parcela na fonte canônica; uma OS na legada), em reais. 0 quando não há itens."
                  },
                  "clientesInadimplentes": {
                    "type": "integer",
                    "description": "Quantidade de clientes distintos com item em atraso. Fonte canônica: título sem cliente vinculado conta como um \"cliente\" próprio por título (id sintético `OMIE:<app>:<codTitulo>`)."
                  }
                },
                "required": [
                  "quantidadeItensAtrasados",
                  "valorTotalEmAtraso",
                  "maiorValorEmAtraso",
                  "clientesInadimplentes"
                ]
              },
              "graficos": {
                "type": "object",
                "properties": {
                  "porDepartamento": {
                    "type": "array",
                    "description": "Agrupado por departamento, ordenado por valor decrescente e depois por nome.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "departamento": {
                          "type": "string",
                          "description": "Nome/descrição do departamento Omie, ou \"Sem departamento\"."
                        },
                        "quantidadeItensAtrasados": {
                          "type": "integer",
                          "description": "Itens em atraso do departamento."
                        },
                        "valorTotalEmAtraso": {
                          "type": "number",
                          "description": "Valor em atraso do departamento, em reais."
                        }
                      },
                      "required": [
                        "departamento",
                        "quantidadeItensAtrasados",
                        "valorTotalEmAtraso"
                      ]
                    }
                  },
                  "topDevedores": {
                    "type": "array",
                    "description": "Os 10 clientes com maior valor em atraso (empate por nome).",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clienteId": {
                          "type": "string",
                          "description": "Id do cliente (UUID) ou id sintético `OMIE:<app>:<codTitulo>` para título sem cliente vinculado."
                        },
                        "cliente": {
                          "type": "string",
                          "description": "Nome do cliente."
                        },
                        "quantidadeItensAtrasados": {
                          "type": "integer",
                          "description": "Itens em atraso do cliente."
                        },
                        "valorTotalEmAtraso": {
                          "type": "number",
                          "description": "Valor em atraso do cliente, em reais."
                        }
                      },
                      "required": [
                        "clienteId",
                        "cliente",
                        "quantidadeItensAtrasados",
                        "valorTotalEmAtraso"
                      ]
                    }
                  },
                  "evolucaoPorMesVencimento": {
                    "type": "array",
                    "description": "Itens agrupados pelo mês de vencimento, em ordem cronológica. Só meses com itens aparecem.",
                    "items": {
                      "type": "object",
                      "properties": {
                        "mes": {
                          "type": "string",
                          "description": "Mês de vencimento `YYYY-MM` (UTC).",
                          "pattern": "^\\d{4}-\\d{2}$"
                        },
                        "label": {
                          "type": "string",
                          "description": "Rótulo `MM/YYYY`."
                        },
                        "quantidadeItensAtrasados": {
                          "type": "integer",
                          "description": "Itens em atraso que venceram no mês."
                        },
                        "valorTotalEmAtraso": {
                          "type": "number",
                          "description": "Valor em atraso dos itens que venceram no mês, em reais."
                        }
                      },
                      "required": [
                        "mes",
                        "label",
                        "quantidadeItensAtrasados",
                        "valorTotalEmAtraso"
                      ]
                    }
                  },
                  "matrizHistoricaAnoMes": {
                    "type": "object",
                    "properties": {
                      "meses": {
                        "type": "array",
                        "description": "Sempre os 12 meses fixos, Jan a Dez.",
                        "items": {
                          "type": "object",
                          "properties": {
                            "mes": {
                              "type": "integer",
                              "description": "Número do mês (1-12).",
                              "minimum": 1,
                              "maximum": 12
                            },
                            "key": {
                              "type": "string",
                              "description": "Chave `01`..`12`.",
                              "enum": [
                                "01",
                                "02",
                                "03",
                                "04",
                                "05",
                                "06",
                                "07",
                                "08",
                                "09",
                                "10",
                                "11",
                                "12"
                              ]
                            },
                            "label": {
                              "type": "string",
                              "description": "Abreviação em português: Jan, Fev, Mar, Abr, Mai, Jun, Jul, Ago, Set, Out, Nov, Dez."
                            }
                          },
                          "required": [
                            "mes",
                            "key",
                            "label"
                          ]
                        }
                      },
                      "anos": {
                        "type": "array",
                        "description": "Uma linha por ano de vencimento com itens, em ordem crescente.",
                        "items": {
                          "type": "object",
                          "properties": {
                            "ano": {
                              "type": "integer",
                              "description": "Ano de vencimento."
                            },
                            "totalAno": {
                              "type": "number",
                              "description": "Soma do ano, em reais."
                            },
                            "valoresPorMes": {
                              "type": "object",
                              "description": "Valor em atraso por mês de vencimento; meses sem itens valem 0.",
                              "properties": {
                                "10": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "11": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "12": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "01": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "02": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "03": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "04": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "05": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "06": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "07": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "08": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                },
                                "09": {
                                  "type": "number",
                                  "description": "Valor em atraso que venceu neste mês, em reais."
                                }
                              },
                              "required": [
                                "01",
                                "02",
                                "03",
                                "04",
                                "05",
                                "06",
                                "07",
                                "08",
                                "09",
                                "10",
                                "11",
                                "12"
                              ]
                            }
                          },
                          "required": [
                            "ano",
                            "totalAno",
                            "valoresPorMes"
                          ]
                        }
                      }
                    },
                    "required": [
                      "meses",
                      "anos"
                    ]
                  },
                  "serieHistoricaAnoMes": {
                    "type": "array",
                    "description": "Mesma informação da matriz, em série por ano (12 pontos por ano).",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ano": {
                          "type": "integer",
                          "description": "Ano de vencimento."
                        },
                        "totalAno": {
                          "type": "number",
                          "description": "Soma do ano, em reais."
                        },
                        "pontos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "mes": {
                                "type": "integer",
                                "description": "Número do mês (1-12)."
                              },
                              "key": {
                                "type": "string",
                                "description": "Chave `01`..`12`.",
                                "enum": [
                                  "01",
                                  "02",
                                  "03",
                                  "04",
                                  "05",
                                  "06",
                                  "07",
                                  "08",
                                  "09",
                                  "10",
                                  "11",
                                  "12"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "Abreviação do mês."
                              },
                              "valorTotalEmAtraso": {
                                "type": "number",
                                "description": "Valor em atraso que venceu no mês, em reais."
                              }
                            },
                            "required": [
                              "mes",
                              "key",
                              "label",
                              "valorTotalEmAtraso"
                            ]
                          }
                        }
                      },
                      "required": [
                        "ano",
                        "totalAno",
                        "pontos"
                      ]
                    }
                  }
                },
                "required": [
                  "porDepartamento",
                  "topDevedores",
                  "evolucaoPorMesVencimento",
                  "matrizHistoricaAnoMes",
                  "serieHistoricaAnoMes"
                ]
              }
            },
            "required": [
              "cards",
              "graficos"
            ]
          }
        },
        "required": [
          "data"
        ]
      },
      "InadimplenciaParcela": {
        "type": "object",
        "properties": {
          "codTituloOmie": {
            "type": "string",
            "description": "Código do título no Omie."
          },
          "numero": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número da parcela (por exemplo `1/3`), quando existe."
          },
          "vencimento": {
            "type": "string",
            "description": "Vencimento da parcela, `YYYY-MM-DD`. Usa a data de vencimento efetiva (a mais tardia entre o vencimento e a previsão de recebimento do Omie).",
            "format": "date"
          },
          "valorOriginal": {
            "type": "number",
            "description": "Valor original da parcela, em reais."
          },
          "valorEmAberto": {
            "type": "number",
            "description": "Saldo em aberto da parcela, em reais."
          },
          "estado": {
            "type": "string",
            "description": "`OPEN` (em aberto) ou `PARTIALLY_PAID` (parcialmente paga).",
            "enum": [
              "OPEN",
              "PARTIALLY_PAID"
            ]
          },
          "ultimaSincronizacao": {
            "type": "string",
            "description": "Instante (ISO 8601, UTC) da última sincronização do recebível com o Omie.",
            "format": "date-time"
          },
          "compoeTotalFiltrado": {
            "type": "boolean",
            "description": "Sempre `true`: toda parcela devolvida compõe o total filtrado."
          }
        },
        "required": [
          "codTituloOmie",
          "numero",
          "vencimento",
          "valorOriginal",
          "valorEmAberto",
          "estado",
          "ultimaSincronizacao",
          "compoeTotalFiltrado"
        ]
      },
      "InadimplenciaContratoDetalhe": {
        "type": "object",
        "properties": {
          "existeVinculo": {
            "type": "boolean",
            "description": "`false` quando a linha não tem contrato vinculado (então `label` = \"Sem contrato vinculado\")."
          },
          "id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id interno do contrato de serviço."
          },
          "numero": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número do contrato (`numCtr`)."
          },
          "label": {
            "type": "string",
            "description": "Rótulo para exibição: número do contrato, senão código Omie, senão `Contrato #<id>`; \"Sem contrato vinculado\" sem vínculo."
          },
          "codCtrOmie": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código do contrato no Omie."
          },
          "situacao": {
            "type": [
              "string",
              "null"
            ],
            "description": "Situação do contrato. Só preenchido na fonte legada; sempre `null` na canônica."
          },
          "descStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descrição do status do contrato. Só na fonte legada."
          },
          "vigenciaInicio": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Início da vigência, `YYYY-MM-DD`. Só na fonte legada."
          },
          "vigenciaFim": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Fim da vigência, `YYYY-MM-DD`. Só na fonte legada."
          },
          "valorTotalMensal": {
            "type": [
              "number",
              "null"
            ],
            "description": "Valor mensal total do contrato, em reais. Só na fonte legada."
          }
        },
        "required": [
          "existeVinculo",
          "id",
          "numero",
          "label",
          "codCtrOmie",
          "situacao",
          "descStatus",
          "vigenciaInicio",
          "vigenciaFim",
          "valorTotalMensal"
        ]
      },
      "InadimplenciaOrdemServicoDetalhe": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Id interno da OS."
          },
          "numero": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número da OS."
          },
          "codOsOmie": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código da OS no Omie."
          },
          "status": {
            "type": "string",
            "description": "Fonte canônica: sempre `OPEN`. Legada: status da OS (`BOLETO_GERADO` ou `FATURADA`)."
          },
          "statusDescricao": {
            "type": "string",
            "description": "Descrição do status. Canônica: \"Recebível em aberto\"."
          },
          "vencimento": {
            "type": "string",
            "description": "Vencimento (o mais antigo da linha), `YYYY-MM-DD`. Usa a data de vencimento efetiva (a mais tardia entre o vencimento e a previsão de recebimento do Omie).",
            "format": "date"
          },
          "dataFaturamento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "Data de faturamento da OS. Sempre `null` na fonte canônica."
          },
          "diasAtraso": {
            "type": "integer",
            "description": "Dias de atraso."
          },
          "valorEmAtraso": {
            "type": "number",
            "description": "Valor em atraso, em reais."
          },
          "quantidadeParcelas": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Canônica: número de parcelas em atraso da linha. Legada: parcelas da OS (sempre 1)."
          },
          "parcelas": {
            "type": "array",
            "description": "Parcelas em atraso (vazio na fonte legada).",
            "items": {
              "$ref": "#/components/schemas/InadimplenciaParcela"
            }
          }
        },
        "required": [
          "id",
          "numero",
          "codOsOmie",
          "status",
          "statusDescricao",
          "vencimento",
          "dataFaturamento",
          "diasAtraso",
          "valorEmAtraso",
          "quantidadeParcelas",
          "parcelas"
        ]
      },
      "InadimplenciaProjetoDetalhe": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id do projeto (UUID)."
          },
          "nome": {
            "type": "string",
            "description": "Nome do projeto."
          },
          "clienteId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do cliente (UUID)."
          },
          "cliente": {
            "type": "string",
            "description": "Nome do cliente."
          },
          "produtoId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do produto (UUID)."
          },
          "produto": {
            "type": "string",
            "description": "Nome do produto ou \"Sem produto\"."
          },
          "tipoProjetoId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do tipo de projeto."
          },
          "tipoProjeto": {
            "type": "string",
            "description": "Nome do tipo de projeto ou \"Sem tipo de projeto\"."
          },
          "departamento": {
            "type": "string",
            "description": "Departamento ou \"Sem departamento\"."
          }
        },
        "required": [
          "id",
          "nome",
          "clienteId",
          "cliente",
          "produtoId",
          "produto",
          "tipoProjetoId",
          "tipoProjeto",
          "departamento"
        ]
      },
      "InadimplenciaItem": {
        "type": "object",
        "properties": {
          "rowKey": {
            "type": "string",
            "description": "Chave estável da linha: `OS:<id da OS>` (títulos ligados a uma OS são agrupados em uma linha) ou `TITLE:<omieTenantAppId>:<codTituloOmie>` (título sem OS). Em casos raros (títulos da mesma OS ligados a projeto, cliente ou contrato diferentes) a mesma chave pode aparecer em mais de uma linha; nesse caso cada linha traz todas as parcelas da OS em `detalhe.parcelas` e a soma delas pode não bater com `valorEmAtraso`."
          },
          "omieTenantAppId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id do cadastro Omie de origem. `null` na fonte legada."
          },
          "origem": {
            "type": "string",
            "description": "Apelido do cadastro Omie (ou `Omie app <id>`); \"Legado\" na fonte legada."
          },
          "codTituloOmie": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código do título no Omie; preenchido só em linha sem OS."
          },
          "cpfCnpjCliente": {
            "type": [
              "string",
              "null"
            ],
            "description": "CPF/CNPJ do cliente no recebível (dado pessoal, sem máscara garantida). `null` na fonte legada."
          },
          "ordemServicoId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id interno da OS."
          },
          "ordemServicoNumero": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número da OS."
          },
          "codOsOmie": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código da OS no Omie."
          },
          "contratoId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id interno do contrato vinculado."
          },
          "contratoLabel": {
            "type": "string",
            "description": "Rótulo do contrato (ver `detalhe.contrato.label`)."
          },
          "clienteId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do cliente (UUID)."
          },
          "cliente": {
            "type": "string",
            "description": "Nome do cliente (nome fantasia; senão a empresa do projeto; senão o nome vindo do Omie; senão \"Sem cliente vinculado\" na canônica / \"Cliente não identificado\" na legada)."
          },
          "projetoId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do projeto (UUID)."
          },
          "projeto": {
            "type": "string",
            "description": "Nome do projeto; `Projeto <id>` se o projeto não tem nome; \"Sem projeto vinculado\" sem projeto (só canônica)."
          },
          "produtoId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do produto (UUID)."
          },
          "produto": {
            "type": "string",
            "description": "Nome do produto ou \"Sem produto\"."
          },
          "tipoProjetoId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do tipo de projeto."
          },
          "tipoProjeto": {
            "type": "string",
            "description": "Nome do tipo de projeto ou \"Sem tipo de projeto\"."
          },
          "departamento": {
            "type": "string",
            "description": "Departamento ou \"Sem departamento\"."
          },
          "tipoDocumento": {
            "type": [
              "string",
              "null"
            ],
            "description": "Tipos de documento Omie dos títulos da linha, distintos e separados por \", \" (ex.: `BOL, REC`). `null` na fonte legada."
          },
          "status": {
            "type": "string",
            "description": "Canônica: `OPEN`. Legada: `BOLETO_GERADO` ou `FATURADA`."
          },
          "statusDescricao": {
            "type": "string",
            "description": "Canônica: \"Recebível em aberto\". Legada: descrição do status da OS."
          },
          "vencimento": {
            "type": "string",
            "description": "Vencimento mais antigo entre as parcelas da linha, `YYYY-MM-DD`. Usa a data de vencimento efetiva (a mais tardia entre o vencimento e a previsão de recebimento do Omie).",
            "format": "date"
          },
          "diasAtraso": {
            "type": "integer",
            "description": "Dias inteiros de atraso a partir do vencimento mais antigo da linha."
          },
          "valorEmAtraso": {
            "type": "number",
            "description": "Soma do saldo em aberto das parcelas da linha que passam nos filtros, em reais."
          },
          "historico": {
            "type": "boolean",
            "description": "`true` quando o projeto foi excluído (soft delete) ou o cliente está inativo; o item continua devendo, mas é \"histórico\" no catálogo. Sempre `false` na fonte legada."
          },
          "catalogoAusente": {
            "type": "boolean",
            "description": "`true` quando cliente, produto ou tipo de projeto referenciado não existe, foi excluído ou (cliente) não é mais da relação CLIENTE. Sempre `false` na fonte legada."
          },
          "detalhe": {
            "type": "object",
            "properties": {
              "tipoDocumento": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Igual a `tipoDocumento`."
              },
              "parcelas": {
                "type": "array",
                "description": "Parcelas em atraso da linha (vazio na fonte legada).",
                "items": {
                  "$ref": "#/components/schemas/InadimplenciaParcela"
                }
              },
              "ordemServico": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/InadimplenciaOrdemServicoDetalhe"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "`null` quando a linha é de um título sem OS."
              },
              "contrato": {
                "$ref": "#/components/schemas/InadimplenciaContratoDetalhe"
              },
              "projeto": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/InadimplenciaProjetoDetalhe"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "`null` quando não há projeto vinculado."
              }
            },
            "required": [
              "tipoDocumento",
              "parcelas",
              "ordemServico",
              "contrato",
              "projeto"
            ]
          }
        },
        "required": [
          "rowKey",
          "omieTenantAppId",
          "origem",
          "codTituloOmie",
          "cpfCnpjCliente",
          "ordemServicoId",
          "ordemServicoNumero",
          "codOsOmie",
          "contratoId",
          "contratoLabel",
          "clienteId",
          "cliente",
          "projetoId",
          "projeto",
          "produtoId",
          "produto",
          "tipoProjetoId",
          "tipoProjeto",
          "departamento",
          "tipoDocumento",
          "status",
          "statusDescricao",
          "vencimento",
          "diasAtraso",
          "valorEmAtraso",
          "historico",
          "catalogoAusente",
          "detalhe"
        ]
      },
      "InadimplenciaItensResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InadimplenciaItem"
                }
              },
              "pagination": {
                "type": "object",
                "properties": {
                  "page": {
                    "type": "integer",
                    "description": "Página atual (base 1)."
                  },
                  "pageSize": {
                    "type": "integer",
                    "description": "Tamanho da página."
                  },
                  "total": {
                    "type": "integer",
                    "description": "Total de **linhas** (grupos por OS ou título) que passam nos filtros. Na fonte canônica, quando `page` passa da última página, vem `0` (e `totalPages` = 0); para paginar, pare quando `items` vier com menos de `pageSize` linhas ou quando `page >= totalPages` da página anterior."
                  },
                  "totalPages": {
                    "type": "integer",
                    "description": "Total de páginas; 0 quando não há linhas."
                  }
                },
                "required": [
                  "page",
                  "pageSize",
                  "total",
                  "totalPages"
                ]
              }
            },
            "required": [
              "items",
              "pagination"
            ]
          }
        },
        "required": [
          "data"
        ]
      },
      "InadimplenciaOpcaoFiltro": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Valor a enviar no filtro correspondente da consulta."
          },
          "label": {
            "type": "string",
            "description": "Rótulo para exibição."
          },
          "availability": {
            "type": "string",
            "description": "`ACTIVE`, `INACTIVE` (cadastro inativo) ou `DELETED_HISTORICAL` (projeto excluído). Opções não ativas só aparecem quando seu id foi pedido em `selectedId`.",
            "enum": [
              "ACTIVE",
              "INACTIVE",
              "DELETED_HISTORICAL"
            ]
          }
        },
        "required": [
          "id",
          "label",
          "availability"
        ]
      },
      "InadimplenciaOpcoesFiltroResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/InadimplenciaOpcaoFiltro"
                }
              },
              "hasMore": {
                "type": "boolean",
                "description": "`true` se há mais opções após esta página."
              },
              "nextCursor": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Cursor opaco para a próxima página; `null` na última."
              }
            },
            "required": [
              "items",
              "hasMore",
              "nextCursor"
            ]
          }
        },
        "required": [
          "data"
        ]
      },
      "ComissoesComponente": {
        "type": "object",
        "description": "Estado de um componente da comissão.",
        "properties": {
          "aplicavel": {
            "type": "boolean",
            "description": "`true` quando o valor de comissão deste componente é maior que zero."
          },
          "aptoPagamento": {
            "type": "boolean",
            "description": "`true` quando já foi conferido, ainda não foi pago e a comissão está em status apto (`ELEGIVEL`, `FECHADA` ou `PAGA_PARCIAL`). Para comissão especial: `status = CONFERIDA` e componente aplicável."
          },
          "conferencia": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "`NAO_APLICAVEL`, `CONFERIDO` ou `PENDENTE`.",
                "enum": [
                  "NAO_APLICAVEL",
                  "CONFERIDO",
                  "PENDENTE"
                ]
              },
              "em": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Instante da conferência."
              },
              "porId": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "Id do usuário que conferiu."
              },
              "observacao": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Observação da conferência."
              }
            },
            "required": [
              "status",
              "em",
              "porId",
              "observacao"
            ]
          },
          "pagamento": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "`NAO_APLICAVEL`, `PAGO` ou `PENDENTE`.",
                "enum": [
                  "NAO_APLICAVEL",
                  "PAGO",
                  "PENDENTE"
                ]
              },
              "em": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Instante do pagamento (a data informada por quem registrou)."
              },
              "porId": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "Id do usuário que registrou o pagamento."
              },
              "observacao": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Observação do pagamento."
              }
            },
            "required": [
              "status",
              "em",
              "porId",
              "observacao"
            ]
          }
        },
        "required": [
          "aplicavel",
          "aptoPagamento",
          "conferencia",
          "pagamento"
        ]
      },
      "ComissoesComissaoPadrao": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id da comissão (UUID).",
            "format": "uuid"
          },
          "tipoComissao": {
            "type": "string",
            "enum": [
              "PADRAO"
            ],
            "description": "Sempre `PADRAO` neste formato."
          },
          "origem": {
            "type": "string",
            "description": "`FORMULARIO` (gerada pela aprovação de um formulário comercial) ou `IMPORTACAO_EXTERNA` (planilha importada).",
            "enum": [
              "FORMULARIO",
              "IMPORTACAO_EXTERNA"
            ]
          },
          "formularioId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Formulário comercial de origem."
          },
          "projetoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Projeto vinculado."
          },
          "vendedorId": {
            "type": "string",
            "description": "Vendedor comissionado.",
            "format": "uuid"
          },
          "produtoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Produto."
          },
          "tipoProjetoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Tipo de projeto (cai no do formulário quando vazio)."
          },
          "regraId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Regra de comissão que foi resolvida na data da venda."
          },
          "importacaoReferenciaExterna": {
            "type": [
              "string",
              "null"
            ],
            "description": "Referência externa, só para importadas."
          },
          "clienteSnapshot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do cliente gravado na criação."
          },
          "projetoSnapshot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do projeto gravado na criação."
          },
          "vendedorNomeSnapshot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do vendedor gravado na criação (importadas)."
          },
          "produtoSnapshot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do produto gravado na criação."
          },
          "tipoProjetoSnapshot": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do tipo de projeto gravado na criação."
          },
          "contratoId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Contrato de serviço vinculado."
          },
          "primeiraOsPagaId": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Id da OS cujo pagamento liberou a comissão. `null` enquanto não houve OS paga."
          },
          "fechamentoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Fechamento de período em que a comissão foi congelada (status `FECHADA`)."
          },
          "status": {
            "type": "string",
            "description": "Ciclo de vida da comissão (ver descrição da rota).",
            "enum": [
              "PENDENTE_REGRA",
              "PENDENTE_CONFERENCIA",
              "PENDENTE_ELEGIBILIDADE",
              "ELEGIVEL",
              "FECHADA",
              "PAGA_PARCIAL",
              "PAGA",
              "CANCELADA"
            ]
          },
          "dataVendaConclusao": {
            "type": "string",
            "description": "Data da venda/conclusão (instante UTC). Datas digitadas como dia vêm como `T00:00:00.000Z`.",
            "format": "date-time"
          },
          "dataBaseRegra": {
            "type": "string",
            "description": "Data usada para escolher a regra de comissão vigente.",
            "format": "date-time"
          },
          "percentualPrimeiraParcelaSnapshot": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentual (0-100) da regra para a primeira parcela/SaaS, congelado na resolução."
          },
          "percentualServicoSnapshot": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentual (0-100) da regra para o serviço, congelado na resolução."
          },
          "regraVigenciaInicio": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Início da vigência da regra usada."
          },
          "regraVigenciaFim": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Fim da vigência da regra usada."
          },
          "baseCalculoPrimeiraParcela": {
            "type": [
              "number",
              "null"
            ],
            "description": "Base (R$) da comissão de primeira parcela/SaaS. Pode ser derivada do formulário quando a coluna está vazia."
          },
          "valorComissaoPrimeiraParcela": {
            "type": [
              "number",
              "null"
            ],
            "description": "Valor (R$) da comissão de primeira parcela/SaaS (base x percentual / 100)."
          },
          "baseCalculoServico": {
            "type": [
              "number",
              "null"
            ],
            "description": "Base (R$) da comissão de serviço."
          },
          "valorComissaoServico": {
            "type": [
              "number",
              "null"
            ],
            "description": "Valor (R$) da comissão de serviço."
          },
          "baseCalculo": {
            "type": [
              "number",
              "null"
            ],
            "description": "Soma das bases dos componentes (R$)."
          },
          "valorComissao": {
            "type": [
              "number",
              "null"
            ],
            "description": "Soma dos valores dos componentes (R$). É o valor total da comissão."
          },
          "dataElegibilidade": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quando a comissão virou `ELEGIVEL`."
          },
          "conferidaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Conferência da venda (componente serviço)."
          },
          "conferidaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário que conferiu a venda."
          },
          "observacaoConferencia": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação da conferência do serviço."
          },
          "primeiraParcelaConferidaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Conferência do componente primeira parcela/SaaS."
          },
          "primeiraParcelaConferidaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário que conferiu a primeira parcela."
          },
          "observacaoConferenciaPrimeiraParcela": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação da conferência da primeira parcela."
          },
          "primeiraParcelaPagaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento da primeira parcela/SaaS."
          },
          "primeiraParcelaPagaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário que registrou o pagamento."
          },
          "servicoPagoEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento do serviço."
          },
          "servicoPagoPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário que registrou o pagamento."
          },
          "observacaoPagamentoServico": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pagamento do serviço."
          },
          "observacaoPagamentoPrimeiraParcela": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pagamento da primeira parcela."
          },
          "pagaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento agregado: o mais recente entre os componentes pagos, só quando todos os aplicáveis estão pagos."
          },
          "pagaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Usuário do pagamento agregado."
          },
          "observacaoOperacional": {
            "type": [
              "string",
              "null"
            ],
            "description": "Texto interno gerado pelo sistema sobre o último passo do fluxo."
          },
          "createdAt": {
            "type": "string",
            "description": "Criação do registro.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "Última atualização.",
            "format": "date-time"
          },
          "vendedor": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do vendedor (UUID).",
                    "format": "uuid"
                  },
                  "nome": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do vendedor."
                  }
                },
                "required": [
                  "id",
                  "nome"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Vendedor comissionado."
          },
          "produto": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do produto (UUID).",
                    "format": "uuid"
                  },
                  "produto": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do produto."
                  }
                },
                "required": [
                  "id",
                  "produto"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "tipoProjeto": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do tipo de projeto (UUID).",
                    "format": "uuid"
                  },
                  "tipoProjeto": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do tipo de projeto."
                  },
                  "status": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Tipo de projeto ativo."
                  }
                },
                "required": [
                  "id",
                  "tipoProjeto",
                  "status"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Tipo de projeto (o do formulário tem prioridade)."
          },
          "fechamento": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do fechamento.",
                    "format": "uuid"
                  },
                  "periodoInicio": {
                    "type": "string",
                    "description": "Início do período.",
                    "format": "date-time"
                  },
                  "periodoFim": {
                    "type": "string",
                    "description": "Fim do período.",
                    "format": "date-time"
                  }
                },
                "required": [
                  "id",
                  "periodoInicio",
                  "periodoFim"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "cliente": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do cliente (UUID)."
                  },
                  "nomeEmpresa": {
                    "type": "string",
                    "description": "Nome fantasia do cliente."
                  }
                },
                "required": [
                  "id",
                  "nomeEmpresa"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Cliente do formulário de origem; `null` em importadas ou sem formulário."
          },
          "projeto": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "nomeProjeto": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do projeto."
                  },
                  "quantidadeHoras": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Horas vendidas."
                  },
                  "valorHora": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Valor da hora (R$)."
                  }
                },
                "required": [
                  "nomeProjeto",
                  "quantidadeHoras",
                  "valorHora"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "contrato": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "integer",
                    "description": "Id interno do contrato."
                  },
                  "tipoContrato": {
                    "type": "string",
                    "description": "Tipo do contrato."
                  },
                  "numCtr": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Número do contrato."
                  },
                  "codStatus": {
                    "type": "string",
                    "description": "Status do contrato no Omie."
                  },
                  "descStatus": {
                    "type": "string",
                    "description": "Descrição do status."
                  },
                  "valorTotalMensal": {
                    "type": "number",
                    "description": "Valor mensal total, em reais."
                  },
                  "diaFaturamento": {
                    "type": "integer",
                    "description": "Dia do mês do faturamento."
                  },
                  "vigenciaInicio": {
                    "type": "string",
                    "description": "Início da vigência.",
                    "format": "date-time"
                  },
                  "vigenciaFim": {
                    "type": "string",
                    "description": "Fim da vigência.",
                    "format": "date-time"
                  },
                  "pagamentoStatus": {
                    "type": "string",
                    "description": "Situação dos recebíveis do contrato: `SEM_RECEBIVEL`, `PENDENTE`, `PARCIAL` ou `PAGA`. Pode estar ausente.",
                    "enum": [
                      "SEM_RECEBIVEL",
                      "PENDENTE",
                      "PARCIAL",
                      "PAGA"
                    ]
                  },
                  "dataPrimeiroRecebimento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Primeira liquidação ativa do contrato; pode estar ausente."
                  }
                },
                "required": [
                  "id",
                  "tipoContrato",
                  "numCtr",
                  "codStatus",
                  "descStatus",
                  "valorTotalMensal",
                  "diaFaturamento",
                  "vigenciaInicio",
                  "vigenciaFim"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "primeiraOsPaga": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "integer",
                    "description": "Id interno da OS."
                  },
                  "numOs": {
                    "type": "string",
                    "description": "Número da OS."
                  },
                  "codStatus": {
                    "type": "string",
                    "description": "Status da OS (`PAGO`, `FATURADA`, `BOLETO_GERADO`...)."
                  },
                  "descStatus": {
                    "type": "string",
                    "description": "Descrição do status."
                  },
                  "valorTotal": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Valor total da OS, em reais."
                  },
                  "dataFaturamento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Data de faturamento (ISO 8601)."
                  },
                  "dataVencimento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Vencimento (ISO 8601)."
                  },
                  "quantidadeParcelas": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Número de parcelas."
                  },
                  "pagamentoStatus": {
                    "type": "string",
                    "description": "Situação do recebimento da OS no Omie: `SEM_RECEBIVEL`, `PENDENTE`, `PARCIAL` ou `PAGA`. Pode estar ausente.",
                    "enum": [
                      "SEM_RECEBIVEL",
                      "PENDENTE",
                      "PARCIAL",
                      "PAGA"
                    ]
                  },
                  "dataPrimeiroRecebimento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Primeira liquidação ativa da OS; pode estar ausente."
                  }
                },
                "required": [
                  "id",
                  "numOs",
                  "codStatus",
                  "descStatus",
                  "valorTotal",
                  "dataFaturamento",
                  "dataVencimento",
                  "quantidadeParcelas"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "OS que liberou a comissão."
          },
          "primeiraOsPendente": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "integer",
                    "description": "Id interno da OS."
                  },
                  "numOs": {
                    "type": "string",
                    "description": "Número da OS."
                  },
                  "codStatus": {
                    "type": "string",
                    "description": "Status da OS (`PAGO`, `FATURADA`, `BOLETO_GERADO`...)."
                  },
                  "descStatus": {
                    "type": "string",
                    "description": "Descrição do status."
                  },
                  "valorTotal": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Valor total da OS, em reais."
                  },
                  "dataFaturamento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Data de faturamento (ISO 8601)."
                  },
                  "dataVencimento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Vencimento (ISO 8601)."
                  },
                  "quantidadeParcelas": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Número de parcelas."
                  },
                  "pagamentoStatus": {
                    "type": "string",
                    "description": "Situação do recebimento da OS no Omie: `SEM_RECEBIVEL`, `PENDENTE`, `PARCIAL` ou `PAGA`. Pode estar ausente.",
                    "enum": [
                      "SEM_RECEBIVEL",
                      "PENDENTE",
                      "PARCIAL",
                      "PAGA"
                    ]
                  },
                  "dataPrimeiroRecebimento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "Primeira liquidação ativa da OS; pode estar ausente."
                  }
                },
                "required": [
                  "id",
                  "numOs",
                  "codStatus",
                  "descStatus",
                  "valorTotal",
                  "dataFaturamento",
                  "dataVencimento",
                  "quantidadeParcelas"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Enquanto `primeiraOsPagaId` é `null`: a OS mais antiga ainda não paga (nem cancelada/excluída/erro) do formulário/projeto; `null` se não há."
          },
          "componentes": {
            "type": "object",
            "description": "Estado de conferência e pagamento por componente: `SERVICO` e `PRIMEIRA_PARCELA` (SaaS/mensalidade).",
            "properties": {
              "SERVICO": {
                "$ref": "#/components/schemas/ComissoesComponente"
              },
              "PRIMEIRA_PARCELA": {
                "$ref": "#/components/schemas/ComissoesComponente"
              }
            },
            "required": [
              "SERVICO",
              "PRIMEIRA_PARCELA"
            ]
          }
        },
        "required": [
          "id",
          "tipoComissao",
          "origem",
          "status",
          "vendedorId",
          "valorComissao",
          "dataVendaConclusao",
          "dataBaseRegra",
          "componentes",
          "createdAt",
          "updatedAt"
        ]
      },
      "ComissoesComissaoEspecial": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Id da comissão especial (UUID).",
            "format": "uuid"
          },
          "tipoComissao": {
            "type": "string",
            "enum": [
              "ESPECIAL"
            ],
            "description": "Sempre `ESPECIAL` neste formato."
          },
          "origem": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "FORMULARIO",
              "IMPORTACAO_EXTERNA",
              null
            ],
            "description": "Origem da comissão padrão que a gerou."
          },
          "regraEspecialId": {
            "type": "string",
            "description": "Regra especial aplicada.",
            "format": "uuid"
          },
          "comissaoOriginalId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Comissão padrão em que esta se baseia."
          },
          "comissaoOriginal": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id da comissão original.",
                    "format": "uuid"
                  },
                  "origem": {
                    "type": "string",
                    "description": "Origem.",
                    "enum": [
                      "FORMULARIO",
                      "IMPORTACAO_EXTERNA"
                    ]
                  },
                  "vendedorId": {
                    "type": "string",
                    "description": "Vendedor da comissão original.",
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "description": "Status da comissão original.",
                    "enum": [
                      "PENDENTE_REGRA",
                      "PENDENTE_CONFERENCIA",
                      "PENDENTE_ELEGIBILIDADE",
                      "ELEGIVEL",
                      "FECHADA",
                      "PAGA_PARCIAL",
                      "PAGA",
                      "CANCELADA"
                    ]
                  },
                  "dataVendaConclusao": {
                    "type": "string",
                    "description": "Data da venda.",
                    "format": "date-time"
                  },
                  "vendedor": {
                    "oneOf": [
                      {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "description": "Id do vendedor (UUID).",
                            "format": "uuid"
                          },
                          "nome": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Nome do vendedor."
                          }
                        },
                        "required": [
                          "id",
                          "nome"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "id",
                  "origem",
                  "vendedorId",
                  "status",
                  "dataVendaConclusao",
                  "vendedor"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "beneficiarioVendedorId": {
            "type": "string",
            "description": "Vendedor beneficiário da comissão especial.",
            "format": "uuid"
          },
          "vendedorId": {
            "type": "string",
            "description": "Igual a `beneficiarioVendedorId`.",
            "format": "uuid"
          },
          "vendedor": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do vendedor (UUID).",
                    "format": "uuid"
                  },
                  "nome": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do vendedor."
                  }
                },
                "required": [
                  "id",
                  "nome"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "Vendedor beneficiário."
          },
          "formularioId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Formulário de origem."
          },
          "projetoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Projeto."
          },
          "produtoId": {
            "type": "string",
            "description": "Produto.",
            "format": "uuid"
          },
          "produto": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do produto (UUID).",
                    "format": "uuid"
                  },
                  "produto": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do produto."
                  }
                },
                "required": [
                  "id",
                  "produto"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "tipoProjetoId": {
            "type": "string",
            "description": "Tipo de projeto.",
            "format": "uuid"
          },
          "tipoProjeto": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Id do tipo de projeto (UUID).",
                    "format": "uuid"
                  },
                  "tipoProjeto": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Nome do tipo de projeto."
                  },
                  "status": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Tipo de projeto ativo."
                  }
                },
                "required": [
                  "id",
                  "tipoProjeto",
                  "status"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "fechamentoId": {
            "type": "null",
            "description": "Sempre `null`: comissão especial não entra em fechamento."
          },
          "status": {
            "type": "string",
            "description": "Ciclo da comissão especial: `ABERTA` (aguardando conferência) -> `CONFERIDA` (pronta para pagar) -> `PAGA`; `CANCELADA` quando a original é cancelada por OS terminal.",
            "enum": [
              "ABERTA",
              "CONFERIDA",
              "PAGA",
              "CANCELADA"
            ]
          },
          "componente": {
            "type": "string",
            "description": "Componente a que se refere (cada comissão especial tem um só).",
            "enum": [
              "SERVICO",
              "PRIMEIRA_PARCELA"
            ]
          },
          "dataVendaConclusao": {
            "type": "string",
            "description": "Data de referência da venda.",
            "format": "date-time"
          },
          "dataReferenciaVenda": {
            "type": "string",
            "description": "Data de referência da venda (igual a `dataVendaConclusao`).",
            "format": "date-time"
          },
          "dataElegibilidade": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Igual a `conferidaEm`."
          },
          "baseCalculoTipoSnapshot": {
            "type": "string",
            "description": "Base usada: `COMISSAO_ORIGINAL_COMPONENTE` (comissão do componente na original) ou `BASE_VENDA_COMPONENTE` (base de venda do componente).",
            "enum": [
              "COMISSAO_ORIGINAL_COMPONENTE",
              "BASE_VENDA_COMPONENTE"
            ]
          },
          "percentualSnapshot": {
            "type": "number",
            "description": "Percentual (0-100) da regra especial, congelado."
          },
          "baseCalculo": {
            "type": "number",
            "description": "Base de cálculo, em reais."
          },
          "valorComissao": {
            "type": "number",
            "description": "Valor da comissão especial, em reais."
          },
          "baseCalculoServico": {
            "type": [
              "number",
              "null"
            ],
            "description": "Igual a `baseCalculo` quando `componente = SERVICO`, senão `null`."
          },
          "valorComissaoServico": {
            "type": [
              "number",
              "null"
            ],
            "description": "Igual a `valorComissao` quando `componente = SERVICO`, senão `null`."
          },
          "baseCalculoPrimeiraParcela": {
            "type": [
              "number",
              "null"
            ],
            "description": "Igual a `baseCalculo` quando `componente = PRIMEIRA_PARCELA`, senão `null`."
          },
          "valorComissaoPrimeiraParcela": {
            "type": [
              "number",
              "null"
            ],
            "description": "Igual a `valorComissao` quando `componente = PRIMEIRA_PARCELA`, senão `null`."
          },
          "conferidaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Conferência."
          },
          "conferidaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Quem conferiu."
          },
          "observacaoConferencia": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação da conferência."
          },
          "primeiraParcelaConferidaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Conferência, quando o componente é primeira parcela."
          },
          "primeiraParcelaConferidaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Quem conferiu, quando primeira parcela."
          },
          "observacaoConferenciaPrimeiraParcela": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação, quando primeira parcela."
          },
          "servicoPagoEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento, quando o componente é serviço."
          },
          "servicoPagoPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Quem pagou, quando serviço."
          },
          "observacaoPagamentoServico": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pagamento, quando serviço."
          },
          "primeiraParcelaPagaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento, quando primeira parcela."
          },
          "primeiraParcelaPagaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Quem pagou, quando primeira parcela."
          },
          "observacaoPagamentoPrimeiraParcela": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pagamento, quando primeira parcela."
          },
          "pagaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Pagamento."
          },
          "pagaPorId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Quem pagou."
          },
          "observacaoPagamento": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pagamento."
          },
          "observacaoOperacional": {
            "type": [
              "string",
              "null"
            ],
            "description": "Texto interno."
          },
          "createdAt": {
            "type": "string",
            "description": "Criação.",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "description": "Atualização.",
            "format": "date-time"
          },
          "cliente": {
            "type": "null",
            "description": "Sempre `null` em comissão especial."
          },
          "projeto": {
            "type": "null",
            "description": "Sempre `null` em comissão especial."
          },
          "contrato": {
            "type": "null",
            "description": "Sempre `null` em comissão especial."
          },
          "primeiraOsPaga": {
            "type": "null",
            "description": "Sempre `null` em comissão especial."
          },
          "componentes": {
            "type": "object",
            "properties": {
              "SERVICO": {
                "$ref": "#/components/schemas/ComissoesComponente"
              },
              "PRIMEIRA_PARCELA": {
                "$ref": "#/components/schemas/ComissoesComponente"
              }
            },
            "required": [
              "SERVICO",
              "PRIMEIRA_PARCELA"
            ]
          }
        },
        "required": [
          "id",
          "tipoComissao",
          "origem",
          "regraEspecialId",
          "comissaoOriginalId",
          "comissaoOriginal",
          "beneficiarioVendedorId",
          "vendedorId",
          "vendedor",
          "formularioId",
          "projetoId",
          "produtoId",
          "produto",
          "tipoProjetoId",
          "tipoProjeto",
          "fechamentoId",
          "status",
          "componente",
          "dataVendaConclusao",
          "dataReferenciaVenda",
          "dataElegibilidade",
          "baseCalculoTipoSnapshot",
          "percentualSnapshot",
          "baseCalculo",
          "valorComissao",
          "baseCalculoServico",
          "valorComissaoServico",
          "baseCalculoPrimeiraParcela",
          "valorComissaoPrimeiraParcela",
          "conferidaEm",
          "conferidaPorId",
          "observacaoConferencia",
          "primeiraParcelaConferidaEm",
          "primeiraParcelaConferidaPorId",
          "observacaoConferenciaPrimeiraParcela",
          "servicoPagoEm",
          "servicoPagoPorId",
          "observacaoPagamentoServico",
          "primeiraParcelaPagaEm",
          "primeiraParcelaPagaPorId",
          "observacaoPagamentoPrimeiraParcela",
          "pagaEm",
          "pagaPorId",
          "observacaoPagamento",
          "observacaoOperacional",
          "createdAt",
          "updatedAt",
          "cliente",
          "projeto",
          "contrato",
          "primeiraOsPaga",
          "componentes"
        ]
      },
      "ComissoesComissao": {
        "description": "Comissão no formato padrão ou especial; o campo `tipoComissao` discrimina.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ComissoesComissaoPadrao"
          },
          {
            "$ref": "#/components/schemas/ComissoesComissaoEspecial"
          }
        ],
        "discriminator": {
          "propertyName": "tipoComissao",
          "mapping": {
            "PADRAO": "#/components/schemas/ComissoesComissaoPadrao",
            "ESPECIAL": "#/components/schemas/ComissoesComissaoEspecial"
          }
        }
      },
      "ComissoesResumoResponse": {
        "type": "object",
        "description": "`CANCELADA` e especiais canceladas não aparecem em nenhum cartão.",
        "properties": {
          "cards": {
            "type": "object",
            "properties": {
              "pendenteRegra": {
                "type": "object",
                "properties": {
                  "totalComissoes": {
                    "type": "integer",
                    "description": "Quantidade de comissões (padrão + especiais) em `PENDENTE_REGRA`."
                  },
                  "valorComissaoTotal": {
                    "type": "number",
                    "description": "Valor total em reais."
                  }
                },
                "required": [
                  "totalComissoes",
                  "valorComissaoTotal"
                ],
                "description": "Sem regra de comissão vigente. `valorComissaoTotal` aqui é a base de serviço estimada do projeto (horas x valor da hora) quando existe, não uma comissão calculada."
              },
              "pendenteConferencia": {
                "type": "object",
                "properties": {
                  "totalComissoes": {
                    "type": "integer",
                    "description": "Quantidade de comissões (padrão + especiais) em `PENDENTE_CONFERENCIA` (mais especiais `ABERTA`)."
                  },
                  "valorComissaoTotal": {
                    "type": "number",
                    "description": "Valor total em reais."
                  }
                },
                "required": [
                  "totalComissoes",
                  "valorComissaoTotal"
                ],
                "description": "Aguardando conferência da venda."
              },
              "pendenteElegibilidade": {
                "type": "object",
                "properties": {
                  "totalComissoes": {
                    "type": "integer",
                    "description": "Quantidade de comissões (padrão + especiais) em `PENDENTE_ELEGIBILIDADE`."
                  },
                  "valorComissaoTotal": {
                    "type": "number",
                    "description": "Valor total em reais."
                  }
                },
                "required": [
                  "totalComissoes",
                  "valorComissaoTotal"
                ],
                "description": "Venda conferida, aguardando a primeira OS paga."
              },
              "prontaPagamento": {
                "type": "object",
                "description": "Comissões `ELEGIVEL`, `FECHADA` ou `PAGA_PARCIAL` (mais especiais `CONFERIDA`). Para `PAGA_PARCIAL` conta só o que ainda falta pagar.",
                "properties": {
                  "totalComissoes": {
                    "type": "integer",
                    "description": "Quantidade de comissões prontas para pagamento."
                  },
                  "valorComissaoTotal": {
                    "type": "number",
                    "description": "Valor total em reais (saldo a pagar nas parcialmente pagas)."
                  },
                  "valorComissaoPrimeiraParcelaPendente": {
                    "type": "number",
                    "description": "Parte do saldo que é componente primeira parcela/SaaS ainda não pago, em reais."
                  },
                  "valorComissaoServicoPendente": {
                    "type": "number",
                    "description": "Parte do saldo que é componente serviço ainda não pago, em reais."
                  }
                },
                "required": [
                  "totalComissoes",
                  "valorComissaoTotal",
                  "valorComissaoPrimeiraParcelaPendente",
                  "valorComissaoServicoPendente"
                ]
              },
              "paga": {
                "type": "object",
                "properties": {
                  "totalComissoes": {
                    "type": "integer",
                    "description": "Quantidade de comissões (padrão + especiais) em `PAGA` (mais especiais `PAGA`)."
                  },
                  "valorComissaoTotal": {
                    "type": "number",
                    "description": "Valor total em reais."
                  }
                },
                "required": [
                  "totalComissoes",
                  "valorComissaoTotal"
                ],
                "description": "Totalmente paga."
              }
            },
            "required": [
              "pendenteRegra",
              "pendenteConferencia",
              "pendenteElegibilidade",
              "prontaPagamento",
              "paga"
            ]
          }
        },
        "required": [
          "cards"
        ]
      },
      "ComissoesFilterOptionsResponse": {
        "type": "object",
        "properties": {
          "statuses": {
            "type": "array",
            "description": "Status presentes nos dados visíveis ao usuário (padrão e especiais), ordenados alfabeticamente e sem repetição.",
            "items": {
              "type": "string",
              "enum": [
                "PENDENTE_REGRA",
                "PENDENTE_CONFERENCIA",
                "PENDENTE_ELEGIBILIDADE",
                "ELEGIVEL",
                "FECHADA",
                "PAGA_PARCIAL",
                "PAGA",
                "CANCELADA",
                "ABERTA",
                "CONFERIDA"
              ]
            }
          },
          "vendedores": {
            "type": "array",
            "description": "Vendedores com ao menos uma comissão visível.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Id do vendedor (UUID).",
                  "format": "uuid"
                },
                "nome": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Nome do vendedor."
                }
              },
              "required": [
                "id",
                "nome"
              ]
            }
          },
          "produtos": {
            "type": "array",
            "description": "Produtos com comissão visível, em ordem alfabética (pt-BR).",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Id do produto (UUID).",
                  "format": "uuid"
                },
                "produto": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Nome do produto."
                }
              },
              "required": [
                "id",
                "produto"
              ]
            }
          },
          "fechamentos": {
            "type": "array",
            "description": "Fechamentos de período visíveis, do período mais recente ao mais antigo. Quem vê só as próprias comissões vê só os fechamentos que as contêm.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Id do fechamento (UUID).",
                  "format": "uuid"
                },
                "periodoInicio": {
                  "type": "string",
                  "description": "Início do período.",
                  "format": "date-time"
                },
                "periodoFim": {
                  "type": "string",
                  "description": "Fim do período.",
                  "format": "date-time"
                }
              },
              "required": [
                "id",
                "periodoInicio",
                "periodoFim"
              ]
            }
          }
        },
        "required": [
          "statuses",
          "vendedores",
          "produtos",
          "fechamentos"
        ]
      },
      "OportunidadePessoaRef": {
        "type": "object",
        "description": "Referência {id, nome}. nome nunca é nulo (nome ausente no cadastro vira \"(sem nome)\").",
        "required": [
          "id",
          "nome"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          }
        }
      },
      "OportunidadeVendedorRef": {
        "type": "object",
        "description": "Vendedor da oportunidade (id do cadastro de vendedores, o mesmo de `vendedorId` nos filtros).",
        "required": [
          "id",
          "nome",
          "iniciais"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "iniciais": {
            "type": "string",
            "description": "Primeira letra do primeiro e do último nome, maiúsculas (ex.: \"VE\"); \"?\" se sem nome."
          }
        }
      },
      "OportunidadeEmpresa": {
        "type": "object",
        "required": [
          "id",
          "nome",
          "cnpj",
          "relacao",
          "ativo"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string",
            "description": "Nome fantasia (ou razão social, ou \"(sem nome)\")."
          },
          "cnpj": {
            "type": [
              "string",
              "null"
            ],
            "description": "CNPJ normalizado (14 caracteres, sem pontuação) ou nulo."
          },
          "relacao": {
            "type": "string",
            "enum": [
              "CLIENTE",
              "PROSPECT",
              "TERCEIRO"
            ]
          },
          "ativo": {
            "type": "boolean",
            "description": "Falso para cliente inativado."
          }
        }
      },
      "OportunidadeContato": {
        "type": "object",
        "description": "Contato da empresa vinculado à oportunidade (o CPF nunca é exposto).",
        "required": [
          "id",
          "nome",
          "cargo",
          "email",
          "telefone"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nome": {
            "type": "string"
          },
          "cargo": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "telefone": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "OportunidadeProximaTarefa": {
        "type": "object",
        "required": [
          "id",
          "titulo",
          "tipo",
          "prazo",
          "vencida"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "titulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "TAREFA",
              "LIGACAO",
              "EMAIL",
              "REUNIAO",
              "WHATSAPP"
            ]
          },
          "prazo": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Instante ISO 8601 UTC; nulo = sem prazo."
          },
          "vencida": {
            "type": "boolean",
            "description": "Prazo anterior ao momento da consulta."
          }
        }
      },
      "OportunidadePropostaSelo": {
        "type": "object",
        "description": "Versão mais recente da proposta que não é rascunho nem substituída.",
        "required": [
          "versao",
          "status",
          "aberturas",
          "validadeAte"
        ],
        "properties": {
          "versao": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "ENVIADA",
              "AJUSTE_SOLICITADO",
              "ACEITA",
              "RECUSADA",
              "EXPIRADA"
            ]
          },
          "aberturas": {
            "type": "integer",
            "description": "Total de aberturas reais (sem robô) da proposta, somando todos os destinatários, desde o envio."
          },
          "validadeAte": {
            "type": "string",
            "format": "date",
            "description": "YYYY-MM-DD; vale até 23:59:59 de São Paulo."
          }
        }
      },
      "OportunidadeContratoSelo": {
        "type": "object",
        "description": "Selo do contrato da oportunidade.",
        "required": [
          "situacao",
          "contratoId",
          "categoria",
          "assinados",
          "total",
          "quantidade",
          "clienteObrigatorio",
          "prospect",
          "podeGerar"
        ],
        "properties": {
          "situacao": {
            "type": "string",
            "enum": [
              "semContrato",
              "rascunho",
              "emAssinatura",
              "assinado",
              "recusado",
              "expirado",
              "cancelado",
              "falhaNoEnvio"
            ]
          },
          "contratoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Contrato que originou o selo."
          },
          "categoria": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "SAAS",
              "SERVICO",
              "ADITIVO",
              "OUTRO",
              null
            ]
          },
          "assinados": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Só em `emAssinatura`: signatários que já assinaram."
          },
          "total": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Só em `emAssinatura`: total de signatários."
          },
          "quantidade": {
            "type": "integer",
            "description": "Contratos da oportunidade (sem rascunho/falha descartados)."
          },
          "clienteObrigatorio": {
            "type": "boolean",
            "description": "Oportunidade ACEITA cuja empresa ainda não é cliente."
          },
          "prospect": {
            "type": "boolean",
            "description": "A empresa da oportunidade é prospect."
          },
          "podeGerar": {
            "type": "boolean",
            "description": "ACEITA e o escopo do usuário escreve nela."
          }
        }
      },
      "OportunidadeCard": {
        "type": "object",
        "description": "Cartão de oportunidade (item do quadro). Valores monetários em reais, como número JSON.",
        "required": [
          "id",
          "codigo",
          "titulo",
          "tipo",
          "status",
          "fluxo",
          "etapaId",
          "position",
          "valorEstimado",
          "dataPrevisaoFechamento",
          "empresa",
          "contato",
          "vendedor",
          "entrouNaEtapaEm",
          "diasNaEtapa",
          "proximaTarefa",
          "tarefasVencidas",
          "proposta",
          "termometro",
          "termometroMotivo",
          "contrato"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "codigo": {
            "type": "string",
            "description": "Código legível \"OP-AAAA-NNNN\" (sequência por ano)."
          },
          "titulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "VENDA_NOVA",
              "ADITIVO",
              "SERVICO"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ABERTA",
              "ACEITA",
              "PERDIDA"
            ],
            "description": "ACEITA = ganha."
          },
          "fluxo": {
            "type": "string",
            "enum": [
              "COMERCIAL",
              "CS_PARA_COMERCIAL",
              "CS_PARA_CS",
              "COMERCIAL_PARA_CS"
            ],
            "description": "Herdado do pipeline."
          },
          "etapaId": {
            "type": "string",
            "format": "uuid",
            "description": "Etapa atual (nome em `etapa.nome` da lista, ou `etapas[].nome` do quadro)."
          },
          "position": {
            "type": "integer",
            "description": "Ordem do cartão dentro da etapa (ordem de arraste)."
          },
          "valorEstimado": {
            "type": [
              "number",
              "null"
            ],
            "description": "Valor estimado em reais (até 2 casas); nulo = não informado."
          },
          "dataPrevisaoFechamento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "YYYY-MM-DD."
          },
          "empresa": {
            "$ref": "#/components/schemas/OportunidadeEmpresa"
          },
          "contato": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadeContato"
              },
              {
                "type": "null"
              }
            ]
          },
          "vendedor": {
            "$ref": "#/components/schemas/OportunidadeVendedorRef"
          },
          "entrouNaEtapaEm": {
            "type": "string",
            "format": "date-time"
          },
          "diasNaEtapa": {
            "type": "integer",
            "minimum": 0,
            "description": "Dias civis (São Paulo) na etapa atual."
          },
          "proximaTarefa": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadeProximaTarefa"
              },
              {
                "type": "null"
              }
            ],
            "description": "Tarefa aberta de menor prazo (ADIADA não conta; só as que o usuário enxerga)."
          },
          "tarefasVencidas": {
            "type": "integer",
            "minimum": 0
          },
          "proposta": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePropostaSelo"
              },
              {
                "type": "null"
              }
            ]
          },
          "termometro": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "QUENTE",
              "MORNA",
              "FRIA",
              null
            ],
            "description": "Só enquanto a proposta espera o cliente (ENVIADA/AJUSTE_SOLICITADO) e a oportunidade está ABERTA."
          },
          "termometroMotivo": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "AJUSTE_SOLICITADO",
              "ITEM_ALTERADO",
              "ABERTURAS_RECENTES",
              "VENCE_SEM_ABERTURA",
              "SEM_ABERTURA_RECENTE",
              "ABERTA",
              "AGUARDANDO_ABERTURA",
              null
            ],
            "description": "Regra que decidiu o termômetro. QUENTE: ajuste solicitado, item alterado ou 2+ aberturas reais nas últimas 72 h. FRIA: validade em até 3 dias sem abertura, ou sem abertura/entrega há mais de 5 dias. MORNA: o resto."
          },
          "contrato": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadeContratoSelo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Nulo para o usuário restrito (que não vê contratos)."
          }
        }
      },
      "OportunidadeItemLista": {
        "description": "Item de `GET /oportunidades`: o cartão mais pipeline, etapa e produto.",
        "allOf": [
          {
            "$ref": "#/components/schemas/OportunidadeCard"
          },
          {
            "type": "object",
            "required": [
              "pipeline",
              "etapa",
              "produto",
              "createdAt"
            ],
            "properties": {
              "pipeline": {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              "etapa": {
                "type": "object",
                "required": [
                  "id",
                  "nome",
                  "papel"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "nome": {
                    "type": "string"
                  },
                  "papel": {
                    "type": "string",
                    "enum": [
                      "NENHUM",
                      "PROPOSTA_ENVIADA",
                      "AJUSTE_SOLICITADO",
                      "ACEITE",
                      "PERDA"
                    ]
                  }
                }
              },
              "produto": {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "OportunidadeLista": {
        "type": "object",
        "required": [
          "items",
          "page",
          "pageSize",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OportunidadeItemLista"
            }
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer"
          },
          "total": {
            "type": "integer",
            "description": "Total de registros que casam com os filtros (todas as páginas)."
          }
        }
      },
      "OportunidadeDetalhe": {
        "type": "object",
        "required": [
          "id",
          "codigo",
          "numero",
          "titulo",
          "tipo",
          "status",
          "pipeline",
          "fluxo",
          "etapa",
          "etapasDoPipeline",
          "entrouNaEtapaEm",
          "diasNaEtapa",
          "empresa",
          "contato",
          "projeto",
          "produto",
          "tipoProjeto",
          "vendedor",
          "csResponsavel",
          "valorEstimado",
          "horasEstimadas",
          "dataPrevisaoFechamento",
          "briefing",
          "clausulaEspecial",
          "motivoPerda",
          "motivoPerdaDescricao",
          "fechadaEm",
          "escopo",
          "createdBy",
          "createdAt",
          "updatedAt",
          "proposta",
          "termometro",
          "termometroMotivo",
          "contrato",
          "permissoes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "codigo": {
            "type": "string",
            "description": "\"OP-AAAA-NNNN\"."
          },
          "numero": {
            "type": "integer",
            "description": "Número sequencial (parte final do código)."
          },
          "titulo": {
            "type": "string"
          },
          "tipo": {
            "type": "string",
            "enum": [
              "VENDA_NOVA",
              "ADITIVO",
              "SERVICO"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ABERTA",
              "ACEITA",
              "PERDIDA"
            ]
          },
          "pipeline": {
            "$ref": "#/components/schemas/OportunidadePessoaRef"
          },
          "fluxo": {
            "type": "string",
            "enum": [
              "COMERCIAL",
              "CS_PARA_COMERCIAL",
              "CS_PARA_CS",
              "COMERCIAL_PARA_CS"
            ]
          },
          "etapa": {
            "type": "object",
            "required": [
              "id",
              "nome",
              "papel",
              "probabilidade"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "nome": {
                "type": "string"
              },
              "papel": {
                "type": "string",
                "enum": [
                  "NENHUM",
                  "PROPOSTA_ENVIADA",
                  "AJUSTE_SOLICITADO",
                  "ACEITE",
                  "PERDA"
                ]
              },
              "probabilidade": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "0 a 100."
              }
            }
          },
          "etapasDoPipeline": {
            "type": "array",
            "description": "Todas as etapas do pipeline, em ordem.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "nome",
                "position",
                "papel",
                "cor"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "nome": {
                  "type": "string"
                },
                "position": {
                  "type": "integer"
                },
                "papel": {
                  "type": "string",
                  "enum": [
                    "NENHUM",
                    "PROPOSTA_ENVIADA",
                    "AJUSTE_SOLICITADO",
                    "ACEITE",
                    "PERDA"
                  ]
                },
                "cor": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "entrouNaEtapaEm": {
            "type": "string",
            "format": "date-time"
          },
          "diasNaEtapa": {
            "type": "integer",
            "minimum": 0
          },
          "empresa": {
            "$ref": "#/components/schemas/OportunidadeEmpresa"
          },
          "contato": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadeContato"
              },
              {
                "type": "null"
              }
            ]
          },
          "projeto": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Projeto vinculado (nome do projeto)."
          },
          "produto": {
            "$ref": "#/components/schemas/OportunidadePessoaRef"
          },
          "tipoProjeto": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "vendedor": {
            "$ref": "#/components/schemas/OportunidadePessoaRef"
          },
          "csResponsavel": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Usuário de CS responsável (id de usuário)."
          },
          "valorEstimado": {
            "type": [
              "number",
              "null"
            ],
            "description": "Reais."
          },
          "horasEstimadas": {
            "type": [
              "number",
              "null"
            ],
            "description": "Horas (decimal)."
          },
          "dataPrevisaoFechamento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "briefing": {
            "type": [
              "string",
              "null"
            ]
          },
          "clausulaEspecial": {
            "type": [
              "string",
              "null"
            ],
            "description": "Texto livre que, no contrato gerado, prevalece sobre as demais cláusulas."
          },
          "motivoPerda": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "PRECO",
              "CONCORRENTE",
              "SEM_ORCAMENTO",
              "SEM_RETORNO",
              "ADIADO",
              "OUTRO",
              null
            ]
          },
          "motivoPerdaDescricao": {
            "type": [
              "string",
              "null"
            ]
          },
          "fechadaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "escopo": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "ordem",
                "descricao",
                "horas",
                "modulo",
                "subModulo"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "ordem": {
                  "type": "integer"
                },
                "descricao": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "horas": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Horas (decimal)."
                },
                "modulo": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OportunidadePessoaRef"
                    },
                    {
                      "type": "null"
                    }
                  ]
                },
                "subModulo": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OportunidadePessoaRef"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          },
          "createdBy": {
            "$ref": "#/components/schemas/OportunidadePessoaRef"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "proposta": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePropostaSelo"
              },
              {
                "type": "null"
              }
            ]
          },
          "termometro": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "QUENTE",
              "MORNA",
              "FRIA",
              null
            ]
          },
          "termometroMotivo": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "AJUSTE_SOLICITADO",
              "ITEM_ALTERADO",
              "ABERTURAS_RECENTES",
              "VENCE_SEM_ABERTURA",
              "SEM_ABERTURA_RECENTE",
              "ABERTA",
              "AGUARDANDO_ABERTURA",
              null
            ]
          },
          "contrato": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadeContratoSelo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Nulo para o usuário restrito."
          },
          "permissoes": {
            "type": "object",
            "required": [
              "podeEditar",
              "podeMover",
              "podeTrocarPipeline",
              "podeReabrir"
            ],
            "properties": {
              "podeEditar": {
                "type": "boolean"
              },
              "podeMover": {
                "type": "boolean"
              },
              "podeTrocarPipeline": {
                "type": "boolean"
              },
              "podeReabrir": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "OportunidadeHistoricoItem": {
        "type": "object",
        "required": [
          "id",
          "origem",
          "acao",
          "entidadeTipo",
          "entidadeId",
          "texto",
          "dados",
          "autor",
          "destinatario",
          "createdAt",
          "editadaEm",
          "editadaPor",
          "podeAlterar"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "origem": {
            "type": "string",
            "enum": [
              "OPORTUNIDADE",
              "TAREFA",
              "PROPOSTA",
              "NOTA",
              "CONTRATO",
              "FORMULARIO"
            ]
          },
          "acao": {
            "type": "string",
            "description": "Código da ação (ex.: CRIADA, ETAPA_ALTERADA, NOTA_ADICIONADA). Lista não fechada."
          },
          "entidadeTipo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Tipo da entidade relacionada (ex.: \"proposta\", \"oportunidade_anexo\"), quando houver."
          },
          "entidadeId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "texto": {
            "type": [
              "string",
              "null"
            ]
          },
          "dados": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Detalhes da ação; a forma depende de `acao`."
          },
          "autor": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Usuário que agiu; nulo quando quem agiu foi o cliente na proposta pública (aí `destinatario` traz o contato) ou o próprio sistema (expiração automática, eventos do provedor de assinatura, falha técnica do formulário; aí `destinatario` também é nulo)."
          },
          "destinatario": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "Contato do cliente que agiu na proposta (só id e nome)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "editadaEm": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "editadaPor": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OportunidadePessoaRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "podeAlterar": {
            "type": "boolean",
            "description": "Só verdadeiro em `NOTA_ADICIONADA` do próprio autor (ou escopo total)."
          }
        }
      },
      "OportunidadeHistoricoPagina": {
        "type": "object",
        "required": [
          "items",
          "nextCursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OportunidadeHistoricoItem"
            }
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cursor para a próxima página; nulo na última."
          }
        }
      },
      "MetasMeta": {
        "type": "object",
        "description": "Meta mensal de um produto e classe.",
        "required": [
          "id",
          "produtoId",
          "produtoNome",
          "classe",
          "ano",
          "mes",
          "quantidadeMeta",
          "valorSaasMeta",
          "valorServicoMeta"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "produtoId": {
            "type": "string",
            "format": "uuid"
          },
          "produtoNome": {
            "type": "string",
            "description": "Vazio se o produto não tem nome."
          },
          "classe": {
            "type": "string",
            "enum": [
              "ADITIVO",
              "NOVO"
            ]
          },
          "ano": {
            "type": "integer"
          },
          "mes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "quantidadeMeta": {
            "type": "integer",
            "minimum": 0,
            "description": "Nº de vendas desejadas no mês."
          },
          "valorSaasMeta": {
            "type": "number",
            "minimum": 0,
            "description": "Meta de MRR do mês, em reais."
          },
          "valorServicoMeta": {
            "type": "number",
            "minimum": 0,
            "description": "Meta de valor de serviço do mês, em reais."
          }
        }
      },
      "MetasRealizadoOs": {
        "type": "object",
        "required": [
          "id",
          "numOs",
          "valorTotal",
          "dataFaturamento",
          "dataVencimento",
          "pagamentoStatus",
          "dataPagamento"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "numOs": {
            "type": "string"
          },
          "valorTotal": {
            "type": [
              "number",
              "null"
            ],
            "description": "Reais."
          },
          "dataFaturamento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dataVencimento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "pagamentoStatus": {
            "type": "string",
            "enum": [
              "PAGA",
              "PARCIAL",
              "PENDENTE",
              "SEM_RECEBIVEL"
            ]
          },
          "dataPagamento": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Data do recebimento no Omie."
          }
        }
      },
      "MetasRealizadoVenda": {
        "type": "object",
        "required": [
          "id",
          "origem",
          "status",
          "classe",
          "vendedorId",
          "vendedorNome",
          "clienteNome",
          "projetoId",
          "projetoNome",
          "produtoId",
          "produtoNome",
          "tipoProjetoId",
          "tipoProjetoNome",
          "metrica",
          "dataVenda",
          "dataAssinatura",
          "dataAssinaturaSaas",
          "dataAssinaturaServico",
          "valorSaasMensal",
          "valorServico",
          "valorComissao",
          "os"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id do formulário comercial."
          },
          "origem": {
            "type": "string",
            "enum": [
              "FORMULARIO"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "CONCLUIDO"
            ]
          },
          "classe": {
            "type": "string",
            "enum": [
              "ADITIVO",
              "NOVO"
            ]
          },
          "vendedorId": {
            "type": "string",
            "format": "uuid"
          },
          "vendedorNome": {
            "type": [
              "string",
              "null"
            ]
          },
          "clienteNome": {
            "type": [
              "string",
              "null"
            ]
          },
          "projetoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "projetoNome": {
            "type": [
              "string",
              "null"
            ]
          },
          "produtoId": {
            "type": "string",
            "format": "uuid",
            "description": "Produto principal."
          },
          "produtoNome": {
            "type": [
              "string",
              "null"
            ]
          },
          "tipoProjetoId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "tipoProjetoNome": {
            "type": [
              "string",
              "null"
            ]
          },
          "metrica": {
            "type": "string",
            "enum": [
              "MRR",
              "SERVICO"
            ],
            "description": "Evento exibido (o de data mais recente quando o formulário tem os dois)."
          },
          "dataVenda": {
            "type": "string",
            "format": "date-time",
            "description": "Data de referência do evento (igual a `dataAssinatura`)."
          },
          "dataAssinatura": {
            "type": "string",
            "format": "date-time"
          },
          "dataAssinaturaSaas": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "dataAssinaturaServico": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "valorSaasMensal": {
            "type": [
              "number",
              "null"
            ],
            "description": "MRR do SaaS, em reais."
          },
          "valorServico": {
            "type": [
              "number",
              "null"
            ],
            "description": "Valor de serviço, em reais."
          },
          "valorComissao": {
            "type": [
              "number",
              "null"
            ],
            "description": "Comissão da venda, em reais."
          },
          "os": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/MetasRealizadoOs"
              },
              {
                "type": "null"
              }
            ],
            "description": "Primeira OS paga ligada à comissão."
          }
        }
      },
      "MetasRealizadosLista": {
        "type": "object",
        "required": [
          "data",
          "pagination"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MetasRealizadoVenda"
            }
          },
          "pagination": {
            "type": "object",
            "required": [
              "page",
              "pageSize",
              "total",
              "totalPages"
            ],
            "properties": {
              "page": {
                "type": "integer"
              },
              "pageSize": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "totalPages": {
                "type": "integer"
              }
            }
          }
        }
      },
      "MetasDashboardValores": {
        "type": "object",
        "description": "Quatro medidas. Em `meta` e `realizado` são quantidades e reais; em `atingimento` são percentuais (0 a 100 ou mais, 1 casa decimal; 0 quando a meta é 0).",
        "required": [
          "quantidadeMrr",
          "saas",
          "quantidadeServico",
          "servico"
        ],
        "properties": {
          "quantidadeMrr": {
            "type": "number",
            "description": "Nº de vendas com MRR (inteiro em meta/realizado)."
          },
          "saas": {
            "type": "number",
            "description": "MRR em reais."
          },
          "quantidadeServico": {
            "type": "number",
            "description": "Nº de vendas com valor de serviço."
          },
          "servico": {
            "type": "number",
            "description": "Valor de serviço em reais."
          }
        }
      },
      "MetasDashboardMes": {
        "type": "object",
        "required": [
          "mes",
          "meta",
          "realizado",
          "atingimento"
        ],
        "properties": {
          "mes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "meta": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          },
          "realizado": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          },
          "atingimento": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          }
        }
      },
      "MetasDashboardTrimestre": {
        "type": "object",
        "required": [
          "tri",
          "meta",
          "realizado",
          "atingimento"
        ],
        "properties": {
          "tri": {
            "type": "integer",
            "minimum": 1,
            "maximum": 4
          },
          "meta": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          },
          "realizado": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          },
          "atingimento": {
            "$ref": "#/components/schemas/MetasDashboardValores"
          }
        }
      },
      "MetasDashboard": {
        "type": "object",
        "required": [
          "ano",
          "linhas"
        ],
        "properties": {
          "ano": {
            "type": "integer"
          },
          "linhas": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "produtoId",
                "produtoNome",
                "classe",
                "meses",
                "trimestres"
              ],
              "properties": {
                "produtoId": {
                  "type": "string",
                  "format": "uuid"
                },
                "produtoNome": {
                  "type": "string",
                  "description": "Vazio se o produto não tem nome."
                },
                "classe": {
                  "type": "string",
                  "enum": [
                    "ADITIVO",
                    "NOVO"
                  ]
                },
                "meses": {
                  "type": "array",
                  "minItems": 12,
                  "maxItems": 12,
                  "items": {
                    "$ref": "#/components/schemas/MetasDashboardMes"
                  }
                },
                "trimestres": {
                  "type": "array",
                  "minItems": 4,
                  "maxItems": 4,
                  "items": {
                    "$ref": "#/components/schemas/MetasDashboardTrimestre"
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Parâmetros inválidos. `message` é uma lista com um item por problema,\nno formato `campo: motivo`.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "status": 400,
              "path": "/oportunidades",
              "message": [
                "pageSize: Too small: expected number to be >=1"
              ]
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Token ausente, inválido, expirado ou de usuário desativado. Faça login de novo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "status": 401,
              "path": "/metas",
              "message": "Unauthorized"
            }
          }
        }
      },
      "Forbidden": {
        "description": "O usuário do token não tem a permissão exigida pela rota.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "status": 403,
              "path": "/financeiro/inadimplencia/resumo",
              "message": "Sem acesso ao financeiro."
            }
          }
        }
      },
      "NotFound": {
        "description": "Registro não encontrado (ou de outra organização).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Erro"
            },
            "example": {
              "status": 404,
              "path": "/oportunidades/00000000-0000-0000-0000-000000000000",
              "message": "Oportunidade não encontrada."
            }
          }
        }
      }
    }
  }
}
