@hed-hog/catalog 0.0.293 → 0.0.295

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 (66) hide show
  1. package/README.md +391 -361
  2. package/dist/catalog-resource.config.d.ts.map +1 -1
  3. package/dist/catalog-resource.config.js +51 -24
  4. package/dist/catalog-resource.config.js.map +1 -1
  5. package/dist/catalog.controller.d.ts +420 -0
  6. package/dist/catalog.controller.d.ts.map +1 -1
  7. package/dist/catalog.controller.js +98 -0
  8. package/dist/catalog.controller.js.map +1 -1
  9. package/dist/catalog.module.d.ts.map +1 -1
  10. package/dist/catalog.module.js +5 -1
  11. package/dist/catalog.module.js.map +1 -1
  12. package/dist/catalog.service.d.ts +216 -1
  13. package/dist/catalog.service.d.ts.map +1 -1
  14. package/dist/catalog.service.js +1121 -7
  15. package/dist/catalog.service.js.map +1 -1
  16. package/hedhog/data/catalog_attribute.yaml +202 -0
  17. package/hedhog/data/catalog_attribute_option.yaml +109 -0
  18. package/hedhog/data/catalog_category.yaml +47 -0
  19. package/hedhog/data/catalog_category_attribute.yaml +209 -0
  20. package/hedhog/data/menu.yaml +46 -12
  21. package/hedhog/data/role.yaml +7 -7
  22. package/hedhog/data/route.yaml +64 -0
  23. package/hedhog/frontend/app/[resource]/page.tsx.ejs +358 -0
  24. package/hedhog/frontend/app/_components/catalog-ai-form-assist-dialog.tsx.ejs +340 -0
  25. package/hedhog/frontend/app/_components/catalog-resource-form-sheet.tsx.ejs +815 -0
  26. package/hedhog/frontend/app/_lib/catalog-resources.tsx.ejs +504 -736
  27. package/hedhog/frontend/app/dashboard/page.tsx.ejs +14 -83
  28. package/hedhog/frontend/messages/en.json +150 -60
  29. package/hedhog/frontend/messages/pt.json +185 -95
  30. package/hedhog/table/catalog_affiliate_program.yaml +41 -41
  31. package/hedhog/table/catalog_attribute.yaml +22 -7
  32. package/hedhog/table/catalog_attribute_group.yaml +18 -18
  33. package/hedhog/table/catalog_attribute_option.yaml +40 -0
  34. package/hedhog/table/catalog_brand.yaml +34 -34
  35. package/hedhog/table/catalog_category.yaml +40 -0
  36. package/hedhog/table/catalog_category_attribute.yaml +13 -7
  37. package/hedhog/table/catalog_click_event.yaml +50 -50
  38. package/hedhog/table/catalog_comparison.yaml +3 -6
  39. package/hedhog/table/catalog_comparison_highlight.yaml +39 -39
  40. package/hedhog/table/catalog_comparison_item.yaml +30 -30
  41. package/hedhog/table/catalog_content_relation.yaml +42 -42
  42. package/hedhog/table/catalog_import_run.yaml +33 -33
  43. package/hedhog/table/catalog_import_source.yaml +24 -24
  44. package/hedhog/table/catalog_merchant.yaml +29 -29
  45. package/hedhog/table/catalog_offer.yaml +83 -83
  46. package/hedhog/table/catalog_price_history.yaml +34 -34
  47. package/hedhog/table/catalog_product.yaml +5 -3
  48. package/hedhog/table/catalog_product_attribute_value.yaml +15 -2
  49. package/hedhog/table/catalog_product_category.yaml +3 -3
  50. package/hedhog/table/catalog_product_image.yaml +34 -34
  51. package/hedhog/table/catalog_product_score.yaml +38 -38
  52. package/hedhog/table/catalog_product_site.yaml +47 -47
  53. package/hedhog/table/catalog_product_tag.yaml +19 -19
  54. package/hedhog/table/catalog_score_criterion.yaml +25 -8
  55. package/hedhog/table/catalog_seo_page_rule.yaml +2 -2
  56. package/hedhog/table/catalog_similarity_rule.yaml +19 -6
  57. package/hedhog/table/catalog_site.yaml +8 -0
  58. package/hedhog/table/catalog_site_category.yaml +3 -3
  59. package/package.json +7 -7
  60. package/src/catalog-resource.config.ts +51 -24
  61. package/src/catalog.controller.ts +67 -0
  62. package/src/catalog.module.ts +5 -1
  63. package/src/catalog.service.ts +1531 -6
  64. package/src/index.ts +1 -1
  65. package/src/language/en.json +4 -4
  66. package/src/language/pt.json +4 -4
