Pular para o conteúdo principal
Versão: Versão Mercados

Arquitetura do Sistema

Esta página descreve a arquitetura técnica da plataforma Divino Alimento - Versão Mercados.

Visão Geral

A plataforma Divino Alimento é uma aplicação web full-stack que facilita a distribuição de produtos em múltiplos mercados. O sistema utiliza uma arquitetura híbrida que combina:

  • Backend legado com renderização server-side (Express + EJS)
  • Frontend como SPA (React + TypeScript)
  • APIs REST (legado) e GraphQL

Stack Tecnológico

Backend

Core

  • Runtime: Node.js
  • Framework: Express.js ^4.21.2
  • Template Engine: EJS ^3.1.6 (views legadas)
  • ORM: Sequelize ^6.37.1
  • Database: PostgreSQL (produção), SQLite (testes)

APIs

  • GraphQL: graphql ^16.11.0
  • GraphQL HTTP: graphql-http ^1.22.4
  • GraphQL IDE: Ruru ^2.0.0-beta.30

Autenticação

  • OAuth2/OIDC: express-openid-connect ^2.4.0
  • Session Management: Model Session com tokens

Utilitários

  • PDF Generation: PDFKit ^0.12.1
  • CSV Export: json2csv ^5.0.7
  • Environment Config: dotenv ^9.0.1

Desenvolvimento

  • Auto-reload: Nodemon ^2.0.7
  • Migrations: Sequelize CLI ^6.6.2

Frontend

Build & Tooling

  • Build Tool: Vite ^5.4.19
  • Language: TypeScript ^5.8.3
  • Linting: ESLint ^9.32.0
  • Package Manager: Bun (lockfile presente)

Framework & Bibliotecas

  • UI Framework: React ^18.3.1
  • React DOM: ^18.3.1
  • Routing: React Router DOM ^6.30.1

UI Components

  • Component Library: shadcn/ui (headless)
  • Primitives: Radix UI (30+ componentes)
    • Accordion, Alert Dialog, Avatar, Checkbox
    • Dialog, Dropdown Menu, Select, Tabs
    • Toast, Tooltip, e muitos outros
  • Icons: Lucide React ^0.462.0

Styling

  • CSS Framework: Tailwind CSS ^3.4.17
  • Typography: @tailwindcss/typography
  • Utilities:
    • clsx ^2.1.1
    • tailwind-merge ^2.6.0
    • class-variance-authority ^0.7.1
    • tailwindcss-animate ^1.0.7

State Management

  • Server State: TanStack React Query ^5.83.0
  • Client State: React Context API
  • GraphQL Client: graphql-request ^7.3.1

Forms & Validation

  • Forms: React Hook Form ^7.62.0
  • Resolvers: @hookform/resolvers ^3.10.0
  • Validation: Zod ^3.25.76

Componentes Especializados

  • Date Picker: React Day Picker ^8.10.1
  • Date Utils: date-fns ^3.6.0
  • Charts: Recharts ^2.15.4
  • Carousel: Embla Carousel React ^8.6.0
  • Notifications: Sonner ^1.7.4
  • Themes: Next Themes ^0.3.0
  • Drawer: Vaul ^0.9.9
  • OTP Input: input-otp ^1.4.2
  • Panels: react-resizable-panels ^2.1.9

Testing

Frameworks

  • BDD: Cucumber ^12.2.0
  • Unit: Jest (configurado)
  • E2E: Mocha ^11.7.4
  • Assertions: Chai ^4.3.6
  • Test Data: @faker-js/faker ^10.0.0

DevOps

  • Containerização: Docker & Docker Compose v2
  • Automação: Rake (Ruby-based)
  • Mock Auth: Mock OAuth2 Server (desenvolvimento)

Estrutura do Projeto

