KOLOS — Registro de Rotas, Acessos e Commands
Documentação KOLOS

KOLOS — Registro de Rotas, Acessos e Commands

Contexto

Para evitar divergências entre código e documentação, precisamos de um registro central (fonte única) com:

  • Rotas HTTP (paths, nomes, handlers)
  • Regras de acesso (middleware, roles, policies)
  • Commands (Artisan)
  • Channels (Broadcast)

Isso reduz retrabalho e evita “esqueci de documentar uma rota” em cada mudança.

Fonte da Verdade

  • routes/web.php
  • routes/console.php
  • routes/channels.php

Web (HTTP)

Públicas

  • GET /homeApp\Livewire\Home
  • GET /p/{token}play-card.publicApp\Livewire\PublicPlayCard — Play Card só leitura (UUID public_card_token); sem auth; não expõe email nem Academy; meta noindex, nofollow (@stack('head') no layout)
  • GET /docsdocs — view docs.index
  • GET /docs/architecturedocs.architecture — view docs.markdown (README de docs/architecture/; links .mddocs.read)
  • GET /docs/changesdocs.changes — view docs.markdown (README de docs/changes/; idem)
  • GET /docs/read/{path}docs.readApp\Http\Controllers\DocsReadController — qualquer *.md sob docs/ (path com /; recusa .. fora de docs/)

Guest

Middleware: guest

  • GET /loginloginApp\Livewire\Auth\Login
  • GET /cadastroonboarding.registerApp\Livewire\Onboarding\RegisterPlayer

Autenticadas

Middleware: auth

  • GET /dashboarddashboardApp\Livewire\Dashboard
  • POST /logoutlogoutApp\Http\Controllers\Auth\LogoutController

Mundo Player

Middleware: auth + actor_context + role:player

  • GET /bookingbookingApp\Livewire\Booking
  • GET /communitycommunityApp\Livewire\Community
  • GET /my-cardmy-cardApp\Livewire\MyCardPlay Card: players.level, players.skill_rating, apelido (updateNickname), partilha (sharePlayCard, EnsurePlayerPublicCardShareUrlAction, RegeneratePlayerPublicCardShareUrlActionplay-card.public), faixa (Player::playCardTierLabel()); Academy: presenças (ListPlayerAcademyAttendanceForMyCardAction), só leitura
  • GET /my-classesmy-classesApp\Livewire\Academy\MyEnrollments — turmas em que o jogador está inscrito (ListPlayerAcademyEnrollmentsAction); convites pendentes (ListPendingInvitationsForPlayerAction); pedidos de entrada pendentes (ListPlayerPendingEnrollmentRequestsAction + CancelAcademyClassEnrollmentRequestAction); ação Livewire leaveClass + LeaveAcademyClassAsMemberAction (AcademyClassPolicy::leaveAsMember)
  • GET /academy/browse-classesacademy.browse-classesApp\Livewire\Academy\BrowseAcademyClasses — lista turmas com accepts_enrollment_requests e não arquivadas, excluindo turmas em que o jogador já é membro (ListOpenAcademyClassesForPlayerAction); pedido via CreateAcademyClassEnrollmentRequestAction (AcademyClassPolicy::requestEnrollment)
  • GET /academy/invitations/{token}academy.invitations.respondApp\Livewire\Academy\InvitationRespond — convite do treinador (AcademyClassInvitation); aceitar AcceptAcademyClassInvitationAction / recusar DeclineAcademyClassInvitationAction (AcademyClassInvitationPolicy)
  • GET /my-locationmy-locationApp\Livewire\MyLocation

Mundo Coach (Academy — lista de turmas)

Middleware: auth + actor_context + role:coach + permission:academy.class.view + active_actor:coach

  • GET /academy/classesacademy.classes.indexApp\Livewire\Academy\MyClasses
    • Lista academy_classes em que o utilizador é coach_user_id.
  • Regra UX: contexto ativo «Treinador»; caso contrário 403.

Mundo Coach — criar turma (arena onde está associado)

Middleware: mesmo grupo coach acima e permission:academy.class.create_as_coach

  • GET /academy/linked-venuesacademy.linked-venuesApp\Livewire\Academy\CoachLinkedAcademyVenues
    • Lista venues com venue_coaches ativo para o utilizador (ListVenuesLinkedToCoachAction).
  • GET /academy/venues/{venue}/classes/newacademy.venues.classes.createApp\Livewire\Academy\CoachCreateAcademyClass
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::createClassAsCoach (permissão + papel coach + vínculo ativo na arena)
    • Criação: CreateAcademyClassAction com coach_user_id = ator (não-dono não pode indicar outro treinador)

