# ClickPoints API — Referência completa para LLMs e integrações > ClickPoints é uma plataforma SaaS multi-tenant de engajamento e gamificação corporativa (missões, desafios, > PDI, quizzes, loja de recompensas, academia/e-learning, ranking, feed social, check-in diário, mural de avisos). Esta é a referência completa > da API REST — tudo que um agente/LLM precisa para operar o sistema. ## Convenções gerais - Base URL: `https://api.points.click.app/api/v1` - Formato: JSON. Toda resposta de sucesso envelopa em `{"data": ...}`. Erros: `{"message": "..."}` (+ `errors` por campo em 422). - Status: 200/201 sucesso · 401 sem token · 403 sem permissão/módulo · 404 não encontrado · 422 validação · 429 rate limit. - Datas: `YYYY-MM-DD HH:MM:SS` (America/Sao_Paulo). ## Autenticação 1. `POST /auth/login` com `{"email": "...", "password": "..."}` → `data.token` (Bearer, Laravel Sanctum). 2. Envie em TODAS as chamadas: `Authorization: Bearer ` e `Accept: application/json`. 3. **Multi-empresa**: o usuário pode ser membro de N empresas. Envie o header `X-Company: ` para escolher a empresa ativa (obrigatório quando o usuário tem mais de uma; liste com `GET /me/companies`). Todo dado é isolado por empresa — a mesma chamada retorna dados diferentes conforme o X-Company. 4. Módulos por empresa: rotas com gate `module:` retornam 404 se a empresa desativou o módulo (challenges, store, roulette, quizzes, polls, forum, evaluations, calendar, academia, quests, pdi, competencies, checkin). 5. Permissões: papéis por empresa (hierarchy_level: 0=super-admin da plataforma, 1=admin da empresa, 2-3=gestor, 4+=participante). Endpoints `admin/*` exigem admin/gestor; `tenant/*` exige DONO do tenant. 6. Identidade de conta: nome/e-mail/senha são globais da PESSOA. Admin só edita identidade de contas criadas pela própria empresa (managed); contas pessoais são self-service (use POST /admin/users/{id}/send-reset). ## Fluxos essenciais (receitas) - **Agir numa empresa**: login → `GET /me/companies` → escolher id → header `X-Company` em tudo. - **Criar desafio**: `POST /challenges` (admin) → participantes entram via `POST /challenges/{id}/participate` → enviam evidência `POST /challenges/{id}/submissions` (multipart: content/link/files[]) → admin aprova `POST /challenge-submissions/{id}/approve` → pontos são creditados. - **Curso completo (academia)**: criar curso/módulos/aulas (admin/academia) → aluno `POST /academia/courses/{id}/enroll` → `POST /academia/lessons/{id}/progress` (a cada ~15s) → `POST /academia/lessons/{id}/complete` → aula type=quiz exige aprovação no quiz (pass_percentage) para avançar → 100% = XP+pontos+certificado automáticos. - **Dar pontos manualmente**: `POST /admin/point-transactions` ou via aprovação de atividades. --- ## Autenticação e Mídia Login/cadastro via Laravel Sanctum (Bearer token), recuperação de senha e upload/serviço de mídia. **Convenções de auth:** faça `POST /api/v1/auth/login` e envie o token retornado em `Authorization: Bearer ` nas rotas autenticadas. Header opcional `X-Company: ` seleciona a empresa ativa (validado contra a membership do usuário; sem ele, usa a empresa default). Empresa não autorizada → 403. Rotas com middleware `module:` retornam 404 se o módulo estiver desligado na empresa. ### POST /api/v1/auth/login Autentica e emite token Sanctum. Auth: público (throttle 6/min). - Body: `email` (string, required) — e-mail do usuário. `password` (string, required). `device_name` (string, opcional) — nome do token (default "spa"). - Resposta: 201 `data: { token, user }` — user com id, name, email, avatar_path, status, must_change_password, company_id, sector_id, is_owner, is_admin, is_manager, points, level, level_title, roles, permissions. Credenciais inválidas → 422; usuário inativo → 403. ### GET /api/v1/auth/me Retorna o usuário autenticado (e avalia conquistas pendentes em background). Auth: token. - Resposta: `data: { ...UserResource }` (mesmo shape do user do login). ### POST /api/v1/auth/logout Revoga o token de acesso atual. Auth: token. - Resposta: 204 sem corpo. ### POST /api/v1/auth/signup Cria conta nova: tenant + primeira empresa + usuário dono (papel Administrador, todos os módulos ligados). Auth: público (throttle 5/min). Gate: fechado se `tenancy.allow_signup` desligado → 403. - Body: `nome` (string, required, max 255). `email` (email, required, único global). `password` (string, required, min 8). `company_name` (string, required, max 255). `tenant_type` (string, opcional) — `independentes` (default) ou `filiais`. - Resposta: 201 `data: { token, company_id, user: { id, nome, email } }`. ### GET /api/v1/auth/signup-companies Lista empresas que liberaram o domínio do e-mail para auto-cadastro. Auth: público (throttle 20/min). - Query: `email` (string, required na prática) — usa o domínio após o @. - Resposta: `data: [{ id, name }]` (máx. 20, ordenado por nome; vazio se domínio inválido/sem match). ### POST /api/v1/auth/signup-user Colaborador se cadastra numa empresa existente (domínio do e-mail precisa estar liberado pela empresa). Auth: público (throttle 5/min). - Body: `nome` (string, required, max 255). `email` (email, required, único global). `password` (string, required, min 8). `company_id` (integer, required, empresa existente). - Resposta: 201 `data: { token, company_id, user: { id, nome, email } }`. Empresa sem auto-cadastro → 403; domínio não autorizado → 422. ### POST /api/v1/auth/forgot-password Envia link de recuperação de senha por e-mail (link aponta pro SPA). Auth: público (throttle 5/min). - Body: `email` (email, required). - Resposta: `data: { message }` — mensagem genérica (não revela se o e-mail existe). ### POST /api/v1/auth/reset-password Redefine a senha usando o token do e-mail de recuperação. Auth: público (throttle 5/min). - Body: `token` (string, required) — token do link. `email` (email, required). `password` (string, required, min 8) + `password_confirmation` (required, igual). - Resposta: `data: { message }`. Token inválido/expirado ou e-mail não encontrado → 422. ### POST /api/v1/uploads Sobe um arquivo (S3 privado em prod) e devolve a chave durável + URL de exibição. Auth: token. - Body (multipart): `file` (file, required, max 50MB) — mimes: jpeg, jpg, png, gif, webp, pdf, doc(x), xls(x), ppt(x), mp4, webm, mov. `folder` (string, opcional, max 40) — só [a-z0-9-]; default "geral". - Resposta: `data: { key, url }` — guarde a `key` no banco; use a `url` (aponta para /media/{token}) para exibir. ### GET /api/v1/media/{token} Serve mídia via URL estável: redireciona (302) para a URL real (pré-assinada no S3). Auth: público (o token é uma capability opaca). - Resposta: 302 redirect; token inválido → 404. ## Me (área do participante) Dados do próprio usuário logado: carteira, dashboard, missões, pedidos, perfil, conquistas, XP e empresas. Todas as rotas exigem token (Sanctum) + empresa ativa resolvida (ResolveTenant). ### GET /api/v1/me/wallet Saldo + extrato paginado do próprio usuário. Auth: token. - Query: `page` (int) — padrão 1; `per_page` (int) — 5..50, padrão 15; `type` (string) — filtra por tipo de transação; `direction` (string) — `in` (créditos) ou `out` (débitos); `q` (string) — busca na descrição. - Resposta: `data.points`, `data.level`, `data.transactions[]` (id, amount, balance_after, type, description, created_at), `data.total`, `data.page`, `data.per_page`, `data.has_more`, `data.types[]` (tipos existentes p/ filtro). ### GET /api/v1/me/dashboard Números do painel inicial do participante. Auth: token. - Resposta: `data.saldo` (pontos na empresa ativa), `data.polls_voted`, `data.challenges_participating`, `data.challenges_completed`. ### GET /api/v1/me/quests Missões ativas do usuário (máx 20). Auth: token. - Resposta: `data.quests[]` (id, progress, quest_type, title, difficulty), `data.count`. ### GET /api/v1/me/orders Pedidos do usuário logado (máx 100, mais recentes primeiro). Auth: token. - Resposta: `data[]` (id, order_number, status, quantity, points_spent, total_points, created_at, delivered_at, notes, product_id, product_name, product_image). ### GET /api/v1/me/orders/{id} Detalhe de um pedido próprio (404 se não for do usuário). Auth: token. Gate: owner. - Resposta: `data.order` (campos do pedido + product_name, product_image, product_description), `data.items[]` (itens + product_name). ### POST /api/v1/me/orders/{id}/cancel Cancela pedido próprio com status pending/approved; estorna pontos e devolve estoque. Auth: token. Gate: owner. 422 se status não permitir. - Resposta: `data.message`. ### GET /api/v1/me/profile Perfil completo do usuário logado + timeline recente. Auth: token. - Resposta: `data.profile` (id, nome, email, avatar_path, description, points, level, level_title, experience, xp_in_level, xp_to_next, balance, followers, following, posts, challenges_completed, achievements_earned/total, sectors[], cargo, status, data_nascimento, created_at), `data.posts[]` (posts recentes com likes_count, comments_count, liked, mentions). ### PUT /api/v1/me/profile Atualiza os próprios dados. Auth: token. - Body: `nome` (string, required) — máx 255; `email` (email, required) — único em users; `description` (string) — máx 1000; `data_nascimento` (date); `avatar_path` (string) — máx 500. - Resposta: `data.message`. ### POST /api/v1/me/avatar Sobe a foto de avatar (multipart) para o S3 e salva imediatamente. Auth: token. - Body: `file` (file, required) — imagem jpeg/jpg/png/webp, máx 5MB. - Resposta: `data.avatar_path` (URL). ### POST /api/v1/me/password Troca a própria senha (exige a atual); zera must_change_password e revoga as outras sessões. Auth: token. 422 se senha atual incorreta. - Body: `current_password` (string, required); `password` (string, required) — 8..100 chars. - Resposta: `data.message`. ### GET /api/v1/me/achievements Conquistas do usuário (ganhas + bloqueadas) com progresso. Auth: token. - Resposta: `data.achievements[]` (id, slug, name, description, icon, image_path, earned, awarded_at), `data.earned`, `data.total`, `data.percent`. ### GET /api/v1/me/experience-history Histórico de XP (últimos 100) + totais. Auth: token. - Resposta: `data.history[]` (amount, reason, source, created_at, expires_at, is_expired), `data.valid_xp`, `data.total_earned`, `data.level`. ### GET /api/v1/me/companies Empresas das quais o usuário é membro ativo (para o seletor de empresa). Auth: token. - Resposta: `data[]` (id, name, tenant_type, currency, logo, is_owner, active — se é a empresa ativa da sessão). ### GET /api/v1/me/joinable-companies Empresas ativas cujo domínio do e-mail do usuário está liberado e onde ele ainda não é membro (máx 20). Auth: token. - Resposta: `data[]` (id, name). ### POST /api/v1/me/join-company Entra numa empresa que liberou o domínio do e-mail (autoatendimento). Auth: token. 403 se domínio não autorizado; 422 se já for membro. - Body: `company_id` (int, required) — id de empresa existente. - Resposta: `data.company_id`, `data.name`, `data.message`. ## Notificações Notificações do usuário (sino do header). Auth: token em todas. ### GET /api/v1/me/notifications Últimas 20 notificações + contador de não-lidas. Auth: token. - Resposta: `data.notifications[]` (id, type, title, message, data, action_url, action_text, read_at, read, created_at), `data.unread_count`. ### GET /api/v1/me/notifications/unread-count Só o contador de não-lidas. Auth: token. - Resposta: `data.count`. ### POST /api/v1/me/notifications/{id}/read Marca uma notificação própria como lida. Auth: token. Gate: owner. - Resposta: `data.message` ("ok"). ### POST /api/v1/me/notifications/read-all Marca todas as notificações do usuário como lidas. Auth: token. - Resposta: `data.message` ("ok"). ### DELETE /api/v1/me/notifications/{id} Remove uma notificação própria. Auth: token. Gate: owner. - Resposta: `data.message` ("ok"). ## Equipe (gestor no portal) Gestor vê/gerencia usuários dos setores que gerencia, respeitando hierarquia (só nível inferior). ### GET /api/v1/me/team Setores gerenciados + usuários deles. Auth: token. Se não for gestor, retorna `is_manager: false` com listas vazias. - Resposta: `data.is_manager`, `data.sectors[]` (id, name), `data.users[]` (id, nome, email, ativo, avatar_path, sector_name, job_position_name, role, can_manage, is_following), `data.managed_sectors`. ### POST /api/v1/me/team/{id}/password Define a senha de um membro gerenciável; força troca no próximo login e revoga as sessões do alvo. Auth: token. Gate: gestor do setor + hierarquia (403 se não puder gerenciar). - Body: `password` (string, required) — 6..100 chars. - Resposta: `data.message`. ### POST /api/v1/me/team/{id}/toggle-active Ativa/desativa o membro NA EMPRESA ATIVA (company_user.ativo). Auth: token. Gate: gestor do setor + hierarquia. 404 se não for membro da empresa ativa. - Resposta: `data.ativo` (0|1), `data.message`. ## Carteira (visão administrativa) Saldos e movimentação de pontos de outros usuários. Gestor age só sobre os setores que gerencia; admin/`wallet.admin` sobre todos. ### GET /api/v1/wallet Lista de carteiras (usuários + saldo da empresa ativa), paginada. Auth: token. Gate: gestor ou permissão `wallet.view`/`wallet.admin` (escopo por setor se não for admin). - Query: `search` (string) — nome/e-mail; `sector_id` (int); `min_points` (num); `max_points` (num); `per_page` (int, máx 100, padrão 20). - Resposta: `data[]` (id, nome, email, avatar_path, points, level, sector_name), `meta` (current_page, per_page, total, last_page), `can_manage`, `sectors[]`. ### POST /api/v1/wallet/adjustment Ajuste administrativo de pontos (positivo ou negativo). Auth: token. Gate: gestor ou `wallet.adjustment`/`wallet.admin`; alvo deve estar no setor gerenciado. - Body: `user_id` (int, required); `amount` (num, required) — ≠ 0; `description` (string) — máx 255. - Resposta: 201 com `data.transaction_id`, `data.balance_after`, `data.message`. ### POST /api/v1/wallet/bulk-credit Crédito em massa por lista de usuários e/ou setor inteiro. Auth: token. Gate: gestor ou `wallet.bulk_credit`/`wallet.admin`. 422 se nenhum alvo. - Body: `user_ids` (int[]) — opcional; `sector_id` (int) — credita os ativos do setor; `amount` (num, required) — mín 0.01; `description` (string) — máx 255. - Resposta: 201 com `data.credited`, `data.skipped`, `data.message`. ### POST /api/v1/wallet/credit Crédito manual de pontos a um usuário. Auth: token. Gate: gestor ou `wallet.admin`. - Body: `user_id` (int, required); `amount` (num, required) — mín 0.01; `description` (string). - Resposta: 201 com `data.transaction_id`, `data.balance_after`. ### POST /api/v1/wallet/debit Débito manual de pontos de um usuário (amount é enviado positivo). Auth: token. Gate: gestor ou `wallet.admin`. - Body: `user_id` (int, required); `amount` (num, required) — mín 0.01; `description` (string). - Resposta: 201 com `data.transaction_id`, `data.balance_after`. ## Dashboard (visão geral admin) Contadores e KPIs da empresa ativa para a visão geral administrativa. ### GET /api/v1/dashboard/stats Contadores por módulo, KPIs, pendências, distribuição por setor e top 10 usuários. Auth: token. - Resposta: `data.kpis` (total_users, active_users, points_circulating, achievements_awarded, avg_level), `data.attention[]` (key, label, route, count), `data.by_sector[]` (name, users, points), `data.counts[]` (key, label, count), `data.top_users[]` (id, nome, points, current_level), `data.total_points`, `data.total_users`. ## Ranking Rankings por categoria (ClickPoints, XP, Quiz, RolePlay, Desafios, Duelos, Tabuleiros). ### GET /api/v1/ranking Ranking (top 50) por tipo e período; sem permissão de ver todos os setores, trava no setor do próprio usuário. Auth: token. - Query: `type` (string) — points|experience|quiz|roleplay|challenges|duels|boards, padrão points; `period` (string) — all|week|month|year, padrão all; `sector_id` (int) — só com permissão global; `job_position_id` (int). - Resposta: `data.type`, `data.period`, `data.rows[]` (id, nome, avatar_path, sector_name, job_position_name, value, value_label, unit, detail), `data.types[]`, `data.can_view_all_sectors`, `data.my_sector_id`, `data.sectors[]`, `data.job_positions[]`. ## Permissões Catálogo de permissões RBAC (somente leitura; versionado no código). ### GET /api/v1/permissions Permissões ativas agrupadas por módulo. Auth: token. Gate: gestor ou permissão `roles.view`. - Resposta: `data[]` (module, module_alias, module_order, icon, permissions[] {name, display_name, type, category, assignable}). ## Tenant (empresas do dono) Gestão do tenant pelo dono da conta: empresas, membros (acessos), módulos e logo. Todas as rotas exigem token; gate: dono do tenant (owner_user_id) ou master de plataforma. ### GET /api/v1/tenant/companies Lista empresas do tenant + meta. Auth: token. Gate: owner. - Resposta: `data[]` {id, name, active, parent_company_id, is_matriz, shares_users, members}; `meta` {tenant_type, shared_catalogs[], shareable_catalogs[], can_delete}. ### POST /api/v1/tenant/companies Cria empresa no tenant (dono vira membro/admin dela). Auth: token. Gate: owner. - Body: `name` (string, required); `parent_company_id` (int) — matriz; `clone_from` (int) — clona catálogos de outra empresa do tenant; `shares_users` (bool); `shared_catalogs` (string[]) — só vale se for matriz; `modules` (string[]) — módulos habilitados (ausente = todos); `currency` (string, max 20). - Resposta: 201, `data` {id}. ### GET /api/v1/tenant/companies/{id} Dados da empresa para o form de edição. Auth: token. Gate: owner. - Resposta: `data` {id, name, active, currency, parent_company_id, is_matriz, shares_users, shared_catalogs[], allowed_signup_domains[], logo}. ### PATCH /api/v1/tenant/companies/{id} Edita empresa (nome/ativa/sharing/domínios de auto-cadastro). Auth: token. Gate: owner. - Body (todos opcionais): `name` (string); `active` (bool); `shares_users` (bool) — true concede acesso a todos os usuários do tenant; `shared_catalogs` (string[]) — só matriz; `currency` (string); `allowed_signup_domains` (string[]|null) — allowlist de domínios. - Resposta: `data` {id}. ### POST /api/v1/tenant/companies/{id}/delete-code Envia código de 6 dígitos (validade 15 min) ao e-mail do solicitante para confirmar exclusão. Auth: token. Gate: owner + permissão `companies.delete` (ou super admin). - Resposta: `data` {sent_to}. ### DELETE /api/v1/tenant/companies/{id} Soft-delete da empresa (exige código enviado por e-mail); bloqueia última empresa do tenant e matriz com filiais. Auth: token. Gate: owner + `companies.delete`. - Body: `code` (string, required) — código recebido por e-mail. - Resposta: 204 sem corpo. 422 se código inválido/expirado ou regras violadas. ### POST /api/v1/tenant/companies/{id}/logo Sobe a logo da empresa (multipart). Auth: token. Gate: owner. - Body: `file` (file, required) — imagem jpeg/jpg/png/webp/svg, max 5MB. - Resposta: `data` {logo} (URL). ### DELETE /api/v1/tenant/companies/{id}/logo Remove a logo da empresa. Auth: token. Gate: owner. - Resposta: `data` {logo: null}. ### GET /api/v1/tenant/companies/{id}/members Membros ativos da empresa, paginado (20/página) com busca. Auth: token. Gate: owner. - Query: `q` (string) — busca por nome/e-mail; `page` (int). - Resposta: `data[]` {user_id, nome, email, ativo, points, current_level, is_master}; `meta` {total, page, last_page}. ### POST /api/v1/tenant/companies/{id}/members Dá acesso a um usuário do MESMO tenant à empresa. Auth: token. Gate: owner. - Body: `user_id` (int, required) — 422 se não pertencer ao tenant. - Resposta: 201, `data` {ok: true}. ### DELETE /api/v1/tenant/companies/{id}/members/{userId} Revoga acesso (soft, ativo=0). 422 se alvo for master/dono ou for o último membro ativo. Auth: token. Gate: owner. - Resposta: 204 sem corpo. ### GET /api/v1/tenant/companies/{id}/modules Lista módulos da empresa com status. Auth: token. Gate: owner. - Resposta: `data[]` {module, enabled}. ### PUT /api/v1/tenant/companies/{id}/modules Liga/desliga um módulo da empresa. Auth: token. Gate: owner. - Body: `module` (string, required, max 40); `enabled` (bool, required). - Resposta: `data` {ok: true}. ### GET /api/v1/tenant/users/search Busca usuários do tenant por nome/e-mail (autosuggest, max 15). Auth: token. Gate: owner. - Query: `q` (string). - Resposta: `data[]` {id, label (nome), hint (email)}. ## Usuários (listagem básica + papéis do usuário) Listagem simples de usuários e atribuição/remoção de papéis por usuário. ### GET /api/v1/users Lista usuários paginada (Spatie QueryBuilder). Auth: token. Gate: permissão `users.view`. - Query: `filter[nome]`, `filter[email]` (parcial); `filter[ativo]`, `filter[sector_id]` (exatos); `sort` (id|nome|points|created_at, prefixo `-` = desc); `per_page` (int, max 100); `page`. - Resposta: `data[]` {id, name, email, status (active|pending|inactive), company_id, sector_id, points}; links/meta de paginação Laravel. ### GET /api/v1/users/{user} Detalhe do usuário. Auth: token. Gate: permissão `users.view`. - Resposta: `data` {id, name, email, avatar_path, status, must_change_password, company_id, sector_id, is_owner, is_admin, is_manager, points, level, level_title}; + {is_master, roles[], permissions[]} se o viewer for o próprio/admin/master. ### POST /api/v1/users/{id}/roles Atribui papel a um usuário. Auth: token. Gate: gestor ou `roles.assign` + gerenciar o usuário; papel deve estar no alcance hierárquico do autor. - Body: `role_id` (int, required, existente). - Resposta: 201, `data` {message}. ### DELETE /api/v1/users/{id}/roles/{roleId} Remove papel de um usuário. Auth: token. Gate: gestor ou `roles.assign` + gerenciar o usuário; papel no alcance hierárquico. - Resposta: `data` {message}. ## Administração de usuários CRUD administrativo de usuários amarrando setores + cargo/nível + papéis. Escopo: admin vê todos (da empresa ativa); gestor vê só membros dos setores que gerencia. ### GET /api/v1/admin/users Lista escopada com filtros e papéis. Auth: token. Gate: gestor ou `users.view`/`users.admin`. - Query: `search` (nome/e-mail); `sector_id` (int); `job_position_id` (int); `status` (active|inactive); `role` (int, role_id); `per_page` (int, max 100); `page`. - Resposta: `data[]` {id, name, email, status, points, current_level, sector_id, sector_name, job_position, is_master, roles[]}; `meta` {current_page, per_page, total, last_page}. Pontos/nível são por empresa ativa. ### POST /api/v1/admin/users Cria usuário (transação: setores + cargo/nível + papéis + vínculo à empresa ativa). Senha inicial é temporária (troca obrigatória). Auth: token. Gate: gestor ou `users.create`/`users.admin`; setores/papéis limitados ao escopo/hierarquia do autor. - Body: `name` (string, required, max 150); `email` (email, required, único); `password` (string, required, min 6); `status` (active|inactive); `sector_ids` (int[], required, min 1); `primary_sector_id` (int); `job_position_id` (int) — exige `current_level` (int, ≥1, existente no cargo); `role_ids` (int[]). - Resposta: 201, mesmo shape do GET /admin/users/{id}. ### POST /api/v1/admin/users/attach Vincula usuário JÁ EXISTENTE (por e-mail) à empresa ativa; exige que o domínio do e-mail esteja em `allowed_signup_domains` da empresa. Auth: token. Gate: gestor ou `users.create`/`users.admin`. - Body: `email` (email, required, max 190). - Resposta: `data` {id, nome, message}. 404 se não existir; 422 se já for membro; 403 se domínio não liberado. ### GET /api/v1/admin/users/{id} Perfil administrativo completo (form). Auth: token. Gate: gestor/`users.view` + usuário dentro do escopo. - Resposta: `data` {id, name, email, status, points, sector_id, job_position_id, job_position, current_level, hierarchy_level, is_master, sectors[] {id,name,is_primary,joined_at}, roles[] {id,name,hierarchy_level}, identity_editable, identity_lock_reason, managed, salary? {amount,currency} (só com permissão de salário)}. ### PUT /api/v1/admin/users/{id} Edita usuário; identidade (nome/e-mail/senha) só se o tenant do ator gerencia a conta (UserIdentity). Status/cargo são gravados por empresa (company_user). Senha/e-mail trocados derrubam sessões do alvo. Auth: token. Gate: admin ou canManageUser (hierarquia/setor). - Body (todos opcionais): `name`; `email` (único); `password` (min 6, vira temporária); `status` (active|inactive); `sector_ids` (int[]); `primary_sector_id` (int); `job_position_id` (int) + `current_level`; `role_ids` (int[]). - Resposta: mesmo shape do GET /admin/users/{id}. ### POST /api/v1/admin/users/{id}/password Define/redefine senha do usuário (temporária, troca no 1º acesso; derruba sessões). Auth: token. Gate: gestor ou `users.admin`/`users.edit` + canManageUser + identidade editável pelo tenant. - Body: `password` (string, required, 6-100). - Resposta: `data` {message}. ### POST /api/v1/admin/users/{id}/send-reset Envia link de redefinição de senha por e-mail a um membro da empresa ativa (caminho p/ contas de identidade não editável). Auth: token. Gate: gestor ou `users.admin`/`users.edit`; exige empresa ativa e alvo membro dela. - Resposta: `data` {message} (neutra, não confirma existência do e-mail). ### GET /api/v1/admin/users/import/template Colunas aceitas + exemplo para o import em lote. Auth: token. - Resposta: `data` {columns[] (nome,email,senha,setores,setor_primario,cargo,papeis,status,nivel,salario,moeda,pontos,data_nascimento,descricao,admin), required[] (nome,email), example{}, notes}. ### POST /api/v1/admin/users/import Import em lote: resolve nomes → ids (setores/cargo/papéis), upsert por e-mail, relatório por linha; salário aplicado por cargo+nível. Auth: token. Gate: admin (`users.admin` ou master). - Body: `rows` (array, required, 1-2000, cada item objeto com as colunas do template); `dry_run` (bool) — só valida, nada é gravado; `update_existing` (bool, default true); `send_invite` (bool) — convite por e-mail p/ criados. - Resposta: `summary` {total, dry_run, invites_sent, salaries_applied, created, updated, linked, skipped, errors}; `rows[]` {line, email, user_id?, action (created|updated|linked|skipped|error), messages[], generated_password}. ## Papéis (roles) Gestão de papéis (spatie) com hierarquia: só se gerencia papéis de nível abaixo do seu; papéis de sistema protegidos; papéis globais só editáveis pelo master. ### GET /api/v1/roles Lista papéis visíveis (globais + da empresa ativa; não-admin só vê níveis abaixo do seu). Auth: token. Gate: gestor ou `roles.view`. - Resposta: `data[]` {id, name, display_name, description, hierarchy_level, active, is_system, users_count, permissions_count, can_manage}. ### POST /api/v1/roles Cria papel na empresa ativa. Auth: token. Gate: admin ou `roles.manage`; nível dentro do alcance do autor. - Body: `name` (string, required, max 100, único na empresa+globais); `display_name` (string); `description` (string, max 500); `hierarchy_level` (int, required, 0-10); `active` (bool); `permissions` (string[], nomes existentes). - Resposta: 201, mesmo shape do GET /roles/{id}. ### GET /api/v1/roles/{id} Papel + nomes de permissões. Auth: token. Gate: gestor ou `roles.view`. - Resposta: `data` {id, name, display_name, description, hierarchy_level, active, is_system, permissions[] (nomes), users_count, can_manage}. ### PUT /api/v1/roles/{id} Atualiza papel (is_system não muda nível/ativo; permissões filtradas pelo que o autor pode atribuir). Auth: token. Gate: admin ou `roles.manage`; papel editável (da empresa; global só master) e no alcance. - Body (opcionais): `display_name`; `description`; `hierarchy_level` (int 0-10); `active` (bool); `permissions` (string[]). - Resposta: mesmo shape do GET /roles/{id}. ### PUT /api/v1/roles/{id}/permissions Sincroniza permissões do papel (preserva as fora do alcance do autor). Auth: token. Gate: admin ou `roles.manage`; papel editável e no alcance. - Body: `permissions` (string[], required/present, nomes existentes). - Resposta: mesmo shape do GET /roles/{id}. ### DELETE /api/v1/roles/{id} Exclui papel; 422 se for de sistema ou tiver usuários. Auth: token. Gate: admin ou `roles.manage`; papel editável e no alcance. - Resposta: `data` {message}. ## Setores Setores com gestores (principal + adicionais) e membros (user_sectors). Escopo: admin=todos; gestor=setores que gerencia ou onde é membro; escrita restrita aos setores que gerencia. ### GET /api/v1/sectors Lista escopada com gestor e contagens. Auth: token. Gate: permissão `sectors.view` (middleware) + gestor/`sectors.admin`. - Query: `search` (nome); `active` (bool); `per_page` (int, max 100); `page`. - Resposta: `data[]` {id, name, code, description, active, manager_id, manager_name, participants_count, managers_count}; `meta` paginação. ### POST /api/v1/sectors Cria setor (code auto-gerado; gestores precisam ter papel de gestão nível ≤3 e ser membros da empresa). Auth: token. Gate: gestor ou `sectors.manage`/`sectors.admin`. - Body: `name` (string, required, max 150); `description` (string, max 1000); `active` (bool); `manager_id` (int) — gestor principal; `managers` (int[]) — gestores adicionais. - Resposta: 201, `data` {id, name, code, description, active, manager {id,name}, managers[], participants_count, managers_count}. ### GET /api/v1/sectors/{id} Detalhe do setor (escopado). Auth: token. Gate: `sectors.view` + setor no escopo. - Resposta: mesmo shape do POST /sectors. ### PUT /api/v1/sectors/{id} Atualiza setor (code imutável; gestor não-admin só edita setor que gerencia). Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Body (opcionais): `name`; `description`; `active` (bool); `manager_id` (int); `managers` (int[]). - Resposta: mesmo shape do detalhe. ### DELETE /api/v1/sectors/{id} Exclui setor; 422 se houver membros. Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Resposta: `data` {message}. ### GET /api/v1/sectors/{id}/members Membros do setor com cargo e is_primary. Auth: token. Gate: `sectors.view` + setor no escopo. - Resposta: `data[]` {id, name, email, job_position, current_level, is_primary, joined_at}. ### POST /api/v1/sectors/{id}/members Adiciona membros (usuários ativos da empresa ativa). Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Body: `user_ids` (int[], required, min 1). - Resposta: 201, `data` {message}. ### DELETE /api/v1/sectors/{id}/members/{userId} Remove vínculo do membro (se era primário, promove outro setor). Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Resposta: `data` {message}. ### PATCH /api/v1/sectors/{id}/members/{userId}/primary Marca o setor como principal do usuário. Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Resposta: `data` {message}. ### POST /api/v1/sectors/{id}/managers Substitui a lista de gestores adicionais (nível ≤3). Auth: token. Gate: gestor/`sectors.manage` + gerencia o setor. - Body: `user_ids` (int[], required/present, pode ser vazio). - Resposta: `data` = payload do setor (mesmo shape do detalhe). ## Cargos (job positions) Cargos com níveis e salário por nível (com histórico). Escopo por setor (gestor só nos setores que gerencia); salário sob permissões dedicadas. ### GET /api/v1/job-positions Lista escopada com filtros e contagens. Auth: token. Gate: gestor ou `job-positions.view`/`job-positions.admin`. - Query: `sector_id` (int); `is_active` (bool); `search` (nome); `per_page` (int, max 100); `page`. - Resposta: `data[]` {id, name, sector_id, sector_name, description, max_levels, is_active, levels_count, users_count, salary_min, salary_max (null sem permissão de salário)}; `meta` paginação. ### POST /api/v1/job-positions Cria cargo + níveis (transação); nome único por setor. Auth: token. Gate: gestor/`job-positions.manage` + gerencia o setor. - Body: `sector_id` (int, required); `name` (string, required, max 150); `description` (string, max 1000); `is_active` (bool); `levels` (array, required, min 1) — itens {level (int, required, ≥1), alias (string), description (string)}. - Resposta: 201, `data` {id, name, sector_id, sector_name, description, max_levels, is_active, users_count, levels[] {id, level, alias, description, base_salary?, salary_currency?, salary_effective_from?}}. ### GET /api/v1/job-positions/{id} Cargo + níveis (salário só com permissão). Auth: token. Gate: view + setor no escopo. - Resposta: mesmo shape do POST. ### PUT /api/v1/job-positions/{id} Atualiza cargo + upsert de níveis por (cargo, level); 422 ao remover nível com funcionários. Auth: token. Gate: manage + escopo. - Body (opcionais): `name`; `description`; `is_active` (bool); `levels` (array, min 1, mesmo shape do POST) — níveis ausentes são removidos. - Resposta: mesmo shape do detalhe. ### DELETE /api/v1/job-positions/{id} Exclui cargo (cascade em níveis/histórico); 422 se houver funcionários. Auth: token. Gate: manage + escopo. - Resposta: `data` {message}. ### GET /api/v1/job-positions/{id}/levels Níveis do cargo (alimenta cascata do form de usuário). Auth: token. Gate: view + escopo. - Resposta: `data[]` {id, level, alias, description, base_salary?, salary_currency?, salary_effective_from? (só com permissão de salário)}. ### POST /api/v1/job-positions/{id}/levels/{levelId}/salary Registra alteração de salário do nível (histórico + cache vigente; vigência futura fica só no histórico). Auth: token. Gate: admin ou `job-positions.salary.manage` + escopo. - Body: `amount` (numeric, required, ≥0); `currency` (string, 3 letras); `change_type` (initial|raise|decrease|adjustment|correction, derivado se ausente); `reason` (string, max 500); `effective_from` (date, required). - Resposta: 201, `data` {message, scheduled (bool)}. ### GET /api/v1/job-positions/{id}/levels/{levelId}/salary-history Histórico de salário do nível + valor vigente. Auth: token. Gate: admin ou `job-positions.salary.view`/`.manage` + escopo. - Resposta: `data` {current {base_salary, amount, currency, effective_from}, history[] {id, amount, previous_amount, currency, change_type, reason, effective_from, changed_by_name, created_at}} (max 200 itens). ## Catálogo genérico CRUD genérico sobre recursos configurados em config/resources.php, escopado por empresa/tenant. Leitura pública (autenticada) só p/ recursos de referência (sectors, achievements, job-positions, store-categories, quiz-categories, forum-categories, store-products, polls, ranking, challenges, quizzes, roulette-wheels, competencies, calendar-events, quests); o resto exige gestor. Escrita: gestor só em taxonomias simples (store-categories, quiz-categories, forum-categories, calendar-events, roulette-wheels); demais recursos, admin. ### GET /api/v1/catalog/{resource} Lista paginada do recurso (colunas configuradas + `{fk}_label` amigáveis). `ranking` usa pontos/nível por empresa. Auth: token. Gate: leitura conforme recurso (público autenticado ou gestor). - Query: `sort` (coluna, prefixo `-` = desc); `search` (em name/title/nome); `per_page` (int, max 100); `page`. - Resposta: `data[]` (linhas flat com as colunas do recurso); `meta` {current_page, per_page, total, last_page}. ### GET /api/v1/catalog/{resource}/meta Metadados p/ o frontend: colunas, campos de formulário e ações. Auth: token. Gate: leitura conforme recurso. - Resposta: `data` {columns[], form[], actions[], can_create}. ### GET /api/v1/catalog/{resource}/options Opções {id, label} para selects de referência (max 2000). Auth: token. Gate: gestor. - Resposta: `data[]` {id, label}. ### GET /api/v1/catalog/{resource}/{id} Registro completo (todos os campos, exceto sensíveis como password/token) + labels de FKs. Auth: token. Gate: leitura conforme recurso. - Resposta: `data` (objeto flat com todas as colunas + `{fk}_label`). ### POST /api/v1/catalog/{resource} Cria registro validando pelo `form` do recurso; carimba company/tenant automaticamente. 405 se recurso somente leitura. Auth: token. Gate: escrita (gestor p/ taxonomias; admin p/ o resto). - Body: campos definidos no `form` do recurso (ver /meta); tipos number/boolean/date/datetime/string. - Resposta: 201, `data` (linha criada com as colunas do recurso). 422 em violação de unicidade/FK. ### PUT /api/v1/catalog/{resource}/{id} Atualiza registro (campos do `form`, todos opcionais). Auth: token. Gate: escrita conforme recurso. - Resposta: `data` (linha atualizada). ### DELETE /api/v1/catalog/{resource}/{id} Remove registro (soft-delete se o recurso suporta). 405 se somente leitura. Auth: token. Gate: escrita conforme recurso. - Resposta: `data` {message}. ## Desafios, Orçamento e Transações de Pontos Desafios com evidências e recompensas, orçamento por setor/atividade e extrato global de pontos. Rotas com Gate `module:challenges` retornam 404 se o módulo estiver desligado na empresa. ### GET /api/v1/challenges Lista paginada de desafios (visibilidade aplicada: gestor vê tudo). Auth: token. Gate: module:challenges + permissão `challenges.view`. - Query: `filter[title]` (string) — busca parcial; `filter[status]`, `filter[active]`, `filter[approval_status]`, `filter[creator_id]` — exatos; `sort` (id, title, points_reward, created_at, start_date; padrão -id); `per_page` (int, máx 100); `page`. - Resposta: `data[]` com id, title, description, status, approval_status, points_reward, reward_type, visibility_type, allow_teams, is_race, creator_id, start_date, end_date, active, created_at; `links`/`meta` de paginação. ### GET /api/v1/challenges/{id} Detalhe simples do desafio (403 se não visível ao usuário). Auth: token. Gate: module:challenges + permissão `challenges.view`. - Resposta: `data` no mesmo shape do ChallengeResource acima. ### POST /api/v1/challenges Cria desafio (nasce como draft). Auth: token. Gate: module:challenges + gestor. - Body: `title` (string, required); `description` (string, required); `instructions` (string); `min_level` (int); `target_type` (required, global|sector|individual); `target_sectors[]`/`target_users[]` (int[]); `allow_teams` (bool); `team_min_members`/`team_max_members` (int); `evidence_types[]` (file|link|text); `is_race` (bool); `reward_kind` (points|product|badge|experience|free_spins|custom); `points_reward` (int); `reward_slots`, `reward_product_id`, `reward_quantity`, `reward_badge_id`, `reward_xp`, `reward_roulette_id`, `reward_spins` (int); `reward_description` (string); `start_date` (date, required); `end_date` (date, required, >= start_date); `uses_budget`, `publish_results` (bool); `max_submissions` (int); `approval_source` (manual|api, default manual) — em `api` a evidência só é aprovada/rejeitada por token M2M externo (não pelo admin no painel). - Resposta: 201 `data: {id, message}`. ### PUT /api/v1/challenges/{id} Atualiza desafio (422 se já há evidência aprovada). Auth: token. Gate: module:challenges + criador/gestor (canManage). - Body: mesmos campos do POST /challenges. - Resposta: `data: {id, message}`. ### DELETE /api/v1/challenges/{id} Exclui desafio, reembolsa verba não usada e devolve estoque de prêmio (422 se há evidência aprovada, salvo master). Auth: token. Gate: module:challenges + canManage. - Resposta: `data: {message, refunded, returned_stock}`. ### GET /api/v1/challenges/list Lista enriquecida (até 100) com estado de participação do usuário. Auth: token. Gate: module:challenges. - Query: `available` (bool) — só desafios ativos dentro da janela de datas. - Resposta: `data[]` com campos do desafio (id, title, status, approval_source, points_reward, target_type, dates, creator_name...) + is_participating, is_creator, can_participate, can_submit. ### GET /api/v1/challenges/{id}/detail Detalhe rico: quem gerencia vê todas as submissões/participantes; colaborador vê só os seus. Auth: token. Gate: module:challenges + visibilidade. - Resposta: `data: {challenge (inclui approval_source: manual|api — front esconde os botões aprovar/rejeitar manuais quando 'api'), stats {participants, submissions, pending, approved}, participants[], submissions[] (attachments como URLs), budget, me {is_participating, has_submission, can_submit, is_creator, can_participate, participate_reason, can_leave, leave_reason, can_manage, level}}`. ### GET /api/v1/challenges/{id}/history Timeline de auditoria do desafio (até 200 eventos). Auth: token. Gate: module:challenges + canManage. - Resposta: `data[]: {id, user, action, action_label, created_at, changes[] {field, old, new}}`. ### POST /api/v1/challenges/{id}/state Muda o estado do desafio com transições validadas (activate exige estoque de prêmio; complete/cancel auto-rejeitam pendentes e reembolsam verba). Auth: token. Gate: module:challenges + canManage. - Body: `action` (string, required) — activate|pause|complete|cancel. - Resposta: `data: {status, refunded, settled_pending}`. ### POST /api/v1/challenges/{id}/participate Inscreve o usuário no desafio (elegibilidade validada no servidor; 422 com motivo se não pode); emite o webhook `challenge.joined`. Auth: token. - Resposta: 201 `data: {message}` (200 se já participava). ### POST /api/v1/challenges/{id}/submissions Envia evidência (multipart). Exige participação, desafio ativo, sem pendente e respeita max_submissions e tipos de evidência exigidos. Auth: token. - Body: `content` (string, 10-5000); `link` (url); `files[]` (arquivos jpeg/png/gif/webp/pdf/doc(x)/xls(x), máx 5, 10MB cada). Ao menos um dos três. - Resposta: 201 `data: {submission_id, status: "pending"}`. ### POST /api/v1/challenges/{id}/leave Sai do desafio (só se sem evidência/não concluído). Auth: token. Gate: module:challenges. - Resposta: `data: {message}`. ### POST /api/v1/challenges/{id}/allocate Aloca verba de um orçamento de setor para o desafio (marca uses_budget=1). Auth: token. Gate: module:challenges + canManage. - Body: `sector_budget_id` (int, required); `amount` (numeric, required, min 0.01); `description` (string, máx 1000). - Resposta: 201 `data: {message}`. ### POST /api/v1/challenges/{id}/refund Reembolsa ao setor o saldo de orçamento não utilizado do desafio. Auth: token. Gate: module:challenges + canManage. - Resposta: `data: {message, refunded}`. ### POST /api/v1/challenges/{id}/budget/remove Reembolsa o não utilizado e desativa o orçamento do desafio (uses_budget=0). Auth: token. Gate: module:challenges + canManage. - Resposta: `data: {message, refunded}`. ### POST /api/v1/challenge-submissions/{id}/approve Aprova a submissão, credita pontos (com teto por usuário e trava de orçamento) e entrega prêmio de produto; em desafio corrida encerra os demais. Emite os webhooks `challenge.submission.approved` (evento da submissão) e `challenge.completed` (participante concluiu). Auth: token. Gate: module:challenges + canManage do desafio. Se `approval_source='api'`, só um token M2M REAL (com `challenges.write`) pode confirmar — sessão SPA/admin → 403. - Resposta: `data: {message}` (422 se já processada ou orçamento insuficiente; 403 se approval_source=api e a request não vem de token M2M). ### POST /api/v1/challenge-submissions/{id}/reject Rejeita a submissão pendente com feedback opcional; emite o webhook `challenge.submission.rejected`. Auth: token. Gate: module:challenges + canManage do desafio. Se `approval_source='api'`, só um token M2M REAL pode rejeitar — sessão SPA/admin → 403. - Body: `feedback` (string) — motivo mostrado ao participante. - Resposta: `data: {message}` (422 se já processada; 403 se approval_source=api e a request não vem de token M2M). ### GET /api/v1/sector-budgets-available Orçamentos de setor com saldo disponível para alocação (gestor não-admin só vê seus setores). Auth: token. Gate: module:challenges + gestor ou `budget.view`. - Resposta: `data[]` de orçamentos de setor disponíveis. ### POST /api/v1/sector-budgets/{id}/add-funds Adiciona verba a um orçamento de setor. Auth: token. Gate: module:challenges + gestor do setor ou `budget.fund.add`/`budget.admin`. - Body: `amount` (numeric, required, min 0.01). - Resposta: 201 `data: {message}`. ### GET /api/v1/budget/dashboard Visão consolidada de orçamento: totais, por setor e por tipo de atividade. Auth: token. Gate: module:challenges + gestor ou `budget.view`/`budget.admin`. - Resposta: `data: {totals {total, used, available, budgets}, by_sector[] {id, sector_id, name, sector, total, used, available, percent, is_current, period_type, period_end}, by_activity_type[] {activity_type, allocated, used, count}}`. ### GET /api/v1/budget/sectors/{id} Detalhe de um orçamento de setor: atividades financiadas + últimas 30 transações. Auth: token. Gate: module:challenges + gestor/`budget.view` (setor no escopo). - Resposta: `data: {id, name, sector, sector_id, total, used, available, percent, period_type, period_start, period_end, active, can_add_funds, activities[], transactions[]}`. ### POST /api/v1/budget/sectors Cria orçamento de setor calculando período e nome automaticamente. Auth: token. Gate: module:challenges + gestor do setor ou `budget.create`/`budget.admin`. - Body: `sector_id` (int, required); `total_amount` (numeric, required, min 0.01); `period_type` (required, monthly|quarterly|yearly|custom); `year` (int 2020-2035); `month` (1-12); `quarter` (1-4); `period_start`/`period_end` (date, required se custom). - Resposta: 201 `data: {id, message}`. ### GET /api/v1/budget/sector-view/{id} Página do setor: resumo agregado dos orçamentos vigentes + lista (vigentes/futuros por padrão). Auth: token. Gate: module:challenges + gestor/`budget.view` (setor no escopo). - Query: `show_all` (bool) — inclui períodos passados. - Resposta: `data: {sector {id, name}, summary {total, used, available, percent, vigente_count}, budgets[], show_all, has_past, can_add_funds}`. ### GET /api/v1/budget/sector-view/{id}/transactions Transações de todos os orçamentos do setor, com filtros e paginação (per_page grande serve para exportação). Auth: token. Gate: module:challenges + gestor/`budget.view` (setor no escopo). - Query: `type` (string); `category` (string); `from`/`to` (date); `q` (string, busca na descrição); `per_page` (int, 1-5000, padrão 20); `page`. - Resposta: `data[]: {id, type, category, amount, description, balance, created_at, budget_name}`; `meta {current_page, per_page, total, last_page}`. ### POST /api/v1/activity-budgets/{id}/suspend Suspende o orçamento de uma atividade (id = linha de activity_budgets; a verba fica retida). Auth: token. Gate: module:challenges + gestor ou `budget.admin`. - Resposta: `data: {message}`. ### POST /api/v1/activity-budgets/{id}/resume Reativa o orçamento suspenso de uma atividade. Auth: token. Gate: module:challenges + gestor ou `budget.admin`. - Resposta: `data: {message}`. ### PATCH /api/v1/user-budget-limits/{id}/reset-usage Zera o uso corrente de um limite de orçamento por usuário. Auth: token. Gate: module:challenges + gestor ou `budget.admin`. - Resposta: `data: {message}`. ### PATCH /api/v1/user-budget-limits/{id}/toggle Ativa/desativa um limite de orçamento por usuário. Auth: token. Gate: module:challenges + gestor ou `budget.admin`. - Resposta: `data: {is_active (0|1), message}`. ### GET /api/v1/point-transactions Extrato global de transações de pontos, com filtros e paginação. Auth: token. Gate: permissão `wallet.view`. - Query: `user_id` (int); `type` (string); `search` (string, nome/email do usuário); `per_page` (int, máx 100); `page`. - Resposta: `data[]: {id, user_id, user_name, user_email, amount, type, description, balance_after, created_at}`; `meta` de paginação; `types[]` (tipos distintos existentes). ### POST /api/v1/point-transactions/{id}/reverse Estorna uma transação (só credit|debit|admin_adjustment; 422 se já estornada). Auth: token. Gate: gestor/`wallet.adjustment` + usuário-alvo no escopo de setor (admin/`wallet.admin` sem restrição). - Resposta: `data: {message, ...resultado do ajuste}`. ## Academia (e-learning) Cursos com módulos/aulas, matrícula, progresso por aula, marcos de recompensa, quiz-gate e certificado. Todas as rotas exigem token (Sanctum) e módulo `academia` ativo na empresa (senão 404). Fluxo do aluno: `POST enroll` → `POST progress` (player) → `POST complete` por aula (aula `type=quiz` exige aprovação prévia no quiz vinculado) → ao chegar a 100% a matrícula vira `completed`, credita points/XP e emite certificado (se `certificate_enabled` e sem `requires_approval`) → `GET certificates/{code}` valida/exibe. ### GET /api/v1/academia/courses Catálogo de cursos publicados com estado do usuário. Auth: token. Gate: module:academia + permissão academia.view. - Query: `category_id` (int) — filtra categoria; `level` (string) — iniciante|intermediario|avancado; `search` (string) — busca no título. - Resposta: `data[]` {id, title, slug, summary, cover_image_url, category_id, level, estimated_minutes, instructor_name, lessons_count, certificate_enabled, points_reward, xp_reward, my_status, my_progress}; `meta.categories[]` {id, name, icon}. ### GET /api/v1/academia/courses/{id} Detalhe do curso com módulos, aulas, marcos e meu progresso (404 se não publicado, salvo quem tem academia.manage). Auth: token. Gate: module:academia + academia.view. - Resposta: `data` {id, title, description, summary, cover_image_url, level, instructor_name, estimated_minutes, certificate_enabled, points_reward, xp_reward, my_enrollment {status, progress, last_lesson_id}|null, my_certificate (código|null), modules[] {id, title, description, order, lessons[]}, loose_lessons[] (aulas sem módulo), milestones[] {id, title, trigger_value, points_reward, xp_reward, earned}}. Cada aula: {id, module_id, title, description, type, duration_seconds, is_preview, order, my_status}. ### POST /api/v1/academia/courses/{id}/enroll Matricula o usuário no curso publicado (idempotente; cria lesson_progress por aula ativa). Auth: token. Gate: module:academia + academia.view. - Resposta: 201, `data` = matrícula (course_enrollments: id, course_id, user_id, status=in_progress, progress_percentage, enrolled_at...). 404 se curso não publicado. ### GET /api/v1/academia/my-courses Lista as matrículas do usuário. Auth: token. Gate: module:academia + academia.view. - Resposta: `data[]` {enrollment_id, course_id, title, cover_image_url, level, status, progress, completed_at}. ### GET /api/v1/academia/lessons/{id} Conteúdo da aula (exige matrícula, ou `is_preview=true`; senão 403). Auth: token. Gate: module:academia + academia.view. - Resposta: `data` {id, course_id, title, description, type (video|text|pdf|quiz|link), video_provider, video_url, content, attachment_url, quiz_id, duration_seconds, is_preview, my_status, watched_seconds, next_lesson_id, prev_lesson_id, quiz_passed (bool), quiz_pass_percentage (float|null)}. ### POST /api/v1/academia/lessons/{id}/progress Registra tempo assistido no player; marca a aula in_progress e atualiza last_lesson_id. Auth: token. Gate: module:academia + academia.view. - Body: `watched_seconds` (integer, required, min 0) — guarda o máximo já registrado. - Resposta: `data` = matrícula atualizada. 403 se não matriculado. ### POST /api/v1/academia/lessons/{id}/complete Conclui a aula (idempotente), credita points_reward da aula (1x) e recalcula o curso; a 100% conclui a matrícula, concede recompensas/certificado e marcos cruzados. Auth: token. Gate: module:academia + academia.view. - Resposta: `data` = matrícula recalculada (progress_percentage, status, completed_at...). - Erro 422 se a aula é `type=quiz` e não há tentativa `completed` com score >= pass_percentage do quiz (quiz-gate). ### GET /api/v1/academia/certificates/{code} Valida/exibe um certificado emitido (escopo da empresa). Auth: token. Gate: module:academia + academia.view. - Resposta: `data` {code, issued_at, course_title, user_name, company_name, company_logo}. 404 se código inválido. ### GET /api/v1/admin/academia/categories Lista todas as categorias de curso. Auth: token. Gate: module:academia + academia.manage. - Resposta: `data[]` = linhas de course_categories (id, name, slug, description, icon, order, active). ### POST /api/v1/admin/academia/categories Cria categoria (slug gerado do nome). Auth: token. Gate: module:academia + academia.manage. - Body: `name` (string, required, max 255); `description` (string); `icon` (string, max 255); `order` (integer); `active` (boolean). - Resposta: 201, `data` = categoria criada. ### PUT /api/v1/admin/academia/categories/{id} Atualiza categoria (campos parciais). Auth: token. Gate: module:academia + academia.manage. - Body: `name` (string); `description` (string); `icon` (string); `order` (integer); `active` (boolean) — todos opcionais. - Resposta: `data` = categoria atualizada. ### DELETE /api/v1/admin/academia/categories/{id} Remove categoria; cursos vinculados ficam com category_id=null. Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` {deleted: true}. ### GET /api/v1/admin/academia/courses Lista cursos (todos os status) com contagens, paginado. Auth: token. Gate: module:academia + academia.manage. - Query: `search` (string) — título; `status` (string) — draft|published|archived; `per_page` (int, default 20, max 100). - Resposta: `data[]` = curso completo + lessons_count + enrollments_count; `meta` {total, current_page, last_page}. ### POST /api/v1/admin/academia/courses Cria curso (status default draft; slug gerado do título). Auth: token. Gate: module:academia + academia.manage. - Body: `title` (string, required); `description` (string, required); `summary` (string, max 500); `cover_image_url` (string); `category_id` (int); `level` (iniciante|intermediario|avancado); `status` (draft|published|archived); `instructor_name` (string); `estimated_minutes` (int); `points_reward` (int); `xp_reward` (int); `requires_approval` (bool); `certificate_enabled` (bool); `tags` (array); `is_active` (bool). - Resposta: 201, `data` = curso criado. ### GET /api/v1/admin/academia/courses/{id} Detalhe admin do curso com estrutura completa. Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` = curso + `modules[]` + `lessons[]` + `milestones[]` (linhas completas das tabelas). ### PUT /api/v1/admin/academia/courses/{id} Atualiza curso (mesmos campos do POST, todos opcionais; status/slug não alterados aqui). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` = curso atualizado. ### DELETE /api/v1/admin/academia/courses/{id} Soft delete do curso (seta deleted_at). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` {deleted: true}. ### POST /api/v1/admin/academia/courses/{id}/publish Muda o status do curso (publicar/despublicar/arquivar). Auth: token. Gate: module:academia + academia.manage. - Body: `status` (string, default "published") — draft|published|archived (422 se inválido). - Resposta: `data` = curso atualizado. ### GET /api/v1/admin/academia/courses/{id}/report Relatório de matrículas e conclusão do curso. Auth: token. Gate: module:academia + permissão academia.report. - Resposta: `data` {total, completed, completion_rate, rows[] {user_id, name, status, progress, completed_at, enrolled_at, watched_seconds}}. ### POST /api/v1/admin/academia/courses/{courseId}/modules Cria módulo no curso (order default = próximo). Auth: token. Gate: module:academia + academia.manage. - Body: `title` (string, required, max 255); `description` (string); `order` (integer). - Resposta: 201, `data` = módulo criado. ### PUT /api/v1/admin/academia/courses/{courseId}/modules/{id} Atualiza módulo. Auth: token. Gate: module:academia + academia.manage. - Body: `title` (string); `description` (string); `order` (integer) — opcionais. - Resposta: `data` = módulo atualizado. ### DELETE /api/v1/admin/academia/courses/{courseId}/modules/{id} Remove módulo; aulas do módulo ficam com module_id=null (viram "soltas"). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` {deleted: true}. ### POST /api/v1/admin/academia/courses/{courseId}/modules/reorder Reordena módulos do curso pela ordem do array. Auth: token. Gate: module:academia + academia.manage. - Body: `order` (array de int, required) — ids dos módulos na nova ordem. - Resposta: `data` {reordered: true}. ### POST /api/v1/admin/academia/courses/{courseId}/lessons Cria aula no curso. Regras por tipo: video exige video_provider+video_url; text exige content; pdf/link exigem attachment_url; quiz exige quiz_id válido da empresa (422 caso contrário). Auth: token. Gate: module:academia + academia.manage. - Body: `title` (string, required); `description` (string); `module_id` (int, deve ser do curso); `type` (required, video|text|pdf|quiz|link); `video_provider` (youtube|vimeo|url); `video_url` (string); `content` (string); `attachment_url` (string); `quiz_id` (int); `duration_seconds` (int); `order` (int); `is_preview` (bool); `points_reward` (int); `is_active` (bool). - Resposta: 201, `data` = aula criada. ### PUT /api/v1/admin/academia/lessons/{id} Atualiza aula (mesma validação e regras por tipo do POST; `title` e `type` continuam required). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` = aula atualizada. ### DELETE /api/v1/admin/academia/lessons/{id} Remove a aula (hard delete). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` {deleted: true}. ### POST /api/v1/admin/academia/courses/{courseId}/lessons/reorder Reordena as aulas do curso pela ordem do array. Auth: token. Gate: module:academia + academia.manage. - Body: `order` (array de int, required) — ids das aulas na nova ordem. - Resposta: `data` {reordered: true}. ### GET /api/v1/admin/academia/courses/{courseId}/milestones Lista marcos de avanço do curso (recompensas por % de progresso). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data[]` = linhas de course_milestones ordenadas por trigger_value. ### POST /api/v1/admin/academia/courses/{courseId}/milestones Cria marco de progresso (trigger_type fixo "progress"; concedido quando o aluno cruza o %). Auth: token. Gate: module:academia + academia.manage. - Body: `title` (string, required); `trigger_value` (int, required, 1-100, % de progresso); `points_reward` (int); `xp_reward` (int); `achievement_id` (int, deve existir na empresa); `order` (int). - Resposta: 201, `data` = marco criado. ### PUT /api/v1/admin/academia/courses/{courseId}/milestones/{id} Atualiza marco (mesma validação do POST; `title` e `trigger_value` required). Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` = marco atualizado. ### DELETE /api/v1/admin/academia/courses/{courseId}/milestones/{id} Remove marco do curso. Auth: token. Gate: module:academia + academia.manage. - Resposta: `data` {deleted: true}. ## Quizzes Quizzes gamificados: jogar individual (tentativas, XP), duelos PvP 1x1, tabuleiro multiplayer e administração (quizzes, perguntas, categorias, IA, mapas). Rotas com gate `module:quizzes` retornam 404 se o módulo estiver desligado na empresa. ### GET /api/v1/quizzes Lista quizzes individuais jogáveis pelo participante (respeita visibilidade/nível). Auth: token. Gate: module:quizzes. - Resposta: `data[]` com id, title, description, category, total_questions, available_questions, time_limit (seg), reward_points, experience_per_question, pass_percentage, minimum_level, best_score, attempts_done, attempts_remaining, attempts_limit, period_label, can_play, reason. ### GET /api/v1/quizzes/{id} Pré-visualização do quiz antes de iniciar. Auth: token. Gate: module:quizzes (403 se fora do perfil). - Resposta: `data.quiz` (id, title, type, category, total_questions, time_limit, pass_percentage, reward_points, bonus_points, attempts_limit/period), `data.stats` (avg_score, my_best, total_attempts, participants, my_attempts, max_points), `data.me` (can_play, reason, in_progress, in_progress_attempt_id, attempts_remaining). ### POST /api/v1/quizzes/{id}/start Inicia OU retoma tentativa; devolve perguntas sem gabarito. Auth: token. Gate: module:quizzes. 422 se não pode jogar / sem perguntas. - Resposta: `data` com attempt_id, resumed (bool), quiz {id, title, time_limit, total_questions, remaining_seconds}, questions[] {id, question, type, points, image_path, options[] {id, text, image_path}}, saved_answers. 201 se nova, 200 se retomada. ### POST /api/v1/quiz-attempts/{id}/progress Salva respostas parciais da tentativa em andamento (para retomar). Auth: token. Gate: module:quizzes, owner da tentativa. - Body: `answers` (array, required) — respostas parciais em formato livre. - Resposta: `data.saved` (bool). ### POST /api/v1/quiz-attempts/{id}/abandon Marca a tentativa em andamento como abandonada (desistir). Auth: token. Gate: module:quizzes, owner da tentativa. - Resposta: `data.message`. ### POST /api/v1/quiz-attempts/{id}/finish Corrige, pontua, credita XP (com reciclagem e expiração) e devolve resultado. Auth: token. Gate: module:quizzes, owner. 422 se já finalizada. - Body: `answers` (array, required) — itens {`question_id` (int, required), `selected_option_id` (int), `time_spent` (int)}; `time_spent` (int) — ignorado, tempo é ancorado no servidor. - Resposta: `data` com attempt_id, score (0-100), correct_answers, total_questions, passed, points_awarded, xp_awarded, balance_after, review[] {question_id, question, explanation, is_correct, your_answer, correct_answer}. ### GET /api/v1/quiz-attempts/{id} Resultado de uma tentativa concluída (revisão). Auth: token. Gate: module:quizzes, owner ou gestor. - Resposta: `data.attempt` (id, quiz_id, score, correct_answers, total_questions, status, completed_at) e `data.review[]` com explicações. ### GET /api/v1/quizzes/{id}/ranking Top 50 melhores pontuações do quiz. Auth: token. Gate: module:quizzes. - Resposta: `data[]` com rank, user, best_score, best_time, attempts. ### GET /api/v1/quizzes/{id}/history Últimas 50 tentativas concluídas do próprio usuário no quiz. Auth: token. Gate: module:quizzes. - Resposta: `data[]` com id, score, correct_answers, total_questions, time_spent, completed_at. ### GET /api/v1/quiz-duels Meus duelos agrupados em abas + estatísticas. Auth: token. Gate: module:quizzes. - Resposta: `data.duels[]` (id, status, tab [pendentes|aguardando|andamento|historico], i_am_challenger, opponent, my_score, opponent_score, i_completed, winner_id, i_won, can_accept/can_decline/can_cancel/can_play, expires_at) e `data.stats` (total, wins, losses, win_rate). ### GET /api/v1/quiz-duels/categories Categorias de quiz visíveis para o modal de criar duelo. Auth: token. Gate: module:quizzes. - Resposta: `data[]` com id, name, color, icon. ### POST /api/v1/quiz-duels Cria duelo 1x1 (gera quiz tipo 'duel' com N perguntas aleatórias; expira em 24h). Auth: token. Gate: module:quizzes. - Body: `challenged_id` (int) OU `challenged_email` (email) — obrigatório informar um; `category_id` (int) ou `categories` (int[]); `total_questions` (int, 3-20, default 5); `time_per_question` (int, in:15,20,30,60, default 15). - Resposta: `data.id` (duel_id) + message. 201. 422 se duelo pendente duplicado ou perguntas insuficientes. ### GET /api/v1/quiz-duels/{id} Detalhe/resultado do duelo. Auth: token. Gate: module:quizzes, participante do duelo. - Resposta: `data` com id, status, i_am_challenger, challenger, challenged, challenger_score, challenged_score, my_score, opponent_score, winner_id, i_won, total_questions, challenger_stats/challenged_stats {correct, total, accuracy, total_time, avg_time}, question_results[] (quando completed). ### POST /api/v1/quiz-duels/{id}/accept Desafiado aceita o duelo (notifica o desafiante). Auth: token. Gate: module:quizzes, desafiado. 422 se expirou/não pendente. - Resposta: `data.message`. ### POST /api/v1/quiz-duels/{id}/decline Desafiado recusa (status vira cancelled). Auth: token. Gate: module:quizzes, desafiado. - Resposta: `data.message`. ### POST /api/v1/quiz-duels/{id}/cancel Desafiante cancela duelo ainda pendente. Auth: token. Gate: module:quizzes, desafiante. - Resposta: `data.message`. ### GET /api/v1/quiz-duels/{id}/play Perguntas do duelo (sem gabarito) para o jogador atual. Auth: token. Gate: module:quizzes, participante. 422 se já jogou ou não aceitou. - Resposta: `data` com duel_id, time_per_question, questions[] {id, question, type, options[] {id, text}}, me, opponent. ### POST /api/v1/quiz-duels/{id}/submit Registra a pontuação do jogador; define vencedor quando ambos jogam (maior score; empate decide pelo menor tempo). Auth: token. Gate: module:quizzes, participante. 422 se já jogou. - Body: `answers` (array, required) — itens {`question_id` (int, required), `selected_option_id` (int)}; `time_spent` (int, seg). - Resposta: `data` com score, correct_answers, total_questions, both_completed, i_won (null até ambos jogarem), message. ### GET /api/v1/board/maps Mapas de tabuleiro ativos (para o form de criação de partida). Auth: token. - Resposta: `data[]` com id, name, description, total_cells, start_x, start_y, steps, special_cells, is_active. ### GET /api/v1/board/games Partidas visíveis (públicas, minhas, convites) com filtros, paginação e estatísticas. Auth: token. - Query: `status` (string) — waiting_players|in_progress|finished|cancelled; `search` (string); `categories` (int[]); `per_page` (int, max 100, default 20). Sem filtros, mostra só waiting_players/in_progress. - Resposta: `data[]` (id, name, status, visibility, creator, is_creator, is_player, players_count, max_players, time_per_question, map, categories, players[], winner_id), `meta` (current_page, per_page, total, last_page), `stats` (waiting, in_progress, finished, victories). ### POST /api/v1/board/games Cria a partida; criador entra como 1º jogador. Auth: token. - Body: `board_map_id` (int, required); `max_players` (int, 2-6, required); `time_per_question` (int, 10-180, required); `visibility` (in:public,private, required); `categories` (int[]); `dice_seconds` (int, 5-60, default 15); `game_name` (string); `invited_users` (array de ids ou emails, para private). - Resposta: `data.id` + message. 201. ### GET /api/v1/board/games/{id} Detalhe da partida (mapa, jogadores, estado). Auth: token. Gate: visibilidade da partida (403). - Resposta: `data` com id, name, status, visibility, creator, is_creator, is_player, is_my_turn, max_players, time_per_question, dice_seconds, current_turn, turn_player_id, winner_id, finished_reason, categories, map, map_config, players[] {user_id, name, color, position, is_ready, is_online}, can_join, can_start, state. ### POST /api/v1/board/games/{id}/join Entra na partida (lobby). Auth: token. Gate: canJoin (403). - Resposta: `data.message`. ### POST /api/v1/board/games/{id}/ready Alterna prontidão do jogador no lobby. Auth: token. Gate: jogador da partida. - Resposta: `data.is_ready` (bool). ### POST /api/v1/board/games/{id}/start Inicia a partida. Auth: token. Gate: criador/canStart (403). - Resposta: `data.message`. ### POST /api/v1/board/games/{id}/cancel Criador cancela partida no lobby. Auth: token. Gate: criador; 422 fora do lobby. - Resposta: `data.message`. ### POST /api/v1/board/games/{id}/leave Sai da partida. Auth: token. Gate: jogador da partida. - Resposta: `data` com resultado do engine (estado pós-saída). ### POST /api/v1/board/games/{id}/roll Rola o dado na sua vez; devolve a jogada + pergunta. Auth: token. Gate: vez do jogador (validado no engine). - Resposta: `data` com resultado da jogada do engine (dado, posições, pergunta). ### POST /api/v1/board/games/{id}/answer Responde a pergunta da jogada atual. Auth: token. - Body: `question_id` (int, required); `option_id` (int) — null = timeout/sem resposta; `time_taken` (int, seg). - Resposta: `data` com resultado do engine (correta ou não, próxima vez, estado). ### POST /api/v1/board/games/{id}/pass-dice Passa a vez (timeout de dado). Auth: token. - Resposta: `data` com resultado do engine. ### GET /api/v1/board/games/{id}/pending Retoma jogada pendente do jogador da vez (após F5). Auth: token. - Resposta: `data.has_pending` (bool); se true: move_id, dice_value, old_position, new_position, time_remaining, question {id, text, category, options}. ### GET /api/v1/board/games/{id}/state Estado completo da partida para polling (mesmo shape do RTDB). Auth: token. Gate: visibilidade (403). - Resposta: `data` = objeto de estado (players, turno, posições etc.). ### GET /api/v1/board/games/{id}/ranking Ranking da partida. Auth: token. Gate: jogador ou visibilidade. - Resposta: `data[]` = ranking do engine. ### POST /api/v1/board/games/{id}/rtdb-token Token do Firebase RTDB para realtime; hoje desabilitado. Auth: token. - Resposta: `data` = {token: null, enabled: false} — cliente deve usar polling em /state. ### GET /api/v1/admin/quizzes Lista quizzes (admin/gestor) com filtros e stats. Auth: token. Gate: module:quizzes + gestor (gestor de setor vê só o próprio escopo). - Query: `archived` (bool); `category_id` (int); `visibility` (global|sector|individual); `search` (string). - Resposta: `data.quizzes[]` (id, title, category, visibility, is_active, type, reward_points, total_questions, questions_count, attempts_count, starts_at, ends_at, archived), `data.categories[]`, `data.stats` (total, active, archived, attempts). ### POST /api/v1/admin/quizzes Cria quiz individual. Auth: token. Gate: module:quizzes + gestor (setores dentro do escopo). - Body: `title` (string, required); `category_id` (int, required); `visibility` (in:global,sector,individual, required); `total_questions` (int, required); `description`, `allowed_sectors` (int[]), `individual_users` (int[]), `additional_users` (int[]), `attempts_limit` (int), `attempts_period` (day|week|month|year), `time_limit_minutes` (int), `pass_percentage` (0-100), `reward_points` (int, XP de recompensa), `bonus_points` (int, ao gabaritar), `experience_per_question` (int), `experience_duration_days` (1-365), `minimum_score_for_experience` (0-100, default 70), `minimum_level` (int), `uses_budget` (bool), `is_active` (bool), `unlisted` (bool), `starts_at`/`ends_at` (date) — todos opcionais. - Resposta: `data.id` + message. 201. ### GET /api/v1/admin/quizzes/{id} Quiz + perguntas (com opções e gabarito) + orçamento + stats. Auth: token. Gate: module:quizzes + gestor com escopo sobre o quiz. - Resposta: `data.quiz` (todas as colunas), `data.questions[]` (com options[]), `data.stats` (attempts, avg_score), `data.budget`, `data.sector_budgets`. ### PUT /api/v1/admin/quizzes/{id} Atualiza quiz individual (mesmo body do POST). Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.message`. ### DELETE /api/v1/admin/quizzes/{id} Arquiva o quiz (soft delete). Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.message`. ### POST /api/v1/admin/quizzes/{id}/restore Restaura quiz arquivado. Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.message`. ### PATCH /api/v1/admin/quizzes/{id}/toggle-active Ativa/desativa o quiz. Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.is_active` (bool) + message. ### POST /api/v1/admin/quizzes/{id}/duplicate Duplica quiz com perguntas e opções (cópia inativa). Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.id` do novo quiz + message. 201. ### GET /api/v1/admin/quizzes/{id}/export Exporta quiz + perguntas + opções como JSON (para import). Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data` com version, quiz {title, category, total_questions, pass_percentage, reward_points...}, questions[] {question, type, points, explanation, options[] {option_text, is_correct}}. ### POST /api/v1/admin/quizzes/import Importa quiz a partir do JSON exportado (cria inativo; categoria criada se não existir). Auth: token. Gate: module:quizzes + gestor. - Body: `quiz.title` (string, required); `quiz.category` (string); `questions` (array, min:1, required) — itens com `question` (required), `type` (in:multiple_choice,true_false,text, required), `options` (array min:2, required, itens com `option_text` required). - Resposta: `data.id` + message. 201. ### POST /api/v1/admin/quizzes/{id}/budget/allocate Aloca orçamento de setor ao quiz (ativa uses_budget). Auth: token. Gate: module:quizzes + gestor com escopo. - Body: `sector_budget_id` (int, required); `amount` (numeric, min:0.01, required); `description` (string). - Resposta: `data.message`. 201. ### POST /api/v1/admin/quizzes/{id}/budget/refund Reembolsa o orçamento não utilizado. Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.refunded` (número) + message. ### POST /api/v1/admin/quizzes/{id}/budget/remove Remove o orçamento do quiz (reembolsa e desativa uses_budget). Auth: token. Gate: module:quizzes + gestor com escopo. - Resposta: `data.refunded` + message. ### GET /api/v1/admin/quiz-reports Relatório geral de quizzes. Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.overall` (total_quizzes, total_attempts, unique_players, avg_score, pass_rate), `data.top_quizzes[]`, `data.by_category[]`, `data.recent_attempts[]` (user, quiz, score, completed_at). ### POST /api/v1/admin/quizzes/{quizId}/questions Cria pergunta + opções. Auth: token. Gate: module:quizzes + gestor. 422 se nenhuma opção correta. - Body: `question` (string, required); `type` (in:multiple_choice,true_false, required); `options` (array 2-6, required) — itens {`option_text` (string, required), `is_correct` (bool)}; `points` (int), `time_limit` (int), `explanation` (string), `image_path` (string), `is_active` (bool). - Resposta: `data.id` + message. 201. ### PUT /api/v1/admin/quiz-questions/{id} Edita pergunta e SUBSTITUI todas as opções (mesmo body do POST). Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.message`. ### DELETE /api/v1/admin/quiz-questions/{id} Remove pergunta e suas opções. Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.message`. ### POST /api/v1/admin/quizzes/{quizId}/questions/reorder Reordena as perguntas do quiz. Auth: token. Gate: module:quizzes + gestor. - Body: `ids` (int[], required) — ids das perguntas na nova ordem. - Resposta: `data.message`. ### GET /api/v1/admin/quiz-categories Lista categorias (com contagem de quizzes). Auth: token. Gate: module:quizzes + gestor. - Resposta: `data[]` com todas as colunas da categoria + quizzes_count. ### POST /api/v1/admin/quiz-categories Cria categoria. Auth: token. Gate: module:quizzes + gestor. - Body: `name` (string, required); `description` (string); `color` (string, hex, max:7); `icon` (string); `is_active`/`is_hidden`/`is_community` (bool); `order` (int). - Resposta: `data.id` + message. 201. ### PUT /api/v1/admin/quiz-categories/{id} Atualiza categoria (mesmo body do POST). Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.message`. ### DELETE /api/v1/admin/quiz-categories/{id} Remove categoria (soft delete); 422 se houver quizzes usando. Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.message`. ### PATCH /api/v1/admin/quiz-categories/{id}/toggle-status Ativa/desativa a categoria. Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.is_active` (bool) + message. ### GET /api/v1/admin/quiz-ai/status Informa se o provedor de IA está configurado. Auth: token. Gate: module:quizzes + gestor. - Resposta: `data.configured` (bool). ### POST /api/v1/admin/quiz-ai/generate Gera perguntas por IA (preview, não salva). Auth: token. Gate: module:quizzes + gestor. - Body: `topic` (string, required); `difficulty` (in:facil,media,dificil, default media); `count` (int, 1-20, default 5). - Resposta: `data.questions[]` geradas. ### POST /api/v1/admin/quiz-ai/save Cria um quiz (inativo) a partir das perguntas geradas/editadas. Auth: token. Gate: module:quizzes + gestor. - Body: `title` (string, required); `category_id` (int, required); `questions` (array min:1, required) — itens {`question` (required), `options` (array min:2, required, itens com `option_text` required + `is_correct`), `type`, `explanation`}. - Resposta: `data.id` + message. 201. ### GET /api/v1/admin/board-maps Lista mapas de tabuleiro (templates). Auth: token. Gate: gestor ou permissão board.manage. - Resposta: `data[]` com id, name, description, start_x, start_y, steps, special_cells, total_cells, is_active. ### POST /api/v1/admin/board-maps Cria mapa; total_cells é derivado de steps (não aceito do cliente). Auth: token. Gate: gestor ou board.manage. - Body: `name` (string, required); `steps` (array min:1, required) — itens {`dir` (in:right,left,up,down), `count` (int, min:1)}; `description` (string); `start_x`/`start_y` (int); `special_cells` (array); `is_active` (bool). - Resposta: `data` = mapa criado (payload completo). 201. ### PUT /api/v1/admin/board-maps/{id} Atualiza mapa (campos opcionais/parciais; total_cells recalculado). Auth: token. Gate: gestor ou board.manage. - Resposta: `data` = mapa atualizado. ### DELETE /api/v1/admin/board-maps/{id} Exclui mapa; 422 se houver partidas usando. Auth: token. Gate: gestor ou board.manage. - Resposta: `data.message`. ### PATCH /api/v1/admin/board-maps/{id}/toggle-active Ativa/desativa o mapa. Auth: token. Gate: gestor ou board.manage. - Resposta: `data.is_active` (bool). ## Loja (Store) Catálogo de produtos resgatáveis com pontos e pedidos. ### GET /api/v1/store-products Lista produtos da loja (paginado). Auth: token. Gate: module:store + permissão `store.view`. - Query: `filter[name]` (string) — busca parcial; `filter[active]`, `filter[category_id]`; `sort` (id|name|price_points|created_at, prefixo `-` p/ desc); `per_page` (int, max 100, default 20). - Resposta: paginação Laravel com `data[]`: id, name, description, price_points, category_id, stock, active, requires_approval, image_path, created_at. ### GET /api/v1/store-products/{id} Detalhe de um produto. Auth: token. Gate: module:store + permissão `store.view`. - Resposta: `data` com os mesmos campos do item da listagem. ### POST /api/v1/store-products/{id}/buy Compra o produto: debita pontos, baixa estoque e cria pedido (status `pending` se requires_approval, senão `completed`). Auth: token. - Body: `quantity` (int, opcional, 1-999, default 1). - Erros 422: produto inativo, estoque ou saldo insuficiente. - Resposta: `data`: order_id, status, balance_after. ## Pedidos (Orders) Gestão de pedidos da loja. ### GET /api/v1/orders Lista pedidos (paginado). Auth: token. Gate: module:store + permissão `orders.index`. - Query: `filter[status]`, `filter[user_id]`, `filter[product_id]`; `sort` (id|created_at|total_points); `per_page` (int, max 100, default 20). - Resposta: paginação com `data[]`: id, order_number, user_id, product_id, quantity, points_spent, total_points, status, created_at. ### PATCH /api/v1/orders/{id}/status Altera status do pedido; rejeitar/cancelar estorna pontos e devolve estoque. Auth: token. Gate: gestor ou permissão `orders.manage`. - Body: `status` (string, required) — pending|approved|rejected|delivered|completed|cancelled. - Resposta: `data`: id, status. ## Enquetes (Polls) — participante Enquetes com voto único/múltiplo, recompensa em pontos e comentários. ### GET /api/v1/polls Lista enquetes ativas visíveis ao participante. Auth: token. - Resposta: `data[]`: id, title, description, is_featured, reward_points, min_level, allow_multiple_votes, hide_results_until_end, allow_comments, starts_at, ends_at, has_voted, ended, total_votes, options_count, can_vote. ### GET /api/v1/polls/{id}/detail Detalhe da enquete com opções (votos/percent ocultos se hide_results_until_end e ainda ativa) e meus votos. Auth: token. - Resposta: `data`: poll {id, title, ..., ended}, options[] {id, text, votes|null, percent|null}, my_option_ids[], has_voted, total_votes, results_visible, can_vote, vote_reason, comments[]. ### GET /api/v1/polls/{id}/results Resultados da enquete (403 se ocultos até o fim). Auth: token. - Resposta: `data`: total_votes, voters, options[] {id, text, votes, percent}. ### POST /api/v1/polls/{id}/vote Registra/atualiza voto (diff em múltipla escolha); credita recompensa uma vez por enquete. Auth: token. - Body: `option_ids` (array de int, múltipla) OU `poll_option_id` (int, voto único, compat); `comment` (string, opcional, max 1000). - Resposta 201: `data`: message, credited, balance_after, my_option_ids[]. ### POST /api/v1/polls/{id}/comment Comenta na enquete (se allow_comments). Auth: token. - Body: `comment` (string, required, max 1000). - Resposta 201: `data.message`. ## Enquetes — administração CRUD de enquetes, estado, orçamento e comentários. Todas exigem Auth: token, Gate: module:polls + gestor de enquetes (admin ou gestor de setor no escopo). ### GET /api/v1/admin/polls Lista enquetes (admin vê todas; gestor vê as próprias + as dos seus setores). Auth: token. Gate: module:polls + gestor. - Resposta: `data[]`: id, title, visibility, is_active, is_featured, reward_points, starts_at, ends_at, ended, total_votes, options_count. ### POST /api/v1/admin/polls Cria enquete + opções. Auth: token. Gate: module:polls + gestor (setores-alvo limitados ao escopo do gestor). - Body: `title` (string, required, max 255); `description` (string, max 2000); `options` (array de string, required, 2-10); `reward_points`, `min_level` (int); `visibility` (required, global|sector|individual); `allowed_sectors`, `individual_users`, `additional_users` (array de int); `is_active`, `is_featured`, `allow_multiple_votes`, `allow_comments`, `hide_results_until_end`, `uses_budget` (boolean); `starts_at`, `ends_at` (date). - Resposta 201: `data`: id, message. ### GET /api/v1/admin/polls/{id} Detalhe admin: enquete, opções com votos, stats, comentários e orçamento. Auth: token. Gate: module:polls + gestão da enquete. - Resposta: `data`: poll (linha completa), options[] {id, text, order, votes_count}, stats {total_votes, voters}, comments[], budget, sector_budgets. ### PUT /api/v1/admin/polls/{id} Edita enquete; opções com `id` são atualizadas, sem `id` criadas, omitidas sem votos são removidas. Auth: token. Gate: module:polls + gestão. - Body: igual ao POST, mas `options` é array de objetos {id? (int), text (string, required, max 255)}. - Resposta: `data.message`. ### DELETE /api/v1/admin/polls/{id} Exclui a enquete e dados relacionados; reembolsa verba alocada. Auth: token. Gate: module:polls + gestão. - Resposta: `data.message`. ### PATCH /api/v1/admin/polls/{id}/toggle-active Ativa/desativa a enquete. Auth: token. Gate: module:polls + gestão. - Resposta: `data`: is_active, message. ### PATCH /api/v1/admin/polls/{id}/toggle-featured Destaca/remove destaque. Auth: token. Gate: module:polls + gestão. - Resposta: `data`: is_featured, message. ### PATCH /api/v1/admin/polls/{id}/finalize Encerra a enquete (is_active=0, ends_at=agora) e reembolsa verba não usada. Auth: token. Gate: module:polls + gestão. - Resposta: `data`: message, refunded. ### POST /api/v1/admin/polls/{id}/comment Comentário do admin na enquete. Auth: token. Gate: module:polls + gestão. - Body: `comment` (string, required, max 1000). - Resposta 201: `data.message`. ### POST /api/v1/admin/polls/{id}/budget/allocate Aloca verba de um orçamento setorial à enquete (marca uses_budget=1). Auth: token. Gate: module:polls + gestão. - Body: `sector_budget_id` (int, required); `amount` (numeric, required, min 0.01); `description` (string, opcional, max 1000). - Resposta 201: `data.message`. ### POST /api/v1/admin/polls/{id}/budget/refund Reembolsa a verba não utilizada. Auth: token. Gate: module:polls + gestão. - Resposta: `data`: message, refunded. ### POST /api/v1/admin/polls/{id}/budget/remove Remove o orçamento da enquete (uses_budget=0) e reembolsa. Auth: token. Gate: module:polls + gestão. - Resposta: `data`: message, refunded. ## Fórum Fórum interno: categorias, tópicos (com visibilidade global/setor/individual) e respostas. ### GET /api/v1/forum/categories Categorias ativas com contagens e último post, tópicos recentes e estatísticas. Auth: token. - Resposta: `data`: categories[] {id, name, slug, description, icon, color, parent_id, topics_count, last_post}, recent_topics[] (até 8), stats {topics, posts, categories}. ### GET /api/v1/forum/categories/{id}/topics Tópicos visíveis da categoria (até 100, fixados primeiro). Auth: token. - Resposta: `data`: category {id, name, description, icon, color}, topics[] {id, title, posts_count, views_count, is_pinned, is_locked, last_activity_at, created_at, author_id, author_name}. ### GET /api/v1/forum/search Busca tópicos por texto no título/conteúdo, geral ou por categoria (até 50). Auth: token. - Query: `q` (string) — termo; `category_id` (int, opcional). - Resposta: `data`: query, category_id, topics[] (mesmos campos da listagem + content, author, category, category_color). ### GET /api/v1/forum/topics/{id} Tópico + respostas; incrementa views. 403 se fora da visibilidade. Auth: token. - Resposta: `data`: topic {id, title, content (HTML), is_locked, is_pinned, posts_count, views_count, author_name, category_name, can_edit, can_delete, can_moderate, can_reply, ...}, posts[] {id, content, author_name, quoted_author, quoted_content, can_edit, can_delete, ...}. ### POST /api/v1/forum/topics Cria tópico (HTML sanitizado). Auth: token. - Body: `category_id` (int, required); `title` (string, required, max 255); `content` (string, required, max 200000); `allow_replies` (boolean); `visibility` (global|sector|individual); `allowed_sectors`, `individual_users`, `additional_users` (array). - Resposta 201: `data`: id, message. ### PUT /api/v1/forum/topics/{id} Edita tópico. Auth: token. Gate: autor (até 30min) ou gestor/moderador. - Body: mesmos campos do POST (title e content required; category_id opcional). - Resposta: `data.message`. ### DELETE /api/v1/forum/topics/{id} Exclui tópico (soft delete) e apaga respostas. Auth: token. Gate: autor ou gestor/moderador. - Resposta: `data.message`. ### POST /api/v1/forum/topics/{id}/reply Responde ao tópico (422 se bloqueado ou sem allow_replies); notifica o autor. Auth: token. - Body: `content` (string, required, max 200000); `quoted_post_id` (int, opcional) — cita outra resposta. - Resposta 201: `data`: id, message. ### PUT /api/v1/forum/posts/{id} Edita resposta. Auth: token. Gate: autor (até 30min) ou gestor/moderador. - Body: `content` (string, required, max 200000). - Resposta: `data.message`. ### DELETE /api/v1/forum/posts/{id} Exclui resposta e recalcula posts_count do tópico. Auth: token. Gate: autor ou gestor/moderador. - Resposta: `data.message`. ### POST /api/v1/forum/topics/{id}/pin Alterna fixado/desafixado. Auth: token. Gate: gestor/moderador. - Resposta: `data`: is_pinned, message. ### POST /api/v1/forum/topics/{id}/lock Alterna bloqueio de respostas. Auth: token. Gate: gestor/moderador. - Resposta: `data`: is_locked, message. ### POST /api/v1/forum/images Upload de imagem para o editor do fórum. Auth: token. - Body (multipart): `image` (file, required, jpeg/jpg/png/gif/webp, max 5MB). - Resposta: `data.url` (URL pública). ## Calendário Eventos do portal com visibilidade (public/department/role_based/private_users), convites e RSVP. ### GET /api/v1/calendar Eventos visíveis do mês + aniversariantes + estatísticas. Auth: token. - Query: `year` (int, 2020-2035, default atual); `month` (int, 1-12, default atual). - Resposta: `data`: year, month, events[] {id, title, start_date, end_date, is_all_day, event_type, type_label, color, location, requires_confirmation, attendees_count, my_status, can_edit, ...}, birthdays[], can_create, stats, event_types[]. ### POST /api/v1/calendar/events Cria evento; convida participantes (com notificação) se requires_confirmation. Auth: token. Gate: gestor ou permissão `calendar.create`. - Body: `title` (string, required, max 255); `description` (string); `start_date` (date, required); `end_date` (date, required, >= start_date); `event_type` (required, meeting|holiday|training|celebration|deadline|other); `location` (string, max 255); `is_all_day` (boolean); `visibility_type` (required, public|department|role_based|private|private_users); `target_departments`, `target_roles`, `private_users`, `attendees` (array); `requires_confirmation` (boolean). - Resposta 201: `data`: id, message. ### GET /api/v1/calendar/events/{id} Detalhe do evento + lista de convidados. Auth: token. Gate: visibilidade do evento. - Resposta: `data`: campos do evento + attendees[] {user_id, status, response_at, nome, avatar_path}. ### PUT /api/v1/calendar/events/{id} Edita evento e recria a lista de convidados. Auth: token. Gate: admin ou criador. - Body: mesmos campos do POST. - Resposta: `data.message`. ### DELETE /api/v1/calendar/events/{id} Exclui evento e convidados. Auth: token. Gate: admin ou criador. - Resposta: `data.message`. ### POST /api/v1/calendar/events/{id}/confirm RSVP do convidado (422 se evento não pede confirmação; 403 se não convidado); notifica o criador. Auth: token. - Body: `status` (required, accepted|declined|tentative). - Resposta: `data`: status, message. ## Check-in Diário Presença diária do colaborador: marca 1x por dia, acumula sequência (streak) e ganha recompensa nos MARCOS configurados pela empresa. Módulo opt-in — todas as rotas sob `module:checkin` (404 se a empresa desligou). A tela do participante vive dentro do Calendário (portal: `/app/calendario` — `/app/checkin` redireciona pra lá; app mobile: tela Calendário em `/checkin`). O dia vira à meia-noite LOCAL (`app.business_tz`, America/Sao_Paulo), não em UTC. Ciclo `monthly` (reseta dia 1, chave `YYYY-MM`) ou `weekly` (reseta segunda, chave ISO `YYYY-Www`); cada marco é pago no máximo 1x por ciclo (idempotente). ### GET /api/v1/checkin/current Estado do check-in do usuário: config, marcos, sequência e presença do mês. Auth: token. Gate: module:checkin. - Query: `year` (int, 2020-2035, default: ano atual); `month` (int, 1-12, default: mês atual). - Resposta: `data`: configured (false quando o admin ainda não configurou — nesse caso vem config null, milestones/present vazios, streak/total 0 e SEM os campos stack/awarded/cycle_key), config {cycle: weekly|monthly, break_on_miss}, milestones[] {id, threshold, mode: streak|total, reward_type, reward_value, reward_ref_id, reward_label (nome da insígnia/produto/roleta), position}, awarded[] (ids de marcos já pagos no ciclo atual), stack[] (7 dias fixos terminando hoje: {date, status: done|today|miss}), streak, total, checked_today, cycle, cycle_key, calendar {year, month, present[] (dias `YYYY-MM-DD` marcados no mês), since (data em que a empresa configurou o check-in — dias anteriores não são falta)}. ### POST /api/v1/checkin Marca a presença de HOJE (idempotente: repetir no mesmo dia não duplica nem repaga) e credita os marcos recém-atingidos. Auth: token. Gate: module:checkin. 422 se a empresa ainda não configurou o check-in. - Body: nenhum. - Resposta: `data`: streak, total, checked_today, cycle_key, awarded[] (ids dos marcos pagos AGORA), present[] (dias marcados no ciclo atual). Recompensa por tipo: points (crédito na carteira, tipo `checkin_reward`), xp, badge (conquista), spins (giros bônus na roleta escolhida); product/custom não creditam nada automaticamente (entrega manual) — em todos os casos vai notificação in-app/push. ### GET /api/v1/admin/checkin Config da empresa (ou defaults) + marcos. Auth: token. Gate: module:checkin + admin ou `checkin.manage`/`checkin.admin`. - Resposta: `data`: configured (bool), cycle (default "monthly"), break_on_miss (default true), milestones[] {id, threshold, mode, reward_type, reward_value, reward_ref_id, position}. ### PUT /api/v1/admin/checkin Salva config + marcos de uma vez (upsert por `id`; marco ausente da lista é REMOVIDO — mande sempre a lista completa e preserve os ids para não repagar recompensa no mesmo ciclo). Auth: token. Gate: module:checkin + admin ou `checkin.manage`/`checkin.admin`. - Body: `cycle` (required, weekly|monthly); `break_on_miss` (boolean, required — false faz a sequência valer o total do ciclo); `milestones` (array, máx 50) com `id` (int, opcional — id existente que se quer preservar), `threshold` (int, required, 1-366), `mode` (required, streak=dias seguidos | total=acumulado no ciclo), `reward_type` (required, points|xp|badge|product|spins|custom), `reward_value` (string, max 255 — pontos/xp/qtd de giros/descrição), `reward_ref_id` (int — obrigatório em badge/product/spins; precisa existir na empresa ativa: achievements/store_products/roulette_wheels). - Resposta: `data`: ok, config_id. 422 se faltar a permissão do tipo de recompensa, se o item não for da empresa, ou se houver prêmio em pontos sem a permissão "criar atividades sem orçamento". ## Avisos Quadro de avisos VIGENTES (mural fixado, diferente do feed que rola e do sino que morre depois de lido) + aniversariantes do dia (linhas sintéticas, nunca persistidas). SEM gate de módulo: sempre disponível; o ícone 📣 das topbars é auto-gated por conteúdo (some quando não há nada). Vigente = `active` e `starts_at` nulo ou já passado e `ends_at` nulo ou ainda no futuro (fuso `app.business_tz`). Spec: `docs/avisos.md`. ### GET /api/v1/avisos/current Avisos vigentes visíveis a mim (máx 50, mais recentes primeiro) + aniversariantes de hoje. Auth: token. - Resposta: `data`: avisos[] {id, title, content, kind: aviso|atualizacao, starts_at, ends_at, created_at}, birthdays[] {user_id, nome, avatar_path}, count (avisos + aniversariantes — é o número do badge). Visibilidade aplicada no servidor: individual (`allowed_users`), sector (interseção com meus setores; `extra_users` passa por cima) e global — em todos, `allowed_roles`/`target_leaders` ainda restringem por cargo/liderança. ### GET /api/v1/admin/avisos Lista os avisos do escopo do gestor (máx 200; vigentes e não). Admin/`avisos.admin` vê todos; gestor comum vê os que criou + os de setores que lidera. Auth: token. Gate: `avisos.manage` ou `avisos.admin`. - Resposta: `data[]` {id, title, content, kind, starts_at, ends_at, visibility, allowed_sectors[], allowed_users[], allowed_roles[], target_leaders, extra_users[], active, notify (true = sino/push já disparado), created_by, created_at}. ### POST /api/v1/admin/avisos Cria o aviso e, se `notify`, dispara sino + push na hora para o público (deep-link `/avisos`; respeita a preferência "announcements" de cada um). Aviso agendado (`starts_at` no futuro) ou já expirado NÃO dispara. Auth: token. Gate: `avisos.manage` ou `avisos.admin`. - Body: `title` (string, required, max 150); `content` (string, required, max 5000); `kind` (aviso|atualizacao, default "aviso"); `starts_at` (date, opcional — nulo = já vale); `ends_at` (date, opcional, depois de starts_at — nulo = sem prazo); `visibility` (required, global|sector|individual); `allowed_sectors` (array de int, obrigatório em sector); `allowed_users` (array de int, obrigatório em individual); `allowed_roles` (array de int); `target_leaders` (boolean); `extra_users` (array de int — pessoas além do setor); `active` (boolean, default true); `notify` (boolean, default true). - Resposta 201: `data.id`. 403 se gestor comum tentar `global`, setor que não lidera ou pessoa fora dos seus setores; 422 se sector sem setor / individual sem pessoa. ### GET /api/v1/admin/avisos/{id} Aviso completo para o formulário. Auth: token. Gate: `avisos.manage`/`avisos.admin` + escopo (criador, admin, ou todos os setores do aviso sob sua gestão). 404 se não existir. - Resposta: `data` (mesmos campos da lista). ### PUT /api/v1/admin/avisos/{id} Atualiza o aviso. NÃO redispara notificação (o campo `notify` é aceito mas ignorado na edição). Auth: token. Gate: mesmo do GET por id. - Body: mesmos campos do POST. - Resposta: `data.ok`. ### DELETE /api/v1/admin/avisos/{id} Remove o aviso (hard delete). Auth: token. Gate: mesmo do GET por id. - Resposta: `data.ok`. ## Feed social (Posts) Feed de posts com comentários, curtidas, menções e seguir posts. ### GET /api/v1/feed Posts recentes (até 50) com autor, contadores e flags do usuário. Auth: token. - Query: `filter=following` (opcional) — apenas posts de quem eu sigo + meus. - Resposta: `data[]`: id, content, type, created_at, user_id, mentions, author_name, author_avatar, likes_count, comments_count, liked, notified. ### POST /api/v1/posts Cria post no feed ou no mural de outro usuário; notifica seguidores/dono do mural/mencionados. Auth: token. Gate: permissão `posts.create` (`posts.create_others` p/ mural alheio). - Body: `content` (string, required, max 5000); `mentions` (array de int, opcional); `profile_user_id` (int, opcional) — mural alvo. - Resposta 201: `data` com o post criado (mesmo shape do feed). ### DELETE /api/v1/posts/{id} Remove post, comentários e curtidas. Auth: token. Gate: autor ou master. - Resposta: `data.message`. ### GET /api/v1/posts/{id}/comments Comentários do post (não deletados, ordem cronológica). Auth: token. - Resposta: `data[]`: id, content, created_at, user_id, mentions, author_name, author_avatar. ### POST /api/v1/posts/{id}/comments Comenta no post. Auth: token. - Body: `content` (string, required, max 2000); `mentions` (array de int, opcional). - Resposta 201: `data`: id, message. ### POST /api/v1/posts/{id}/like Alterna curtir/descurtir. Auth: token. - Resposta: `data`: liked, likes_count. ### POST /api/v1/posts/{id}/follow Alterna seguir/parar de seguir o post (notificações). Auth: token. - Resposta: `data.following` (bool). ## Missões (Quests) Catálogo de missões gamificadas com ciclo de vida do participante (iniciar → progresso → concluir), recompensas de XP/pontos/talentos e fila de aprovação. Ações de escrita aceitam header `Idempotency-Key` (start/progress/complete) e `user_id` no body para agir em nome de outro usuário (requer quests.manage ou escopo de token quests.write). ### GET /api/v1/quests Lista missões globais visíveis ao usuário com seu estado de participação. Auth: token. Gate: module:quests. - Query: `type` (string) — daily|weekly|main|special. `difficulty` (string) — easy|medium|hard|expert. `search` (string) — busca no título. - Resposta: `data[]` {id, title, description, type, difficulty, xp_reward, points_reward, talent_rewards[], requires_approval, is_repeatable, is_active, duration, my_status, my_progress, locked, locked_reason, can_start}; `meta.is_manager`. ### GET /api/v1/quests/{id} Detalhe da missão + estado do usuário. Auth: token. Gate: module:quests (403 se não visível ao perfil). - Resposta: campos da listagem + visibility_type, target_sectors[], individual_users[], unlock_rules {min_level, required_quest_ids[], metrics[]}, external_ref, can_manage. ### GET /api/v1/quests/meta Métricas e operadores disponíveis para o construtor de gatilho de liberação (unlock_rules). Auth: token. Gate: module:quests + quests.manage. - Resposta: `data` {metrics: [{key, label}], operators: [">=", ">", "<=", "<", "=="]}. ### POST /api/v1/quests Cria missão (upsert por `external_ref` se já existir). Auth: token. Gate: module:quests + quests.manage + escopo de token quests.write. - Body: `title` (string, required) — máx 255. `description` (string) — máx 5000. `type` (string) — daily|weekly|main|special (default daily). `difficulty` (string) — easy|medium|hard|expert (default easy). `xp_reward`/`points_reward` (int, 0-100000). `talent_rewards` (array) — [{competency_id, progress}]. `requires_approval`/`is_repeatable`/`is_active` (bool). `duration` (int). `visibility_type` (string) — global|sector|individual. `target_sectors[]`/`individual_users[]` (int[]). `unlock_rules` (object) — {min_level (int), required_quest_ids (int[]), metrics: [{metric, operator, value}]}. `external_ref` (string, máx 191) — idempotência de integração. - Resposta: 201, `data` = missão completa (shape do show). ### PUT /api/v1/quests/{id} Atualiza missão (mesmos campos do POST, todos opcionais). Auth: token. Gate: module:quests + quests.manage (canManage) + quests.write. - Resposta: `data` = missão completa atualizada. ### DELETE /api/v1/quests/{id} Exclui missão. Auth: token. Gate: module:quests + quests.manage (canManage) + quests.write. - Resposta: `data.message`. ### POST /api/v1/quests/{id}/start Inicia participação na missão (valida elegibilidade e gatilho de liberação; 422 se bloqueada). Auth: token. Gate: module:quests + quests.write. - Body: `user_id` (int) — opcional, agir por outro usuário (requer quests.manage/master/escopo quests.write). Header `Idempotency-Key` opcional. - Resposta: `data` {user_quest_id, status: "active", progress: 0}. ### POST /api/v1/quests/{id}/progress Atualiza progresso (0-100); ao chegar em 100 conclui automaticamente. Auth: token. Gate: module:quests + quests.write. - Body: `progress` (int, required) — 0 a 100. `evidence` (string) — máx 5000. `user_id` (int) — opcional. Header `Idempotency-Key` opcional. - Resposta: `data` {user_quest_id, status, progress} (ou shape de conclusão se atingir 100). ### POST /api/v1/quests/{id}/complete Conclui a missão; se `requires_approval`, cria aprovação pendente em vez de creditar. Auth: token. Gate: module:quests + quests.write. - Body: `evidence` (string, máx 5000). `notes` (string, máx 2000). `user_id` (int) — opcional. Header `Idempotency-Key` opcional. - Resposta: `data` {user_quest_id, status: "completed", xp_earned, points_earned, talents[]} ou {user_quest_id, status: "pending_approval", approval_id}. ### POST /api/v1/quests/{id}/abandon Abandona a missão ativa. Auth: token. Gate: module:quests + quests.write. - Body: `user_id` (int) — opcional. - Resposta: `data` {user_quest_id, status: "abandoned"}. ### GET /api/v1/quest-approvals Fila de aprovação de missões que exigem revisão. Auth: token. Gate: module:quests + (master | quests.approve | quests.manage). - Query: `status` (string) — pending (default), approved, rejected ou all. Limite fixo de 200. - Resposta: `data[]` {id, status, evidence, notes, manager_notes, submitted_at, reviewed_at, quest {id, title, xp_reward, points_reward}, user {id, name}}. ### POST /api/v1/quest-approvals/{id}/approve Aprova a conclusão e credita recompensas. Auth: token. Gate: module:quests + quests.write + (master | quests.approve | quests.manage). - Body: `notes` (string, máx 2000) — nota do gestor. - Resposta: `data` {status: "approved", xp_earned, points_earned, talents[]}. ### POST /api/v1/quest-approvals/{id}/reject Rejeita a conclusão (user_quest vira "failed"). Auth: token. Gate: module:quests + quests.write + (master | quests.approve | quests.manage). - Body: `notes` (string, máx 2000). - Resposta: `data` {status: "rejected"}. ## PDI (Plano de Desenvolvimento Individual) Gestor monta plano de metas (competências) + missões para um funcionário; o funcionário executa e envia para revisão final. Missões do plano são quests com `pdi_id`. Gestão exige master, quests.manage ou pdi.manage (ou ser o criador do PDI). Todas as rotas: Gate module:pdi. ### GET /api/v1/pdis Lista meus PDIs (como funcionário) + os que gerencio (se gestor). Auth: token. Gate: module:pdi. - Resposta: `data[]` {id, title, status, completion, end_date, user {id, name}, is_owner, can_manage}; `meta.is_manager`. ### POST /api/v1/pdis Cria PDI em rascunho para um funcionário. Auth: token. Gate: module:pdi + gestor (master|quests.manage|pdi.manage). - Body: `user_id` (int, required) — funcionário alvo. `title` (string, required, máx 255). `description` (string, máx 2000). `start_date`/`end_date` (date). - Resposta: 201, `data` {id}. ### GET /api/v1/pdis/{id} Detalhe: metas, missões (com status do dono) e revisão. Auth: token. Gate: module:pdi + dono ou gestor. - Resposta: `data` {id, title, description, status, completion, start_date, end_date, user, is_owner, can_manage, activation_errors[], goals[] {id, competency_id, name, icon, target_level, current_level, priority, is_mandatory}, missions[] {id, title, xp_reward, points_reward, requires_approval, my_status, my_progress}, review {id, status, reviewer_notes}|null}. ### DELETE /api/v1/pdis/{id} Exclui o PDI e suas missões/metas/revisões. Auth: token. Gate: module:pdi + gestor/criador. - Resposta: `data.message`. ### POST /api/v1/pdis/{id}/goals Adiciona meta de competência ao plano. Auth: token. Gate: module:pdi + gestor/criador. - Body: `competency_tree_id` (int, required) — id da competência. `target_level` (int, min 1). `priority` (string) — low|medium|high. `is_mandatory` (bool). - Resposta: 201, `data` {id}. ### DELETE /api/v1/pdis/{id}/goals/{goalId} Remove meta do plano. Auth: token. Gate: module:pdi + gestor/criador. - Resposta: `data.message`. ### POST /api/v1/pdis/{id}/missions Cria missão vinculada ao PDI. Auth: token. Gate: module:pdi + gestor/criador. - Body: `title` (string, required, máx 255). `description` (string, máx 2000). `xp_reward`/`points_reward` (int, min 0). `requires_approval` (bool). `talent_rewards` (array) — [{competency_id (int, required), progress (numeric, required)}]. - Resposta: 201, `data` {id} — id da quest criada. ### DELETE /api/v1/pdis/{id}/missions/{questId} Remove missão do plano e recalcula o % de conclusão. Auth: token. Gate: module:pdi + gestor/criador. - Resposta: `data.message`. ### POST /api/v1/pdis/{id}/activate Ativa o plano (valida metas, missões e distribuição de 100%; 422 com erros se inválido). Auth: token. Gate: module:pdi + gestor/criador. - Resposta: `data.message`. ### POST /api/v1/pdis/{id}/state Muda o estado do plano. Auth: token. Gate: module:pdi + gestor/criador. - Body: `action` (string, required) — pause|resume|draft|cancel. - Resposta: `data.message`. ### POST /api/v1/pdis/{id}/submit Funcionário (dono) envia o PDI para revisão final. Auth: token. Gate: module:pdi + owner. - Body: `notes` (string, máx 2000). - Resposta: `data` {review_id, message}. ### POST /api/v1/pdis/{id}/review Gestor decide a revisão final pendente (422 se não houver). Auth: token. Gate: module:pdi + gestor/criador. - Body: `decision` (string, required) — approve|reject. `notes` (string, máx 2000). - Resposta: `data.message`. ## Competências Catálogo de competências (árvore de talentos) usado por missões e PDI: leitura para autosuggest e CRUD administrativo. Todas as rotas: Gate module:competencies. ### GET /api/v1/competencies Busca de competências ativas (autosuggest, máx 30). Auth: token. Gate: module:competencies. - Query: `q` (string) — filtra por nome. `user_id` (int) — restringe pela matriz do cargo do usuário e traz `suggested_level` (sem matriz, todas com nível máximo). - Resposta: `data[]` {id, name, icon, color, level, category, suggested_level}. ### GET /api/v1/admin/competencies Lista administrativa com contagem de usuários que desenvolvem cada competência. Auth: token. Gate: module:competencies + (master | quests.manage | competencies.manage). - Resposta: `data[]` {id, name, description, icon, color, is_active, holders}. ### POST /api/v1/admin/competencies Cria competência. Auth: token. Gate: module:competencies + (master | quests.manage | competencies.manage). - Body: `name` (string, required, máx 255). `description` (string, máx 2000). `icon` (string, máx 40). `color` (string, máx 20). `is_active` (bool, default true). - Resposta: 201, `data` {id}. ### PUT /api/v1/admin/competencies/{id} Atualiza competência (mesmos campos do POST). Auth: token. Gate: module:competencies + (master | quests.manage | competencies.manage). - Resposta: `data.message`. ### DELETE /api/v1/admin/competencies/{id} Exclui competência. Auth: token. Gate: module:competencies + (master | quests.manage | competencies.manage). - Resposta: `data.message`. ## Conquistas (admin) CRUD do catálogo de conquistas com regras declarativas (métrica/operador/valor) e reavaliação em massa. ### GET /api/v1/admin/achievements Lista o catálogo com regras e nº de detentores. Auth: token. Gate: master ou permissão `achievements.admin`/`achievements.manage`. - Query: `search` (string, opcional) — filtra por name/slug (like). - Resposta: `data[]` com id, slug, name, description, icon, image_path, match_mode (all|any), active, holders, rules[] {id, metric, operator, value}. ### GET /api/v1/admin/achievements/metrics Lista as métricas disponíveis para o editor de regras. Auth: token. Gate: achievements.admin/manage ou master. - Resposta: `data[]` de {key, label}. ### GET /api/v1/admin/achievements/{id} Detalhe de uma conquista para edição. Auth: token. Gate: achievements.admin/manage ou master. - Resposta: `data` {id, slug, name, description, icon, image_path, match_mode, active, rules[]}. ### POST /api/v1/admin/achievements Cria conquista (slug gerado automaticamente do name). Auth: token. Gate: achievements.admin/manage ou master. - Body: `name` (string, required, max 120); `description` (string, required, max 1000); `icon` (string?); `image_path` (string?); `match_mode` (in: all,any); `active` (bool?); `rules` (array?) — cada item: `metric` (string, das métricas válidas), `operator` (in: >=,>,<=,<,==), `value` (numeric). - Resposta: `data` com a conquista criada (mesmo shape do GET {id}). ### PUT /api/v1/admin/achievements/{id} Atualiza conquista; se `rules` for enviado, substitui TODAS as regras. Auth: token. Gate: achievements.admin/manage ou master. - Body: mesmos campos do POST, todos opcionais (`sometimes`). - Resposta: `data` com a conquista atualizada. ### DELETE /api/v1/admin/achievements/{id} Exclui a conquista (regras caem por FK cascade). Auth: token. Gate: achievements.admin/manage ou master. - Resposta: `data.message`. ### POST /api/v1/admin/achievements/recheck Reavalia todos os usuários e concede conquistas pendentes. Auth: token. Gate: achievements.admin/manage ou master. - Resposta: `data` {message, granted (int, nº de concessões)}. ## Roleta (participante) Rodas da roleta visíveis ao usuário, prêmios e o giro (com giros bônus e limite por período). ### GET /api/v1/roulette-wheels Lista roletas no ar (active/maintenance) com disponibilidade para o usuário. Auth: token. - Resposta: `data[]` {id, name, status, cost_to_spin, theme_color, total_spins, remaining_spins, spins_limit, period_label, bonus_spins, available (bool), reason (string|null)}. ### GET /api/v1/roulette-wheels/{id}/prizes Prêmios ativos ordenados (mesma ordem do spin) para renderizar a roda. Auth: token. - Resposta: `data` {wheel {id, name, theme_color, cost_to_spin, status}, prizes[] {index, id, name, type, value, color, icon}}. ### GET /api/v1/roulette-wheels/{id}/my-spins Histórico dos últimos 20 giros do próprio usuário nesta roleta. Auth: token. - Resposta: `data[]` {id, prize, prize_type, prize_value, spin_cost, created_at}. ### POST /api/v1/roulette-wheels/{id}/spin Gira a roleta: debita cost_to_spin da carteira (grátis se houver giro bônus), sorteia por weight e credita prêmio em pontos. 422 se indisponível, nível/saldo insuficiente, limite atingido ou sem orçamento. Auth: token. - Resposta 201: `data` {won {id, index, name, type, value}, reward_points, balance_after, spin_angle, used_bonus, granted_spins, bonus_spins_left, prizes[]}. ## Roleta (admin) Gestão da roda, prêmios, giros bônus e orçamento. Todas exigem gestor ou permissão `roulette.admin`/`roulette.manage`. Gate de módulo: module:roulette (404 se desligado). ### GET /api/v1/admin/roulette-wheels/{id} Roda + TODOS os prêmios (inclui inativos), orçamento e estatísticas. Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data` {wheel, prizes[], budget, sector_budgets, stats {total_spins, points_distributed, players}, recent_spins[] (20), bonus_users[]}. ### PUT /api/v1/admin/roulette-wheels/{id} Edita a roda; bloqueia ativar com orçamento se a verba não cobrir o maior prêmio (422). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Body: `name` (string, required); `description` (string?); `cost_to_spin` (int, required, min 0); `status` (required, in: active,inactive,maintenance); `max_spins_per_user` (int?, min 1); `reset_period` (in: daily,weekly,monthly,never); `min_level` (int?); `theme_color` (string?, max 7); `background_image` (string?); `uses_budget` (bool?). - Resposta: `data.message`. ### DELETE /api/v1/admin/roulette-wheels/{id} Soft-delete da roda: reembolsa verba não usada, remove prêmios e fontes. Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data` {message, refunded (float)}. ### POST /api/v1/admin/roulette-wheels/{id}/prizes Adiciona prêmio (position automática no fim). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Body: `name` (string, required); `type` (required, in: points,product,badge,experience,free_spin,custom); `value` (int?, min 0); `weight` (numeric, required, 0-100000) — peso do sorteio; `color` (string?, max 7); `image_url` (string?); `description` (string?); `is_active` (bool?); `is_jackpot` (bool?); `max_wins_total` (int?, min 1). - Resposta 201: `data` {id, message}. ### PUT /api/v1/admin/roulette-prizes/{id} Edita prêmio (mesmo body do POST prizes, `name`/`type`/`weight` obrigatórios). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data.message`. ### DELETE /api/v1/admin/roulette-prizes/{id} Remove prêmio. Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data.message`. ### POST /api/v1/admin/roulette-wheels/{id}/spins/grant Credita giros bônus a 1+ usuários (403 se incluir a si mesmo — anti-farm). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Body: `user_ids` (array de int, required, min 1); `quantity` (int, required, 1-1000); `reason` (string?, max 255). - Resposta 201: `data.message`. ### POST /api/v1/admin/roulette-wheels/{id}/spins/remove Remove giros bônus de um usuário (não fica negativo). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Body: `user_id` (int, required); `quantity` (int, required, min 1). - Resposta: `data` {message, bonus_spins (saldo restante)}. ### POST /api/v1/admin/roulette-wheels/{id}/budget/allocate Aloca verba de um orçamento de setor à roleta (liga uses_budget). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Body: `sector_budget_id` (int, required); `amount` (numeric, required, min 0.01); `description` (string?). - Resposta 201: `data.message`. ### POST /api/v1/admin/roulette-wheels/{id}/budget/refund Reembolsa o saldo não utilizado ao orçamento do setor. Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data` {message, refunded (float)}. ### POST /api/v1/admin/roulette-wheels/{id}/budget/remove Reembolsa e desativa o orçamento da roleta (uses_budget=0). Auth: token. Gate: module:roulette + gestor/roulette.manage. - Resposta: `data` {message, refunded (float)}. ## Mapas de tabuleiro (admin) Templates de mapa do jogo de tabuleiro; `total_cells` é derivado de `steps` (nunca aceito do cliente). ### GET /api/v1/admin/board-maps Lista mapas (ativos primeiro). Auth: token. Gate: gestor ou permissão `board.manage`. - Resposta: `data[]` {id, name, description, start_x, start_y, steps[], special_cells, total_cells, is_active}. ### POST /api/v1/admin/board-maps Cria mapa. Auth: token. Gate: gestor ou board.manage. - Body: `name` (string, required, max 150); `description` (string?); `start_x`/`start_y` (int?, min 0); `steps` (array, required, min 1) — cada item: `dir` (in: right,left,up,down), `count` (int, min 1); `special_cells` (array?); `is_active` (bool?). - Resposta 201: `data` com o mapa criado (mesmo shape do GET). ### PUT /api/v1/admin/board-maps/{id} Atualiza mapa (campos opcionais; total_cells recalculado). Auth: token. Gate: gestor ou board.manage. - Body: mesmos campos do POST, todos opcionais. - Resposta: `data` com o mapa atualizado. ### DELETE /api/v1/admin/board-maps/{id} Exclui mapa; 422 se houver partidas usando-o. Auth: token. Gate: gestor ou board.manage. - Resposta: `data.message`. ### PATCH /api/v1/admin/board-maps/{id}/toggle-active Alterna ativação do mapa. Auth: token. Gate: gestor ou board.manage. - Resposta: `data` {is_active (bool)}. ## Competências (admin) CRUD do catálogo de competências (árvores) usadas por missões/talentos. Gate de módulo: module:competencies (404 se desligado). ### GET /api/v1/admin/competencies Lista competências com nº de usuários que desenvolvem cada uma. Auth: token. Gate: module:competencies + master ou `quests.manage`/`competencies.manage`. - Resposta: `data[]` {id, name, description, icon, color, is_active, holders}. ### POST /api/v1/admin/competencies Cria competência. Auth: token. Gate: module:competencies + master ou quests.manage/competencies.manage. - Body: `name` (string, required, max 255); `description` (string?, max 2000); `icon` (string?, max 40); `color` (string?, max 20); `is_active` (bool, default true). - Resposta 201: `data` {id}. ### PUT /api/v1/admin/competencies/{id} Atualiza competência (body igual ao POST; `name` required). Auth: token. Gate: module:competencies + master ou quests.manage/competencies.manage. - Resposta: `data.message`. ### DELETE /api/v1/admin/competencies/{id} Exclui competência. Auth: token. Gate: module:competencies + master ou quests.manage/competencies.manage. - Resposta: `data.message`. ## Integrações Tokens de API M2M (Sanctum, com escopos) e webhooks de saída com log de entregas. Todas exigem master ou permissão `integrations.manage`. ### GET /api/v1/integrations/meta Escopos e eventos disponíveis para a UI. Auth: token. Gate: integrations.manage ou master. - Resposta: `data` {abilities: [...escopos concedíveis pelo usuário...], events: [...catálogo de eventos de webhook...]}. - Catálogo de eventos: quest.* (started, submitted, completed, approved, rejected); challenge.joined (entrou no desafio) e challenge.completed (participante concluiu); challenge.submission.created/approved/rejected; course.completed; certificate.issued; order.created; okr.* (checkin.created, kr.achieved, kr.completed, objective.completed, cycle.closed, kpi.entry.created, widget.value.updated). Assine `*` para receber todos. ### GET /api/v1/integrations/api-tokens Lista tokens de API (prefixo interno `api:`). Auth: token. Gate: integrations.manage ou master. - Resposta: `data[]` {id, name, abilities[], last_used_at, created_at}. ### POST /api/v1/integrations/api-tokens Cria token; o texto puro do token é retornado APENAS nesta resposta. Auth: token. Gate: integrations.manage ou master. - Body: `name` (string, required, max 80); `abilities` (array, required, min 1) — valores: quests.read, quests.write. - Resposta 201: `data` {id, name, abilities, token (plaintext, exibido só agora)}. ### DELETE /api/v1/integrations/api-tokens/{id} Revoga o token. Auth: token. Gate: integrations.manage ou master. - Resposta: `data.message`. ### GET /api/v1/integrations/webhooks Lista endpoints de webhook. Auth: token. Gate: integrations.manage ou master. - Resposta: `data[]` {id, name, url, events[], active, secret_hint (últimos 6 chars), created_at}. ### POST /api/v1/integrations/webhooks Cria webhook; o `secret` (whsec_...) é retornado em texto puro só na criação. Auth: token. Gate: integrations.manage ou master. - Body: `name` (string, required, max 120); `url` (url, required, max 500); `events` (array, required, min 1) — eventos do catálogo (quest.*, challenge.joined, challenge.completed, challenge.submission.*, order.created, okr.*, ...) ou `*`; `active` (bool?). - Resposta 201: `data` {id, name, url, events, secret}. ### PUT /api/v1/integrations/webhooks/{id} Atualiza webhook (campos opcionais, mesmo body do POST). Auth: token. Gate: integrations.manage ou master. - Resposta: `data.message`. ### DELETE /api/v1/integrations/webhooks/{id} Exclui webhook. Auth: token. Gate: integrations.manage ou master. - Resposta: `data.message`. ### GET /api/v1/integrations/webhooks/{id}/deliveries Últimas 50 entregas do webhook (diagnóstico). Auth: token. Gate: integrations.manage ou master. - Resposta: `data[]` {id, event, status, response_code, created_at}. ## Dashboard administrativo Visão geral da empresa ativa para o painel admin. ### GET /api/v1/dashboard/stats Contadores por tabela, KPIs, pendências, distribuição por setor e top 10 usuários por pontos (escopo por empresa quando há tenant ativo). Auth: token. - Resposta: `data` {kpis {total_users, active_users, points_circulating, achievements_awarded, avg_level}, attention[] {key, label, route, count}, by_sector[] {name, users, points}, counts[] {key, label, count}, top_users[] {id, nome, points, current_level}, total_points, total_users}. ## Blog Artigos do blog ClickPoints sobre engajamento, gamificação corporativa, RH e desenvolvimento de pessoas. Cada link é um artigo completo: - [Reconhecimento entre pares: o que é e como implementar na sua empresa](https://clickpontos.com.br/blog/reconhecimento-entre-pares-o-que-e-e-como-implementar-na-sua-empresa) — Reconhecimento entre pares transforma cultura quando o "obrigado" deixa de depender só do chefe. Veja o que é, por que funciona e como implementar em 5 passos. - [Onboarding de funcionários: como estruturar os primeiros 90 dias](https://clickpontos.com.br/blog/onboarding-primeiros-90-dias) — Um onboarding bem estruturado nos primeiros 90 dias reduz o turnover e acelera a produtividade. Veja as fases, o que medir e os erros que sabotam a integração. - [Engajamento de funcionários: o guia definitivo [2026]](https://clickpontos.com.br/blog/guia-engajamento-de-funcionarios) — Guia definitivo de engajamento de funcionários em 2026: o que é, pilares, como medir e estratégias práticas para construir um time motivado e que fica. - [Endomarketing: o que é e como aplicar na prática](https://clickpontos.com.br/blog/endomarketing-o-que-e-e-como-aplicar) — Entenda o que é endomarketing, como ele difere de comunicação interna e um passo a passo prático para aplicar e engajar seus colaboradores de verdade. - [Como reduzir o turnover: causas e 8 ações práticas](https://clickpontos.com.br/blog/como-reduzir-turnover) — Descubra as principais causas do turnover, quanto ele custa para a empresa e 8 ações práticas para reduzir a rotatividade e reter os melhores talentos. - [OKRs e metas de equipe: como transformar objetivos em rotina](https://clickpontos.com.br/blog/okrs-e-metas-para-equipes) — Aprenda o que são OKRs, como definir objetivos e resultados-chave para equipes e como transformar metas em rotina com acompanhamento e reconhecimento. - [Cultura de feedback contínuo: por que anuais não bastam](https://clickpontos.com.br/blog/cultura-de-feedback-continuo) — Por que avaliações anuais não bastam: veja como construir uma cultura de feedback contínuo que acelera o desenvolvimento e fortalece a confiança do time. - [O que é gamificação corporativa e por que funciona](https://clickpontos.com.br/blog/o-que-e-gamificacao-corporativa) — Entenda o que é gamificação corporativa, como ela usa mecânicas de jogos para engajar equipes e por que ela funciona tão bem dentro das empresas. - [Como engajar funcionários: 9 estratégias que funcionam](https://clickpontos.com.br/blog/como-engajar-funcionarios) — Descubra 9 estratégias práticas para engajar funcionários, reduzir turnover e criar um time mais motivado, com exemplos aplicáveis a qualquer empresa. - [PDI: como montar um Plano de Desenvolvimento Individual eficaz](https://clickpontos.com.br/blog/pdi-plano-de-desenvolvimento-individual) — Aprenda o que é um PDI, como montar um Plano de Desenvolvimento Individual passo a passo e por que ele é essencial para reter e desenvolver talentos. - [Programa de reconhecimento e recompensas: guia completo](https://clickpontos.com.br/blog/programa-de-reconhecimento-e-recompensas) — Guia completo para criar um programa de reconhecimento e recompensas que motiva de verdade: tipos, boas práticas, erros comuns e como medir resultados. - [Gamificação em treinamentos: como acelerar o aprendizado](https://clickpontos.com.br/blog/gamificacao-em-treinamentos-corporativos) — Veja como a gamificação em treinamentos corporativos aumenta a conclusão de cursos, melhora a retenção do conteúdo e torna o aprendizado mais eficaz. - [Métricas de engajamento que realmente importam](https://clickpontos.com.br/blog/metricas-de-engajamento-que-importam) — Conheça as métricas de engajamento que realmente importam, como medi-las e por que olhar para os números certos muda a forma como você gere pessoas.