{
 "openapi": "3.0.3",
 "info": {
  "title": "Orbit Precisão — API pública do tiro de precisão",
  "version": "1.0.0",
  "description": "API REST das provas de **tiro de precisão** (fogo circular /\nbullseye): provas, modalidades, séries, turnos, inscrições, juízes, ranking\ne a fita alvo a alvo de cada atleta.\n\nTudo é escopado à entidade da chave e endereçado por **uuid** — nenhuma rota\naceita ou devolve id interno do banco.\n\n## O esporte em uma linha\nAlvo fixo com anéis concêntricos (X, 10, 9, …, 1; fora = 0), N alvos de prova\npor série e M disparos por alvo. Pontuação é a soma; **X vale 10 pontos** e\nconta em separado para o desempate.\n\n## Autenticação\nÉ a **mesma chave** das APIs do IPSC e do Clay. Crie em **Entidade → Chaves\nde API** (somente owner) e envie em cada request:\n\n```\nAuthorization: Bearer <access_key>.<secret>\n```\n\nA secret aparece **uma única vez** na criação/regeneração. Escopo `leitura`\npermite apenas GET; `leitura_escrita` libera escrita.\n\n## Módulo habilitado\nTodas as rotas exigem o módulo ligado para a entidade. Sem isso a resposta é\n**404** (e não 403): a chave é válida, o recurso é que não existe para essa\nentidade. Peça ao clube que habilite o Tiro de Precisão.\n\n## Os três eixos da prova\n- **Modalidade** é o percurso de fogo (alvos × disparos × séries). No\n  cadastro do clube ela é reutilizável; **dentro da prova é um snapshot** —\n  editar o cadastro depois não muda prova montada, e por isso o resultado de\n  prova encerrada nunca muda. A inscrição aponta a modalidade da prova, e o\n  ranking agrupa por ela: é a \"divisão\" do módulo.\n- **Série** é o eixo de tempo (a rodada).\n- **Turno** (bancada) é o eixo de espaço: quem atira junto, em que posto.\n\n**Uma inscrição por modalidade.** O mesmo atleta pode atirar Carabina\nStandard e PCP na mesma prova — são duas inscrições.\n\n## Score não se escreve por aqui\nA fita nasce assinada no campo (HMAC do juiz, encadeado com o PIN do atleta\nquando a prova exige). Um POST de score criaria resultado sem essa cadeia,\nque é justamente o que o módulo existe para impedir. A API **lê** o\nresultado; quem o produz é o juiz na tela de lançamento. Pelo mesmo motivo a\nAPI nunca emite nem devolve o PIN de um atleta.\n\n## Congelamento\nO que já foi assinado não se reescreve. Com score lançado devolvem **409**:\no critério de desempate e a regra de assinatura da prova, o percurso de fogo\nda modalidade, o número da série, a modalidade da inscrição e a exclusão de\ninscrição (use `cancelada`). Prova encerrada congela a estrutura inteira.\n\n## Paginação\nListagens aceitam `?page` (1-based) e `?per_page` (padrão 25, máx. 100; nas\ninscrições padrão 50, máx. 200) e devolvem `page`, `per_page` e `total`.\n\n## Rate limit\nPor chave (padrão 120 req/min), com headers `X-RateLimit-Limit`,\n`X-RateLimit-Remaining` e `X-RateLimit-Reset`; excedido devolve **429** com\n`Retry-After`.\n\n## Erros\nSempre JSON: `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}`. Códigos:\n`unauthorized`, `forbidden`, `not_found`, `conflict`, `confirmation_required`,\n`validation_error`, `invalid_json`, `rate_limited`, `internal_error`."
 },
 "servers": [
  {
   "url": "/api/v1"
  }
 ],
 "security": [
  {
   "bearerAuth": []
  }
 ],
 "tags": [
  {
   "name": "Provas",
   "description": "A prova de precisão: datas, publicação, critério de desempate e a regra de assinatura."
  },
  {
   "name": "Modalidades",
   "description": "O percurso de fogo — no cadastro do clube e como snapshot dentro da prova. É o eixo do ranking."
  },
  {
   "name": "Séries",
   "description": "A rodada da prova. Eixo de tempo; o percurso vem da modalidade."
  },
  {
   "name": "Turnos",
   "description": "Bancadas de tiro com horário e limite de vagas. Eixo de espaço."
  },
  {
   "name": "Inscrições",
   "description": "Atletas na prova, uma inscrição por modalidade, ligados ao mesmo cadastro que IPSC e Clay usam."
  },
  {
   "name": "Juízes",
   "description": "Juízes/anotadores de campo do módulo (cadastro próprio, separado do RO do IPSC e do juiz do Clay)."
  },
  {
   "name": "Resultados",
   "description": "Ranking, fita alvo a alvo, links públicos e auditoria. Somente leitura."
  },
  {
   "name": "Campeonatos",
   "description": "Temporada: etapas, soma com descartes e ranking agregado."
  },
  {
   "name": "Catálogo",
   "description": "Categorias e as classes de cada uma, com as regras de enquadramento (idade, sexo e faixa)."
  }
 ],
 "paths": {
  "/precisao/provas": {
   "get": {
    "tags": [
     "Provas"
    ],
    "summary": "Lista as provas de precisão",
    "description": "Escopada à entidade da chave, mais recente primeiro.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "status",
      "in": "query",
      "description": "Filtra por status.",
      "schema": {
       "type": "string",
       "enum": [
        "rascunho",
        "aberta",
        "andamento",
        "encerrada"
       ]
      }
     },
     {
      "name": "external_id",
      "in": "query",
      "description": "Reencontra a prova pelo id do SEU sistema, sem guardar o uuid nosso.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "provas": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProvaResumo"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Provas"
    ],
    "summary": "Cria uma prova",
    "description": "A prova nasce em **rascunho** e já utilizável: Série 1, Turno 1 e código público são criados junto, e os juízes implícitos ficam vinculados.\n\nRascunho não publica — ative a prova antes de ligar inscrição, resultados ou hub.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "data_prova": {
          "type": "string",
          "description": "",
          "format": "date"
         },
         "data_fim": {
          "type": "string",
          "description": "",
          "format": "date",
          "nullable": true
         },
         "local": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "descricao": {
          "type": "string",
          "description": "Markdown. Aparece na página pública da prova.",
          "nullable": true
         },
         "status": {
          "type": "string",
          "description": "",
          "enum": [
           "rascunho",
           "aberta",
           "andamento",
           "encerrada"
          ]
         },
         "desempate_modo": {
          "type": "string",
          "description": "",
          "enum": [
           "x_aneis_alvos",
           "x_aneis",
           "serie_final"
          ]
         },
         "exige_assinatura_atleta": {
          "type": "boolean",
          "description": ""
         },
         "pin_pelo_portal": {
          "type": "boolean",
          "description": "Permite ao atleta criar ou trocar o PIN pelo portal. Desligada por padrão; o PIN do campo não depende dela."
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "inscricao_publica": {
          "type": "boolean",
          "description": ""
         },
         "inscricao_limite_em": {
          "type": "string",
          "description": "",
          "format": "date-time",
          "nullable": true
         },
         "resultados_publicos": {
          "type": "boolean",
          "description": ""
         },
         "hub_publico": {
          "type": "boolean",
          "description": ""
         },
         "comprovante_inscricao_active": {
          "type": "boolean",
          "description": "Libera o comprovante impresso para o atleta e para a secretaria."
         },
         "enviar_email_confirmacao": {
          "type": "boolean",
          "description": "Enfileira e-mail quando a inscrição fica confirmada."
         },
         "cadastro_equipamento": {
          "type": "boolean",
          "description": "Quando verdadeiro, a inscrição aceita o cadastro opcional da arma (tabela precisao_equipamentos)."
         },
         "valor_principal": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "valor_adicional": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "external_id": {
          "type": "string",
          "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
          "maxLength": 64,
          "nullable": true
         },
         "modalidades": {
          "type": "array",
          "items": {
           "type": "string"
          },
          "description": "Só na criação: uuids de modalidades do cadastro do clube a copiar para a prova."
         }
        },
        "required": [
         "nome",
         "data_prova"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Prova criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "prova": {
           "$ref": "#/components/schemas/Prova"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Provas"
    ],
    "summary": "Detalha uma prova",
    "description": "Todos os campos da prova.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "prova": {
           "$ref": "#/components/schemas/Prova"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "patch": {
    "tags": [
     "Provas"
    ],
    "summary": "Atualiza uma prova",
    "description": "Só os campos enviados mudam.\n\n**Congelamento:** com score já lançado, `desempate_modo` e `exige_assinatura_atleta` devolvem **409** — as duas regras fazem parte do que foi assinado, e mudá-las no meio da prova reordenaria o ranking sem ninguém atirar.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "data_prova": {
          "type": "string",
          "description": "",
          "format": "date"
         },
         "data_fim": {
          "type": "string",
          "description": "",
          "format": "date",
          "nullable": true
         },
         "local": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "descricao": {
          "type": "string",
          "description": "Markdown. Aparece na página pública da prova.",
          "nullable": true
         },
         "status": {
          "type": "string",
          "description": "",
          "enum": [
           "rascunho",
           "aberta",
           "andamento",
           "encerrada"
          ]
         },
         "desempate_modo": {
          "type": "string",
          "description": "",
          "enum": [
           "x_aneis_alvos",
           "x_aneis",
           "serie_final"
          ]
         },
         "exige_assinatura_atleta": {
          "type": "boolean",
          "description": ""
         },
         "pin_pelo_portal": {
          "type": "boolean",
          "description": "Permite ao atleta criar ou trocar o PIN pelo portal. Desligada por padrão; o PIN do campo não depende dela."
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "inscricao_publica": {
          "type": "boolean",
          "description": ""
         },
         "inscricao_limite_em": {
          "type": "string",
          "description": "",
          "format": "date-time",
          "nullable": true
         },
         "resultados_publicos": {
          "type": "boolean",
          "description": ""
         },
         "hub_publico": {
          "type": "boolean",
          "description": ""
         },
         "comprovante_inscricao_active": {
          "type": "boolean",
          "description": "Libera o comprovante impresso para o atleta e para a secretaria."
         },
         "enviar_email_confirmacao": {
          "type": "boolean",
          "description": "Enfileira e-mail quando a inscrição fica confirmada."
         },
         "cadastro_equipamento": {
          "type": "boolean",
          "description": "Quando verdadeiro, a inscrição aceita o cadastro opcional da arma (tabela precisao_equipamentos)."
         },
         "valor_principal": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "valor_adicional": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "external_id": {
          "type": "string",
          "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
          "maxLength": 64,
          "nullable": true
         },
         "modalidades": {
          "type": "array",
          "items": {
           "type": "string"
          },
          "description": "Só na criação: uuids de modalidades do cadastro do clube a copiar para a prova."
         }
        },
        "required": [
         "nome",
         "data_prova"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "prova": {
           "$ref": "#/components/schemas/Prova"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Provas"
    ],
    "summary": "Exclui uma prova (soft delete)",
    "description": "Zona de perigo: exige `?confirmacao=<nome exato da prova>`, o mesmo gate da interface. Sem isso devolve **409 confirmation_required**.",
    "parameters": [
     {
      "name": "confirmacao",
      "in": "query",
      "description": "Nome exato da prova.",
      "schema": {
       "type": "string",
       "required": true
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/modalidades": {
   "get": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Lista as modalidades do clube",
    "description": "O cadastro de percursos de fogo da entidade — reutilizável entre provas.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "incluir_inativas",
      "in": "query",
      "description": "Traz também as desativadas (`1`). Prova antiga pode apontar para uma modalidade já aposentada.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "modalidades": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ModalidadeCadastro"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Cria uma modalidade no cadastro do clube",
    "description": "Informe `slug_preset` para adotar um preset do catálogo (o clube passa a ser dono da linha e pode editá-la), ou `nome` + percurso para criar do zero.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "slug_preset": {
          "type": "string",
          "description": "Adota um preset do catálogo."
         },
         "distancia": {
          "type": "string",
          "description": ""
         },
         "num_alvos": {
          "type": "integer",
          "description": ""
         },
         "disparos_por_alvo": {
          "type": "integer",
          "description": ""
         },
         "num_series": {
          "type": "integer",
          "description": ""
         },
         "tem_ensaio": {
          "type": "boolean",
          "description": ""
         },
         "tem_x": {
          "type": "boolean",
          "description": ""
         },
         "tempo_ensaio_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "tempo_prova_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "equipamento": {
          "type": "string",
          "description": ""
         },
         "mira": {
          "type": "string",
          "description": ""
         },
         "classe_equip": {
          "type": "string",
          "description": ""
         },
         "origem": {
          "type": "string",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Modalidade criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "modalidade": {
           "$ref": "#/components/schemas/ModalidadeCadastro"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/modalidades/catalogo": {
   "get": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Presets SCTSL/CBTT disponíveis",
    "description": "Catálogo em código, igual para todos os clubes — a lista de partida do módulo. Adotar um preset copia a linha para o cadastro da entidade.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "presets": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "slug": {
              "type": "string",
              "description": ""
             },
             "nome": {
              "type": "string",
              "description": ""
             },
             "distancia": {
              "type": "string",
              "description": ""
             },
             "num_alvos": {
              "type": "integer",
              "description": ""
             },
             "disparos_por_alvo": {
              "type": "integer",
              "description": ""
             },
             "num_series": {
              "type": "integer",
              "description": ""
             },
             "tem_x": {
              "type": "boolean",
              "description": ""
             },
             "equipamento": {
              "type": "string",
              "description": ""
             },
             "mira": {
              "type": "string",
              "description": ""
             },
             "classe_equip": {
              "type": "string",
              "description": ""
             },
             "origem": {
              "type": "string",
              "description": ""
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/modalidades/{modalidade_uuid}": {
   "parameters": [
    {
     "name": "modalidade_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da modalidade do cadastro.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Atualiza uma modalidade do cadastro",
    "description": "Não afeta prova já montada: a prova guarda um **snapshot** do percurso, não uma referência viva.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "slug_preset": {
          "type": "string",
          "description": "Adota um preset do catálogo."
         },
         "distancia": {
          "type": "string",
          "description": ""
         },
         "num_alvos": {
          "type": "integer",
          "description": ""
         },
         "disparos_por_alvo": {
          "type": "integer",
          "description": ""
         },
         "num_series": {
          "type": "integer",
          "description": ""
         },
         "tem_ensaio": {
          "type": "boolean",
          "description": ""
         },
         "tem_x": {
          "type": "boolean",
          "description": ""
         },
         "tempo_ensaio_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "tempo_prova_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "equipamento": {
          "type": "string",
          "description": ""
         },
         "mira": {
          "type": "string",
          "description": ""
         },
         "classe_equip": {
          "type": "string",
          "description": ""
         },
         "origem": {
          "type": "string",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "modalidade": {
           "$ref": "#/components/schemas/ModalidadeCadastro"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Exclui uma modalidade do cadastro",
    "description": "Soft delete do cadastro. Provas já montadas continuam intactas pelo mesmo motivo do PATCH.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/modalidades": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Modalidades DA PROVA",
    "description": "O snapshot congelado do percurso dentro da prova. É a esta modalidade que a inscrição aponta e por ela que o ranking agrupa.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "modalidades": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProvaModalidade"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Adiciona uma modalidade à prova",
    "description": "Do cadastro (`modalidade_uuid`) ou avulsa (`nome` + percurso). Sem os campos de percurso, valem os padrões do módulo (4 alvos × 5 disparos, 1 série).\n\nProva encerrada devolve **409**.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "modalidade_uuid": {
          "type": "string",
          "description": "Copia do cadastro do clube."
         },
         "nome": {
          "type": "string",
          "description": "Modalidade avulsa, só nesta prova."
         },
         "distancia": {
          "type": "string",
          "description": ""
         },
         "num_alvos": {
          "type": "integer",
          "description": ""
         },
         "disparos_por_alvo": {
          "type": "integer",
          "description": ""
         },
         "num_series": {
          "type": "integer",
          "description": ""
         },
         "tem_ensaio": {
          "type": "boolean",
          "description": ""
         },
         "tem_x": {
          "type": "boolean",
          "description": ""
         },
         "tempo_ensaio_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "tempo_prova_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Modalidade adicionada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "modalidade": {
           "$ref": "#/components/schemas/ProvaModalidade"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/prova-modalidades/{pm_uuid}": {
   "parameters": [
    {
     "name": "pm_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da modalidade da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Atualiza a modalidade da prova",
    "description": "Com score já lançado, `num_alvos`, `disparos_por_alvo`, `num_series` e `tem_x` devolvem **409**: mudar o percurso tornaria inválida uma fita já assinada, e o desempate por alvo passaria a ler alvos que não existem. Nome, tempos e `ativo` continuam liberados.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "modalidade_uuid": {
          "type": "string",
          "description": "Copia do cadastro do clube."
         },
         "nome": {
          "type": "string",
          "description": "Modalidade avulsa, só nesta prova."
         },
         "distancia": {
          "type": "string",
          "description": ""
         },
         "num_alvos": {
          "type": "integer",
          "description": ""
         },
         "disparos_por_alvo": {
          "type": "integer",
          "description": ""
         },
         "num_series": {
          "type": "integer",
          "description": ""
         },
         "tem_ensaio": {
          "type": "boolean",
          "description": ""
         },
         "tem_x": {
          "type": "boolean",
          "description": ""
         },
         "tempo_ensaio_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "tempo_prova_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "modalidade": {
           "$ref": "#/components/schemas/ProvaModalidade"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Modalidades"
    ],
    "summary": "Remove a modalidade da prova",
    "description": "Bloqueado (**409**) com score lançado ou inscrição ativa — use `ativo: false` para tirá-la da inscrição sem apagar histórico.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/series": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Séries"
    ],
    "summary": "Lista as séries da prova",
    "description": "A série é o eixo de tempo (a rodada). O percurso vem da modalidade, não daqui.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "series": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Serie"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Séries"
    ],
    "summary": "Cria uma série",
    "description": "Sem `numero`, usa o próximo livre. Número repetido devolve **409**.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "numero": {
          "type": "integer",
          "description": "Omitido na criação, usa o próximo livre."
         },
         "nome": {
          "type": "string",
          "description": ""
         },
         "horario_inicio": {
          "type": "string",
          "description": "HH:MM."
         },
         "ativa": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Série criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "serie": {
           "$ref": "#/components/schemas/Serie"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/series/{serie_uuid}": {
   "parameters": [
    {
     "name": "serie_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da série.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Séries"
    ],
    "summary": "Atualiza uma série",
    "description": "Renumerar série que já tem score devolve **409**: o desempate `serie_final` lê as séries por este número, e a última deixaria de ser a última.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "numero": {
          "type": "integer",
          "description": "Omitido na criação, usa o próximo livre."
         },
         "nome": {
          "type": "string",
          "description": ""
         },
         "horario_inicio": {
          "type": "string",
          "description": "HH:MM."
         },
         "ativa": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "serie": {
           "$ref": "#/components/schemas/Serie"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Séries"
    ],
    "summary": "Exclui uma série",
    "description": "Bloqueado (**409**) com score lançado — use `ativa: false` para tirá-la do ranking.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/turnos": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Turnos"
    ],
    "summary": "Lista os turnos de tiro",
    "description": "O turno (bancada) é o eixo de espaço: quem atira junto e em que posto.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "turnos": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Turno"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Turnos"
    ],
    "summary": "Cria um turno",
    "description": "Limite padrão de 10 vagas.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "numero": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "limite": {
          "type": "integer",
          "description": ""
         },
         "horario_inicio": {
          "type": "string",
          "description": "HH:MM."
         },
         "observacao": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Turno criado",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "turno": {
           "$ref": "#/components/schemas/Turno"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/turnos/{turno_uuid}": {
   "parameters": [
    {
     "name": "turno_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do turno.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Turnos"
    ],
    "summary": "Atualiza um turno",
    "description": "Reduzir `limite` abaixo de quem já está no turno devolve **409** — deixaria a bancada num estado que a própria regra de inscrição proíbe criar.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "numero": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "limite": {
          "type": "integer",
          "description": ""
         },
         "horario_inicio": {
          "type": "string",
          "description": "HH:MM."
         },
         "observacao": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         }
        }
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "turno": {
           "$ref": "#/components/schemas/Turno"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Turnos"
    ],
    "summary": "Exclui um turno",
    "description": "Bloqueado (**409**) com atleta vinculado.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluido": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/inscricoes": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Lista as inscrições da prova",
    "description": "Ordenadas por nome.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "status_inscricao",
      "in": "query",
      "description": "Filtra por status.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "prova_modalidade_uuid",
      "in": "query",
      "description": "Só de uma modalidade.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "turno_uuid",
      "in": "query",
      "description": "Só de um turno.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "external_id",
      "in": "query",
      "description": "Reencontra pelo id do SEU sistema.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "inscricoes": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/InscricaoResumo"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Inscreve um atleta",
    "description": "O atleta entra no **mesmo cadastro** que IPSC e Clay usam (e-mail → perfil → membro do clube), e a inscrição guarda um snapshot dos dados no ato.\n\n**Uma inscrição por modalidade:** o mesmo atleta pode ter duas inscrições na mesma prova (Carabina e PCP, por exemplo). Repetir a **mesma** modalidade é que é conflito.\n\nCampos adicionais do clube vão achatados no corpo, com o mesmo nome de input das telas.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "prova_modalidade_uuid": {
          "type": "string",
          "description": "Obrigatório: no tiro de precisão a inscrição é por modalidade."
         },
         "nome": {
          "type": "string",
          "description": ""
         },
         "email": {
          "type": "string",
          "description": "",
          "format": "email"
         },
         "sexo": {
          "type": "string",
          "description": "`M` ou `F`. Obrigatório — decide a categoria."
         },
         "data_nascimento": {
          "type": "string",
          "description": "YYYY-MM-DD. Obrigatório — decide o enquadramento por idade.",
          "format": "date"
         },
         "cpf": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "telefone": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "categoria": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "classe": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "turno_uuid": {
          "type": "string",
          "description": "Turno de tiro; string vazia tira do turno.",
          "nullable": true
         },
         "status_inscricao": {
          "type": "string",
          "description": "",
          "enum": [
           "pendente",
           "confirmada",
           "cancelada"
          ]
         },
         "status_participacao": {
          "type": "string",
          "description": "",
          "enum": [
           "ativo",
           "dnf",
           "dq"
          ]
         },
         "status_pgto": {
          "type": "string",
          "description": "",
          "enum": [
           "PENDENTE",
           "PAGO",
           "ISENTO"
          ]
         },
         "obs_pgto": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "presenca": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "external_id": {
          "type": "string",
          "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
          "maxLength": 64,
          "nullable": true
         }
        },
        "required": [
         "prova_modalidade_uuid",
         "nome",
         "email",
         "sexo",
         "data_nascimento"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Inscrição criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "inscricao": {
           "$ref": "#/components/schemas/Inscricao"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/inscricoes/{inscricao_uuid}": {
   "parameters": [
    {
     "name": "inscricao_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da inscrição.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Detalha uma inscrição",
    "description": "Todos os campos.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "inscricao": {
           "$ref": "#/components/schemas/Inscricao"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "patch": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Atualiza uma inscrição",
    "description": "Cancelar libera a vaga do turno automaticamente.\n\nTrocar `prova_modalidade_uuid` com score lançado devolve **409**: moveria uma fita assinada para outro percurso de fogo, com outro número de alvos.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "prova_modalidade_uuid": {
          "type": "string",
          "description": "Obrigatório: no tiro de precisão a inscrição é por modalidade."
         },
         "nome": {
          "type": "string",
          "description": ""
         },
         "email": {
          "type": "string",
          "description": "",
          "format": "email"
         },
         "sexo": {
          "type": "string",
          "description": "`M` ou `F`. Obrigatório — decide a categoria."
         },
         "data_nascimento": {
          "type": "string",
          "description": "YYYY-MM-DD. Obrigatório — decide o enquadramento por idade.",
          "format": "date"
         },
         "cpf": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "telefone": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "categoria": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "classe": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "turno_uuid": {
          "type": "string",
          "description": "Turno de tiro; string vazia tira do turno.",
          "nullable": true
         },
         "status_inscricao": {
          "type": "string",
          "description": "",
          "enum": [
           "pendente",
           "confirmada",
           "cancelada"
          ]
         },
         "status_participacao": {
          "type": "string",
          "description": "",
          "enum": [
           "ativo",
           "dnf",
           "dq"
          ]
         },
         "status_pgto": {
          "type": "string",
          "description": "",
          "enum": [
           "PENDENTE",
           "PAGO",
           "ISENTO"
          ]
         },
         "obs_pgto": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "presenca": {
          "type": "string",
          "description": "",
          "nullable": true
         },
         "external_id": {
          "type": "string",
          "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
          "maxLength": 64,
          "nullable": true
         }
        },
        "required": [
         "prova_modalidade_uuid",
         "nome",
         "email",
         "sexo",
         "data_nascimento"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "inscricao": {
           "$ref": "#/components/schemas/Inscricao"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Exclui uma inscrição (soft delete)",
    "description": "Bloqueado (**409**) com score lançado: apagá-la deixaria uma fita assinada apontando para o vazio. Use `status_inscricao: \"cancelada\"`, que tira do ranking sem quebrar a cadeia de assinatura.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/inscricoes/{inscricao_uuid}/pin": {
   "parameters": [
    {
     "name": "inscricao_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da inscrição.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "delete": {
    "tags": [
     "Inscrições"
    ],
    "summary": "Apaga o PIN da inscrição",
    "description": "O atleta define outro na próxima assinatura.\n\n**A API não gera PIN e nunca devolve um.** O PIN é segredo do atleta: quem o escolhe é ele, digitando no aparelho, e deste lado só existe o hash. Um endpoint que emitisse o PIN faria o integrador — e o log dele — conhecer a credencial que dá validade à assinatura do atleta, que é o que a dupla assinatura existe para impedir.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "inscricao_uuid": {
           "type": "string",
           "description": ""
          },
          "pin_definido": {
           "type": "boolean",
           "description": ""
          },
          "mensagem": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/juizes": {
   "get": {
    "tags": [
     "Juízes"
    ],
    "summary": "Lista os juízes da entidade",
    "description": "Nunca devolve credencial — só quem existe.",
    "parameters": [
     {
      "name": "incluir_inativos",
      "in": "query",
      "description": "Traz os desativados (`1`).",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "incluir_implicitos",
      "in": "query",
      "description": "Traz os espelhos de gestor (`1`). Fora por padrão: não são juízes que alguém cadastrou.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "juizes": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Juiz"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/juizes": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Juízes"
    ],
    "summary": "Juízes vinculados à prova",
    "description": "Só estes podem lançar score nesta prova.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "juizes": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Juiz"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/resultados": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Resultados"
    ],
    "summary": "Ranking da prova",
    "description": "Um bloco por modalidade: o atleta de Carabina Standard não disputa com o de PCP Custom, ainda que atirem no mesmo dia.\n\nSó score **aprovado** entra. Cada linha traz o vetor de desempate aberto (`aneis`, `por_alvo`, `por_serie`) — sem ele não há como explicar por que dois atletas com o mesmo total ficaram em posições diferentes.\n\nEmpate irredutível divide a mesma posição.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "prova": {
           "type": "object",
           "properties": {
            "uuid": {
             "type": "string",
             "description": ""
            },
            "nome": {
             "type": "string",
             "description": ""
            },
            "status": {
             "type": "string",
             "description": ""
            },
            "desempate_modo": {
             "type": "string",
             "description": "",
             "enum": [
              "x_aneis_alvos",
              "x_aneis",
              "serie_final"
             ]
            }
           }
          },
          "series": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Serie"
           }
          },
          "modalidades": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "modalidade": {
              "$ref": "#/components/schemas/ProvaModalidade"
             },
             "ranking": {
              "type": "array",
              "items": {
               "$ref": "#/components/schemas/RankingLinha"
              }
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/scores": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Resultados"
    ],
    "summary": "Fita alvo a alvo",
    "description": "Somente leitura. A fita nasce assinada no campo (HMAC do juiz, encadeado com o PIN do atleta quando a prova exige) — um POST aqui criaria resultado sem cadeia de assinatura, que é o que o módulo existe para impedir.\n\nO hash não é exposto: ele é o segredo que prova a integridade da cadeia. O que sai é **se** está assinado e quando.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "serie_uuid",
      "in": "query",
      "description": "Só de uma série.",
      "schema": {
       "type": "string"
      }
     },
     {
      "name": "incluir_nao_aprovados",
      "in": "query",
      "description": "Traz o rascunho do campo (`1`), útil para acompanhar a prova em andamento — mas o que não foi assinado ainda pode mudar, e não é resultado.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "scores": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Score"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/urls": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Resultados"
    ],
    "summary": "Links públicos da prova",
    "description": "Cada link vem com `ativa` e o motivo de estar fechado — assim você não precisa reimplementar o gate de publicação para saber por que a página não abre.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "codigo_publico": {
           "type": "string",
           "description": "",
           "nullable": true
          },
          "urls": {
           "type": "object",
           "properties": {
            "inscricao": {
             "$ref": "#/components/schemas/LinkPublico"
            },
            "resultados": {
             "$ref": "#/components/schemas/LinkPublico"
            },
            "hub": {
             "$ref": "#/components/schemas/LinkPublico"
            },
            "resultados_json": {
             "$ref": "#/components/schemas/LinkPublico"
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/auditoria": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Resultados"
    ],
    "summary": "Trilha de auditoria da prova",
    "description": "A mesma trilha da tela de auditoria, incluindo as ações feitas por esta API (ator `api_key`) — é como se reconcilia o que o robô fez com o que o staff fez na tela.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "acao",
      "in": "query",
      "description": "Filtra por ação, ex. `precisao.prova_update`.",
      "schema": {
       "type": "string"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "eventos": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "acao": {
              "type": "string",
              "description": ""
             },
             "resumo": {
              "type": "string",
              "description": ""
             },
             "entidade": {
              "type": "string",
              "description": ""
             },
             "registro_id": {
              "type": "integer",
              "description": "",
              "nullable": true
             },
             "actor_tipo": {
              "type": "string",
              "description": ""
             },
             "em": {
              "type": "string",
              "description": "",
              "format": "date-time"
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos": {
   "get": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Lista os campeonatos",
    "description": "Mais recentes primeiro.",
    "parameters": [
     {
      "name": "page",
      "in": "query",
      "description": "Página (1-based).",
      "schema": {
       "type": "integer",
       "default": 1
      }
     },
     {
      "name": "per_page",
      "in": "query",
      "description": "Itens por página (máx. 100; inscrições até 200).",
      "schema": {
       "type": "integer",
       "default": 25
      }
     },
     {
      "name": "ano",
      "in": "query",
      "description": "Filtra pelo ano da temporada.",
      "schema": {
       "type": "integer"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "page": {
           "type": "integer",
           "description": "Página atual."
          },
          "per_page": {
           "type": "integer",
           "description": "Itens por página."
          },
          "total": {
           "type": "integer",
           "description": "Total de itens que casam com o filtro."
          },
          "campeonatos": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Campeonato"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "post": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Cria um campeonato",
    "description": "Tudo é opcional: sem `usa_categorias`/`usa_classes` é só um agrupador de etapas com ranking somado.\n\n`descartes` maior ou igual a `etapas_previstas` devolve **422** — nenhuma etapa entraria no total.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "ano": {
          "type": "integer",
          "description": ""
         },
         "etapas_previstas": {
          "type": "integer",
          "description": ""
         },
         "minimo_etapas": {
          "type": "integer",
          "description": ""
         },
         "descartes": {
          "type": "integer",
          "description": ""
         },
         "usa_categorias": {
          "type": "boolean",
          "description": ""
         },
         "usa_classes": {
          "type": "boolean",
          "description": ""
         },
         "idade_modo": {
          "type": "string",
          "description": "",
          "enum": [
           "dia_exato",
           "fim_ano"
          ]
         },
         "classe_criterio": {
          "type": "string",
          "description": "",
          "enum": [
           "percentual",
           "pontos"
          ]
         },
         "trava_primeira_participacao": {
          "type": "boolean",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "publico": {
          "type": "boolean",
          "description": ""
         }
        },
        "required": [
         "nome",
         "ano"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Campeonato criado",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "campeonato": {
           "$ref": "#/components/schemas/Campeonato"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos/{campeonato_uuid}": {
   "parameters": [
    {
     "name": "campeonato_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do campeonato.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Detalha um campeonato",
    "description": "Com a lista de etapas vinculadas.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "campeonato": {
           "$ref": "#/components/schemas/Campeonato"
          },
          "etapas": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/ProvaResumo"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "patch": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Atualiza um campeonato",
    "description": "Campos parciais.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "ano": {
          "type": "integer",
          "description": ""
         },
         "etapas_previstas": {
          "type": "integer",
          "description": ""
         },
         "minimo_etapas": {
          "type": "integer",
          "description": ""
         },
         "descartes": {
          "type": "integer",
          "description": ""
         },
         "usa_categorias": {
          "type": "boolean",
          "description": ""
         },
         "usa_classes": {
          "type": "boolean",
          "description": ""
         },
         "idade_modo": {
          "type": "string",
          "description": "",
          "enum": [
           "dia_exato",
           "fim_ano"
          ]
         },
         "classe_criterio": {
          "type": "string",
          "description": "",
          "enum": [
           "percentual",
           "pontos"
          ]
         },
         "trava_primeira_participacao": {
          "type": "boolean",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "publico": {
          "type": "boolean",
          "description": ""
         }
        },
        "required": [
         "nome",
         "ano"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "campeonato": {
           "$ref": "#/components/schemas/Campeonato"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Exclui um campeonato",
    "description": "Bloqueado (**409**) com etapa vinculada — as etapas ficariam apontando para um campeonato inexistente.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluido": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos/{campeonato_uuid}/etapas/{prova_uuid}": {
   "parameters": [
    {
     "name": "campeonato_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do campeonato.",
     "schema": {
      "type": "string"
     }
    },
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "put": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Vincula uma prova como etapa",
    "description": "Idempotente: revincular a mesma prova devolve 200.\n\nUma prova pertence a no máximo um campeonato. Vincular uma etapa já vinculada devolve **409** em vez de mover em silêncio — mover muda o ranking dos dois campeonatos, e isso tem de ser deliberado.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "prova": {
           "$ref": "#/components/schemas/ProvaResumo"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "409": {
      "description": "Conflito com o estado atual (ver `message`).",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Desvincula a etapa",
    "description": "A prova continua existindo, fora do campeonato.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "prova": {
           "$ref": "#/components/schemas/ProvaResumo"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos/{campeonato_uuid}/ranking": {
   "parameters": [
    {
     "name": "campeonato_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do campeonato.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Campeonatos"
    ],
    "summary": "Ranking da temporada",
    "description": "Soma o total de cada etapa por atleta e descarta as `descartes` PIORES pontuações; `classificado` diz quem atingiu `minimo_etapas`.\n\nA identidade do atleta entre etapas é o e-mail normalizado — o único dado estável presente em toda inscrição.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "campeonato": {
           "$ref": "#/components/schemas/Campeonato"
          },
          "etapas": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "uuid": {
              "type": "string",
              "description": ""
             },
             "nome": {
              "type": "string",
              "description": ""
             },
             "data_prova": {
              "type": "string",
              "description": "",
              "format": "date"
             }
            }
           }
          },
          "ranking": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "posicao": {
              "type": "integer",
              "description": "Mesmo total divide a mesma posição (1, 1, 3)."
             },
             "nome": {
              "type": "string",
              "description": ""
             },
             "email": {
              "type": "string",
              "description": ""
             },
             "categoria": {
              "type": "string",
              "description": "",
              "nullable": true
             },
             "total": {
              "type": "integer",
              "description": ""
             },
             "etapas_disputadas": {
              "type": "integer",
              "description": ""
             },
             "classificado": {
              "type": "boolean",
              "description": ""
             },
             "etapas": {
              "type": "array",
              "items": {
               "type": "object",
               "properties": {
                "prova_uuid": {
                 "type": "string",
                 "description": ""
                },
                "nome": {
                 "type": "string",
                 "description": ""
                },
                "total": {
                 "type": "integer",
                 "description": "",
                 "nullable": true
                }
               }
              }
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos/{campeonato_uuid}/catalogo": {
   "parameters": [
    {
     "name": "campeonato_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do campeonato.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Categorias e classes da temporada",
    "description": "O catálogo que as etapas herdam.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "usa_categorias": {
           "type": "boolean",
           "description": ""
          },
          "usa_classes": {
           "type": "boolean",
           "description": ""
          },
          "classe_criterio": {
           "type": "string",
           "description": ""
          },
          "categorias": {
           "type": "array",
           "items": {
            "$ref": "#/components/schemas/Categoria"
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/campeonatos/{campeonato_uuid}/categorias": {
   "parameters": [
    {
     "name": "campeonato_uuid",
     "in": "path",
     "required": true,
     "description": "uuid do campeonato.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "post": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Cria uma categoria",
    "description": "O `codigo` é derivado do nome, com sufixo quando já existe.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "tipo": {
          "type": "string",
          "description": "",
          "enum": [
           "automatica",
           "manual"
          ]
         },
         "sexo": {
          "type": "string",
          "description": "`M`, `F` ou `null`.",
          "nullable": true
         },
         "idade_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "idade_max": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": "",
          "nullable": true
         }
        },
        "required": [
         "nome"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Categoria criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "categoria": {
           "$ref": "#/components/schemas/Categoria"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/categorias/{categoria_uuid}": {
   "parameters": [
    {
     "name": "categoria_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da categoria.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Atualiza uma categoria",
    "description": "Campos parciais.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "tipo": {
          "type": "string",
          "description": "",
          "enum": [
           "automatica",
           "manual"
          ]
         },
         "sexo": {
          "type": "string",
          "description": "`M`, `F` ou `null`.",
          "nullable": true
         },
         "idade_min": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "idade_max": {
          "type": "integer",
          "description": "",
          "nullable": true
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": "",
          "nullable": true
         }
        },
        "required": [
         "nome"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "categoria": {
           "$ref": "#/components/schemas/Categoria"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Exclui uma categoria",
    "description": "Soft delete da categoria e das classes dentro dela. O snapshot já gravado na inscrição não muda: o ranking de uma prova passada continua agrupando pelo nome que valia no dia.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/categorias/{categoria_uuid}/classes": {
   "parameters": [
    {
     "name": "categoria_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da categoria.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "post": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Cria uma classe na categoria",
    "description": "A faixa é `[valor_min, valor_max)` — semiaberta de propósito: o teto de uma classe é o piso da seguinte, e quem está no valor exato da fronteira cai sempre na de cima. `valor_min >= valor_max` devolve **422**.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "criterio": {
          "type": "string",
          "description": "",
          "enum": [
           "manual",
           "faixa"
          ]
         },
         "valor_min": {
          "type": "number",
          "description": "",
          "nullable": true
         },
         "valor_max": {
          "type": "number",
          "description": "",
          "nullable": true
         },
         "sem_historico": {
          "type": "boolean",
          "description": ""
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": "",
          "nullable": true
         }
        },
        "required": [
         "nome"
        ]
       }
      }
     }
    },
    "responses": {
     "201": {
      "description": "Classe criada",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "classe": {
           "$ref": "#/components/schemas/Classe"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/classes/{classe_uuid}": {
   "parameters": [
    {
     "name": "classe_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da classe.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "patch": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Atualiza uma classe",
    "description": "Campos parciais.",
    "requestBody": {
     "required": true,
     "content": {
      "application/json": {
       "schema": {
        "type": "object",
        "properties": {
         "nome": {
          "type": "string",
          "description": ""
         },
         "criterio": {
          "type": "string",
          "description": "",
          "enum": [
           "manual",
           "faixa"
          ]
         },
         "valor_min": {
          "type": "number",
          "description": "",
          "nullable": true
         },
         "valor_max": {
          "type": "number",
          "description": "",
          "nullable": true
         },
         "sem_historico": {
          "type": "boolean",
          "description": ""
         },
         "ordem": {
          "type": "integer",
          "description": ""
         },
         "ativo": {
          "type": "boolean",
          "description": ""
         },
         "descricao": {
          "type": "string",
          "description": "",
          "nullable": true
         }
        },
        "required": [
         "nome"
        ]
       }
      }
     }
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "classe": {
           "$ref": "#/components/schemas/Classe"
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "422": {
      "description": "Payload inválido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   },
   "delete": {
    "tags": [
     "Catálogo"
    ],
    "summary": "Exclui uma classe",
    "description": "Soft delete.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "ok": {
           "type": "boolean",
           "description": ""
          },
          "excluida": {
           "type": "string",
           "description": ""
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Credencial ausente ou inválida.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "403": {
      "description": "Chave com escopo somente leitura.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "404": {
      "description": "Módulo indisponível para a entidade, ou recurso inexistente.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     },
     "429": {
      "description": "Rate limit excedido.",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/Erro"
        }
       }
      }
     }
    }
   }
  },
  "/precisao/provas/{prova_uuid}/equipes": {
   "parameters": [
    {
     "name": "prova_uuid",
     "in": "path",
     "required": true,
     "description": "uuid da prova.",
     "schema": {
      "type": "string"
     }
    }
   ],
   "get": {
    "tags": [
     "Resultados"
    ],
    "summary": "Ranking de equipes da prova",
    "description": "Um bloco por modalidade, com a soma dos `equipe_n_best` melhores. Mesmo total divide a posição (1, 1, 3). Vazio com as equipes desligadas. Somente leitura.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "type": "object",
         "properties": {
          "prova": {
           "type": "object",
           "properties": {
            "uuid": {
             "type": "string"
            },
            "nome": {
             "type": "string"
            },
            "status": {
             "type": "string"
            },
            "equipe_ativo": {
             "type": "boolean"
            },
            "equipe_tamanho": {
             "type": "integer"
            },
            "equipe_n_best": {
             "type": "integer"
            }
           }
          },
          "modalidades": {
           "type": "array",
           "items": {
            "type": "object",
            "properties": {
             "modalidade": {
              "type": "object",
              "properties": {
               "uuid": {
                "type": "string"
               },
               "nome": {
                "type": "string"
               }
              }
             },
             "ranking": {
              "type": "array",
              "items": {
               "type": "object",
               "properties": {
                "posicao": {
                 "type": "integer",
                 "description": "Mesmo total divide a mesma posição."
                },
                "empatado": {
                 "type": "boolean"
                },
                "uuid": {
                 "type": "string"
                },
                "nome": {
                 "type": "string"
                },
                "total": {
                 "type": "integer"
                },
                "contam": {
                 "type": "integer"
                },
                "completa": {
                 "type": "boolean"
                },
                "faltam": {
                 "type": "integer"
                },
                "sem_resultado": {
                 "type": "boolean"
                },
                "membros": {
                 "type": "array",
                 "items": {
                  "type": "object",
                  "properties": {
                   "inscricao_uuid": {
                    "type": "string"
                   },
                   "nome": {
                    "type": "string"
                   },
                   "total": {
                    "type": "integer"
                   },
                   "dq": {
                    "type": "boolean"
                   },
                   "dnf": {
                    "type": "boolean"
                   },
                   "pontua": {
                    "type": "boolean"
                   },
                   "conta_no_total": {
                    "type": "boolean"
                   }
                  }
                 }
                }
               }
              }
             }
            }
           }
          }
         }
        }
       }
      }
     },
     "401": {
      "description": "Chave ausente ou inválida."
     },
     "404": {
      "description": "Prova não encontrada."
     }
    }
   }
  }
 },
 "components": {
  "securitySchemes": {
   "bearerAuth": {
    "type": "http",
    "scheme": "bearer",
    "description": "Envie `<access_key>.<secret>` como token."
   }
  },
  "schemas": {
   "Erro": {
    "type": "object",
    "properties": {
     "error": {
      "type": "object",
      "properties": {
       "code": {
        "type": "string",
        "description": "`unauthorized`, `forbidden`, `not_found`, `conflict`, `confirmation_required`, `validation_error`, `invalid_json`, `rate_limited`, `internal_error`."
       },
       "message": {
        "type": "string",
        "description": "Texto explicando o que fazer."
       }
      }
     }
    }
   },
   "ProvaResumo": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": "Identificador público da prova."
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "status": {
      "type": "string",
      "description": "Estado da prova.",
      "enum": [
       "rascunho",
       "aberta",
       "andamento",
       "encerrada"
      ]
     },
     "ativo": {
      "type": "boolean",
      "description": "Visibilidade para inscrição. Não é exclusão: prova inativa continua legível pela API."
     },
     "data_prova": {
      "type": "string",
      "description": "YYYY-MM-DD.",
      "format": "date"
     },
     "data_fim": {
      "type": "string",
      "description": "YYYY-MM-DD.",
      "format": "date",
      "nullable": true
     },
     "local": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "codigo_publico": {
      "type": "string",
      "description": "Token dos links públicos (/precisao/r/<codigo>).",
      "nullable": true
     },
     "external_id": {
      "type": "string",
      "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
      "maxLength": 64,
      "nullable": true
     }
    }
   },
   "Prova": {
    "allOf": [
     {
      "$ref": "#/components/schemas/ProvaResumo"
     },
     {
      "type": "object",
      "properties": {
       "descricao": {
        "type": "string",
        "description": "",
        "nullable": true
       },
       "campeonato_uuid": {
        "type": "string",
        "description": "Campeonato do qual esta prova é etapa.",
        "nullable": true
       },
       "desempate_modo": {
        "type": "string",
        "description": "Critério de desempate desta prova. `x_aneis_alvos` (SCTSL, padrão): mais X, depois 10s, 9s…; persistindo, melhor pontuação no último alvo e assim decrescendo. `x_aneis`: só a contagem por anel. `serie_final` (molde ISSF): total da última série, depois a anterior, e só então os anéis.",
        "enum": [
         "x_aneis_alvos",
         "x_aneis",
         "serie_final"
        ]
       },
       "exige_assinatura_atleta": {
        "type": "boolean",
        "description": "Quando falso (padrão do módulo), o score fecha só com a assinatura do juiz e o PIN do atleta nunca é pedido."
       },
       "pin_pelo_portal": {
        "type": "boolean",
        "description": "Permite ao atleta criar ou trocar o PIN pelo portal. Desligada por padrão; o PIN do campo não depende dela."
       },
       "inscricao_publica": {
        "type": "boolean",
        "description": ""
       },
       "inscricao_limite_em": {
        "type": "string",
        "description": "ISO 8601.",
        "format": "date-time",
        "nullable": true
       },
       "resultados_publicos": {
        "type": "boolean",
        "description": ""
       },
       "hub_publico": {
        "type": "boolean",
        "description": ""
       },
       "comprovante_inscricao_active": {
        "type": "boolean",
        "description": "Libera o comprovante impresso para o atleta e para a secretaria."
       },
       "enviar_email_confirmacao": {
        "type": "boolean",
        "description": "Enfileira e-mail quando a inscrição fica confirmada."
       },
       "cadastro_equipamento": {
        "type": "boolean",
        "description": "Quando verdadeiro, a inscrição aceita o cadastro opcional da arma (tabela precisao_equipamentos)."
       },
       "valor_principal": {
        "type": "number",
        "description": "",
        "nullable": true
       },
       "valor_adicional": {
        "type": "number",
        "description": "",
        "nullable": true
       },
       "criada_via": {
        "type": "string",
        "description": "`interface` ou `api_v1`.",
        "nullable": true
       },
       "criada_em": {
        "type": "string",
        "description": "",
        "format": "date-time",
        "nullable": true
       },
       "atualizada_em": {
        "type": "string",
        "description": "",
        "format": "date-time",
        "nullable": true
       }
      }
     }
    ]
   },
   "ModalidadeCadastro": {
    "type": "object",
    "properties": {
     "distancia": {
      "type": "string",
      "description": "Texto livre: `50m`, `25m`, `10m`.",
      "nullable": true
     },
     "num_alvos": {
      "type": "integer",
      "description": "Alvos de prova por série."
     },
     "disparos_por_alvo": {
      "type": "integer",
      "description": "Disparos em cada alvo."
     },
     "num_series": {
      "type": "integer",
      "description": "Séries previstas."
     },
     "disparos_por_serie": {
      "type": "integer",
      "description": "Derivado: num_alvos × disparos_por_alvo."
     },
     "tem_ensaio": {
      "type": "boolean",
      "description": ""
     },
     "tempo_ensaio_min": {
      "type": "integer",
      "description": "Minutos; `null` = livre.",
      "nullable": true
     },
     "tempo_prova_min": {
      "type": "integer",
      "description": "Minutos; `null` = livre.",
      "nullable": true
     },
     "tem_x": {
      "type": "boolean",
      "description": "O anel central X existe. Vale 10 pontos e conta em separado para o desempate."
     },
     "uuid": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "slug_preset": {
      "type": "string",
      "description": "Preset do catálogo de onde veio; `null` = criada do zero pelo clube.",
      "nullable": true
     },
     "equipamento": {
      "type": "string",
      "description": "",
      "nullable": true,
      "enum": [
       "carabina",
       "pistola",
       "revolver",
       "pcp",
       "outro"
      ]
     },
     "mira": {
      "type": "string",
      "description": "",
      "nullable": true,
      "enum": [
       "optica",
       "aberta"
      ]
     },
     "classe_equip": {
      "type": "string",
      "description": "",
      "nullable": true,
      "enum": [
       "standard",
       "custom"
      ]
     },
     "origem": {
      "type": "string",
      "description": "",
      "nullable": true,
      "enum": [
       "nacional",
       "importada"
      ]
     },
     "descricao": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     }
    }
   },
   "ProvaModalidade": {
    "type": "object",
    "properties": {
     "distancia": {
      "type": "string",
      "description": "Texto livre: `50m`, `25m`, `10m`.",
      "nullable": true
     },
     "num_alvos": {
      "type": "integer",
      "description": "Alvos de prova por série."
     },
     "disparos_por_alvo": {
      "type": "integer",
      "description": "Disparos em cada alvo."
     },
     "num_series": {
      "type": "integer",
      "description": "Séries previstas."
     },
     "disparos_por_serie": {
      "type": "integer",
      "description": "Derivado: num_alvos × disparos_por_alvo."
     },
     "tem_ensaio": {
      "type": "boolean",
      "description": ""
     },
     "tempo_ensaio_min": {
      "type": "integer",
      "description": "Minutos; `null` = livre.",
      "nullable": true
     },
     "tempo_prova_min": {
      "type": "integer",
      "description": "Minutos; `null` = livre.",
      "nullable": true
     },
     "tem_x": {
      "type": "boolean",
      "description": "O anel central X existe. Vale 10 pontos e conta em separado para o desempate."
     },
     "uuid": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "ordem": {
      "type": "integer",
      "description": ""
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "pontos_maximos_serie": {
      "type": "integer",
      "description": "Derivado: disparos_por_serie × 10."
     },
     "aneis": {
      "type": "array",
      "items": {
       "type": "string"
      },
      "description": "Vocabulário aceito na fita, do maior para o menor (`x`, `10`… `0`). Sem `tem_x`, `x` não aparece."
     },
     "modalidade_uuid": {
      "type": "string",
      "description": "Modalidade do cadastro que originou o snapshot. Informativo — editá-la não muda esta prova.",
      "nullable": true
     },
     "inscritos": {
      "type": "integer",
      "description": "Inscrições ativas nesta modalidade."
     }
    }
   },
   "Serie": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "numero": {
      "type": "integer",
      "description": "Ordem da série na prova. Não muda depois que há score: o desempate `serie_final` lê as séries por este número."
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "horario_inicio": {
      "type": "string",
      "description": "HH:MM.",
      "nullable": true
     },
     "ativa": {
      "type": "boolean",
      "description": "Série inativa fica fora do ranking."
     },
     "scores_lancados": {
      "type": "integer",
      "description": "Scores já lançados nesta série."
     }
    }
   },
   "Turno": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "numero": {
      "type": "integer",
      "description": "",
      "nullable": true
     },
     "limite": {
      "type": "integer",
      "description": "Vagas do turno."
     },
     "horario_inicio": {
      "type": "string",
      "description": "HH:MM.",
      "nullable": true
     },
     "observacao": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "inscritos": {
      "type": "integer",
      "description": "Vagas ocupadas."
     }
    }
   },
   "InscricaoResumo": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": "Identificador da inscrição NESTA prova."
     },
     "perfil_uuid": {
      "type": "string",
      "description": "Atleta no cadastro global — o mesmo nos três módulos. É como se correlaciona o atleta entre provas.",
      "nullable": true
     },
     "membro_entidade_uuid": {
      "type": "string",
      "description": "Atleta no cadastro do clube.",
      "nullable": true
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "email": {
      "type": "string",
      "description": "",
      "format": "email"
     },
     "prova_modalidade_uuid": {
      "type": "string",
      "description": "Modalidade disputada. É o eixo do ranking — a \"divisão\" do módulo."
     },
     "prova_modalidade_nome": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "categoria": {
      "type": "string",
      "description": "Snapshot do nome no dia.",
      "nullable": true
     },
     "classe": {
      "type": "string",
      "description": "Snapshot do nome no dia.",
      "nullable": true
     },
     "bancada_uuid": {
      "type": "string",
      "description": "Turno de tiro, ou `null`.",
      "nullable": true
     },
     "posicao": {
      "type": "integer",
      "description": "Posição no turno.",
      "nullable": true
     },
     "status_inscricao": {
      "type": "string",
      "description": "",
      "enum": [
       "pendente",
       "confirmada",
       "cancelada"
      ]
     },
     "status_participacao": {
      "type": "string",
      "description": "",
      "enum": [
       "ativo",
       "dnf",
       "dq"
      ]
     },
     "external_id": {
      "type": "string",
      "description": "Id deste registro no SEU sistema. Único no escopo (clube para provas, prova para inscrições) — reenviar o mesmo valor devolve 409 em vez de duplicar. `null` desvincula.",
      "maxLength": 64,
      "nullable": true
     }
    }
   },
   "Inscricao": {
    "allOf": [
     {
      "$ref": "#/components/schemas/InscricaoResumo"
     },
     {
      "type": "object",
      "properties": {
       "cpf": {
        "type": "string",
        "description": "",
        "nullable": true
       },
       "telefone": {
        "type": "string",
        "description": "",
        "nullable": true
       },
       "status_pgto": {
        "type": "string",
        "description": "",
        "enum": [
         "PENDENTE",
         "PAGO",
         "ISENTO"
        ]
       },
       "obs_pgto": {
        "type": "string",
        "description": "",
        "nullable": true
       },
       "presenca": {
        "type": "string",
        "description": ""
       },
       "campos_entidade": {
        "type": "object",
        "description": "Campos adicionais do clube."
       },
       "pin_definido": {
        "type": "boolean",
        "description": "Se o atleta já criou o PIN da prova. O PIN em si nunca circula pela API."
       },
       "criada_em": {
        "type": "string",
        "description": "",
        "format": "date-time",
        "nullable": true
       },
       "atualizada_em": {
        "type": "string",
        "description": "",
        "format": "date-time",
        "nullable": true
       }
      }
     }
    ]
   },
   "Juiz": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "username": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "email": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "telefone": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "gestor_implicito": {
      "type": "boolean",
      "description": "Espelho de um gestor da entidade, criado pelo sistema — não é um juiz cadastrado à mão."
     }
    }
   },
   "Alvo": {
    "type": "object",
    "properties": {
     "alvo": {
      "type": "integer",
      "description": "Número do alvo no percurso (1..num_alvos)."
     },
     "contagem": {
      "type": "object",
      "additionalProperties": {
       "type": "integer"
      },
      "description": "Disparos por anel: `{\"x\": 1, \"10\": 2, \"9\": 2}`. Só anéis com contagem maior que zero aparecem."
     },
     "pontos": {
      "type": "integer",
      "description": "Derivado no servidor. X vale 10."
     }
    }
   },
   "Score": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "serie_uuid": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "inscricao_uuid": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "juiz": {
      "type": "object",
      "properties": {
       "uuid": {
        "type": "string",
        "description": ""
       },
       "nome": {
        "type": "string",
        "description": ""
       }
      }
     },
     "alvos": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Alvo"
      },
      "description": "A fita, alvo a alvo, na ordem do percurso."
     },
     "aneis": {
      "type": "object",
      "additionalProperties": {
       "type": "integer"
      },
      "description": "Agregado da fita por anel — base do desempate."
     },
     "pontos": {
      "type": "integer",
      "description": "Soma da fita, derivada no servidor."
     },
     "contagem_x": {
      "type": "integer",
      "description": ""
     },
     "penalidade": {
      "type": "integer",
      "description": "Descontada do total no ranking."
     },
     "total": {
      "type": "integer",
      "description": "pontos − penalidade."
     },
     "observacao": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "aprovado": {
      "type": "boolean",
      "description": "Assinado. Só score aprovado entra no ranking."
     },
     "aprovado_sem_pin": {
      "type": "boolean",
      "description": "Fechado por EXCEÇÃO do staff, com motivo no histórico — distinto de prova que não pede aceite do atleta."
     },
     "bloqueado": {
      "type": "boolean",
      "description": ""
     },
     "is_dnf": {
      "type": "boolean",
      "description": ""
     },
     "versao": {
      "type": "integer",
      "description": ""
     },
     "assinaturas": {
      "type": "object",
      "properties": {
       "juiz": {
        "type": "object",
        "properties": {
         "assinado": {
          "type": "boolean",
          "description": ""
         },
         "em": {
          "type": "string",
          "description": "",
          "format": "date-time",
          "nullable": true
         }
        }
       },
       "atleta": {
        "type": "object",
        "properties": {
         "assinado": {
          "type": "boolean",
          "description": ""
         },
         "em": {
          "type": "string",
          "description": "",
          "format": "date-time",
          "nullable": true
         }
        }
       }
      }
     },
     "atualizado_em": {
      "type": "string",
      "description": "",
      "format": "date-time",
      "nullable": true
     }
    }
   },
   "RankingLinha": {
    "type": "object",
    "properties": {
     "posicao": {
      "type": "integer",
      "description": "Empate irredutível divide a mesma posição, até haver resultado de shoot-off."
     },
     "empatado": {
      "type": "boolean",
      "description": "Outro atleta ocupa esta mesma posição."
     },
     "shoot_off_pendente": {
      "type": "boolean",
      "description": "Empate na modalidade sem resultado de shoot-off. Só no ranking geral da modalidade."
     },
     "shootoff_colocacao": {
      "type": "integer",
      "nullable": true,
      "description": "Colocação registrada no shoot-off que definiu esta posição."
     },
     "total": {
      "type": "integer",
      "description": "pontos − penalidade."
     },
     "pontos": {
      "type": "integer",
      "description": ""
     },
     "penalidade": {
      "type": "integer",
      "description": ""
     },
     "contagem_x": {
      "type": "integer",
      "description": ""
     },
     "aneis": {
      "type": "object",
      "additionalProperties": {
       "type": "integer"
      },
      "description": "Critério de desempate por anel, já agregado."
     },
     "por_alvo": {
      "type": "object",
      "additionalProperties": {
       "type": "integer"
      },
      "description": "Pontos somados por número de alvo — o critério SCTSL (alvo 4 → 3 → 2 → 1)."
     },
     "series_lancadas": {
      "type": "integer",
      "description": ""
     },
     "dnf": {
      "type": "boolean",
      "description": ""
     },
     "dq": {
      "type": "boolean",
      "description": "Desclassificado — vai para o fim do ranking."
     },
     "atleta": {
      "$ref": "#/components/schemas/InscricaoResumo"
     },
     "por_serie": {
      "type": "array",
      "items": {
       "type": "object",
       "properties": {
        "serie_uuid": {
         "type": "string",
         "description": ""
        },
        "numero": {
         "type": "integer",
         "description": ""
        },
        "nome": {
         "type": "string",
         "description": ""
        },
        "pontos": {
         "type": "integer",
         "description": "",
         "nullable": true
        }
       }
      }
     }
    }
   },
   "Campeonato": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "ano": {
      "type": "integer",
      "description": ""
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "publico": {
      "type": "boolean",
      "description": ""
     },
     "codigo_publico": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "etapas_previstas": {
      "type": "integer",
      "description": ""
     },
     "minimo_etapas": {
      "type": "integer",
      "description": "Etapas necessárias para ser classificado."
     },
     "descartes": {
      "type": "integer",
      "description": "Quantas das PIORES pontuações saem do total."
     },
     "usa_categorias": {
      "type": "boolean",
      "description": ""
     },
     "usa_classes": {
      "type": "boolean",
      "description": ""
     },
     "idade_modo": {
      "type": "string",
      "description": "",
      "enum": [
       "dia_exato",
       "fim_ano"
      ]
     },
     "classe_criterio": {
      "type": "string",
      "description": "",
      "enum": [
       "percentual",
       "pontos"
      ]
     },
     "trava_primeira_participacao": {
      "type": "boolean",
      "description": ""
     },
     "criado_em": {
      "type": "string",
      "description": "",
      "format": "date-time",
      "nullable": true
     },
     "atualizado_em": {
      "type": "string",
      "description": "",
      "format": "date-time",
      "nullable": true
     }
    }
   },
   "Classe": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "codigo": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "ordem": {
      "type": "integer",
      "description": ""
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "criterio": {
      "type": "string",
      "description": "",
      "enum": [
       "manual",
       "faixa"
      ]
     },
     "valor_min": {
      "type": "number",
      "description": "Piso da faixa, inclusivo.",
      "nullable": true
     },
     "valor_max": {
      "type": "number",
      "description": "Teto da faixa, EXCLUSIVO — `[min, max)`. O teto de uma classe é o piso da seguinte.",
      "nullable": true
     },
     "sem_historico": {
      "type": "boolean",
      "description": "Classe de quem ainda não tem índice."
     },
     "descricao": {
      "type": "string",
      "description": "",
      "nullable": true
     }
    }
   },
   "Categoria": {
    "type": "object",
    "properties": {
     "uuid": {
      "type": "string",
      "description": ""
     },
     "codigo": {
      "type": "string",
      "description": ""
     },
     "nome": {
      "type": "string",
      "description": ""
     },
     "ordem": {
      "type": "integer",
      "description": ""
     },
     "ativo": {
      "type": "boolean",
      "description": ""
     },
     "tipo": {
      "type": "string",
      "description": "",
      "enum": [
       "automatica",
       "manual"
      ]
     },
     "sexo": {
      "type": "string",
      "description": "`M`, `F` ou `null` (mista).",
      "nullable": true
     },
     "idade_min": {
      "type": "integer",
      "description": "",
      "nullable": true
     },
     "idade_max": {
      "type": "integer",
      "description": "",
      "nullable": true
     },
     "faixa_etaria": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "descricao": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "classes": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/Classe"
      }
     }
    }
   },
   "LinkPublico": {
    "type": "object",
    "properties": {
     "url": {
      "type": "string",
      "description": "",
      "nullable": true
     },
     "ativa": {
      "type": "boolean",
      "description": ""
     },
     "motivo": {
      "type": "string",
      "description": "Por que o link não abre, quando `ativa` é falso.",
      "nullable": true
     }
    }
   }
  }
 }
}