DivinoAlimento/
├── app/ # Backend Node.js
│ ├── src/
│ │ ├── server.js # Servidor Express
│ │ ├── routes.js # Definição de rotas
│ │ ├── api-graphql.js # Resolvers GraphQL
│ │ ├── api.graphql # Schema GraphQL
│ │ │
│ │ ├── controllers/ # Controllers REST
│ │ │ ├── CicloController.js
│ │ │ ├── CestaController.js
│ │ │ ├── ProdutoController.js
│ │ │ ├── OfertaController.js
│ │ │ ├── ComposicaoController.js
│ │ │ ├── PedidoConsumidoresController.js
│ │ │ ├── UsuarioController.js
│ │ │ ├── RelatoriosController.js
│ │ │ └── ...
│ │ │
│ │ ├── model/ # Business Logic
│ │ │ ├── Ciclo.js
│ │ │ ├── Produto.js
│ │ │ ├── Usuario.js
│ │ │ └── ...
│ │ │
│ │ ├── services/ # Service Layer
│ │ │ ├── services.js
│ │ │ └── ...
│ │ │
│ │ ├── db/ # SQL Queries
│ │ │ ├── cicloSql.js
│ │ │ ├── produtoSql.js
│ │ │ └── ...
│ │ │
│ │ └── views/ # Templates EJS (legado)
│ │ ├── index.ejs
│ │ ├── ciclo.ejs
│ │ └── parts/
│ │
│ ├── models/ # Sequelize Models
│ │ ├── index.js
│ │ ├── usuario.js
│ │ ├── ciclo.js
│ │ ├── produto.js
│ │ ├── oferta.js
│ │ ├── cesta.js
│ │ ├── session.js # Sessões GraphQL
│ │ └── ...
│ │
│ ├── migrations/ # Database Migrations
│ │ ├── 20210513-create-produto.js
│ │ ├── 20250923-create-sessions.js
│ │ └── ...
│ │
│ ├── features/ # BDD Tests (Cucumber)
│ │ ├── ciclo.feature
│ │ ├── usuario.feature
│ │ ├── sessions.feature
│ │ └── step_definitions/
│ │
│ ├── config/ # Configurações
│ │ └── config.js
│ │
│ ├── public/ # Assets estáticos
│ │
│ ├── package.json
│ ├── cucumber.js
│ └── jest.config.js

├── frontend/ # Frontend React
│ ├── src/
│ │ ├── main.tsx # Entry point
│ │ ├── App.tsx # Routing principal
│ │ │
│ │ ├── pages/ # Páginas
│ │ │ ├── Index.tsx # Homepage
│ │ │ ├── Login.tsx
│ │ │ ├── Register.tsx
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Cesta.tsx
│ │ │ ├── Resumo.tsx
│ │ │ │
│ │ │ ├── admin/ # Área administrativa
│ │ │ │ ├── AdminDashboard.tsx
│ │ │ │ ├── AdminMercados.tsx
│ │ │ │ ├── AdminProdutos.tsx
│ │ │ │ ├── AdminEstoque.tsx
│ │ │ │ └── ...
│ │ │ │
│ │ │ └── fornecedor/ # Área do fornecedor
│ │ │ ├── FornecedorLogin.tsx
│ │ │ ├── LojaProdutor.tsx
│ │ │ ├── PedidosAberto.tsx
│ │ │ └── ...
│ │ │
│ │ ├── components/ # Componentes
│ │ │ ├── ui/ # shadcn/ui components
│ │ │ │ ├── button.tsx
│ │ │ │ ├── card.tsx
│ │ │ │ ├── dialog.tsx
│ │ │ │ └── ... (50+)
│ │ │ │
│ │ │ └── layout/ # Layout components
│ │ │ ├── AppShell.tsx
│ │ │ ├── ResponsiveLayout.tsx
│ │ │ ├── AdminSidebar.tsx
│ │ │ └── ...
│ │ │
│ │ ├── hooks/ # Custom Hooks
│ │ │ ├── graphql.ts # GraphQL queries/mutations
│ │ │ ├── useCycle.tsx
│ │ │ └── use-mobile.tsx
│ │ │
│ │ ├── contexts/ # React Context
│ │ │ └── ConsumerContext.tsx
│ │ │
│ │ ├── assets/ # Imagens, ícones
│ │ │
│ │ └── lib/ # Utilitários
│ │
│ ├── public/ # Static assets
│ ├── index.html
│ ├── vite.config.ts
│ ├── tailwind.config.ts
│ ├── tsconfig.json
│ └── package.json

