funifier-mcp 0.2.25 → 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 (211) 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 +5 -2
  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 +1011 -77
  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/core/api-client.d.ts +21 -1
  66. package/dist/core/api-client.d.ts.map +1 -1
  67. package/dist/core/api-client.js +154 -1
  68. package/dist/core/api-client.js.map +1 -1
  69. package/dist/core/constants.d.ts +14 -0
  70. package/dist/core/constants.d.ts.map +1 -1
  71. package/dist/core/constants.js +14 -0
  72. package/dist/core/constants.js.map +1 -1
  73. package/dist/core/types/Folder.d.ts +16 -0
  74. package/dist/core/types/Folder.d.ts.map +1 -0
  75. package/dist/core/types/Folder.js +3 -0
  76. package/dist/core/types/Folder.js.map +1 -0
  77. package/dist/core/types/FolderContent.d.ts +10 -0
  78. package/dist/core/types/FolderContent.d.ts.map +1 -0
  79. package/dist/core/types/FolderContent.js +3 -0
  80. package/dist/core/types/FolderContent.js.map +1 -0
  81. package/dist/core/types/FolderContentType.d.ts +10 -0
  82. package/dist/core/types/FolderContentType.d.ts.map +1 -0
  83. package/dist/core/types/FolderContentType.js +3 -0
  84. package/dist/core/types/FolderContentType.js.map +1 -0
  85. package/dist/core/types/FolderLog.d.ts +11 -0
  86. package/dist/core/types/FolderLog.d.ts.map +1 -0
  87. package/dist/core/types/FolderLog.js +3 -0
  88. package/dist/core/types/FolderLog.js.map +1 -0
  89. package/dist/core/types/index.d.ts +4 -0
  90. package/dist/core/types/index.d.ts.map +1 -1
  91. package/dist/core/types/index.js +4 -0
  92. package/dist/core/types/index.js.map +1 -1
  93. package/dist/mcp/bundle.js +121 -87
  94. package/dist/mcp/check-update.d.ts +2 -0
  95. package/dist/mcp/check-update.d.ts.map +1 -0
  96. package/dist/mcp/check-update.js +44 -0
  97. package/dist/mcp/check-update.js.map +1 -0
  98. package/dist/mcp/index.js +5 -2
  99. package/dist/mcp/index.js.map +1 -1
  100. package/dist/mcp/resources/documentation.d.ts +1 -1
  101. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  102. package/dist/mcp/resources/documentation.js +39 -3
  103. package/dist/mcp/resources/documentation.js.map +1 -1
  104. package/dist/mcp/tools/_char-guard.js +1 -1
  105. package/dist/mcp/tools/_char-guard.js.map +1 -1
  106. package/dist/mcp/tools/_fetch-current.d.ts +1 -1
  107. package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
  108. package/dist/mcp/tools/_fetch-current.js +12 -0
  109. package/dist/mcp/tools/_fetch-current.js.map +1 -1
  110. package/dist/mcp/tools/connect.d.ts.map +1 -1
  111. package/dist/mcp/tools/connect.js +18 -8
  112. package/dist/mcp/tools/connect.js.map +1 -1
  113. package/dist/mcp/tools/database.d.ts.map +1 -1
  114. package/dist/mcp/tools/database.js +59 -47
  115. package/dist/mcp/tools/database.js.map +1 -1
  116. package/dist/mcp/tools/database.test.js +2 -2
  117. package/dist/mcp/tools/database.test.js.map +1 -1
  118. package/dist/mcp/tools/delete.d.ts.map +1 -1
  119. package/dist/mcp/tools/delete.js +33 -3
  120. package/dist/mcp/tools/delete.js.map +1 -1
  121. package/dist/mcp/tools/execute.d.ts.map +1 -1
  122. package/dist/mcp/tools/execute.js +20 -9
  123. package/dist/mcp/tools/execute.js.map +1 -1
  124. package/dist/mcp/tools/folder.d.ts +4 -0
  125. package/dist/mcp/tools/folder.d.ts.map +1 -0
  126. package/dist/mcp/tools/folder.js +68 -0
  127. package/dist/mcp/tools/folder.js.map +1 -0
  128. package/dist/mcp/tools/get.d.ts.map +1 -1
  129. package/dist/mcp/tools/get.js +16 -6
  130. package/dist/mcp/tools/get.js.map +1 -1
  131. package/dist/mcp/tools/index.d.ts +1 -1
  132. package/dist/mcp/tools/index.d.ts.map +1 -1
  133. package/dist/mcp/tools/index.js +5 -1
  134. package/dist/mcp/tools/index.js.map +1 -1
  135. package/dist/mcp/tools/list.d.ts.map +1 -1
  136. package/dist/mcp/tools/list.js +38 -14
  137. package/dist/mcp/tools/list.js.map +1 -1
  138. package/dist/mcp/tools/logs.d.ts.map +1 -1
  139. package/dist/mcp/tools/logs.js +15 -5
  140. package/dist/mcp/tools/logs.js.map +1 -1
  141. package/dist/mcp/tools/save.d.ts.map +1 -1
  142. package/dist/mcp/tools/save.js +26 -4
  143. package/dist/mcp/tools/save.js.map +1 -1
  144. package/dist/mcp/tools/save.test.js +192 -1
  145. package/dist/mcp/tools/save.test.js.map +1 -1
  146. package/dist/mcp/tools/search-docs.d.ts +3 -0
  147. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  148. package/dist/mcp/tools/search-docs.js +102 -0
  149. package/dist/mcp/tools/search-docs.js.map +1 -0
  150. package/package.json +6 -2
  151. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  152. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  153. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  154. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  155. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  156. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  157. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  158. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  159. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  160. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  161. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  162. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  163. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  164. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  165. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  166. package/skills/funifier/SKILL.md +88 -0
  167. package/skills/funifier/references/configure-security.md +96 -0
  168. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  169. package/skills/funifier/references/create-aggregate.md +144 -0
  170. package/skills/funifier/references/create-challenge.md +116 -0
  171. package/skills/funifier/references/create-competition.md +98 -0
  172. package/skills/funifier/references/create-crossword.md +574 -0
  173. package/skills/funifier/references/create-custom-object.md +91 -0
  174. package/skills/funifier/references/create-custom-page.md +135 -0
  175. package/skills/funifier/references/create-folder.md +104 -0
  176. package/skills/funifier/references/create-lastmile.md +643 -0
  177. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  178. package/skills/funifier/references/create-level.md +94 -0
  179. package/skills/funifier/references/create-lottery.md +913 -0
  180. package/skills/funifier/references/create-mystery.md +769 -0
  181. package/skills/funifier/references/create-notification.md +75 -0
  182. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  183. package/skills/funifier/references/create-quiz.md +98 -0
  184. package/skills/funifier/references/create-scheduler.md +141 -0
  185. package/skills/funifier/references/create-story.md +636 -0
  186. package/skills/funifier/references/create-swap.md +95 -0
  187. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  188. package/skills/funifier/references/create-virtual-good.md +96 -0
  189. package/skills/funifier/references/create-webhook.md +72 -0
  190. package/skills/funifier/references/create-websocket.md +71 -0
  191. package/skills/funifier/references/create-widget.md +76 -0
  192. package/skills/funifier/references/debug.md +87 -0
  193. package/skills/funifier/references/help.md +81 -0
  194. package/skills/funifier/references/implement-frontend.md +106 -0
  195. package/skills/funifier/references/import-csv.md +75 -0
  196. package/skills/funifier/references/manage-player.md +82 -0
  197. package/skills/funifier/references/manage-team.md +76 -0
  198. package/skills/funifier/references/upload-file.md +91 -0
  199. package/datasource-funifier-docs/.search-index.json +0 -17318
  200. package/datasource-funifier-docs/.skills-map.json +0 -73
  201. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  202. package/skills/funifier-create-challenge/SKILL.md +0 -88
  203. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  204. package/skills/funifier-create-level/SKILL.md +0 -87
  205. package/skills/funifier-create-quiz/SKILL.md +0 -87
  206. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  207. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  208. package/skills/funifier-debug/SKILL.md +0 -92
  209. package/skills/funifier-help/SKILL.md +0 -86
  210. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  211. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,34 +1,333 @@
