funifier-mcp 0.2.26 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/index.js +2 -2
  67. package/dist/mcp/index.js.map +1 -1
  68. package/dist/mcp/resources/documentation.d.ts +1 -1
  69. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  70. package/dist/mcp/resources/documentation.js +39 -3
  71. package/dist/mcp/resources/documentation.js.map +1 -1
  72. package/dist/mcp/tools/connect.d.ts.map +1 -1
  73. package/dist/mcp/tools/connect.js +18 -8
  74. package/dist/mcp/tools/connect.js.map +1 -1
  75. package/dist/mcp/tools/database.d.ts.map +1 -1
  76. package/dist/mcp/tools/database.js +59 -47
  77. package/dist/mcp/tools/database.js.map +1 -1
  78. package/dist/mcp/tools/database.test.js +2 -2
  79. package/dist/mcp/tools/database.test.js.map +1 -1
  80. package/dist/mcp/tools/delete.d.ts.map +1 -1
  81. package/dist/mcp/tools/delete.js +13 -3
  82. package/dist/mcp/tools/delete.js.map +1 -1
  83. package/dist/mcp/tools/execute.d.ts.map +1 -1
  84. package/dist/mcp/tools/execute.js +20 -9
  85. package/dist/mcp/tools/execute.js.map +1 -1
  86. package/dist/mcp/tools/folder.d.ts.map +1 -1
  87. package/dist/mcp/tools/folder.js +22 -12
  88. package/dist/mcp/tools/folder.js.map +1 -1
  89. package/dist/mcp/tools/get.d.ts.map +1 -1
  90. package/dist/mcp/tools/get.js +16 -6
  91. package/dist/mcp/tools/get.js.map +1 -1
  92. package/dist/mcp/tools/index.d.ts +1 -1
  93. package/dist/mcp/tools/index.d.ts.map +1 -1
  94. package/dist/mcp/tools/index.js +3 -1
  95. package/dist/mcp/tools/index.js.map +1 -1
  96. package/dist/mcp/tools/list.d.ts.map +1 -1
  97. package/dist/mcp/tools/list.js +38 -14
  98. package/dist/mcp/tools/list.js.map +1 -1
  99. package/dist/mcp/tools/logs.d.ts.map +1 -1
  100. package/dist/mcp/tools/logs.js +15 -5
  101. package/dist/mcp/tools/logs.js.map +1 -1
  102. package/dist/mcp/tools/save.d.ts.map +1 -1
  103. package/dist/mcp/tools/save.js +14 -4
  104. package/dist/mcp/tools/save.js.map +1 -1
  105. package/dist/mcp/tools/save.test.js +3 -3
  106. package/dist/mcp/tools/save.test.js.map +1 -1
  107. package/dist/mcp/tools/search-docs.d.ts +3 -0
  108. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  109. package/dist/mcp/tools/search-docs.js +102 -0
  110. package/dist/mcp/tools/search-docs.js.map +1 -0
  111. package/package.json +6 -2
  112. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  113. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  114. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  115. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  116. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  117. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  118. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  119. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  120. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  121. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  122. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  123. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  124. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  125. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  126. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  127. package/skills/funifier/SKILL.md +88 -0
  128. package/skills/funifier/references/configure-security.md +96 -0
  129. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  130. package/skills/funifier/references/create-aggregate.md +144 -0
  131. package/skills/funifier/references/create-challenge.md +116 -0
  132. package/skills/funifier/references/create-competition.md +98 -0
  133. package/skills/funifier/references/create-crossword.md +574 -0
  134. package/skills/funifier/references/create-custom-object.md +91 -0
  135. package/skills/funifier/references/create-custom-page.md +135 -0
  136. package/skills/funifier/references/create-folder.md +104 -0
  137. package/skills/funifier/references/create-lastmile.md +643 -0
  138. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  139. package/skills/funifier/references/create-level.md +94 -0
  140. package/skills/funifier/references/create-lottery.md +913 -0
  141. package/skills/funifier/references/create-mystery.md +769 -0
  142. package/skills/funifier/references/create-notification.md +75 -0
  143. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  144. package/skills/funifier/references/create-quiz.md +98 -0
  145. package/skills/funifier/references/create-scheduler.md +141 -0
  146. package/skills/funifier/references/create-story.md +636 -0
  147. package/skills/funifier/references/create-swap.md +95 -0
  148. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  149. package/skills/funifier/references/create-virtual-good.md +96 -0
  150. package/skills/funifier/references/create-webhook.md +72 -0
  151. package/skills/funifier/references/create-websocket.md +71 -0
  152. package/skills/funifier/references/create-widget.md +76 -0
  153. package/skills/funifier/references/debug.md +87 -0
  154. package/skills/funifier/references/help.md +81 -0
  155. package/skills/funifier/references/implement-frontend.md +106 -0
  156. package/skills/funifier/references/import-csv.md +75 -0
  157. package/skills/funifier/references/manage-player.md +82 -0
  158. package/skills/funifier/references/manage-team.md +76 -0
  159. package/skills/funifier/references/upload-file.md +91 -0
  160. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  161. package/skills/funifier-create-challenge/SKILL.md +0 -88
  162. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  163. package/skills/funifier-create-level/SKILL.md +0 -87
  164. package/skills/funifier-create-quiz/SKILL.md +0 -87
  165. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  166. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  167. package/skills/funifier-debug/SKILL.md +0 -92
  168. package/skills/funifier-help/SKILL.md +0 -86
  169. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  170. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,75 +1,593 @@
1
- # Team (Equipe)
1
+ # `team`
2
2
 
3
3
  **Acesso Studio:** `/studio/team`
4
4
  **API Endpoint:** `/v3/team`
5
+ **Coleção MongoDB:** `team` (coleções auxiliares: `principal`, `team_player`; coleção legada: `team_challenge`)
5
6
 
6
- ## O que é
7
+ > Documentação de engenharia reversa baseada **exclusivamente** no código de `funifier-service` (pacote `com.funifier.engine.team` + `com.funifier.rest.v3.rest.TeamRest`). Reflete o comportamento real do runtime, não a documentação anterior.
7
8
 
