API REST em JSON do sistema do Observatório de Turismo de Olímpia. Com a aplicação no ar,
a documentação interativa fica em http://localhost:8080/swagger-ui.html (botão
Authorize com o token do login), e o contrato OpenAPI em /v3/api-docs.
| Assunto | Regra |
|---|---|
| Endereço base | http://localhost:8080 em desenvolvimento |
| Datas | ISO 8601: "2026-02-28"; instantes em UTC: "2026-03-01T13:45:00Z"; horários "14:00:00" |
| Taxas e percentuais | de 0 a 100, com até duas casas (72.5 = 72,5%) |
| Valores em dinheiro | reais, com duas casas (410.00) |
| Fluxo de turistas | em pernoites (não são pessoas distintas), como na metodologia do OTO |
| Códigos | tipos de hospedagem, feriados e faixas vêm de GET /api/referencias. Nos cadastros, o código é opcional: sem ele, é gerado do nome ("Área de lazer" → AREA_DE_LAZER) |
| Campos sem valor | vêm como null |
POST /api/auth/logincom e-mail e senha devolve um token (JWT), válido por 8 horas.- Envie o token em todas as outras chamadas:
Authorization: Bearer <token>. - Token ausente, inválido ou vencido → 401. Nesse caso, volte para a tela de login.
POST /api/auth/login
Content-Type: application/json
{ "email": "ana@hotel.com", "senha": "minha-senha" }{
"token": "eyJhbGciOiJIUzI1NiJ9...",
"expiraEm": "2026-10-21T22:00:00Z",
"usuario": {
"id": 7, "nome": "Ana", "email": "ana@hotel.com", "perfil": "PARCEIRO", "ativo": true,
"primeiroAcessoPendente": false, "ultimoAcessoEm": "2026-10-21T14:00:00Z",
"estabelecimentos": [ { "id": 12, "nomeFantasia": "Hotel Exemplo" } ]
}
}Primeiro acesso e senha esquecida. Não há envio de e-mail: o ADMIN cadastra o usuário e
recebe um link de acesso (convite) para mandar ao parceiro (por WhatsApp, como o OTO já faz).
O front monta o endereço da página de senha com o token, por exemplo
https://<front>/definir-senha?token=<token>, e essa página chama:
POST /api/auth/definir-senha
{ "token": "<token do link>", "novaSenha": "pelo-menos-8-caracteres" }Resposta 204. O convite vale 7 dias e o link de redefinição, 24 horas; cada link só pode ser
usado uma vez. Para um link novo: POST /api/usuarios/{id}/link-de-senha (ADMIN).
| Perfil | O que pode fazer |
|---|---|
ADMIN |
Tudo: usuários, inventário, pesquisas, publicação, parâmetros, série histórica. Também digita à mão qualquer dado que normalmente vem da pesquisa (veja Preenchimento manual) |
PARCEIRO |
Ver e editar o inventário dos seus estabelecimentos, responder às pesquisas abertas, ver os resultados gerais e as próprias respostas |
VISUALIZADOR |
Ver inventário, resultados e dashboards. Nunca altera nada e não vê respostas individuais |
Resposta individual de ocupação é sigilosa: só o ADMIN e o próprio parceiro a veem. Nos
resultados publicados, para quem não é ADMIN, os tipos de hospedagem com menos de 3
respondentes vêm com os valores nulos e "omitido": true.
Todos os erros vêm no formato Problem Details (RFC 9457):
{ "status": 422, "title": "Operação não permitida", "detail": "A pesquisa 005/26 não está aberta para respostas." }| Status | Quando | O que mostrar |
|---|---|---|
| 400 | Corpo ou parâmetro inválido. Traz campos com a mensagem de cada campo |
Marcar os campos do formulário |
| 401 | Sem login, token vencido, ou e-mail/senha errados no login | Voltar ao login |
| 403 | O perfil não pode fazer isso, ou o estabelecimento não é do parceiro | "Sem permissão" |
| 404 | Registro não existe | "Não encontrado" |
| 409 | Registro repetido no banco (caso raro, normalmente vira 422 antes) | detail |
| 422 | Regra do OTO (ex.: pesquisa fechada, CNPJ repetido, mínimo de leitos para publicar) | detail |
Exemplo de 400 (as mensagens seguem o idioma do navegador, pelo cabeçalho Accept-Language):
{
"status": 400, "title": "Dados inválidos", "detail": "Corrija os campos indicados.",
"campos": { "dados.nomeFantasia": "não deve estar em branco", "capacidade.qtdUh": "deve ser maior que 0" }
}Todo dado de entrada pode ser digitado ou corrigido à mão pelo perfil ADMIN, além do que
chega pela pesquisa ou pelo parceiro:
| Dado | Pela pesquisa ou pelo parceiro | À mão pela equipe (ADMIN) |
|---|---|---|
| Respostas de ocupação (taxa e diária) | Parceiro, com a pesquisa aberta | PUT /api/campanhas/{id}/respostas/{estabelecimentoId}, também depois do encerramento, até publicar |
| Diária média por tipo e geral | Média das respostas | PUT /api/campanhas/{id}/diarias (substitui a média no cálculo) |
| Dados do estabelecimento | Parceiro altera os seus | POST e PUT /api/estabelecimentos |
| Capacidade (UH e leitos) | Parceiro inclui nova vigência | Inclui, corrige ou exclui um lançamento: /api/estabelecimentos/{id}/capacidades |
| Itens do inventário | Parceiro marca os seus | PUT /api/estabelecimentos/{id}/inventario, inclusive os grupos da Secretaria |
| Catálogo do inventário (perguntas) | — | /api/inventario/grupos e /api/inventario/itens |
| Hospedagem caseira (RHC) | — | POST, PUT e DELETE /api/rhc |
| Parâmetros de cálculo | — | POST, PUT e DELETE /api/parametros-calculo |
| Tipos de hospedagem, feriados e faixas | — | /api/referencias/tipos-hospedagem, /feriados e /faixas |
| Série histórica | Importação de CSV | POST, PUT e DELETE /api/historico |
| Usuários, pesquisas e campanhas | — | /api/usuarios e /api/rodadas |
Não são digitados: os resultados (taxa, fluxo, receita), que sempre saem do cálculo: para mudar um número, corrija o dado de entrada e publique de novo (cancele a publicação antes, se já estiver publicada). Também ficam fora a auditoria, o histórico de versões das respostas, os links de acesso e as senhas (cada usuário define a sua pelo link).
Correções valem para o que ainda vai ser calculado. Resultados já publicados guardam os números da publicação e não mudam sozinhos.
| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/referencias |
todos | Tipos de hospedagem, feriados e faixas de ocupação (para selects e legendas) |
| POST | /api/referencias/tipos-hospedagem |
ADMIN | Novo tipo (201) |
| PUT · DELETE | /api/referencias/tipos-hospedagem/{codigo} |
ADMIN | Altera nome, calculaReceita e ordem; exclui só se nunca foi usado (204) |
| POST | /api/referencias/feriados |
ADMIN | Novo feriado ou evento (201) |
| PUT · DELETE | /api/referencias/feriados/{codigo} |
ADMIN | Altera nome e ordem; exclui só se nunca foi usado (204) |
| POST | /api/referencias/faixas |
ADMIN | Nova faixa de ocupação (201) |
| PUT · DELETE | /api/referencias/faixas/{codigo} |
ADMIN | Altera nome e percentual mínimo; exclui só se nunca foi usada em publicação (204) |
| GET | /api/me |
todos | Dados do usuário logado (inclui os estabelecimentos do parceiro) |
| PUT | /api/me/senha |
todos | Troca de senha: { "senhaAtual": "...", "novaSenha": "..." } → 204 |
// GET /api/referencias (trecho)
{ "tiposHospedagem": [ { "codigo": "HOTEL", "nome": "Hotel", "calculaReceita": true, "ordem": 1 } ],
"feriados": [ { "codigo": "CARNAVAL", "nome": "Carnaval", "ordem": 1 } ],
"faixasOcupacao": [ { "codigo": "RUIM", "nome": "Ruim", "percentualMinimo": 0.00 } ] }
// POST /api/referencias/tipos-hospedagem — sem código, vira CHALE; sem ordem, vai para o fim
{ "codigo": null, "nome": "Chalé", "calculaReceita": false, "ordem": null }
// POST /api/referencias/feriados
{ "codigo": null, "nome": "Festa Junina", "ordem": null }
// POST /api/referencias/faixas
{ "codigo": null, "nome": "Média", "percentualMinimo": 50.00 }O código não muda depois de criado (é a referência usada nos estabelecimentos, campanhas,
resultados e série histórica); no PUT, mande o mesmo código ou null. calculaReceita diz se
o tipo entra na diária, no RevPAR e na receita. Nas faixas, cada percentual mínimo é único e a
primeira faixa começa sempre em 0% (dela só o nome muda). Excluir algo já usado devolve 422.
| Método | Caminho | Descrição |
|---|---|---|
| GET | /api/usuarios |
Lista |
| GET | /api/usuarios/{id} |
Detalhe |
| POST | /api/usuarios |
Cria e devolve o convite (201) |
| PUT | /api/usuarios/{id} |
Altera nome, perfil e estabelecimentos |
| POST | /api/usuarios/{id}/inativar · /reativar |
204. Usuário inativo não entra |
| POST | /api/usuarios/{id}/link-de-senha |
Novo link de acesso (convite ou redefinição) |
// POST /api/usuarios — PARCEIRO precisa de ao menos um estabelecimento; os outros perfis, de nenhum
{ "nome": "Ana Souza", "email": "ana@hotel.com", "perfil": "PARCEIRO", "estabelecimentoIds": [12] }
// 201
{ "usuario": { "id": 7, "nome": "Ana Souza", "...": "..." },
"convite": { "token": "f3K9...", "tipo": "CONVITE", "expiraEm": "2026-10-28T14:00:00Z" } }| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/estabelecimentos?tipo=HOTEL&ativo=true |
todos | Lista resumida (o parceiro só vê os seus). UH e leitos da capacidade vigente hoje |
| GET | /api/estabelecimentos/{id} |
todos | Detalhe completo |
| POST | /api/estabelecimentos |
ADMIN | Cria com a capacidade inicial (201) |
| PUT | /api/estabelecimentos/{id} |
ADMIN, PARCEIRO | Altera. O bloco secretaria só vale para o ADMIN |
| POST | /api/estabelecimentos/{id}/inativar · /reativar |
ADMIN | 204. Não há exclusão |
| GET | /api/estabelecimentos/{id}/capacidades |
todos | Histórico de UH e leitos |
| POST | /api/estabelecimentos/{id}/capacidades |
ADMIN, PARCEIRO | Nova capacidade a partir de uma data (201) |
| PUT | /api/estabelecimentos/{id}/capacidades/{capacidadeId} |
ADMIN | Corrige um lançamento digitado errado (mesmo corpo do POST) |
| DELETE | /api/estabelecimentos/{id}/capacidades/{capacidadeId} |
ADMIN | Exclui um lançamento feito por engano (204). A única capacidade não sai |
| GET | /api/estabelecimentos/{id}/inventario |
todos | Itens que o estabelecimento possui |
| PUT | /api/estabelecimentos/{id}/inventario |
ADMIN, PARCEIRO | Substitui os itens marcados |
| GET | /api/inventario/catalogo |
todos | Perguntas do inventário (grupos e itens ativos) para montar o formulário |
// POST /api/estabelecimentos — todos os campos de "dados" são opcionais, menos nomeFantasia
{
"dados": {
"nomeFantasia": "Hotel Exemplo", "razaoSocial": "Exemplo Hotelaria Ltda", "cnpj": "12345678000190",
"endereco": "Rua das Águas, 100", "bairro": "Centro", "telefoneComercial": "17 3281-0000",
"emailComercial": "contato@hotelexemplo.com", "site": "https://hotelexemplo.com",
"cadasturExigencia": "OBRIGATORIO", "possuiCadastur": true, "cadasturNumero": "12.345678.10.0001-2",
"funcionariosFixos": 25, "funcionamento24h": true, "horarioCheckIn": "14:00:00", "horarioCheckOut": "12:00:00"
},
"secretaria": {
"tipoHospedagemCodigo": "HOTEL", "situacao": "REGULAR", "contatoNome": "Maria",
"aceitaPesquisas": true, "telefonePesquisas": "17 99999-0000", "emailPesquisas": "gerencia@hotelexemplo.com",
"destinoInteligente": false, "dadosPddt": false
},
"capacidade": { "vigenteDesde": "2026-01-01", "qtdUh": 40, "qtdUhPcd": 2, "qtdLeitos": 120, "maxLeitosPorUh": 4 }
}Campos de dados (todos do inventário oficial): nomeFantasia, razaoSocial, cnpj (14 dígitos, sem
pontuação), dataAbertura, proprietarios, cnae, endereco, bairro, regiao, localizacao,
latitude, longitude, telefoneComercial, emailComercial, site, facebook, instagram,
cadasturExigencia (OBRIGATORIO, OPCIONAL, NAO_CONSTA), possuiCadastur, cadasturNumero,
cadasturValidade, funcionariosFixos, funcionariosTemporarios, funcionamento24h, horarioCheckIn,
horarioCheckOut, formasReserva, formasPagamento.
situacao: REGULAR ou IRREGULAR (irregular fica fora das pesquisas e dos cálculos).
maxLeitosPorUh é a capacidade máxima de pessoas por UH; os leitos úteis do cálculo são
UH × (1 + máximo) / 2, e a resposta já traz leitosUteis.
// PUT /api/estabelecimentos/{id}/inventario — a lista completa do que o estabelecimento possui
{ "itens": [ { "itemId": 1, "complemento": null }, { "itemId": 6, "complemento": "Alemão" } ] }O que não vier na lista é desmarcado. complemento só vale para itens com
permiteComplemento: true (como "Outros"). Para o PARCEIRO, os grupos com
exclusivoSecretaria: true (indicadores ODS) ficam como estão, mesmo que venham na lista.
As perguntas do inventário (grupos e itens) são mantidas pela equipe, sem mexer no banco.
| Método | Caminho | Descrição |
|---|---|---|
| GET | /api/inventario/catalogo/completo |
Catálogo inteiro, com o que está inativo e os grupos ainda sem itens |
| POST | /api/inventario/grupos |
Novo grupo (201) |
| PUT | /api/inventario/grupos/{id} |
Altera o grupo; devolve o grupo com os itens |
| POST | /api/inventario/grupos/{id}/inativar · /reativar |
204. Grupo inativo sai do formulário |
| DELETE | /api/inventario/grupos/{id} |
Só grupo sem itens (204) |
| POST | /api/inventario/grupos/{grupoId}/itens |
Novo item no grupo (201) |
| PUT | /api/inventario/itens/{id} |
Altera o item |
| POST | /api/inventario/itens/{id}/inativar · /reativar |
204. Item inativo sai do formulário |
| DELETE | /api/inventario/itens/{id} |
Só item que nenhum estabelecimento marcou (204) |
// POST /api/inventario/grupos — categoria: SERVICOS_EQUIPAMENTOS, ACESSIBILIDADE ou SUSTENTABILIDADE
{ "categoria": "SERVICOS_EQUIPAMENTOS", "codigo": null, "nome": "Área de lazer",
"exclusivoSecretaria": false, "ordem": null }
// POST /api/inventario/grupos/28/itens
{ "codigo": null, "nome": "Outros", "descricao": "Quais?", "permiteComplemento": true, "ordem": null }
// 201 (item)
{ "id": 160, "codigo": "OUTROS", "nome": "Outros", "descricao": "Quais?", "permiteComplemento": true,
"ordem": 1, "ativo": true }Sem codigo, ele é gerado do nome; sem ordem, o novo vai para o fim e o alterado fica onde
está. No catálogo, grupos e itens também trazem ordem e ativo. O que os estabelecimentos
marcaram em um item inativo fica guardado e volta se ele for reativado. Um item com texto
digitado pelos estabelecimentos não pode deixar de aceitar complemento: crie outro e inative o antigo.
| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/rhc |
ADMIN, VISUALIZADOR | Totais por vigência |
| GET | /api/rhc/vigente?data=2026-02-28 |
ADMIN, VISUALIZADOR | Totais vigentes na data (padrão: hoje) |
| POST | /api/rhc |
ADMIN | { "vigenteDesde", "qtdRegistros", "qtdUh", "qtdLeitos", "maxLeitosPorUh" } (201) |
| PUT | /api/rhc/{id} |
ADMIN | Corrige um lançamento digitado errado (mesmo corpo do POST) |
| DELETE | /api/rhc/{id} |
ADMIN | Exclui um lançamento (204) |
| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/parametros-calculo |
ADMIN, VISUALIZADOR | Todas as vigências |
| GET | /api/parametros-calculo/vigente?data= |
todos | Vigentes na data (para notas metodológicas) |
| POST | /api/parametros-calculo |
ADMIN | Nova vigência (201) |
| PUT | /api/parametros-calculo/{id} |
ADMIN | Corrige a vigência, só se nenhum resultado publicado a usou (mesmo corpo do POST) |
| DELETE | /api/parametros-calculo/{id} |
ADMIN | Só se nenhum resultado publicado usou (204) |
{ "vigenteDesde": "2027-01-01", "percentualDayUse": 15.15, "diasPermanenciaMedia": 4, "diasEmParques": 2,
"gastoMedioDiario": 250.00, "gastoMedioIngresso": 105.00, "percentualMinimoLeitosPublicacao": 70.00,
"fonte": "Pesquisa de Demanda Turística 2026" }Uma pesquisa (rodada, ex.: 005/26) é o envio aos parceiros e junta uma ou mais
campanhas (cada período medido: um mês ou um feriado, consolidado ou expectativa).
Ciclo: RASCUNHO → ABERTA (recebe respostas; o parceiro pode corrigir) → ENCERRADA
(as campanhas podem ser publicadas). Uma pesquisa encerrada pode ser reaberta enquanto
nenhuma campanha dela estiver publicada.
| Método | Caminho | Descrição |
|---|---|---|
| GET | /api/rodadas?status=ABERTA |
Lista, com as campanhas |
| GET | /api/rodadas/{id} |
Detalhe |
| POST | /api/rodadas |
Cria em rascunho (201): { "codigo": "005/26", "titulo": "...", "dataAbertura": null, "prazoResposta": null } |
| PUT | /api/rodadas/{id} |
Altera (só em rascunho) |
| DELETE | /api/rodadas/{id} |
Exclui (só em rascunho) |
| POST | /api/rodadas/{id}/campanhas |
Inclui campanha (só em rascunho, 201) |
| PUT · DELETE | /api/rodadas/{id}/campanhas/{campanhaId} |
Altera ou exclui campanha (só em rascunho) |
| POST | /api/rodadas/{id}/abrir |
{ "dataAbertura": null, "prazoResposta": "2026-03-10" } (sem data de abertura, vale hoje) |
| POST | /api/rodadas/{id}/prorrogar |
{ "prazoResposta": "2026-03-15" } |
| POST | /api/rodadas/{id}/encerrar |
Fecha para respostas |
| POST | /api/rodadas/{id}/reabrir |
{ "prazoResposta": "2026-03-20" } |
| GET | /api/rodadas/{id}/acompanhamento |
Quem respondeu e quem falta, com os contatos e o % de leitos respondentes |
// POST /api/rodadas/{id}/campanhas — campanha mensal: do primeiro ao último dia do mês
{ "nome": "Consolidada fevereiro/2026", "modalidade": "CONSOLIDADA", "tipoPeriodo": "MENSAL",
"feriadoCodigo": null, "periodoInicio": "2026-02-01", "periodoFim": "2026-02-28", "qtdDiarias": 28 }
// campanha de feriado
{ "nome": "Carnaval 2026", "modalidade": "CONSOLIDADA", "tipoPeriodo": "FERIADO",
"feriadoCodigo": "CARNAVAL", "periodoInicio": "2026-02-13", "periodoFim": "2026-02-17", "qtdDiarias": 5 }| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/respostas/pendencias |
PARCEIRO | Campanhas abertas para cada estabelecimento do parceiro, respondidas ou não (tela "Responder pesquisa") |
| PUT | /api/campanhas/{campanhaId}/respostas/{estabelecimentoId} |
ADMIN, PARCEIRO | Envia ou corrige a resposta; vale o último envio |
| GET | /api/respostas/minhas |
PARCEIRO | Respostas do parceiro com a taxa geral da cidade, quando publicada |
| GET | /api/respostas/{id}/historico |
ADMIN, PARCEIRO | Todas as versões enviadas |
| GET | /api/campanhas/{campanhaId}/respostas |
ADMIN | Respostas da campanha (dado individual) |
// PUT /api/campanhas/20/respostas/12 — diariaMedia é opcional
{ "taxaOcupacao": 72.5, "diariaMedia": 410.00 }
// 200
{ "id": 301, "campanhaId": 20, "estabelecimentoId": 12, "nomeFantasia": "Hotel Exemplo",
"taxaOcupacao": 72.50, "diariaMedia": 410.00, "versao": 2, "enviadoPor": 7, "enviadoEm": "2026-03-05T18:20:00Z" }O ADMIN também lança respostas recebidas por telefone ou WhatsApp (fica registrado quem enviou).
O parceiro responde enquanto a pesquisa está ABERTA; o ADMIN, também depois do encerramento,
enquanto a campanha não for publicada. Só estabelecimentos ativos e regulares respondem.
| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/campanhas/{id}/previa |
ADMIN | Simula o resultado com as respostas atuais e diz o que ainda impede a publicação |
| POST | /api/campanhas/{id}/publicacao |
ADMIN | Calcula e grava o resultado. Exige a pesquisa encerrada e o mínimo de leitos respondentes (70%) |
| DELETE | /api/campanhas/{id}/publicacao |
ADMIN | Cancela uma publicação feita por engano (204) |
| GET | /api/campanhas/{id}/resultado |
todos | Resultado publicado (404 se ainda não foi publicado) |
| GET | /api/campanhas/{id}/resultado.csv |
todos | O mesmo em CSV para Excel (;, vírgula decimal) |
| GET | /api/campanhas/publicadas?modalidade=&tipoPeriodo=&ano= |
todos | Campanhas publicadas, mais recentes primeiro |
| GET | /api/campanhas/{id}/diarias |
ADMIN | Diárias médias digitadas pela equipe para a campanha |
| PUT | /api/campanhas/{id}/diarias |
ADMIN | Substitui as diárias digitadas (só antes da publicação) |
Diária digitada pela equipe. Como na planilha de tabulação, a equipe pode informar a diária média de cada tipo e a geral (ex.: da pesquisa de tarifas no Booking). A diária digitada substitui a média das respostas no cálculo do tipo (RevPAR e receita) e na diária geral; a prévia avisa quais estão valendo. A lista enviada substitui a anterior; lista vazia volta tudo para a média das respostas.
// PUT /api/campanhas/20/diarias — tipoHospedagemCodigo null = diária média geral
{ "diarias": [ { "tipoHospedagemCodigo": "HOTEL", "diariaMedia": 480.00 },
{ "tipoHospedagemCodigo": null, "diariaMedia": 510.00 } ],
"fonte": "Pesquisa de tarifas Booking 02/2026" }
// 200 — a geral vem primeiro
[ { "tipoHospedagemCodigo": null, "diariaMedia": 510.00, "fonte": "Pesquisa de tarifas Booking 02/2026",
"registradoPor": 1, "registradoEm": "2026-03-11T14:00:00Z" },
{ "tipoHospedagemCodigo": "HOTEL", "diariaMedia": 480.00, "fonte": "Pesquisa de tarifas Booking 02/2026",
"registradoPor": 1, "registradoEm": "2026-03-11T14:00:00Z" } ]// GET /api/campanhas/20/previa
{
"campanhaId": 20, "qtdMh": 74, "qtdRespondentes": 41, "leitosTotal": 13508, "leitosRespondentes": 9720,
"percentualLeitosRespondentes": 71.96, "percentualMinimoPublicacao": 70.00, "podePublicar": false,
"impedimentos": [ "A pesquisa 005/26 precisa ser encerrada antes da publicação." ],
"avisos": [ "Hotel X: sem UH/leitos cadastrados para 2026-02-28; fica fora do cálculo." ],
"resultado": { "...": "mesmo formato do resultado publicado" }
}
// GET /api/campanhas/20/resultado
{
"campanha": { "id": 20, "rodadaId": 5, "nome": "Consolidada fevereiro/2026", "modalidade": "CONSOLIDADA",
"tipoPeriodo": "MENSAL", "feriadoCodigo": null, "periodoInicio": "2026-02-01",
"periodoFim": "2026-02-28", "qtdDiarias": 28, "publicada": true, "publicadaEm": "2026-03-12T15:00:00Z" },
"publicado": true,
"taxaOcupacao": 64.73, "faixa": { "codigo": "OTIMA", "nome": "Ótima" },
"qtdMh": 74, "qtdMhRespondentes": 41, "percentualMhRespondentes": 55.41,
"leitosTotal": 13508, "leitosRespondentes": 9720, "percentualLeitosRespondentes": 71.96,
"fluxoMh": 58931.79, "fluxoRhc": 9282.65, "fluxoDayUse": 12179.71, "fluxoTotal": 80394.15, "fluxoMedioDiario": 16078.83,
"diariaMedia": 812.40, "receitaHospedagem": 17137719.18, "receitaSemHospedagem": 97276924.77,
"parametroCalculoId": 1, "calculadoEm": "2026-03-12T15:00:00Z",
"porTipo": [
{ "tipoHospedagemCodigo": "HOTEL", "nome": "Hotel", "qtdMh": 20, "qtdMhRespondentes": 9, "qtdUh": 980,
"taxaOcupacao": 40.35, "diariaMedia": 657.13, "revpar": 265.12, "receitaEstimada": 776805.30, "omitido": false },
{ "tipoHospedagemCodigo": "HOTEL_FAZENDA", "nome": "Hotel fazenda", "qtdMh": 2, "qtdMhRespondentes": 1, "qtdUh": 60,
"taxaOcupacao": null, "diariaMedia": null, "revpar": null, "receitaEstimada": null, "omitido": true }
],
"distribuicaoFaixas": [
{ "codigo": "EXCELENTE", "nome": "Excelente", "percentualMinimo": 81.00, "qtdMh": 6, "percentual": 20.00 },
{ "codigo": "OTIMA", "nome": "Ótima", "percentualMinimo": 61.00, "qtdMh": 6, "percentual": 20.00 }
]
}Como o OTO calcula (igual à planilha de tabulação): taxa geral = média das taxas dos respondentes ponderada pelas UH; fluxo em MH = leitos úteis × taxa × diárias de cada respondente, mais os leitos úteis dos não respondentes × taxa geral × diárias; RHC = leitos úteis do RHC × taxa geral × diárias; day use = parcela do dia que não pernoita (15,15% hoje); RevPAR = diária × taxa; receita por tipo = RevPAR × UH do tipo × diárias (hotel, resort e pousada); receita fora da hospedagem = fluxo total × (ingresso × dias em parques + gasto diário × dias de permanência). A diária de cada tipo é a média das respostas ponderada pelos leitos, ou a digitada pela equipe. O resultado fica congelado na publicação.
Só valores gerais da cidade. Junta os resultados publicados no sistema com a série histórica;
quando os dois têm o mesmo período, vale o do sistema. Cada valor traz origem: SISTEMA ou HISTORICO.
| Método | Caminho | Uso na tela inicial |
|---|---|---|
| GET | /api/dashboard/resumo?modalidade=CONSOLIDADA |
Cartões: último mês e último feriado publicados, com o comparativo do ano anterior e o acumulado do ano |
| GET | /api/dashboard/serie-mensal?indicador=TAXA_OCUPACAO&modalidade=CONSOLIDADA&anoInicio=2024&anoFim=2026 |
Gráfico de linhas mês a mês, uma linha por ano |
| GET | /api/dashboard/feriados?modalidade=CONSOLIDADA&feriado=CARNAVAL&anoInicio=2024&anoFim=2026 |
Gráfico de barras dos feriados por ano (sem feriado, todos os que têm dados) |
indicador: TAXA_OCUPACAO, FLUXO_TURISTAS ou DIARIA_MEDIA. Sem anos, o intervalo é dos
últimos três anos.
// GET /api/dashboard/resumo
{
"modalidade": "CONSOLIDADA",
"ultimoMes": {
"campanhaId": 21, "nome": "Consolidada fevereiro/2026", "tipoPeriodo": "MENSAL", "feriadoCodigo": null,
"periodoInicio": "2026-02-01", "periodoFim": "2026-02-28", "taxaOcupacao": 66.00,
"faixa": { "codigo": "OTIMA", "nome": "Ótima" }, "fluxoTotal": 90000.00, "fluxoMedioDiario": 3214.29,
"diariaMedia": 512.00, "receitaHospedagem": 1234567.00, "receitaSemHospedagem": 108900000.00,
"percentualLeitosRespondentes": 75.00,
"anoAnterior": { "ano": 2025, "taxaOcupacao": 55.00, "fluxoTotal": 80000.00, "diariaMedia": 400.00,
"diferencaTaxaPontos": 11.00, "variacaoFluxoPercentual": 12.50,
"variacaoDiariaPercentual": 28.00, "origem": "HISTORICO" }
},
"ultimoFeriado": { "...": "mesmo formato" },
"acumuladoAno": { "ano": 2026, "mesesComTaxa": 2, "taxaOcupacaoMedia": 68.00, "mesesComFluxo": 2, "fluxoTotal": 190000.00 }
}
// GET /api/dashboard/serie-mensal (trecho)
{ "indicador": "TAXA_OCUPACAO", "modalidade": "CONSOLIDADA",
"anos": [ { "ano": 2026, "meses": [ { "mes": 1, "valor": 70.00, "origem": "SISTEMA" },
{ "mes": 3, "valor": null, "origem": null } ] } ] }| Método | Caminho | Perfil | Descrição |
|---|---|---|---|
| GET | /api/historico?indicador=&modalidade=&ano=&tipo= |
todos | Valores importados |
| POST | /api/historico |
ADMIN | Grava um valor (substitui o da mesma combinação) |
| PUT | /api/historico/{id} |
ADMIN | Corrige um valor, inclusive o indicador, o período e o tipo (mesmo corpo do POST) |
| DELETE | /api/historico/{id} |
ADMIN | Exclui um valor (204) |
| POST | /api/historico/importacao |
ADMIN | Importa CSV (multipart: arquivo e fonte) |
// POST /api/historico — informe o mês OU o feriado; sem tipo = valor geral
{ "indicador": "TAXA_OCUPACAO", "modalidade": "CONSOLIDADA", "ano": 2024, "mes": 7,
"feriadoCodigo": null, "tipoHospedagemCodigo": null, "valor": 68.40, "observacao": null,
"fonte": "Indicadores do Turismo 2017-2025" }CSV da importação (como o Excel em português salva: ; e vírgula decimal; UTF-8 ou o
padrão do Windows). O cabeçalho é opcional:
indicador;modalidade;ano;mes;feriado;tipo;valor;observacao
TAXA_OCUPACAO;CONSOLIDADA;2024;1;;;63,45;
FLUXO_TURISTAS;CONSOLIDADA;2024;;CARNAVAL;;98.512;
DIARIA_MEDIA;CONSOLIDADA;2024;7;;HOTEL;412,90;tarifa de balcão
Ponto é separador de milhar (98.512 = 98512) e vírgula, de decimal. Com qualquer erro, nada é
gravado e a resposta é 422 com a lista de erros por linha:
{ "linhasLidas": 3, "inseridos": 0, "atualizados": 0,
"erros": [ { "linha": 3, "mensagem": "feriado inválido: CARNAVALL (veja /api/referencias)" } ] }Sem erros, 200 com inseridos e atualizados (gravar de novo a mesma combinação atualiza o valor).
- ADMIN cria a pesquisa (
POST /api/rodadas) e inclui as campanhas (POST /api/rodadas/{id}/campanhas). - ADMIN abre (
POST /api/rodadas/{id}/abrir) e avisa os parceiros. - Parceiro entra, vê
GET /api/respostas/pendenciase responde comPUT /api/campanhas/{campanhaId}/respostas/{estabelecimentoId}. Pode corrigir até o encerramento. - ADMIN acompanha em
GET /api/rodadas/{id}/acompanhamentoe confereGET /api/campanhas/{id}/previa. Se quiser, digita as diárias (PUT /api/campanhas/{id}/diarias). - ADMIN encerra (
POST /api/rodadas/{id}/encerrar), lança as respostas que chegaram depois por telefone ou WhatsApp (mesmoPUTdas respostas) e publica cada campanha (POST /api/campanhas/{id}/publicacao). - O resultado aparece em
GET /api/campanhas/{id}/resultadoe nos dashboards.