1
- # Action (Ação)
1
+ # `action`
2
2
 
3
- **Acesso Studio:** `/studio/action`
3
+ **Acesso Studio:** `/studio/action` _(convenção de UI; não verificável no `funifier-service`)_
4
4
  **API Endpoint:** `/v3/action`
5
+ **Endpoint Legado:** `/2.0.0/action` (`com.funifier.rest.engine.ActionRest`)
6
+ **Endpoint System:** `/v3/system/action` (gamificação de sistema)
7
+ **Coleção MongoDB:** `action`
5
8
 
6
- ## O que é
9
+ > Documento de engenharia reversa baseado **exclusivamente** no código de `funifier-service` (commit `830037e`). Cobre o **cadastro/registro de ações** (entidade `Action`, coleção `action`). O registro de execuções (firing) — entidade `ActionLog`, coleção `action_log`, endpoints `/v3/action/log*` — está documentado em `action-log.md`. Os dois compartilham o mesmo controller (`ActionRest`).
7
10
 
8
- Cadastro das diferentes ações que os jogadores podem realizar na gamificação. As ações são a base da estratégia de engajamento — definem o que pode ser feito e servem para disparar desafios e recompensas. É possível adicionar atributos extras para detalhar o contexto (ex: `produto`, `valor` numa ação de venda).
11
+ ---
9
12
 
10
- ## Quando usar
13
+ ## 1. Visão Geral
11
14
 
12
- - Em todo projeto de gamificação (obrigatório)
13
- - Para definir os comportamentos que serão rastreados
14
- - Para criar a base de eventos que disparam challenges e recompensas
15
+ O módulo `action` é o **catálogo de tipos de ação** que os jogadores podem executar na gamificação. Cada documento da coleção `action` é a *definição* de um comportamento rastreável (ex: `sell`, `watch_video`, `checkin`), não a ocorrência dele. A ocorrência é registrada como `action_log` (vide `action-log.md`).
15
16
 
16
- ## Ação simples vs ação com atributos
17
+ Papel arquitetural:
17
18
 