8
- Configuração de grupos ou equipes de jogadores. Permite criar grupos fixos (como "Equipe de Vendas") ou equipes dinâmicas, formadas automaticamente com base em critérios como aniversariantes do dia. Equipes permitem competição ou colaboração entre grupos.
9
+ ---
9
10
 
10
- ## Quando usar
11
+ ## 1. Visão Geral
11
12
 
12
- - Quando competição ou colaboração entre grupos
13
- - Para organizar jogadores por departamento, região ou função
14
- - Para criar rankings por equipe
15
- - Para atribuir desafios coletivos
13
+ O módulo `team` agrupa jogadores em **equipes**. Uma equipe é uma entidade de gamificação de primeira classe: ao ser criada, recebe um registro na coleção `principal` (`type = 1 / TYPE_TEAM`), o que permite que a equipe **participe de desafios, acumule pontos, itens e níveis exatamente como um jogador** — todos os totais da equipe são lançamentos da coleção `achievement` cujo campo `player` é o `_id` da equipe.
16
14
 
17
- ## Checklist de Configuração no Studio
15
+ Papel arquitetural:
18
16
 
19
- - [ ] Definir _id da equipe
20
- - [ ] Definir nome da equipe
21
- - [ ] Adicionar descrição e imagem/brasão (opcional)
22
- - [ ] Adicionar membros à equipe
23
- - [ ] Definir dono da equipe (owner)
17
+ - **Agrupamento de principais.** A equipe é um `Principal` do tipo time. O `_id` da equipe entra no mesmo espaço de identificadores dos jogadores (coleção `principal`), e por isso pode ser alvo de `achievement`, `action_log`, `challenge_progress`, etc.
18
+ - **Membership (vínculo jogador↔equipe).** Existem dois mecanismos mutuamente exclusivos por equipe:
19
+ - **Manual** vínculos persistidos na coleção `team_player` (uma linha `TeamLink` por par equipe/jogador), espelhados no array `player.teams[]`.
20
+ - **Dinâmico (v4)** a equipe não tem vínculos persistidos; seus membros são **calculados em tempo de execução** rodando um pipeline de aggregation MongoDB (`Team.dynamic`) sobre uma coleção arbitrária.
21
+ - **Desafios de equipe.** Quando um jogador dispara uma ação, `AchievementManager.fireAction` reavalia a mesma ação para **cada equipe manual** do jogador, gerando achievements no nome da equipe (vide §2.5). É assim que uma equipe “ganha” pontos/desafios.
22
+ - **Status agregado.** `GET /v3/team/{id}/status` calcula o extrato de gamificação **da própria equipe** (não a soma dos membros) via `AchievementManager.calculateTeamStatus`.
24
23
 
25
- ## API Endpoints
24
+ Problemas que resolve:
26
25
 
27
- ### Listar Equipes
28
- **Método:** GET
29
- **Endpoint:** `/v3/team`
26
+ - Competição/colaboração entre grupos (rankings por equipe via `crowning`/leaderboard).
27
+ - Organização de jogadores por departamento, região, função (manual) ou por critério calculado, ex.: “aniversariantes do dia”, “jogadores com score > X” (dinâmico).
28
+ - Atribuição de desafios coletivos (`Challenge.teamChallenge = true`).
30
29
 
31
- ### Criar Equipe
32
- **Método:** POST
33
- **Endpoint:** `/v3/team`
30
+ Relação direta com outros módulos:
34
31
 
