@horizon-framework/api-nextjs-docs 2.3.0

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.
@@ -0,0 +1,333 @@
1
+ # SSOT (Single Source of Truth) - Especificação v2.0
2
+
3
+ ## Visão Geral
4
+
5
+ O novo formato SSOT v2.0 organiza metadados de campos em **contextos específicos**, permitindo separação clara de responsabilidades e melhor organização dos dados.
6
+
7
+ ### Filosofia do Formato
8
+ - **Contextos Separados**: Cada tipo de metadado tem seu lugar específico
9
+ - **Estrutura Hierárquica**: Agrupamento lógico das propriedades
10
+ - **Flexibilidade**: Campos podem ser vazios e preenchidos conforme necessário
11
+ - **Evolução Gradual**: Estrutura permite crescimento sem quebrar compatibilidade
12
+
13
+ ---
14
+
15
+ ## Estrutura dos Contextos
16
+
17
+ ### 1. **RAIZ** - Identificação Básica
18
+ ```typescript
19
+ {
20
+ "key": "campo_exemplo", // Identificador único (snake_case)
21
+ "type": "String", // Tipo de dado (String, Number, Boolean, String[], Json)
22
+ "enum": {"key": "Label"}, // Valores possíveis (quando aplicável)
23
+ "format": "currency", // Formatação de exibição
24
+ "unit": "BRL", // Unidade de medida
25
+ "categories": ["valores"] // Tags/categorias para filtros
26
+ }
27
+ ```
28
+
29
+ ### 2. **RULES** - Regras e Relações
30
+ ```typescript
31
+ "rules": {
32
+ "parent": "tipo", // Campo pai hierárquico
33
+ "conditions": ["operacao:venda"] // Condições de visibilidade
34
+ }
35
+ ```
36
+
37
+ ### 3. **VALIDATION** - Validação Backend/Zod
38
+ ```typescript
39
+ "validation": {
40
+ "required": true, // Campo obrigatório
41
+ "min": 0, // Valor mínimo (números)
42
+ "max": 999999.99, // Valor máximo (números)
43
+ "minLength": 3, // Tamanho mínimo (strings)
44
+ "maxLength": 200, // Tamanho máximo (strings)
45
+ "precision": 2 // Casas decimais
46
+ }
47
+ ```
48
+
49
+ ### 4. **DB** - Configurações de Banco
50
+ ```typescript
51
+ "db": {
52
+ // VAZIO por enquanto - será preenchido conforme necessário
53
+ // Futuro: index, unique, default, etc.
54
+ }
55
+ ```
56
+
57
+ ### 5. **UI** - Interface e Experiência
58
+ ```typescript
59
+ "ui": {
60
+ "label": "Campo Exemplo", // Rótulo para exibição
61
+ "description": "Texto de ajuda", // Helper text para formulários
62
+ "placeholder": "Digite...", // Placeholder para inputs
63
+ "icon": "home", // Ícone semântico
64
+ "displayTemplate": "{{value}} m²", // Template de exibição
65
+ "mask": "cpf", // Máscara de input (cpf, cnpj, cep, phone, email)
66
+ "searchable": true, // Pesquisável por texto
67
+ "filterable": true, // Aparece nos filtros
68
+ "sortable": true // Permite ordenação
69
+ }
70
+ ```
71
+
72
+ ### 6. **AUDIT** - Auditoria e Rastreamento
73
+ ```typescript
74
+ "audit": {
75
+ "origin": "horizon-base/imovel", // Origem do campo
76
+ "modifiedBy": ["system"] // Histórico de modificações
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Tipos de Dados Suportados
83
+
84
+ | Tipo | Descrição | Exemplo |
85
+ |------|-----------|---------|
86
+ | `String` | Texto simples | "Casa no Centro" |
87
+ | `Number` | Números inteiros e decimais | 150000.50 |
88
+ | `Boolean` | Verdadeiro/Falso | true, false |
89
+ | `String[]` | Array de strings | ["piscina", "churrasqueira"] |
90
+ | `Json` | Objeto JSON | {"lat": -27.5, "lng": -48.5} |
91
+ | `Json[]` | Array de objetos | [{"url": "...", "caption": "..."}] |
92
+
93
+ ---
94
+
95
+ ## Formatos de Exibição
96
+
97
+ | Format | Unit | Exemplo Entrada | Exemplo Saída |
98
+ |--------|------|----------------|---------------|
99
+ | `currency` | `BRL` | 150000 | R$ 150.000,00 |
100
+ | `currency` | `USD` | 150000 | $ 150,000.00 |
101
+ | `area` | `m2` | 150 | 150 m² |
102
+ | `distance` | `km` | 1.5 | 1,5 km |
103
+ | `percent` | - | 0.15 | 15% |
104
+ | `count` | - | 3 | 3 itens |
105
+ | `date` | - | "2024-01-01" | 01/01/2024 |
106
+ | `datetime` | - | "2024-01-01T10:30:00Z" | 01/01/2024 10:30 |
107
+
108
+ ---
109
+
110
+ ## Categorias Recomendadas
111
+
112
+ | Categoria | Descrição | Campos Típicos |
113
+ |-----------|-----------|----------------|
114
+ | `valores` | Campos monetários | valor_venda, valor_locacao |
115
+ | `localizacao` | Endereço e geolocalização | endereco_cidade, latitude, longitude |
116
+ | `estrutura` | Características físicas | quartos, banheiros, area_total |
117
+ | `identificacao` | Títulos e referências | title, reference |
118
+ | `comercial` | Informações comerciais | operacao, status |
119
+ | `caracteristicas` | Amenidades e comodidades | piscina, churrasqueira |
120
+ | `sistema` | Campos internos | id, created_at, updated_at |
121
+
122
+ ---
123
+
124
+ ## Máscaras Disponíveis
125
+
126
+ ### Automáticas (inferidas do format + unit)
127
+ - `currency-brl` → R$ 1.000,00 (format: currency + unit: BRL)
128
+ - `currency-usd` → $ 1,000.00 (format: currency + unit: USD)
129
+ - `area` → 100 m² (format: area + unit: m2)
130
+ - `distance` → 1,5 km (format: distance + unit: km)
131
+ - `percent` → 15% (format: percent)
132
+ - `date` → DD/MM/AAAA (format: date)
133
+
134
+ ### Explícitas (para casos específicos)
135
+ - `cpf` → 000.000.000-00
136
+ - `cnpj` → 00.000.000/0000-00
137
+ - `cep` → 00000-000
138
+ - `phone` → (00) 00000-0000
139
+ - `email` → Validação de email
140
+
141
+ ---
142
+
143
+ ## Condições de Visibilidade
144
+
145
+ ### Operadores Disponíveis
146
+
147
+ | Operador | Descrição | Exemplo |
148
+ |----------|-----------|---------|
149
+ | `:` (padrão) | Contém | `"operacao:venda"` |
150
+ | `.equals` | Igual a | `"tipo.equals:apartamento"` |
151
+ | `.not` | Diferente de | `"status.not:vendido"` |
152
+ | `.in` | Está na lista | `"tipo.in:casa,apartamento"` |
153
+ | `.gt` | Maior que | `"valor.gt:100000"` |
154
+ | `.gte` | Maior ou igual | `"valor.gte:100000"` |
155
+ | `.lt` | Menor que | `"quartos.lt:5"` |
156
+ | `.lte` | Menor ou igual | `"area.lte:200"` |
157
+ | `.exists` | Campo preenchido | `"condominio.exists:true"` |
158
+
159
+ ### Exemplos de Uso
160
+ ```typescript
161
+ "rules": {
162
+ "conditions": [
163
+ "operacao:venda", // operacao contém "venda"
164
+ "tipo.equals:apartamento", // tipo é exatamente "apartamento"
165
+ "valor.gte:100000", // valor >= 100000
166
+ "quartos.in:2,3,4" // quartos é 2, 3 ou 4
167
+ ]
168
+ }
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Exemplos Práticos
174
+
175
+ ### Campo Simples
176
+ ```json
177
+ {
178
+ "key": "title",
179
+ "type": "String",
180
+ "categories": ["identificacao"],
181
+ "validation": {
182
+ "required": true,
183
+ "minLength": 3,
184
+ "maxLength": 200
185
+ },
186
+ "ui": {
187
+ "label": "Título",
188
+ "description": "Título do imóvel",
189
+ "placeholder": "Ex: Casa no Centro",
190
+ "searchable": true
191
+ },
192
+ "audit": {
193
+ "origin": "horizon-base/imovel"
194
+ }
195
+ }
196
+ ```
197
+
198
+ ### Campo com Enum
199
+ ```json
200
+ {
201
+ "key": "tipo",
202
+ "type": "String",
203
+ "enum": {
204
+ "casa": "Casa",
205
+ "apartamento": "Apartamento",
206
+ "terreno": "Terreno"
207
+ },
208
+ "categories": ["estrutura"],
209
+ "validation": {
210
+ "required": true
211
+ },
212
+ "ui": {
213
+ "label": "Tipo",
214
+ "filterable": true,
215
+ "sortable": true
216
+ },
217
+ "audit": {
218
+ "origin": "horizon-base/imovel"
219
+ }
220
+ }
221
+ ```
222
+
223
+ ### Campo Monetário
224
+ ```json
225
+ {
226
+ "key": "valor_venda",
227
+ "type": "Number",
228
+ "format": "currency",
229
+ "unit": "BRL",
230
+ "categories": ["valores"],
231
+ "validation": {
232
+ "min": 0,
233
+ "max": 999999999.99,
234
+ "precision": 2
235
+ },
236
+ "ui": {
237
+ "label": "Valor de Venda",
238
+ "displayTemplate": "{{value}}",
239
+ "filterable": true,
240
+ "sortable": true
241
+ },
242
+ "audit": {
243
+ "origin": "horizon-base/imovel"
244
+ }
245
+ }
246
+ ```
247
+
248
+ ### Campo com Condição
249
+ ```json
250
+ {
251
+ "key": "valor_locacao",
252
+ "type": "Number",
253
+ "format": "currency",
254
+ "unit": "BRL",
255
+ "categories": ["valores"],
256
+ "rules": {
257
+ "conditions": ["operacao:locacao"]
258
+ },
259
+ "validation": {
260
+ "min": 0,
261
+ "precision": 2
262
+ },
263
+ "ui": {
264
+ "label": "Valor de Locação",
265
+ "filterable": true,
266
+ "sortable": true
267
+ },
268
+ "audit": {
269
+ "origin": "horizon-base/imovel"
270
+ }
271
+ }
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Geração Automática
277
+
278
+ ### Zod Schema
279
+ A partir do SSOT, é gerado automaticamente:
280
+ - Schema Zod para validação
281
+ - Tipos TypeScript inferidos
282
+ - Funções de validação (parse, safeParse, partial)
283
+
284
+ ### Exemplo de Geração
285
+ ```typescript
286
+ // Gerado automaticamente a partir do SSOT
287
+ export const ImovelZod = z.object({
288
+ id: z.number().optional(),
289
+ reference: z.string().min(1).max(50),
290
+ title: z.string().min(3).max(200),
291
+ tipo: z.enum(["casa", "apartamento", "terreno"]),
292
+ valor_venda: z.number().min(0).multipleOf(0.01).optional()
293
+ })
294
+
295
+ export type ImovelType = z.infer<typeof ImovelZod>
296
+ ```
297
+
298
+ ---
299
+
300
+ ## Vantagens do Novo Formato
301
+
302
+ 1. **Organização Clara**: Cada contexto tem seu espaço definido
303
+ 2. **Flexibilidade**: Campos podem ser vazios e evoluir gradualmente
304
+ 3. **Separação de Responsabilidades**: UI, validação, DB em contextos isolados
305
+ 4. **Manutenibilidade**: Fácil identificar onde cada metadado pertence
306
+ 5. **Evolução**: Adicionar novos contextos sem quebrar existentes
307
+ 6. **Reuso**: Mesmo campo pode ter comportamentos diferentes por contexto
308
+
309
+ ---
310
+
311
+ ## Checklist de Criação
312
+
313
+ ### Campo Mínimo Obrigatório
314
+ - [ ] `key` - Identificador único
315
+ - [ ] `type` - Tipo de dado
316
+ - [ ] `ui.label` - Rótulo para exibição
317
+ - [ ] `audit.origin` - Origem do campo
318
+
319
+ ### Campo Típico (recomendado)
320
+ - [ ] Validação básica (`required`, limites)
321
+ - [ ] Categoria para organização
322
+ - [ ] Formatação quando aplicável
323
+ - [ ] Configurações de UI (searchable, filterable)
324
+
325
+ ### Campo Complexo
326
+ - [ ] Enum quando valores limitados
327
+ - [ ] Condições de visibilidade
328
+ - [ ] Display template customizado
329
+ - [ ] Relacionamentos hierárquicos
330
+
331
+ ---
332
+
333
+ Esta especificação serve como **base definitiva** para criação de schemas SSOT v2.0 no sistema, garantindo consistência, flexibilidade e evolução controlada dos metadados.
@@ -0,0 +1,268 @@
1
+ # Sync API — Padrao de Sincronizacao de Modulos
2
+
3
+ > Endpoint /sync que TODO modulo backend Horizon implementa.
4
+ > Ponto de entrada de dados externos (CRM, integradores) para o banco.
5
+
6
+ ---
7
+
8
+ ## Visao Geral
9
+
10
+ Cada modulo tem 3 operacoes no /sync:
11
+
12
+ | Metodo | Rota | Autenticacao | Funcao |
13
+ |---|---|---|---|
14
+ | GET | /api/{entity}/sync | Publico (CORS *) | Listing — retorna IDs + refs + updated_at para diff |
15
+ | PUT | /api/{entity}/sync | API Key (x-api-key) | Upsert batch — recebe array, valida, transforma, persiste |
16
+ | DELETE | /api/{entity}/sync | API Key (x-api-key) | Delete por refs/keys — remove items do banco |
17
+
18
+ ---
19
+
20
+ ## Arquitetura de Arquivos
21
+
22
+ ```
23
+ apps/api/src/
24
+ ├── app/api/{entity}/sync/
25
+ │ └── route.ts # "Ponte seca" — exporta controller
26
+ ├── core/
27
+ │ ├── prisma.ts # Singleton Prisma Client
28
+ │ └── route-helpers.ts # withApiKeyProtection() + withPublicCors()
29
+ └── modules/{entity}/
30
+ ├── schemas/
31
+ │ └── source-schema.zod.ts # Importa Zod do pacote CRM
32
+ ├── controllers/
33
+ │ └── sync.controller.ts # GET/PUT/DELETE — valida body, chama service
34
+ ├── services/
35
+ │ └── sync.service.ts # Logica: prepare → transaction → return
36
+ └── mappers/
37
+ ├── dataModifier/
38
+ │ ├── index.ts # ObjectTransformer com regras do cliente
39
+ │ └── rules.ts # Regras especificas CRM/cliente (pode estar vazio)
40
+ └── computedFields/
41
+ ├── index.ts # Orquestra todos os computed
42
+ ├── seo-slug-gen.ts # Gera slug SEO
43
+ ├── thumbnails.ts # Gera thumbnails das imagens
44
+ ├── endereco-completo.ts# Monta endereco formatado
45
+ ├── tipo-subtipo-gen.ts # Mapeia tipo → tipo_gen + subtipo_gen
46
+ └── valor-m2.ts # Calcula valor por m2
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Fluxo PUT (Upsert)
52
+
53
+ ```
54
+ 1. REQUEST → PUT /api/{entity}/sync (x-api-key header)
55
+ Body: { "{entity}s": [ {...item1}, {...item2} ] }
56
+
57
+ 2. ROUTE → withApiKeyProtection() verifica x-api-key
58
+ Se invalido: 401 Unauthorized
59
+
60
+ 3. CONTROLLER
61
+ → Parse body JSON
62
+ → Validacao Zod RIGOROSA do array
63
+ Property: usa Zod do pacote CRM (ex: HorizonPropertySchemaBySi9Zod)
64
+ Broker: schema inline com .passthrough()
65
+ Se falha: 400 com detalhes Zod
66
+
67
+ 4. SERVICE
68
+ → validateUpsertInput() — array nao vazio, cada item tem key/reference
69
+ → Loop de preparacao (cada item):
70
+ - dataModifier(item) → Regras de negocio do cliente
71
+ - computeFields(item) → Campos calculados (slug, thumb, endereco, flags)
72
+ - stripUnknownFields(item) → Remove campos que nao existem no model Prisma
73
+ - Salva sync_raw_data → JSONB com snapshot completo
74
+ → Prisma.$transaction():
75
+ - BACKUP first_synced_at dos existentes
76
+ - deleteMany({ where: { reference: { in: refs } } })
77
+ - createMany({ data: prepared, skipDuplicates: true })
78
+ - Restaura first_synced_at
79
+
80
+ 5. RESPONSE
81
+ { success: true, inserted: N, updated: N, total: N, references: [...] }
82
+ ```
83
+
84
+ **Por que delete + insert (e nao upsert nativo)?**
85
+ Prisma upsert opera 1 registro por vez. Para batches de 50+ itens, deleteMany + createMany numa transaction e mais performatico. Garante que campos removidos do CRM sao limpos.
86
+
87
+ ---
88
+
89
+ ## Fluxo GET (Listing)
90
+
91
+ ```
92
+ GET /api/{entity}/sync?source_key=si9-api-imoveis
93
+
94
+ → findMany com select minimo: { id, reference, source_updated_at }
95
+ → Filtro opcional por source_key
96
+ → Ordenado por source_updated_at DESC
97
+ → Mapeia source_updated_at → updated_at na saida
98
+
99
+ Response: { properties: [{ id, reference, updated_at }, ...] }
100
+ ```
101
+
102
+ **Proposito:** Integrador faz GET listing → compara updated_at com dados do CRM → envia PUT apenas com os que mudaram. Evita re-sincronizar tudo.
103
+
104
+ ---
105
+
106
+ ## Fluxo DELETE
107
+
108
+ ```
109
+ DELETE /api/{entity}/sync (x-api-key header)
110
+ Body: { "refs": ["ABC-123", "DEF-456"] }
111
+ (property usa "refs", broker usa "keys")
112
+
113
+ → Validacao: array nao vazio
114
+ → prisma.{entity}.deleteMany({ where: { reference: { in: refs } } })
115
+
116
+ Response: { success: true, deleted: N, references: [...] }
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Pipeline de Transformacao
122
+
123
+ ```
124
+ Dados do CRM (formato CRM)
125
+
126
+ Pacote CRM (@horizon-integrations/{crm}-crm) ← FORA da API, no automations
127
+ Converte formato CRM → formato Horizon
128
+
129
+ PUT /api/{entity}/sync ← ENTRADA NA API
130
+
131
+ source-schema.zod.ts ← Validacao Zod rigorosa
132
+
133
+ dataModifier (mappers/dataModifier/) ← Regras de negocio do CLIENTE
134
+ ObjectTransformer com rules.ts
135
+ Pode estar VAZIO (passthrough)
136
+
137
+ computedFields (mappers/computedFields/) ← Campos CALCULADOS
138
+ seo_slug_gen, thumbnails, endereco_completo,
139
+ tipo_gen, subtipo_gen, has_video, has_tour,
140
+ financiavel, first_synced_at
141
+
142
+ stripUnknownFields() ← Protecao do Prisma
143
+ Filtra via Prisma.{Entity}ScalarFieldEnum
144
+
145
+ sync_raw_data (JSONB) ← Snapshot completo
146
+
147
+ Prisma $transaction (delete + insert) ← Persistencia
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Protecao de Rotas
153
+
154
+ ```typescript
155
+ // route.ts — padrao para todo modulo
156
+ export const { OPTIONS, GET, PUT, DELETE } = withApiKeyProtection({
157
+ GET: ControllerGET, // Publico (CORS *)
158
+ PUT: ControllerPUT, // Protegido (x-api-key)
159
+ DELETE: ControllerDELETE // Protegido (x-api-key)
160
+ });
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Diferencas Property vs Broker
166
+
167
+ | Aspecto | Property | Broker |
168
+ |---|---|---|
169
+ | Chave unica | reference | key |
170
+ | Validacao Zod | Rigorosa (schema CRM) | Flexivel (.passthrough()) |
171
+ | dataModifier | ObjectTransformer com rules | Funcao simples |
172
+ | computedFields | 7+ campos | Poucos (slug, timestamps) |
173
+ | stripUnknownFields | Sim | Nao |
174
+ | sync_raw_data | Sim (JSONB) | Nao |
175
+ | first_synced_at | Sim (preservado) | Nao |
176
+ | GET source_key | Sim | Nao (ainda) |
177
+
178
+ ---
179
+
180
+ ## Multi-Source Sync — Padrao source_key
181
+
182
+ ### O Problema
183
+
184
+ Uma integracao pode ter MULTIPLAS fontes de dados para a mesma entidade:
185
+
186
+ | source_key | Fonte | Descricao |
187
+ |---|---|---|
188
+ | si9-api-imoveis | SI9 CRM | Imoveis proprios da imobiliaria |
189
+ | nifb-vrsync-imoveis | VRSync/NIFB | Imoveis do Nucleo de Imoveis |
190
+
191
+ Sem source_key, ao sincronizar imoveis do SI9, o deleteMany poderia deletar imoveis do NIFB.
192
+
193
+ ### Regra Obrigatoria
194
+
195
+ **Toda integracao que enviar dados via PUT /sync DEVE incluir source_key em cada item.**
196
+
197
+ ```json
198
+ {
199
+ "properties": [
200
+ {
201
+ "source_key": "si9-api-imoveis",
202
+ "reference": "ABC-123",
203
+ "title": "Casa 3 quartos"
204
+ }
205
+ ]
206
+ }
207
+ ```
208
+
209
+ ### Onde o source_key atua
210
+
211
+ **PUT (Upsert):** Cada item chega com source_key preenchido, salvo no banco.
212
+
213
+ **GET (Listing):** Integrador filtra apenas seus registros:
214
+ ```
215
+ GET /api/property/sync?source_key=si9-api-imoveis → So imoveis do SI9
216
+ GET /api/property/sync?source_key=nifb-vrsync-imoveis → So imoveis do NIFB
217
+ GET /api/property/sync → Sem filtro: retorna TODOS
218
+ ```
219
+
220
+ **DELETE — Scoping por fonte (EVOLUCAO NECESSARIA):**
221
+ Estado atual: DELETE opera por references SEM filtrar por source_key.
222
+ Evolucao: DELETE deveria aceitar source_key como filtro adicional:
223
+
224
+ ```typescript
225
+ // Evolucao: DELETE com scoping por source
226
+ await tx.property.deleteMany({
227
+ where: {
228
+ reference: { in: refs },
229
+ source_key: sourceKey // ← Protecao: so deleta da mesma fonte
230
+ }
231
+ });
232
+ ```
233
+
234
+ **Upsert Transaction — Scoping (EVOLUCAO NECESSARIA):**
235
+ Estado atual: deleteMany na transaction deleta por reference sem filtrar source_key.
236
+ Evolucao: Adicionar source_key no where:
237
+
238
+ ```typescript
239
+ const sourceKey = preparedImoveis[0]?.source_key;
240
+ await tx.property.deleteMany({
241
+ where: {
242
+ reference: { in: references },
243
+ ...(sourceKey && { source_key: sourceKey })
244
+ }
245
+ });
246
+ ```
247
+
248
+ ### Nomenclatura padrao de source_key
249
+
250
+ Formato: `{crm}-{api|sync|vrsync}-{entidade-plural}`
251
+
252
+ Exemplos:
253
+ - `si9-api-imoveis`
254
+ - `jetimob-api-imoveis`
255
+ - `nifb-vrsync-imoveis`
256
+ - `si9-api-corretores`
257
+ - `manual-import-imoveis` (importacoes manuais)
258
+
259
+ ### Ao criar modulo com multi-source
260
+
261
+ 1. Banco: adicionar coluna `source_key TEXT`
262
+ 2. Prisma: campo `source_key String?`
263
+ 3. entity-schema.ts: campo com `filterable: true` e enum das fontes
264
+ 4. Controller GET: aceitar `?source_key=` como query param
265
+ 5. Service listing: filtrar `where: { source_key }` quando presente
266
+ 6. Service upsert: garantir que source_key vem no payload e e persistido
267
+ 7. Service delete: usar source_key como filtro adicional no deleteMany
268
+ 8. Automations: cada integracao DEVE enviar source_key
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "@horizon-framework/api-nextjs-docs",
3
+ "version": "2.3.0",
4
+ "description": "Documentacao do backend generico Horizon — sync, search engine, SSOT, batch totals",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "publishConfig": {
8
+ "access": "public"
9
+ }
10
+ }