package/README.md CHANGED
@@ -1,379 +1,409 @@
1
- ```markdown
2
1
  # @hed-hog/catalog
3
2
 
4
- ## 1. Visão geral do módulo
5
-
6
- O módulo `@hed-hog/catalog` é responsável pela gestão centralizada dos recursos do catálogo, incluindo produtos, ofertas, marcas, atributos, comparações, imagens e regras relacionadas. Ele oferece uma API REST para operações CRUD, listagem paginada, estatísticas e manipulação de imagens de produtos, integrando-se com outros módulos do monorepo HedHog.
7
-
8
- ## 2. Escopo e responsabilidades
9
-
10
- - Gerenciar recursos do catálogo como produtos, ofertas, marcas, atributos, comparações, imagens, entre outros.
11
- - Fornecer endpoints para criação, leitura, atualização, exclusão e listagem paginada de recursos.
12
- - Aplicar filtros e buscas sensíveis a campos configurados para cada recurso.
13
- - Gerar estatísticas básicas dos recursos.
14
- - Controlar imagens associadas a produtos.
15
- - Garantir autenticação e autorização baseadas em papéis específicos.
16
- - Integrar com serviços de paginação, localização e banco de dados Prisma.
17
-
18
- ## 3. Endpoints
19
-
20
- ### Listar imagens de um produto
21
-
22
- - **Método:** GET
23
- - **Path:** `/catalog/products/:id/images`
24
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
25
- - **Descrição:** Lista imagens associadas a um produto, com paginação e ordenação por `sort_order`.
26
- - **Parâmetros:**
27
- - `id` (path): ID numérico do produto
28
- - Query params de paginação (ex: `page`, `limit`)
29
- - Header `Accept-Language` para locale (ex: `pt-BR`)
30
- - **Resposta:** Lista paginada de imagens do produto com campos conforme tabela `catalog_product_image`.
31
- - **Erros:**
32
- - 400 Bad Request: recurso não suportado
33
- - 401 Unauthorized: acesso não autorizado
34
-
35
- ---
36
-
37
- ### Obter estatísticas de um recurso
38
-
39
- - **Método:** GET
40
- - **Path:** `/catalog/:resource/stats`
41
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
42
- - **Descrição:** Retorna estatísticas básicas do recurso, como total de registros e quantidade ativa (se aplicável).
43
- - **Parâmetros:**
44
- - `resource` (path): nome do recurso (ex: `product`, `brand`)
45
- - Header `Accept-Language` para locale
46
- - **Resposta:**
47
- ```json
48
- {
49
- "total": number,
50
- "active": number (opcional)
51
- }
52
- ```
53
- - **Erros:**
54
- - 400 Bad Request: recurso não suportado
55
- - 401 Unauthorized
56
-
57
- ---
58
-
59
- ### Obter recurso por ID
60
-
61
- - **Método:** GET
62
- - **Path:** `/catalog/:resource/:id`
63
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
64
- - **Descrição:** Retorna um registro específico do recurso pelo ID.
65
- - **Parâmetros:**
66
- - `resource` (path): nome do recurso
67
- - `id` (path): ID numérico do registro
68
- - Header `Accept-Language` para locale
69
- - **Resposta:** Objeto do recurso com campos conforme configuração.
70
- - **Erros:**
71
- - 400 Bad Request: recurso não suportado
72
- - 404 Not Found: recurso não encontrado
73
- - 401 Unauthorized
74
-
75
- ---
76
-
77
- ### Atualizar recurso por ID
78
-
79
- - **Método:** PATCH
80
- - **Path:** `/catalog/:resource/:id`
81
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
82
- - **Descrição:** Atualiza campos permitidos de um registro do recurso.
83
- - **Parâmetros:**
84
- - `resource` (path): nome do recurso
85
- - `id` (path): ID do registro
86
- - Body JSON: campos a atualizar (somente campos configurados para o recurso)
87
- - Header `Accept-Language` para locale
88
- - **Resposta:** Objeto atualizado do recurso.
89
- - **Erros:**
90
- - 400 Bad Request: recurso não suportado ou payload inválido
91
- - 404 Not Found: recurso não encontrado
92
- - 401 Unauthorized
93
-
94
- ---
95
-
96
- ### Deletar recurso por ID
97
-
98
- - **Método:** DELETE
99
- - **Path:** `/catalog/:resource/:id`
100
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
101
- - **Descrição:** Remove um registro do recurso pelo ID.
102
- - **Parâmetros:**
103
- - `resource` (path): nome do recurso
104
- - `id` (path): ID do registro
105
- - Header `Accept-Language` para locale
106
- - **Resposta:** Objeto do recurso deletado.
107
- - **Erros:**
108
- - 400 Bad Request: recurso não suportado
109
- - 404 Not Found: recurso não encontrado
110
- - 401 Unauthorized
111
-
112
- ---
113
-
114
- ### Listar recursos com paginação e filtros
115
-
116
- - **Método:** GET
117
- - **Path:** `/catalog/:resource`
118
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
119
- - **Descrição:** Lista registros do recurso com paginação, ordenação e filtros configurados.
120
- - **Parâmetros:**
121
- - `resource` (path): nome do recurso
122
- - Query params: filtros conforme campos configurados, paginação (`page`, `limit`)
123
- - Header `Accept-Language` para locale
124
- - **Resposta:** Lista paginada de registros do recurso.
125
- - **Erros:**
126
- - 400 Bad Request: recurso não suportado ou filtro inválido
127
- - 401 Unauthorized
128
-
129
- ---
130
-
131
- ### Criar novo recurso
132
-
133
- - **Método:** POST
134
- - **Path:** `/catalog/:resource`
135
- - **Autenticação:** Sim (roles: `admin`, `admin-catalog`)
136
- - **Descrição:** Cria um novo registro do recurso com os campos permitidos.
137
- - **Parâmetros:**
138
- - `resource` (path): nome do recurso
139
- - Body JSON: campos para criação (somente campos configurados para o recurso)
140
- - Header `Accept-Language` para locale
141
- - **Resposta:** Objeto criado do recurso.
142
- - **Erros:**
143
- - 400 Bad Request: recurso não suportado ou payload inválido
144
- - 401 Unauthorized
145
-
146
- ## 4. Regras de autenticação e autorização
147
-
148
- - Todos os endpoints são protegidos e requerem autenticação.
149
- - Apenas usuários com os papéis `admin` ou `admin-catalog` podem acessar os endpoints do módulo.
150
- - A autorização é baseada em roles definidas no sistema, conforme arquivo `hedhog/data/role.yaml`.
151
-
152
- ## 5. Estruturas de request/response
153
-
154
- - **Request Body:**
155
- - Campos aceitos são somente os definidos na configuração do recurso (`catalogResourceConfig`), ignorando campos extras.
156
- - Valores booleanos podem ser enviados como strings `"true"` ou `"false"`.
157
- - Valores numéricos podem ser enviados como strings numéricas e serão convertidos automaticamente.
158
- - **Response:**
159
- - Objetos JSON com os campos do recurso conforme tabelas do banco.
160
- - Listagens paginadas seguem o padrão do serviço de paginação do HedHog.
161
-
162
- ## 6. Erros comuns
163
-
164
- - **400 Bad Request:** Recurso não suportado, payload inválido ou filtro inválido.
165
- - **401 Unauthorized:** Usuário não autenticado ou sem permissão.
166
- - **404 Not Found:** Registro não encontrado para o ID informado.
167
-
168
- ## 7. Banco de dados (tabelas YAML)
169
-
170
- ### catalog_product
171
-
172
- - **Finalidade:** Armazena os produtos do catálogo.
173
- - **Colunas principais:**
174
- - `id` (PK)
175
- - `brand_id` (FK para `catalog_brand`, nullable)
176
- - `category_id` (FK para `category`)
177
- - `primary_content_id` (FK para `content`, nullable)
178
- - `slug` (varchar 255, único)
179
- - `name` (varchar 255)
180
- - `short_description` (varchar 500, nullable)
181
- - `description` (text, nullable)
182
- - `model_name` (varchar 255, nullable)
183
- - `sku` (varchar 120, nullable)
184
- - `gtin` (varchar 120, nullable)
185
- - `status` (enum: `draft`, `published`, `archived`, default `draft`)
186
- - `comparison_status` (enum: `draft`, `ready`, `disabled`, default `draft`)
187
- - `release_date` (datetime, nullable)
188
- - `spec_snapshot_json` (json, nullable)
189
- - `comparison_snapshot_json` (json, nullable)
190
- - `is_active` (boolean, default `true`)
191
- - `created_at`, `updated_at` (timestamps)
192
- - **Defaults:** Conforme descrito acima.
193
- - **Nulabilidade:** Campos como `brand_id`, `primary_content_id`, `short_description`, `description`, `model_name`, `sku`, `gtin`, `release_date`, `spec_snapshot_json`, `comparison_snapshot_json` são opcionais.
194
- - **Integridade:** FK para `catalog_brand`, `category`, `content`.
195
- - **Índices:**
196
- - Único em `slug`
197
- - Índices em `category_id` + `status`, `brand_id` + `status`, `primary_content_id`, `is_active`
198
-
199
- ---
200
-
201
- ### catalog_product_image
202
-
203
- - **Finalidade:** Armazena imagens associadas a produtos.
204
- - **Colunas principais:**
205
- - `id` (PK)
206
- - `product_id` (FK para `catalog_product`)
207
- - `file_id` (FK para `file`)
208
- - `role` (enum: `primary`, `gallery`, `thumbnail`, `lifestyle`, `technical`, default `gallery`)
209
- - `sort_order` (int, default 0)
210
- - `is_primary` (boolean, default false)
211
- - `alt_text` (varchar 255, nullable)
212
- - `created_at`, `updated_at` (timestamps)
213
- - **Defaults:** Conforme descrito acima.
214
- - **Nulabilidade:** `alt_text` é opcional.
215
- - **Integridade:** FK para `catalog_product` e `file`.
216
- - **Índices:**
217
- - Índices em `product_id` + `sort_order`, `product_id` + `is_primary`, `file_id`
218
-
219
- ---
220
-
221
- ### catalog_brand
222
-
223
- - **Finalidade:** Armazena marcas do catálogo.
224
- - **Colunas principais:**
225
- - `id` (PK)
226
- - `slug` (varchar 255, único)
227
- - `name` (varchar 255)
228
- - `normalized_name` (varchar 255)
229
- - `logo_file_id` (FK para `file`, nullable)
230
- - `status` (enum: `active`, `inactive`, default `active`)
231
- - `website_url` (varchar 500, nullable)
232
- - `created_at`, `updated_at` (timestamps)
233
- - **Defaults:** Conforme descrito acima.
234
- - **Nulabilidade:** `logo_file_id` e `website_url` são opcionais.
235
- - **Integridade:** FK para `file`.
236
- - **Índices:**
237
- - Único em `slug`
238
- - Índices em `normalized_name`, `logo_file_id`, `status`
239
-
240
- ---
241
-
242
- ### catalog_offer
243
-
244
- - **Finalidade:** Armazena ofertas vinculadas a produtos e comerciantes.
245
- - **Colunas principais:**
246
- - `id` (PK)
247
- - `product_id` (FK para `catalog_product`)
248
- - `merchant_id` (FK para `catalog_merchant`)
249
- - `affiliate_program_id` (FK para `catalog_affiliate_program`, nullable)
250
- - `site_id` (FK para `catalog_site`, nullable)
251
- - `external_offer_id` (varchar 255)
252
- - `title` (varchar 255)
253
- - `price_amount` (decimal 14,2)
254
- - `price_currency` (varchar 3)
255
- - `original_price_amount` (decimal 14,2, nullable)
256
- - `installment_json` (json, nullable)
257
- - `availability_status` (enum: `in_stock`, `out_of_stock`, `pre_order`, `unknown`, default `unknown`)
258
- - `affiliate_url` (varchar 500, nullable)
259
- - `deep_link_url` (varchar 500, nullable)
260
- - `priority_score` (int, default 0)
261
- - `is_featured` (boolean, default false)
262
- - `valid_from` (datetime, nullable)
263
- - `valid_until` (datetime, nullable)
264
- - `last_seen_at` (datetime, nullable)
265
- - `created_at`, `updated_at` (timestamps)
266
- - **Defaults:** Conforme descrito acima.
267
- - **Nulabilidade:** Campos como `affiliate_program_id`, `site_id`, `original_price_amount`, `installment_json`, `affiliate_url`, `deep_link_url`, `valid_from`, `valid_until`, `last_seen_at` são opcionais.
268
- - **Integridade:** FK para `catalog_product`, `catalog_merchant`, `catalog_affiliate_program`, `catalog_site`.
269
- - **Índices:**
270
- - Único em `product_id`, `merchant_id`, `external_offer_id`
271
- - Índices em `product_id`, `availability_status`, `price_amount`
272
- - Índices em `site_id`, `is_featured`
273
-
274
- ---
275
-
276
- ### catalog_affiliate_program
277
-
278
- - **Finalidade:** Armazena programas de afiliados vinculados a comerciantes.
279
- - **Colunas principais:**
280
- - `id` (PK)
281
- - `merchant_id` (FK para `catalog_merchant`, nullable)
282
- - `slug` (varchar 255, único)
283
- - `name` (varchar 255)
284
- - `network_type` (enum: `amazon`, `mercado_livre`, `aliexpress`, `kabum`, `direct`, `network`, default `direct`)
285
- - `tracking_template` (text, nullable)
286
- - `commission_type` (enum: `percentage`, `fixed`, default `percentage`)
287
- - `default_commission_value` (decimal 8,2, default 0)
288
- - `status` (enum: `active`, `inactive`, default `active`)
289
- - `created_at`, `updated_at` (timestamps)
290
- - **Defaults:** Conforme descrito acima.
291
- - **Nulabilidade:** `merchant_id` e `tracking_template` são opcionais.
292
- - **Integridade:** FK para `catalog_merchant`.
293
- - **Índices:**
294
- - Único em `slug`
295
- - Índices em `merchant_id`, `status`
296
-
297
- ---
298
-
299
- *(Outras tabelas seguem padrão similar, com chaves primárias, estrangeiras, enums e índices conforme arquivos YAML fornecidos.)*
300
-
301
- ## 8. Regras de negócio relevantes
302
-
303
- - O módulo suporta múltiplos recursos configurados dinamicamente via `catalogResourceMap`.
304
- - Campos de busca e filtro são configurados por recurso para permitir consultas eficientes.
305
- - Campos booleanos e numéricos são normalizados automaticamente no payload (ex: strings `"true"`, `"false"` e números em string são convertidos).
306
- - Exclusão e atualização verificam existência prévia do registro, retornando erro 404 se não encontrado.
307
- - Estatísticas incluem contagem total e contagem por status ativo quando aplicável.
308
- - Imagens de produtos são ordenadas por `sort_order` e podem ter uma imagem primária.
309
- - O sistema suporta múltiplos idiomas via parâmetro `locale` (header `Accept-Language`).
310
- - Autorização é restrita a usuários com papéis `admin` e `admin-catalog`.
311
-
312
- ## 9. Guia rápido de uso (exemplos)
313
-
314
- ### Listar produtos com paginação e filtro por status
315
-
316
- ```http
317
- GET /catalog/product?page=1&limit=10&status=published
318
- Authorization: Bearer <token>
319
- Accept-Language: pt-BR
320
- ```
3
+ ## Overview
321
4
 
322
- ### Criar uma nova marca
5
+ `@hed-hog/catalog` is the central catalog module for HedHog. It manages:
323
6
 
324
- ```http
325
- POST /catalog/brand
326
- Authorization: Bearer <token>
327
- Content-Type: application/json
328
- Accept-Language: pt-BR
7
+ - catalog categories
8
+ - brands, merchants, sites, offers, and products
9
+ - structured technical attributes by category
10
+ - product attribute values with typed storage
11
+ - product images and comparison-oriented payloads
329
12
 
