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,42 +1,628 @@
1
- # Widget (Widget)
1
+ # `widget`
2
2
 
3
3
  **Acesso Studio:** `/studio/widget`
4
4
  **API Endpoint:** `/v3/widget`
5
+ **Coleção MongoDB:** `widget_v3` (instâncias e catálogo) + `widget_log` (registro de consumo)
5
6
 
6
- ## O que é
7
+ > Documentação de engenharia reversa baseada no código real de `funifier-service`. O módulo `/v3/widget` é implementado por `Widget_V3` / `WidgetManagerV3` / `WidgetManagerSystem` (pacote `com.funifier.engine.octalysis`). **Não confundir** com o subsistema legado de widgets (`engine.widget.Widget`, `engine.integration.html.WidgetHtml`, `engine.studio.widget`) que serve os endpoints legados de injeção HTML inline — esse é outro módulo, descrito na seção 7.
7
8
 
8
- Criação de componentes visuais para exibir técnicas de jogo. Permite criar e configurar widgets gráficos — como rankings, listas de desafios, status do jogador, lojas virtuais — para apresentar informações gamificadas de maneira clara e motivadora. Podem ser integrados em sistemas web existentes (intranets, CRMs, LMS).
9
+ ---
9
10
 
10
- ## Quando usar
11
+ ## 1. Visão Geral
11
12
 
12
- - Para exibir rankings em intranets
13
- - Para mostrar lista de desafios ativos
14
- - Para exibir status do jogador
15
- - Para integrar gamificação visualmente em sistemas existentes
13
+ O módulo `widget` armazena **componentes de UI da gamificação**: blocos auto-contidos de HTML + CSS + JavaScript, com internacionalização (`i18n`) e recursos externos (`resources`), que um frontend consome para renderizar uma técnica de jogo (ranking, status do jogador, lista de desafios, loja, etc.).
16
14
 
17
- ## Checklist de Configuração no Studio
15
+ Cada documento `Widget_V3` carrega quatro tipos de informação:
18
16
 
19
- - [ ] Definir tipo do widget
20
- - [ ] Configurar dados a exibir
21
- - [ ] Personalizar estilo visual
22
- - [ ] Gerar código de embed
17
+ - **Apresentação:** `html`, `css`, `script`, `resources` (URIs de JS/CSS externos), `i18n`.
18
+ - **Metadados de catálogo:** `title`, `description`, `image` (ícone), `screenshot`.
19
+ - **Vínculo de gamificação:** `techniques` (códigos de técnica de jogo "GT…") e `references` (ligações a outros componentes da estratégia).
20
+ - **Estilo de grafo:** `style` (`background`, `visible`) — usado na visualização de relacionamento de componentes.
23
21
 
24
- ## API Endpoints
22
+ Papel arquitetural:
25
23
 
26
- ### Listar Widgets
27
- **Método:** GET
28
- **Endpoint:** `/v3/widget`
24
+ - Existe em **dois escopos**, ambos na coleção `widget_v3`, mas em bancos diferentes:
25
+ - **Catálogo do sistema** (global, singleton `SystemFactory` → `WidgetManagerSystem`): widgets prontos, instaláveis por qualquer gamificação.
26
+ - **Instâncias do tenant** (por `api_key`, `ManagerFactory` → `WidgetManagerV3`): a cópia editável de cada gamificação.
27
+ - `POST /v3/widget/install/{id}` copia um widget do catálogo do sistema para o tenant.
28
+ - É um **repositório de conteúdo passivo**: o save é um upsert de substituição total por `_id`; o módulo **não dispara triggers, webhooks ou notificações** em nenhuma operação de escrita ou exclusão.
29
+ - Registra consumo em `widget_log` (`GET /v3/widget/{id}?player=…`), agregado por jogador + widget + **dia**.
29
30
 
30
- ### Criar Widget
31
- **Método:** POST
32
- **Endpoint:** `/v3/widget`
31
+ Relação com outros módulos:
33
32
 
34
- ### Deletar Widget
35
- **Método:** DELETE
36
- **Endpoint:** `/v3/widget/:id`
33
+ - `inline` (`InlineRest`) — serve `widget.script` e `widget.css` como arquivos `.js`/`.css` para embute em páginas externas.
34
+ - `game-technique` (`GameTechniqueManager`) — lê `widget_log` + `widget_v3.techniques` para calcular o **uso de cada técnica de jogo**; e usa `widget.style` / `widget.references` para montar o grafo de relacionamento de componentes (`findComponentsRelationship`).
35
+ - `analytics` / `statistic` — `widget_log` entra na contagem de **jogadores ativos** (`StatisticManager`) e na contagem bruta de eventos (`AnalyticsRest`).
36
+ - `packages` (`PackageManager`) — widgets são exportados/importados como componentes (`type = widget_v3`).
37
+ - `player` (`PlayerDaoMongo`) — exclusão de um jogador remove seus registros em `widget_log`.
37
38
 
38
- ## Validações e Testes
39
+ ---
39
40
 