18
- **Ação simples** sem atributos. Use quando o contexto não importa, o fato de ter ocorrido:
19
+ - **Vocabulário de eventos da gamificação.** Nenhum `action_log` pode ser registrado para um `actionId` que não exista e esteja `active` (validado em `ActionRest.insertLog`, linhas 415-421).
20
+ - **Esquema de atributos.** O campo `attributes` declara quais chaves o `action_log.attributes` pode carregar e o *tipo* esperado de cada uma. O tipo é consultado por consumidores (challenge, leaderboard, crowning, csv) para decidir coerção de valor (`ActionAttribute.TYPE_NUMBER`/`TYPE_BOOLEAN`).
21
+ - **Configuração de comportamento no disparo.** Uma `Action` pode carregar `points`, `notifications` e `limit`. Esses campos não fazem nada no momento do cadastro — são lidos quando um `action_log` correspondente é processado (`AchievementManager.fireAction`).
22
+ - **Base para challenges e triggers.** `ChallengeRule.actionId` e `trigger_html.actions` referenciam `action._id`. Por isso a exclusão de uma action tem efeito cascata (seção 6).
23
+
24
+ Problemas que resolve:
25
+
26
+ - Define o que pode ser rastreado e com qual contexto (atributos).
27
+ - Permite recompensa direta por ação (pontos por ação, sem precisar de challenge).
28
+ - Permite limitar a frequência de registro de uma ação (anti-fraude / anti-spam) via `limit`.
29
+
30
+ Relação com outros módulos:
31
+
32
+ | Módulo | Relação |
33
+ |---|---|
34
+ | `action-log` | Toda inserção de log valida a existência e `active` da action; resolve `actionId` por `_id` **ou** nome. |
35
+ | `achievement` | `AchievementManager.fireAction` lê `action.points` (linha 152) e `action.notifications` (linha 196) ao processar um log. |
36
+ | `challenge` | `ChallengeRule.actionId` aponta para `action._id`. Excluir a action **remove a rule** do challenge. |
37
+ | `trigger` (`trigger_html`) | `trigger_html.actions` referencia actions; excluir a action a remove dessa lista. |
38
+ | `point_category` | `RewardPoint.category` referencia uma categoria de pontos. |
39
+ | `player` | Limites `per: "player"` contam `action_log` por `userId`. |
40
+
41
+ ---
42
+
43
+ ## 2. Arquitetura e Fluxos
44
+
45
+ ### 2.1 Classes envolvidas
46
+
47
+ | Classe | Papel |
48
+ |---|---|
49
+ | `com.funifier.engine.action.Action` | Entidade/POJO raiz — documento da coleção `action`. |
50
+ | `com.funifier.engine.action.ActionAttribute` | Subentidade — definição de atributo (`name`, `type`, `get_value_script`). |
51
+ | `com.funifier.engine.action.Limit` | Subentidade — configuração de limite de frequência. |
52
+ | `com.funifier.engine.action.LinkedAction` | Subentidade **legada/morta** — sem consumidores (seção 7). |
53
+ | `com.funifier.engine.action.ActionDaoMongo` | Repositório Jongo. Coleção `action`. Faz sanitização e cascata de exclusão. |
54
+ | `com.funifier.engine.action.ActionManager` | Manager. Delega CRUD ao DAO; calcula disponibilidade; orquestra o tracking de logs. |
55
+ | `com.funifier.engine.action.ActionAsyncProcessor` | Thread por gamificação que consome a fila de `action_log` (firing assíncrono). |
56
+ | `com.funifier.rest.v3.rest.ActionRest` | Controller REST v3 (`/v3/action` + `/v3/action/log*` + `/queue/*`). |
57
+ | `com.funifier.engine.challenge.RewardPoint` | Subentidade de `points` (recompensa direta por ação). |
58
+ | `com.funifier.engine.notify.NotificationDefinition` | Subentidade de `notifications`. |
59
+ | `com.funifier.engine.image.Image` | Subentidade de `image` (`small`/`medium`/`original`). |
60
+
61
+ Não há validação dedicada de schema; `Action` usa `@JsonIgnoreProperties(ignoreUnknown=true)` — **campos desconhecidos são silenciosamente descartados na desserialização**.
62
+
63
+ ### 2.2 Pipeline de criação/atualização — `POST /v3/action`
64
+
65
+ Sequência real (`ActionRest.insert` → `ActionManager.addAction` → `ActionDaoMongo.addAction`):
66
+
67
+ ```
68
+ [1] ActionRest.insert(action) → ManagerFactory.getActionManager().addAction(action)
69
+ [2] ActionManager.addAction(action) → mongo.addAction(action, jongo)
70
+ [3] ActionDaoMongo.addAction:
71
+ se action.id == null:
72
+ action.id = Guid.newShortGuid() // gera ID curto
73
+ senão:
74
+ action.id = Normalizer.normalize(id, NFD)
75
+ .replaceAll("[^\\p{ASCII}]", "") // remove acentos/não-ASCII
76
+ .trim()
77
+ .replaceAll(" ", "_") // espaços viram _
78
+ .replaceAll("[^a-zA-Z0-9_]", "") // remove o resto
79
+ para cada attribute:
80
+ att.name = Normalizer.normalize(att.name, NFD)
81
+ .replaceAll("[^\\p{ASCII}]", "")
82
+ .trim()
83
+ .replaceAll(" ", "_")
84
+ .replaceAll("[^a-zA-Z0-9_.]", "") // nomes de attribute também permitem '.'
85
+ c.save(action) // UPSERT por _id (Jongo)
86
+ [4] Retorna 201 CREATED com a action (JsonUtil.toJsonRemoveNullFields)
87
+ ```
88
+
89
+ **Síncrono.** Sem transação. `c.save` é **upsert por `_id`**: reenviar a mesma action substitui o documento inteiro (full replace, não patch).
90
+
91
+ #### Fluxo de criação/atualização — `POST /v3/action`
92
+
93
+ ```mermaid
94
+ flowchart LR
95
+ A[POST /v3/action] --> B{_id informado?}
96
+ B -- não --> C[gera Guid.newShortGuid]
97
+ B -- sim --> D["sanitiza _id<br/>NFD, strip não-ASCII,<br/>espaços→_, strip ^a-zA-Z0-9_"]
98
+ C --> E["sanitiza nomes de attributes<br/>(permite . além de a-zA-Z0-9_)"]
99
+ D --> E
100
+ E --> F["c.save(action)<br/>UPSERT por _id"]
101
+ F --> G[201 CREATED]
102
+ ```
103
+
104
+ > **Não existe endpoint PUT.** `ActionManager.updateAction` / `ActionDaoMongo.updateAction` existem mas **não são acessíveis pelo REST** — toda escrita passa por `addAction` (upsert).
105
+
106
+ ### 2.3 Pipeline de exclusão — `DELETE /v3/action/:id` (cascata)
107
+
108
+ `ActionRest.delete` → `ActionManager.deleteAction` → `ActionDaoMongo.deleteAction`:
109
+
110
+ ```
111
+ [1] updateChallenges(id):
112
+ para CADA challenge da coleção `challenge`:
113
+ remove toda ChallengeRule cujo actionId == id
114
+ se removeu alguma rule → cc.remove(_id) + cc.save(challenge) // re-salva o challenge alterado
115
+ [2] updateActionConfigs(id):
116
+ para CADA trigger_html da coleção `trigger_html`:
117
+ remove a action da lista `actions` onde action.id == id
118
+ se removeu → re-salva o trigger_html
119
+ [3] c.remove("{_id:#}", id) // remove o documento da action
120
+ [4] Retorna 204 NO CONTENT
121
+ ```
122
+
123
+ Esse comportamento é **silencioso e cascateado** — vide seção 6. Não há log nem aviso; um challenge pode ficar com **zero rules** após a exclusão da action.
124
+
125
+ ### 2.4 Como a configuração da action influencia o disparo (firing)
126
+
127
+ Os campos `points`, `notifications`, `limit` e `active` só produzem efeito quando um `action_log` correspondente é processado. Onde cada um é lido:
128
+
129
+ | Campo | Lido em | Efeito |
130
+ |---|---|---|
131
+ | `active` | `ActionRest.insertLog:419` | Se `false`, o log é rejeitado (`"action ... is not active"`). |
132
+ | `limit` | `ActionRest.insertLog:494` / `ActionManager.evaluateLimit` | Bloqueia o registro se exceder a frequência. |
133
+ | `points` | `AchievementManager.fireAction:152` | Gera `Achievement` TYPE_POINT para o jogador (recompensa direta). |
134
+ | `notifications` | `AchievementManager.fireAction:196` | Envia notificações de `EVENT_WIN` (scope ≠ custom). |
135
+ | `attributes[].type` | `AchievementManager:2054`, `LeaderBoardManager:127`, `CsvManager:186`, `CrowningManager` | Coerção de valor (Number/Boolean) na agregação dos logs. |
136
+
137
+ ```mermaid
138
+ sequenceDiagram
139
+ participant C as Cliente
140
+ participant R as ActionRest
141
+ participant AM as ActionManager
142
+ participant Ach as AchievementManager
143
+ C->>R: POST /v3/action/log {actionId, userId, attributes}
144
+ R->>AM: findActionByIdOrName(actionId)
145
+ AM-->>R: Action (erro 400 se null ou active=false)
146
+ R->>R: resolve player + checa Limit
147
+ alt async=false (síncrono)
148
+ R->>AM: trackSynchonous(log)
149
+ AM->>AM: triggerManager.execute(BEFORE_WIN) + addLog
150
+ AM->>Ach: fireAction(log)
151
+ Ach->>Ach: lê action.points → Achievement TYPE_POINT (só para player)
152
+ Ach->>Ach: lê action.notifications (EVENT_WIN) → envia
153
+ else async=true (default)
154
+ R->>AM: track(log) → enfileira (ActionAsyncProcessor)
155
+ end
156
+ ```
157
+
158
+ **Importante:** recompensa por `points` e `notifications` da action só ocorrem quando o `principal` é **jogador** (`principal.isPlayer()`); para times, esta seção do `fireAction` é ignorada.
159
+
160
+ ### 2.5 Listagem — `GET /v3/action`
161
+
162
+ `ActionManager.findAllActions(id, action, q, fields, orderby, reverse, max_results)` monta um pipeline de aggregation sobre a coleção `action`:
163
+
164
+ ```
165
+ $match { _id: {$exists:true}
166
+ [, _id: <id>]
167
+ [, <q literal — concatenado cru>]
168
+ [, action: {$regex: <action>, $options:'i'}] }
169
+ [$project { <fields separados por vírgula>: 1 }]
170
+ [$sort { <orderby>: 1|-1 }]
171
+ $limit <max_results> // default 100 quando max_results <= 0
172
+ ```
173
+
174
+ `q`, `orderby` e `fields` são **concatenados literalmente** na string do pipeline (superfície de injeção — seção 8). A resposta é um array JSON via `Callback.callback` (**não paginado**; o header `Range` não se aplica a `/v3/action`, apenas aos endpoints `/log*`).
175
+
176
+ ---
177
+
178
+ ## 3. Estrutura dos Objetos
179
+
180
+ ### 3.1 `Action` — documento raiz (coleção `action`)
181
+
182
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
183
+ |---|---|---|---|---|
184
+ | `_id` | String | auto (`shortGuid`) ou sanitizado | — | ID da action. Se omitido, gerado; se informado, **sanitizado** (NFD, sem acentos, espaços→`_`, só `a-zA-Z0-9_`). |
185
+ | `action` | String | — | recomendado | Nome amigável (ex: `"Sell"`). Pode ser usado para resolver a action no registro de log (`findActionByIdOrName`). |
186
+ | `attributes` | `ActionAttribute[]` | — | não | Definição dos atributos aceitos no `action_log`. Vide 3.2. |
187
+ | `points` | `RewardPoint[]` | — | não | Pontos concedidos **diretamente** quando a action é disparada (jogadores). Vide 3.4. |
188
+ | `notifications` | `NotificationDefinition[]` | — | não | Notificações disparadas no `EVENT_WIN` ao disparar a action. Vide 3.5. |
189
+ | `image` | `Image` | — | não | Imagem (`small`/`medium`/`original`). Vide 3.6. |
190
+ | `limit` | `Limit` | — | não | Limite de frequência de registro de logs. Vide 3.3. |
191
+ | `active` | boolean | `true` | não | Se `false`, logs para esta action são rejeitados. |
192
+ | `extra` | `Map<String,Object>` | `{}` | não | Metadado livre. Persistido, **não interpretado** pelo firing. |
193
+ | `available` | Boolean | — | — | **Campo calculado em runtime** (não persistido por design). Vide abaixo. |
194
+ | `linkedActions` | `LinkedAction[]` | — | não | **Legado/morto** — aceito e persistido, **nunca lido**. Vide 3.7 e seção 7. |
195
+
196
+ **Campo computado (não-persistente):**
197
+
198
+ - `available` — preenchido somente em `GET /v3/action?available=true&player=<id>` por `ActionManager.isActionsAvailable` → `evaluateLimit`. `true` quando a action **não tem `limit`** ou o jogador ainda está abaixo do limite; `false` quando excedeu o limite **ou** a expressão de `limit.total` lançou exceção (falha silenciosa). Não é gravado no Mongo a menos que o cliente o envie explicitamente no POST.
199
+
200
+ **Campos silenciosamente sanitizados no save (`ActionDaoMongo.addAction`):**
201
+
202
+ - `_id` informado → normalizado (perde acentos, espaços viram `_`, caracteres especiais removidos). Ex: `"Vender Açaí"` → `Vender_Acai`.
203
+ - `attributes[].name` → mesma normalização (adicionalmente permite `.`).
204
+
205
+ **Campos aceitos mas descartados:**
206
+
207
+ - Qualquer chave não mapeada na classe `Action` → descartada (`@JsonIgnoreProperties(ignoreUnknown=true)`).
208
+
209
+ ### 3.2 `ActionAttribute` — subentidade de `attributes`
210
+
211
+ | Campo | Tipo | Obrigatório | Descrição |
212
+ |---|---|---|---|
213
+ | `name` | String | sim | Nome do atributo (sanitizado, permite `.`). |
214
+ | `type` | String | recomendado | `"String"`, `"Number"` ou `"Boolean"`. **Não validado na escrita.** |
215
+ | `get_value_script` | String | não | Exclusivo de trigger HTML — script de localização do valor numa página. |
216
+
217
+ **`type` não é validado no cadastro.** As constantes `ActionAttribute.TYPE_STRING/NUMBER/BOOLEAN` só são consultadas por *consumidores* dos logs para decidir coerção: `Number` → cast numérico; `Boolean` → cast booleano; qualquer outro valor → tratado como string. Logo, um `type` arbitrário (ex: `"Integer"`) é aceito silenciosamente e se comporta como `String`.
218
+
219
+ ### 3.3 `Limit` — subentidade de `limit`
220
+
221
+ | Campo | Tipo | Descrição |
222
+ |---|---|---|
223
+ | `total` | Object (Number **ou** String) | Número máximo de registros no período, **ou** fórmula Mustache+exp4j avaliada com `{player, action_log}` (ex: `"{{player.extra.max_actions}}"`). |
224
+ | `per` | String | Escopo da contagem: `"player"`, `"team"` ou `"gamification"`. |
225
+ | `every` | String | Janela de tempo (ex: `"1h"`, `"5h"`, `"1d"`). Se ausente → janela de `-10y` (efetivamente sempre). |
226
+ | `query` | String | Filtro Mongo adicional, Mustache-parseado com `{player, action_log}`, **concatenado cru** na query de contagem. |
227
+
228
+ Constantes: `PER_PLAYER = "player"`, `PER_TEAM = "team"`, `PER_GAME = "gamification"`.
229
+
230
+ > ⚠️ **`per: "team"` é um branch morto.** A verificação de limite (`evaluateLimit`, `trackWithRestrictions`, `ActionRest.insertLog`, `insertLogBulk`) só trata `PER_PLAYER` e `PER_GAME`. Com `per: "team"` nenhum branch executa, `count` permanece `0`, `0 >= total` é falso → **o limite nunca bloqueia**. Vide seção 7.
231
+
232
+ Lógica de contagem (quando `total > 0`):
233
+
234
+ ```
235
+ from = (every != null) ? agora - every : agora - 10anos
236
+ PER_PLAYER → db.action_log.count({userId, actionId, time: {$gte: from} <query>})
237
+ PER_GAME → db.action_log.count({actionId, time: {$gte: from} <query>})
238
+ se count >= total → bloqueia
239
+ ```
240
+
241
+ ### 3.4 `RewardPoint` — subentidade de `points`
242
+
243
+ | Campo | Tipo | Descrição |
244
+ |---|---|---|
245
+ | `total` | double | Quantidade base de pontos. |
246
+ | `category` | String | ID da categoria de pontos (`point_category`). |
247
+ | `operation` | int | `0` NONE; `1` MULTIPLY_BY_ATTRIBUTE; `2` DIVIDE_BY_ATTRIBUTE. |
248
+ | `value` | String | Nome do atributo do `action_log` usado em multiply/divide. |
249
+ | `perPlayer` | boolean | Presente no schema; **não consultado** no firing de action. |
250
+
251
+ Cálculo em `fireAction` (linhas 152-191), apenas para jogadores e quando `total != 0`:
19
252
 
