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,88 +1,495 @@
1
- # Action Log (Registro de Ação)
1
+ # `action-log`
2
2
 
3
- **API Endpoint:** `/v3/action/log`
3
+ **Acesso Studio:** sem tela dedicada no backend; os logs são consumidos via API e aparecem indiretamente no frontend do Studio (atividade do jogador / módulo Action). Nenhuma rota `/studio/...` é definida neste serviço.
4
+ **API Endpoint:** `/v3/action/log` (gamificação) · `/v3/system/action/log` (sistema) · `/2.0.0/action/log` (legado)
5
+ **Coleção MongoDB:** `action_log`
4
6
 
5
- ## O que é
7
+ ---
6
8
 
7
- Registro detalhado de todas as ações executadas pelos jogadores. Armazena cada ação realizada, com detalhes como quem executou, qual ação foi feita e quando ocorreu. Esse registro alimenta todos os outros módulos da plataforma.
9
+ ## 1. Visão Geral
8
10
 
9
- ## Quando usar
11
+ O módulo `action-log` é o **ponto de entrada de eventos de jogador** na gamificação Funifier. Cada documento da coleção `action_log` representa uma ocorrência de uma `Action` previamente cadastrada — quem fez (`userId`), o que fez (`actionId`), quando (`time`) e com quais dados (`attributes`).
10
12
 
11
- - Para registrar que um jogador executou uma ação
12
- - Para alimentar desafios, rankings e recompensas
13
- - Para importar ações em lote de sistemas externos
14
- - Para rastrear comportamento dos jogadores
13
+ Papel arquitetural:
15
14
 
16
- ## Dependências
15
+ - É o **gatilho universal do motor de gamificação**: registrar um action log é o que dispara a avaliação de desafios, pontuação, níveis, triggers e webhooks. O processamento real acontece em `AchievementManager.fireAction(ActionLog)` (ver `achievement.md` §2.2) — este módulo apenas valida, persiste e enfileira.
16
+ - É também a **fonte de verdade histórica para contadores baseados em frequência**: desafios, KPIs e regras de elegibilidade contam/agrupam documentos de `action_log` por jogador, por time e por janela de tempo (métodos `countActionLogsRegistered*`).
17
+ - A escrita é **assíncrona por padrão** — a chamada HTTP retorna imediatamente e o processamento ocorre em uma fila em memória por gamificação.
17
18
 
18
- - **Action**: a ação referenciada deve existir
19
- - **Player**: o jogador referenciado deve existir
19
+ Problemas que resolve:
20
20
 
21
- ## API Endpoints
21
+ - Desacopla a captura do evento (rápida, sob alta carga) do seu processamento (caro: avaliação de desafios, agregações).
22
+ - Mantém um log auditável de tudo que cada jogador executou, com atributos arbitrários por evento.
23
+ - Permite contagens e agregações temporais (frequência, somatórios de atributos) diretamente sobre o histórico bruto.
22
24
 
23
- ### Listar ActionLogs
24
- **Método:** GET
25
- **Endpoint:** `/v3/action/log`
25
+ Relação direta com outros módulos:
26
26
 
