funifier-mcp 0.2.26 → 0.2.28

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 (181) 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/check-update.d.ts +5 -0
  67. package/dist/mcp/check-update.d.ts.map +1 -1
  68. package/dist/mcp/check-update.js +21 -10
  69. package/dist/mcp/check-update.js.map +1 -1
  70. package/dist/mcp/check-update.test.d.ts +2 -0
  71. package/dist/mcp/check-update.test.d.ts.map +1 -0
  72. package/dist/mcp/check-update.test.js +33 -0
  73. package/dist/mcp/check-update.test.js.map +1 -0
  74. package/dist/mcp/index.js +2 -2
  75. package/dist/mcp/index.js.map +1 -1
  76. package/dist/mcp/prompts/templates.d.ts.map +1 -1
  77. package/dist/mcp/prompts/templates.js +35 -0
  78. package/dist/mcp/prompts/templates.js.map +1 -1
  79. package/dist/mcp/resources/documentation.d.ts +1 -1
  80. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  81. package/dist/mcp/resources/documentation.js +39 -3
  82. package/dist/mcp/resources/documentation.js.map +1 -1
  83. package/dist/mcp/tools/connect.d.ts.map +1 -1
  84. package/dist/mcp/tools/connect.js +18 -8
  85. package/dist/mcp/tools/connect.js.map +1 -1
  86. package/dist/mcp/tools/database.d.ts.map +1 -1
  87. package/dist/mcp/tools/database.js +59 -47
  88. package/dist/mcp/tools/database.js.map +1 -1
  89. package/dist/mcp/tools/database.test.js +2 -2
  90. package/dist/mcp/tools/database.test.js.map +1 -1
  91. package/dist/mcp/tools/delete.d.ts.map +1 -1
  92. package/dist/mcp/tools/delete.js +13 -3
  93. package/dist/mcp/tools/delete.js.map +1 -1
  94. package/dist/mcp/tools/execute.d.ts.map +1 -1
  95. package/dist/mcp/tools/execute.js +20 -9
  96. package/dist/mcp/tools/execute.js.map +1 -1
  97. package/dist/mcp/tools/folder.d.ts.map +1 -1
  98. package/dist/mcp/tools/folder.js +22 -12
  99. package/dist/mcp/tools/folder.js.map +1 -1
  100. package/dist/mcp/tools/get.d.ts.map +1 -1
  101. package/dist/mcp/tools/get.js +16 -6
  102. package/dist/mcp/tools/get.js.map +1 -1
  103. package/dist/mcp/tools/index.d.ts +1 -1
  104. package/dist/mcp/tools/index.d.ts.map +1 -1
  105. package/dist/mcp/tools/index.js +28 -1
  106. package/dist/mcp/tools/index.js.map +1 -1
  107. package/dist/mcp/tools/list.d.ts.map +1 -1
  108. package/dist/mcp/tools/list.js +38 -14
  109. package/dist/mcp/tools/list.js.map +1 -1
  110. package/dist/mcp/tools/logs.d.ts.map +1 -1
  111. package/dist/mcp/tools/logs.js +15 -5
  112. package/dist/mcp/tools/logs.js.map +1 -1
  113. package/dist/mcp/tools/save.d.ts.map +1 -1
  114. package/dist/mcp/tools/save.js +14 -4
  115. package/dist/mcp/tools/save.js.map +1 -1
  116. package/dist/mcp/tools/save.test.js +3 -3
  117. package/dist/mcp/tools/save.test.js.map +1 -1
  118. package/dist/mcp/tools/search-docs.d.ts +3 -0
  119. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  120. package/dist/mcp/tools/search-docs.js +102 -0
  121. package/dist/mcp/tools/search-docs.js.map +1 -0
  122. package/package.json +6 -2
  123. package/skills/acquire-funifier-knowledge/SKILL.md +155 -0
  124. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  125. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  126. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  127. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  128. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  129. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  130. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +86 -0
  131. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  132. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  133. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  134. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  135. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  136. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  137. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  138. package/skills/funifier/SKILL.md +88 -0
  139. package/skills/funifier/references/configure-security.md +96 -0
  140. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  141. package/skills/funifier/references/create-aggregate.md +144 -0
  142. package/skills/funifier/references/create-challenge.md +116 -0
  143. package/skills/funifier/references/create-competition.md +98 -0
  144. package/skills/funifier/references/create-crossword.md +574 -0
  145. package/skills/funifier/references/create-custom-object.md +91 -0
  146. package/skills/funifier/references/create-custom-page.md +135 -0
  147. package/skills/funifier/references/create-folder.md +104 -0
  148. package/skills/funifier/references/create-lastmile.md +643 -0
  149. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  150. package/skills/funifier/references/create-level.md +94 -0
  151. package/skills/funifier/references/create-lottery.md +913 -0
  152. package/skills/funifier/references/create-mystery.md +769 -0
  153. package/skills/funifier/references/create-notification.md +75 -0
  154. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  155. package/skills/funifier/references/create-quiz.md +98 -0
  156. package/skills/funifier/references/create-scheduler.md +141 -0
  157. package/skills/funifier/references/create-story.md +636 -0
  158. package/skills/funifier/references/create-swap.md +95 -0
  159. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  160. package/skills/funifier/references/create-virtual-good.md +96 -0
  161. package/skills/funifier/references/create-webhook.md +72 -0
  162. package/skills/funifier/references/create-websocket.md +71 -0
  163. package/skills/funifier/references/create-widget.md +76 -0
  164. package/skills/funifier/references/debug.md +87 -0
  165. package/skills/funifier/references/help.md +81 -0
  166. package/skills/funifier/references/implement-frontend.md +106 -0
  167. package/skills/funifier/references/import-csv.md +75 -0
  168. package/skills/funifier/references/manage-player.md +82 -0
  169. package/skills/funifier/references/manage-team.md +76 -0
  170. package/skills/funifier/references/upload-file.md +91 -0
  171. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  172. package/skills/funifier-create-challenge/SKILL.md +0 -88
  173. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  174. package/skills/funifier-create-level/SKILL.md +0 -87
  175. package/skills/funifier-create-quiz/SKILL.md +0 -87
  176. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  177. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  178. package/skills/funifier-debug/SKILL.md +0 -92
  179. package/skills/funifier-help/SKILL.md +0 -86
  180. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  181. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,83 +1,565 @@