35
- **Exemplo de Body:**
32
+ - `player` — `player.teams[]` é a fonte de verdade do membership manual; salvar um player re-sincroniza `team_player` (vide §2.4).
33
+ - `achievement` — `AchievementManager.fireAction` faz a recursão de desafios de equipe; `calculateTeamStatus` lê achievements da equipe.
34
+ - `principal` — toda equipe cria/atualiza um `Principal` espelho.
35
+ - `challenge` — `teamChallenge = true` direciona desafios para principais do tipo time.
36
+ - `crowning` — `findLeadersByPlayerId(teamId)` devolve posições de leaderboard da equipe.
37
+ - `competition` — reaproveita a classe `AutoJoin` (`com.funifier.engine.competition.AutoJoin`) como configuração de equipe dinâmica.
38
+
39
+ > **Nota de manutenção (no código):** o comentário em `AchievementManager.java:849` indica que a resolução de equipes do jogador está sendo migrada para Node.js (`"...quando o esau implementar no nodejs"`). O módulo Java aqui descrito é o comportamento vigente no `funifier-service`.
40
+
41
+ ---
42
+
43
+ ## 2. Arquitetura e Fluxos
44
+
45
+ ### 2.1 Classes envolvidas
46
+
47
+ | Classe | Arquivo | Papel |
48
+ |---|---|---|
49
+ | `Team` | `engine/team/Team.java` | Entidade/POJO raiz — documento da coleção `team` |
50
+ | `TeamManager` | `engine/team/TeamManager.java` | Manager: CRUD, membership, resolução dinâmica |
51
+ | `TeamDaoMongo` | `engine/team/TeamDaoMongo.java` | Acesso MongoDB via Jongo; cascatas de delete/reset |
52
+ | `TeamLink` | `engine/team/TeamLink.java` | Vínculo jogador↔equipe persistido em `team_player` |
53
+ | `TeamStatus` | `engine/team/TeamStatus.java` | **Código morto** — definido mas nunca instanciado (vide §7) |
54
+ | `AutoJoin` | `engine/competition/AutoJoin.java` | Configuração de equipe dinâmica (`entity` + `aggregate`) |
55
+ | `Principal` | `engine/player/Principal.java` | Espelho da equipe na coleção `principal` (`type = 1`) |
56
+ | `TeamRest` | `rest/v3/rest/TeamRest.java` | Controller REST `/v3/team` |
57
+
58
+ Não há `TeamService` separado — a lógica de negócio mora inteiramente em `TeamManager`. O acesso ao Mongo é direto via Jongo dentro de `TeamDaoMongo`.
59
+
60
+ ### 2.2 Pipeline de criação — `TeamManager.add(Team)`
61
+
62
+ Sequência real (`TeamManager.add`, linhas 34-49 → `TeamDaoMongo.add`, linhas 19-27):
63
+
64
+ ```
65
+ [1] Se team.id é null ou vazio → team.id = Guid.shortTimeMillis()
66
+ [2] Se team.created é null → team.created = new Date()
67
+ [3] team.updated = new Date() (sempre sobrescreve)
68
+ [4] Monta Principal(id=team.id, name=team.name, type=TYPE_TEAM, teamId=team.id, userId=null)
69
+ [5] mongo.add:
70
+ - team collection → c.save(team) (Jongo save = UPSERT por _id)
71
+ - principal collection → cP.save(principal) (UPSERT por _id = team.id)
72
+ ```
73
+
74
+ ```mermaid
75
+ flowchart LR
76
+ A["POST /v3/team"] --> B{"id vazio?"}
77
+ B -- sim --> C["id = Guid.shortTimeMillis()"]
78
+ B -- nao --> D["mantem id"]
79
+ C --> E{"created null?"}
80
+ D --> E
81
+ E -- sim --> F["created = now"]
82
+ E -- nao --> G["mantem created"]
83
+ F --> H["updated = now"]
84
+ G --> H
85
+ H --> I["save(team) UPSERT"]
86
+ I --> J["save(Principal type=TEAM)"]
87
+ ```
88
+
89
+ Observações de comportamento:
90
+
91
+ - **Não existe operação de update/PATCH.** `POST /v3/team` sempre chama `add`. Como `c.save` é **upsert por `_id`**, reenviar um POST com um `_id` existente faz **substituição total (full replace)** do documento. Se o corpo não trouxer `created`, o `created` é **reescrito com a data atual** (perde-se a data original).
92
+ - A geração de id no manager (`Guid.shortTimeMillis`) torna o fallback de `TeamDaoMongo.add` (`Guid.newShortGuid`, linha 20) um **ramo morto** — quando `mongo.add` roda, o id já foi definido.
93
+ - `add` é **síncrono** e **não dispara triggers nem webhooks** próprios do módulo team. A equipe só entra no fluxo de eventos quando passa a receber achievements (via desafios de equipe).
94
+
95
+ ### 2.3 Fluxo de adição de membro (manual)
96
+
97
+ `TeamRest.addMember` → `TeamManager.linkUser` (linhas 188-202):
98
+
99
+ ```mermaid
100
+ sequenceDiagram
101
+ participant C as Cliente
102
+ participant R as TeamRest.addMember
103
+ participant TM as TeamManager
104
+ participant DAO as TeamDaoMongo
105
+ participant PM as PlayerManager
106
+ C->>R: GET /v3/team/{id}/member/add/{playerId}
107
+ R->>TM: isUserOfTeam(playerId, id)?
108
+ alt ja e membro
109
+ R-->>C: 200 (no-op)
110
+ else nao e membro
111
+ R->>TM: linkUser(team, playerId)
112
+ TM->>DAO: linkUser -> save TeamLink em team_player
113
+ TM->>PM: player.teams += teamId; insert(player)
114
+ Note over PM: insert re-sincroniza team_player a partir de player.teams<br/>e chama updateUserStatus(playerId)
115
+ R-->>C: 200 {team, player}
116
+ end
117
+ ```
118
+
119
+ - A resposta é sempre `{ "team": id, "player": playerId }` — **não** retorna a equipe nem a lista de membros.
120
+ - `linkUser` deduplica (`teamIds.remove` seguido de `add`) antes de salvar o player.
121
+ - `POST /v3/team/{id}/member/bulk` (corpo = `["p1","p2",...]`) processa em laço e devolve um status por membro: `DONT_EXIST` (player inexistente), `MEMBER_ADDED`, `ALREADY_MEMBER`.
122
+
123
+ ### 2.4 Re-sincronização `player.teams[]` → `team_player`
124
+
125
+ Ponto não óbvio: a coleção `team_player` é **derivada de `player.teams[]`** a cada save de player (`PlayerManager`, linhas 309-320):
126
+
127
+ ```
128
+ [1] teamDao.deleteLinksByPlayer(player.id) → apaga TODOS os TeamLink do jogador
129
+ [2] para cada teamId em player.teams:
130
+ team = teamDao.findById(teamId)
131
+ se team != null E team.dynamic == null:
132
+ teamDao.linkUser(teamId, player.id) → recria o vínculo
133
+ [3] updateUserStatus(player.id)
134
+ ```
135
+
136
+ Consequências:
137
+
138
+ - Editar `player.teams[]` (via `PUT/POST /v3/player`) é uma **forma alternativa e equivalente** de gerenciar membership manual.
139
+ - Equipes **dinâmicas são ignoradas** nesse passo (`team.dynamic == null`); um id de equipe dinâmica em `player.teams[]` é **silenciosamente descartado** (não vira `TeamLink`).
140
+ - Um id de equipe **inexistente** em `player.teams[]` também é descartado em silêncio.
141
+ - Como `TeamManager.linkUser`/`removeUsers` chamam `playerManager.insert(player)`, toda mutação de membership manual passa por essa re-sincronização completa + recálculo de status.
142
+
143
+ ### 2.5 Desafios de equipe — recursão em `fireAction`
144
+
145
+ `AchievementManager.fireAction` (linhas 815-823), após avaliar os desafios do **jogador**:
146
+
147
+ ```
148
+ se principal é PLAYER:
149
+ teamIds = findTeamIdsByUserId(userId) // AchievementManager:850 — SOMENTE team_player
150
+ para cada teamId:
151
+ fireAction(new ActionLog(actionId, teamId, time, attributes))
152
+ ```
153
+
154
+ Na chamada recursiva, o principal resolvido é a **equipe** (`type = TEAM`), então apenas desafios com `teamChallenge = true` são avaliados, e os achievements resultantes são gravados com `player = teamId`.
155
+
156
+ ```mermaid
157
+ flowchart TB
158
+ A["Jogador dispara acao"] --> B["fireAction(player)"]
159
+ B --> C["Avalia desafios do jogador"]
160
+ C --> D{"principal e PLAYER?"}
161
+ D -- sim --> E["findTeamIdsByUserId(userId)<br/>SOMENTE team_player (manual)"]
162
+ E --> F["para cada teamId"]
163
+ F --> G["fireAction(teamId) recursivo"]
164
+ G --> H["Avalia desafios teamChallenge=true"]
165
+ H --> I["addAchievement(player=teamId)"]
166
+ D -- nao --> Z["fim"]
167
+ ```
168
+
169
+ > **Assimetria crítica (vide §7):** a recursão usa `AchievementManager.findTeamIdsByUserId` (linha 850), que consulta **apenas** `team_player`. Equipes **dinâmicas não entram nessa lista**, logo **membros de equipes dinâmicas nunca disparam desafios de equipe**. Isto difere de `TeamManager.findTeamIdsByUserId` (linha 204), que inclui dinâmicas — e que é o método usado por `GET /v3/player/{id}/team`.
170
+
171
+ Quando o principal é uma equipe, o passo de “update status” é um **TODO vazio** (`AchievementManager.java:807-809`): **não existe `player_status` materializado para equipes**. O status da equipe é sempre calculado sob demanda.
172
+
173
+ ### 2.6 Resolução de membership — `findUserIdsByTeamId`
174
+
175
+ ```mermaid
176
+ flowchart LR
177
+ A["findUserIdsByTeamId(teamId)"] --> B["findById(teamId)"]
178
+ B --> C{"team.dynamic.entity E<br/>team.dynamic.aggregate definidos?"}
179
+ C -- sim --> D["roda pipeline em dynamic.entity<br/>coleta _id de cada doc"]
180
+ C -- nao --> E["team_player.distinct(linkId)<br/>where teamId"]
181
+ D --> F["lista de ids dinamica"]
182
+ E --> G["lista de ids manual"]
183
+ ```
184
+
185
+ `findDynamicMemberIds` (linhas 318-337): desserializa `dynamic.aggregate` (string JSON) numa lista de estágios, executa `collection(dynamic.entity).aggregate(estágio[0])` encadeando `.and(estágio[i])`, e coleta o campo `_id` de cada resultado. `isDynamicMember` (linhas 339-355) faz o mesmo e anexa um estágio final `{$match:{_id: <user>}}` para testar pertinência de um jogador específico.
186
+
187
+ ### 2.7 Status da equipe — `calculateTeamStatus`
188
+
189
+ `AchievementManager.calculateTeamStatus(teamId)` (linhas 1086-1129) retorna um objeto **`PlayerStatus`** (não `TeamStatus`), tratando o `teamId` como se fosse um `player`:
190
+
191
+ | Campo do status | Origem |
192
+ |---|---|
193
+ | `player`, `name`, `image`, `extra` | Copiados do documento `team` |
194
+ | `total_challenges`, `challenges` | `achievement` `type=CHALLENGE` onde `player = teamId` |
195
+ | `total_points`, `point_categories` | `sumTotalPoints(teamId)` / `getTotalPointsGroupedByType(teamId)` |
196
+ | `total_catalog_items`, `catalog_items` | `achievement` `type=CATALOG_ITEM` onde `player = teamId` |
197
+ | `challenge_progress` | `challenge_progress` onde `player = teamId` |
198
+ | `positions` | `crowningManager.findLeadersByPlayerId(teamId)` (envolto em try/catch silencioso) |
199
+ | `time` | `new Date()` |
200
+
201
+ > O status reflete **as conquistas da própria equipe** (geradas por desafios de equipe), **não** a soma das conquistas individuais dos membros.
202
+
203
+ ---
204
+
205
+ ## 3. Estrutura dos Objetos
206
+
207
+ ### 3.1 `Team` — documento raiz (coleção `team`)
208
+
209
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
210
+ |---|---|---|---|---|
211
+ | `_id` | String | auto (`Guid.shortTimeMillis()`) | — | Id da equipe. Se ausente/vazio no POST, é gerado. |
212
+ | `name` | String | — | não (recomendado) | Nome de exibição. Copiado para o `Principal`. |
213
+ | `description` | String | — | não | Descrição livre. |
214
+ | `image` | `Image` | — | não | Estrutura `{small, medium, original}`, cada um `{url}`. |
215
+ | `extra` | `Map<String,Object>` | `{}` | não | Atributos arbitrários. Persistido e devolvido. Lido em `calculateTeamStatus`. |
216
+ | `owner` | String | — | não | **Persistido mas nunca lido** em nenhum ponto do código (campo decorativo — vide §7). |
217
+ | `created` | Date | `now` no 1º save | — | Definido por `add` apenas se null; reescrito em re-POST sem `created`. |
218
+ | `updated` | Date | `now` | — | Reescrito em todo `add`/`update`. |
219
+ | `dynamic` | `AutoJoin` | — | não | Configuração de equipe dinâmica (v4). Se presente e válido, a equipe não usa `team_player`. |
220
+
221
+ **Getters/setters:** a entidade só expõe getters/setters para `id`, `name`, `image`, `description`. Os campos `extra`, `owner`, `created`, `updated`, `dynamic` são **públicos** e mapeados diretamente pelo Jackson.
222
+
223
+ `@JsonIgnoreProperties(ignoreUnknown = true)` — campos desconhecidos no corpo do POST são **ignorados silenciosamente**.
224
+
225
+ ### 3.2 `AutoJoin` — configuração dinâmica (`Team.dynamic`)
226
+
227
+ | Campo | Tipo | Descrição |
228
+ |---|---|---|
229
+ | `entity` | String | Coleção MongoDB onde o pipeline é executado (ex.: `"player"`). |
230
+ | `aggregate` | String | **String JSON** com um array de estágios de aggregation. Deve retornar documentos cujo `_id` é o id do membro (ex.: `[{"$project":{"_id":1}}]`). |
231
+
232
+ Ambos `entity` e `aggregate` precisam estar **não-nulos** para a equipe ser tratada como dinâmica (`TeamManager:240, 319, 340`). Caso contrário, cai no caminho manual.
233
+
234
+ ### 3.3 `TeamLink` — vínculo manual (coleção `team_player`)
235
+
236
+ | Campo | Tipo | Descrição |
237
+ |---|---|---|
238
+ | `_id` | String | `Guid.newShortGuid()` por vínculo. |
239
+ | `teamId` | String | Id da equipe. |
240
+ | `linkId` | String | Id do jogador (membro). |
241
+
242
+ ### 3.4 `Principal` — espelho da equipe (coleção `principal`)
243
+
244
+ Criado/atualizado a cada `add`. Para equipes: `_id = teamId`, `name = team.name`, `type = 1 (TYPE_TEAM)`, `teamId = teamId`, `userId = null`.
245
+
246
+ **Enum `Principal.type`:**
247
+
248
+ | Valor | Constante | Significado |
249
+ |---|---|---|
250
+ | `0` | `TYPE_USER` | Principal é um jogador |
251
+ | `1` | `TYPE_TEAM` | Principal é uma equipe |
252
+
253
+ ### 3.5 `TeamStatus` — subentidade **inativa**
254
+
255
+ `TeamStatus extends Team` adiciona um booleano `joined`. **Nunca é instanciada** em lugar nenhum do repositório (vide §7). O endpoint de status retorna `PlayerStatus`, não esta classe.
256
+
257
+ ### 3.6 Coleções escritas pelo módulo
258
+
259
+ | Coleção | Conteúdo | Quem escreve |
260
+ |---|---|---|
261
+ | `team` | Documentos `Team` | `add`/`update` |
262
+ | `principal` | Espelho `Principal` (type=1) | `add`/`update`/`delete` |
263
+ | `team_player` | Vínculos `TeamLink` manuais | `linkUser`/`removeUsers`/sync do player |
264
+ | `team_challenge` | **Legado** — vínculos via `linkChallenge` (`@Deprecated`) | Não usado em fluxo ativo (§7) |
265
+
266
+ ---
267
+
268
+ ## 4. Endpoints
269
+
270
+ Todos os endpoints exigem `Authorization: Bearer <token>` (`@BeanParam AuthBean`). O tenant é resolvido por `FrontController.getInstance(authBean.getApiKey())` — isolamento por organização (§8).
271
+
272
+ ### `GET /v3/team` — listar/consultar equipes
273
+
274
+ | Aspecto | Detalhe |
275
+ |---|---|
276
+ | Finalidade | Lista paginada de equipes (`findAllPaginated`) |
277
+ | Autenticação | Bearer token |
278
+ | Paginação | Header `Range: items=0-100` (default 0-100); `max_results` default 100 |
279
+
280
+ **Query params:**
281
+
282
+ | Param | Tipo | Descrição |
283
+ |---|---|---|
284
+ | `team` | String | Ids separados por vírgula → `{_id:{$in:[...]}}`. |
285
+ | `name` | String | Filtro `{name:{$regex:<name>, $options:'i'}}` (substring, case-insensitive). |
286
+ | `q` | String | **Critério MongoDB cru** injetado diretamente no `$match` (`", " + q`). Ex.: `extra.country:"USA"`. Superfície de injeção (§8). |
287
+ | `fields` | String | Projeção `$project` (campos separados por vírgula). |
288
+ | `orderby` | String | Campo de ordenação (`$sort`). |
289
+ | `reverse` | boolean | `true` → ordem decrescente. |
290
+ | `max_results` | int | Limite (`$limit`); `<=0` vira 100. |
291
+
292
+ **Comportamento real:** monta um pipeline de aggregation `[$match, ($project), ($sort), ($limit)]`. A resposta é o envelope paginado padrão (`Callback.paginatedCallback`).
293
+
294
+ ### `GET /v3/team/{id}` — obter uma equipe
295
+
296
+ Retorna o documento `Team` (campos null removidos via `JsonUtil.toJsonRemoveNullFields`). Não embute membros.
297
+
298
+ ### `GET /v3/team/{id}/status` — extrato de gamificação da equipe
299
+
300
+ Retorna um `PlayerStatus` calculado por `calculateTeamStatus` (§2.7). Reflete as conquistas **da equipe**, não dos membros.
301
+
302
+ ### `POST /v3/team` — criar/substituir equipe
303
+
304
+ | Aspecto | Detalhe |
305
+ |---|---|
306
+ | Finalidade | `TeamManager.add` (upsert por `_id`) |
307
+ | Full replace ou patch | **Full replace** (não há PATCH). Re-POST com `_id` existente substitui o documento inteiro. |
308
+ | Status | `201 CREATED`; corpo = a equipe (com `_id`/`created`/`updated` preenchidos) |
309
+
310
+ **Exemplo de request:**
36
311
  ```json
37
312
  {
38
313
  "_id": "sales",
39
314
  "name": "Sales",
40
315
  "description": "Funifier sales team",
41
316
  "image": {
42
- "small": {"url": "https://my.funifier.com/images/funny.png"},
43
- "medium": {"url": "https://my.funifier.com/images/funny.png"},
44
- "original": {"url": "https://my.funifier.com/images/funny.png"}
317
+ "small": {"url": "https://my.funifier.com/funny.png"},
318
+ "medium": {"url": "https://my.funifier.com/funny.png"},
319
+ "original": {"url": "https://my.funifier.com/funny.png"}
45
320
  },
46
- "extra": { "country": "USA" },
321
+ "extra": {"country": "USA"},
47
322
  "owner": "john"
48
323
  }
49
324
  ```
