Rancho

Plataforma marketplace B2B2C para varejo alimentar—sistema com 3 camadas (app mobile + backend + dashboard admin) orquestrando gestão multi-inquilino de vendedores, inventário em tempo real com event sourcing, pagamentos em dual-moeda e sincronização de catálogo entre 30+ mercados.

2025Produção & EscalandoFundador & Arquiteto Full-StackMarketplaceE-commerceLogísticaFintechSaaS
██+ vendedores, ██+ clientes
███+ pedidos/transações
<200ms (API), <1s (sincronização de catálogo)
50+ modelos BD, 3 apps sincronizados
⚠️

Este projeto contém informações operacionais e comerciais protegidas por acordo de confidencialidade. Números específicos de usuários ativos, volume de transações e métricas de tráfego foram omitidos por NDA.

Impact

  • Sistema de inventário com event sourcing: Eventos RESERVE/RELEASE/STOCK_IN/ADJUSTMENT/RETURN com trilha de auditoria imutável. Cache StockSnapshot + ledger StockEvent previne race conditions; transações atômicas garantem nunca vender além do estoque mesmo com pedidos concorrentes. Query-friendly via snapshots indexados, pronto para auditoria via log de eventos.
  • Kafka para ingestão de catálogo event-driven: Uploader CSV/XLSX de produtos → tópico Kafka → Consumer valida/correlaciona → Elasticsearch indexa full-text → snapshots PostgreSQL. DLQ captura falhas; batch job tracking (IDs correlação) permite visibilidade de progresso no dashboard admin. Suporta 1000+ SKU por upload em massa por mercado.
  • Hierarquia multi-nível de usuários/vendedores: 3 tipos de usuário (CUSTOMER, SELLER, EMPLOYEE) com RBAC (ADMIN/MANAGER/OPERATOR/VIEWER). Vendedores podem possuir múltiplos mercados; funcionários atribuídos a vendedores específicos com escopo departamental. Relação SellerUser N:N habilita cenários multi-fundador. Estrutura com árvore de mercados suporta franquias (matriz ↔ filiais).
  • Orquestração de pagamentos em dual-moeda: API Stripe para subscrições em USD + SDK AbacatePay para PIX instantâneo em BRL + processamento de cartão. Criação de intent de pagamento → fluxos específicos do provedor → reconciliação webhook → atualização status pedido. Lida com ciclos de settlement diferentes e conversões de moeda transparentemente.
  • Ecossistema com 3 aplicações: Mobile React Native/Expo (iOS/Android/Web simultâneos), API NestJS (30+ módulos, docs OpenAPI automáticos), dashboard admin Next.js 15 (analytics real-time, operações em massa). Cliente API TypeScript auto-gerado do Swagger garante segurança de tipo frontend/backend. Todos apps compartilham schema Prisma.
  • Observabilidade admin em tempo real: Rastreamento de progresso de batch jobs (IDs correlação), alertas de reconciliação de pagamentos, detecção de discrepâncias de inventário, logs de auditoria de atividade de funcionários. Dashboard mostra SLA de importação de catálogo, taxas de sucesso de pagamento, KPIs de desempenho de mercado. Sem polling—webhooks + database subscriptions.
  • Padrões enterprise em escala: Filtros de exceção global (erros Prisma customizados), interceptadores request/response (transform + timeout), validação baseada em classes (whitelist + forbid non-whitelisted), JWT + guards baseados em role. SSL/TLS em desenvolvimento, gestão de segredos production-grade.
  • Logística & acoplamento de fulfillment: Áreas de entrega mapeadas por mercado (geo-fencing), métodos de envio com preço dinâmico, fluxo pedido → reserva de inventário → shipment. Suporta múltiplos locais de estoque por mercado (warehouse principal + satélite). Módulo de cálculo de frete integra custos fulfillment no total do pedido.

Key Performance Indicators

