Visão Geral da Arquitetura
Ouroboros opera dentro de um ambiente controlado: a rede local de um evento escolar. Esse contexto define tudo. Diferente de uma aplicação web convencional, não há usuários anônimos, não há escalabilidade horizontal necessária, e o maior risco não é carga — é indisponibilidade de rede.
A arquitetura reflete isso.
Componentes
Servidor Principal
O núcleo do sistema. Roda em qualquer máquina na rede local (um notebook comum é suficiente).
Responsabilidades:
- Processar e persistir todas as transações
- Manter o event store imutável (SQLite)
- Transmitir eventos em tempo real via WebSocket
Stack: Node.js + Express ou Python + FastAPI + Uvicorn, ambos com SQLite (WAL mode)
Terminais de Loja
Qualquer dispositivo com browser conectado à rede local. Sem instalação.
Responsabilidades:
- Inserir o ID da comanda do cliente
- Enviar requisição de débito ao servidor
- Receber confirmação em tempo real
- Exibir histórico de transações da sessão
Stack: React + Vite (interface de demonstração — pode ser substituída)
Interface de Administração e Operação (Admin & Banco)
Interface de gestão para organizadores e sub-administradores (operadores do Banco).
Responsabilidades:
- Admin Principal: Controle total, gestão de lojas (criar/revogar tokens), visão macro da economia.
- Banco (Operador): Cadastro de novos produtos/categorias, emissão de comandas com saldo inicial (carrinho de avaliação).
- Visualização do estado geral da economia.
Sobre o frontend
O frontend React incluído é uma interface de demonstração básica. Implementa todos os fluxos (login, emissão de comandas, carrinho de vendas, débito, gestão de lojas) mas foi construído com foco em funcionalidade, não em design final. A interface pode ser livremente redesenhada, customizada ou substituída por qualquer tecnologia frontend — o backend (API REST + WebSocket) é a camada estável.
Firebase Firestore (ideia não implementada)
Não implementado
A integração com Firebase descrita abaixo é uma ideia futura, não uma funcionalidade implementada. O campo synced_to_firebase existe no banco de dados como preparação, mas nenhum código de sync foi escrito. O sistema funciona 100% sem Firebase.
Espelho eventual dos eventos confirmados. Nunca é consultado pelo servidor principal durante operações críticas.
Responsabilidades planejadas:
- Receber eventos síncronizados do servidor (quando há internet)
- Servir consultas de saldo para o cliente final (celular)
- Não bloquear nunca — falha silenciosa, retry automático
Diagrama de componentes
graph TB
subgraph rede_local["Rede Local do Evento"]
terminal_a["Terminal Loja A\n(browser)"]
terminal_b["Terminal Loja B\n(browser)"]
admin["Painel Admin\n(browser)"]
servidor["Servidor Principal\nNode.js+Express ou FastAPI+Uvicorn"]
sqlite[("SQLite\nEvent Store")]
static["Assets Estáticos\n(React build)"]
terminal_a -- "WS (Lojas)" --> servidor
terminal_b -- "WS (Lojas)" --> servidor
admin -- "WS (Operacional)" --> servidor
admin -- "REST (Relatórios)" --> servidor
servidor -- "leitura/escrita" --> sqlite
servidor -- "serve" --> static
end
subgraph nuvem["Nuvem (opcional)"]
firebase[("Firebase\nFirestore")]
end
subgraph cliente["Cliente (celular)"]
app["Browser\n(consulta saldo)"]
end
servidor -- "sync assíncrono\nbest-effort\n(não implementado)" --> firebase
firebase -- "leitura" --> app
Gestão de Produtos e Categorias
O Banco é responsável pela entrada de novos produtos no ecossistema da feira. A arquitetura de produtos segue uma estratégia de Categorização Generalizada por motivos de eficiência operacional (evitar o "trabalho braçal" de etiquetar milhares de itens individualmente):
- Abstração de Itens: Em vez de IDs únicos por objeto físico, o sistema trabalha com categorias (ex: CAMISETA, JAQUETA, BOLSA, BRINQUEDO).
- Finalidade Organizacional: A categorização serve para fins estatísticos e de auditoria, sem travar a venda em caso de erro humano (ex: vender uma Jaqueta como Camiseta).
- Controle de Estoque: O sistema rastreia o volume total de entradas e saídas por categoria de forma cumulativa (incremento), permitindo comparar o que entrou no Banco vs o que circulou nas lojas.
- Preços Fixos: Cada categoria possui uma tabela de preços definida centralmente no Banco.
Modelo de dados
O sistema opera com sete tabelas (quatro core + três para distribuição/packing) e duas views derivadas:
Comanda
id: UUID (interno)
code: string (ID curto: "F001" a "F999")
holder_name: string
created_at: timestamp
Event
id: UUID
type: ENUM (credit | debit)
comanda_id: UUID → Comanda
amount: integer (ETC inteiros)
store_id: UUID → Store
note: string (ex: "Saldo inicial", "Crédito adicional" — usado para auditoria e filtragem de relatórios)
timestamp: timestamp
synced_to_firebase: integer (reservado para uso futuro — não implementado)
Store
id: UUID
name: string
theme: string
terminal_token: string
Category (Tabela de Preços e Categorização)
id: UUID
name: string (ex: "Jaqueta")
price: integer (ETC inteiros)
total_entries: integer (contador acumulativo de entrada no banco)
total_exits: integer (contador acumulativo de vendas confirmadas)
Balance (view derivada — nunca armazenada diretamente)
comanda_id: UUID
balance: SUM(credits) - SUM(debits)
store_box_count (view derivada — totalizador de caixas por loja)
store_id: UUID
store_name: string
boxes_total: integer
boxes_done: integer
Distribution (rodada de distribuição de caixas)
id: UUID
name: string
num_boxes: integer
status: ENUM (planning | active | complete)
needs_recalc: boolean
created_at: timestamp
completed_at: timestamp
Box (caixa física de produtos)
id: UUID
distribution_id: UUID → Distribution
box_number: integer
assigned_store_id: UUID → Store
responsible_name: string (voluntário responsável)
status: ENUM (pending | in_progress | done)
claimed_at: timestamp
completed_at: timestamp
BoxItem (itens planejados para uma caixa)
id: UUID
box_id: UUID → Box
category_id: UUID → Category
target_quantity: integer
Por que saldo é uma view?
O saldo nunca é um campo armazenado. Ele é sempre computado ao agregar o event store. Isso garante que não existe estado inconsistente: se o event log está correto, o saldo está correto. Ver ADR-003 para a justificativa completa.
Fluxo de uma transação (visão geral)
- Cliente informa o número/ID da comanda no terminal da loja
- Operador digita o ID e envia
debit_requestvia WebSocket - Servidor valida saldo disponível consultando o event store
- Se válido: persiste o evento no SQLite, retorna
debit_confirmed - Todos os outros terminais conectados recebem broadcast do evento
Tempo de resposta esperado: < 50ms na rede local (SQLite + loopback)
Decisões de design
As principais escolhas arquiteturais estão documentadas como ADRs (Architecture Decision Records). O projeto foca em suportar o fluxo de usuários (Lojas, Clientes) e os níveis de permissão administrativa (Admin e Banco/Sub-admin).
| Decisão | Documento |
|---|---|
| Por que a operação principal é local, não cloud | ADR-001: Local-First |
| Por que SQLite em vez de PostgreSQL ou outro banco | ADR-002: SQLite |
| Por que Event Sourcing em vez de CRUD convencional | ADR-003: Event Sourcing |