330
- {
331
- "slug": "nova-marca",
332
- "name": "Nova Marca",
333
- "status": "active",
334
- "website_url": "https://novamarca.com"
335
- }
336
- ```
13
+ The module keeps the existing generic CRUD backbone, pagination, locale, roles, Prisma integration, and admin experience. The main architectural change is that structured attribute tables are now the primary source of truth for technical metadata, while legacy JSON snapshots remain secondary and derived.
337
14
 
338
- ### Atualizar um produto
15
+ ## Core architecture
339
16
 
340
- ```http
341
- PATCH /catalog/product/123
342
- Authorization: Bearer <token>
343
- Content-Type: application/json
344
- Accept-Language: pt-BR
17
+ ### Primary domain entities
345
18
 
346
- {
347
- "name": "Nome Atualizado",
348
- "status": "published"
349
- }
350
- ```
19
+ - `catalog_category`
20
+ Product taxonomy used by the catalog itself.
21
+ - `catalog_attribute`
22
+ Master definition of a technical/comparable attribute.
23
+ - `catalog_attribute_option`
24
+ Enumerated values for `option` attributes.
25
+ - `catalog_category_attribute`
26
+ Rules that define which attributes belong to a category and how they behave in UI/comparison.
27
+ - `catalog_product_attribute_value`
28
+ Typed value storage for product attributes.
29
+ - `catalog_product`
30
+ Main product record with brand, category, content, status, and secondary snapshots.
351
31
 
352
- ### Deletar uma oferta
32
+ ### Supporting entities
353
33
 
354
- ```http
355
- DELETE /catalog/offer/456
356
- Authorization: Bearer <token>
357
- Accept-Language: pt-BR
358
- ```
34
+ - `catalog_brand`
35
+ Supports `logo_file_id` via the shared `file` module.
36
+ - `catalog_site`
37
+ Supports `logo_file_id` and site-specific JSON settings.
38
+ - `catalog_merchant`
39
+ Supports `logo_file_id` when merchant branding is needed.
40
+ - `catalog_product_image`
41
+ Product gallery/media records via `file_id`.
42
+ - `catalog_offer`, `catalog_affiliate_program`, `catalog_product_site`, `catalog_comparison`, and related tables
43
+ Commercial/publication/comparison layers that build on top of products and attributes.
359
44
 
360
- ### Obter estatísticas de ofertas
45
+ ## Structured attributes vs JSON snapshots
361
46
 
362
- ```http
363
- GET /catalog/offer/stats
364
- Authorization: Bearer <token>
365
- Accept-Language: pt-BR
366
- ```
47
+ ### Primary data
48
+
49
+ The canonical source for technical product data is:
50
+
51
+ 1. `catalog_category`
52
+ 2. `catalog_attribute`
53
+ 3. `catalog_category_attribute`
54
+ 4. `catalog_product_attribute_value`
55
+
56
+ This is what enables:
57
+
58
+ - consistent admin forms
59
+ - typed validation
60
+ - category-aware required fields
61
+ - filters and sorting by normalized values
62
+ - comparison payload generation
63
+
64
+ ### Secondary data
65
+
66
+ `catalog_product.spec_snapshot_json` and `catalog_product.comparison_snapshot_json` are still present for compatibility and fast reads.
67
+
68
+ Important rules:
69
+
70
+ - they are **not** the primary source of truth anymore
71
+ - they should be treated as **derived/cache fields**
72
+ - they are materialized from structured attributes
73
+ - old data should not be deleted without a migration plan
74
+
75
+ The service now exposes snapshot materialization helpers and keeps snapshots warm after attribute updates.
76
+
77
+ ## Main tables
78
+
79
+ ### `catalog_category`
80
+
81
+ Suggested use:
82
+
83
+ - create taxonomy nodes like `gpu`, `cpu`, `motherboard`, `ram`, `ssd`, `monitor`
84
+ - optionally organize them with `parent_category_id`
85
+ - control whether the category participates in comparison with `comparison_enabled`
86
+
87
+ Key fields:
88
+
89
+ - `slug`
90
+ - `name`
91
+ - `description`
92
+ - `parent_category_id`
93
+ - `comparison_enabled`
94
+ - `status`
95
+ - `sort_order`
96
+
97
+ ### `catalog_attribute`
98
+
99
+ Defines the technical vocabulary shared across categories.
100
+
101
+ Key fields:
102
+
103
+ - `code`
104
+ - `slug`
105
+ - `name`
106
+ - `description`
107
+ - `data_type`
108
+ - `unit`
109
+ - `group_name`
110
+ - `comparison_mode`
111
+ - `is_filterable`
112
+ - `is_sortable`
113
+ - `is_comparable`
114
+ - `is_required_default`
115
+ - `status`
116
+ - `display_order`
117
+
118
+ Supported `data_type` values:
119
+
120
+ - `text`
121
+ - `long_text`
122
+ - `number`
123
+ - `boolean`
124
+ - `option`
125
+
126
+ ### `catalog_attribute_option`
127
+
128
+ Used when `catalog_attribute.data_type = option`.
129
+
130
+ Key fields:
131
+
132
+ - `attribute_id`
133
+ - `slug`
134
+ - `label`
135
+ - `option_value`
136
+ - `normalized_value`
137
+ - `sort_order`
138
+ - `status`
139
+ - `is_default`
140
+
141
+ ### `catalog_category_attribute`
142
+
143
+ Binds attributes to categories and controls behavior.
144
+
145
+ Key fields:
146
+
147
+ - `catalog_category_id`
148
+ - `attribute_id`
149
+ - `is_required`
150
+ - `is_highlight`
151
+ - `is_filter_visible`
152
+ - `is_comparison_visible`
153
+ - `sort_order`
154
+ - `weight`
155
+ - `facet_mode`
156
+
157
+ ### `catalog_product_attribute_value`
158
+
159
+ Stores one typed value per `product_id + attribute_id`.
160
+
161
+ Key fields:
162
+
163
+ - `product_id`
164
+ - `attribute_id`
165
+ - `attribute_option_id`
166
+ - `value_text`
167
+ - `value_number`
168
+ - `value_boolean`
169
+ - `raw_value`
170
+ - `value_unit`
171
+ - `normalized_value`
172
+ - `normalized_text`
173
+ - `normalized_number`
174
+ - `source_type`
175
+ - `confidence_score`
176
+ - `is_verified`
177
+
178
+ ## Attribute value rules
179
+
180
+ Only one value channel should be used per attribute value row:
181
+
182
+ - `text` and `long_text` use `value_text`
183
+ - `number` uses `value_number`
184
+ - `boolean` uses `value_boolean`
185
+ - `option` uses `attribute_option_id`
186
+
187
+ The backend validates this and rejects inconsistent payloads.
188
+
189
+ ## Initial catalog seeds
190
+
191
+ The module now ships declarative YAML seeds in `libraries/catalog/hedhog/data`.
192
+
193
+ Seeded categories:
194
+
195
+ - `gpu`
196
+ - `cpu`
197
+ - `motherboard`
198
+ - `ram`
199
+ - `ssd`
200
+ - `monitor`
201
+
202
+ Seeded example attributes:
203
+
204
+ ### GPU
205
+
206
+ - `chipset`
207
+ - `vram_gb`
208
+ - `memory_type`
209
+ - `memory_bus_bits`
210
+ - `boost_clock_mhz`
211
+ - `tdp_w`
212
+ - `length_mm`
213
+ - `ray_tracing`
214
+
215
+ ### CPU
216
+
217
+ - `cores`
218
+ - `threads`
219
+ - `base_clock_ghz`
220
+ - `boost_clock_ghz`
221
+ - `socket`
222
+ - `tdp_w`
223
+ - `integrated_graphics`
224
+
225
+ Seeded example options:
367
226
 
368
- ### Listar imagens de um produto
227
+ - GPU memory types like `GDDR6`, `GDDR6X`, `GDDR7`, `HBM3`, `HBM3E`
228
+ - CPU sockets like `AM4`, `AM5`, `LGA1700`, `LGA1851`, `sTR5`
369
229
 
370
- ```http
371
- GET /catalog/products/123/images?page=1&limit=5
372
- Authorization: Bearer <token>
373
- Accept-Language: pt-BR
230
+ ## Admin resources
231
+
232
+ The catalog admin supports generic CRUD plus specialized product attribute editing.
233
+
234
+ Main resources:
235
+
236
+ - `categories`
237
+ - `brands`
238
+ - `sites`
239
+ - `products`
240
+ - `attribute-groups`
241
+ - `attributes`
242
+ - `attribute-options`
243
+ - `category-attributes`
244
+ - `product-attributes`
245
+ - `comparisons`
246
+ - `offers`
247
+ - `merchants`
248
+ - `affiliate-programs`
249
+ - `seo-rules`
250
+ - `import-sources`
251
+
252
+ ### Product admin experience
253
+
254
+ When editing a product:
255
+
256
+ 1. select `catalog_category_id`
257
+ 2. the admin loads category attributes dynamically
258
+ 3. fields are grouped by attribute group or `group_name`
259
+ 4. typed inputs are rendered for `text`, `long_text`, `number`, `boolean`, and `option`
260
+ 5. required fields are validated before save
261
+ 6. values are persisted into `catalog_product_attribute_value`
262
+
263
+ The product screen also shows:
264
+
265
+ - brand
266
+ - category
267
+ - status and active flag
268
+ - grouped technical attributes
269
+ - product images as a secondary block
270
+ - snapshots as secondary/derived data
271
+
272
+ ## Logos and files
273
+
274
+ The module uses the shared `file` flow already present in the monorepo.
275
+
276
+ ### Brand logo
277
+
278
+ Use `catalog_brand.logo_file_id`.
279
+
280
+ ### Site logo
281
+
282
+ Use `catalog_site.logo_file_id`.
283
+
284
+ ### Merchant logo
285
+
286
+ Use `catalog_merchant.logo_file_id`.
287
+
288
+ ### Product gallery
289
+
290
+ Use `catalog_product_image.file_id`.
291
+
292
+ No custom upload mechanism was introduced. The admin uses the same file upload pattern already used across the project.
293
+
294
+ ## Runtime helpers
295
+
296
+ `CatalogService` now includes helpers for:
297
+
298
+ - listing category attributes
299
+ - listing product attributes
300
+ - building a structured product payload
301
+ - building a comparison payload from structured attributes
302
+ - materializing `spec_snapshot_json` and `comparison_snapshot_json`
303
+
304
+ Relevant endpoints:
305
+
306
+ - `GET /catalog/categories/tree`
307
+ - `GET /catalog/categories/:id/attributes`
308
+ - `GET /catalog/products/:id/attributes`
309
+ - `PUT /catalog/products/:id/attributes`
310
+ - `GET /catalog/products/:id/structured`
311
+ - `GET /catalog/products/:id/comparison-payload`
312
+ - `POST /catalog/products/:id/materialize-snapshots`
313
+ - generic CRUD under `/catalog/:resource`
314
+
315
+ ## Safe transition strategy
316
+
317
+ The module preserves snapshot fields to avoid destructive migration behavior.
318
+
319
+ Current strategy:
320
+
321
+ - structured attribute tables are canonical
322
+ - snapshots remain readable
323
+ - snapshots are regenerated from structured values
324
+ - attribute updates trigger snapshot materialization
325
+ - compatibility aliases are still accepted in a few payloads where the old UI/model used different names
326
+
327
+ This keeps the module safe for gradual adoption while avoiding JSON-first modeling going forward.
328
+
329
+ ## How to apply schema and seeds
330
+
331
+ This repository follows a database-first HedHog workflow.
332
+
333
+ ### Schema/data apply
334
+
335
+ ```powershell
336
+ hedhog dev apply
337
+ pnpm db:update
374
338
  ```
