case/projetos/luvero
06/10
Luvero·Dono · Fullstack·Negócio próprio · em produçãoNo ar em luvero.com.br, recebendo agendamentos

Luvero

O banco é o árbitro final da corrida por horário: uma exclusion constraint do Postgres sobre faixas de tempo, não um lock de aplicação, é o que torna o agendamento duplo fisicamente impossível.

23P01 → 409
constraint no banco torna agendamento duplo impossível
247 tests
em 47 arquivos, rodando em ~1,4s
36 use cases
num domínio em camadas, livre de framework
Solo · dono e único dev
time

Dono e único dev · jul a ago 2026 · em produção. Toco um negócio de estética automotiva a domicílio em João Pessoa e precisava que o cliente agendasse online sem nunca colidir dois atendimentos no mesmo horário. Construí o Luvero ponta a ponta: um wizard de agendamento em 5 passos que recalcula o preço no servidor, uma regra anti-conflito garantida pelo próprio banco, e um admin mobile-first onde opero tudo - agenda, um CRM derivado do histórico, orçamentos e financeiro do mês. Preço em centavos no sistema todo, fotos de carro em bucket privado atrás de URL assinada, e 247 testes seguram o domínio no lugar.

01

O que ele resolve

Um negócio de uma pessoa só que dirige até o endereço do cliente não pode prometer dois carros pra mesma tarde. O Luvero deixa o cliente escolher a cobertura do serviço, o veículo e um horário livre online, com a taxa de deslocamento por bairro já embutida no total. Do outro lado, opero o negócio inteiro de um admin mobile: agenda, agendamento manual boca a boca, orçamentos, CRM e financeiro do mês, sem tocar em planilha.

02

Anti-conflito no banco

Dois clientes podem clicar em 'agendar' no mesmo horário no mesmo instante, então o árbitro tem que estar abaixo da aplicação. Uma exclusion constraint do Postgres (btree_gist sobre um tstzrange half-open) nos agendamentos pendentes e confirmados torna a sobreposição impossível de representar. O Prisma não modela EXCLUDE, então a constraint mora numa migration SQL escrita à mão; o repository captura o SQLSTATE 23P01, transforma em erro de domínio que vira HTTP 409, e a UI refaz a busca de horários sem alarde. Um lock de aplicação não sobreviveria a duas instâncias nem a uma corrida real.

23P01 → 409
constraint no banco torna agendamento duplo impossível
03

O wizard de agendamento

Cinco passos guiados por um useReducer, com o rascunho salvo em sessionStorage e deep-link por query string. A busca de CEP passa pelo ViaCEP com fallback manual, um cliente recorrente é reconhecido pelo endereço salvo, e um lead é capturado antes de a conversão terminar. O preço mostrado ao vivo usa a mesma política de preço do servidor, que recalcula tudo no POST, então o client nunca é dono do número final. Um gerador de slots em domínio puro espelha exatamente a semântica half-open da constraint do banco, então a UI e o árbitro sempre concordam.

04

Um CRM sem tabela de cliente

O admin v2 é mobile-first: uma aba Agenda pinta 14 dias coloridos por ocupação e monta uma mensagem de disponibilidade pronta pra mandar no WhatsApp, apoiada num use case que devolve os horários livres de cada dia. De propósito não existe tabela de 'cliente' - uma agregação em domínio puro agrupa agendamentos por número de WhatsApp em total gasto, dias desde a última lavagem e uma flag de lembrete. Contatos e leads têm modelo próprio com atribuição de marketing (UTM, fbclid, gclid), e a visão do mês soma despesas, ticket médio, ocupação da agenda e taxa de recompra.

05

Fotos privadas e disciplina de engenharia

A foto de um carro carrega a placa e o endereço do cliente, então é PII. Os uploads são comprimidos no client num canvas, validados por MIME e tamanho no servidor, guardados sob uma key UUID num bucket S3 privado, e só lidos por um proxy autenticado que responde 302 pra uma URL assinada de uma hora, com um script de faxina de órfãs numa carência de 24h. Embaixo de tudo há uma arquitetura em camadas - 7 subdomínios puros, 36 use cases e ports, infraestrutura e interface separadas - segura por 247 testes em 47 arquivos que rodam em cerca de 1,4 segundo.

decisões & tradeoffs
  • Onde você garante que dois agendamentos nunca colidem?
    Exclusion constraint no PostgresUm lock de aplicação não sobrevive a duas instâncias nem a uma corrida real; a constraint sobrevive. O custo que doeu: sair do trilho do Prisma pra uma migration SQL escrita à mão, e nunca mais aceitar um migrate reset, porque um drift nessa constraint seria silencioso e catastrófico.
  • Por que Next.js full-stack em vez de HTML estático mais Supabase?
    Next App Router com API routes + PrismaO browser nunca fala com o banco, o preço é sempre recalculado no servidor, e o deploy fica agnóstico de fornecedor. O trade-off: carregar um framework inteiro pra um site que é 80% conteúdo estático.
  • Como lidar com timezone com o servidor em UTC?
    Um offset fixo -03:00 centralizadoSão Paulo não tem horário de verão desde 2019, então um offset fixo mata a classe inteira de bugs de new Date e getDay local. O trade-off: se o horário de verão voltar há exatamente um ponto pra corrigir, mas é um ponto, não uma caça.
  • NextAuth ou sessão própria pra um admin de um usuário?
    timingSafeEqual + JWT em cookie httpOnlyNextAuth é overkill pra um operador, então é uma comparação de senha em tempo constante, um JWT assinado, e um gate na primeira linha de toda rota de admin. O custo: sem multiusuário e sem recuperação de senha, o que aceito porque só há um operador, eu.
  • Fotos de carro públicas ou privadas?
    Bucket privado + URL assinadaUma foto de carro mostra placa e endereço, que é PII, então nunca fica numa URL pública. O custo é um hop a mais pelo proxy e um script de faxina de órfãs, barato perto de vazar a placa de um cliente.
  • Como guardar dinheiro?
    Centavos inteiros em todo lugarFloat em dinheiro derrapa em silêncio, então o sistema todo carrega centavos inteiros e só a UI formata pra reais. O desconto de abertura virou uma constante única, então desligar a promoção foi mudar um número, não caçar por ele.
Histórico
em evolução
  1. Ago 2026Lavagem de moto entra como serviço sem porte
  2. Ago 2026Admin v2 mobile-first: agenda de 14 dias, CRM unificado
  3. Jul 2026Funil de marketing: captura de lead, atribuição UTM, gerador de orçamento
  4. Jul 2026Operação do dono: agendamento manual, bloqueios de agenda, templates de WhatsApp
  5. Jul 2026Catálogo por cobertura de serviço, taxa de deslocamento por bairro
  6. Jul 2026MVP num commit denso: wizard, anti-conflito no banco, admin

No ar em luvero.com.br, indexado e recebendo agendamentos, com foco agora em aquisição. É um negócio de verdade que eu toco, e a engenharia mostra isso: agendamento duplo é impossível no nível do banco, preço nunca é confiado ao client, a PII do cliente fica privada, e um domínio em camadas sob 247 testes mantém a operação inteira honesta - agendamento, agenda, CRM e financeiro.