1
- # Level (Nível)
1
+ # `level`
2
2
 
3
3
  **Acesso Studio:** `/studio/level`
4
4
  **API Endpoint:** `/v3/level`
5
+ **Coleção MongoDB:** `level` (configuração global em `level_config`)
5
6
 
6
- ## O que é
7
+ ---
7
8
 
8
- Sistema de progressão por níveis. Permite definir a progressão dos jogadores com base em pontos acumulados ou outros critérios, atribuindo títulos ou benefícios a cada nível. Pode-se exigir desafios específicos para desbloquear certos níveis.
9
+ ## 1. Visão Geral
9
10
 
10
- ## Quando usar
11
+ O módulo `level` define a **escada de progressão** de um jogo Funifier: uma lista ordenada de níveis que o jogador conquista à medida que acumula pontos. Cada documento da coleção `level` é uma **definição estática** (configuração do designer); o nível efetivamente alcançado por um jogador **não é gravado no documento do jogador**, e sim derivado da coleção `achievement`.
11
12
 
12
- - Para criar senso de progressão e conquista
13
- - Para desbloquear funcionalidades por nível
14
- - Para segmentar jogadores por experiência
15
- - Exemplos: Júnior → Pleno → Sênior; faixas coloridas
13
+ Pontos arquiteturais que diferem da intuição REST/CRUD:
16
14
 
17
- ## Dependências
15
+ - O módulo `level` em si (`LevelRest` + `LevelManager` + `LevelDaoMongo`) é apenas **CRUD da definição de níveis**. Ele não contém o motor de promoção.
16
+ - O **motor de level-up real vive em `AchievementManager`** — métodos `evaluateLevelUp`, `findLatestLevelAchieved` e `updatePlayerStatus`. É lá que se decide quando o jogador sobe de nível, não no `LevelManager`.
17
+ - O nível atual de um jogador é o **achievement mais recente do tipo `LEVEL`** (`Achievement.TYPE_LEVEL = 3`). Níveis nunca são removidos/regredidos (comentário do código: *"challenges and levels will never be deducted to avoid problems"*).
18
+ - O snapshot de progresso (`LevelProgress`) é materializado dentro de `player_status.level_progress`, recalculado a cada `updatePlayerStatus`.
18
19
 
19
- - **Point**: a categoria de ponto usada para progressão deve existir antes
20
- - **Configuração global**: definir qual ponto controla os níveis via `/v3/database/level_config`
20
+ Relação com outros módulos:
21
21
 
22
- ## Checklist de Configuração no Studio
22
+ - **`achievement`** fonte da verdade. Gera o achievement `TYPE_LEVEL` na promoção e soma os pontos (`sumTotalRewards`) que disparam a subida. `updatePlayerStatus` é o ponto onde o nível é avaliado e materializado.
23
+ - **`point` / `point_category`** — a progressão consome o total de pontos. A categoria considerada depende de `level_config.pointCategory` (global) ou da categoria embutida no nível via `getPoint()`.
24
+ - **`challenge` (Requirement)** — o array `requirements` de um nível reusa a classe `Requirement` para exigir pré-condições (desafios, items, pontos) antes de liberar a promoção.
25
+ - **`trigger`** — eventos `before_win` / `after_win` na entidade `level` são disparados em torno do registro da promoção.
26
+ - **`notification`** — `NotificationDefinition[]` do nível com evento `EVENT_WIN` gera notificação na promoção.
27
+ - **`technique` (GameTechniqueManager)** — usa `level_config` e `getPoint()` para desenhar as ligações ponto → nível ("level up") no mapa do jogo; código de técnica do módulo = **GT85**.
28
+ - **`player_status`** — coleção materializada que guarda `level_progress` por jogador.
23
29
 
24
- - [ ] Definir _id do nível (ex: L0, L1, L2)
25
- - [ ] Definir nome do nível (ex: "Apprentice", "Master")
26
- - [ ] Definir descrição motivadora
27
- - [ ] Definir minPoints (pontuação mínima crescente)
28
- - [ ] Definir position (ordem sequencial, iniciando em 0)
29
- - [ ] Configurar categoria de ponto global (level_config)
30
+ ---
30
31
 
31
- ## API Endpoints
32
+ ## 2. Arquitetura e Fluxos
32
33
 
33
- ### Listar Níveis
34
- **Método:** GET
35
- **Endpoint:** `/v3/level`
34
+ ### 2.1 Classes envolvidas
36
35
 
37
- ### Criar Nível
38
- **Método:** POST
39
- **Endpoint:** `/v3/level`
36
+ | Classe | Papel |
37
+ |---|---|
38
+ | `com.funifier.engine.level.Level` | Entidade/POJO — documento raiz da coleção `level` |
39
+ | `com.funifier.engine.level.LevelConfig` | Configuração global (`level_config`, `_id: "global"`) — define `pointCategory` |
40
+ | `com.funifier.engine.level.LevelProgress` | DTO de progresso, **não persiste sozinho** — embutido em `player_status.level_progress` |
41
+ | `com.funifier.engine.level.LevelManager` | CRUD + queries auxiliares; **não promove jogadores** |
42
+ | `com.funifier.engine.level.LevelDaoMongo` | Acesso MongoDB direto (Jongo) à coleção `level` |
43
+ | `com.funifier.rest.v3.rest.LevelRest` | Controller REST v3 (`/v3/level`) |
44
+ | `com.funifier.engine.achievement.AchievementManager` | **Motor real de level-up** (`evaluateLevelUp`, `findLatestLevelAchieved`, `updatePlayerStatus`, `findPlayerStatusByLevel`) |
45
+ | `com.funifier.engine.challenge.Requirement` | Pré-requisitos de promoção (reusada de challenge) |
40
46
 