50
325
 
51
- ### Excluir Equipe
52
- **Método:** DELETE
53
- **Endpoint:** `/v3/team/:id`
326
+ ### `DELETE /v3/team/{id}` — excluir equipe (cascata)
327
+
328
+ `TeamManager.delete` (linhas 66-72) → `removeUsers(membros)` → `mongo.delete`:
329
+ - remove `principal {teamId:id}`
330
+ - remove `team {_id:id}`
331
+ - remove `team_player {teamId:id}`
332
+ - executa `reset(id)` (vide abaixo)
333
+
334
+ Resposta `204 NO_CONTENT`.
335
+
336
+ ### `DELETE /v3/team/{id}/reset` — **reset destrutivo**
337
+
338
+ `TeamManager.reset` → `TeamDaoMongo.reset` (linhas 37-46) apaga, tratando `id` como id de jogador, **8 coleções**:
339
+
340
+ | Coleção | Filtro |
341
+ |---|---|
342
+ | `action_log` | `{userId:id}` |
343
+ | `mystery_box_log` | `{player:id}` |
344
+ | `player_status` | `{_id:id}` |
345
+ | `achievement` | `{player:id}` |
346
+ | `challenge_progress` | `{player:id}` |
347
+ | `lottery_ticket` | `{player:id}` |
348
+ | `competition_join` | `{player:id}` |
349
+ | `folder_log` | `{player:id}` |
350
+
351
+ Resposta `200 OK`.
352
+
353
+ > **Atenção:** não há validação de que `id` é uma equipe. Se um `id` de **jogador** for passado, os dados de gamificação **desse jogador** são apagados. Vide anti-pattern em §10.
354
+
355
+ ### `GET /v3/team/{id}/member` — listar membros (objetos `Player`)
356
+
357
+ Resolve ids via `findUserIdsByTeamId` (manual **ou** dinâmico) e carrega cada `Player`. Custo O(n) de `findById` por membro.
358
+
359
+ ### `GET /v3/team/{id}/memberids` — listar ids dos membros
360
+
361
+ Mesma resolução, devolve apenas a lista de ids.
362
+
363
+ ### `GET /v3/team/{id}/member/add/{playerId}` — adicionar membro
364
+
365
+ `isUserOfTeam` → se não membro, `linkUser` (§2.3). Resposta `{team, player}`. **Não tem efeito útil em equipes dinâmicas** (membership é calculado; o `TeamLink` criado seria ignorado na resolução dinâmica).
366
+
367
+ ### `POST /v3/team/{id}/member/bulk` — adicionar membros em lote
368
+
369
+ Corpo: array de ids `["p1","p2"]`. Retorna `{team, members:[{member, status}]}` com `status ∈ {DONT_EXIST, MEMBER_ADDED, ALREADY_MEMBER}`. Status `201 CREATED`.
370
+
371
+ ### `GET /v3/team/{id}/member/remove/{playerId}` — remover membro
372
+
373
+ `isUserOfTeam` → se membro, `removeUsers([playerId], id)` (remove `TeamLink` + atualiza `player.teams`). Resposta `200 OK`.
374
+
375
+ ### Endpoints de membership no controller de player
376
+
377
+ São equivalentes funcionais aos de `team` (mesmos métodos de `TeamManager`):
378
+
379
+ | Método | Endpoint | Ação |
380
+ |---|---|---|
381
+ | GET | `/v3/player/{id}/team` | Lista equipes do jogador (`TeamManager.findTeamIdsByUserId` — **inclui dinâmicas**) |
382
+ | GET | `/v3/player/{id}/team/join/{teamId}` | `linkUser` (entrar) |
383
+ | GET | `/v3/player/{id}/team/unjoin/{teamId}` | `removeUsers` (sair) |
384
+
385
+ ---
386
+
387
+ ## 5. Regras de Negócio
388
+
389
+ Regras presentes no código e **não** evidentes pelo schema:
390
+
391
+ - **Membership manual vs dinâmico é exclusivo por equipe.** Se `dynamic.entity` e `dynamic.aggregate` estão definidos, a equipe **ignora** completamente `team_player`; vínculos manuais criados nessa equipe não têm efeito na resolução de membros.
392
+ - **`player.teams[]` é a fonte de verdade do membership manual.** `team_player` é reconstruído a partir dele em todo save de player (§2.4).
393
+ - **Equipe é um principal.** Pontos/desafios/itens da equipe vivem em `achievement` com `player = teamId`. Não há campo agregado de “pontos da equipe” no documento `team`.
394
+ - **Desafios de equipe só alcançam membros manuais** (assimetria de §2.5/§7).
395
+ - **Idempotência de criação:** `POST` é upsert; reenviar com mesmo `_id` substitui o documento (e zera `created` se omitido).
396
+ - **Sem update parcial:** a única forma de “editar” é re-POSTar o objeto completo.
397
+ - **Multi-tenant:** todo acesso é escopado pela `apiKey` do bearer (instância isolada de `ManagerFactory`/conexão Mongo). Não há vazamento entre organizações.
398
+ - **`name` propaga para `principal`:** em `update` (não exposto) e em saves de player, o nome do principal é mantido em sincronia; em `add`, o principal é (re)criado com o nome atual.
399
+
400
+ ---
401
+
402
+ ## 6. Comportamentos Automáticos
403
+
404
+ | Comportamento | Trigger | Impacto | Persistência |
405
+ |---|---|---|---|
406
+ | Geração de `_id` | `add` com id vazio | `Guid.shortTimeMillis()` | `team._id` |
407
+ | Criação do `Principal` espelho | `add` | Equipe vira principal `type=1` | coleção `principal` |
408
+ | `created`/`updated` automáticos | `add` | Datas preenchidas/reescritas | `team` |
409
+ | Sync `player.teams[]` → `team_player` | save de qualquer player | Reescreve vínculos manuais (ignora dinâmicas) | `team_player` |
410
+ | Recálculo de status do jogador | `linkUser`/`removeUsers` (via `insert`) | `updateUserStatus(playerId)` | `player_status` |
411
+ | Recursão de desafio de equipe | `fireAction` de um jogador | Achievements no nome da equipe (só membros manuais) | `achievement` |
412
+ | Cascata de delete | `DELETE /v3/team/{id}` | Remove principal+team+team_player e roda reset | múltiplas coleções |
413
+ | Reset destrutivo | `DELETE /v3/team/{id}/reset` ou delete | Apaga 8 coleções pelo id | múltiplas coleções |
414
+
415
+ ```mermaid
416
+ flowchart TB
417
+ A["DELETE /v3/team/{id}"] --> B["findUserIdsByTeamId(id)"]
418
+ B --> C["removeUsers(membros)<br/>limpa team_player + player.teams"]
419
+ C --> D["remove principal {teamId:id}"]
420
+ D --> E["remove team {_id:id}"]
421
+ E --> F["remove team_player {teamId:id}"]
422
+ F --> G["reset(id)"]
423
+ G --> H["apaga 8 colecoes:<br/>action_log, mystery_box_log, player_status,<br/>achievement, challenge_progress,<br/>lottery_ticket, competition_join, folder_log"]
424
+ ```
425
+
426
+ ---
427
+
428
+ ## 7. Suportado vs NÃO Suportado
429
+
430
+ ### ✅ Suportado
431
+
432
+ - CRUD de equipes (criar/substituir via POST, ler, listar paginado, excluir).
433
+ - Membership manual: add/remove individual, add em lote (`bulk`), via endpoints de team **ou** de player.
434
+ - Equipes dinâmicas (membros calculados por pipeline de aggregation).
435
+ - Equipe como principal de gamificação (pontos/desafios/itens/níveis via `achievement`).
436
+ - Desafios de equipe (`Challenge.teamChallenge = true`) para **membros manuais**.
437
+ - Status agregado da equipe (`/status`) e posições de leaderboard.
438
+ - Reset destrutivo e cascata de delete.
439
+
440
+ ### ❌ NÃO Suportado / armadilhas confirmadas no código
441
+
442
+ - **Update parcial / PUT.** `TeamManager.update` existe (remove+save + atualiza nome do principal) mas **não está ligado a nenhum endpoint** (nenhum chamador no repositório). “Editar” = re-POST full replace.
443
+ - **`TeamStatus` é código morto.** A classe e seu campo `joined` nunca são instanciados (`grep "new TeamStatus"` = 0 ocorrências). O endpoint `/status` devolve `PlayerStatus`.
444
+ - **Campo `owner` é decorativo.** Persistido e devolvido, mas **nenhum código o lê** — não confere permissão, propriedade nem qualquer comportamento.
445
+ - **Desafios de equipe não alcançam membros dinâmicos.** `AchievementManager.findTeamIdsByUserId` (linha 850) consulta só `team_player`; equipes dinâmicas ficam de fora da recursão de `fireAction`.
446
+ - **Sem `player_status` materializado para equipes.** `fireAction` tem `// TODO UPDATE TEAM STATUS` vazio; status só existe sob demanda via `calculateTeamStatus`.
447
+ - **`TeamDaoMongo.findByLikeName` está quebrado:** consulta `{team: {$regex:...}}`, mas o campo correto é `name` — sempre retorna vazio. Método **não usado** por nenhum endpoint.
448
+ - **`Team.equals` usa `==` em String** (`this.id == t.id`) — comparação por referência, não por valor. Não está em caminho crítico, mas é incorreto.
449
+ - **Coleção/fluxo `team_challenge` é legado.** `linkChallenge` é `@Deprecated`; `removeTeamsOfChallenge` filtra `{challengeId:#}` enquanto `TeamLink` só tem `linkId`/`teamId` — o filtro nunca casa. Bloco de métodos comentados em `TeamDaoMongo` (findTeamIdsByUserId, findByUserId, findByChallengeId, isUserInChallengeTeams, isUserOfTeam) foi movido para `TeamManager`.
450
+ - **`findAll`, `findLimited`, `findByLikeName` (variantes não paginadas)** não são chamados por nenhum endpoint — `GET /v3/team` usa exclusivamente `findAllPaginated`.
451
+ - **Sem hooks/triggers/webhooks próprios** do módulo team (criação/edição/exclusão de equipe não dispara eventos). Eventos só surgem indiretamente via achievements de desafio de equipe.
452
+
453
+ ---
454
+
455
+ ## 8. Segurança e Permissões
456
+
457
+ - **Autenticação:** todos os endpoints exigem `Authorization: Bearer <token>` (`AuthBean`). A `apiKey` extraída do token seleciona a instância de `FrontController`/`ManagerFactory`, isolando dados por **organização/tenant**.
458
+ - **Isolamento multi-tenant:** todas as queries rodam na conexão Mongo do tenant; não há cruzamento entre organizações.
459
+ - **Superfícies de injeção (requerem token válido, normalmente de administração):**
460
+ - `GET /v3/team?q=...` — o parâmetro `q` é **concatenado cru** no `$match` da aggregation (`TeamManager.java:105`). Um cliente autorizado pode injetar operadores arbitrários do Mongo.
461
+ - `Team.dynamic.aggregate` — é uma **string JSON de pipeline executada como aggregation** sobre `dynamic.entity` (coleção arbitrária). Quem cria/edita equipes dinâmicas controla um pipeline Mongo arbitrário (`findDynamicMemberIds`/`isDynamicMember`). Não há sanitização nem allowlist de coleções/operadores.
462
+ - **Operações destrutivas sem confirmação:**
463
+ - `DELETE /v3/team/{id}/reset` apaga 8 coleções **pelo id**, sem checar se o id é de equipe — pode apagar dados de um **jogador** homônimo.
464
+ - `DELETE /v3/team/{id}` remove a equipe e roda o mesmo reset.
465
+ - **Sem autorização granular por equipe:** o campo `owner` não é verificado; qualquer token com acesso ao endpoint pode editar/excluir qualquer equipe do tenant.
466
+
467
+ ---
468
+
469
+ ## 9. Observabilidade e Troubleshooting
470
+
471
+ ### Diagnóstico rápido
472
+
473
+ ```http
474
+ GET /v3/team/{id} # a equipe existe? confira _id, dynamic
475
+ GET /v3/team/{id}/memberids # quem o runtime considera membro (manual ou dinâmico)
476
+ GET /v3/team/{id}/status # a equipe acumulou pontos/desafios? (achievements com player=teamId)
477
+ GET /v3/player/{id}/team # de quais equipes o jogador participa (inclui dinâmicas)
478
+ ```
479
+
480
+ ### Queries MongoDB úteis
481
+
482
+ ```js
483
+ // Vínculos manuais de uma equipe
484
+ db.team_player.find({ teamId: "sales" })
485
+
486
+ // Equipes de um jogador (manual)
487
+ db.team_player.distinct("teamId", { linkId: "john" })
488
+
489
+ // O principal espelho existe? (necessário para desafios de equipe)
490
+ db.principal.findOne({ _id: "sales", type: 1 })
491
+
492
+ // Conquistas geradas pela equipe (desafios de equipe)
493
+ db.achievement.find({ player: "sales" })
494
+
495
+ // Todas as equipes dinâmicas
496
+ db.team.find({ "dynamic.entity": { $exists: true }, "dynamic.aggregate": { $exists: true } })
497
+ ```
498
+
499
+ ### Erros comuns e causas
500
+
501
+ | Sintoma | Causa provável |
502
+ |---|---|
503
+ | Equipe não ganha pontos em desafio de equipe | Equipe é **dinâmica** (recursão de `fireAction` só vê `team_player`); ou `Challenge.teamChallenge` não é `true`; ou falta o `principal` (recriar via re-POST). |
504
+ | `/memberids` vazio numa equipe dinâmica | `dynamic.aggregate` não retorna documentos com `_id`, ou `dynamic.entity` errada, ou JSON do pipeline inválido. |
505
+ | Membro adicionado “some” | Save de player sobrescreveu `player.teams[]` e re-sincronizou `team_player` (§2.4); ou a equipe é dinâmica e o `TeamLink` é ignorado. |
506
+ | `created` mudou após “editar” | Re-POST sem o campo `created` (full replace reescreve a data). |
507
+ | Dados de gamificação de um jogador sumiram | Alguém chamou `DELETE /v3/team/{id}/reset` com um id que coincide com o do jogador. |
508
+ | Filtro por nome não funciona via `findByLikeName` | Método legado quebrado (campo `team` em vez de `name`); use `GET /v3/team?name=...`. |
509
+
510
+ ---
511
+
512
+ ## 10. Exemplos Práticos
513
+
514
+ ### Exemplo mínimo (equipe manual)
515
+
516
+ ```http
517
+ POST /v3/team
518
+ Authorization: Bearer <token>
519
+ Content-Type: application/json
520
+
521
+ { "_id": "sales", "name": "Sales" }
522
+ ```
523
+ ```http
524
+ GET /v3/team/sales/member/add/john # adiciona "john"
525
+ GET /v3/team/sales/memberids # => ["john"]
526
+ ```
527
+
528
+ ### Exemplo avançado (equipe dinâmica)
529
+
530
+ Equipe cujos membros são, em tempo real, todos os jogadores com `extra.department == "sales"`:
531
+
532
+ ```http
533
+ POST /v3/team
534
+ Authorization: Bearer <token>
535
+ Content-Type: application/json
536
+
537
+ {
538
+ "_id": "sales_dynamic",
539
+ "name": "Sales (dinâmico)",
540
+ "description": "Todos os jogadores do departamento de vendas",
541
+ "image": { "small": {"url": "https://cdn/sales.png"} },
542
+ "extra": { "managedBy": "rule" },
543
+ "dynamic": {
544
+ "entity": "player",
545
+ "aggregate": "[{\"$match\":{\"extra.department\":\"sales\"}},{\"$project\":{\"_id\":1}}]"
546
+ }
547
+ }
548
+ ```
549
+
550
+ - `GET /v3/team/sales_dynamic/memberids` executa o pipeline e devolve os `_id` casados.
551
+ - **Não** adicione membros manualmente nessa equipe — o vínculo seria ignorado.
552
+ - Lembre-se: membros dinâmicos **não** disparam desafios de equipe (§2.5).
54
553
 