375
339
 
376
- ---
340
+ `hedhog dev apply` applies table YAML and the declarative data under `hedhog/data`, so the catalog seed data lives in the same standard workflow as the rest of the project.
377
341
 
378
- Este módulo deve ser utilizado por administradores do catálogo para gerenciar os dados essenciais do sistema de forma segura e consistente.
342
+ ### Build checks
343
+
344
+ ```powershell
345
+ pnpm --filter api build
346
+ pnpm --filter @hed-hog/catalog build
347
+ pnpm --filter admin build
379
348
  ```
349
+
350
+ ## Example flows
351
+
352
+ ### 1. Create a brand with logo
353
+
354
+ 1. Open `Catalog > Brands`
355
+ 2. Create a brand with `name`, `slug`, and optional `website_url`
356
+ 3. Upload the logo through the shared upload field
357
+ 4. Save, which stores the uploaded file in `logo_file_id`
358
+
359
+ ### 2. Create a category
360
+
361
+ 1. Open `Catalog > Categories`
362
+ 2. Create `gpu`-like or business-specific categories
363
+ 3. Configure `comparison_enabled`, hierarchy, and status
364
+
365
+ ### 3. Create an attribute
366
+
367
+ 1. Open `Catalog > Attributes`
368
+ 2. Define `code`, `slug`, `name`, `data_type`, and `group_name`
369
+ 3. Mark whether the attribute is filterable/sortable/comparable
370
+
371
+ ### 4. Link an attribute to a category
372
+
373
+ 1. Open `Catalog > Category Attributes`
374
+ 2. Pick the category and the attribute
375
+ 3. Configure required/highlight/filter/comparison behavior
376
+ 4. Save ordering and facet mode
377
+
378
+ ### 5. Create a product
379
+
380
+ 1. Open `Catalog > Products`
381
+ 2. Fill the base product fields
382
+ 3. Choose brand and category
383
+ 4. Save the product
384
+
385
+ ### 6. Save product technical attributes
386
+
387
+ 1. In the same product sheet, after category selection, the technical fields load automatically
388
+ 2. Fill typed values
389
+ 3. Save the product
390
+ 4. The admin persists values to `catalog_product_attribute_value`
391
+ 5. Snapshots are materialized as derived JSON
392
+
393
+ ## Starting with GPU and CPU
394
+
395
+ The seeded data is enough to bootstrap a real first catalog slice:
396
+
397
+ 1. create brands like `NVIDIA`, `AMD`, `Intel`
398
+ 2. create products under `gpu` and `cpu`
399
+ 3. fill structured attributes
400
+ 4. optionally attach product images
401
+ 5. use comparison payload endpoints to power future storefront or editorial flows
402
+
403
+ ## Future improvements
404
+
405
+ - automatic snapshot materialization on more product lifecycle events
406
+ - richer import pipelines that map external payloads directly to structured attributes
407
+ - category-specific admin presets and product templates
408
+ - storefront queries optimized for faceting and comparison at scale
409
+ - deeper read models for SEO, rankings, and recommendation features