41
- **Exemplo de Body:**
47
+ > A configuração `level_config` **não tem endpoint próprio** no `LevelRest`. Ela é manipulada pelo módulo genérico de banco/custom-object (`/v3/database/level_config`). `LevelManager.findConfig()` apenas a lê (`_id: "global"`).
48
+
49
+ ### 2.2 Pipeline principal — promoção de nível
50
+
51
+ O motor não é acionado pelo `LevelRest`. Ele roda dentro de `AchievementManager.updatePlayerStatus(player, jongo)`, que é chamado **após qualquer registro de achievement** (ex.: ganho de pontos via `fireAction` linha 805/861, compras em `CatalogManager`, trocas em `SwapManager`).
52
+
53
+ Sequência real (linhas 1012-1055 de `AchievementManager.java`):
54
+
55
+ ```
56
+ [1] while(evaluateLevelUp(player, jongo, achievements)); // promove 1 nível por iteração até não haver mais
57
+ [2] level = findLatestLevelAchieved(player, jongo); // nível atual = último achievement TYPE_LEVEL
58
+ [3] next_level = clevel.findOne("{position: {$gte: #}}", level.position+1).orderBy("{position:1}")
59
+ // se não há nível atual → primeiro nível por position asc
60
+ [4] total_levels = clevel.count();
61
+ [5] calcula percent_completed e next_points (ver 2.3)
62
+ [6] status.level_progress = new LevelProgress(level, percent, next_level, next_points, total_levels);
63
+ [7] player_status: remove({_id: player}) + save(status) // full replace do snapshot
64
+ ```
65
+
66
+ Detalhe de `evaluateLevelUp` (linhas 1132-1199) — avalia **uma** promoção:
67
+
68
+ ```
69
+ currentLevel = findLatestLevelAchieved(player) // pode ser null (jogador sem nível)
70
+ nextLevel = primeiro level com position >= currentLevel.position+1 (asc)
71
+ // se currentLevel == null → primeiro level por position asc
72
+ config = level_config (_id: "global")
73
+ pointCategory = (config.pointCategory não-vazio) ? config.pointCategory : null
74
+ points = sumTotalRewards(player, TYPE_POINT, pointCategory)
75
+ // pointCategory == null → soma TODOS os pontos do jogador
76
+
77
+ se nextLevel != null E points >= nextLevel.minPoints:
78
+ se evaluateRequirements(nextLevel.requirements, player, jongo): // ver 2.4
79
+ trigger.execute(level, EVENT_BEFORE_WIN)
80
+ addAchievement( Achievement(TYPE_LEVEL, item = nextLevel._id) ) // registra promoção
81
+ trigger.execute(level, EVENT_AFTER_WIN)
82
+ envia NotificationDefinition(EVENT_WIN) do nível
83
+ retorna true // → o while volta e tenta o PRÓXIMO nível
84
+ retorna false // → encerra o loop
85
+ ```
86
+
87
+ Consequência do `while`: se o jogador ganha pontos suficientes para pular vários níveis de uma vez, **cada nível intermediário gera seu próprio achievement, trigger e notificação** — ele "passa" por todos, não salta direto para o final.
88
+
89
+ ### Fluxo de promoção — `evaluateLevelUp` (loop de `updatePlayerStatus`)
90
+
91
+ ```mermaid
92
+ flowchart TD
93
+ A[updatePlayerStatus] --> B{evaluateLevelUp}
94
+ B --> C[currentLevel = findLatestLevelAchieved]
95
+ C --> D["nextLevel = level position >= current.position+1<br/>(ou 1º level se sem nível)"]
96
+ D --> E["pointCategory = level_config.pointCategory<br/>(ou null = todos os pontos)"]
97
+ E --> F["points = sumTotalRewards(TYPE_POINT, pointCategory)"]
98
+ F --> G{nextLevel != null<br/>E points >= nextLevel.minPoints?}
99
+ G -- não --> H[retorna false → fim do loop]
100
+ G -- sim --> I{evaluateRequirements<br/>nextLevel.requirements?}
101
+ I -- não --> H
102
+ I -- sim --> J[trigger before_win]
103
+ J --> K["addAchievement TYPE_LEVEL<br/>item = nextLevel._id"]
104
+ K --> L[trigger after_win + notificação EVENT_WIN]
105
+ L --> M[retorna true → tenta próximo nível]
106
+ M --> B
107
+ H --> N[calcula LevelProgress e grava player_status]
108
+ ```
109
+
110
+ ### 2.3 Cálculo de `LevelProgress` (percent / next_points)
111
+
112
+ Trecho real (linhas 1020-1055). Define quanto falta para o próximo nível:
113
+
114
+ ```
115
+ percent_completed = 100 // default quando não há próximo nível
116
+ next_points = 0
117
+
118
+ se next_level != null:
119
+ point1 = (level != null) ? level.getPoint() : null
120
+ point2 = next_level.getPoint()
121
+ // só usa total por categoria se AMBOS os níveis apontam para a MESMA categoria
122
+ total_points = (point1 != null && point2 != null && point1.equals(point2))
123
+ ? sumTotalRewards(TYPE_POINT, point1)
124
+ : status.total_points // senão: total global de pontos
125
+
126
+ next_points = max(0, next_level.minPoints - total_points)
127
+
128
+ se level != null:
129
+ diff_total = total_points - level.minPoints
130
+ diff_next = next_level.minPoints - level.minPoints
131
+ senão: // jogador ainda sem nível → base 0
132
+ diff_total = total_points
133
+ diff_next = next_level.minPoints
134
+
135
+ percent_completed = (total_points <= next_level.minPoints)
136
+ ? round2( (diff_total / diff_next) * 100 )
137
+ : 100
138
+ ```
139
+
140
+ `percent_completed` é arredondado para 2 casas decimais (`ParserUtil.setDoubleDecimalScale(x, 2)`).
141
+
142
+ ### 2.4 Avaliação de `requirements` no level-up
143
+
144
+ `AchievementManager.evaluateRequirements(Requirement[] all, String player, Jongo jongo)` (linhas 2584-2601) é o validador usado pelo level-up:
145
+
146
+ ```
147
+ para cada r em requirements:
148
+ period = DateUtil.fromKeywordPeriodExpression(r.period)
149
+ total = sumTotalRewards(player, r.type, r.item, period)
150
+ se r.total > total: retorna false // requisito não atingido
151
+ retorna true
152
+ ```
153
+
154
+ **Comportamento silencioso crítico:** esta variante **ignora `r.operation`**. Não existe dedução — todo requirement de um nível é tratado como `VERIFY` (apenas confere se `total >= r.total`). Configurar `operation: 1 (DEDUCT)` num requirement de nível **não deduz nada**; comporta-se como `VERIFY`.
155
+
156
+ ### 2.5 Interação entre módulos na promoção
157
+
158
+ ```mermaid
159
+ sequenceDiagram
160
+ participant Act as ActionManager / Catalog / Swap
161
+ participant AM as AchievementManager
162
+ participant Lvl as coleção level / level_config
163
+ participant Ach as coleção achievement
164
+ participant Trg as TriggerManager
165
+ participant Ntf as NotificationManager
166
+
167
+ Act->>AM: updatePlayerStatus(player)
168
+ loop até não promover
169
+ AM->>Ach: sumTotalRewards(TYPE_POINT, pointCategory)
170
+ AM->>Lvl: lê level_config + próximo nível (position)
171
+ AM->>Ach: evaluateRequirements (sumTotalRewards por requirement)
172
+ alt points >= minPoints e requirements ok
173
+ AM->>Trg: execute(level, before_win)
174
+ AM->>Ach: addAchievement(TYPE_LEVEL, nextLevel._id)
175
+ AM->>Trg: execute(level, after_win)
176
+ AM->>Ntf: send(EVENT_WIN)
177
+ end
178
+ end
179
+ AM->>AM: monta LevelProgress
180
+ AM->>Ach: player_status.save(level_progress)
181
+ ```
182
+
183
+ ### 2.6 CRUD da definição (síncrono)
184
+
185
+ `LevelDaoMongo` (coleção fixa `"level"`):
186
+
187
+ - `add(level)` → se `_id` ausente/vazio gera `Guid.newShortGuid()`, depois `c.save(level)` (**upsert** por `_id`).
188
+ - `update(level)` → `c.remove({_id: level._id})` **seguido de** `c.save(level)` → **full replace** (remove e regrava o documento inteiro; não há merge/patch).
189
+ - `delete(id)` → `c.remove({_id: id})`.
190
+ - `findById(id)` / `findAll()` → `findAll` ordena por `{position: 1}`.
191
+
192
+ `LevelManager.updateAllPositions()` (linhas 32-42): carrega **todos** os níveis ordenados por `{minPoints: 1}` e reatribui `position = índice` (0-based), salvando cada um — realinha `position` à ordem de `minPoints`.
193
+
194
+ ---
195
+
196
+ ## 3. Estrutura dos Objetos
197
+
198
+ ### 3.1 `Level` — documento raiz (coleção `level`)
199
+
200
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
201
+ |---|---|---|---|---|
202
+ | `_id` | String | auto (`Guid.newShortGuid()`) | — | Identificador. Gerado no POST se ausente/vazio. |
203
+ | `level` | String | — | Recomendado | Nome/título do nível (ex.: "Apprentice"). É o campo exibido como título no Studio (`GameManager`). |
204
+ | `position` | int | 0 | Sim (na prática) | Ordem do nível. **Governa a navegação "próximo nível"** no motor (não o `minPoints`). |
205
+ | `description` | String | — | Não | Descrição livre. |
206
+ | `minPoints` | int | 0 | Sim (na prática) | Limiar de pontos para promoção a este nível. |
207
+ | `image` | Image | — | Não | Imagem; estrutura `{small, medium, original}` cada um com `url` (vide exemplo do `LevelRest`). |
208
+ | `notifications` | NotificationDefinition[] | null | Não | Notificações disparadas; só as de `event = EVENT_WIN (0)` são usadas na promoção. |
209
+ | `requirements` | Requirement[] | null | Não | Pré-condições de promoção (ver 3.2). Tratadas sempre como `VERIFY`. |
210
+ | `i18n` | Map<String, Map<String,String>> | `{}` | Não | Traduções. Campo público, persistido como veio. |
211
+ | `extra` | Map<String, Object> | `{}` | Não | Campos arbitrários do designer. Persistido. |
212
+ | `techniques` | List\<String\> | `null` | Não | Códigos de técnica de jogo (GT). Ver 3.4. |
213
+
214
+ **Campo computado `point` (getter `getPoint()`):**
215
+
216
+ Não existe campo `point` armazenável diretamente. O getter `Level.getPoint()` (linhas 39-49) **deriva** a categoria de ponto do nível:
217
+
218
+ ```
219
+ se requirements tem EXATAMENTE 1 item
220
+ E requirements[0].type == TYPE_POINT (0)
221
+ E requirements[0].operation == OPERATION_VERIFY (0)
222
+ E requirements[0].total == minPoints:
223
+ point = requirements[0].item // a categoria de ponto
224
+ senão:
225
+ point = null
226
+ ```
227
+
228
+ Como Jackson serializa todo getter público sem `@JsonIgnore`, a propriedade `point` **aparece nas respostas REST** e **é gravada no documento Mongo** como snapshot do cálculo. Não há `setPoint` → um `point` enviado no corpo de um POST é **silenciosamente descartado** (regenerado a partir de `requirements`).
229
+
230
+ **Campos removidos/legados:**
231
+
232
+ - `apiKey` (linha 20) — **comentado no código** (legado: vincularia o nível a uma licença específica). Não existe em runtime.
233
+ - `getPoint()` não-nulo só ocorre quando o nível tem exatamente 1 requirement de ponto cujo `total` casa com `minPoints`; em qualquer outra configuração de `requirements`, `point` é `null`.
234
+
235
+ ### 3.2 `Requirement` (subentidade de `requirements`)
236
+
237
+ | Campo | Tipo | Descrição |
238
+ |---|---|---|
239
+ | `total` | int | Quantidade mínima exigida (no level, sempre comparada como `>=`). |
240
+ | `type` | int | Tipo do item exigido (ver enum abaixo). |
241
+ | `item` | String | Id do item exigido (challenge_id, catalog_item_id, point_category…). |
242
+ | `operation` | int | `0 VERIFY` / `1 DEDUCT`. **Ignorado no level-up** (sempre VERIFY). |
243
+ | `period` | String | Expressão de período (ex.: `-5h;-0h`) para limitar o somatório. |
244
+ | `extra`, `folder`, `restrict`, `perPlayer` | — | Demais campos da classe; não usados pelo fluxo de level. |
245
+
246
+ Enum `type` (`Requirement`): `0 POINT`, `1 CHALLENGE`, `2 CATALOG_ITEM`, `3 LEVEL`, `4 CROWN`, `5 LOTTERY`, `6 MYSTERY_BOX`, `7 CHARACTER_STAR_STAT`, `50 LOTTERY_TICKET`.
247
+
248
+ ### 3.3 `LevelConfig` — configuração global (coleção `level_config`)
249
+
250
+ | Campo | Tipo | Padrão | Descrição |
251
+ |---|---|---|---|
252
+ | `_id` | String | `"global"` | Único documento de config; o motor sempre lê `_id: "global"`. |
253
+ | `pointCategory` | String | null | Categoria de ponto que controla a progressão. Se vazio/null → progressão usa o **total de todos os pontos**. |
254
+
255
+ ### 3.4 `LevelProgress` — snapshot embutido em `player_status.level_progress`
256
+
257
+ Os nomes JSON vêm dos getters (não dos campos):
258
+
259
+ | Propriedade JSON | Campo Java | Tipo | Descrição |
260
+ |---|---|---|---|
261
+ | `level` | `level` | Level | Nível atual (null se o jogador ainda não conquistou nenhum). |
262
+ | `percent` | `percent_completed` | double | % concluído rumo ao próximo nível (2 casas). 100 se não há próximo. |
263
+ | `next_level` | `next_level` | Level | Próximo nível por `position`; null no topo da escada. |
264
+ | `next_points` | `next_points` | int | Pontos faltantes para o próximo nível (`max(0, …)`). |
265
+ | `total_levels` | `total_levels` | int | Total de documentos na coleção `level`. |
266
+
267
+ ### 3.5 Técnicas de jogo (`techniques`)
268
+
269
+ O único código associado ao módulo `level` é:
270
+
271
+ | Código GT | Significado operacional |
272
+ |---|---|
273
+ | `GT85` | **Level up** — técnica de gamificação "níveis/progressão". Atribuída automaticamente a níveis sem `techniques` por `GameTechniqueManager.autoConfigureMissingTechniqueFields()` (constante `LEVEL_CODE = "GT85"`). |
274
+
275
+ > O campo `techniques` é uma `List<String>` livre; o sistema só **autoinjeta** `GT85` quando o nível não tem técnica alguma. Outros códigos GT pertencem a outros módulos (ex.: `GT01` point, `GT35` challenge, `GT03` leaderboard).
276
+
277
+ ### 3.6 Ciclo de vida do nível de um jogador
278
+
279
+ ```mermaid
280
+ stateDiagram-v2
281
+ [*] --> SemNivel: jogador novo (nenhum achievement TYPE_LEVEL)
282
+ SemNivel --> Nivel0: points >= level[pos=0].minPoints + requirements
283
+ Nivel0 --> Nivel1: points >= próximo.minPoints + requirements
284
+ Nivel1 --> NivelN: ... (1 promoção por iteração do while)
285
+ NivelN --> NivelN: nunca regride (levels não são deduzidos)
286
+ NivelN --> [*]: topo da escada (next_level == null)
287
+ ```
288
+
289
+ ---
290
+
291
+ ## 4. Endpoints
292
+
293
+ Base: `@Path("v3/level")`, `Produces: application/json; charset=UTF-8`. Autenticação: `@BeanParam AuthBean` (Bearer token) em todos.
294
+
295
+ ### `GET /v3/level`
296
+
297
+ | Aspecto | Detalhe |
298
+ |---|---|
299
+ | Finalidade | Lista todos os níveis. |
300
+ | Implementação | `LevelManager.findAll()` → ordenado por `position` asc. |
301
+ | Resposta | Array de `Level`; campos nulos removidos (`JsonUtil.toJsonRemoveNullFields`). |
302
+
303
+ ### `GET /v3/level/{id}`
304
+
305
+ | Aspecto | Detalhe |
306
+ |---|---|
307
+ | Finalidade | Busca um nível por `_id`. |
308
+ | Resposta | Objeto `Level` (200) ou objeto vazio se não encontrado. |
309
+
310
+ ### `POST /v3/level`
311
+
312
+ | Aspecto | Detalhe |
313
+ |---|---|
314
+ | Finalidade | **Cria OU atualiza** um nível. |
315
+ | Full replace ou patch | **Full replace** — `c.save()` faz upsert por `_id`. |
316
+ | Status | `201 CREATED`. |
317
+
318
+ **Comportamento real:**
319
+ - Sem `_id` no corpo → gera `_id` curto automático.
320
+ - Com `_id` existente → **sobrescreve o documento inteiro** (não é merge). Campos omitidos são perdidos.
321
+ - **Não existe `PUT /v3/level/{id}`** — a atualização de um nível é feita por este POST com `_id` preenchido.
322
+ - `point` enviado no corpo é descartado (recalculado de `requirements`).
323
+
324
+ **Exemplo (do javadoc do `LevelRest`):**
42
325
  ```json
326
+ POST /v3/level
43
327
  {
44
- "level": "Apprentice",
45
- "position": 0,
46
- "description": "It indicates that you are learning and growing.",
47
- "minPoints": 10,
48
- "notifications": [],
49
- "requirements": [],
50
- "i18n": {},
51
- "extra": {},
52
- "techniques": ["GT85"],
53
- "_id": "L0"
328
+ "level": "Level",
329
+ "description": "Level Description",
330
+ "position": 1,
331
+ "minPoints": 100,
332
+ "image": {
333
+ "small": { "url": "http://funifier.com/level.png" },
334
+ "medium": { "url": "http://funifier.com/level.png" },
335
+ "original": { "url": "http://funifier.com/level.png" }
336
+ }
54
337
  }
55
338
  ```
