Pular para o conteúdo
KW
Voltar aos projetos

amostra 04/em desenvolvimento

Atlas

Um CRM de vendas que virou um sistema de gestão para pequenos negócios, moldado ao setor de cada empresa.

  • Next
  • Prisma
  • Zod
  • Turbo

01

Contexto

O Atlas começou como um CRM para uma equipe de vendas: funil por status, metas, modelos de e-mail e envio pela conta Google de cada vendedor.

Em agosto o produto mudou de direção. Virou uma plataforma multiempresa em que cada negócio, como um restaurante ou uma barbearia, recebe um painel com os módulos do seu setor.

02

Ponto de partida

A primeira versão atendia um único time de vendas, rodando com dados de demonstração.

3

papéis: vendedor, gestor e administrador

600

contas simuladas no modo demonstração

1

empresa por instalação

03

Linha do tempo

56 commits no histórico do projeto. Estes são os marcos.

  1. 02 de jul. de 2026

    Primeira versão

    CRM de vendas com um portal por papel e modo demonstração com dados simulados.

  2. 09 de jul. de 2026

    Integração com Gmail

    Conexão OAuth com a conta Google, tokens cifrados, limite de envio e um botão de desligar a integração inteira.

  3. 10 de jul. de 2026

    Login com Google

    Identidade individual por conta Google, além do login por senha.

  4. 08 de ago. de 2026

    Virada para multiempresa

    Fundação refeita: empresa e setor dentro da sessão assinada, cadastro de empresa com seu dono e o primeiro módulo completo, Clientes, no padrão repositório, serviço e rota.

  5. 09 de ago. de 2026

    Módulos de operação

    Caixa, catálogo, atendimentos, estoque e previsão de faturamento.

  6. 11 de ago. de 2026

    CRM e análises

    Segmentos de clientes com consentimento da LGPD e painéis de análise por setor.

  7. 15 de ago. de 2026

    Banco de dados real

    Saída do modo demonstração para o Postgres, com os dados do esquema antigo guardados em backup antes da troca.

  8. 08 de set. de 2026

    Acesso restrito ao alfa

    Login fechado para o grupo do teste alfa.

04

Mapa de decisões

Reconstruído a partir do histórico de commits, do README e da documentação do projeto.

01

Refazer a fundação, não o produto inteiro

Autenticação, o padrão de repositórios e o design system continuaram. Saiu o domínio de vendas e entrou um modelo genérico de negócio.

02

A empresa vem da sessão

Toda rota de negócio lê a empresa de cabeçalhos que o middleware injeta a partir da sessão assinada. O navegador não tem como pedir os dados de outra empresa.

03

Setor é configuração

Um registro em código define quais módulos cada setor ativa e como cada coisa se chama. Um setor novo é uma entrada nova, não uma tabela nova.

04

Um modelo de dados para todos

Clientes, itens de catálogo, atendimentos, lançamentos e estoque servem a todos os setores, com um campo flexível para o que é específico de cada um.

05

Demonstração sem infraestrutura

Uma fábrica de repositórios alterna entre dados em memória e Prisma com Postgres. Dá para avaliar o produto inteiro sem configurar banco nenhum.

06

Alfa com escopo mínimo

O teste alfa atende só donos de negócio, um por empresa, sem gestão de equipe. Menos rotas no ar significa menos superfície de ataque.

05

Arquitetura

As mesmas cinco camadas desta página, aplicadas ao projeto.

  1. 000 m/interface

    • Painel que se molda ao setor
    • Design system com shadcn/ui
    • Gráficos com Recharts
  2. 030 m/rota

    • Middleware com controle por papel
    • Sessão assinada com HMAC-SHA256
    • Cabeçalhos forjados pelo cliente descartados
  3. 060 m/serviço

    • Serviços por módulo
    • Validação com Zod
    • Registro de setores e módulos
  4. 090 m/repositório

    • Fábrica de repositórios: memória ou Prisma
    • Toda consulta filtrada por empresa
    • Exclusão lógica
  5. 120 m/persistência

    • Postgres no Supabase
    • Senhas com scrypt
    • Tokens OAuth cifrados com AES-256-GCM

06

Código

Um trecho do repositório privado, só para mostrar a camada funcionando.

A empresa nunca vem do navegador

Todo módulo de negócio começa por aqui. O identificador da empresa sai da sessão assinada, e sem ele a rota nem continua.

apps/web/lib/auth/api-context.tsts
// Lê identidade + tenant dos headers injetados pelo middleware (assinados
// via cookie HMAC — o client não forja). Use em route handlers.

export async function getApiContext(): Promise<ApiContext | null> {
  const h = await headers();
  const userId = h.get("x-user-id");
  const role = h.get("x-user-role") as Role | null;
  if (!userId || !role) return null;
  return { userId, role, companyId: h.get("x-company-id") };
}

/**
 * Contexto que EXIGE empresa (módulos de negócio). SK_ADMIN sem empresa
 * selecionada recebe 400 — a visão cross-tenant tem rotas próprias (/api/sk).
 */
export async function requireCompany(): Promise<RequireCompanyResult> {
  const ctx = await getApiContext();
  if (!ctx) {
    return { error: NextResponse.json({ error: "Não autorizado" }, { status: 401 }) };
  }
  if (!ctx.companyId) {
    return {
      error: NextResponse.json(
        { error: "Nenhuma empresa no contexto." },
        { status: 400 }
      ),
    };
  }
  return { ctx: { ...ctx, companyId: ctx.companyId } };
}

O mesmo item, nomes diferentes por setor

Uma tabela só no banco, com a linguagem de cada negócio na tela. É o que permite atender setores novos sem mexer no modelo de dados.

apps/web/lib/sectors/index.tsts
// Terminologia do catálogo por setor (o mesmo CatalogItem se chama "prato"
// num restaurante e "serviço" num salão).
export function catalogTerms(sector: Sector | null): CatalogTerms {
  switch (sector) {
    case "CABELEIREIRO":
      return { title: "Serviços", singular: "serviço", plural: "serviços", addLabel: "Novo serviço" };
    case "RESTAURANTE":
    default:
      return { title: "Cardápio", singular: "item", plural: "itens", addLabel: "Novo item" };
  }
}

07

Resultado

2

setores ativos: restaurante e barbearia

11

tabelas num modelo comum a todos os setores

3

papéis: equipe da plataforma, dono e funcionário

Estado atual

Em teste alfa com donos de negócio, começando pela barbearia.