funifier-mcp 0.2.26 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +4 -1
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/mcp/bundle.js +119 -93
  66. package/dist/mcp/index.js +2 -2
  67. package/dist/mcp/index.js.map +1 -1
  68. package/dist/mcp/resources/documentation.d.ts +1 -1
  69. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  70. package/dist/mcp/resources/documentation.js +39 -3
  71. package/dist/mcp/resources/documentation.js.map +1 -1
  72. package/dist/mcp/tools/connect.d.ts.map +1 -1
  73. package/dist/mcp/tools/connect.js +18 -8
  74. package/dist/mcp/tools/connect.js.map +1 -1
  75. package/dist/mcp/tools/database.d.ts.map +1 -1
  76. package/dist/mcp/tools/database.js +59 -47
  77. package/dist/mcp/tools/database.js.map +1 -1
  78. package/dist/mcp/tools/database.test.js +2 -2
  79. package/dist/mcp/tools/database.test.js.map +1 -1
  80. package/dist/mcp/tools/delete.d.ts.map +1 -1
  81. package/dist/mcp/tools/delete.js +13 -3
  82. package/dist/mcp/tools/delete.js.map +1 -1
  83. package/dist/mcp/tools/execute.d.ts.map +1 -1
  84. package/dist/mcp/tools/execute.js +20 -9
  85. package/dist/mcp/tools/execute.js.map +1 -1
  86. package/dist/mcp/tools/folder.d.ts.map +1 -1
  87. package/dist/mcp/tools/folder.js +22 -12
  88. package/dist/mcp/tools/folder.js.map +1 -1
  89. package/dist/mcp/tools/get.d.ts.map +1 -1
  90. package/dist/mcp/tools/get.js +16 -6
  91. package/dist/mcp/tools/get.js.map +1 -1
  92. package/dist/mcp/tools/index.d.ts +1 -1
  93. package/dist/mcp/tools/index.d.ts.map +1 -1
  94. package/dist/mcp/tools/index.js +3 -1
  95. package/dist/mcp/tools/index.js.map +1 -1
  96. package/dist/mcp/tools/list.d.ts.map +1 -1
  97. package/dist/mcp/tools/list.js +38 -14
  98. package/dist/mcp/tools/list.js.map +1 -1
  99. package/dist/mcp/tools/logs.d.ts.map +1 -1
  100. package/dist/mcp/tools/logs.js +15 -5
  101. package/dist/mcp/tools/logs.js.map +1 -1
  102. package/dist/mcp/tools/save.d.ts.map +1 -1
  103. package/dist/mcp/tools/save.js +14 -4
  104. package/dist/mcp/tools/save.js.map +1 -1
  105. package/dist/mcp/tools/save.test.js +3 -3
  106. package/dist/mcp/tools/save.test.js.map +1 -1
  107. package/dist/mcp/tools/search-docs.d.ts +3 -0
  108. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  109. package/dist/mcp/tools/search-docs.js +102 -0
  110. package/dist/mcp/tools/search-docs.js.map +1 -0
  111. package/package.json +6 -2
  112. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  113. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  114. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  115. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  116. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  117. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  118. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  119. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  120. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  121. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  122. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  123. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  124. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  125. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  126. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  127. package/skills/funifier/SKILL.md +88 -0
  128. package/skills/funifier/references/configure-security.md +96 -0
  129. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  130. package/skills/funifier/references/create-aggregate.md +144 -0
  131. package/skills/funifier/references/create-challenge.md +116 -0
  132. package/skills/funifier/references/create-competition.md +98 -0
  133. package/skills/funifier/references/create-crossword.md +574 -0
  134. package/skills/funifier/references/create-custom-object.md +91 -0
  135. package/skills/funifier/references/create-custom-page.md +135 -0
  136. package/skills/funifier/references/create-folder.md +104 -0
  137. package/skills/funifier/references/create-lastmile.md +643 -0
  138. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  139. package/skills/funifier/references/create-level.md +94 -0
  140. package/skills/funifier/references/create-lottery.md +913 -0
  141. package/skills/funifier/references/create-mystery.md +769 -0
  142. package/skills/funifier/references/create-notification.md +75 -0
  143. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  144. package/skills/funifier/references/create-quiz.md +98 -0
  145. package/skills/funifier/references/create-scheduler.md +141 -0
  146. package/skills/funifier/references/create-story.md +636 -0
  147. package/skills/funifier/references/create-swap.md +95 -0
  148. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  149. package/skills/funifier/references/create-virtual-good.md +96 -0
  150. package/skills/funifier/references/create-webhook.md +72 -0
  151. package/skills/funifier/references/create-websocket.md +71 -0
  152. package/skills/funifier/references/create-widget.md +76 -0
  153. package/skills/funifier/references/debug.md +87 -0
  154. package/skills/funifier/references/help.md +81 -0
  155. package/skills/funifier/references/implement-frontend.md +106 -0
  156. package/skills/funifier/references/import-csv.md +75 -0
  157. package/skills/funifier/references/manage-player.md +82 -0
  158. package/skills/funifier/references/manage-team.md +76 -0
  159. package/skills/funifier/references/upload-file.md +91 -0
  160. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  161. package/skills/funifier-create-challenge/SKILL.md +0 -88
  162. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  163. package/skills/funifier-create-level/SKILL.md +0 -87
  164. package/skills/funifier-create-quiz/SKILL.md +0 -87
  165. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  166. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  167. package/skills/funifier-debug/SKILL.md +0 -92
  168. package/skills/funifier-help/SKILL.md +0 -86
  169. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  170. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,30 +1,655 @@
1
- # CSV Data (Dados CSV)
1
+ # `csv`
2
2
 
3
- **API Endpoint:** `/v3/csv`
3
+ **API Base:** `/v3/csv`
4
+ **Coleções MongoDB:** `csv_config__c` (configurações de formato/importação), `csv_log__c` (logs de execução), `csv_format__c` (órfã — ver §7)
5
+ **Controller:** `com.funifier.rest.v3.rest.CsvRest`
6
+ **Manager:** `com.funifier.engine.csv.CsvManager` (1981 linhas, sem Repository/Dao dedicado — acesso direto via Jongo)
4
7
 
5
- ## O que é
8
+ > Documentação produzida por engenharia reversa do código-fonte de `funifier-service`. Reflete o **comportamento real** das classes, não o schema declarado nem a documentação anterior.
6
9
 