56
339
 
57
- ### Excluir Nível
58
- **Método:** DELETE
59
- **Endpoint:** `/v3/level/:id`
340
+ ### `PUT /v3/level/position`
341
+
342
+ | Aspecto | Detalhe |
343
+ |---|---|
344
+ | Finalidade | Recalcula `position` de todos os níveis ordenando por `minPoints` asc. |
345
+ | Implementação | `LevelManager.updateAllPositions()` → `position = índice` (0-based). |
346
+ | Corpo | Nenhum (não recebe payload). |
347
+ | Resposta | `201 CREATED` `{ "status": "OK" }`. |
348
+
349
+ ### `DELETE /v3/level/{id}`
350
+
351
+ | Aspecto | Detalhe |
352
+ |---|---|
353
+ | Finalidade | Remove a definição do nível. |
354
+ | Resposta | `204 NO_CONTENT`. |
355
+ | Efeito colateral | **Nenhum** sobre achievements/jogadores. Achievements `TYPE_LEVEL` que apontam para o `_id` removido permanecem; `findLatestLevelAchieved` pode retornar um nível inexistente (ver §5). |
356
+
357
+ > **Não exposto via `/v3/level`:** gestão de `level_config`. Use o módulo de banco: `PUT /v3/database/level_config` com `{ "_id": "global", "pointCategory": "<categoria>" }`.
358
+
359
+ ---
360
+
361
+ ## 5. Regras de Negócio
362
+
363
+ Regras que existem no código mas não no schema:
364
+
365
+ 1. **`position` define a ordem, `minPoints` define o limiar.** O motor escolhe o "próximo nível" por `{position: {$gte: atual.position+1}}` ordenado por `position`. O gatilho de promoção compara `points >= nextLevel.minPoints`. Se `position` e `minPoints` ficarem **desalinhados** (ex.: o nível com `position` seguinte tem `minPoints` menor que o atual), o jogador pode subir de forma inesperada ou um nível pode ficar inalcançável. Use `PUT /v3/level/position` para realinhar.
366
+
367
+ 2. **Categoria de ponto da progressão:**
368
+ - `level_config.pointCategory` definido → conta apenas aquela categoria.
369
+ - vazio/ausente → soma **todos** os pontos (`sumTotalRewards(..., null)`).
370
+ - O `LevelConfig` é **global por tenant**; não há configuração por nível além do truque do `getPoint()`.
371
+
372
+ 3. **`getPoint()` por nível** só vale quando o nível tem exatamente 1 requirement de ponto com `total == minPoints`. O `percent_completed` só usa esse total por categoria quando **ambos** (atual e próximo) apontam para a **mesma** categoria; caso contrário usa o total global.
373
+
374
+ 4. **Promoção é monotônica e em cascata.** Níveis nunca regridem; um único `updatePlayerStatus` pode promover vários níveis em sequência (um achievement/trigger/notificação por nível).
375
+
376
+ 5. **Requirements são sempre VERIFY** no contexto de nível (operação ignorada). Não há dedução de pontos/itens ao subir de nível.
377
+
378
+ 6. **`findPlayerStatusByLevel(Level level)`** consulta `player_status` com `{level_progress.level: <objeto Level inteiro>}` — é uma **igualdade de objeto embutido completo**, frágil: qualquer divergência de campo (ex.: `point` recalculado, `image`, `extra`) faz o match falhar.
379
+
380
+ 7. **Consistência eventual:** o nível só muda quando `updatePlayerStatus` roda (após ganho de pontos, compra, troca, etc.). Alterar `minPoints` de um nível **não** rebaixa nem promove jogadores retroativamente até o próximo `updatePlayerStatus` daquele jogador.
381
+
382
+ 8. **Multi-tenant:** todo acesso passa por `FrontController.getInstance(apiKey)` → `ManagerFactory` → conexão Jongo do banco do tenant. Níveis e `level_config` são isolados por organização.
383
+
384
+ ---
385
+
386
+ ## 6. Comportamentos Automáticos
387
+
388
+ | Comportamento | Trigger | Impacto | Persistência |
389
+ |---|---|---|---|
390
+ | Geração de `_id` | POST sem `_id` | Cria GUID curto | Sim (`level`) |
391
+ | Promoção de nível | `updatePlayerStatus` após ganho de pontos | Registra achievement `TYPE_LEVEL` | Sim (`achievement`) |
392
+ | Trigger `before_win`/`after_win` | Em torno de cada promoção (entidade `level`) | Executa triggers configurados | Conforme trigger |
393
+ | Notificação de nível | Promoção, `NotificationDefinition` com `EVENT_WIN` | Envia notificação ao jogador | Conforme notification |
394
+ | Recalculo de `LevelProgress` | Todo `updatePlayerStatus` | Atualiza `player_status.level_progress` | Sim (`player_status`, full replace) |
395
+ | Snapshot `point` | `c.save(level)` (POST) | Grava propriedade derivada `point` | Sim (`level`) |
396
+ | Autoinjeção `GT85` | `autoConfigureMissingTechniqueFields()` | Adiciona técnica a níveis sem `techniques` | Sim (`level`) |
397
+
398
+ ### Encadeamento na promoção
399
+
400
+ ```mermaid
401
+ flowchart LR
402
+ P[Ganho de pontos] --> U[updatePlayerStatus]
403
+ U --> E{evaluateLevelUp:<br/>points >= minPoints<br/>e requirements?}
404
+ E -- sim --> BW[trigger before_win]
405
+ BW --> AC[achievement TYPE_LEVEL]
406
+ AC --> AW[trigger after_win]
407
+ AW --> NT[notificação EVENT_WIN]
408
+ NT --> E
409
+ E -- não --> LP[grava level_progress<br/>em player_status]
410
+ ```
411
+
412
+ ---
413
+
414
+ ## 7. Suportado vs NÃO Suportado
415
+
416
+ ### ✅ Suportado
417
+
418
+ - CRUD da definição de níveis (`GET`, `GET /{id}`, `POST`, `DELETE /{id}`).
419
+ - Reordenação automática por `minPoints` (`PUT /v3/level/position`).
420
+ - Progressão automática por pontos (categoria global ou total).
421
+ - Pré-requisitos de promoção via `requirements` (como VERIFY).
422
+ - Triggers `before_win`/`after_win` e notificações `EVENT_WIN` por nível.
423
+ - Materialização de progresso (`percent`, `next_points`, `next_level`, `total_levels`) em `player_status`.
424
+ - i18n e `extra` arbitrários.
425
+ - Isolamento multi-tenant.
426
+
427
+ ### ❌ NÃO Suportado / Armadilhas
428
+
429
+ - **`PUT /v3/level/{id}` não existe** — atualização só via `POST` (full replace).
430
+ - **`POST` não faz patch** — campos omitidos são apagados do documento.
431
+ - **`operation` em `requirements` de nível é ignorado** — sempre VERIFY; não há dedução ao subir.
432
+ - **`LevelManager.findCurrentByPoints` e `findNextByPoints` são código morto** — nenhum chamador no projeto (confirmado via grafo). Além disso usam `minPoints` (não `position`), divergindo do motor real. Não confie neles para entender o comportamento.
433
+ - **`level_config` não tem endpoint no módulo level** — apenas leitura via `findConfig()`; escrita via `/v3/database/level_config`.
434
+ - **`point` enviado no corpo é descartado** — é derivado de `requirements`.
435
+ - **`apiKey`** no `Level` é legado comentado — inexistente em runtime.
436
+ - **DELETE não limpa achievements** — remover um nível não remove achievements `TYPE_LEVEL` que o referenciam; `findLatestLevelAchieved` pode retornar id órfão e `findById` devolver objeto vazio.
437
+ - **Sem rebaixamento** — reduzir pontos do jogador (estorno) **não** o rebaixa; o achievement `TYPE_LEVEL` mais recente permanece como nível atual.
438
+ - **`findPlayerStatusByLevel`** depende de igualdade de objeto embutido completo — quebradiço.
439
+ - Não há job/scheduler dedicado ao módulo `level` (não localizado no código).
60
440
 