├── mock-oauth2-server/ # OAuth Mock (dev)
├── compose.live.yml # Docker Compose
├── compose.tests.yml # Docker Compose Tests
├── Rakefile # Rake tasks
├── env.example # Environment template
└── README.md

Arquitetura em Camadas

Backend

1. Camada de Apresentação

REST (Legado)

  • Templates EJS renderizados server-side
  • Bootstrap 5 + jQuery
  • Formulários tradicionais

GraphQL (Moderno)

  • Schema definido: /app/src/api.graphql
  • Endpoint: /graphql
  • IDE integrado: Ruru

2. Camada de Controle

REST Controllers

// app/src/controllers/CicloController.js
class CicloController {
async showIndex(req, res) {
const ciclos = await Ciclo.getAll();
res.render('ciclo-index', { ciclos });
}

async save(req, res) {
await Ciclo.create(req.body);
res.redirect('/ciclo-index');
}
}

GraphQL Resolvers

// app/src/api-graphql.js
const resolvers = {
Query: {
healthcheck: () => ({ status: 'OK' }),
systemInformation: () => ({ version: '1.0.0' })
},
Mutation: {
sessionLogin: async (parent, { input }, context) => {
const session = await UsuarioService.login(input);
return session;
}
}
};

3. Camada de Serviços

// app/src/services/services.js
class UsuarioService {
static async login(email, senha) {
// Valida credenciais
// Cria sessão
// Retorna token
}
}

4. Camada de Modelos

Sequelize Models (ORM)

// app/models/usuario.js
module.exports = (sequelize, DataTypes) => {
const Usuario = sequelize.define('Usuario', {
nome: DataTypes.STRING,
email: DataTypes.STRING,
perfis: DataTypes.ARRAY(DataTypes.STRING)
});

Usuario.associate = (models) => {
Usuario.hasMany(models.Oferta);
Usuario.hasMany(models.PedidoConsumidores);
};

return Usuario;
};

Business Models

// app/src/model/Ciclo.js
class Ciclo {
static async getAll() {
const sql = cicloSql.getAll();
return await db.query(sql);
}

static async getCicloAtivo() {
// Lógica de negócio
}
}

5. Camada de Dados

// app/src/db/cicloSql.js
const cicloSql = {
getAll: () => `
SELECT c.*,
COUNT(DISTINCT ce.id) as num_entregas,
COUNT(DISTINCT cc.id) as num_cestas
FROM ciclo c
LEFT JOIN cicloentregas ce ON c.id = ce.cicloid
LEFT JOIN ciclocestas cc ON c.id = cc.cicloid
GROUP BY c.id
ORDER BY c.id DESC
`
};

Frontend

1. Roteamento

// frontend/src/App.tsx
<Routes>
<Route path="/" element={<Index />} />
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<Dashboard />} />

{/* Admin */}
<Route path="/admin/login" element={<AdminLogin />} />
<Route path="/admin/dashboard" element={<AdminDashboard />} />
<Route path="/admin/mercados" element={<AdminMercados />} />

{/* Fornecedor */}
<Route path="/fornecedor/login" element={<FornecedorLogin />} />
<Route path="/fornecedor/loja" element={<LojaProdutor />} />
</Routes>

2. Data Fetching (GraphQL)

// frontend/src/hooks/graphql.ts
import { useMutation, useQuery } from '@tanstack/react-query';
import { request, gql } from 'graphql-request';

const LOGIN_MUTATION = gql`
mutation SessionLogin($input: LoginInput!) {
sessionLogin(input: $input) {
usuarioId
token
perfis
}
}
`;

export const useLoginUsuario = () => {
return useMutation({
mutationFn: (input: LoginInput) =>
request(GRAPHQL_ENDPOINT, LOGIN_MUTATION, { input })
});
};

3. State Management

Context API