20
- ```json
21
- {
22
- "_id": "watch_video",
23
- "action": "Watch Video",
24
- "active": true,
25
- "attributes": []
26
- }
27
253
  ```
254
+ NONE → totalPoints = total
255
+ MULTIPLY_BY_ATTRIBUTE → totalPoints = total * action_log.attributes[value]
256
+ DIVIDE_BY_ATTRIBUTE → totalPoints = total / action_log.attributes[value]
257
+ ```
258
+
259
+ > Se o atributo de `value` não existir ou não for numérico, a exceção é **engolida** e `totalPoints` permanece com o `total` base (a operação degrada silenciosamente para NONE).
260
+
261
+ ### 3.5 `NotificationDefinition` — subentidade de `notifications`
262
+
263
+ | Campo | Tipo | Descrição |
264
+ |---|---|---|
265
+ | `event` | int | `0` WIN; `1` CREATE; `2` CHANGE; `3` LOSE; `4` REMOVE. |
266
+ | `type` | int | `0` TEXT; `1` VIDEO. |
267
+ | `scope` | int | `0` PRIVATE; `1` NEWSFEED; `99` CUSTOM. |
268
+ | `content` | String | Conteúdo/mensagem. |
269
+ | `tag` | String | Tag opcional. |
270
+
271
+ No disparo da action, somente `event = EVENT_WIN (0)` com `scope ≠ SCOPE_CUSTOM (99)` é enviado (`getNotificationsByEvent` ignora scope custom).
272
+
273
+ ### 3.6 `Image` — subentidade de `image`
274
+
275
+ Três variantes `ImageItem` (`small`, `medium`, `original`), cada uma com `url` (e `size`/`width`/`height`/`depth`, default `0`). Na prática, preencher `url` igual nas três variantes é suficiente.
276
+
277
+ ### 3.7 `LinkedAction` — subentidade de `linkedActions` _(legado/morto)_
278
+
279
+ | Campo | Tipo | Descrição |
280
+ |---|---|---|
281
+ | `action` | String | ID da action filha a ser rastreada. |
282
+ | `playerAttributeName` | String | Nome do atributo da action pai que referencia o jogador da action filha. |
283
+
284
+ **Sem consumidores em `funifier-service`.** A classe e o campo só possuem getters/setters; nenhuma lógica de firing os lê. Aceito e persistido, porém inerte.
285
+
286
+ ---
28
287
 
29
- Exemplos típicos: `login`, `acessar_feed`, `assistir_video`, `jogar_mini`, `compartilhar`.
288
+ ## 4. Endpoints
30
289
 
31
- **Ação com atributos** adicione atributos quando precisar filtrar ou acumular valores dentro de um challenge. Tipos disponíveis: `String`, `Number`, `Boolean`.
290
+ Todos sob `ActionRest` (`@Path("v3/action")`). Autenticação: Bearer token (`AuthBean`). Abaixo apenas os endpoints do **cadastro de actions**; os endpoints de log (`/log`, `/log/bulk`, `/log/frequency`, `/log/aggregate`, `/log/{id}`, `/log/attribute/.../index/*`) e de fila (`/queue/*`) estão em `action-log.md`.
291
+
292
+ ### `GET /v3/action`
293
+
294
+ | Aspecto | Detalhe |
295
+ |---|---|
296
+ | Finalidade | Listar/pesquisar actions. |
297
+ | Paginação | **Não paginado** (`Callback.callback`, array completo). |
298
+ | Resposta | Array de `Action` com campos nulos removidos. |
299
+
300
+ **Query params:**
301
+
302
+ | Param | Tipo | Descrição |
303
+ |---|---|---|
304
+ | `id` | String | Filtra por `_id` exato. |
305
+ | `action` | String | Filtra por nome (`$regex`, case-insensitive). |
306
+ | `q` | String | Critério Mongo bruto (concatenado no `$match`). |
307
+ | `fields` | String | Projeção (lista separada por vírgula). |
308
+ | `orderby` | String | Campo de ordenação. |
309
+ | `reverse` | boolean | `true` = descendente. |
310
+ | `max_results` | int | Limite; default `100` quando `<= 0`. |
311
+ | `player` | String | Usado com `available`. |
312
+ | `available` | String | `"true"` + `player` → calcula `Action.available` por `evaluateLimit`. |
313
+
314
+ ### `GET /v3/action/:id`
315
+
316
+ | Aspecto | Detalhe |
317
+ |---|---|
318
+ | Finalidade | Buscar uma action por `_id` **exato** (`findActionById`). |
319
+ | Observação | **Não** resolve por nome amigável — diferente do caminho de log. |
320
+
321
+ ### `POST /v3/action`
322
+
323
+ | Aspecto | Detalhe |
324
+ |---|---|
325
+ | Finalidade | Criar **ou** atualizar action. |
326
+ | Full replace ou patch | **Full replace** (upsert por `_id` via `c.save`). |
327
+ | Status | `201 CREATED`. |
328
+ | Comportamento real | Gera/sanitiza `_id`; sanitiza `attributes[].name`; descarta campos desconhecidos. Não há erro em `_id` duplicado (upsert). |
329
+
330
+ **Exemplo (action com atributos, pontos e limite):**
32
331
 
33
332
  ```json
34
333
  {
@@ -38,56 +337,186 @@ Exemplos típicos: `login`, `acessar_feed`, `assistir_video`, `jogar_mini`, `com
38
337
  "attributes": [
39
338
  { "name": "product", "type": "String" },
40
339
  { "name": "price", "type": "Number" }
41
- ]
340
+ ],
341
+ "points": [
342
+ { "total": 10, "category": "xp", "operation": 1, "value": "price" }
343
+ ],
344
+ "limit": { "total": 5, "per": "player", "every": "1d" }
42
345
  }
43
346
  ```
44
347
 
45
- Use atributos quando quiser criar challenges como "vender 10 livros" (filtro por `product = "book"`) ou "vender R$500 em eletrônicos" (soma de `price`).
348
+ ### `DELETE /v3/action/:id`
349
+
350
+ | Aspecto | Detalhe |
351
+ |---|---|
352
+ | Finalidade | Excluir action. |
353
+ | Status | `204 NO CONTENT`. |
354
+ | Comportamento real | **Cascata silenciosa**: remove a action das rules de **todos** os challenges e da lista `actions` de **todos** os `trigger_html` antes de remover o documento (seções 2.3 e 6). |
355
+
356
+ ### Variantes adicionais
357
+
358
+ - **Legado** `/2.0.0/action` (`rest.engine.ActionRest`) — endpoints `GET/DELETE /{id}` e `/log` antigos. Preferir v3.
359
+ - **System** `/v3/system/action` (`rest.v3.rest.system.ActionRest`) — equivalente para a gamificação de sistema/admin.
360
+
361
+ ---
362
+
363
+ ## 5. Regras de Negócio
364
+
365
+ Regras presentes no código que **não** estão no schema:
366
+
367
+ 1. **`_id` é sempre saneado.** Não é possível persistir `_id` com acentos, espaços ou símbolos — eles são transformados/removidos no save. O `_id` final pode diferir do enviado.
368
+ 2. **POST é upsert.** Reenviar com `_id` existente substitui o documento inteiro (full replace). Campos omitidos são apagados.
369
+ 3. **Resolução por nome no firing.** Ao registrar um `action_log`, o `actionId` é resolvido por `_id` **ou** por `action` (nome). O log persistido sempre usa o `_id` canônico (`log.actionId = a.getId()`).
370
+ 4. **`active = false` bloqueia logs.** Em `/log` retorna 400; em `/log/bulk` o item é silenciosamente descartado.
371
+ 5. **Limite por período.** `limit` restringe quantos logs de uma action podem ser registrados por jogador/gamificação numa janela (`every`). `total` pode ser uma fórmula dinâmica baseada em `player`/`action_log`.
372
+ 6. **Pontos diretos.** Uma action com `points` concede pontos ao jogador a cada log processado, independentemente de challenges.
373
+ 7. **Multi-tenant.** Cada gamificação (apiKey) tem sua própria coleção `action` e sua própria thread `ActionAsyncProcessor` (`"Funifier-ActionLog-Async-<apiKey>"`).
374
+ 8. **Consistência eventual no firing assíncrono.** Por padrão (`async=true`), o log é enfileirado e processado por uma thread; os efeitos (pontos, achievements) não são imediatos na resposta.
375
+
376
+ ---
377
+
378
+ ## 6. Comportamentos Automáticos
379
+
380
+ | Comportamento | Trigger | Impacto | Persistência |
381
+ |---|---|---|---|
382
+ | Geração de `_id` | POST sem `_id` | `Guid.newShortGuid()` | Sim |
383
+ | Sanitização de `_id` | POST com `_id` | NFD/strip/espaços→`_` | Sim |
384
+ | Sanitização de `attributes[].name` | POST | NFD/strip (permite `.`) | Sim |
385
+ | Upsert | POST com `_id` existente | Substitui documento inteiro | Sim |
386
+ | **Cascata de exclusão em challenges** | DELETE | Remove a action das rules de todos os challenges; re-salva os alterados | Sim (challenge) |
387
+ | **Cascata de exclusão em trigger_html** | DELETE | Remove a action da lista `actions` de todos os `trigger_html` | Sim (trigger_html) |
388
+ | Cálculo de `available` | GET `?available=true&player=` | Preenche `Action.available` via limite | Não |
389
+ | Recompensa por `points` | Log processado (firing) | Cria `Achievement` TYPE_POINT (só player) | Sim (achievement) |
390
+ | Notificações por `notifications` | Log processado (firing) | Envia notificações `EVENT_WIN` | Sim (notification) |
391
+
392
+ #### Fluxo de exclusão em cascata — `DELETE /v3/action/:id`
393
+
394
+ ```mermaid
395
+ flowchart TD
396
+ A["DELETE /v3/action/:id"] --> B["updateChallenges(id)"]
397
+ A --> C["updateActionConfigs(id)"]
398
+ B --> B1["varre TODOS os challenges"]
399
+ B1 --> B2{"rule.actionId == id?"}
400
+ B2 -- sim --> B3["remove a rule + re-salva o challenge"]
401
+ B2 -- não --> B4["mantém"]
402
+ C --> C1["varre TODOS os trigger_html"]
403
+ C1 --> C2["remove id de actions + re-salva config"]
404
+ B3 --> D["c.remove(_id) da action"]
405
+ B4 --> D
406
+ C2 --> D
407
+ D --> E[204 No Content]
408
+ ```
46
409
 
47
- ## Imagem
410
+ > ⚠️ A documentação antiga afirmava que excluir uma action "pode causar inconsistências, verifique antes". **Isso está incorreto.** O sistema *auto-limpa* as rules dos challenges e as listas de trigger_html. O efeito colateral real é: um challenge que dependia exclusivamente daquela action fica **sem rules** (e potencialmente nunca mais completa).
48
411
 
49
- Actions podem ter uma imagem associada via campo `image` com três tamanhos:
412
+ ---
50
413
 
51
- ```json
52
- "image": {
53
- "small": { "url": "https://...", "size": 0, "width": 0, "height": 0, "depth": 0 },
54
- "medium": { "url": "https://...", "size": 0, "width": 0, "height": 0, "depth": 0 },
55
- "original": { "url": "https://...", "size": 0, "width": 0, "height": 0, "depth": 0 }
56
- }
57
- ```
414
+ ## 7. Suportado vs NÃO Suportado
415
+
416
+ ### Suportado
417
+
418
+ - CRUD de actions: `GET /v3/action`, `GET /v3/action/:id`, `POST /v3/action` (upsert), `DELETE /v3/action/:id` (com cascata).
419
+ - Atributos tipados (`String`/`Number`/`Boolean`) consumidos por challenge, leaderboard, crowning, csv.
420
+ - `active` para habilitar/desabilitar o registro de logs.
421
+ - `limit` com `per: "player"` e `per: "gamification"`, `every` (janela), `query` (filtro extra) e `total` numérico ou fórmula Mustache+exp4j.
422
+ - `points` (recompensa direta por ação, com operações NONE/MULTIPLY/DIVIDE).
423
+ - `notifications` de `EVENT_WIN`.
424
+ - `image` (small/medium/original).
425
+ - Cálculo de disponibilidade (`available`) por jogador.
426
+ - Resolução de action por `_id` ou nome no registro de logs.
58
427
 
59
- Na prática basta preencher `url` nos três tamanhos com a mesma URL se não houver versões distintas.
428
+ ### NÃO Suportado / Armadilhas
60
429
 
61
- ## Comportamento de `_id` duplicado (upsert)
430
+ - **`linkedActions`** campo e classe `LinkedAction` existem, são persistidos, mas **nenhum código os lê**. Inerte.
431
+ - **`limit.per: "team"`** — constante `PER_TEAM` existe, mas **nenhum branch trata** esse valor; o limite nunca bloqueia (no-op silencioso). Use `"player"` ou `"gamification"`.
432
+ - **Validação de `attributes[].type`** — inexistente na escrita. Tipos inválidos são aceitos e tratados como `String`.
433
+ - **Endpoint `PUT`** — não existe. `updateAction` no manager/DAO é inacessível pelo REST.
434
+ - **Patch parcial** — não suportado; POST é full replace (upsert).
435
+ - **`extra`** — persistido mas não interpretado pelo firing de action.
436
+ - **`RewardPoint.perPlayer`** — presente no schema, não consultado no firing de action.
437
+ - **`points`/`notifications` para times** — ignorados; só processados quando o principal é jogador.
438
+ - **Paginação em `GET /v3/action`** — não há; retorna o array completo.
439
+ - **Inconsistência de janela no limite** (vide seção 8) — o mesmo `limit` pode avaliar diferente conforme o ponto de entrada.
62
440
 
63
- O endpoint POST faz **upsert**: se o `_id` já existir, a ação é **atualizada**; se não existir, é **criada**. Não retorna erro em caso de duplicata.
441
+ ---
64
442
 
65
- Isso significa que é seguro reenviar a mesma ação para atualizar atributos ou o nome — os challenges existentes que referenciam esse `_id` continuam funcionando.
443
+ ## 8. Segurança e Permissões
66
444
 
67
- ## Boas Práticas
445
+ - **Autenticação:** Bearer token via `AuthBean`/`FrontController.getInstance(apiKey)`. Cada apiKey isola a coleção `action` da sua gamificação.
446
+ - **Isolamento multi-tenant:** garantido pela resolução de `ManagerFactory` por apiKey; não há cruzamento entre gamificações.
447
+ - **Registro de log por jogador:** em `/v3/action/log`, um jogador autenticado só pode registrar log para si mesmo (`ActionRest.insertLog:489`); registro para terceiros exige credencial de admin.
68
448
 
69
- - `_id`: letras minúsculas, sem espaços ou acentos — use `_` como separador (`watch_video`, não `Watch Video`)
70
- - Crie ações **genéricas** e use atributos para detalhar: prefira `comprar` com atributo `produto` a criar `comprar_sapato` e `comprar_camisa`
71
- - Omita `attributes` (ou envie `[]`) quando o contexto não for necessário para nenhum challenge
449
+ **Superfícies de injeção (confirmadas no código):**
72
450
 
73
- ## API Endpoints
451
+ 1. **`GET /v3/action` — `q`, `orderby`, `fields`** são concatenados literalmente na string do pipeline de aggregation (`ActionManager.findAllActions`). Entrada controlável pode alterar o pipeline (NoSQL injection / DoS por query). Mitigação no código: nenhuma.
452
+ 2. **`limit.query`** é Mustache-parseado com `{player, action_log}` e **concatenado cru** na query de contagem (`ActionRest.insertLog:522`, `evaluateLimit:285`, `trackWithRestrictions:200`). Valores controláveis que caiam nos placeholders entram na query sem sanitização.
453
+ 3. **`limit.total` como fórmula** é avaliada via `ExpressionBuilder` (exp4j) sobre string Mustache — exceções são engolidas, podendo silenciosamente liberar o registro.
74
454
 
75
- ### Listar Ações
76
- **Método:** GET
77
- **Endpoint:** `/v3/action`
455
+ **Inconsistência de comportamento (mesma config, resultado diferente conforme entrada):**
456
+
457
+ | Caminho | Janela de contagem |
458
+ |---|---|
459
+ | `ActionRest.insertLog` / `insertLogBulk` | `{$gte: from, $lte: log.time}` |
460
+ | `ActionManager.trackWithRestrictions` | `{$gte: from}` |
461
+ | `ActionManager.evaluateLimit` (cálculo de `available`) | `{$gte: from}` |
462
+
463
+ O `available` exibido (sem limite superior) pode divergir do que de fato será permitido em `insertLog` (com `$lte log.time`).
464
+
465
+ ---
466
+
467
+ ## 9. Observabilidade e Troubleshooting
468
+
469
+ **Verificar se uma action existe / está ativa:**
78
470
 
79
- **Busca múltipla com funifier_list:** o parâmetro `search` aceita termos separados por vírgula (OR):
80
471
  ```
81
- funifier_list type=action search="assistir video, jogar mini, acessar feed"
472
+ GET /v3/action/<id>
473
+ GET /v3/action?action=<nome> # busca por nome (regex, case-insensitive)
82
474
  ```
83
475
 
84
- ### Criar / Atualizar Ação
85
- **Método:** POST
86
- **Endpoint:** `/v3/action`
476
+ **Verificar disponibilidade para um jogador (limite):**
477
+
478
+ ```
479
+ GET /v3/action?available=true&player=<playerId>
480
+ # cada action retorna "available": true|false
481
+ ```
87
482
 
88
- POST com `_id` existente = update (upsert). POST sem `_id` = cria com ID gerado.
483
+ **Inspecionar via banco (coleção `action`):**
484
+
485
+ ```
486
+ db.action.findOne({_id: "<id>"})
487
+ db.action.find({active: false}) # actions desativadas
488
+ db.action.find({"limit.per": "team"}) # ATENÇÃO: limite inerte (no-op)
489
+ ```
490
+
491
+ **Antes de excluir uma action — quais challenges/triggers a usam:**
492
+
493
+ ```
494
+ db.challenge.find({"rules.actionId": "<id>"}) # serão alterados pela cascata
495
+ db.trigger_html.find({"actions._id": "<id>"}) # serão alterados pela cascata
496
+ ```
497
+
498
+ **Erros comuns (registro de log) e causas:**
499
+
500
+ | Mensagem | Causa |
501
+ |---|---|
502
+ | `action <x> does not exist` | `actionId` não casa com `_id` nem `action` (nome). |
503
+ | `action <x> is not active` | `active = false` na action. |
504
+ | `you have exced the limit ...` | `limit` excedido para o período/escopo. |
505
+ | `it was not possible to identify the limit ...` | Fórmula `limit.total` lançou exceção ao avaliar. |
506
+
507
+ **O que verificar quando algo não funciona:**
508
+
509
+ - Action não concede pontos → confira `points` na action **e** que o principal é jogador (times não recebem). Confira que o log foi de fato processado (firing assíncrono por padrão).
510
+ - Limite não bloqueia → confira se `per` é `"player"`/`"gamification"` (não `"team"`).
511
+ - `_id` "mudou" após criar → sanitização (acentos/espaços/símbolos removidos).
512
+ - Atributo numérico tratado como texto → confira `attributes[].type == "Number"` na definição da action.
513
+
514
+ ---
515
+
516
+ ## 10. Exemplos Práticos
517
+
518
+ ### 10.1 Mínimo funcional — ação simples
89
519
 
90
- **Ação simples:**
91
520
  ```json
92
521
  {
93
522
  "_id": "watch_video",
@@ -96,7 +525,8 @@ POST com `_id` existente = update (upsert). POST sem `_id` = cria com ID gerado.
96
525
  }
97
526
  ```
98
527
 
99
- **Ação com atributos:**
528
+ ### 10.2 Avançado — atributos, pontos dinâmicos, limite e notificação
529
+
100
530
  ```json
101
531
  {
102
532
  "_id": "sell",
@@ -105,33 +535,55 @@ POST com `_id` existente = update (upsert). POST sem `_id` = cria com ID gerado.
105
535
  "attributes": [
106
536
  { "name": "product", "type": "String" },
107
537
  { "name": "price", "type": "Number" }
108
- ]
538
+ ],
539
+ "points": [
540
+ { "total": 1, "category": "revenue", "operation": 1, "value": "price" }
541
+ ],
542
+ "notifications": [
543
+ { "event": 0, "type": 0, "scope": 1, "content": "Você registrou uma venda!" }
544
+ ],
545
+ "limit": {
546
+ "total": "{{player.extra.daily_quota}}",
547
+ "per": "player",
548
+ "every": "1d",
549
+ "query": "attributes.product:'{{action_log.attributes.product}}'"
550
+ }
109
551
  }
110
552
  ```
111
553
 
112
- ### Excluir Ação
113
- **Método:** DELETE
114
- **Endpoint:** `/v3/action/:id`
554
+ - `operation: 1` (MULTIPLY_BY_ATTRIBUTE) com `value: "price"` → pontos = `1 * price`.
555
+ - `limit.total` é fórmula dinâmica resolvida do `player.extra.daily_quota`.
556
+ - `limit.query` restringe a contagem ao mesmo `product`.
115
557
 
116
- > Deletar uma ação que é referenciada em challenges ativos pode causar inconsistências. Verifique antes quais challenges usam essa ação:
117
- > ```
118
- > funifier_database action=aggregate collection=challenge pipeline='[{"$match": {"rules.actionId": "<action_id>"}}]'
119
- > ```
558
+ ### 10.3 Anti-pattern o que NÃO fazer
120
559
 
121
- ## Validação após criar
560
+ ```json
561
+ {
562
+ "_id": "Vender Açaí #1",
563
+ "action": "venda",
564
+ "attributes": [ { "name": "preço final", "type": "Integer" } ],
565
+ "linkedActions": [ { "action": "bonus", "playerAttributeName": "indicado" } ],
566
+ "limit": { "total": 3, "per": "team", "every": "1h" }
567
+ }
568
+ ```
122
569
 
123
- Use `funifier_list` para confirmar que a ação foi criada:
570
+ Por que está errado:
124
571
 
125
- ```
126
- funifier_list type=action search=<_id ou nome>
127
- ```
572
+ - `_id` com espaços/acentos/símbolos → será sanitizado para `Vender_Acai_1` (não será o que você espera referenciar).
573
+ - `attributes[].name` `"preço final"` → vira `preo_final`; e `type: "Integer"` é inválido → tratado como `String` (sem coerção numérica).
574
+ - `linkedActions` → **inerte**, nunca executado.
575
+ - `limit.per: "team"` → **não bloqueia nada** (branch morto); o limite é ignorado.
128
576
 
129
- Não use `funifier_logs` para validar actions — esse comando é específico para triggers e schedulers.
577
+ ---
130
578
 
131
- ## Checklist
579
+ ## Checklist de Configuração
132
580
 
133
- - [ ] `_id` definido: minúsculas, sem espaços (`watch_video`, não `Watch Video`)
134
- - [ ] `action` (nome amigável) preenchido
135
- - [ ] `active: true`
136
- - [ ] Atributos com tipos corretos: `String`, `Number` ou `Boolean`
137
- - [ ] Confirmado com `funifier_list type=action search=<_id>`
581
+ - [ ] `_id` em minúsculas, sem espaços/acentos (`watch_video`, não `Watch Video`) — caso contrário será sanitizado.
582
+ - [ ] `action` (nome amigável) preenchido.
583
+ - [ ] `active: true` (senão logs serão rejeitados).
584
+ - [ ] `attributes[].type` `String` | `Number` | `Boolean` (qualquer outro vira `String` silenciosamente).
585
+ - [ ] Se usar `limit`: `per` ∈ `player` | `gamification` (**nunca** `team` — não funciona).
586
+ - [ ] Se usar `points`: `category` aponta para um `point_category` existente; lembre que só jogadores recebem.
587
+ - [ ] Antes de `DELETE`: ciente de que rules de challenges e referências em trigger_html serão removidas em cascata.
588
+ - [ ] Não conte com `linkedActions` (campo morto).
589
+ - [ ] Validado com `GET /v3/action/<id>` ou `GET /v3/action?action=<nome>`.