40
- - [ ] Widget aparece na lista
41
- - [ ] Widget exibe dados corretos
42
- - [ ] Widget funciona integrado em página externa
41
+ ## 2. Arquitetura e Fluxos
42
+
43
+ ### 2.1 Classes envolvidas
44
+
45
+ | Classe | Papel |
46
+ |---|---|
47
+ | `com.funifier.engine.octalysis.Widget_V3` | Entidade/POJO — documento raiz da coleção `widget_v3` |
48
+ | `com.funifier.engine.octalysis.WidgetManagerV3` | Manager do **tenant**: insert, find, findAll, delete, findAllAggregate, **saveLog** |
49
+ | `com.funifier.engine.octalysis.WidgetManagerSystem` | Manager do **sistema** (catálogo global): insert, find, findAll, delete (sem saveLog, sem aggregate) |
50
+ | `com.funifier.engine.octalysis.WidgetLog` | Entidade de consumo — coleção `widget_log` |
51
+ | `com.funifier.rest.v3.rest.WidgetRest` | Controller REST v3 (`/v3/widget`) |
52
+ | `com.funifier.rest.v3.rest.InlineRest` | Serve `script`/`css` do widget como arquivos estáticos (`/v3/inline/widget-…`) |
53
+ | `com.funifier.engine.integration.trigger.ObjectStyle` | Sub-objeto `style` (`background`, `visible`) |
54
+ | `com.funifier.engine.integration.trigger.Reference` | Sub-objeto de `references` (ligação a outro componente) |
55
+
56
+ Não há `Repository`/`Dao` dedicado para `Widget_V3` — a manipulação MongoDB é feita diretamente via `Jongo` dentro dos managers. (As classes `WidgetDaoMongo` que existem no projeto pertencem ao subsistema **legado**, não ao `/v3/widget` — ver seção 7.)
57
+
58
+ ### 2.2 Pipeline de escrita — `WidgetManagerV3.insert(Widget_V3)`
59
+
60
+ Caminho de `POST /v3/widget`, `POST /v3/widget/install/{id}`, import de package e `POST /v3/widget/system` (via `WidgetManagerSystem.insert`, que é idêntico):
61
+
62
+ ```
63
+ [1] Se widget != null:
64
+ [1.1] se widget.id == null → widget.id = Guid.newShortGuid() (SHORT GUID, não ObjectId)
65
+ [1.2] se widget.created == null → widget.created = new Date()
66
+ [1.3] widget.updated = new Date() (SEMPRE sobrescreve)
67
+ [2] Resolve a coleção Entity.WIDGET_V3.collection ("widget_v3")
68
+ [3] Normalização de i18n: se widget.i18n.size() > 0 → recria o mapa com TODAS as chaves de locale em minúsculas
69
+ [4] c.save(widget) → upsert por _id (SUBSTITUIÇÃO TOTAL do documento)
70
+ ```
71
+
72
+ **Comportamento real / armadilhas:**
73
+
74
+ - O passo `[3]` lê `widget.i18n.size()` **fora** do guard `if (widget != null)`. Se `widget` for `null` (corpo vazio) ou se o cliente enviar `"i18n": null` explicitamente, ocorre **`NullPointerException` → HTTP 500**. Ver seção 9.
75
+ - `c.save()` é **substituição total**: campos omitidos no corpo passam a ter o valor default da classe (não há merge/patch). Não existe `PUT`.
76
+ - Nenhuma trigger, webhook ou notificação é disparada. A escrita é silenciosa.
77
+ - `created` é preservado apenas se já vier preenchido no corpo; como o save é full-replace, um update que **não** reenvie `created` recebe `new Date()` (perde a data de criação original).
78
+
79
+ ```mermaid
80
+ flowchart LR
81
+ A[POST /v3/widget] --> B{widget != null?}
82
+ B -- não --> N[i18n.size NPE → 500]
83
+ B -- sim --> C{id == null?}
84
+ C -- sim --> D[id = ShortGuid]
85
+ C -- não --> E[mantém id]
86
+ D --> F{created == null?}
87
+ E --> F
88
+ F -- sim --> G[created = now]
89
+ F -- não --> H[mantém created]
90
+ G --> I[updated = now]
91
+ H --> I
92
+ I --> J{i18n vazio?}
93
+ J -- não --> K[chaves de locale → minúsculas]
94
+ J -- sim --> L[save upsert por _id]
95
+ K --> L
96
+ ```
97
+
98
+ ### 2.3 Leitura + registro de consumo — `GET /v3/widget/{id}` e `saveLog`
99
+
100
+ ```
101
+ [1] Resolve player: se ausente, em branco ou "me" → authBean.getPlayerFromTokenIfExist()
102
+ [2] manager.find(id) → Widget_V3 (ou null se não existir)
103
+ [3] Se player != null E widget != null → manager.saveLog(id, player)
104
+ [4] Retorna o widget com campos null removidos (JsonUtil.toJsonRemoveNullFields)
105
+ ```
106
+
107
+ `WidgetManagerV3.saveLog(widget, player)` mantém um **contador diário idempotente** por par jogador+widget:
108
+
109
+ ```
110
+ time = trunca(hoje, "yyyy-MM-dd") // zera hora/min/seg via reparse do formato de data
111
+ current = widget_log.findOne({ player, widget, time })
112
+ se current existe:
113
+ log._id = current._id
114
+ log.total = current.total + 1 // incrementa
115
+ senão:
116
+ log._id = Guid.newShortGuid()
117
+ log.total = 1 // primeiro acesso do dia
118
+ widget_log.save(log) // upsert por _id
119
+ ```
120
+
121
+ ```mermaid
122
+ sequenceDiagram
123
+ participant Client
124
+ participant WidgetRest
125
+ participant WidgetManagerV3
126
+ participant widget_v3 as widget_v3 (Mongo)
127
+ participant widget_log as widget_log (Mongo)
128
+
129
+ Client->>WidgetRest: GET /v3/widget/{id}?player=me
130
+ WidgetRest->>WidgetRest: resolve player (token se "me"/vazio)
131
+ WidgetRest->>WidgetManagerV3: find(id)
132
+ WidgetManagerV3->>widget_v3: findOne({_id:#})
133
+ widget_v3-->>WidgetManagerV3: Widget_V3 | null
134
+ alt player != null E widget != null
135
+ WidgetRest->>WidgetManagerV3: saveLog(id, player)
136
+ WidgetManagerV3->>widget_log: findOne({player, widget, time=hoje})
137
+ widget_log-->>WidgetManagerV3: doc | null
138
+ WidgetManagerV3->>widget_log: save (total+1 ou total=1)
139
+ end
140
+ WidgetRest-->>Client: 200 + widget (campos null removidos)
141
+ ```
142
+
143
+ ### 2.4 Instalação — `POST /v3/widget/install/{id}`
144
+
145
+ ```
146
+ [1] widget = SystemFactory.getInstance().getWidgetV3Manager().find(id) // catálogo do sistema
147
+ [2] manager.getWidgetV3Manager().insert(widget) // tenant atual
148
+ ```
149
+
150
+ - O widget é inserido no tenant **com o mesmo `_id`** do widget de sistema (o `insert` não regenera `id` porque já está preenchido).
151
+ - Como `insert` faz upsert por `_id`, instalar de novo **sobrescreve** silenciosamente a instância local — incluindo edições que o tenant tenha feito.
152
+ - Se `id` **não existe** no catálogo do sistema → `find` retorna `null` → `insert(null)` → **NPE (500)** no passo `[3]` da seção 2.2.
153
+
154
+ ### 2.5 Listagem agregada — `WidgetManagerV3.findAllAggregate(aggregations, range)`
155
+
156
+ ```
157
+ StringBuilder query = new StringBuilder(); // NUNCA recebe append em nenhum ponto
158
+ ...
159
+ se query.length() > 0: → RAMO MORTO (sempre falso)
160
+ senão se aggregations não vazio: → aggregate(aggregations[0]).and(aggregations[1..n])
161
+ senão: → aggregate("{$match:{ }}") // coleção inteira
162
+ return PaginationUtil.getPageResultThrowsException(a, range, 0, 100)
163
+ ```
164
+
165
+ - O primeiro ramo (`query.toString().length() > 0`) é **código morto** — `query` jamais é preenchido.
166
+ - Sem `aggregations` no corpo → roda `{$match:{}}`, ou seja, **todos os widgets do tenant** paginados pelo header `Range` (default `items=0-100`).
167
+ - Os estágios do corpo são repassados **crus** ao MongoDB (mesma superfície de injeção de pipeline da seção 8).
168
+
169
+ ### 2.6 Consumo de `widget_log` por outros módulos
170
+
171
+ | Consumidor | O que faz com `widget_log` |
172
+ |---|---|
173
+ | `GameTechniqueManager` (linhas ~822-864) | Agrega `widget_log` por `widget`, faz `$lookup` em `widget_v3` para obter `techniques`, e soma o total por **técnica de jogo** → relatório de uso de técnicas (opcionalmente filtrado por `player`). É a realização concreta do comentário "calcular as motivações que mais influenciam este jogador" do `WidgetRest.find`. |
174
+ | `StatisticManager` (linha ~233) | `$lookup` de `widget_log` unido a `action_log` para o conjunto de **jogadores ativos** (`$setUnion`). |
175
+ | `AnalyticsRest` (linha ~256) | `count()` bruto de documentos em `widget_log`. |
176
+ | `PlayerDaoMongo` (linha ~106) | Ao excluir um jogador, remove `widget_log.remove({player:#})`. |
177
+ | `ConnectionPool` (linha ~149) | Cria índices em `widget_log`: `player`, `widget`, `time`. |
178
+
179
+ > O contador `widget_log` **é lido** (não é dado órfão). Porém, nenhum desses consumidores produz uma "lista de motivações por jogador" pronta — o que existe é a agregação de uso por técnica (`GameTechniqueManager`) e a contribuição para jogadores ativos.
180
+
181
+ ### 2.7 Conteúdo servido inline — `InlineRest`
182
+
183
+ | Endpoint | Origem |
184
+ |---|---|
185
+ | `GET /v3/inline/widget-{apikey}-{widget}.js` | `Widget_V3.script` (string crua, `Content-Type: text/javascript`) |
186
+ | `GET /v3/inline/widget-{apikey}-{widget}.css` | `Widget_V3.css` (string crua, `Content-Type: text/css`) |
187
+
188
+ Ambos retornam corpo vazio se o widget não existir ou se o campo estiver vazio/nulo. **Não há autenticação por token** nesses endpoints — o `api_key` vai no path (ver seção 8).
189
+
190
+ ---
191
+
192
+ ## 3. Estrutura dos Objetos
193
+
194
+ ### 3.1 `Widget_V3` — documento raiz (coleção `widget_v3`)
195
+
196
+ `@JsonIgnoreProperties(ignoreUnknown=true)` — campos não mapeados enviados no corpo são **descartados silenciosamente**.
197
+
198
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
199
+ |---|---|---|---|---|
200
+ | `_id` | String | `Guid.newShortGuid()` se ausente | Não (auto) | SHORT GUID interno do Funifier. **Não** é ObjectId do Mongo. Pode ser definido pelo cliente |
201
+ | `title` | String | — | Comentado como "required", **não validado** | Título do widget |
202
+ | `image` | String | — | Não | URL do ícone |
203
+ | `screenshot` | String | — | Não | URL da captura de tela |
204
+ | `description` | String | — | Comentado como "required", **não validado** | Descrição |
205
+ | `extra` | Map<String,Object> | `{}` | Não | Atributos arbitrários. Persistido, mas **não consumido** por nenhuma regra do core (passthrough). Substitui o antigo `tags` (ver abaixo) |
206
+ | `techniques` | List<String> | `[]` | Não | Códigos de técnica de jogo ("GT…") associados. Lista livre, **sem validação** (ver 3.5) |
207
+ | `style` | `ObjectStyle` | `null` | Não | Estilo do nó no grafo de componentes (ver 3.3) |
208
+ | `references` | List<`Reference`> | `null` | Não | Ligações a outros componentes da gamificação (ver 3.4) |
209
+ | `script` | String | — | Não | JavaScript do widget. Servido cru via `/v3/inline/…js` |
210
+ | `html` | String | — | Não | HTML do widget |
211
+ | `css` | String | — | Não | CSS do widget. Servido cru via `/v3/inline/…css` |
212
+ | `resources` | List<String> | `[]` | Não | URIs externos de JS/CSS (ex.: `https://code.angularjs.org/angular-1.0.1.js`) |
213
+ | `i18n` | Map<String, Map<String,String>> | `{}` | Não | Internacionalização. **Chaves de locale são forçadas a minúsculas no save.** Enviar `null` causa NPE (seção 2.2) |
214
+ | `created` | Date | `new Date()` se ausente no insert | Não (auto) | Data de criação. Serializada como epoch ms |
215
+ | `updated` | Date | `new Date()` **sempre** no insert | Não (auto) | Última atualização. Sempre sobrescrita no save |
216
+
217
+ #### Campos computados / não persistidos
218
+
219
+ Nenhum. Todos os campos da classe são persistidos.
220
+
221
+ #### Campos removidos / sanitizados silenciosamente no save
222
+
223
+ - **Chaves de `i18n`** → convertidas para minúsculas (`pt_BR` vira `pt_br`). O cliente não é avisado.
224
+ - **`updated`** → sempre substituído por `new Date()`, ignorando qualquer valor enviado.
225
+ - Campos fora do schema → descartados por `@JsonIgnoreProperties(ignoreUnknown=true)`.
226
+
227
+ #### Campo legado / deprecated
228
+
229
+ ```java
230
+ /** Tags related to this widget */
231
+ //public List<String> tags = new ArrayList<>();
232
+ public Map<String, Object> extra = new HashMap<>();
233
+ ```
234
+
235
+ O campo **`tags`** está **comentado** na entidade (linha 34) e foi substituído por `extra`. Enviar `"tags": [...]` no corpo é aceito pela requisição mas **silenciosamente descartado** (não mapeado → `ignoreUnknown`). Documentos antigos com `tags` no Mongo mantêm o campo, mas o runtime não o lê.
236
+
237
+ ### 3.2 `WidgetLog` — registro de consumo (coleção `widget_log`)
238
+
239
+ `@JsonIgnoreProperties(ignoreUnknown=true)`.
240
+
241
+ | Campo | Tipo | Padrão | Descrição |
242
+ |---|---|---|---|
243
+ | `_id` | String | `Guid.newShortGuid()` no primeiro acesso do dia | Identificador |
244
+ | `widget` | String | — | Id do widget consumido |
245
+ | `player` | String | — | Jogador que consumiu |
246
+ | `total` | double | 1 no primeiro acesso, incrementa +1 | Quantidade de acessos **no dia** |
247
+ | `time` | Date | hoje truncado a `yyyy-MM-dd` | Bucket diário (hora zerada) |
248
+
249
+ Um documento por (`player`, `widget`, `dia`). Não há histórico por requisição — apenas o agregado diário. Índices: `player`, `widget`, `time`.
250
+
251
+ ### 3.3 `ObjectStyle` — sub-objeto `style`
252
+
253
+ | Campo | Tipo | Descrição |
254
+ |---|---|---|
255
+ | `background` | String | Cor de fundo do nó no grafo de componentes |
256
+ | `visible` | Boolean | Se o nó é exibido no grafo |
257
+
258
+ Usado exclusivamente por `GameTechniqueManager.findComponentsRelationship` para desenhar o widget no grafo de relacionamento. Não afeta a renderização do widget em si.
259
+
260
+ ### 3.4 `Reference` — sub-objeto de `references`
261
+
262
+ | Campo | Tipo | Descrição |
263
+ |---|---|---|
264
+ | `way` | String | `"from"` (`WAY_FROM`) ou `"to"` (`WAY_TO`) — direção da ligação |
265
+ | `linkLabel` | String | Texto sobre a seta de conexão (vazio se ausente) |
266
+ | `_id` | String | Id do componente referenciado |
267
+ | `type` | String | Tipo/coleção do componente referenciado (ex.: `point_category`) |
268
+ | `title` | String | Título do componente referenciado |
269
+
270
+ No grafo: `way=from` → seta do componente **para** o widget (consumo); `way=to` → seta do widget **para** o componente (produção).
271
+
272
+ ### 3.5 Técnicas de jogo (`techniques`)
273
+
274
+ `techniques` é uma `List<String>` livre de códigos "GT…". **Não há validação** contra um catálogo — qualquer string é aceita e persistida. Os códigos canônicos vivem no módulo `game-technique` (`com.funifier.engine.system.technique.GameTechnique`), não no widget.
275
+
276
+ Códigos observados no código (`GameTechniqueManager`, atribuídos a outros componentes; servem de referência para `techniques`):
277
+
278
+ | Código | Significado operacional |
279
+ |---|---|
280
+ | `GT01` | Pontos (point_category) |
281
+ | `GT03` | Leaderboard |
282
+ | `GT05` | Blank fills (question) |
283
+ | `GT08` | Virtual good (catalog_item) |
284
+ | `GT13` | Widget (exemplo do apidoc de `WidgetRest`) |
285
+ | `GT26` | Elitism (competition) |
286
+ | `GT35` | Challenge |
287
+ | `GT53` | Last mile |
288
+ | `GT74` | Lottery |
289
+ | `GT85` | Level |
290
+
291
+ > O `$lookup` de `widget_log` → `widget_v3.techniques` (seção 2.6) só contabiliza uso de técnica para widgets cujo `techniques` esteja preenchido. Widget sem `techniques` não aparece no relatório de uso de técnicas.
292
+
293
+ ---
294
+
295
+ ## 4. Endpoints
296
+
297
+ Todos sob `@Path("v3/widget")`, `Produces: application/json; charset=UTF-8`. Autenticação: **Bearer token** (OAuth 2.0 `client_credentials`), exceto os endpoints de `InlineRest` (path com `api_key`).
298
+
299
+ ### 4.1 `GET /v3/widget/{id}`
300
+
301
+ | Aspecto | Detalhe |
302
+ |---|---|
303
+ | Finalidade | Buscar um widget e **registrar consumo** |
304
+ | Autenticação | Bearer token |
305
+ | Patch ou full | Leitura |
306
+
307
+ **Path / Query params:**
308
+
309
+ | Param | Tipo | Descrição |
310
+ |---|---|---|
311
+ | `id` | path | Id do widget |
312
+ | `player` | query | Jogador que consumiu. `me`, vazio ou ausente → resolvido pelo token. Se resolver para não-nulo, dispara `saveLog` |
313
+
314
+ **Comportamento real:** retorna o widget com campos `null` removidos. Se o id não existir, retorna `200` com corpo `null` (não 404). `saveLog` só ocorre se `player` e widget forem não-nulos.
315
+
316
+ ### 4.2 `GET /v3/widget`
317
+
318
+ | Aspecto | Detalhe |
319
+ |---|---|
320
+ | Finalidade | Listar **todos** os widgets do tenant |
321
+ | Autenticação | Bearer token |
322
+
323
+ Sem paginação e sem filtros — `findAll()` materializa a coleção inteira em memória (`IteratorUtils.toList`). Campos `null` removidos na resposta.
324
+
325
+ ### 4.3 `POST /v3/widget/aggregate`
326
+
327
+ | Aspecto | Detalhe |
328
+ |---|---|
329
+ | Finalidade | Aggregation pipeline arbitrário sobre `widget_v3` |
330
+ | Autenticação | Bearer token |
331
+ | Body | `List<Object>` — estágios do pipeline |
332
+ | Paginação | Header `Range` (default `items=0-100`, máx. 100) |
333
+
334
+ Corpo vazio → `{$match:{}}` (coleção inteira). Ver ramo morto na seção 2.5 e injeção na seção 8.
335
+
336
+ ### 4.4 `POST /v3/widget`
337
+
338
+ | Aspecto | Detalhe |
339
+ |---|---|
340
+ | Finalidade | Criar/substituir um widget do tenant |
341
+ | Autenticação | Bearer token |
342
+ | Patch ou full | **Full replace** (`save` upsert por `_id`) |
343
+
344
+ **Comportamento real:** gera `_id` se ausente; define `created`/`updated`; força chaves de `i18n` a minúsculas; **NPE 500 se o corpo for vazio ou `i18n: null`**; **não** dispara triggers/webhooks; **não** valida `title`/`description`. Retorna `201 Created` com o widget serializado.
345
+
346
+ ### 4.5 `POST /v3/widget/install/{id}`
347
+
348
+ | Aspecto | Detalhe |
349
+ |---|---|
350
+ | Finalidade | Copiar um widget do **catálogo do sistema** para o tenant |
351
+ | Autenticação | Bearer token |
352
+
353
+ Reusa o `_id` de origem → **sobrescreve** instância local de mesmo id. Id inexistente no sistema → **NPE 500**. Retorna `201` com o widget instalado.
354
+
355
+ ### 4.6 `DELETE /v3/widget/{id}`
356
+
357
+ | Aspecto | Detalhe |
358
+ |---|---|
359
+ | Finalidade | Remover um widget do tenant |
360
+ | Autenticação | Bearer token |
361
+
362
+ `remove({_id:#})`. Retorna `200` **sempre**, inclusive quando o id não existe (cliente não distingue "removi" de "não havia nada"). Não remove `widget_log` associado nem dispara triggers.
363
+
364
+ ### 4.7 Endpoints de sistema (catálogo global)
365
+
366
+ | Método | Path | Comportamento |
367
+ |---|---|---|
368
+ | `GET` | `/v3/widget/system/{id}` | Busca no catálogo do sistema |
369
+ | `GET` | `/v3/widget/system` | Lista todo o catálogo do sistema |
370
+ | `POST` | `/v3/widget/system` | Cria/substitui no catálogo do sistema |
371
+ | `DELETE` | `/v3/widget/system/{id}` | Remove do catálogo do sistema |
372
+
373
+ Operam via `SystemFactory.getInstance().getWidgetV3Manager()` (`WidgetManagerSystem`) → banco **global**, compartilhado por todos os tenants. **Só exigem autenticação (Bearer), sem guarda de papel/permissão** — ver seção 8.
374
+
375
+ ### 4.8 Endpoints inline (`InlineRest`)
376
+
377
+ | Método | Path | Produz |
378
+ |---|---|---|
379
+ | `GET` | `/v3/inline/widget-{apikey}-{widget}.js` | `text/javascript` (campo `script`) |
380
+ | `GET` | `/v3/inline/widget-{apikey}-{widget}.css` | `text/css` (campo `css`) |
381
+
382
+ Sem token; `api_key` no path. Corpo vazio se widget/campo ausente.
383
+
384
+ ### Exemplo de request/response
385
+
386
+ ```
387
+ POST /v3/widget
388
+ Authorization: Bearer eyJhbGciOi...
389
+ Content-Type: application/json
390
+ {
391
+ "_id": "MY",
392
+ "title": "My Widget",
393
+ "description": "Display my widget",
394
+ "techniques": ["GT13"],
395
+ "html": "<div>My Widget</div>",
396
+ "css": "",
397
+ "script": "",
398
+ "resources": [],
399
+ "i18n": {}
400
+ }
401
+ ```
402
+
403
+ ```json
404
+ HTTP/1.1 201 Created
405
+ {
406
+ "_id": "MY",
407
+ "title": "My Widget",
408
+ "description": "Display my widget",
409
+ "techniques": ["GT13"],
410
+ "html": "<div>My Widget</div>",
411
+ "resources": [],
412
+ "i18n": {},
413
+ "extra": {},
414
+ "created": 1685011053000,
415
+ "updated": 1685011053000
416
+ }
417
+ ```
418
+
419
+ ---
420
+
421
+ ## 5. Regras de Negócio
422
+
423
+ Regras presentes no código mas **ausentes do schema**:
424
+
425
+ ### 5.1 Multi-tenant por conexão, não por campo
426
+
427
+ Não há `apiKey` no documento. O isolamento é **inteiramente** dado pela conexão Jongo (`manager.getJongoConnection()`). Tenant ↔ banco. O catálogo do **sistema** é um banco à parte (singleton `SystemFactory`), comum a todos.
428
+
429
+ ### 5.2 Save é substituição total (não há PUT/patch)
430
+
431
+ Toda escrita é `c.save()` (upsert por `_id`). Atualizar = reenviar o documento inteiro. Campos omitidos voltam ao default — inclusive `created`, que é regravado com a data atual quando não reenviado.
432
+
433
+ ### 5.3 Sem validação de campos "obrigatórios"
434
+
435
+ `title` e `description` têm comentário "(required)" no código, mas **não há validação**. É possível criar um widget vazio (desde que o corpo não seja `null` e `i18n` não seja `null`).
436
+
437
+ ### 5.4 Normalização de locale
438
+
439
+ Chaves de `i18n` são forçadas a minúsculas no save. `pt_BR` e `pt_br` colidem no mesmo bucket.
440
+
441
+ ### 5.5 Contador de consumo é diário e idempotente
442
+
443
+ `saveLog` não cria um registro por acesso — incrementa o `total` do bucket diário (`yyyy-MM-dd`) do par (player, widget). Acessos no mesmo dia somam no mesmo documento.
444
+
445
+ ### 5.6 Instalação reusa o id de origem
446
+
447
+ `install` mantém o `_id` do widget de sistema, então reinstalar **sobrescreve** a instância editada do tenant.
448
+
449
+ ### 5.7 Nenhum efeito colateral de gamificação na escrita
450
+
451
+ Criar/editar/excluir widget **não** dispara triggers (`before_create`/`after_create`/`before_save`/`after_save`), webhooks nem notificações — diferentemente de `achievement`/`action`. Widget é conteúdo passivo.
452
+
453
+ ---
454
+
455
+ ## 6. Comportamentos Automáticos
456
+
457
+ | Comportamento | Trigger | Impacto | Persistência |
458
+ |---|---|---|---|
459
+ | Atribuição de `_id` (ShortGuid) | `insert` se `_id` é null | Cria id curto interno | Sim |
460
+ | `created = new Date()` | `insert` se `created` é null | Data de criação automática | Sim |
461
+ | `updated = new Date()` | `insert` (sempre) | Sobrescreve qualquer `updated` enviado | Sim |
462
+ | Locale → minúsculas | `insert` se `i18n` não vazio | Reescreve chaves de `i18n` | Sim |
463
+ | Incremento de `widget_log` | `GET /v3/widget/{id}` com player resolvido | Cria/incrementa bucket diário | Sim |
464
+ | Sobrescrita na instalação | `POST /install/{id}` | Substitui instância de mesmo id | Sim |
465
+ | Criação de índices `widget_log` | Bootstrap (`ConnectionPool`) | Índices em player/widget/time | Sim |
466
+ | Exclusão de `widget_log` ao remover player | `PlayerDaoMongo` ao excluir jogador | Limpa consumo do jogador | Sim |
467
+
468
+ ---
469
+
470
+ ## 7. Suportado vs NÃO Suportado
471
+
472
+ ### ✅ Suportado
473
+
474
+ - CRUD por tenant (`GET /{id}`, `GET`, `POST`, `DELETE /{id}`).
475
+ - Catálogo de sistema (`/v3/widget/system…`) e instalação (`/install/{id}`).
476
+ - Aggregation pipeline arbitrário (`POST /aggregate`) com paginação por `Range`.
477
+ - Registro de consumo diário em `widget_log` e seu uso em uso-de-técnica e jogadores-ativos.
478
+ - Conteúdo `script`/`css` servido inline (`InlineRest`).
479
+ - `i18n`, `resources`, `techniques`, `style`, `references`.
480
+ - Export/import via `packages`.
481
+
482
+ ### ❌ NÃO Suportado
483
+
484
+ - **Atualização parcial (`PUT`/patch)** — só full replace via `POST`.
485
+ - **Validação de `title`/`description`** — comentados como required, nunca validados.
486
+ - **Triggers/webhooks/notificações** no ciclo de vida do widget — nenhum é disparado.
487
+ - **Guarda de papel nos endpoints de sistema** — qualquer token válido escreve/remove no catálogo global (seção 8).
488
+ - **Campo `tags`** — comentado/removido; enviado é descartado. Use `extra`.
489
+ - **Uso do `extra` pelo core** — é persistido, mas nenhuma regra o lê (passthrough puro).
490
+ - **404 em recurso inexistente** — `GET` retorna `200` com `null`; `DELETE` retorna `200` sempre.
491
+ - **Filtro/paginação no `GET /v3/widget`** — devolve a coleção inteira em memória.
492
+ - **Corpo vazio / `i18n: null`** — provoca `NullPointerException` (500), não erro de validação.
493
+ - **Histórico de consumo por requisição** — `widget_log` agrega por dia; não há granularidade por acesso.
494
+
495
+ #### Subsistema legado de widgets (não é o `/v3/widget`)
496
+
497
+ Existem três artefatos com "Widget" no nome que **não** pertencem a este módulo e atendem os endpoints legados `/2.0.0` de injeção HTML inline:
498
+
499
+ - `com.funifier.engine.widget.Widget` — POJO com `category`/`path`/`image`/`active` (catálogo legado de widgets web/mobile). Gerido por `engine.studio.widget.WidgetManager` (sistema) via `SystemFactory.getWidgetManager()`. Usado por `MobileRest`, `StudioRest` e `InlineRest` legados.
500
+ - `com.funifier.engine.integration.html.WidgetHtml` (implementa `Config`) — vínculo de injeção HTML (`selector`/`page`/`bind`) em páginas externas. Gerido por `engine.widget.WidgetManager` (tenant) via `ManagerFactory.getWidgetManager()`.
501
+ - `WidgetDaoMongo` (em `engine.widget` e `engine.studio.widget`) — DAOs desse subsistema legado.
502
+
503
+ Esses componentes são independentes de `Widget_V3` e da coleção `widget_v3`. Não são manipulados por `/v3/widget`.
504
+
505
+ ---
506
+
507
+ ## 8. Segurança e Permissões
508
+
509
+ - **Autenticação:** Bearer token (OAuth 2.0 `client_credentials`) em todos os endpoints de `WidgetRest`. `WidgetRest` **não** declara `@RolesAllowed` nem chama `authBean.checkSystemPermission(...)`.
510
+ - **Isolamento por tenant:** garantido pela conexão Jongo por `api_key`. Sem campo `apiKey` no documento.
511
+ - **Catálogo de sistema sem guarda de papel (achado real):** `POST /v3/widget/system` e `DELETE /v3/widget/system/{id}` escrevem no banco **global** (`SystemFactory`) exigindo apenas um token válido — **sem verificação de admin/permissão**. Qualquer cliente autenticado pode criar/sobrescrever/remover widgets do catálogo compartilhado por todos os tenants.
512
+ - **Injeção de pipeline de aggregation:** `POST /v3/widget/aggregate` repassa os estágios do corpo crus ao MongoDB. Restrito ao banco do tenant, mas permite consultas arbitrárias (inclusive `$lookup` em outras coleções do mesmo tenant).
513
+ - **XSS armazenado / execução de JS arbitrário (achado real):** `script`, `html` e `css` são armazenados **sem sanitização** e servidos **verbatim** — `script` como `text/javascript` e `css` como `text/css` via `InlineRest`. Qualquer frontend que injete esse conteúdo numa página executa código sob controle de quem criou o widget. Os endpoints inline (`/v3/inline/widget-{apikey}-{widget}.js|.css`) **não exigem token** (o `api_key` vai no path), expondo o `script`/`css` publicamente a quem conhecer a URL.
514
+ - **Robustez:** corpo nulo ou `i18n: null` derruba o handler com NPE (500) em vez de retornar erro de validação (negação de serviço trivial por entrada malformada).
515
+
516
+ ---
517
+
518
+ ## 9. Observabilidade e Troubleshooting
519
+
520
+ ### Diagnóstico
521
+
522
+ ```
523
+ GET /v3/widget # lista todos os widgets do tenant
524
+ GET /v3/widget/{id} # confere um widget (cuidado: registra consumo se houver player)
525
+ GET /v3/widget/system # confere o catálogo de sistema
526
+ ```
527
+
528
+ ### Queries úteis (Mongo, banco do tenant)
529
+
530
+ ```js
531
+ // Existe o widget?
532
+ db.widget_v3.findOne({ _id: "MY" })
533
+
534
+ // Consumo de um widget por dia
535
+ db.widget_log.find({ widget: "MY" }).sort({ time: -1 })
536
+
537
+ // Consumo de um jogador hoje
538
+ db.widget_log.find({ player: "joao@empresa.com", time: ISODate("2026-05-20T00:00:00Z") })
539
+
540
+ // Uso por técnica (o que GameTechniqueManager calcula)
541
+ db.widget_log.aggregate([
542
+ { $group: { _id: "$widget", total: { $sum: "$total" } } },
543
+ { $lookup: { from: "widget_v3", localField: "_id", foreignField: "_id", as: "p" } },
544
+ { $unwind: "$p" }, { $unwind: "$p.techniques" },
545
+ { $group: { _id: "$p.techniques", total: { $sum: "$total" } } }
546
+ ])
547
+ ```
548
+
549
+ ### Erros comuns e causas
550
+
551
+ | Sintoma | Causa provável |
552
+ |---|---|
553
+ | `500` ao criar widget | Corpo vazio/`null` ou `"i18n": null` → NPE em `insert` (seção 2.2) |
554
+ | `500` ao instalar | `id` não existe no catálogo de sistema → `insert(null)` → NPE |
555
+ | `GET` retorna `null` com `200` | Widget não existe — não há 404 |
556
+ | `DELETE` "funciona" mas nada muda | `remove` retorna `200` mesmo sem documento |
557
+ | Locale do `i18n` "sumiu" | Chave foi convertida para minúsculas no save |
558
+ | `tags` não persistiu | Campo removido/legado — descartado por `ignoreUnknown`. Use `extra` |
559
+ | Widget editado voltou ao padrão | Reinstalação (`/install/{id}`) sobrescreveu a instância |
560
+ | `created` mudou após editar | Update full-replace sem reenviar `created` regrava a data |
561
+ | Widget não aparece no relatório de técnicas | `techniques` vazio, ou nenhum acesso registrado em `widget_log` |
562
+
563
+ ---
564
+
565
+ ## 10. Exemplos Práticos
566
+
567
+ ### Mínimo funcional
568
+
569
+ ```json
570
+ POST /v3/widget
571
+ {
572
+ "title": "Ranking",
573
+ "html": "<div id='rk'></div>"
574
+ }
575
+ ```
576
+
577
+ Gera `_id` automático, `created`/`updated` automáticos. (Atenção: **não** envie corpo vazio nem `"i18n": null`.)
578
+
579
+ ### Avançado (todos os campos relevantes)
580
+
581
+ ```json
582
+ POST /v3/widget
583
+ {
584
+ "_id": "leaderboard_widget",
585
+ "title": "Leaderboard",
586
+ "description": "Top 10 jogadores",
587
+ "image": "https://cdn.exemplo.com/icon.png",
588
+ "screenshot": "https://cdn.exemplo.com/shot.png",
589
+ "techniques": ["GT03"],
590
+ "style": { "background": "#1e293b", "visible": true },
591
+ "references": [
592
+ { "way": "from", "_id": "lb_principal", "type": "leaderboard", "title": "Ranking Geral", "linkLabel": "exibe" }
593
+ ],
594
+ "resources": ["https://code.jquery.com/jquery-3.7.1.min.js"],
595
+ "script": "fetch('/v3/leaderboard/lb_principal/leader/aggregate').then(...)",
596
+ "css": "#rk{font-family:sans-serif}",
597
+ "html": "<div id='rk'></div>",
598
+ "i18n": { "pt_br": { "title": "Ranking" }, "en": { "title": "Leaderboard" } }
599
+ }
600
+ ```
601
+
602
+ ### Anti-pattern (o que NÃO fazer)
603
+
604
+ ```json
605
+ POST /v3/widget
606
+ {
607
+ "title": "Quebrado",
608
+ "tags": ["ranking", "top"], // ❌ descartado: 'tags' é legado, use 'extra'
609
+ "i18n": null // ❌ NPE 500: i18n nunca pode ser null
610
+ }
611
+ ```
612
+
613
+ - Esperar **patch**: reenviar só `{ "title": "Novo" }` num widget existente **apaga** os demais campos (full replace).
614
+ - Confiar em `extra` como dado funcional: ele é persistido mas **nenhuma regra do core o lê**.
615
+ - Usar `POST /v3/widget/system` como conveniência: escreve no catálogo **global** de todos os tenants, sem guarda de papel.
616
+
617
+ ---
618
+
619
+ ## Checklist de Configuração
620
+
621
+ - [ ] Corpo do `POST` **nunca** vazio e `i18n` **nunca** `null` (evita NPE 500).
622
+ - [ ] `title` e `description` preenchidos (o código não valida, mas a UI espera).
623
+ - [ ] Locales de `i18n` já em minúsculas (serão convertidos de qualquer forma).
624
+ - [ ] Para atualizar, reenviar o **documento inteiro** (não há patch); reenviar `created` para preservá-lo.
625
+ - [ ] `techniques` preenchido com códigos GT se o widget deve aparecer no relatório de uso de técnicas.
626
+ - [ ] Conteúdo de `script`/`html`/`css` é confiável — é servido verbatim e executável (XSS).
627
+ - [ ] Escrita em `/v3/widget/system` é intencional (catálogo global, sem guarda de papel).
628
+ - [ ] Reinstalar via `/install/{id}` ciente de que sobrescreve a instância local de mesmo id.
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AAkJA,wBAA8B,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CzF"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"AA2LA,wBAA8B,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8CzF"}