// frontend/src/contexts/ConsumerContext.tsx
const ConsumerContext = createContext();

export const ConsumerProvider = ({ children }) => {
const [consumer, setConsumer] = useState(null);
const [cart, setCart] = useState([]);

return (
<ConsumerContext.Provider value={{ consumer, cart }}>
{children}
</ConsumerContext.Provider>
);
};

React Query (Cache de servidor)

const { data, isLoading } = useQuery({
queryKey: ['ciclos'],
queryFn: fetchCiclos
});

4. Componentes UI

// frontend/src/components/ui/button.tsx (shadcn/ui)
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md",
{
variants: {
variant: {
default: "bg-primary text-white",
outline: "border border-input",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 px-3",
},
},
}
);

export const Button = ({ variant, size, ...props }) => {
return <button className={buttonVariants({ variant, size })} {...props} />;
};

Modelo de Dados

Entidades Principais

Gestão de Usuários

  • Usuario: Usuários do sistema (admin, fornecedor, consumidor)
  • Session: Sessões de autenticação (tokens)

Gestão de Ciclos

  • Ciclo: Períodos de comercialização
  • CicloEntregas: Datas de entrega por ciclo
  • CicloCestas: Cestas disponíveis no ciclo
  • CicloProdutos: Produtos disponíveis no ciclo

Produtos e Ofertas

  • Produto: Catálogo de produtos
  • CategoriaProdutos: Categorias dos produtos
  • Oferta: Ofertas de fornecedores por ciclo
  • OfertaProdutos: Produtos específicos em cada oferta

Cestas e Composição

  • Cesta: Tipos de cestas (Kitandinha, PNAE, etc.)
  • Composicoes: Templates de composição
  • ComposicaoCestaProdutos: Produtos que compõem uma cesta
  • ComposicaoCestaOpcoes: Opções alternativas nas cestas
  • ComposicaoOfertaProdutos: Link entre composições e ofertas

Pedidos

  • PedidoConsumidores: Pedidos de consumidores
  • PedidoConsumidoresProdutos: Itens em cada pedido
  • PedidosFornecedores: Pedidos consolidados para fornecedores

Logística

  • PontoEntrega: Locais de entrega

Estoque

  • Movimentacao: Movimentações de estoque
  • TipoMovimentacao: Tipos de movimentação

Relacionamentos Principais

Usuario
├─ hasMany → Oferta
└─ hasMany → PedidoConsumidores

Ciclo
├─ hasMany → CicloEntregas
├─ hasMany → CicloCestas
├─ hasMany → CicloProdutos
├─ hasMany → Oferta
└─ hasMany → PedidoConsumidores

Produto
├─ belongsTo → CategoriaProdutos
├─ hasMany → OfertaProdutos
└─ hasMany → Movimentacao

Oferta
├─ belongsTo → Ciclo
├─ belongsTo → Usuario
└─ hasMany → OfertaProdutos

Cesta
├─ hasMany → ComposicaoCestaProdutos
└─ hasMany → ComposicaoCestaOpcoes

PedidoConsumidores
├─ belongsTo → Ciclo
├─ belongsTo → Usuario
└─ hasMany → PedidoConsumidoresProdutos

APIs

GraphQL API

Endpoint: /graphql

Schema:

type ActiveSession {
usuarioId: ID!
token: String!
perfis: [String!]!
}

type HealthCheck {
status: String!
}

input LoginInput {
email: String!
senha: String!
}

type Mutation {
sessionLogin(input: LoginInput!): ActiveSession!
sessionLogout: LogoutResponse!
}

type Query {
healthcheck: HealthCheck!
systemInformation: SystemInformation!
}

Autenticação:

Authorization: Bearer <token>

REST API

Rotas Principais:

