# APIs - DRE Gerencial ## Visão Geral O sistema possui duas APIs principais construídas com Next.js App Router, utilizando Drizzle ORM para interação com PostgreSQL. ## Estrutura das APIs ### 1. **API DRE Gerencial** (`/api/dre/route.ts`) #### Endpoint ``` GET /api/dre ``` #### Descrição Retorna dados consolidados da view `view_dre_gerencial` para construção da interface hierárquica. #### Implementação ```typescript import db from '@/db'; import { sql } from 'drizzle-orm'; import { NextResponse } from 'next/server'; export async function GET() { try { const data = await db.execute(sql`SELECT * FROM view_dre_gerencial`); return NextResponse.json(data.rows); } catch (error) { console.error('Erro ao buscar dados da view:', error); return NextResponse.json( { error: 'Erro ao carregar dados' }, { status: 500 } ); } } ``` #### Response ```typescript interface DREItem { codfilial: string; data_competencia: string; data_caixa: string; grupo: string; subgrupo: string; centro_custo: string; codigo_conta: number; conta: string; valor: string; } ``` #### Casos de Uso - Carregamento inicial da interface DRE - Construção da hierarquia Grupo → Subgrupo → Centro de Custo → Conta - Cálculo de totais e percentuais --- ### 2. **API Analítica** (`/api/analitico/route.ts`) #### Endpoint ``` GET /api/analitico?dataInicio=YYYY-MM&dataFim=YYYY-MM&[filtros] ``` #### Parâmetros | Parâmetro | Tipo | Obrigatório | Descrição | |-----------|------|-------------|-----------| | `dataInicio` | string | ✅ | Período inicial (YYYY-MM) | | `dataFim` | string | ✅ | Período final (YYYY-MM) | | `centroCusto` | string | ❌ | Filtro por centro de custo | | `codigoGrupo` | string | ❌ | Filtro por código do grupo | | `codigoSubgrupo` | string | ❌ | Filtro por código do subgrupo | | `codigoConta` | string | ❌ | Filtro por código da conta | #### Implementação ```typescript export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const dataInicio = searchParams.get('dataInicio'); const dataFim = searchParams.get('dataFim'); const centroCusto = searchParams.get('centroCusto'); const codigoGrupo = searchParams.get('codigoGrupo'); const codigoSubgrupo = searchParams.get('codigoSubgrupo'); const codigoConta = searchParams.get('codigoConta'); if (!dataInicio || !dataFim) { return NextResponse.json( { message: 'Parâmetros obrigatórios: dataInicio, dataFim' }, { status: 400 } ); } // Construção dinâmica da query baseada nos filtros let query; if (centroCusto || codigoGrupo || codigoSubgrupo || codigoConta) { // Query com filtros específicos query = buildFilteredQuery(dataInicio, dataFim, filtros); } else { // Query simples por período query = buildSimpleQuery(dataInicio, dataFim); } const data = await db.execute(query); return NextResponse.json(data.rows); } catch (error) { console.error('Erro ao buscar dados analíticos:', error); return NextResponse.json( { message: 'Erro ao buscar dados analíticos', error: error instanceof Error ? error.message : String(error), }, { status: 500 } ); } } ``` #### Response ```typescript interface AnaliticoItem { codigo_grupo: string; codigo_subgrupo: string; codigo_fornecedor: string; nome_fornecedor: string; id: number; codfilial: string; recnum: number; data_competencia: string; data_vencimento: string; data_pagamento: string; data_caixa: string; codigo_conta: string; conta: string; codigo_centrocusto: string; valor: number; historico: string; historico2: string; created_at: string; updated_at: string; } ``` ## Estratégias de Query ### 1. **Query Simples (Sem Filtros Específicos)** ```sql SELECT ffa.codigo_fornecedor, ffa.nome_fornecedor, ffa.id, ffa.codfilial, ffa.recnum, ffa.data_competencia, ffa.data_vencimento, ffa.data_pagamento, ffa.data_caixa, ffa.codigo_conta, ffa.conta, ffa.codigo_centrocusto, ffa.valor, ffa.historico, ffa.historico2, ffa.created_at, ffa.updated_at FROM fato_financeiro_analitico AS ffa WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2 ``` ### 2. **Query com Filtros de Centro de Custo e Conta** ```sql SELECT ffa.* FROM fato_financeiro_analitico AS ffa WHERE to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $1 AND $2 AND ffa.codigo_centrocusto = $3 AND ffa.codigo_conta = $4 ``` ### 3. **Query com Filtros de Grupo/Subgrupo** ```sql SELECT ffa.* FROM fato_financeiro_analitico AS ffa WHERE EXISTS ( SELECT 1 FROM public.view_dre_gerencial AS dre WHERE ffa.codigo_conta = dre.codigo_conta::text AND ffa.codigo_centrocusto = dre.centro_custo AND to_char(ffa.data_competencia, 'YYYY-MM') = to_char(dre.data_competencia, 'YYYY-MM') AND SUBSTRING(dre.grupo FROM '^\\s*(\\d+)\\s*\\.') = $1 AND SUBSTRING(dre.subgrupo FROM '^\\s*(\\d+(?:\\.\\d+)+)\\s*-') = $2 ) AND to_char(ffa.data_competencia, 'YYYY-MM') BETWEEN $3 AND $4 ``` ## Tratamento de Erros ### 1. **Validação de Parâmetros** ```typescript if (!dataInicio || !dataFim) { return NextResponse.json( { message: 'Parâmetros obrigatórios: dataInicio, dataFim' }, { status: 400 } ); } ``` ### 2. **Tratamento de Erros de Banco** ```typescript try { const data = await db.execute(query); return NextResponse.json(data.rows); } catch (error) { console.error('Erro ao buscar dados:', error); return NextResponse.json( { message: 'Erro ao buscar dados', error: error instanceof Error ? error.message : String(error), }, { status: 500 } ); } ``` ### 3. **Códigos de Status HTTP** | Status | Cenário | Response | |--------|---------|----------| | 200 | Sucesso | Dados solicitados | | 400 | Parâmetros inválidos | Mensagem de erro | | 500 | Erro interno | Detalhes do erro | ## Performance e Otimização ### 1. **Índices Recomendados** ```sql -- Para filtros por data CREATE INDEX idx_fato_financeiro_data_competencia ON fato_financeiro_analitico (data_competencia); -- Para filtros por centro de custo CREATE INDEX idx_fato_financeiro_centro_custo ON fato_financeiro_analitico (codigo_centrocusto); -- Para filtros por conta CREATE INDEX idx_fato_financeiro_conta ON fato_financeiro_analitico (codigo_conta); ``` ### 2. **Estratégias de Cache** - **Client-side**: React Query para cache de dados - **Server-side**: Cache de views materializadas - **CDN**: Para assets estáticos ### 3. **Paginação (Futuro)** ```typescript interface PaginatedResponse { data: T[]; pagination: { page: number; limit: number; total: number; totalPages: number; }; } ``` ## Segurança ### 1. **Validação de Input** - Sanitização de parâmetros de query - Validação de tipos de dados - Escape de caracteres especiais ### 2. **SQL Injection Prevention** - Uso de prepared statements via Drizzle - Parâmetros tipados - Validação de entrada ### 3. **Rate Limiting (Futuro)** ```typescript // Implementação de rate limiting const rateLimit = new Map(); export async function GET(request: NextRequest) { const ip = request.ip; const now = Date.now(); const windowMs = 15 * 60 * 1000; // 15 minutos const maxRequests = 100; // Lógica de rate limiting } ``` ## Monitoramento ### 1. **Logs Estruturados** ```typescript console.log({ timestamp: new Date().toISOString(), endpoint: '/api/analitico', method: 'GET', params: { dataInicio, dataFim, centroCusto }, duration: Date.now() - startTime, status: 'success' }); ``` ### 2. **Métricas de Performance** - Tempo de resposta por endpoint - Número de requisições por minuto - Taxa de erro por endpoint - Uso de memória e CPU ### 3. **Health Check (Futuro)** ```typescript // GET /api/health export async function GET() { try { await db.execute(sql`SELECT 1`); return NextResponse.json({ status: 'healthy', timestamp: new Date() }); } catch (error) { return NextResponse.json({ status: 'unhealthy', error: error.message }, { status: 500 }); } } ``` ## Testes ### 1. **Testes Unitários** ```typescript // Exemplo de teste para API DRE describe('/api/dre', () => { it('should return DRE data', async () => { const response = await fetch('/api/dre'); const data = await response.json(); expect(response.status).toBe(200); expect(Array.isArray(data)).toBe(true); }); }); ``` ### 2. **Testes de Integração** ```typescript // Teste com filtros describe('/api/analitico', () => { it('should filter by date range', async () => { const params = new URLSearchParams({ dataInicio: '2024-01', dataFim: '2024-12' }); const response = await fetch(`/api/analitico?${params}`); const data = await response.json(); expect(response.status).toBe(200); expect(data.every(item => item.data_competencia.startsWith('2024') )).toBe(true); }); }); ``` ## Próximos Passos 1. **Implementar Autenticação** JWT 2. **Adicionar Rate Limiting** por IP 3. **Implementar Cache Redis** para queries frequentes 4. **Adicionar Paginação** para grandes volumes 5. **Implementar Webhooks** para notificações 6. **Adicionar Documentação OpenAPI** (Swagger) 7. **Implementar Versionamento** de API 8. **Adicionar Monitoramento** com Prometheus/Grafana