27
- **Exemplo de Resposta:**
27
+ - `action` — toda action log referencia uma `Action` (`actionId`). A `Action` define limites (`limit`) e atributos esperados. Sem action ativa correspondente, o log é rejeitado (na via de gamificação).
28
+ - `achievement` — `trackSynchonous` chama `AchievementManager.fireAction` ao final; é onde desafios são avaliados e recompensas geradas.
29
+ - `trigger` — `before_win` e `after_win` da entidade `action` são disparados em torno da persistência.
30
+ - `challenge`, `level`, `kpi-formulas`, `crossword`, etc. — consomem os contadores de action log (`countActionLogsRegisteredByUser/Team`, `findLatestActionLogDate...`) para avaliar regras.
31
+ - `csv-data` — `CsvManager.importActionLogRows` converte linhas de CSV em action logs.
32
+ - `question`, `quiz`, `account`, `arduino/SensorManager` — constroem `ActionLog` internamente e o injetam no pipeline.
33
+
34
+ ---
35
+
36
+ ## 2. Arquitetura e Fluxos
37
+
38
+ ### 2.1 Classes envolvidas
39
+
40
+ | Classe | Arquivo | Papel |
41
+ |---|---|---|
42
+ | `com.funifier.engine.action.ActionLog` | `engine/action/ActionLog.java` (8-77) | Entidade/POJO — documento raiz |
43
+ | `com.funifier.engine.action.ActionManager` | `engine/action/ActionManager.java` (35-839) | Manager de gamificação: track, validação de limites, queries/agregações, contadores |
44
+ | `com.funifier.engine.action.ActionDaoMongo` | `engine/action/ActionDaoMongo.java` | Acesso MongoDB (`action_log`, `action`, `trigger_html`) |
45
+ | `com.funifier.engine.action.ActionAsyncProcessor` | `engine/action/ActionAsyncProcessor.java` | Thread + fila em memória (`LinkedBlockingQueue`) |
46
+ | `com.funifier.engine.action.Limit` | `engine/action/Limit.java` | Constantes e config de limite por player/team/game |
47
+ | `com.funifier.rest.v3.rest.ActionRest` | `rest/v3/rest/ActionRest.java` | Controller REST v3 (`/v3/action`) |
48
+ | `com.funifier.rest.engine.ActionRest` | `rest/engine/ActionRest.java` | Controller legado (`/2.0.0/action`) |
49
+ | `com.funifier.rest.v3.rest.system.ActionRest` | `rest/v3/rest/system/ActionRest.java` | Controller de sistema (`/v3/system/action`) |
50
+ | `com.funifier.engine.system.action.ActionManager` | `engine/system/action/ActionManager.java` | Manager de sistema — escrita direta, **sem pipeline** |
51
+
52
+ > Não há `Repository` para a entidade `ActionLog` além do `ActionDaoMongo`; várias queries de `action_log` são montadas diretamente no `ActionManager`.
53
+
54
+ ### 2.2 Pipeline principal — `POST /v3/action/log`
55
+
56
+ Sequência real (método `ActionRest.insertLog`, `rest/v3/rest/ActionRest.java:408`):
57
+
58
+ ```
59
+ [1] Resolve a Action → ActionManager.findActionByIdOrName(log.actionId)
60
+ query: {$or:[{_id:#},{action:#}]} (aceita id OU nome)
61
+ Se null → 400 "action ... does not exist"
62
+ Se a.active==false → 400 "action ... is not active"
63
+
64
+ [2] Defaults silenciosos
65
+ - log.time = new Date() se null
66
+ - log.id = Guid.newShortGuid() se null
67
+ - log.actionId = a.getId() ← NORMALIZA para o _id canônico da Action
68
+
69
+ [3] Resolve o principal (jogador OU time)
70
+ - userId null/vazio/"me" → usa player do token; senão 400 "player is required"
71
+ - findPrincipalByUserOrTeamId(userId)≠null → ok (jogador ou time existente)
72
+ - findByAlternativeLogin(userId)≠null → userId = player.getId()
73
+ - senão → tenta auto-criar (ver §5); se não puder → 400
74
+
75
+ [4] Restrição de token de jogador
76
+ Se autenticado como jogador e player != log.userId → 400
77
+ (só pode registrar a própria ação)
78
+
79
+ [5] Avaliação de limite (a.limit), se a.limit.total != null → ver §5
80
+ Se count >= total → 400 "you have exced the limit ..."
81
+
82
+ [6] Persistência
83
+ - async != "false" → ActionManager.track(log) (enfileira, retorna já)
84
+ - async == "false" → ActionManager.trackSynchonous(log) (processa e retorna achievements)
85
+ ```
86
+
87
+ ### Fluxo de registro — `action-log`
88
+
89
+ ```mermaid
90
+ flowchart TD
91
+ A["POST /v3/action/log"] --> B{"Action existe<br/>e ativa?"}
92
+ B -- não --> E1["400 Bad Request"]
93
+ B -- sim --> C["Defaults: time, _id<br/>actionId = id canônico"]
94
+ C --> D{"Principal<br/>resolvido?"}
95
+ D -- não --> E2["400 / auto-create"]
96
+ D -- sim --> F{"Token de jogador<br/>= userId?"}
97
+ F -- não --> E3["400"]
98
+ F -- sim --> G{"Limite excedido?"}
99
+ G -- sim --> E4["400 limit exceeded"]
100
+ G -- não --> H{"async == 'false'?"}
101
+ H -- não --> Q["track() → fila em memória"]
102
+ H -- sim --> S["trackSynchonous()"]
103
+ Q -.->|thread async| S
104
+ S --> T1["trigger before_win"]
105
+ T1 --> P["addLog → save em action_log"]
106
+ P --> T2["trigger after_win"]
107
+ T2 --> FA["AchievementManager.fireAction()"]
108
+ FA --> R["achievements retornados"]
109
+ ```
110
+
111
+ ### 2.3 Núcleo de processamento — `trackSynchonous(ActionLog)`
112
+
113
+ `ActionManager.trackSynchonous` (`engine/action/ActionManager.java:322`) é o coração do pipeline. Ordem **exata** de execução:
114
+
115
+ 1. Cria `TriggerContext` e injeta uma lista `achievements` em `context.extra`.
116
+ 2. `triggerManager.execute(actionId, action, Trigger.ENTITY_ACTION, Trigger.EVENT_BEFORE_WIN, userId, context)` — dispara triggers `before_win` da entidade `action`. **Ocorre antes da persistência**, então um trigger pode mutar `attributes` antes do save.
117
+ 3. `addLog(action)` → `ActionDaoMongo.addLog` → `c.save(action)` na coleção `action_log`.
118
+ 4. `triggerManager.execute(... Trigger.EVENT_AFTER_WIN ...)` — dispara triggers `after_win`. **Ocorre após o save**; mutações aqui não re-persistem o log automaticamente.
119
+ 5. `AchievementManager.fireAction(action)` — avalia desafios, pontos, níveis, etc. (ver `achievement.md` §2.2).
120
+ 6. Retorna `fireAction(...)` + os achievements acumulados pelos triggers em `context.extra`.
121
+
122
+ > A entidade de trigger é `Trigger.ENTITY_ACTION = "action"`. Não existe entidade de trigger separada para action log — `ENTITY_ACTIONLOG = "action_log"` está **comentada** no código (`Trigger.java:13`).
123
+
124
+ ### 2.4 Sequência síncrona (`async=false`)
125
+
126
+ ```mermaid
127
+ sequenceDiagram
128
+ participant C as Cliente
129
+ participant R as ActionRest
130
+ participant AM as ActionManager
131
+ participant TM as TriggerManager
132
+ participant DB as Mongo (action_log)
133
+ participant ACH as AchievementManager
134
+
135
+ C->>R: POST /v3/action/log?async=false
136
+ R->>R: valida action, principal, limite
137
+ R->>AM: trackSynchonous(log)
138
+ AM->>TM: execute(action, before_win)
139
+ AM->>DB: save(log)
140
+ AM->>TM: execute(action, after_win)
141
+ AM->>ACH: fireAction(log)
142
+ ACH-->>AM: List<Achievement>
143
+ AM-->>R: achievements
144
+ R-->>C: 201 { action, achievements }
145
+ ```
146
+
147
+ ### 2.5 Processamento assíncrono — `ActionAsyncProcessor`
148
+
149
+ - No construtor de `ActionManager` (`:41-46`) é iniciada **uma thread por gamificação**, nomeada `Funifier-ActionLog-Async-<apiKey>`, rodando `ActionAsyncProcessor.run()`.
150
+ - `track(log)` apenas faz `queue.offer(log)` em um `LinkedBlockingQueue<ActionLog>` **em memória**.
151
+ - O loop (`run()`, `:32-61`): `poll()` retira da fila; **se** `actionId != null && userId != null` → `trackSynchonous`; senão a entrada é **descartada silenciosamente** (já foi removida pelo `poll`). `Thread.sleep(100)` só ocorre quando a fila está vazia.
152
+ - A fila **não é persistida**: reinício/crash do processo perde tudo que estava enfileirado e não processado. Há um paliativo manual: `PUT /v3/action/queue/save` despeja a fila atual na coleção `action_log_memory`.
153
+
154
+ ---
155
+
156
+ ## 3. Estrutura dos Objetos
157
+
158
+ ### 3.1 `ActionLog` — documento raiz (coleção `action_log`)
159
+
160
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
161
+ |---|---|---|---|---|
162
+ | `_id` | String | `Guid.newShortGuid()` se omitido | Não (gerado) | Short GUID alfanumérico (**não** é ObjectId). Mapeado de `id` via `@JsonProperty("_id")`. |
163
+ | `actionId` | String | — | Sim | Id da `Action`. Na via de gamificação é **normalizado** para o `_id` canônico da Action (aceita nome no input). |
164
+ | `userId` | String | token / auto | Sim | Id do jogador OU id do time (principal). Aceita `"me"` (resolve via token). |
165
+ | `time` | Date (BSON date, UTC) | `new Date()` se omitido | Não | Momento da ação. Mongo armazena em GMT0. |
166
+ | `attributes` | Map<String,Object> | `null` | Não | Dados livres do evento (ex: `{product, price, location}`). Tipos arbitrários. |
167
+
168
+ `@JsonIgnoreProperties(ignoreUnknown=true)` na classe: **qualquer campo extra enviado no JSON é ignorado silenciosamente** (não persiste e não gera erro).
169
+
170
+ **Campos legados/comentados (não existem em runtime):**
171
+
172
+ - `configId` — comentado (`ActionLog.java:19`); resquício de uma versão que ligava o log a uma "action config".
173
+ - `entity` (JsonElement) e `attributes` como `DBObject` — comentados (`:25-26`); substituídos por `Map<String,Object>`.
174
+ - Construtor `ActionLog(configId, actionId, userId, time)` — comentado (`:48-53`).
175
+
176
+ **Não há** campos `extra`, `origin`, `created`, `updated` ou índice de tenant no documento. O isolamento entre gamificações é por **banco/conexão** (ver §8), não por campo.
177
+
178
+ ### 3.2 `Limit` — configuração de limite (vive na `Action`, não no log)
179
+
180
+ Define quantas vezes a action pode ser registrada num período. Avaliado em `insertLog` / `trackWithRestrictions` / `evaluateLimit`.
181
+
182
+ | Campo | Tipo | Descrição |
183
+ |---|---|---|
184
+ | `total` | Object (Number **ou** String) | Limite numérico, ou expressão Mustache+exp4j (ex: `{{player.extra.max_actions}}`). |
185
+ | `per` | String | Escopo do limite: ver constantes abaixo. |
186
+ | `every` | String | Janela móvel (ex: `5h`, `1d`). Default `-10y` (praticamente sem limite temporal). |
187
+ | `query` | String | Filtro Mongo adicional, Mustache-parsed (mín. 3 chars). Ex: `attributes.video:'{{action_log.attributes.video}}'`. |
188
+
189
+ Constantes de `Limit.per`:
190
+
191
+ | Valor | Constante | Comportamento na contagem |
192
+ |---|---|---|
193
+ | `player` | `PER_PLAYER` | Conta `{userId, actionId, time>=from [, query]}` |
194
+ | `gamification` | `PER_GAME` | Conta `{actionId, time>=from [, query]}` (toda a gamificação) |
195
+ | `team` | `PER_TEAM` | **Declarado mas NÃO tratado** nas verificações de limite (ver §7) |
196
+
197
+ ---
198
+
199
+ ## 4. Endpoints
200
+
201
+ ### `GET /v3/action/log` — consultar action logs
202
+
203
+ | Aspecto | Detalhe |
204
+ |---|---|
205
+ | Finalidade | Listar/paginar action logs |
206
+ | Método | `ActionManager.findAllActionLogs` (`:534`) |
207
+ | Autenticação | Bearer token |
208
+ | Paginação | Header `Range: items=0-100` (default 100) |
209
+
210
+ **Query params:** `player` (`"me"` resolve via token), `action`, `q` (critério Mongo bruto), `published_min`/`published_max` (RFC 3339 ou keyword `-1d`,`-30m`...), `orderby` (`actionId`/`userId`/`time`), `reverse`, `max_results` (default 100).
211
+
212
+ **Comportamento real:** monta um pipeline `$match` + `$sort` + `$limit`. `q` e `orderby` são **concatenados crus** na query (ver §8). Sempre limita a 100 se `max_results <= 0`.
213
+
214
+ ### `GET /v3/action/log/frequency` — frequência agrupada por tempo
215
+
216
+ | Aspecto | Detalhe |
217
+ |---|---|
218
+ | Finalidade | Agrupar ações por bucket temporal |
219
+ | Método | `ActionManager.findAllActionLogsFrequency` (`:629`) |
220
+ | Autenticação | Bearer token |
221
+
222
+ **Query params:** `player`, `action`, `q`, `published_min/max`, `every` (ex: `1d`, `3M`), `time_zone` (ex: `America/Sao_Paulo`), `sum_field` (soma `attributes.<sum_field>` em vez de contar ocorrências).
223
+
224
+ **Comportamento real:** Mongo grava datas em GMT0; `time_zone` aplica um offset via `$add`/`$subtract` antes do agrupamento. Sem `sum_field`, usa `total: {$sum: 1}`. O agrupamento usa `$dayOfYear`/`$week`/`$month`/`$year` + `$mod` para janelas múltiplas (`everyAmount`).
225
+
226
+ ### `POST /v3/action/log/aggregate` — agregação livre
227
+
228
+ | Aspecto | Detalhe |
229
+ |---|---|
230
+ | Finalidade | Rodar pipeline de agregação arbitrário |
231
+ | Método | `ActionManager.findAllActionLogsAggregate` (`:581`) |
232
+ | Autenticação | Bearer token |
233
+ | Body | Lista de stages (strings) de aggregation Mongo |
234
+
235
+ **Comportamento real:** aplica um `$match` (filtros player/action/q/time) e **acrescenta os stages do body** após ele. Default (body vazio) sugerido: `["{$group:{_id:\"$userId\",total:{$sum:1},start:{$min:\"$time\"},end:{$max:\"$time\"}}}"]`. Superfície de injeção (§8).
236
+
237
+ ### `POST /v3/action/log` — registrar action log
238
+
239
+ | Aspecto | Detalhe |
240
+ |---|---|
241
+ | Finalidade | Registrar ocorrência de uma action |
242
+ | Método | `ActionRest.insertLog` (`:408`) |
243
+ | Autenticação | Bearer token |
244
+ | Full replace ou patch | Insert; com `_id` reutilizado vira **replace + reprocessamento** (§5) |
245
+
246
+ **Query param `async`:** **somente a string literal `"false"`** ativa o modo síncrono (`if(async != null && "false".equals(async))`). Qualquer outro valor (`"true"`, `"sync"`, `"0"`, ausência) → assíncrono. No modo síncrono a resposta inclui os achievements gerados.
247
+
248
+ **Exemplo de request (síncrono):**
249
+ ```
250
+ POST /v3/action/log?async=false
251
+ { "actionId": "buy", "userId": "player@x.com", "attributes": { "product": "book", "price": 86.5 } }
252
+ ```
253
+ **Response 201:**
28
254
  ```json
29
- [
30
- {
31
- "_id": "6509e4e28325771ffaa4506e",
32
- "actionId": "sell",
33
- "userId": "jerry",
34
- "time": 1695147234712,
35
- "attributes": {
36
- "product": "bike",
37
- "price": 1200
38
- }
39
- }
40
- ]
255
+ { "action": { "_id": "aB3xQ", "actionId": "buy", "userId": "player@x.com", "time": 1737300000000, "attributes": { "product": "book", "price": 86.5 } },
256
+ "achievements": [ { "type": 0, "total": 10, "point": "xp" } ] }
41
257
  ```