Modelos de BD
50+
Relações Prisma
N:N complexas, estruturas de árvore
Módulos API
30+
Aplicações
3 (Mobile, Backend, Admin)
Tipos de Evento de Estoque
6 (RESERVE, RELEASE, STOCK_IN, STOCK_OUT, RETURN, ADJUSTMENT)
SLA de Processamento de Pedido
<200ms
Importação Bulk de Catálogo
1000+ SKU/batch
Processadores de Pagamento
2 (Stripe & AbacatePay)
Mercados Ativos
████████
Vendedores Onboarded
████████
Transações Diárias
████████

Traction & Growth

Active Users
██+ vendedores, ██+ clientes
Paying Customers
████████+ mensais ativos
Monthly Price
████████ (tiered para vendedores)
MRR
████████
Acquisition Channel: Vendas diretas, word-of-mouth, Meta Ads (targeting pequenos groceristas)
Product-led growth: tier grátis para primeiro mercado, upgrades para pago ao adicionar 2+ locais. Taxa de sucesso de pagamento >99%. LTV do cliente estimado R$4000-R$6000 em 12 meses. Churn principalmente devido a negócios sazonais (férias-related). NPS: 58 (crescendo para 70).

Architecture

rancho-system-integration

Key Decisions

  • Event sourcing de inventário (StockEvent + StockSnapshot) em vez de campo simples de quantidade: Complexidade adicional (modelos duais) mas garante conformidade de auditoria, habilita queries temporais, previne atualizações perdidas sob alta concorrência. Snapshots fazem leituras O(1), eventos facilitam reconciliação. Escolhido para escalar para 50mil+/dia pedidos.
  • Consumer Kafka para importação de catálogo em vez de processamento síncrono: Trade-off de latência (consistência eventual para produtos) mas habilita uploads em massa de 1000+ SKU sem bloquear. DLQ + retry habilitam recuperação. Monitoramento via batch job tracker dá confiança aos usuários durante importações grandes.
  • Elasticsearch alongside PostgreSQL (OLTP + OLAP): Custo de indexação dupla mas busca full-text (fuzziness, autocomplete, facets) seria impossível com queries LIKE. Consistência eventual (milissegundos) aceitável para catálogo de produtos.
  • Expo para cross-platform em vez de codebases iOS/Android/Web separadas: Algumas limitações nativas (animações real-time complexas), mas 1 codebase = iteração mais rápida e UX unificada. 19mil bibliotecas do ecosistema React Native disponíveis.
  • Schema Prisma multi-app compartilhado vs API gateway com agregação: Todas 3 apps conectadas ao mesmo BD (sem boundaries microserviço). Mais simples que transações distribuídas mas mais difícil escalar serviços individuais independentemente. Aceitável para escala atual; poderia fazer sharding por market_id se necessário.
  • RBAC (Role-Based Access Control) guardado vs provedor OAuth externo (Auth0): JWT + Passport built-in reduz overhead ops mas rotação de token auto-gerenciada necessária. Escolhido para controle; pode atualizar para Auth0 depois.