GET/POST  /ciclo
GET/POST /cesta
GET/POST /produto
GET/POST /oferta
GET/POST /composicao
GET/POST /pedidoconsumidores
GET/POST /usuario
GET /relatorios/*

Autenticação

OAuth 2.0 / OpenID Connect

Fluxo:

1. Usuário acessa aplicação
2. Middleware verifica req.oidc.user
3. Se não autenticado → Redirect para OAuth provider
4. Login bem-sucedido → Callback com token
5. Sistema cria/atualiza usuário no banco
6. Sessão estabelecida

Configuração:

auth({
authRequired: false,
auth0Logout: true,
issuerBaseURL: process.env.issuerBaseURL,
baseURL: process.env.BASE_URL_APP,
clientID: process.env.clientID,
secret: process.env.secret
})

GraphQL Session Management

Autenticação por Token:

// 1. Login
const { sessionLogin } = useMutation();
const result = await sessionLogin({ email, senha });
// result.token armazenado no localStorage

// 2. Requisições autenticadas
Authorization: Bearer <token>

Perfis de Usuário:

  • master - Super administrador
  • admin - Administrador
  • fornecedor - Fornecedor/Produtor
  • consumidor - Consumidor
  • info - Apenas visualização

Testing

BDD com Cucumber

Feature Files: /app/features/*.feature

# features/sessions.feature
Feature: Gerenciamento de Sessões

Scenario: Login com sucesso
Given um usuário cadastrado com email "user@example.com"
When eu faço login com email "user@example.com" e senha "123456"
Then o login deve ser bem-sucedido
And eu devo receber um token de sessão

Step Definitions: /app/features/step_definitions/

Testes Unitários

Jest: Configurado para testes de serviços e models

Mocha: E2E tests

Deployment

Docker Compose

Services:

services:
app.dev:
build: ./app
ports:
- "13000:5000"
environment:
DATABASE_URL: postgresql://...
depends_on:
- db.dev

db.dev:
image: postgres:13
environment:
POSTGRES_DB: divinoalimento
volumes:
- pgdata:/var/lib/postgresql/data

frontend:
build: ./frontend
ports:
- "8080:8080"

mock-oauth2-server:
# Para desenvolvimento
ports:
- "8080:8080"

Rake Tasks

# Construir ambiente
rake vivo:constroi

# Iniciar serviços
rake vivo:liga

# Parar serviços
rake vivo:para

# Popular banco de dados
rake vivo:popular

# Rodar migrations
rake vivo:migracao

# Acessar shell do app
rake vivo:sh

# Acessar PostgreSQL
rake vivo:psql

# Ver logs
rake vivo:mensagens

Fluxos de Requisição

Fluxo REST (Legado)

1. Browser → POST /ciclo (form)
2. Express Routes → CicloController.save()
3. Controller → Ciclo.create(data)
4. Model → cicloSql.insert()
5. Sequelize → PostgreSQL INSERT
6. Response → Redirect /ciclo-index
7. Controller → Ciclo.getAll()
8. Model → cicloSql.getAll()
9. Sequelize → PostgreSQL SELECT
10. Controller → res.render('ciclo-index.ejs', { ciclos })
11. EJS → HTML
12. Browser ← HTML response

Fluxo GraphQL (Moderno)

1. React App → useLoginUsuario mutation
2. graphql-request → POST /graphql
3. GraphQL Middleware → Extract token from Authorization header
4. Resolver → sessionLogin(email, senha)
5. UsuarioService.login() → Validate credentials
6. Session.create() → Sequelize INSERT
7. GraphQL Response → { token, usuarioId, perfis }
8. React Query → Cache response
9. React Component → Update state
10. Navigate → /dashboard

Performance e Escalabilidade

Otimizações

  1. Backend:

    • SQL queries otimizadas com JOINs
    • Sequelize connection pooling
    • EJS template caching (produção)
  2. Frontend:

    • Vite hot module replacement
    • Code splitting com React Router
    • React Query caching
    • Lazy loading de componentes
  3. Database:

    • Índices em migrations
    • Connection pooling
    • Read replicas (futuro)

Escalabilidade

  • Horizontal: Múltiplas instâncias app.dev
  • Load Balancer: Nginx/HAProxy
  • CDN: Assets estáticos
  • Cache: Redis para sessões (futuro)

Próximos Passos


Dúvidas? Consulte o Troubleshooting ou Como Contribuir.