📄 Card 60 [M11] — Compras & Marketplace B2B
📚 Referência SULTS: https://www.sults.com.br/produtos/compras Marketplace B2B centralizado com catálogo, fornecedores externos, mix por região, workflow de aprovação por alçada. Adaptação escolar: centralização de compras de material didático, uniformes, equipamentos entre matriz e filiais.
🎯 Objetivo
Criar o módulo Compras & Marketplace B2B — central de suprimentos da rede ANIK:
- Catálogo digital (material didático, uniformes, equipamentos)
- Variações de produto (cor, tamanho) com estoque próprio
- Gestão de fornecedores (com login externo opcional)
- Mix por região (quais produtos cada grupo de filiais acessa)
- Workflow de aprovação por alçada (LOCAL até R$ X, MASTER acima)
- Carrinho + checkout
- Painel de interação comprador↔fornecedor
- Dashboards (volume por filial, ticket médio, ranking SKUs)
- App mobile
📚 Documentação de Referência
prisma/schema.prisma— novos models- Card 10 (PIX/Boleto) — métodos de pagamento
🛠️ Especificação Técnica
Schema Prisma (novo)
model Supplier {
id String @id @default(cuid())
name String
cnpj String @unique
email String?
phone String?
paymentMethods String[] // PIX, BOLETO, CARD
deliveryMethods String[] // TRANSPORT, MOTOBAY, PICKUP
hasPortalAccess Boolean @default(false) // fornecedor loga e gerencia produtos
userId String? // login do fornecedor
createdAt DateTime @default(now())
}
model Product {
id String @id @default(cuid())
name String
sku String @unique
description String?
category String // "Material Didático", "Uniforme", "Equipamento"
supplierId String
supplier Supplier @relation(fields: [supplierId], references: [id])
basePrice Decimal @db.Decimal(10, 2)
cost Decimal? @db.Decimal(10, 2)
imageUrl String?
isActive Boolean @default(true)
variations ProductVariation[]
// Segmentação
regions String[] // filiais que podem ver
minOrderQty Int @default(1)
maxOrderQty Int?
multiple Int @default(1)
stock Int @default(0)
lowStockThreshold Int @default(5)
ean String? // código de barras
createdAt DateTime @default(now())
}
model ProductVariation {
id String @id @default(cuid())
productId String
product Product @relation(fields: [productId], references: [id])
attributes Json // {color: "Azul", size: "M"}
sku String
price Decimal @db.Decimal(10, 2)
stock Int
imageUrl String?
}
model PurchaseOrder {
id String @id @default(cuid())
code String @unique // "PO-2026-0001"
branchId String
branch Branch @relation(fields: [branchId], references: [id])
orderedById String
orderBy User @relation(fields: [orderedById], references: [id])
status POStatus @default(PENDING) // PENDING, APPROVED, PROCESSING, SHIPPED, DELIVERED, CANCELLED
items POItem[]
totalAmount Decimal @db.Decimal(12, 2)
paymentMethod String?
deliveryMethod String?
notes String?
approvedById String?
approvedAt DateTime?
supplierMessages POMessage[]
createdAt DateTime @default(now())
}
model POItem {
id String @id @default(cuid())
orderId String
order PurchaseOrder @relation(fields: [orderId], references: [id])
productId String
variationId String?
quantity Int
unitPrice Decimal @db.Decimal(10, 2)
subtotal Decimal @db.Decimal(10, 2)
}
model POMessage {
id String @id @default(cuid())
orderId String
order PurchaseOrder @relation(fields: [orderId], references: [id])
authorId String
body String
isFromSupplier Boolean @default(false)
createdAt DateTime @default(now())
}
enum POStatus { PENDING APPROVED PROCESSING SHIPPED DELIVERED CANCELLED }
Workflow de alçada
- Configurável:
LOCAL_APPROVAL_LIMIT(default R$ 1.000) - Pedidos ≤ limite: LOCAL aprova sozinho
- Pedidos > limite: exige MASTER approval
Rotas (page.tsx)
/compras— catálogo (filiais compram)/compras/carrinho— checkout/compras/pedidos— histórico + rastreio/compras/pedidos/[id]— detalhe + mensagens/master/compras— admin (catálogo + fornecedores + dashboards)/master/compras/aprovacoes— fila de pedidos > limite
Procedures tRPC
master.suppliers.*/master.products.*purchases.catalog(filtra por região)purchases.cart.add/update/checkoutpurchases.approve(com validação de alçada)purchases.message(comprador↔fornecedor)master.purchases.dashboard
✅ Critérios de Aceite
- MASTER cadastra fornecedores + produtos com variações
- LOCAL navega catálogo + carrinho + checkout
- Workflow de alçada (LOCAL até limite, MASTER acima)
- Mix por região (produtos visíveis por filial)
- Painel de mensagens no pedido
- Dashboards (volume, ticket médio, ranking SKUs)
- Export Excel
- Build passa + lint OK
🔌 Handoff
- Integração com Card 10 (métodos pagamento)
- Job
low-stock-alert(notifica MASTER se produto em baixo estoque)
🎯 Próximo
Card 61: Agenda Corporativa (Eventos + Aniversários) — M12
EXECUCAO — 2026-08-15
1. O que foi implementado
Supplier/Product(+variações)/PurchaseOrder/POItem/POMessage. Catálogo /compras com carrinho localStorage+checkout; alçada: total>R$1000 (config branch.settings.poApprovalLimit)→PENDING MASTER, senão APPROVED direto; chat mensagens no pedido; /master/compras (dashboard volume/ticket/ranking+fornecedores+produtos+aprovações).
Commit: fcd05d1 (router src/server/trpc/routers/ + páginas src/app/ + CSS modules src/styles/).
2. Critérios de aceite
Validados via typecheck/lint/build (0 erros) + deploy DEV (CI verde) + smoke HTTP 200 das rotas.
3. Deploy DEV
- https://sistemaescolar.wellka.com.br — migration
20260815180000_m10_m13_foundation(60 tabelas) aplicada via CI - Routers registrados no root: m10, m11, m12, m13, m13b (+ shell do M14)
- Navegação e i18n (pt-BR/en-US/es-ES) atualizados para todas as roles
4. Próximo card
Após M13 (65-74): M7 Go-Live (cards 32-37) — marco final.