7
- Importação e exportação de dados no formato CSV. Permite importar ou exportar dados de forma fácil e segura, seja via upload manual ou FTP. Suporta arquivos criptografados para segurança no trânsito das informações.
10
+ ---
8
11
 
9
- ## Quando usar
12
+ ## 1. Visão Geral
10
13
 
11
- - Para importar listas de jogadores em massa
12
- - Para exportar relatórios de desempenho
13
- - Para migrar dados de sistemas externos
14
- - Para importação segura via FTP com criptografia
14
+ O módulo `csv` é o **motor de importação e exportação em massa** da plataforma. Ele converte arquivos CSV (texto plano ou upload multipart) em documentos MongoDB e converte resultados de aggregations MongoDB de volta em CSV/Excel.
15
15
 
16
- ## API Endpoints
16
+ Papel arquitetural — o módulo abriga **dois subsistemas de importação independentes**, que compartilham apenas utilitários de parsing:
17
17
 
18
- ### Importar CSV
19
- **Método:** POST
20
- **Endpoint:** `/v3/csv`
18
+ 1. **Import como `action_log`** (`POST /v3/csv/import` → `importActionLogRows`): orientado por um documento `csv_config__c` que define mapeamento de colunas (`match`), uma `action` alvo e um script Groovy opcional. Cada linha vira um `ActionLog` rastreado via `ActionManager.trackWithRestrictions`. **Síncrono**, parser ingênuo.
21
19
 
22
- ### Exportar CSV
23
- **Método:** GET
24
- **Endpoint:** `/v3/csv`
20
+ 2. **Bulk/merge em coleção arbitrária** (`POST /v3/csv/{collection}/bulk` e `/merge` → `insertBulk`/`mergeBulk`): orientado pela lista `fields[]` do `csv_config__c` (tipagem de campos). Persiste documentos em **qualquer** coleção nomeada na URL. **Assíncrono por padrão**, processado em lotes, cancelável, com triggers. Usa parser Apache Commons CSV.
25
21
 
26
- ## Validações e Testes
22
+ Além disso oferece:
27
23
 
