Projeto Completo — Sistema de Estoque
Vamos construir um sistema de estoque completo do zero, aplicando tudo que aprendemos.
O que vamos construir
Um sistema que permite:
- Cadastrar produtos com categorias e fornecedores
- Registrar entradas e saídas de estoque
- Alertas automáticos quando o estoque fica baixo
- Relatório de movimentações
- Autenticação com permissões por perfil
Passo 1 — Modelagem das entidades
jd
// ─────────────────────────────────
// Enumerações
// ─────────────────────────────────
enum PerfilUsuario
ADMINISTRADOR
GERENTE
OPERADOR
fim
enum TipoMovimento
ENTRADA
SAIDA
AJUSTE
DEVOLUCAO
fim
// ─────────────────────────────────
// Entidades
// ─────────────────────────────────
entidade Usuario
id: id
nome: texto
email: texto
senhaHash: texto
perfil: PerfilUsuario
ativo: booleano
criadoEm: data
fim
entidade Categoria
id: id
nome: texto
descricao: texto
fim
entidade Fornecedor
id: id
nome: texto
cnpj: texto
email: texto
telefone: texto
cidade: texto
ativo: booleano
fim
entidade Produto
id: id
nome: texto
descricao: texto
sku: texto
preco: decimal
precoCusto: decimal
estoque: numero
estoqueMinimo: numero
estoqueMaximo: numero
categoriaId: id
fornecedorId: id
ativo: booleano
criadoEm: data
atualizadoEm: data
fim
entidade MovimentoEstoque
id: id
produtoId: id
tipo: TipoMovimento
quantidade: numero
estoqueAntes: numero
estoqueDepois: numero
observacao: texto
usuarioId: id
realizadoEm: data
fimPasso 2 — Eventos
jd
evento ProdutoCadastrado
produtoId: id
nome: texto
sku: texto
fim
evento EstoqueBaixo
produtoId: id
nomeProduto: texto
estoqueAtual: numero
estoqueMinimo: numero
fim
evento EstoqueZerado
produtoId: id
nomeProduto: texto
fim
evento MovimentoRegistrado
movimentoId: id
produtoId: id
tipo: TipoMovimento
quantidade: numero
fimPasso 3 — Serviços
jd
// ─────────────────────────────────
// Usuários
// ─────────────────────────────────
servico UsuarioService
funcao criar(nome: texto, email: texto, senha: texto, perfil: PerfilUsuario) -> Usuario
erros = ValidacaoService.validarUsuario(nome, email, senha)
se erros.tamanho() > 0
erro erros.obter(0)
fim
u = Usuario()
u.nome = nome
u.email = email
u.senhaHash = Crypto.hash(senha, "sha256")
u.perfil = perfil
u.ativo = verdadeiro
u.criadoEm = DateTime.today()
salvar u
retornar u
fim
funcao autenticar(email: texto, senha: texto) -> booleano
usuarios = EntityManager.buscar(Usuario)
se usuarios.tamanho() == 0
retornar falso
fim
usuario = usuarios.obter(0)
hashDigitado = Crypto.hash(senha, "sha256")
retornar hashDigitado == usuario.senhaHash
fim
fim
// ─────────────────────────────────
// Produtos
// ─────────────────────────────────
servico ProdutoService
funcao cadastrar(
nome: texto,
sku: texto,
preco: decimal,
precoCusto: decimal,
estoqueMinimo: numero,
categoriaId: id,
fornecedorId: id
) -> Produto
se nao PermissionService.hasPermission("produtos.criar")
erro "Sem permissão para cadastrar produtos"
fim
erros = ValidacaoService.validarProduto(nome, preco, precoCusto)
se erros.tamanho() > 0
erro erros.obter(0)
fim
p = Produto()
p.nome = nome
p.sku = sku
p.preco = preco
p.precoCusto = precoCusto
p.estoque = 0
p.estoqueMinimo = estoqueMinimo
p.estoqueMaximo = estoqueMinimo * 10
p.categoriaId = categoriaId
p.fornecedorId = fornecedorId
p.ativo = verdadeiro
p.criadoEm = DateTime.today()
p.atualizadoEm = DateTime.today()
salvar p
emitir ProdutoCadastrado(p.id, p.nome, p.sku)
retornar p
fim
funcao buscar(produtoId: id) -> Produto
p = EntityManager.buscarPorId(Produto, produtoId)
se nao p
erro "Produto não encontrado"
fim
retornar p
fim
funcao listar() -> lista<Produto>
retornar EntityManager.buscar(Produto)
fim
funcao buscarAbaixoMinimo() -> lista<Produto>
todos = listar()
variavel criticos: lista<Produto> = lista()
para produto em todos
se produto.estoque <= produto.estoqueMinimo
criticos.adicionar(produto)
fim
fim
retornar criticos
fim
fim
// ─────────────────────────────────
// Estoque
// ─────────────────────────────────
servico EstoqueService
funcao entrada(produtoId: id, quantidade: numero, observacao: texto)
se quantidade <= 0
erro "Quantidade deve ser positiva"
fim
produto = ProdutoService.buscar(produtoId)
usuario = AuthService.getCurrentUser()
estoqueAntes = produto.estoque
produto.estoque = produto.estoque + quantidade
produto.atualizadoEm = DateTime.today()
salvar produto
mov = MovimentoEstoque()
mov.produtoId = produtoId
mov.tipo = TipoMovimento.ENTRADA
mov.quantidade = quantidade
mov.estoqueAntes = estoqueAntes
mov.estoqueDepois = produto.estoque
mov.observacao = observacao
mov.usuarioId = usuario.id
mov.realizadoEm = DateTime.today()
salvar mov
emitir MovimentoRegistrado(mov.id, produtoId, TipoMovimento.ENTRADA, quantidade)
verificarNivelEstoque(produto)
fim
funcao saida(produtoId: id, quantidade: numero, observacao: texto) -> booleano
se quantidade <= 0
erro "Quantidade deve ser positiva"
fim
produto = ProdutoService.buscar(produtoId)
se produto.estoque < quantidade
Console.avisar("Estoque insuficiente para saída de " + quantidade + " unidades")
retornar falso
fim
usuario = AuthService.getCurrentUser()
estoqueAntes = produto.estoque
produto.estoque = produto.estoque - quantidade
produto.atualizadoEm = DateTime.today()
salvar produto
mov = MovimentoEstoque()
mov.produtoId = produtoId
mov.tipo = TipoMovimento.SAIDA
mov.quantidade = quantidade
mov.estoqueAntes = estoqueAntes
mov.estoqueDepois = produto.estoque
mov.observacao = observacao
mov.usuarioId = usuario.id
mov.realizadoEm = DateTime.today()
salvar mov
emitir MovimentoRegistrado(mov.id, produtoId, TipoMovimento.SAIDA, quantidade)
verificarNivelEstoque(produto)
retornar verdadeiro
fim
funcao historico(produtoId: id) -> lista<MovimentoEstoque>
retornar EntityManager.buscar(MovimentoEstoque)
fim
funcao verificarNivelEstoque(produto: Produto)
se produto.estoque == 0
emitir EstoqueZerado(produto.id, produto.nome)
retornar
fim
se produto.estoque <= produto.estoqueMinimo
emitir EstoqueBaixo(produto.id, produto.nome, produto.estoque, produto.estoqueMinimo)
fim
fim
fim
// ─────────────────────────────────
// Relatório
// ─────────────────────────────────
servico RelatorioService
funcao resumoEstoque() -> texto
produtos = ProdutoService.listar()
total = EntityManager.contar(Produto)
criticos = ProdutoService.buscarAbaixoMinimo()
resumo = "=== RESUMO DO ESTOQUE ===\n"
resumo = resumo + "Total de produtos ativos: " + total + "\n"
resumo = resumo + "Produtos com estoque crítico: " + criticos.tamanho() + "\n\n"
se criticos.tamanho() > 0
resumo = resumo + "ATENÇÃO — Repor urgente:\n"
para produto em criticos
resumo = resumo + " • " + produto.nome
+ " (estoque: " + produto.estoque
+ " / mínimo: " + produto.estoqueMinimo + ")\n"
fim
fim
retornar resumo
fim
funcao movimentacoesHoje() -> lista<MovimentoEstoque>
retornar EntityManager.buscar(MovimentoEstoque)
fim
fim
// ─────────────────────────────────
// Alertas
// ─────────────────────────────────
servico AlertaService
escutar EstoqueBaixo
msg = "⚠️ Estoque baixo: " + nomeProduto
+ " (" + estoqueAtual + "/" + estoqueMinimo + ")"
Console.avisar(msg)
// Aqui você pode chamar HttpClient para enviar para Slack, email, etc.
fim
escutar EstoqueZerado
Console.escrever("ALERTA: ESTOQUE ZERADO: " + nomeProduto)
fim
escutar ProdutoCadastrado
Console.escrever("✓ Produto cadastrado: " + nome + " (SKU: " + sku + ")")
fim
fim
// ─────────────────────────────────
// Validações
// ─────────────────────────────────
servico ValidacaoService
funcao validarProduto(nome: texto, preco: decimal, precoCusto: decimal) -> lista<texto>
variavel erros: lista<texto> = lista()
se nome.aparar().tamanho() < 2
erros.adicionar("Nome do produto muito curto")
fim
se preco <= 0
erros.adicionar("Preço de venda deve ser positivo")
fim
se precoCusto < 0
erros.adicionar("Preço de custo não pode ser negativo")
fim
se precoCusto > preco
erros.adicionar("Preço de custo não pode ser maior que o preço de venda")
fim
retornar erros
fim
funcao validarUsuario(nome: texto, email: texto, senha: texto) -> lista<texto>
variavel erros: lista<texto> = lista()
se nome.tamanho() < 2
erros.adicionar("Nome muito curto")
fim
se nao email.contem("@")
erros.adicionar("Email inválido")
fim
se senha.tamanho() < 6
erros.adicionar("Senha deve ter ao menos 6 caracteres")
fim
retornar erros
fim
fimPasso 4 — Regras de negócio
jd
regra reposicaoAutomatica quando produto.estoque < produto.estoqueMinimo e produto.ativo == verdadeiro entao
emitir EstoqueBaixo(
produto.id,
produto.nome,
produto.estoque,
produto.estoqueMinimo
)
fim
regra bloqueioVendaEstoqueZero quando produto.estoque == 0 entao
produto.disponivelParaVenda = falso
salvar produto
emitir EstoqueZerado(produto.id, produto.nome)
fimPasso 5 — Banco de dados e interface (novo)
Conexão com banco de dados
Com o bloco banco, o compilador gera automaticamente um servidor de sincronização pronto para rodar:
jd
banco
tipo: postgres
url: env("DATABASE_URL") // ex: postgres://user:pass@localhost:5432/estoque
jwt: env("JWT_SECRET") // segredo para assinar tokens JWT
porta: 3001
// RLS: cada usuário vê apenas seus próprios movimentos
politica MovimentoEstoque
dono: usuarioId
fim
fimCompile e execute:
bash
jadec estoque.jd # gera estoque.jade-ui.json + jade-server.js
JWT_SECRET=s3cr3t DATABASE_URL=postgres://... node jade-server.jsO servidor gerado expõe POST /api/sync com autenticação JWT e resolução de conflitos via _rev.
Tela de login
jd
funcao fazerLogin(dados: objeto)
credenciais = dados.credenciais
chave = dados.chave
resultado = AuthService.login(credenciais.usuario, credenciais.senha)
sessao.definir(resultado.accessToken, resultado.refreshToken, resultado.expiresIn)
ui.emitirResultadoAcao(chave)
router.navegar("/inicio")
fim
tela TelaLogin "Sistema de Estoque"
login FormLogin
enviar: fazerLogin
titulo: "Gestão de Estoque"
fim
fimInterface completa — dashboard
jd
funcao abrirFormProduto()
router.navegar("/produtos/novo")
fim
funcao fazerLogout()
sessao.limpar()
router.navegar("/login")
fim
tela TelaDashboard "Dashboard"
cartao TotalProdutos
titulo: "Produtos Ativos"
variante: destaque
fim
cartao ProdutosCriticos
titulo: "Estoque Crítico"
variante: alerta
fim
grafico GraficoMovimentos
tipo: barras
entidade: MovimentoEstoque
eixoX: realizadoEm
eixoY: quantidade
fim
fim
tela TelaProdutos "Catálogo de Produtos"
tabela ListaProdutos
entidade: Produto
colunas: nome, sku, preco, estoque, ativo
filtravel: verdadeiro
ordenavel: verdadeiro
paginacao: verdadeiro
fim
botao NovoProduto
acao: abrirFormProduto
icone: mais
tipo: primario
fim
fimCompilar e rodar
bash
jadec estoque.jd
jade servir dist/O compilador gera tudo automaticamente — servidor, interface e inicialização do browser. Abra o endereço exibido no terminal.
Passo 6 — Usando o sistema
jd
funcao demonstracao()
Console.escrever("=== Sistema de Estoque Jade DSL ===\n")
// 1. Criar categorias
eletronicos = Categoria()
eletronicos.nome = "Eletrônicos"
eletronicos.descricao = "Equipamentos e dispositivos eletrônicos"
salvar eletronicos
// 2. Criar fornecedor
fornecedor = Fornecedor()
fornecedor.nome = "Tech Distribuidora Ltda"
fornecedor.cnpj = "12.345.678/0001-90"
fornecedor.email = "compras@techdist.com.br"
fornecedor.ativo = verdadeiro
salvar fornecedor
// 3. Cadastrar produtos
notebook = ProdutoService.cadastrar(
"Notebook Dell Inspiron",
"NB-DELL-001",
4500.00,
3200.00,
5,
eletronicos.id,
fornecedor.id
)
mouse = ProdutoService.cadastrar(
"Mouse Sem Fio Logitech",
"MS-LOGI-003",
129.90,
65.00,
10,
eletronicos.id,
fornecedor.id
)
// 4. Registrar entradas
EstoqueService.entrada(notebook.id, 20, "Compra inicial — NF 001")
EstoqueService.entrada(mouse.id, 50, "Compra inicial — NF 001")
// 5. Simular saídas
EstoqueService.saida(notebook.id, 17, "Venda para cliente XYZ")
EstoqueService.saida(mouse.id, 43, "Venda lote")
// 6. Ver relatório
Console.escrever(RelatorioService.resumoEstoque())
movs = RelatorioService.movimentacoesHoje()
Console.escrever("Movimentações hoje: " + movs.tamanho())
fimSaída esperada:
✓ Produto cadastrado: Notebook Dell Inspiron (SKU: NB-DELL-001)
✓ Produto cadastrado: Mouse Sem Fio Logitech (SKU: MS-LOGI-003)
⚠️ Estoque baixo: Notebook Dell Inspiron (3/5)
⚠️ Estoque baixo: Mouse Sem Fio Logitech (7/10)
=== RESUMO DO ESTOQUE ===
Total de produtos ativos: 2
Produtos com estoque crítico: 2
ATENÇÃO — Repor urgente:
• Notebook Dell Inspiron (estoque: 3 / mínimo: 5)
• Mouse Sem Fio Logitech (estoque: 7 / mínimo: 10)
Movimentações hoje: 4Parabéns!
Você acabou de construir um sistema de estoque completo em Jade DSL com:
- ✅ 5 entidades bem modeladas
- ✅ 4 enumerações
- ✅ 5 eventos
- ✅ 7 serviços com CRUD completo
- ✅ Validações centralizadas
- ✅ Alertas automáticos por eventos
- ✅ Regras de negócio declarativas
- ✅ Autenticação e permissões
- ✅ Relatórios e histórico
- ✅ Conexão com banco real via
bancoDSL - ✅ Interface declarativa com login, dashboard, tabela e gráfico
- ✅ Sincronização offline-first via SyncManager + JWT
A partir daqui, você tem o conhecimento para construir qualquer sistema empresarial em Jade DSL.