42
258
 
43
- ### Registrar ActionLog
44
- **Método:** POST
45
- **Endpoint:** `/v3/action/log`
259
+ ### `POST /v3/action/log/bulk` — registro em lote
260
+
261
+ | Aspecto | Detalhe |
262
+ |---|---|
263
+ | Finalidade | Registrar múltiplas action logs |
264
+ | Método | `ActionRest.insertLogBulk` (`:564`) |
265
+ | Autenticação | Bearer token |
266
+ | Modo | **Sempre assíncrono** (não há opção `async`) |
267
+
268
+ **Comportamento real:** valida item a item; falhas **não interrompem** o lote — itens inválidos vão para `content_with_not_allowed_actions` / `content_with_not_allowed_players` e contam em `total_ignored`. **A restrição de "jogador só registra a própria ação" NÃO existe nesta rota** (ver §7/§8).
46
269
 
47
- **Exemplo de Body:**
270
+ **Response 201:**
48
271
  ```json
272
+ { "total_registered": 2, "content_size": 3, "total_ignored": 1, "content": [ ... ],
273
+ "content_with_not_allowed_players": [ { "actionId": "buy", "userId": "inexistente" } ] }
274
+ ```
275
+
276
+ ### `DELETE /v3/action/log/{id}` — remover action log
277
+
278
+ `ActionRest.deleteLog` → `ActionManager.deleteActionLog` (`:824`): `log.remove("{_id:#}", id)`. **Não estorna** pontos/achievements já gerados pelo log.
279
+
280
+ ### `GET /v3/action/log/attribute/{attribute}/index/create` — criar índice geoespacial
281
+
282
+ `create2dAttributeIndex` (`:765`): cria índice `2dsphere` em `attributes.<attribute>` (para queries `$near`/geo em checkins). Disponível a qualquer Bearer token.
283
+
284
+ ### `GET /v3/action/log/attribute/{attribute}/index/drop` — remover índice geoespacial
285
+
286
+ `drop2dAttributeIndex` (`:779`): **a resposta `indexes` é montada ANTES do `dropIndex`** — portanto reflete o estado pré-remoção, incluindo o índice que está sendo removido (ver §9).
287
+
288
+ ### Endpoints de fila assíncrona
289
+
290
+ | Endpoint | Método | Ação |
291
+ |---|---|---|
292
+ | `/v3/action/queue/info` | GET | Estatísticas da fila (`size`, `remaining_capacity`, durações, maior log) |
293
+ | `/v3/action/queue/info` | DELETE | Zera as estatísticas |
294
+ | `/v3/action/queue/clear` | DELETE | **Esvazia a fila** (descarta logs não processados) |
295
+ | `/v3/action/queue/first` | GET | Primeiros 100 itens da fila (sem remover) |
296
+ | `/v3/action/queue/last` | GET | Últimos 100 itens da fila (sem remover) |
297
+ | `/v3/action/queue/save` | PUT | Despeja a fila em `action_log_memory` |
298
+
299
+ ### `POST /v3/system/action/log` — registro de sistema (sem pipeline)
300
+
301
+ `system.ActionRest.insertLog` → `system.ActionManager.insertLog` (`engine/system/action/ActionManager.java:27`). Grava direto em `action_log` (`c.save`), com apenas defaults de `time`/`_id`. **NÃO valida action, NÃO valida player, NÃO dispara triggers, NÃO chama fireAction.** Também expõe `GET /v3/system/action/log/{id}`, `DELETE /v3/system/action/log/{id}` e `POST /v3/system/action/log/aggregate`. Use apenas para inserir registros brutos que **não devem** acionar gamificação.
302
+
303
+ ### `GET|POST /2.0.0/action/log` — endpoint legado
304
+
305
+ `rest/engine/ActionRest.java`. Autenticação por `?api_key=` (query param). O `POST` apenas seta `time` e chama `track(log)` — **sem validar action, player ou limite**. Mantido por compatibilidade.
306
+
307
+ ---
308
+
309
+ ## 5. Regras de Negócio
310
+
311
+ Regras que existem no código mas não no schema do documento:
312
+
313
+ - **Normalização de `actionId` (gamificação):** `findActionByIdOrName` casa por `_id` **ou** por `action` (nome); em seguida `log.actionId` é sobrescrito pelo `_id` canônico. Postar pelo nome funciona, mas o persistido é sempre o id. As vias legado (`/2.0.0`) e sistema (`/v3/system`) **não** normalizam — persistem `actionId` verbatim.
314
+ - **Auto-criação de jogador:** se `userId` não existe como principal nem como login alternativo, o sistema tenta `AuthenticationManager.createIfDontExist`, respeitando `Account.players_allowed`. Se o limite da conta foi atingido → 400; se a gamificação não permite auto-criação → 400.
315
+ - **`"me"` como `userId`:** resolvido para o jogador do token.
316
+ - **Restrição de token de jogador:** em `POST /v3/action/log`, um token de **jogador** só registra ações do próprio `userId`. Esta checagem **não** existe em `/bulk`, está **comentada** em `trackWithRestrictions` (`ActionManager.java:167-169`, usado pelo CSV) e inexiste no legado/sistema.
317
+ - **Limite (`a.limit`):** quando `a.limit.total != null` e `> 0`, conta documentos em `action_log` na janela `time >= from` (`from = now - every`, ou `-10y`). `PER_PLAYER` filtra por `userId`; `PER_GAME` por toda a gamificação; `PER_TEAM` **não tem branch** (não limita nada). `total` pode ser expressão (`{{...}}` + exp4j); falha de parse → 400.
318
+ - **Idempotência / risco de upsert:** `addLog` usa `c.save(action)`, que é **upsert por `_id`**. Reenviar um `_id` já existente **substitui** o documento **e dispara `fireAction` novamente** — podendo recontabilizar recompensas. Não há proteção contra reprocessamento por id repetido.
319
+ - **Multi-tenant:** toda operação resolve a conexão via `FrontController.getInstance(apiKey).getManagerFactory().getJongoConnection()` — cada gamificação tem seu próprio contexto Mongo. O documento `action_log` não carrega campo de tenant.
320
+ - **Consistência eventual:** no modo assíncrono (default), o retorno 201 **não garante** que o log foi processado nem persistido — apenas enfileirado.
321
+
322
+ ---
323
+
324
+ ## 6. Comportamentos Automáticos
325
+
326
+ | Comportamento | Trigger | Impacto | Persistência |
327
+ |---|---|---|---|
328
+ | Default de `time` | `time == null` no insert | `time = new Date()` | Sim |
329
+ | Default de `_id` | `id == null` no insert | `id = Guid.newShortGuid()` | Sim |
330
+ | Normalização de `actionId` | via gamificação | `actionId = action._id` | Sim |
331
+ | Trigger `before_win` | antes do save | Pode mutar `attributes`, gerar achievements | Mutação persiste (save ocorre depois) |
332
+ | Persistência | `addLog` | `save` em `action_log` | Sim |
333
+ | Trigger `after_win` | após o save | Efeitos colaterais; não re-salva o log | Não (salvo re-save explícito) |
334
+ | `fireAction` | fim de `trackSynchonous` | Avalia desafios/pontos/níveis/webhooks | Gera docs em `achievement` etc. |
335
+ | Auto-criação de jogador | `userId` inexistente | Cria `Player` (se permitido) | Sim |
336
+ | Descarte de log inválido | `actionId`/`userId` null na fila | Item removido sem aviso | Não |
337
+
338
+ ```mermaid
339
+ flowchart LR
340
+ IN["track(log)"] --> Q["fila em memória"]
341
+ Q --> CK{"actionId e userId<br/>não nulos?"}
342
+ CK -- não --> DROP["descarta (sem log)"]
343
+ CK -- sim --> BW["before_win"]
344
+ BW --> SV["save action_log"]
345
+ SV --> AW["after_win"]
346
+ AW --> FA["fireAction → achievements / level / webhook"]
347
+ ```
348
+
349
+ ---
350
+
351
+ ## 7. Suportado vs NÃO Suportado
352
+
353
+ ### ✅ Suportado
354
+
355
+ - Registro síncrono e assíncrono (`/v3/action/log`).
356
+ - Registro em lote tolerante a falhas (`/v3/action/log/bulk`).
357
+ - Consulta paginada, por frequência temporal e por agregação livre.
358
+ - Limite `per player` e `per gamification`, com janela `every` e filtro `query` dinâmico.
359
+ - Atributos arbitrários por evento, incluindo geolocalização (`location` + índice `2dsphere`).
360
+ - Contadores reutilizáveis por player e por time (usados por challenge/level/kpi).
361
+ - Importação via CSV (`CsvManager.importActionLogRows` → `trackWithRestrictions`).
362
+ - Inserção de sistema sem gamificação (`/v3/system/action/log`).
363
+
364
+ ### ❌ NÃO Suportado / armadilhas
365
+
366
+ - **Limite `per team`:** `Limit.PER_TEAM` (`"team"`) é declarado mas **nenhuma verificação de limite o trata** (`insertLog`, `insertLogBulk`, `trackWithRestrictions`, `evaluateLimit` só tratam `PER_PLAYER` e `PER_GAME`). Configurar `per: "team"` → sem limite efetivo.
367
+ - **Entidade de trigger `action_log`:** `Trigger.ENTITY_ACTIONLOG = "action_log"` está **comentada**. Triggers disparam só na entidade `action`.
368
+ - **Durabilidade da fila assíncrona:** a fila é `LinkedBlockingQueue` em memória; **reinício/crash perde** logs enfileirados e não processados. O retorno 201 assíncrono não é garantia de processamento.
369
+ - **Estorno ao deletar:** `DELETE /v3/action/log/{id}` remove o documento mas **não reverte** pontos/achievements/níveis já concedidos.
370
+ - **Idempotência:** reenviar `_id` existente **reprocessa e pode duplicar** recompensas (upsert via `save`).
371
+ - **Restrição de jogador no `/bulk`, no legado e no sistema:** ausente — ver §8.
372
+ - **Resposta de `index/drop`:** mostra os índices **antes** da remoção (não reflete o pós-estado).
373
+ - **Campos legados `configId`, `entity`:** aceitos no JSON mas ignorados (`@JsonIgnoreProperties`); não persistem.
374
+
375
+ ---
376
+
377
+ ## 8. Segurança e Permissões
378
+
379
+ - **Autenticação:** Bearer token (v3 e sistema); `?api_key=` na via legada `/2.0.0`.
380
+ - **Isolamento multi-tenant:** por conexão Mongo derivada do `apiKey` (`FrontController.getInstance(apiKey)`), não por campo no documento.
381
+ - **Restrição de jogador (parcial):** apenas `POST /v3/action/log` impede um token de jogador de registrar ação de outro `userId`. **Lacunas:**
382
+ - `POST /v3/action/log/bulk` — sem a checagem; um token de jogador pode, em lote, enviar logs com `userId` de terceiros (desde que o principal exista).
383
+ - `trackWithRestrictions` (CSV) — a checagem está **comentada** (`ActionManager.java:167-169`).
384
+ - `/2.0.0/action/log` e `/v3/system/action/log` — sem checagem.
385
+ - **Injeção em queries (raw):** parâmetros concatenados sem whitelist/sanitização no pipeline de agregação:
386
+ - `q` em `findAllActionLogs`/`Aggregate`/`Frequency` (`query.append(", " + q)`).
387
+ - `orderby` em `findAllActionLogs` (`"{$sort:{" + orderby + " : #}}"`).
388
+ - `aggregations` (body) em `/log/aggregate` — stages crus.
389
+ Mitigado por serem de leitura/agregação, mas permitem expressões arbitrárias de Mongo sobre `action_log` do tenant.
390
+ - **Operações de índice abertas:** qualquer Bearer token pode criar/remover índices `2dsphere` (`/log/attribute/{attribute}/index/...`), impactando performance.
391
+ - **Endpoints de fila** (`/v3/action/queue/*`) expõem e permitem **limpar** a fila (DELETE `/queue/clear`) — descarta logs não processados.
392
+
393
+ ---
394
+
395
+ ## 9. Observabilidade e Troubleshooting
396
+
397
+ **Diagnóstico — o módulo está funcionando?**
398
+ - `GET /v3/action/log?player=<id>&orderby=time&reverse=true&max_results=10` — confirma persistência.
399
+ - `GET /v3/action/queue/info` — tamanho da fila e durações; fila crescente indica processamento lento/travado.
400
+
401
+ **Queries úteis (Mongo / aggregate):**
402
+ - Total por jogador na última semana:
403
+ ```
404
+ POST /v3/action/log/aggregate?published_min=-7d
405
+ [ "{$group:{_id:\"$userId\", total:{$sum:1}}}" ]
406
+ ```
407
+ - Frequência diária de uma action:
408
+ ```
409
+ GET /v3/action/log/frequency?action=buy&every=1d&time_zone=America/Sao_Paulo
410
+ ```
411
+ - Buscar um log específico: `GET /v3/system/action/log/{id}`.
412
+
413
+ **Erros comuns e causas:**
414
+
415
+ | Sintoma | Causa provável |
416
+ |---|---|
417
+ | `400 action ... does not exist` | `actionId` não casa por `_id` nem por nome; ou action de outro tenant |
418
+ | `400 action ... is not active` | `Action.active == false` |
419
+ | `400 player is required` | `userId` ausente e sem player no token |
420
+ | `400 ... not allowed to create new players` | `Account.players_allowed` atingido na auto-criação |
421
+ | `400 you have exced the limit ...` | `a.limit` atingido na janela `every` |
422
+ | Log não aparece / sem achievements | Modo assíncrono: ainda na fila, descartado (campos null) ou perdido em reinício |
423
+ | Recompensa duplicada | Reuso de `_id` (upsert reprocessa via `fireAction`) |
424
+ | Limite `per team` ignorado | `PER_TEAM` não é tratado (§7) |
425
+
426
+ **O que verificar quando algo não funciona:**
427
+ 1. A `Action` existe e está ativa nesta gamificação?
428
+ 2. O `userId` é um principal válido (jogador ou time)?
429
+ 3. Foi enviado com `_id` repetido?
430
+ 4. O modo é assíncrono? Cheque `/v3/action/queue/info`.
431
+ 5. Os triggers `before_win`/`after_win` da action lançam exceção (interrompem o pipeline)?
432
+
433
+ ---
434
+
435
+ ## 10. Exemplos Práticos
436
+
437
+ **Mínimo funcional:**
438
+ ```
439
+ POST /v3/action/log
440
+ { "actionId": "tweet", "userId": "player@funifier.com" }
441
+ ```
442
+
443
+ **Avançado (síncrono, com atributos e geolocalização):**
444
+ ```
445
+ POST /v3/action/log?async=false
49
446
  {
50
- "actionId": "sell",
51
- "userId": "jerry",
447
+ "actionId": "checkin",
448
+ "userId": "player@funifier.com",
449
+ "time": "2026-05-20T10:00:00-03:00",
52
450
  "attributes": {
53
- "product": "bike",
54
- "price": 1200
451
+ "company": "coca-cola",
452
+ "price": 1200,
453
+ "location": { "type": "Point", "coordinates": [-15.797922, -47.887791] }
55
454
  }
56
455
  }
57
456
  ```