Mundo Coach — avaliações Play Card Beach Tennis

Middleware: auth + actor_context + role:coach + permission:academy.coach.credentialed + active_actor:coach

  • GET /coach/playcard-evaluationscoach.playcard-evaluationsApp\Livewire\Coach\PlaycardEvaluationHub
    • Lista pedidos abertos (ListOpenBeachPlaycardEvaluationRequestsForCoachAction), procura de atletas (SearchPlayersForBeachPlaycardEvaluationAction), agendar (ScheduleBeachPlaycardEvaluationRequestAction) ou arquivar (DismissBeachPlaycardEvaluationRequestAction).
  • GET /coach/playcard-evaluations/evaluate/{player}coach.playcard-evaluations.formApp\Livewire\Coach\PlaycardEvaluationForm
    • Constraints: {player} UUID (Kolos\Modules\Player\Models\Player)
    • Query opcional ?request= UUID (BeachPlaycardEvaluationRequest) — deve pertencer ao treinador e ao atleta.
    • Policy: PlayerPolicy::evaluateBeachPlaycard (qualquer treinador credenciado pode avaliar); pedido associado validado na ação.
    • Finalização: FinalizeBeachPlaycardEvaluationAction — grava beach_playcard_evaluations, atualiza players.beach_playcard_snapshot, marca pedido completed quando aplicável. Motor: BeachPlaycardMethodology2026Playcard estatístico (drills com acertos × pesos → IEA 0–99 por fundamento; média global; faixas D 0–19 / C 20–45 / B 46–70 / A 71–90 / PRO 91–99; travas: categoria B exige DEF ≥ 45; A na média exige SRV e SMA ≥ 71; PRO na média com SRV/SMA insuficientes desce a A). A categoria no cartão é escolhida pelo treinador dentro de [min(trava, média−1), média] (não se pode subir acima da média IEA; piso coerente com travas e um degrau abaixo da média). methodology_weights.category_after_gates guarda a sugestão pós-travas.

Academy — roster (treinador designado ou dono da arena)

Middleware: auth + actor_context

  • GET /academy/classes/{academyClass}academy.classes.rosterApp\Livewire\Academy\ClassRoster
    • Constraints: {academyClass} UUID (Kolos\Modules\Academy\Models\AcademyClass)
    • Policy: AcademyClassPolicy::view — treinador designado (academy.class.view) ou dono da arena da turma (academy.class.create + owner_user_id)
    • 404 se o UUID não existir
    • Ações Livewire (mesma página): pedidos de entrada pendentes (ListPendingEnrollmentRequestsForClassAction, aprovar ApproveAcademyClassEnrollmentRequestAction, rejeitar RejectAcademyClassEnrollmentRequestAction); pesquisa de atletas (debounce) + inscrição imediata (EnrollPlayerInAcademyClassAction) ou convite com email (InvitePlayerToAcademyClassAction + PlayerInvitedToAcademyClassMail em fila) e remoção (RemoveAcademyClassMemberAction) quando AcademyClassPolicy::manageStudents permite; capacidade max_members aplicada em inscrições e pedidos
    • Play Card oficial: coluna nível/elo (players.level, players.skill_rating); edição só se AcademyClassPolicy::updateMemberOfficialPlayCard (treinador credenciado da turma). Leitura para os restantes com view.

Academy — sessões e presenças

Middleware: auth + actor_context

  • GET /academy/classes/{academyClass}/sessionsacademy.classes.sessionsApp\Livewire\Academy\ClassSessions
    • Constraints: {academyClass} UUID
    • Policy: AcademyClassPolicy::view para ver; manageStudents para criar sessão e guardar presenças
    • Dados: academy_class_sessions (uma por (turma, data)); academy_class_session_attendances (present / absent / excused)
    • Ordem de rotas: esta rota deve estar antes de academy.classes.roster (path mais específico).

Mundo Venue Owner