55
- ### Adicionar Membro
56
- **Método:** GET
57
- **Endpoint:** `/v3/team/:id/member/add/:player`
554
+ ### Exemplo de membership via player
555
+
556
+ ```http
557
+ GET /v3/player/john/team/join/sales # entra na equipe
558
+ GET /v3/player/john/team # lista equipes de john (inclui dinâmicas)
559
+ GET /v3/player/john/team/unjoin/sales # sai
560
+ ```
58
561
 
59
- ### Remover Membro
60
- **Método:** GET
61
- **Endpoint:** `/v3/team/:team_id/member/remove/:player_id`
562
+ ### Anti-pattern — `reset` confundido com “limpar membros”
62
563
 
63
- ### Listar IDs dos Membros
64
- **Método:** GET
65
- **Endpoint:** `/v3/team/:team_id/memberids`
564
+ ```http
565
+ # ERRADO: a intenção era "remover todos os membros da equipe"
566
+ DELETE /v3/team/john/reset
567
+ ```
568
+ Se `john` for (ou coincidir com) um **id de jogador**, este comando **apaga as conquistas, action_logs, status, tickets e progresso do jogador** em 8 coleções — irreversível. Para esvaziar uma equipe, remova membros individualmente (`/member/remove/{playerId}`) ou exclua a equipe (`DELETE /v3/team/{id}`), ciente de que o delete **também** roda o reset.
569
+
570
+ ### Anti-pattern — “editar” com PATCH mental
571
+
572
+ ```http
573
+ # Não existe PATCH. Isto NÃO atualiza só o nome — substitui o documento inteiro:
574
+ POST /v3/team
575
+ { "_id": "sales", "name": "Novo Nome" }
576
+ # Resultado: description, image, extra, owner e created (se omitido) são PERDIDOS/reescritos.
577
+ ```
578
+ Sempre reenvie o objeto **completo** ao re-POSTar.
66
579
 