61
- ### Configurar Ponto Global para Níveis
62
- **Método:** PUT
63
- **Endpoint:** `/v3/database/level_config`
441
+ ---
64
442
 
65
- **Exemplo de Body:**
443
+ ## 8. Segurança e Permissões
444
+
445
+ - **Autenticação:** todos os endpoints exigem `AuthBean` (Bearer token). Não há checagem de papel/role explícita dentro de `LevelRest` — o controle de acesso administrativo depende da chave/token do tenant resolvido por `FrontController.getInstance(apiKey)`.
446
+ - **Isolamento por organização:** cada tenant opera no próprio banco (conexão Jongo via `ManagerFactory`). `level` e `level_config` não cruzam tenants.
447
+ - **Superfície de script (Groovy):** `PublicManager` importa `com.funifier.engine.level.Level` em scripts dinâmicos (linha 172). Conteúdo de scripts públicos/customizados que manipulem níveis deve ser auditado — execução de script é um vetor sensível.
448
+ - **Operações destrutivas silenciosas:** `POST` sobrescreve o documento inteiro e `update` (DAO) faz `remove` + `save`. Uma chamada malformada pode apagar campos sem erro. `PUT /v3/level/position` reescreve `position` de **todos** os níveis de uma vez.
449
+ - Não há sanitização explícita dos campos do `Level` antes de `c.save` — confie no contrato do `AuthBean` e valide entradas na camada cliente.
450
+
451
+ ---
452
+
453
+ ## 9. Observabilidade e Troubleshooting
454
+
455
+ **Diagnóstico — o jogador subiu de nível?**
456
+ ```
457
+ GET /v3/level # confere a escada e a ordem (position)
458
+ GET /v3/level/<id> # confere minPoints/requirements de um nível
459
+ # nível atual do jogador está em player_status.level_progress:
460
+ GET /v3/player/<player_id>/status # ou inspecione a coleção player_status
461
+ ```
462
+
463
+ **Queries úteis (via /v3/database, coleção achievement):**
464
+ ```jsonc
465
+ // histórico de promoções de um jogador (type 3 = LEVEL)
466
+ { "player": "<player_id>", "type": 3 } // ordenar por time desc → nível atual
467
+
468
+ // total de pontos que o motor enxerga (categoria global de level_config)
469
+ // equivale a sumTotalRewards(player, TYPE_POINT, pointCategory)
470
+ ```
471
+
472
+ **Erros comuns e causas:**
473
+
474
+ | Sintoma | Causa provável |
475
+ |---|---|
476
+ | Jogador não sobe de nível mesmo com pontos | `level_config.pointCategory` aponta para categoria errada/inexistente → `points = 0`; ou `requirements` do próximo nível não atendidos; ou `position` desalinhado de `minPoints`. |
477
+ | Jogador "pula" níveis com 1 ganho | Comportamento esperado: o `while` promove em cascata (cada nível ainda gera achievement/notificação). |
478
+ | `percent` parece errado | Categorias de ponto de `getPoint()` divergem entre nível atual e próximo → cálculo cai no total global. |
479
+ | Nível atual aponta para id inexistente | Nível foi deletado mas o achievement `TYPE_LEVEL` permaneceu. |
480
+ | Atualizar um nível "apagou" campos | `POST` é full replace; reenvie o documento completo. |
481
+ | `requirements` com dedução não deduziu | Esperado: no level, `operation` é ignorado (sempre VERIFY). |
482
+
483
+ **O que verificar quando algo não funciona:** confirme `level_config`; confirme que `position` é sequencial e coerente com `minPoints` (rode `PUT /v3/level/position`); confirme que algum evento dispara `updatePlayerStatus` (ganho de pontos, compra, troca).
484
+
485
+ ---
486
+
487
+ ## 10. Exemplos Práticos
488
+
489
+ ### 10.1 Exemplo mínimo funcional (progressão por total de pontos)
490
+ ```json
491
+ POST /v3/level
492
+ { "_id": "L0", "level": "Iniciante", "position": 0, "minPoints": 0 }
493
+ ```
494
+ ```json
495
+ POST /v3/level
496
+ { "_id": "L1", "level": "Bronze", "position": 1, "minPoints": 100 }
497
+ ```
498
+ Com `level_config` ausente/vazio, a soma de **todos** os pontos governa a subida.
499
+
500
+ ### 10.2 Exemplo avançado (categoria específica + requirement de desafio)
501
+ ```json
502
+ PUT /v3/database/level_config
503
+ { "_id": "global", "pointCategory": "xp" }
504
+ ```
505
+ ```json
506
+ POST /v3/level
507
+ {
508
+ "_id": "L2",
509
+ "level": "Prata",
510
+ "position": 2,
511
+ "minPoints": 500,
512
+ "description": "Requer 500 xp e o desafio de onboarding.",
513
+ "image": { "medium": { "url": "https://cdn.exemplo.com/silver.png" } },
514
+ "requirements": [
515
+ { "type": 1, "item": "challenge_onboarding", "total": 1, "operation": 0 }
516
+ ],
517
+ "notifications": [ { "event": 0, "scope": 0, "title": "Você chegou ao Prata!" } ],
518
+ "i18n": { "en": { "level": "Silver" } },
519
+ "techniques": ["GT85"]
520
+ }
521
+ ```
522
+ Para "minPoints em uma categoria via `getPoint()`", use **um único** requirement de ponto com `total == minPoints`:
66
523
  ```json
67
524
  {
68
- "_id": "global",
69
- "pointCategory": "xp"
525
+ "_id": "L3", "level": "Ouro", "position": 3, "minPoints": 1000,
526
+ "requirements": [ { "type": 0, "item": "xp", "total": 1000, "operation": 0 } ]
70
527
  }
71
528
  ```