Middleware: auth + role:venue_owner|super_admin

  • GET /venuesvenues.indexApp\Livewire\Venue\MyVenues
    • Regras internas: bloqueia via canAny(venue.create|venue.update|venue.manage_courts)
  • GET /venues/{venue}/courtsvenues.courtsApp\Livewire\Venue\ManageCourts
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::manageCourts (dono da arena ou super_admin)
    • Policy (criar quadra): CourtPolicy::create(User, Court::class, Venue)
    • Horário diário da arena (venues.daily_opens_at / daily_closes_at): formulário na mesma página; alimenta a grelha de horas livres do picker Academy (ListAvailableCourtPickerHoursForWeekdayAction + VenueCourtPickerHourRange); vazio = fallback config('kolos_academy.court_picker_*')
    • Edição de quadra: UpdateCourtAction + CourtPolicy::update
  • GET /venues/{venue}/schedulevenues.scheduleApp\Livewire\Venue\VenueSchedule
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::manageCourts — agenda semanal por quadra (bookings da arena: BOOKING, MAINTENANCE, ACADEMY_CLASS_HOLD); link para roster da turma quando aplicável (AcademyClassPolicy::view); criação de reserva tipo BOOKING em nome do dono (CreateBookingAction); legenda do horário de funcionamento da arena (VenueCourtPickerHourRange::labelForVenue)
  • GET /venues/{venue}/maintenancevenues.maintenanceApp\Livewire\Venue\DeclareMaintenance
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::declareMaintenance (dono da arena ou super_admin)
  • GET /venues/{venue}/coachesvenues.coachesApp\Livewire\Venue\ManageVenueCoaches
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::manageAcademy
    • Dados: venue_coaches (treinadores ativos na arena); AttachVenueCoachAction / DetachVenueCoachAction (não remove se existir turma ativa não arquivada com esse coach_user_id)
  • GET /venues/{venue}/training-classesvenues.training-classesApp\Livewire\Venue\TrainingClasses
    • Constraints: {venue} UUID
    • Policy: VenuePolicy::manageAcademy (dono da arena + academy.class.create, ou super_admin via before)
    • Lista de turmas inclui resumo de presenças (SummarizeVenueAcademyAttendanceAction): n.º de sessões, última data, % presenças nos últimos 30 dias por turma
    • Criação/edição de turma: treinador tem de constar em venue_coaches com estado active (exceto super_admin em CreateAcademyClassAction / UpdateAcademyClassAction); horários em academy_class_schedule_slots (sync + rótulo automático se schedule_label vazio); max_members (opcional), accepts_enrollment_requests
    • Modelo academy_classes.archived_at: dono arquiva/reativa (SetAcademyClassArchivedAction, AcademyClassPolicy::archive); turma arquivada bloqueia novas sessões, inscrições, gravação de presenças e Play Card oficial (ver AcademyClassArchiveRules)
  • GET /venues/{venue}/training-classes/attendance.csvvenues.training-classes.attendance-csvApp\Http\Controllers\Venue\ExportVenueAcademyAttendanceCsvController — CSV de presenças (turma, data, aluno, email, estado); query opcional from e to (Y-m-d, ambos obrigatórios se um for enviado; intervalo máx. 731 dias; filtra session_date); sem query = histórico completo; policy via ExportVenueAcademyAttendanceCsvAction (manageAcademy)

API interna

  • GET /api/cep/{cep}api.cep.lookupApp\Http\Controllers\CepLookupController
    • Constraints: {cep} regex [0-9\-]{8,9}

Console (Artisan)

  • inspire — Exibe uma frase inspiradora.
  • player:regeocode-address {userId?} [--email=] [--clear-cache] [--force] [--inspect] [--allow-low-confidence] [--min-confidence=] [--omit-neighborhood] [--omit-number] [--query=]
    • Propósito: recalcular latitude/longitude do endereço do Player via OpenCage.

Broadcast Channels

  • App.Models.User.{id}
    • Autorização: apenas o próprio usuário ((int) $user->id === (int) $id).

Middleware (aliases)

  • active_actor:{role}App\Http\Middleware\EnsureActiveActorMatches — exige que ActorContext::get($user) coincida com {role} (ex.: coach).

Como manter atualizado

Sempre que uma rota/command/channel for criado/alterado:

  1. Atualizar este documento.
  2. Incluir um resumo no documento de mudança correspondente em docs/changes/.

Validação

  • Listar rotas: sail artisan route:list
  • Listar comandos: sail artisan list
  • Verificar policies: testes de feature/policy em tests/Feature/**