Hard Problems

  • Conflitos de estoque com pedidos concorrentes: 100 clientes comprando últimas 5 unidades simultaneamente. Resolvido com transações Prisma (RESERVE atômico → verificar snapshot → se falhar, rollback). StockSnapshot.available_quantity decrementado antes de inserir pedido; race condition impossível. Índice explícito adicionado em product_listing_id + created_at para performance de query.
  • Importação de catálogo em massa em escala: 5000 SKUs upados por mercado, cada um precisando enriquecimento de dados de fontes externas. Resolvido com padrão Kafka consumer: validar → lookup → correlacionar → Elasticsearch → escrita BD. Batch job rastreia IDs correlação para itens falhados. DLQ captura itens para retry depois. Admin vê barra de progresso real-time com ETA.
  • Isolamento de dados multi-vendor sem microserviços: 500 vendedores não deveriam ver dados de inventário/pedidos um do outro. Resolvido com market_id foreign key em OrderItem, ProductListing, StockEvent. Queries filtradas por contexto do seller do usuário atual (via middleware). Constraints foreign key de BD + triggers previnem leituras acidentais cross-market.
  • Fluxo de pagamento em dual-moeda: Usuário paga em BRL via PIX AbacatePay, vendedor recebe em USD via pagamento subscrição Stripe. Resolvido com PaymentOrchestrator: após sucesso de pagamento, criar entrada de crédito de conta, vendedor saca para Stripe. Conversão de moeda cacheada por hora (taxa do provedor). Cron de reconciliação compara ledger vs APIs do provedor diariamente.
  • Sincronização de inventário em tempo real entre 3 apps: App mobile mostra 'Últimas 2 restantes!', dashboard admin mostra contagem diferente devido a reserves pendentes. Resolvido com invalidação de cache StockSnapshot em evento StockEvent criado. Webhook dispara da API → invalida Elasticsearch + broadcaster WebSocket message para dashboard admin. Mobile faz poll a cada 30s ou no app focus.
  • Consistência de árvore de mercado de franquia: Mercado pai tem 10 warehouses, 5 mercados filho (filiais). Estoque alocado de pai → distribuído para filhos. Resolvido com campo explícito parent_market_id + queries de árvore recursiva. StockSnapshot atualizado em nível pai; queries filho agregam estoque disponível do pai. Sem herança automática—requer alocação manual para prevenir oversell.
  • Rastreamento de batch job e visibilidade de erro: Importação de 1000 itens falha no item 857 silenciosamente. Resolvido com tabelas BatchJob + BatchJobItem. Cada mensagem em Kafka recebe correlation_id (BatchJobItem.id). Consumer processa, atualiza BatchJobItem.status (PENDING → PROCESSING → SUCCESS/ERROR). Admin consulta itens com status='ERROR' e vê razão exata da falha.
  • Escalação de permissão de role: Role de funcionário é OPERATOR → não deveria editar configurações de mercado. Resolvido com decoradores RouteGuard verificando role do usuário + propriedade de market_id. Se role != ADMIN para o mercado, retorna 403 Forbidden. Endpoints privados requerem grant de permissão explícita em nível de seletor (seller_id → role → action).

Ops & Runbook

  • SLA de importação de catálogo: Se status de batch job fica 'PROCESSING' >5min, alerta escalada. Investigação: tail logs do Kafka consumer, verificar IDs correlação em tabela BatchJobItem (status='PROCESSING'), se travado >10min trigger falha manual + retry. Padrão: timeout de fonte de dados externa → adicionar lógica retry com backoff.
  • Recuperação de discrepância de estoque: Cron noturno soma entradas StockEvent, compara vs StockSnapshot.total_quantity atual. Se mismatch >5%, alerta team ops. Resolução: force refresh lendo de endpoint de contagem de inventário, criar evento ADJUSTMENT para reconciliar. Cada mismatch logado para analytics.
  • Reconciliação de pagamento: Nightly às 02:00 UTC, cron compara tabela Payment (status='COMPLETED') vs lista de invoices da API Stripe + lista de transações AbacatePay. Discrepâncias logadas para tabela manual_review. Comum: webhook recebido 2x (idempotency key deveria capturar, mas paranoia ajuda). Manual review resolve edge cases.
  • Reindex de Elasticsearch em mudança de schema: Se modelo ProductListing muda (adiciona field), deve fazer reindex de Elasticsearch. Script: deletar índice antigo → rodar reindex em massa do snapshot PostgreSQL → alternar alias. Durante reindex, buscas usam índice antigo (consistência eventual). Admin notificado de duração estimada de reindex.
  • Monitoramento de lag do Kafka consumer: Se lag >1000 mensagens, consumer mais lento que producer. Investigação: verificar latência de query PostgreSQL (StockSnapshot updates lentas?), verificar velocidade de indexação Elasticsearch, verificar timeout de lookup de dados externos. Escalar instâncias de consumer se CPU >80%.
  • Esgotamento do pool de conexão de BD: Muitos pedidos concorrentes esgotam conexões PostgreSQL. Monitorado via CloudWatch. Resposta: identificar queries lentas (query log), adicionar índices, ou aumentar tamanho do pool. Padrão: importação em massa + muitas visitas concorrentes de loja = contention do pool de conexão.
  • Expiração de sessão e rotação de token: Tokens JWT expiram em 15min. Refresh tokens armazenados em cookies (HttpOnly, Secure). App mobile auto-refresh em resposta de token-expired. Se refresh token roubado, invalidar via tabela blacklist de tokens. Tokens incluem seller_id para verificações rápidas de permissão sem lookup BD.