28
- - [ ] Importação processa arquivo corretamente
29
- - [ ] Dados importados aparecem nas coleções corretas
30
- - [ ] Exportação gera arquivo CSV válido
24
+ - **Exportação** (`POST /v3/csv/{collection}/aggregate` e `/aggregate/to_excel`): executa um pipeline de aggregation arbitrário sobre uma coleção e devolve CSV (`text/plain`) ou planilha `.xls` (`application/octet-stream`).
25
+ - **Parsing/preview** (`/read`, `/split`, `/split/file`): converte CSV em linhas JSON sem persistir.
26
+ - **Descriptografia PGP** de uploads (campo `upload_encrypted` no config + `CryptTransferKey`).
27
+
28
+ Problemas que resolve: carga inicial de jogadores/times, ingestão recorrente de eventos como `action_log`, migração de sistemas externos, e geração de relatórios CSV/Excel a partir de aggregations.
29
+
30
+ Relação com outros módulos:
31
+
32
+ - `action` — `/import` chama `ActionManager.findActionById` (resolve a action e o tipo de cada atributo) e `ActionManager.trackWithRestrictions`; bulk em `action_log` usa `ActionManager.track`.
33
+ - `trigger` — bulk/merge dispara `csv_before_bulk`/`csv_after_bulk`; saves em coleções comuns disparam `before_create`/`after_create`.
34
+ - `crypt` — `CryptManager.findTransferKeyActive` + `decryptTransfer` (PGP) para uploads criptografados.
35
+ - Qualquer coleção — bulk grava, e aggregate lê, **qualquer** coleção nomeada na URL (sem allowlist). Ver §8.
36
+
37
+ > **Não há FTP.** A documentação anterior afirmava "importação via FTP" — não existe nenhum código de FTP no módulo. As únicas classes que referenciam `CsvManager` são `CsvRest`, `CsvAsyncProcessor` e `ManagerFactory`.
38
+
39
+ ---
40
+
41
+ ## 2. Arquitetura e Fluxos
42
+
43
+ ### 2.1 Classes envolvidas
44
+
45
+ | Classe | Papel |
46
+ |---|---|
47
+ | `com.funifier.rest.v3.rest.CsvRest` | Controller REST v3 (`/v3/csv`) — 12 endpoints |
48
+ | `com.funifier.engine.csv.CsvManager` | Manager monolítico: parsing, import, bulk, export, Groovy, criptografia |
49
+ | `com.funifier.engine.csv.CsvAsyncProcessor` | `Runnable` de thread única que drena uma fila de jobs `insertBulk`/`mergeBulk` assíncronos |
50
+ | `com.funifier.engine.csv.CsvConfig` | POJO da configuração (`csv_config__c`) — **modela só parte** do documento (ver §3.1) |
51
+ | `com.funifier.engine.csv.CsvLog` | POJO do log de execução (`csv_log__c`) |
52
+
53
+ ### 2.2 Os dois parsers de CSV (divergência crítica)
54
+
55
+ O módulo tem **dois parsers diferentes**, e o caminho escolhido depende do endpoint:
56
+
57
+ | Aspecto | `readRows` (linha 278) | `splitCsv` (linha 945) |
58
+ |---|---|---|
59
+ | Usado por | `/read`, `/import` | `/split`, `/split/file`, todo bulk/merge (via `convertCsvInObjects`) |
60
+ | Engine | `line.split(delimiter)` manual | Apache Commons CSV (`CSVParser`) |
61
+ | Aspas / escape | **Não suporta** (split cego) | Suporta (`withQuote('"')`) |
62
+ | Cabeçalho | Controlado por `header` (param/config) | **Sempre** `withFirstRecordAsHeader()` — `header` é ignorado |
63
+ | Normalização do header | NFD → remove não-ASCII → remove não-`[0-9a-zA-Z_]` → `toLowerCase().trim()` | Nenhuma — usa o nome literal |
64
+ | Delimitadores | `comma`, `semicolon`, `tab` (**sem pipe**) | `comma`, `semicolon`, `tab`, `pipe` |
65
+ | Linha curta (menos colunas que o header) | **Descartada silenciosamente** (`fields.length >= names.length`) | Tratada pelo Commons CSV |
66
+
67
+ > Consequência prática: `/import` e `/read` quebram com valores que contêm o delimitador entre aspas; bulk/merge não. E `pipe` só funciona em bulk/merge/split.
68
+
69
+ ### 2.3 Pipeline — `POST /v3/csv/import` (import como action_log)
70
+
71
+ Método-raiz: `importActionLogRows(String id, String content)` (linha 119). **Sempre síncrono** — o query param `async` é aceito e **ignorado** (o caminho assíncrono está comentado em `CsvRest.java:69-78`).
72
+
73
+ ```
74
+ [1] config = find(id) // lê csv_config__c[id]
75
+ [2] cria CsvLog(collection="action_log", status="started")
76
+ if(config.log) insertLog() // só persiste log se config.log == true
77
+ [3] if(config.script.length > 10):
78
+ script = getScript(config); clazz = compile(script) // Groovy sandbox (§2.6)
79
+ [4] keys = config.match.keySet()
80
+ if(config.action) action = ActionManager.findActionById(config.action)
81
+ [5] rows = readRows(config.delimiter, config.header, -1, content) // parser ingênuo (§2.2)
82
+ [6] para cada row:
83
+ [6.1] if(config.log && config.log_field): row[log_field] = importlog._id
84
+ [6.2] monta `match` (de→para):
85
+ para cada key em config.match:
86
+ coluna = config.match[key] // valor do match = NOME da coluna CSV
87
+ val = row[coluna]
88
+ se action e attribute(key).type == "Number": val = Double.parseDouble(val)
89
+ // exceção de parse → engolida em `err`, a linha continua
90
+ match[key] = val
91
+ [6.3] if(clazz): executor(clazz, config, row, match) // Groovy prepare(), timeout 5s
92
+ [6.4] if(config.action):
93
+ log = new ActionLog()
94
+ log.actionId = config.action
95
+ log.time = match["time"] (se Date)
96
+ log.userId = match["userId"] (se String)
97
+ log.id = match["_id"] (se String)
98
+ match.remove("time"); match.remove("userId"); match.remove("_id")
99
+ log.attributes = match
100
+ ActionManager.trackWithRestrictions(log, null)
101
+ [7] importlog.status="finished"; rows=totalrows
102
+ if(config.log) insertLog()
103
+ return { action, log, all (sempre vazio), err }
104
+ ```
105
+
106
+ > Se `config.action` for nulo, **nada é persistido**: as linhas são lidas e o script roda, mas não há `ActionLog` criado. `/import` só sabe importar `action_log`.
107
+
108
+ ```mermaid
109
+ flowchart LR
110
+ A[POST /v3/csv/import<br/>body = CSV] --> B[find csv_config__c]
111
+ B --> C[readRows<br/>parser ingenuo]
112
+ C --> D{para cada linha}
113
+ D --> E[match de->para<br/>via config.match]
114
+ E --> F{config.script?}
115
+ F -- sim --> G[Groovy prepare<br/>timeout 5s]
116
+ F -- nao --> H
117
+ G --> H{config.action?}
118
+ H -- sim --> I[ActionLog +<br/>trackWithRestrictions]
119
+ H -- nao --> J[nada persistido]
120
+ I --> K[CsvLog finished]
121
+ J --> K
122
+ ```
123
+
124
+ ### 2.4 Pipeline — `POST /v3/csv/{collection}/bulk` e `/merge`
125
+
126
+ `insertBulk`/`mergeBulk` (linhas 613/647) criam o `CsvLog` (status `started`) e então:
127
+
128
+ - `async=false` → executa `insertBulkSync` **na thread da requisição** (síncrono).
129
+ - qualquer outro valor (inclusive ausente) → enfileira na fila de `CsvAsyncProcessor` (**assíncrono é o padrão**).
130
+
131
+ > A fila do `CsvAsyncProcessor` é **única por instância de `CsvManager`**, drenada por **uma só thread** com poll de 1s. Imports assíncronos concorrentes da mesma gamificação são **serializados**.
132
+
133
+ `insertBulkSync(collection, format, delimiter, csv, values, logId)` (linha 690):
134
+
135
+ ```
136
+ [1] log = findLog(logId)
137
+ [2] dispara trigger "csv_before_bulk" (context.extra["log"] = log)
138
+ [3] modo merge: se csv vazio e values presente:
139
+ csv = values["source"]
140
+ merge = getFieldsPathAndValue(values["upsert"]) // campos-folha do upsert
141
+ [4] guard: log != null && collection.length > 3 && csv.length > 0
142
+ [5] linhas = contagem total (para percent)
143
+ [6] formats = reverseFormats(collection) // infere tipos de UM doc amostra (findOne)
144
+ ⊕ getFormats(format) // fields[] do csv_config__c[format] (override)
145
+ config = find(format)
146
+ [7] LOTE = 100 linhas por vez (o comentario "1000" esta desatualizado):
147
+ acumula linhas, re-prepende o cabecalho a cada lote;
148
+ a cada lote:
149
+ insertBulkSyncHelper(lote)
150
+ se findLog(logId).status == "canceled":
151
+ log.status = "canceled"; finaliza; RETURN
152
+ percent = (total*100)/linhas // divisao inteira -> truncado
153
+ insertLog(log)
154
+ [8] log.status = "finished"; percent = 100
155
+ [9] dispara trigger "csv_after_bulk" // só no caminho de sucesso
156
+ ```
157
+
158
+ `insertBulkSyncHelper` (linha 828) por documento convertido:
159
+
160
+ ```
161
+ - se nao tem _id -> _id = Guid.newShortGuid()
162
+ - se collection == "action_log":
163
+ converte em ActionLog (time = now se nulo) -> ActionManager.track(a)
164
+ - senao:
165
+ trigger before_create -> collection.save(object) -> trigger after_create
166
+ (exceptions -> e.printStackTrace(), NAO gravadas no CsvLog)
167
+ ```
168
+
169
+ ```mermaid
170
+ flowchart LR
171
+ A[POST /{collection}/bulk] --> B[CsvLog started]
172
+ B --> C{async?}
173
+ C -- false --> D[insertBulkSync<br/>thread da request]
174
+ C -- padrao --> E[fila CsvAsyncProcessor<br/>1 thread]
175
+ E --> D
176
+ D --> F[trigger csv_before_bulk]
177
+ F --> G[lotes de 100 linhas]
178
+ G --> H[convertCsvInObjects<br/>Apache Commons CSV]
179
+ H --> I{collection == action_log?}
180
+ I -- sim --> J[ActionManager.track]
181
+ I -- nao --> K[before_create -> save -> after_create]
182
+ J --> L{status == canceled?}
183
+ K --> L
184
+ L -- sim --> M[CsvLog canceled<br/>RETURN]
185
+ L -- nao --> N[atualiza percent]
186
+ N --> G
187
+ G --> O[CsvLog finished]
188
+ O --> P[trigger csv_after_bulk]
189
+ ```
190
+
191
+ ### 2.5 Pipeline — exportação (`/aggregate`, `/aggregate/to_excel`)
192
+
193
+ `aggregate` (linha 410) / `aggregateExcel` (linha 498):
194
+
195
+ ```
196
+ [1] guard: collection != null && collection.length > 3
197
+ [2] roda o pipeline (body = lista de stages) na coleção, com allowDiskUse(true)
198
+ se body vazio -> usa {$match:{}} (todos os documentos)
199
+ [3] pagina via header Range (default offset 0, page 100) -> PaginationUtil
200
+ [4] achata cada doc em pares path/valor (getFieldsPathAndValue)
201
+ [5] formats = reverseFormats(collection) ⊕ getFormats(format)
202
+ [6] convertObjectFieldsToSampleCsv(...) -> CSV (cabeçalho ordenado alfabeticamente)
203
+ [7a] aggregate -> devolve text/plain + header Content-Range
204
+ [7b] aggregateExcel-> csvToExcel (Apache POI XSSF) -> arquivo .xls temporário
205
+ delimitador FORÇADO para vírgula; nome da planilha = name|collection
206
+ ```
207
+
208
+ ### 2.6 Sandbox Groovy (`/import` com `config.script`)
209
+
210
+ `getScript` (linha 1776) envolve `config.script` numa classe `FunifierCsv` com:
211
+
212
+ - `@TimedInterrupt(value = 5L, unit = SECONDS)` — interrupção dura no bytecode.
213
+ - Imports pré-carregados de entidades Funifier (`Player`, `Team`, `Achievement`, `ActionLog`, etc.) e utilitários (`JsonUtil`, `HttpUtil`, `DateUtil`...).
214
+ - Injeção de `manager` (`ManagerFactory` completo) e `database` (`GameDao`).
215
+ - `println(msg)` redirecionado para uma lista `output`.
216
+
217
+ `compile` (linha 1872) usa `SecureASTCustomizer` com **whitelist de tokens** (operadores aritméticos, comparações, lógicos, colchetes) + `TriggerExpressionChecker`. `executor` (linha 1921) roda em `ExecutorService` de thread única com `future.get(5, SECONDS)`.
218
+
219
+ **O script do usuário deve definir o método `prepare(config, row, match)`** — é o que `executor` invoca. Exceções de compilação/execução/timeout são adicionadas à lista `err` e **a linha continua sendo processada**.
220
+
221
+ ---
222
+
223
+ ## 3. Estrutura dos Objetos
224
+
225
+ ### 3.1 `csv_config__c` — documento de configuração
226
+
227
+ Modelado **parcialmente** por `CsvConfig` (`@JsonIgnoreProperties(ignoreUnknown=true)`). O runtime lê campos adicionais direto do `HashMap` que **não existem no POJO**.
228
+
229
+ | Campo | Tipo | Padrão | Usado em runtime | Descrição |
230
+ |---|---|---|---|---|
231
+ | `_id` | String | — | sim | Id do config; é o valor passado em `config=`/`format=` |
232
+ | `delimiter` | String | `comma` | `/import` | `comma`/`semicolon`/`tab` (pipe **não** vale aqui) |
233
+ | `header` | String | — | `/import` | `"true"` → primeira linha é cabeçalho (comparação textual com `"true"`) |
234
+ | `action` | String | — | `/import` | Id da `action`; define o tipo de import como `action_log` |
235
+ | `match` | Map | — | `/import` | de→para: **chave** = campo destino do ActionLog, **valor** = nome da coluna CSV |
236
+ | `script` | String | — | `/import` | Corpo Groovy; só compila se `length > 10` |
237
+ | `log` | boolean | `false` | ambos | Liga a gravação de `csv_log__c` e a injeção de `log_field` |
238
+ | `log_field` | String | — | ambos | Nome do campo onde o `_id` do log é injetado em cada linha |
239
+ | `title` | String | — | **não** | Declarado no POJO, nunca lido — rótulo de UI |
240
+ | `sample` | Map | — | **não** | Declarado no POJO, nunca lido — preview de UI |
241
+
242
+ **Campos lidos como HashMap cru (fora do POJO `CsvConfig`):**
243
+
244
+ | Campo | Tipo | Lido por | Descrição |
245
+ |---|---|---|---|
246
+ | `fields` | Array de objetos | `getFormats` (linha 1088) | Definições de tipagem/cabeçalho para bulk/merge/export (ver §3.2) |
247
+ | `upload_encrypted` | boolean (string-parseado) | `isEncrypted` (linha 1072) | Se `true`, uploads multipart são descriptografados via PGP |
248
+
249
+ > **Não existe endpoint de CRUD para `csv_config__c` neste módulo.** Os documentos de config são criados pela API genérica de banco/custom object gravando na coleção `csv_config__c`. O endpoint `/format` grava em outra coleção (ver §7).
250
+
251
+ ### 3.2 `fields[]` — definição de formato (subentidade do config)
252
+
253
+ Cada item do array `fields` descreve uma coluna para os fluxos de bulk/merge/export:
254
+
255
+ | Chave | Tipo | Descrição |
256
+ |---|---|---|
257
+ | `path` | String | Caminho do campo no documento (`a.b`, `lista[0].x`) — usado para montar/ler o objeto |
258
+ | `name` | String | Nome "amigável" no cabeçalho CSV; mapeado para/desde `path` por `getPathFromName`/`getNameFromPath` |
259
+ | `type` | String | `string` (default), `integer`, `double`, `boolean`, `date` |
260
+ | `format` | String | Padrão de data quando `type=date` (default `yyyy-MM-dd HH:mm:ss`) |
261
+ | `default` | qualquer | Valor padrão aplicado quando a coluna está ausente/`null` (tipado conforme `type`) |
262
+
263
+ Coerção de tipo (linhas 1281-1297): `integer`→`Integer.parseInt`, `double`→`Double.parseDouble`, `boolean`→`Boolean.parseBoolean`, `date`→`SimpleDateFormat.parse`. Falha de parse → `printStackTrace`, o campo daquela linha fica sem valor convertido.
264
+
265
+ `reverseFormats` (linha 1025) **infere** tipos lendo **um único** documento amostra (`findOne`) da coleção destino; `getFormats` (definições explícitas do `fields[]`) **sobrescreve** a inferência.
266
+
267
+ ### 3.3 `csv_log__c` — log de execução
268
+
269
+ POJO `CsvLog`. Persistido somente quando aplicável (import: só se `config.log`; bulk/merge: sempre).
270
+
271
+ | Campo | Tipo | Descrição |
272
+ |---|---|---|
273
+ | `_id` | String | `Guid.shortTimeMillis()` — devolvido na resposta como `log` |
274
+ | `config` | String | Id do config/format usado |
275
+ | `collection` | String | Coleção destino (`action_log` no import) |
276
+ | `status` | String | `started` → `finished` \| `canceled` |
277
+ | `start` | Date | Início |
278
+ | `finish` | Date | Fim (em sucesso ou cancelamento) |
279
+ | `percent` | Double | Progresso 0–100, **truncado** (divisão inteira `total*100/linhas`) |
280
+ | `rows` | long | Total de linhas processadas |
281
+ | `extra` | Map | Campo livre (não preenchido pelo fluxo padrão) |
282
+
283
+ Constantes: `STATUS_STARTED="started"`, `STATUS_CANCELED="canceled"`, `STATUS_FINISHED="finished"`. O status intermediário `"running"` aparece **comentado** no código e nunca é gravado.
284
+
285
+ ```mermaid
286
+ stateDiagram-v2
287
+ [*] --> started: insertBulk / importActionLogRows
288
+ started --> finished: todos os lotes processados
289
+ started --> canceled: status set p/ "canceled" entre lotes
290
+ finished --> [*]
291
+ canceled --> [*]
292
+ ```
293
+
294
+ ---
295
+
296
+ ## 4. Endpoints
297
+
298
+ Todos sob `/v3/csv`, autenticação **Bearer token** (`AuthBean`). Não há checagem de papel/permissão adicional no controller.
299
+
300
+ ### `POST /v3/csv/read`
301
+ | Aspecto | Detalhe |
302
+ |---|---|
303
+ | Finalidade | Faz preview/parse de um CSV em linhas JSON. **Não persiste.** |
304
+ | Consumes | `text/plain` (CSV no corpo) |
305
+ | Query | `delimiter` (`comma`/`semicolon`/`tab`), `header` (`true`/`false`), `rows` (limite de linhas; default −1 = todas) |
306
+ | Parser | `readRows` (ingênuo, normaliza nomes de cabeçalho) |
307
+ | Resposta | `201` com a lista de linhas (campos `field1..N` se sem cabeçalho) |
308
+
309
+ ### `POST /v3/csv/import`
310
+ | Aspecto | Detalhe |
311
+ |---|---|
312
+ | Finalidade | Importa linhas como `action_log` segundo o config |
313
+ | Consumes | `text/plain` |
314
+ | Query | `config` (= `_id` em `csv_config__c`), `async` (**aceito e ignorado**) |
315
+ | Comportamento | Sempre síncrono; só persiste se `config.action` estiver setado; descriptografia **não** se aplica aqui |
316
+ | Resposta | `201` com `{ action, log, all:[], err:[] }` |
317
+
318
+ ### `POST /v3/csv/format`
319
+ | Aspecto | Detalhe |
320
+ |---|---|
321
+ | Finalidade | Salva um objeto de formato — **grava em `csv_format__c`** (não em `csv_config__c`) |
322
+ | Consumes | `application/json` |
323
+ | Comportamento | Gera `_id` se ausente; salva como BSON tipado. **Coleção órfã** — nenhum fluxo de leitura a consulta (ver §7) |
324
+
325
+ ### `POST /v3/csv/{collection}/aggregate`
326
+ | Aspecto | Detalhe |
327
+ |---|---|
328
+ | Finalidade | Exporta o resultado de um pipeline de aggregation como CSV |
329
+ | Consumes | `application/json` (lista de stages do pipeline) |
330
+ | Query | `format` (config para `fields[]`), `delimiter` |
331
+ | Header | `Range` (paginação; default page 100) |
332
+ | Resposta | `text/plain` (CSV) + header `Content-Range` |
333
+
334
+ ### `POST /v3/csv/{collection}/aggregate/to_excel`
335
+ | Aspecto | Detalhe |
336
+ |---|---|
337
+ | Finalidade | Igual ao acima, mas devolve planilha `.xls` (Apache POI) |
338
+ | Query | `format`, `name` (nome da planilha; default = nome da coleção) |
339
+ | Comportamento | Delimitador **forçado** para vírgula; arquivo temporário gerado |
340
+ | Resposta | `application/octet-stream` + `Content-Disposition: attachment; filename=<name>.xls` |
341
+
342
+ ### `POST /v3/csv/split` / `POST /v3/csv/split/file`
343
+ | Aspecto | Detalhe |
344
+ |---|---|
345
+ | Finalidade | Quebra o CSV em arrays de colunas (preview estruturado). Não persiste. |
346
+ | Consumes | `text/plain` / `multipart/form-data` (parte `file`) |
347
+ | Query | `format`, `delimiter`, `total` (linhas a processar), `encoding` (só na variante file) |
348
+ | Comportamento | Usa `splitCsv` (Commons CSV); a variante `file` descriptografa se `upload_encrypted` |
349
+ | Resposta | `201` com lista de linhas (primeira linha = cabeçalho) |
350
+
351
+ ### `POST /v3/csv/{collection}/bulk` / `POST /v3/csv/{collection}/bulk/file`
352
+ | Aspecto | Detalhe |
353
+ |---|---|
354
+ | Finalidade | Importa o CSV como documentos na coleção `{collection}` |
355
+ | Consumes | `text/plain` / `multipart/form-data` (parte `file`) |
356
+ | Query | `format` (config para `fields[]`), `delimiter`, `async`, `encoding` (só file) |
357
+ | Padrão | **Assíncrono** (somente `async=false` é síncrono) |
358
+ | Resposta | `201` com `{ config, log }` — use `log` para acompanhar via `/log/{log}` |
359
+
360
+ ### `POST /v3/csv/{collection}/merge` / `POST /v3/csv/{collection}/merge/file`
361
+ | Aspecto | Detalhe |
362
+ |---|---|
363
+ | Finalidade | Igual ao bulk, porém aplica campos constantes (`upsert`) a **todas** as linhas |
364
+ | Consumes | `application/json` (`{ source, upsert }`) / `multipart/form-data` (parte `file` + parte `upsert`) |
365
+ | Query | `format`, `delimiter`, `async`, `encoding` (só file) |
366
+ | Comportamento | "merge" **não** é upsert de Mongo — ver §5. Na variante file, ausência da parte `upsert` → `400` |
367
+ | Resposta | `201` com `{ config, log }` |
368
+
369
+ ### `GET /v3/csv/log/{log}`
370
+ | Aspecto | Detalhe |
371
+ |---|---|
372
+ | Finalidade | Consulta o status/progresso de uma importação |
373
+ | Resposta | `200` com o documento `CsvLog` (`status`, `percent`, `rows`, `start`, `finish`) |
374
+
375
+ **Exemplo — `/import` (request/response):**
376
+
377
+ ```
378
+ POST /v3/csv/import?config=import_players
379
+ Content-Type: text/plain
380
+
381
+ userId;points;time
382
+ user01;100;2026-05-20 10:00:00
383
+ user02;250;2026-05-20 10:05:00
384
+ ```
385
+
386
+ ```json
387
+ {
388
+ "action": { "_id": "import_action", "...": "..." },
389
+ "log": { "_id": "1716200000abc", "config": "import_players",
390
+ "status": "finished", "rows": 2, "collection": "action_log" },
391
+ "all": [],
392
+ "err": []
393
+ }
394
+ ```
395
+
396
+ **Exemplo — `/{collection}/bulk` (assíncrono):**
397
+
398
+ ```
399
+ POST /v3/csv/player/bulk?format=player_format&delimiter=semicolon
400
+
401
+ _id;name
402
+ user01;Maria
403
+ user02;João
404
+ ```
405
+
406
+ ```json
407
+ { "config": "player_format", "log": "1716200111xyz" }
408
+ ```
409
+
410
+ ---
411
+
412
+ ## 5. Regras de Negócio
413
+
414
+ Regras presentes no código mas ausentes do schema:
415
+
416
+ - **`match` é de→para invertido:** a **chave** do `match` é o campo destino do `ActionLog`; o **valor** é o nome da coluna CSV (`config.match.get(key)` → `row.get(valor)`). Errar essa ordem é a armadilha de configuração nº 1.
417
+ - **Campos especiais do ActionLog:** `time`, `userId` e `_id` são **extraídos** de `match` para os campos próprios do `ActionLog` e **removidos** de `attributes`. `time` só é aceito se for `Date`; `userId`/`_id` só se forem `String`.
418
+ - **Tipagem só com `action`:** a conversão de coluna para `Double` no import só ocorre quando há uma `action` e o atributo correspondente é do tipo `"Number"`. Sem action, tudo entra como string.
419
+ - **`/import` só persiste com `action`:** sem `config.action`, nenhuma linha é gravada (vira leitura + script).
420
+ - **"merge" = campos constantes, não upsert:** o objeto `upsert` é achatado em pares path/valor e aplicado a **cada** linha antes do `save`. Não há `$set`/upsert no Mongo — é `collection.save(object)` (substitui por `_id`, ou insere com `_id` novo).
421
+ - **Validação de `upsert` assimétrica:** só a variante multipart (`/merge/file`) valida a presença de `upsert` e retorna `400` se faltar. Na variante JSON (`/merge`), `upsert` ausente não é validado no controller — falha mais tarde dentro de `insertBulkSync`.
422
+ - **`collection.length > 3`:** bulk e aggregate ignoram silenciosamente coleções cujo nome tenha ≤ 3 caracteres (guard `collection.trim().length() > 3`).
423
+ - **Lote fixo de 100:** bulk processa 100 linhas por lote, re-prependendo o cabeçalho a cada lote. O comentário do código menciona "1000" — está desatualizado.
424
+ - **`header` ignorado em bulk:** o parser de bulk (`splitCsv`) sempre trata a primeira linha como cabeçalho, independentemente do config.
425
+ - **Injeção de `log_field`:** com `config.log` ligado e `log_field` definido, o `_id` do log é injetado em todas as linhas (no import via `row`, no bulk via `merge`).
426
+ - **Isolamento multi-tenant:** todo acesso usa `manager.getJongoConnection()`/`manager.getApiKey()` resolvido a partir do `apiKey` do token — a separação é por conexão de gamificação.
427
+ - **Consistência eventual:** imports assíncronos retornam `201` imediatamente; o resultado real só existe quando `csv_log__c.status == finished`.
428
+
429
+ ---
430
+
431
+ ## 6. Comportamentos Automáticos
432
+
433
+ | Comportamento | Trigger | Impacto | Persistência |
434
+ |---|---|---|---|
435
+ | Trigger `csv_before_bulk` | Início de `insertBulkSync` (bulk/merge) | Executa código de trigger custom com `log` no contexto | Depende do trigger |
436
+ | Trigger `csv_after_bulk` | Fim de `insertBulkSync` **(só em sucesso)** | Idem; **não dispara** se cancelado | Depende do trigger |
437
+ | Trigger `before_create`/`after_create` | Cada `save` em coleção ≠ `action_log` | Hooks por documento | Depende do trigger |
438
+ | Rastreio de `action_log` | Linha cujo destino é `action_log` (bulk) ou `/import` | `ActionManager.track`/`trackWithRestrictions` — dispara toda a engine de gamificação (achievements, níveis, etc.) | Sim |
439
+ | Geração de `_id` | Documento sem `_id` no bulk | `Guid.newShortGuid()` | Sim |
440
+ | Injeção de `log_field` | `config.log` + `log_field` | Adiciona o id do log a cada linha | Sim |
441
+ | Valores `default` | Coluna ausente/`null` com `default` no `fields[]` | Preenche o campo com o default tipado | Sim |
442
+ | Coerção de tipo | `fields[].type` ∈ {integer,double,boolean,date} | Converte a string da coluna | Sim |
443
+ | Descriptografia PGP | Upload multipart + `upload_encrypted=true` | `decryptTransfer` antes do parse | Não (em memória) |
444
+
445
+ > Os nomes `csv_before_bulk`/`csv_after_bulk` são **literais de string** passados a `TriggerManager.execute` (não constantes em `Trigger.java`). `before_create`/`after_create` são `Trigger.EVENT_BEFORE_CREATE`/`EVENT_AFTER_CREATE`.
446
+
447
+ ---
448
+
449
+ ## 7. Suportado vs NÃO Suportado
450
+
451
+ ### ✅ Suportado
452
+
453
+ - Import como `action_log` orientado por config (`match`, `action`, `script` Groovy, `log`).
454
+ - Bulk/merge em **qualquer** coleção, síncrono ou assíncrono, em lotes, com cancelamento.
455
+ - Exportação de aggregation para CSV e Excel (`.xls`), com paginação por `Range`.
456
+ - Parsing/preview (`/read`, `/split`, `/split/file`).
457
+ - Upload multipart com descriptografia PGP (`upload_encrypted` + `CryptTransferKey`).
458
+ - Tipagem de campos (`integer`/`double`/`boolean`/`date`), valores default e campos aninhados/listas (`a.b`, `lista[0].x`).
459
+ - Acompanhamento de progresso via `csv_log__c` e `GET /log/{log}`.
460
+ - Cancelamento de import (alterando `csv_log__c.status` para `canceled`).
461
+
462
+ ### ❌ NÃO Suportado / Inconsistências confirmadas no código
463
+
464
+ - **FTP** — não existe nenhum código de FTP no módulo (alegação da doc anterior é falsa).
465
+ - **`POST /v3/csv/format` é um beco sem saída** — grava em `csv_format__c`, mas todo fluxo de leitura (`find`, `getFormats`, `isEncrypted`) usa `csv_config__c`. O que é salvo ali nunca é lido.
466
+ - **Query param `async` em `/import`** — aceito e ignorado; o caminho assíncrono está comentado.
467
+ - **`pipe` em `/read` e `/import`** — `readRows` só conhece `comma`/`semicolon`/`tab`.
468
+ - **Aspas/escape em `/read` e `/import`** — `line.split` não trata aspas; valores com delimitador embutido quebram.
469
+ - **`header` em bulk/merge** — ignorado; a primeira linha é sempre cabeçalho.
470
+ - **Descriptografia em `/import`, `/read`, `/split`(texto), `/bulk`(texto), `/merge`(json)** — só os endpoints **multipart** chamam `getCsv`/`decryptTransfer`.
471
+ - **Endpoint de CRUD de config** — inexistente neste módulo; configs são criados/atualizados pela API genérica de banco (ex.: `POST /v3/database/csv_config__c`).
472
+ - **Endpoint de cancelamento** — inexistente; cancela-se via update direto em `csv_log__c`.
473
+ - **Status `running`** — comentado no código; nunca é gravado.
474
+ - **Campos `title` e `sample` do `CsvConfig`** — declarados, nunca lidos em runtime.
475
+ - **`merge` como upsert do Mongo** — não é; é aplicação de campos constantes + `save`.
476
+ - **Erros de `save` em bulk** — apenas `printStackTrace`, **não** refletidos no `CsvLog` nem na resposta.
477
+ - **`converCsvInObjectsOld` / segundo `convertObjectFieldsToSampleCsv`** — código legado morto/comentado, mantido no arquivo.
478
+
479
+ ---
480
+
481
+ ## 8. Segurança e Permissões
482
+
483
+ - **Autenticação:** Bearer token via `AuthBean`; resolução de tenant por `apiKey` em `FrontController.getInstance(apiKey)`. **Sem** checagem de papel/escopo no `CsvRest`.
484
+ - **Isolamento multi-tenant:** por conexão Jongo da gamificação (`manager.getJongoConnection()`); chaves de criptografia filtradas por `gamification == apiKey`.
485
+ - **Superfície de leitura arbitrária:** `/{collection}/aggregate` e `/aggregate/to_excel` executam um **pipeline de aggregation arbitrário** (corpo da requisição) sobre **qualquer** coleção (`collection.length > 3`). Um chamador autenticado pode ler qualquer coleção da gamificação, inclusive `$lookup` entre coleções. Não há allowlist de coleções nem de stages.
486
+ - **Superfície de escrita arbitrária:** `/{collection}/bulk` e `/merge` gravam em **qualquer** coleção nomeada na URL, disparando `before_create`/`after_create`. Não há restrição de coleções destino.
487
+ - **Execução de código (Groovy):** `/import` com `config.script` executa Groovy. Há sandbox (`SecureASTCustomizer` com whitelist de tokens + `TriggerExpressionChecker` + `@TimedInterrupt 5s`), mas o script recebe `manager` (ManagerFactory completo) e `database` (GameDao) — superfície ampla. A criação do config (que carrega o script) deve ser tratada como operação privilegiada.
488
+ - **Criptografia em trânsito:** PGP assimétrico via `CryptTransferKey` (par publicKey/secretKey + passphrase, status `ACTIVE`). **Falha de descriptografia retorna `null`** → NPE no parser subsequente (falha silenciosa do ponto de vista do log).
489
+ - **Concorrência:** a fila assíncrona é única por instância e drenada por uma única thread — sem isolamento entre jobs além da serialização.
490
+
491
+ ---
492
+
493
+ ## 9. Observabilidade e Troubleshooting
494
+
495
+ **Diagnóstico — o módulo está funcionando?**
496
+
497
+ ```
498
+ # status/progresso de uma importação
499
+ GET /v3/csv/log/{log}
500
+
501
+ # config existe e está completo?
502
+ GET /v3/database/csv_config__c/{id} # via API genérica de banco
503
+
504
+ # os documentos chegaram na coleção destino?
505
+ GET /v3/database/{collection}?q={...}
506
+ ```
507
+
508
+ **Queries MongoDB úteis:**
509
+
510
+ ```js
511
+ // imports presos em "started" (travados/cancelados sem finalizar)
512
+ db.csv_log__c.find({ status: "started" })
513
+
514
+ // últimos logs de uma coleção
515
+ db.csv_log__c.find({ collection: "player" }).sort({ start: -1 })
516
+
517
+ // cancelar um import assíncrono em andamento
518
+ db.csv_log__c.updateOne({ _id: "<log>" }, { $set: { status: "canceled" } })
519
+ ```
520
+
521
+ **Erros comuns e causas:**
522
+
523
+ | Sintoma | Causa provável |
524
+ |---|---|
525
+ | `/import` retorna `log` mas nada é importado | `config.action` ausente — `/import` só grava `action_log` |
526
+ | Linhas faltando após import | Parser ingênuo descartou linhas com menos colunas que o cabeçalho; ou valores com delimitador embutido sem suporte a aspas |
527
+ | Colunas trocadas no import | `match` invertido (chave = destino, valor = coluna CSV) |
528
+ | `pipe` não funciona em `/import` | `readRows` não suporta pipe; use bulk |
529
+ | Datas viram string | `fields[].type` não é `date`, ou o `format` não bate com o valor |
530
+ | Upload criptografado falha sem erro claro | Endpoint não-multipart (não descriptografa) ou `CryptTransferKey` ativo ausente → `null` → NPE |
531
+ | Bulk "concluiu" mas faltam docs | Erros de `save` só vão para `printStackTrace`, não para o `CsvLog`; verifique os logs do servidor |
532
+ | Formato salvo via `/format` não tem efeito | Vai para `csv_format__c` (órfã); use `csv_config__c` |
533
+ | `percent` nunca chega a valores intermediários precisos | Divisão inteira trunca; só `100.0` é exato no fim |
534
+
535
+ **O que verificar quando algo não funciona:**
536
+
537
+ 1. O `csv_config__c[id]` existe e tem `fields[]`/`match` corretos?
538
+ 2. O delimitador do arquivo bate com o `delimiter` enviado?
539
+ 3. Para bulk: a coleção destino existe e tem um doc amostra (afeta `reverseFormats`)?
540
+ 4. Para criptografia: existe `CryptTransferKey` com `status=ACTIVE` para a gamificação?
541
+ 5. Logs do servidor (`stdout`) — o módulo loga via `System.out.println` e `printStackTrace`.
542
+
543
+ ---
544
+
545
+ ## 10. Exemplos Práticos
546
+
547
+ ### Exemplo mínimo funcional — bulk de times (semicolon)
548
+
549
+ Config `csv_config__c`:
550
+
551
+ ```json
552
+ {
553
+ "_id": "team_format",
554
+ "fields": [
555
+ { "path": "_id", "name": "id" },
556
+ { "path": "name", "name": "nome", "type": "string" }
557
+ ]
558
+ }
559
+ ```
560
+
561
+ Requisição:
562
+
563
+ ```
564
+ POST /v3/csv/team/bulk?format=team_format&delimiter=semicolon&async=false
565
+
566
+ id;nome
567
+ rh;Recursos Humanos
568
+ com;Equipe Comercial
569
+ ```
570
+
571
+ → cria dois documentos em `team` (`_id` = `rh`/`com`), síncrono.
572
+
573
+ ### Exemplo avançado — import de action_log com tipagem, script e log
574
+
575
+ Config `csv_config__c`:
576
+
577
+ ```json
578
+ {
579
+ "_id": "import_sales",
580
+ "delimiter": "semicolon",
581
+ "header": "true",
582
+ "action": "registrar_venda",
583
+ "match": {
584
+ "userId": "vendedor",
585
+ "time": "data",
586
+ "valor": "total"
587
+ },
588
+ "log": true,
589
+ "log_field": "import_ref",
590
+ "script": "void prepare(config, row, match){ if(match.valor == null){ match.valor = 0 } }"
591
+ }
592
+ ```
593
+
594
+ Requisição:
595
+
596
+ ```
597
+ POST /v3/csv/import?config=import_sales
598
+ Content-Type: text/plain
599
+
600
+ vendedor;data;total
601
+ user01;2026-05-20 10:00:00;1500.50
602
+ user02;2026-05-20 11:00:00;980.00
603
+ ```
604
+
605
+ → cada linha vira um `ActionLog` da action `registrar_venda`, com `userId`/`time` extraídos, `valor` no `attributes`, `import_ref` = id do log, e o script garante `valor` não-nulo.
606
+
607
+ ### Exemplo de exportação — relatório CSV de pontos por jogador
608
+
609
+ ```
610
+ POST /v3/csv/achievement/aggregate?delimiter=comma
611
+ Content-Type: application/json
612
+ Range: items=0-999
613
+
614
+ [
615
+ { "$match": { "type": 0 } },
616
+ { "$group": { "_id": "$player", "total": { "$sum": "$total" } } },
617
+ { "$sort": { "total": -1 } }
618
+ ]
619
+ ```
620
+
621
+ → devolve `text/plain` CSV com cabeçalho `_id,total`.
622
+
623
+ ### Anti-pattern — o que NÃO fazer
624
+
625
+ ```jsonc
626
+ // ERRADO: match com chave/valor invertidos
627
+ { "match": { "vendedor": "userId" } } // grava o literal "userId" no campo "vendedor"
628
+
629
+ // ERRADO: confiar em async para ter o resultado na resposta
630
+ POST /v3/csv/player/bulk?format=f // 201 imediato; dados ainda não existem
631
+
632
+ // ERRADO: usar /format esperando configurar o import
633
+ POST /v3/csv/format // vai para csv_format__c, nunca é lido
634
+
635
+ // ERRADO: CSV com vírgula dentro do valor em /import
636
+ // "Silva, João" quebra (sem suporte a aspas no parser ingênuo)
637
+ // → use /bulk (Apache Commons CSV) ou troque o delimitador
638
+ ```
639
+
640
+ ---
641
+
642
+ ## Checklist de Configuração
643
+
644
+ - [ ] `csv_config__c[id]` criado via API de banco (não existe endpoint dedicado).
645
+ - [ ] `match` no sentido correto: **chave = campo destino**, **valor = coluna CSV**.
646
+ - [ ] Para `/import`: `action` definida (sem ela, nada é gravado).
647
+ - [ ] `delimiter` da requisição = delimitador real do arquivo.
648
+ - [ ] `pipe` apenas em bulk/merge/split — nunca em `/read`/`/import`.
649
+ - [ ] `fields[].type`/`format` corretos para datas e números (bulk/export).
650
+ - [ ] Para bulk síncrono: passar `async=false` explicitamente (o padrão é assíncrono).
651
+ - [ ] Para upload criptografado: usar endpoint **multipart** + `upload_encrypted=true` + `CryptTransferKey` ativo.
652
+ - [ ] Acompanhar imports assíncronos por `GET /log/{log}` até `status=finished`.
653
+ - [ ] Armadilha: erros de `save` em bulk **não** aparecem no `CsvLog` — conferir logs do servidor.
654
+ - [ ] Armadilha: `header` é ignorado em bulk (1ª linha sempre é cabeçalho).
655
+ - [ ] Armadilha: `merge` aplica campos constantes a todas as linhas — não é upsert de Mongo.