@horizon-framework/website-dev-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.
- package/agents/AGENTE_FULLSTACK.md +209 -0
- package/agents/AGENTE_LAYOUT_DESIGNER.md +930 -0
- package/checklists/ADICIONAR_CAMPO.md +95 -0
- package/checklists/CRIAR_SITE_NOVO.md +163 -0
- package/checklists/PUBLICAR_SITE.md +75 -0
- package/checklists/TROCAR_CRM.md +96 -0
- package/commercial/PACOTES_PAGINAS.md +86 -0
- package/commercial/POSSIBILIDADES_VENDA.md +52 -0
- package/index.md +54 -0
- package/knowledge/API_PROPERTY.md +566 -0
- package/knowledge/ARQUITETURA_GERAL.md +169 -0
- package/knowledge/ARQUITETURA_MODULOS_WEB.md +241 -0
- package/knowledge/BATCH_TOTALS_API.md +200 -0
- package/knowledge/CAPABILITIES.md +190 -0
- package/knowledge/COMPONENTES_GLOBAIS_UI.md +407 -0
- package/knowledge/CRMS_INTEGRACOES.md +151 -0
- package/knowledge/DESIGN_AVANCADO.md +403 -0
- package/knowledge/DESIGN_SYSTEM_CATALOG.md +349 -0
- package/knowledge/DESIGN_TEMPLATES_CATALOG.md +61 -0
- package/knowledge/DOMINIO_ENTIDADES.md +328 -0
- package/knowledge/HOOKS_REGISTRY.md +127 -0
- package/knowledge/MODULE_CREATION_PATTERN.md +624 -0
- package/knowledge/NAVEGACAO_DINAMICA.md +233 -0
- package/knowledge/PAGINAS_BASICAS.md +63 -0
- package/knowledge/SEARCH_ENGINE_API.md +1038 -0
- package/knowledge/SISTEMA_MODULAR.md +109 -0
- package/knowledge/SISTEMA_SCHEMAS.md +126 -0
- package/knowledge/SSOT_SPECIFICATION_V2.md +333 -0
- package/knowledge/UI_KIT_COMPLETO.md +125 -0
- package/modules/property/MODULO_IMOBILIARIO.md +356 -0
- package/package.json +17 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Arquitetura Geral — Horizon Platform
|
|
2
|
+
|
|
3
|
+
> Visao completa do projeto: 5 partes, 3 deploys Vercel, monorepo com pnpm.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Estrutura do Projeto
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
raiz/
|
|
11
|
+
├── apps/
|
|
12
|
+
│ ├── web/ ← Frontend (Next.js 15, React 19, porta 3001)
|
|
13
|
+
│ ├── api/ ← Backend API (Next.js 14, Prisma, porta 5000)
|
|
14
|
+
│ └── automations/ ← Automacoes (Next.js 15, porta 8080)
|
|
15
|
+
├── ai/ ← Cerebro do agente (docs NPM, config cliente)
|
|
16
|
+
├── docs/ ← Docs especificas deste site/cliente
|
|
17
|
+
├── dev/ ← Area de trabalho temporaria (screenshots, logs, rascunhos)
|
|
18
|
+
├── CLAUDE.md ← Prompt master do agente
|
|
19
|
+
└── horizon.config.md ← Prontuario do site (versao, cliente, CRM, modulos)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Os 3 Apps
|
|
25
|
+
|
|
26
|
+
### WEB — Frontend (apps/web/)
|
|
27
|
+
- **Framework:** Next.js 15.5.7 + React 19.1.0
|
|
28
|
+
- **Porta dev:** 3001
|
|
29
|
+
- **Stack:** Tailwind 4 + shadcn/ui + Framer Motion + Zustand + React Query
|
|
30
|
+
- **Responsabilidade:** Todas as paginas do site, busca de imoveis, UI completa
|
|
31
|
+
- **Build:** `NODE_OPTIONS='--max-old-space-size=4096' next build`
|
|
32
|
+
- **Storybook:** porta 6006
|
|
33
|
+
- **Email preview:** via React Email
|
|
34
|
+
|
|
35
|
+
### API — Backend (apps/api/)
|
|
36
|
+
- **Framework:** Next.js 14.2.32 + React 18.3.1
|
|
37
|
+
- **Porta dev:** 5000
|
|
38
|
+
- **Stack:** Prisma 6 + PostgreSQL (DigitalOcean) + PostGIS + Zod
|
|
39
|
+
- **Responsabilidade:** API REST, busca multipart, sync com CRM, schemas SSOT
|
|
40
|
+
- **Build output:** standalone
|
|
41
|
+
- **Vercel region:** gru1 (Sao Paulo)
|
|
42
|
+
- **Serverless max:** 30s
|
|
43
|
+
|
|
44
|
+
### AUTOMATIONS — Integracoes (apps/automations/)
|
|
45
|
+
- **Framework:** Next.js 15.5.7 + React 19.0.0
|
|
46
|
+
- **Porta dev:** 8080
|
|
47
|
+
- **Stack:** Resend (emails) + React Email + Zod
|
|
48
|
+
- **Responsabilidade:** Receber leads, enviar emails, sincronizar com CRM
|
|
49
|
+
- **CORS:** Configura domínios permitidos
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Fluxo de Comunicacao
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Browser → WEB (3001) → API (5000) ← PostgreSQL + PostGIS
|
|
57
|
+
↘
|
|
58
|
+
AUTOMATIONS (8080) → Resend (emails)
|
|
59
|
+
→ CRM externo (Jetimob, SI9)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
1. Usuario visita o site (WEB)
|
|
63
|
+
2. WEB busca dados na API (imoveis, corretores, schemas)
|
|
64
|
+
3. Usuario envia formulario → WEB envia lead para AUTOMATIONS
|
|
65
|
+
4. AUTOMATIONS envia email via Resend + sincroniza com CRM
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Deploy Vercel
|
|
70
|
+
|
|
71
|
+
Cada app e deployada separadamente na Vercel:
|
|
72
|
+
- **WEB:** Site publico (dominio do cliente)
|
|
73
|
+
- **API:** API endpoints (region gru1 - SP)
|
|
74
|
+
- **AUTOMATIONS:** Endpoints de integracao
|
|
75
|
+
|
|
76
|
+
Cada app tem seu `vercel.json` com:
|
|
77
|
+
- framework: nextjs
|
|
78
|
+
- installCommand: pnpm i
|
|
79
|
+
- functions maxDuration: 30s
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Variaveis de Ambiente
|
|
84
|
+
|
|
85
|
+
### WEB (.env.local)
|
|
86
|
+
```
|
|
87
|
+
API_SECRET_KEY= # Chave da API
|
|
88
|
+
ADMIN_PASSWORD= # Senha admin (inspect)
|
|
89
|
+
NEXT_PUBLIC_SITE_URL= # URL do site (ex: http://localhost:3001)
|
|
90
|
+
NEXT_PUBLIC_API_URL= # URL da API (ex: http://localhost:5000)
|
|
91
|
+
NEXT_PUBLIC_AUTOMATIONS_URL= # URL automations (ex: http://localhost:8080)
|
|
92
|
+
YOUTUBE_API_KEY= # YouTube Data API v3
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### API (.env)
|
|
96
|
+
```
|
|
97
|
+
DATABASE_URL= # PostgreSQL connection string
|
|
98
|
+
API_SECRET_KEY= # Chave de autenticacao
|
|
99
|
+
API_BASE_URL= # URL da propria API
|
|
100
|
+
FRONTEND_BASE_URL= # URL do frontend
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### AUTOMATIONS (.env.local)
|
|
104
|
+
```
|
|
105
|
+
API_SECRET_KEY= # Chave da API
|
|
106
|
+
RESEND_API_KEY= # Chave do Resend (emails)
|
|
107
|
+
JETIMOB_PUBLIC_KEY= # CRM Jetimob
|
|
108
|
+
JETIMOB_PRIVATE_KEY= # CRM Jetimob
|
|
109
|
+
NEXT_PUBLIC_API_URL= # URL da API
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Banco de Dados
|
|
115
|
+
|
|
116
|
+
- **Provider:** PostgreSQL em DigitalOcean
|
|
117
|
+
- **Porta:** 25061 (SSL required)
|
|
118
|
+
- **ORM:** Prisma 6
|
|
119
|
+
- **Extensoes:** PostGIS (geolocation), UUID-OSSP
|
|
120
|
+
- **Models:** Property, Broker, Branch, spatial_ref_sys
|
|
121
|
+
- **Features:** Full-text search (tsvector), busca geoespacial (geometry)
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Pasta /dev/
|
|
126
|
+
|
|
127
|
+
Area de trabalho temporaria na raiz. Pode conter:
|
|
128
|
+
- Screenshots de debug
|
|
129
|
+
- Logs de teste
|
|
130
|
+
- Arquivos temporarios de desenvolvimento
|
|
131
|
+
- NAO e versionada com importancia (pode ser limpa)
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Pasta /ai/
|
|
136
|
+
|
|
137
|
+
Cerebro do agente de IA. Contem:
|
|
138
|
+
- `package.json` — instala pacotes de conhecimento via NPM
|
|
139
|
+
- `horizon.config.md` — prontuario do site
|
|
140
|
+
- `client/` — docs especificas deste cliente
|
|
141
|
+
- `node_modules/` — docs da plataforma + modulos + integracoes
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Scripts Uteis
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Desenvolvimento
|
|
149
|
+
cd apps/web && pnpm dev # Frontend porta 3001
|
|
150
|
+
cd apps/api && pnpm dev # API porta 5000
|
|
151
|
+
cd apps/automations && pnpm dev # Automations porta 8080
|
|
152
|
+
|
|
153
|
+
# Build
|
|
154
|
+
cd apps/web && pnpm build
|
|
155
|
+
cd apps/api && pnpm build
|
|
156
|
+
|
|
157
|
+
# Banco de dados (API)
|
|
158
|
+
cd apps/api && pnpm db:setup # Setup inicial
|
|
159
|
+
cd apps/api && pnpm db:create # Criar tabelas
|
|
160
|
+
cd apps/api && pnpm db:sync # Sync Prisma
|
|
161
|
+
cd apps/api && npx prisma generate # Gerar client
|
|
162
|
+
|
|
163
|
+
# Storybook
|
|
164
|
+
cd apps/web && pnpm storybook # Porta 6006
|
|
165
|
+
|
|
166
|
+
# Email preview
|
|
167
|
+
cd apps/web && pnpm email
|
|
168
|
+
cd apps/automations && pnpm email
|
|
169
|
+
```
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# Arquitetura de Módulos - WEB
|
|
2
|
+
|
|
3
|
+
> Guia de estrutura padrão para módulos no frontend (`/apps/web/src/modules/`)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Estrutura Padrão de um Módulo
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
apps/web/src/modules/{entity}/
|
|
11
|
+
├── core/ # Lógica core do módulo
|
|
12
|
+
│ ├── module.config.ts # Configuração do módulo
|
|
13
|
+
│ │
|
|
14
|
+
│ ├── item/ # Tudo relacionado a UM item
|
|
15
|
+
│ │ ├── index.ts # Exports principais
|
|
16
|
+
│ │ ├── {entity}-url-builder.ts # Builder de URLs
|
|
17
|
+
│ │ ├── enrichMapper.ts # Mapper para enriquecer dados
|
|
18
|
+
│ │ │
|
|
19
|
+
│ │ ├── schemas/ # Schemas do item
|
|
20
|
+
│ │ │ ├── index.ts # Exports dos schemas
|
|
21
|
+
│ │ │ ├── schema.ts # Schema TypeScript (SSOT)
|
|
22
|
+
│ │ │ ├── schema.generated.json # Schema gerado da API
|
|
23
|
+
│ │ │ ├── base-schema.json # Schema base (campos)
|
|
24
|
+
│ │ │ ├── display-layer-schema.json # Overrides de UI
|
|
25
|
+
│ │ │ └── computed-schema.json # Campos computados
|
|
26
|
+
│ │ │
|
|
27
|
+
│ │ └── computed-fields/ # Campos computados runtime
|
|
28
|
+
│ │ ├── index.ts
|
|
29
|
+
│ │ ├── {entity}-url.ts # Gerador de URL
|
|
30
|
+
│ │ └── assembleComputedData.ts
|
|
31
|
+
│ │
|
|
32
|
+
│ ├── list/ # Lógica de listagem (opcional)
|
|
33
|
+
│ │ └── ...
|
|
34
|
+
│ │
|
|
35
|
+
│ ├── libs/ # Bibliotecas específicas (opcional)
|
|
36
|
+
│ │ └── ...
|
|
37
|
+
│ │
|
|
38
|
+
│ └── services/ # Services do módulo
|
|
39
|
+
│ └── {Entity}Service.ts
|
|
40
|
+
│
|
|
41
|
+
└── features/ # Features/páginas
|
|
42
|
+
├── item-display/ # Página individual
|
|
43
|
+
│ └── page-display/
|
|
44
|
+
│ ├── components/
|
|
45
|
+
│ │ └── sections/
|
|
46
|
+
│ └── data/
|
|
47
|
+
│ └── fetch{Entity}SSR.ts
|
|
48
|
+
│
|
|
49
|
+
└── search-list/ # Listagem com busca (opcional)
|
|
50
|
+
├── components/
|
|
51
|
+
│ └── fields/
|
|
52
|
+
├── config/
|
|
53
|
+
│ └── schema.ts
|
|
54
|
+
└── hooks/
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Arquivos Obrigatórios
|
|
60
|
+
|
|
61
|
+
### **Core Item**
|
|
62
|
+
|
|
63
|
+
| Arquivo | Responsabilidade |
|
|
64
|
+
|---------|------------------|
|
|
65
|
+
| `core/item/index.ts` | Exporta enrichMapper, urlBuilder, schemas |
|
|
66
|
+
| `core/item/{entity}-url-builder.ts` | Gera URLs para páginas da entidade |
|
|
67
|
+
| `core/item/enrichMapper.ts` | Enriquece dados com schema + computed |
|
|
68
|
+
|
|
69
|
+
### **Schemas**
|
|
70
|
+
|
|
71
|
+
| Arquivo | Responsabilidade |
|
|
72
|
+
|---------|------------------|
|
|
73
|
+
| `core/item/schemas/index.ts` | Exporta todos os schemas |
|
|
74
|
+
| `core/item/schemas/schema.ts` | Schema TypeScript principal |
|
|
75
|
+
| `core/item/schemas/schema.generated.json` | Schema gerado pela API (build time) |
|
|
76
|
+
|
|
77
|
+
### **Computed Fields**
|
|
78
|
+
|
|
79
|
+
| Arquivo | Responsabilidade |
|
|
80
|
+
|---------|------------------|
|
|
81
|
+
| `core/item/computed-fields/index.ts` | Exporta funções de computed |
|
|
82
|
+
| `core/item/computed-fields/{entity}-url.ts` | Gera URL do item |
|
|
83
|
+
| `core/item/computed-fields/assembleComputedData.ts` | Monta objeto computed |
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🔄 Fluxo de Dados
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
API Response (raw data)
|
|
91
|
+
│
|
|
92
|
+
▼
|
|
93
|
+
┌─────────────────────┐
|
|
94
|
+
│ enrichMapper() │ ← Combina: data + schema + computedData
|
|
95
|
+
└─────────────────────┘
|
|
96
|
+
│
|
|
97
|
+
▼
|
|
98
|
+
┌─────────────────────┐
|
|
99
|
+
│ assembleComputed() │ ← Gera campos runtime (URLs, etc)
|
|
100
|
+
└─────────────────────┘
|
|
101
|
+
│
|
|
102
|
+
▼
|
|
103
|
+
┌─────────────────────┐
|
|
104
|
+
│ EnrichMapperResult │ → { data, schema, computedData, computedSchema }
|
|
105
|
+
└─────────────────────┘
|
|
106
|
+
│
|
|
107
|
+
▼
|
|
108
|
+
Components
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Exemplo: Broker Module
|
|
114
|
+
|
|
115
|
+
### **core/item/index.ts**
|
|
116
|
+
```typescript
|
|
117
|
+
export { brokerEnrichMapper as enrichMapper } from "./enrichMapper";
|
|
118
|
+
export { brokerURLBuilder } from "./broker-url-builder";
|
|
119
|
+
export * from "./schemas";
|
|
120
|
+
export * from "./computed-fields";
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### **core/item/schemas/index.ts**
|
|
124
|
+
```typescript
|
|
125
|
+
export { brokerEntitySchema, getBrokerEntitySchema, type EntitySchema, type FieldSchema } from "./schema";
|
|
126
|
+
export { default as baseSchema } from "./base-schema.json";
|
|
127
|
+
export { default as displayLayerSchema } from "./display-layer-schema.json";
|
|
128
|
+
export { default as computedSchema } from "./computed-schema.json";
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### **core/item/schemas/schema.ts**
|
|
132
|
+
```typescript
|
|
133
|
+
import generatedSchema from "./schema.generated.json";
|
|
134
|
+
|
|
135
|
+
export interface EntitySchema {
|
|
136
|
+
entity: string;
|
|
137
|
+
version: string;
|
|
138
|
+
fields: FieldSchema[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface FieldSchema {
|
|
142
|
+
key: string;
|
|
143
|
+
type: string;
|
|
144
|
+
categories: string[];
|
|
145
|
+
ui: { label: string; description?: string };
|
|
146
|
+
audit: { origin: string };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const brokerEntitySchema = generatedSchema as EntitySchema;
|
|
150
|
+
|
|
151
|
+
export async function getBrokerEntitySchema(): Promise<EntitySchema> {
|
|
152
|
+
return brokerEntitySchema;
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### **core/item/computed-fields/assembleComputedData.ts**
|
|
157
|
+
```typescript
|
|
158
|
+
import { generateBrokerUrl } from "./broker-url";
|
|
159
|
+
|
|
160
|
+
export function assembleComputedData(rawData: Record<string, any>): Record<string, any> {
|
|
161
|
+
return {
|
|
162
|
+
src: generateBrokerUrl(rawData),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### **core/item/enrichMapper.ts**
|
|
168
|
+
```typescript
|
|
169
|
+
import { baseSchema, displayLayerSchema, computedSchema } from "./schemas";
|
|
170
|
+
import { mergeArraysByKey } from "@/libs/horizon-utils/merge-arrays-by-key";
|
|
171
|
+
import type { EnrichMapperResult, FieldMetadata } from "@/libs/domain-data-display-enricher/types";
|
|
172
|
+
import { assembleComputedData } from "./computed-fields";
|
|
173
|
+
|
|
174
|
+
const displaySchema = mergeArraysByKey(baseSchema.fields, displayLayerSchema as any, "key");
|
|
175
|
+
|
|
176
|
+
export const brokerEnrichMapper = (rawData: Record<string, any>): EnrichMapperResult => {
|
|
177
|
+
return {
|
|
178
|
+
data: rawData,
|
|
179
|
+
schema: displaySchema as FieldMetadata[],
|
|
180
|
+
computedData: assembleComputedData(rawData),
|
|
181
|
+
computedSchema: computedSchema as FieldMetadata[],
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 🆚 Comparação: Property vs Broker
|
|
189
|
+
|
|
190
|
+
| Aspecto | Property | Broker |
|
|
191
|
+
|---------|----------|--------|
|
|
192
|
+
| **Complexidade** | Alta | Baixa |
|
|
193
|
+
| `core/item/schemas/` | | |
|
|
194
|
+
| `core/item/computed-fields/` | | |
|
|
195
|
+
| `core/libs/` | (7 módulos) | |
|
|
196
|
+
| `core/list/` | | |
|
|
197
|
+
| `features/search-list/` | (20+ componentes) | |
|
|
198
|
+
| `features/item-display/` | | |
|
|
199
|
+
|
|
200
|
+
**Use Property** como referência para módulos complexos com busca.
|
|
201
|
+
**Use Broker** como template para módulos simples (página individual apenas).
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🚫 Erros Comuns
|
|
206
|
+
|
|
207
|
+
### Schemas fora de `core/item/`
|
|
208
|
+
```
|
|
209
|
+
# ERRADO
|
|
210
|
+
broker/core/schemas/
|
|
211
|
+
|
|
212
|
+
# CORRETO
|
|
213
|
+
broker/core/item/schemas/
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Imports com caminho antigo
|
|
217
|
+
```typescript
|
|
218
|
+
// ERRADO
|
|
219
|
+
import baseSchema from "@/modules/broker/core/schemas/base-schema.json";
|
|
220
|
+
|
|
221
|
+
// CORRETO
|
|
222
|
+
import { baseSchema } from "./schemas";
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Dependência de pacote npm não instalado
|
|
226
|
+
```typescript
|
|
227
|
+
// ERRADO (se broker-model não está no package.json)
|
|
228
|
+
import type { EntitySchema } from "@horizon-domains/broker-model";
|
|
229
|
+
|
|
230
|
+
// CORRETO (tipos locais)
|
|
231
|
+
export interface EntitySchema { ... }
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Referências
|
|
237
|
+
|
|
238
|
+
- **API Module Pattern**: [MODULE_CREATION_PATTERN.md](./MODULE_CREATION_PATTERN.md)
|
|
239
|
+
- **Search Engine API**: [SEARCH_ENGINE_API.md](./SEARCH_ENGINE_API.md)
|
|
240
|
+
- **Property Module**: `/apps/web/src/modules/property/` (referência completa)
|
|
241
|
+
- **Broker Module**: `/apps/web/src/modules/broker/` (referência simplificada)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Batch Totals API - Menu Dinâmico com Performance
|
|
2
|
+
|
|
3
|
+
## **Problema Resolvido**
|
|
4
|
+
|
|
5
|
+
**Cenário:** Menu de navegação em site imobiliário
|
|
6
|
+
```
|
|
7
|
+
IMÓVEIS
|
|
8
|
+
├── Apartamento 2 quartos (845) ← precisa do total
|
|
9
|
+
├── Apartamento 3 quartos (723)
|
|
10
|
+
├── Casa 2 quartos (400)
|
|
11
|
+
├── Casa 3 quartos (169)
|
|
12
|
+
└── ... mais 12 categorias
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Solução Ruim:** 16 requests HTTP separados
|
|
16
|
+
**Solução Inteligente:** 1 request com array de queries
|
|
17
|
+
|
|
18
|
+
## **Performance**
|
|
19
|
+
|
|
20
|
+
| Abordagem | HTTP Calls | Tempo | Eficiência |
|
|
21
|
+
|-----------|------------|-------|------------|
|
|
22
|
+
| **Múltiplos requests** | 16 | ~2.3s | Lento |
|
|
23
|
+
| **Batch Totals** | 1 | ~250ms | 90% mais rápido |
|
|
24
|
+
|
|
25
|
+
## **API Specification**
|
|
26
|
+
|
|
27
|
+
### **Endpoint:**
|
|
28
|
+
```
|
|
29
|
+
POST /api/property/batch-totals
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### **Request Format:**
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"queries": [
|
|
36
|
+
{
|
|
37
|
+
"key": "apartamento_2q",
|
|
38
|
+
"filters": {
|
|
39
|
+
"tipo": "Apartamento",
|
|
40
|
+
"dormitorios": 2
|
|
41
|
+
},
|
|
42
|
+
"meta": { "total": true }
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"key": "casa_3q",
|
|
46
|
+
"filters": {
|
|
47
|
+
"tipo": "Casa",
|
|
48
|
+
"dormitorios": 3
|
|
49
|
+
},
|
|
50
|
+
"meta": { "total": true }
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### **Response Format:**
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"results": {
|
|
60
|
+
"apartamento_2q": { "total": 845 },
|
|
61
|
+
"casa_3q": { "total": 169 }
|
|
62
|
+
},
|
|
63
|
+
"metadata": {
|
|
64
|
+
"executionTime": 245,
|
|
65
|
+
"queriesProcessed": 2,
|
|
66
|
+
"parallelExecution": true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## **Implementação Técnica**
|
|
72
|
+
|
|
73
|
+
### **Arquitetura:**
|
|
74
|
+
```
|
|
75
|
+
Controller → Service → Promise.all() → advancedSearch() × N
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### **Características:**
|
|
79
|
+
- **Execução paralela** com `Promise.all()`
|
|
80
|
+
- **Keys únicas** para cada query
|
|
81
|
+
- **Filtros flexíveis** (qualquer combinação)
|
|
82
|
+
- **Error handling** individual por query
|
|
83
|
+
- **Performance logs** automáticos
|
|
84
|
+
|
|
85
|
+
## **Casos de Uso**
|
|
86
|
+
|
|
87
|
+
### **1. Menu de Navegação**
|
|
88
|
+
```javascript
|
|
89
|
+
const menuQueries = [
|
|
90
|
+
{ key: "apt_2q", filters: { tipo: "Apartamento", dormitorios: 2 } },
|
|
91
|
+
{ key: "apt_3q", filters: { tipo: "Apartamento", dormitorios: 3 } },
|
|
92
|
+
{ key: "casa_2q", filters: { tipo: "Casa", dormitorios: 2 } }
|
|
93
|
+
];
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### **2. Dashboard com Métricas**
|
|
97
|
+
```javascript
|
|
98
|
+
const dashboardQueries = [
|
|
99
|
+
{ key: "total_ativos", filters: { status: "ativo" } },
|
|
100
|
+
{ key: "total_vendidos", filters: { status: "vendido" } },
|
|
101
|
+
{ key: "total_premium", filters: { destaque: true } }
|
|
102
|
+
];
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### **3. Filtros Dinâmicos**
|
|
106
|
+
```javascript
|
|
107
|
+
const filterCounts = [
|
|
108
|
+
{ key: "por_cidade", filters: { endereco_cidade: "Florianópolis" } },
|
|
109
|
+
{ key: "por_preco", filters: { valor_venda: { gte: 500000 } } },
|
|
110
|
+
{ key: "com_piscina", filters: { caracteristicas: { contains: "Piscina" } } }
|
|
111
|
+
];
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## **Frontend Integration**
|
|
115
|
+
|
|
116
|
+
### **Next.js Static Props:**
|
|
117
|
+
```typescript
|
|
118
|
+
export async function getStaticProps() {
|
|
119
|
+
const response = await fetch('/api/property/batch-totals', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({ queries: MENU_QUERIES })
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const { results } = await response.json();
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
props: { menuCounters: results },
|
|
129
|
+
revalidate: 7200 // Cache 2h
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### **SWR Client-Side:**
|
|
135
|
+
```typescript
|
|
136
|
+
import useSWR from 'swr';
|
|
137
|
+
|
|
138
|
+
const { data: counters } = useSWR(
|
|
139
|
+
'menu-counters',
|
|
140
|
+
() => batchTotals(MENU_QUERIES),
|
|
141
|
+
{ refreshInterval: 300000 } // 5min
|
|
142
|
+
);
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## **Limitações & Considerações**
|
|
146
|
+
|
|
147
|
+
### **Limites:**
|
|
148
|
+
- **Máximo:** 20 queries por request
|
|
149
|
+
- **Timeout:** 30 segundos total
|
|
150
|
+
- **Cache:** Recomendado 1-4 horas
|
|
151
|
+
|
|
152
|
+
### **Error Handling:**
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"results": {
|
|
156
|
+
"valid_query": { "total": 845 },
|
|
157
|
+
"invalid_query": { "error": "Campo inválido", "total": 0 }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### **Performance Tips:**
|
|
163
|
+
- Cache no cliente (SWR/React Query)
|
|
164
|
+
- Cache no servidor (Redis)
|
|
165
|
+
- Revalidação com webhooks
|
|
166
|
+
- Não usar para queries complexas
|
|
167
|
+
|
|
168
|
+
## **Exemplo Completo**
|
|
169
|
+
|
|
170
|
+
### **Request Real:**
|
|
171
|
+
```bash
|
|
172
|
+
curl -X POST http://localhost:5000/api/property/batch-totals \
|
|
173
|
+
-H "Content-Type: application/json" \
|
|
174
|
+
-d '{
|
|
175
|
+
"queries": [
|
|
176
|
+
{
|
|
177
|
+
"key": "apartamento_2q",
|
|
178
|
+
"filters": { "tipo": "Apartamento", "dormitorios": 2 },
|
|
179
|
+
"meta": { "total": true }
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### **Response Real:**
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"results": {
|
|
189
|
+
"apartamento_2q": { "total": 845 }
|
|
190
|
+
},
|
|
191
|
+
"metadata": {
|
|
192
|
+
"executionTime": 156,
|
|
193
|
+
"queriesProcessed": 1
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
**Batch Totals API - Solução definitiva para menus dinâmicos com performance!**
|