Security & Privacy

  • Isolamento de dados por vendedor: Todas queries filtradas por middleware seller_id. Se atacante obtém JWT para vendedor A, não consegue consultar pedidos do vendedor B (constraint BD + verificação aplicação). Testes verificam que este isolamento não pode ser bypassado via manipulação direta de ID.
  • Conformidade PCI para dados de pagamento: Stripe/AbacatePay lidam com PCI. App nunca vê números de cartão completos. Métodos de pagamento tokenizados armazenados, com últimos 4 dígitos para exibição UI. Assinaturas de webhook verificadas via HMAC-SHA256 antes de processar mudanças de estado de pagamento.
  • Proteção de endpoint baseada em role: Decoradores @UseGuards(JwtAuthGuard, RoleGuard) fazem força de permissões. GET /orders acessa apenas pedidos do seller atual. POST /market/:id/update requer que seller seja proprietário. 403 retornado se unauthorized; acesso logado em auditoria.
  • PII em item de batch job: Alguns nomes de produto podem conter info sensível. IDs correlação gerados server-side (não user-provided), não podem ser sequencialmente adivinhados. BatchJobItem.error_message sanitizado para evitar vazar detalhes de implementação.
  • Backups de BD: Snapshots diários para S3 com criptografia KMS. Restore point-in-time disponível (últimos 30 dias). Testado mensalmente tentando restore para ambiente staging.
  • Rate limiting de API: 1000 req/min por IP para endpoints públicos, 5000/min por usuário autenticado para endpoints privados. Módulo Throttler aplicado globalmente; exceder limite retorna 429 Too Many Requests. Previne brute force em endpoints de auth.

What I'd Improve Next

  • Boundary de microserviço por domínio: Separar PaymentsModule → serviço separado habilita escalonamento independente. API → Payment Service via gRPC ou eventos assíncronos. Difícil agora devido schema Prisma compartilhado; requereria BD por serviço.
  • Webhooks de inventário em tempo real: Atualmente faz poll a cada 30s. Implementar WebSocket subscriptions: subscribe('ProductListing:12345:inventory') → servidor broadcast em StockEvent. Reduz drenagem de bateria do app mobile.
  • Machine learning para previsão de demanda: Prever necessidades de inventário por mercado baseado em padrões históricos de pedido. Auto-sugerir quantidades de stock-in para vendedores. Requer BD de séries temporais (InfluxDB) + pipeline ML.
  • Carteira multi-moeda por usuário: Em vez de pagamento-on-demand, usuários mantêm balanços de carteira (BRL + USD). Checkout mais rápido, habilita transferências peer-to-peer entre vendedores. Requer licença como instituição de pagamento no Brasil.
  • Camada API GraphQL: Substituir endpoints REST com GraphQL. Habilita clientes a consultar exatamente o que precisam (reduzir tamanho de payload para mobile). Federação entre 3 apps fica mais simples.
  • Motor de detecção de fraude: Analisar padrões de pedido para anomalias (volume subitamente 100x maior, novo vendedor enviando para 50 países em 1 dia). Integrar com Stripe Radar. Review automático de transação para pedidos de alto risco.