67
- ### Consultar Status da Equipe
68
- **Método:** GET
69
- **Endpoint:** `/v3/team/:id/status`
580
+ ---
70
581
 
71
- ## Validações e Testes
582
+ ## Checklist de Configuração
72
583
 
73
- - [ ] Equipe aparece na lista GET /v3/team
74
- - [ ] Membros foram adicionados corretamente
75
- - [ ] Status da equipe reflete pontos e desafios dos membros
584
+ - [ ] `_id` definido (ou aceitar o gerado automaticamente).
585
+ - [ ] `name` preenchido (propaga para o `principal`).
586
+ - [ ] Para desafios de equipe: equipe é **manual** (`team_player`) — equipes **dinâmicas não recebem desafios de equipe**.
587
+ - [ ] Para desafios de equipe: existe `principal` com `type=1` (recriado via POST) e o `Challenge` tem `teamChallenge=true`.
588
+ - [ ] Equipe dinâmica: `dynamic.entity` aponta para coleção existente e `dynamic.aggregate` é JSON válido retornando documentos com `_id`.
589
+ - [ ] **Não** misture membership manual com `dynamic` na mesma equipe (manual é ignorado).
590
+ - [ ] Ao “editar”, reenviar o objeto **completo** (full replace) e incluir `created` se quiser preservá-lo.
591
+ - [ ] **Nunca** use `/{id}/reset` para esvaziar membros — ele apaga dados de gamificação do id (tratado como jogador).
592
+ - [ ] Não dependa de `owner` para autorização — o campo não tem efeito.
593
+ - [ ] `q` em `GET /v3/team` é critério Mongo cru — valide a entrada se exposto a clientes não confiáveis.