457
+ Retorna `{ action, achievements }` — útil para ver imediatamente o que a ação desencadeou.
458
+
459
+ **Lote:**
460
+ ```
461
+ POST /v3/action/log/bulk
462
+ [ { "actionId": "sell", "userId": "jerry", "attributes": { "price": 1200 } },
463
+ { "actionId": "sell", "userId": "tom", "attributes": { "price": 25 } } ]
464
+ ```
58
465
 
59
- ### Registrar Múltiplos ActionLogs
60
- **Método:** POST
61
- **Endpoint:** `/v3/action/log/bulk`
466
+ **Anti-pattern reenviar com `_id` fixo "para atualizar":**
467
+ ```
468
+ POST /v3/action/log
469
+ { "_id": "fixed-123", "actionId": "buy", "userId": "ana", "attributes": { "price": 10 } }
470
+ ```
471
+ ❌ Por quê: `save` faz upsert e `fireAction` roda de novo → recompensas podem ser **duplicadas**. Action log é um evento imutável; nunca reutilize `_id` para "editar".
62
472
 
63
- **Exemplo de Body:**
64
- ```json
65
- [
66
- {
67
- "actionId": "sell",
68
- "userId": "jerry",
69
- "attributes": { "product": "bike", "price": 1200 }
70
- },
71
- {
72
- "actionId": "sell",
73
- "userId": "tom",
74
- "attributes": { "product": "book", "price": 25 }
75
- }
76
- ]
473
+ **Anti-pattern confiar no 201 assíncrono como confirmação:**
474
+ ```
475
+ POST /v3/action/log // async (default)
476
+ → 201, mas o log pode falhar/ser descartado/perder-se na fila
77
477
  ```
478
+ ❌ Para confirmação de processamento, use `async=false` ou consulte depois via `GET /v3/action/log`.
479
+
480
+ **Anti-pattern — usar `/v3/system/action/log` esperando gamificação:**
481
+ ❌ Esse endpoint grava o documento mas **não** dispara desafios/triggers/achievements. Para gamificar, use `/v3/action/log`.
78
482
 
79
- ### Excluir ActionLog
80
- **Método:** DELETE
81
- **Endpoint:** `/v3/action/log/:id`
483
+ ---
82
484
 
83
- ## Validações e Testes
485
+ ## Checklist de Configuração
84
486
 
85
- - [ ] ActionLog aparece na lista com dados corretos
86
- - [ ] Ao registrar action log, desafios relacionados progridem
87
- - [ ] Bulk insert registra múltiplas ações corretamente
88
- - [ ] Atributos são armazenados conforme definido na ação
487
+ - [ ] A `Action` referenciada existe e está **ativa** antes de registrar o log.
488
+ - [ ] `userId` é um principal válido (jogador ou time), ou auto-criação está habilitada e dentro de `players_allowed`.
489
+ - [ ] Atributos numéricos usados em limite/regra estão no tipo correto (Number, não String).
490
+ - [ ] Se precisa do resultado imediato (achievements), use `async=false` — lembre que **só a string `"false"`** ativa o modo síncrono.
491
+ - [ ] **Nunca** reutilize `_id` (armadilha: upsert reprocessa e duplica recompensas).
492
+ - [ ] Para limite por time: lembre que `per: "team"` **não é aplicado** — use `per: "player"`/`"gamification"` ou valide fora.
493
+ - [ ] Para queries geoespaciais, crie o índice `2dsphere` via `/v3/action/log/attribute/{attr}/index/create` antes.
494
+ - [ ] Não dependa do retorno 201 assíncrono como garantia de processamento; monitore `/v3/action/queue/info`.
495
+ - [ ] Para inserir log bruto sem gamificação, use `/v3/system/action/log` conscientemente (sem triggers/fireAction).