529
+ Aqui `getPoint()` retornará `"xp"` e a resposta REST trará `"point": "xp"`.
530
+
531
+ ### 10.3 Anti-pattern (o que NÃO fazer)
532
+ ```json
533
+ // ❌ ERRADO: tentar "atualizar só o minPoints" via POST parcial
534
+ POST /v3/level
535
+ { "_id": "L2", "minPoints": 600 }
536
+ ```
537
+ Isto **apaga** `level`, `position`, `requirements`, `image` etc. do nível L2 (full replace). Sempre reenvie o documento completo.
538
+
539
+ ```json
540
+ // ❌ ERRADO: esperar dedução de pontos ao subir de nível
541
+ { "type": 0, "item": "xp", "total": 100, "operation": 1 } // operation DEDUCT é ignorado no level → vira VERIFY
542
+ ```
543
+
544
+ ```jsonc
545
+ // ❌ ERRADO: position fora de ordem com minPoints
546
+ { "_id": "A", "position": 0, "minPoints": 500 }
547
+ { "_id": "B", "position": 1, "minPoints": 100 } // "próximo" nível tem minPoints menor → progressão incoerente
548
+ // Rode PUT /v3/level/position para realinhar position ao minPoints.
549
+ ```
72
550
 
73
- ### Atualizar Posições dos Níveis
74
- **Método:** PUT
75
- **Endpoint:** `/v3/level/position`
76
- **Descrição:** Recalcula posições com base no minPoints.
551
+ ---
77
552
 
78
- ## Validações e Testes
553
+ ## Checklist de Configuração
79
554
 
80
- - [ ] Níveis aparecem na lista GET /v3/level
81
- - [ ] minPoints é crescente entre níveis
82
- - [ ] level_config aponta para o ponto correto
83
- - [ ] Ao ganhar pontos suficientes, jogador sobe de nível automaticamente
555
+ - [ ] `_id` definido (ou deixe em branco para autogerar) e estável entre updates.
556
+ - [ ] `level` (nome) preenchido — é o título exibido no Studio.
557
+ - [ ] `minPoints` crescente e coerente com `position`.
558
+ - [ ] `position` sequencial a partir de 0 (rode `PUT /v3/level/position` após mudanças em `minPoints`).
559
+ - [ ] `level_config` (`_id: "global"`) configurado **antes** se a progressão deve usar uma categoria específica; senão a soma de todos os pontos é usada.
560
+ - [ ] A `pointCategory` de `level_config` existe no módulo `point`.
561
+ - [ ] Itens/desafios citados em `requirements` existem antes de criar o nível.
562
+ - [ ] Ciente: `POST` é full replace — sempre envie o documento completo ao "atualizar".
563
+ - [ ] Ciente: `operation` em `requirements` de nível é ignorado (sempre VERIFY).
564
+ - [ ] Ciente: deletar um nível não limpa achievements `TYPE_LEVEL` que o referenciam.
565
+ - [ ] `notifications` usam `event: 0 (EVENT_WIN)` para disparar na promoção.