Hi, I'm

Enzo Terra

Fullstack Developer · Frontend · AI

I build systems and solutions that solve real problems, from backend to interface, with care for design and a layer of intelligence where it truly matters.

scroll to explore

01 · about

Nice to meet you, I'm Enzo.

Photo of Enzo Terra
Campo Grande · MS

I'm a developer from Campo Grande, Brazil, with a degree in Information Systems. Frontend is my foundation: it's where I have the most experience, joining development with design, UI/UX and visual identity. On top of that I built a fullstack profile that today reaches from backend to infra.

I work at UFMS (Federal University of Mato Grosso do Sul), where I moved from technical support to the innovation team at DIDIA (Directorate of Innovation and Artificial Intelligence), and from there became deputy manager of GERIA, the AI management office within DIDIA. Along the way I built entire systems (some of them on my own) that are in production and used every day.

In parallel, I build my own projects and provide services to companies: websites, custom systems and AI solutions. I have shipped live sites for real clients, and I like delivering things that genuinely work, with tests, security and honest care for the people using them.

19+

complete systems delivered so far

3+

years building software professionally

Automation

current focus on automation, integrations and smart systems

02 · experience

The journey so far

From hardware maintenance to managing an AI team. Each phase helped build what I know today.

※ The systems listed are just a sample of what I built; I picked the ones I find most relevant.

  1. Jul 2022 to Feb 2023

    Maintenance technician

    Positivo Tecnologia (outsourced)

    Where it all started: equipment maintenance. I learned hardware and support hands-on, fixing computers, laptops and phones, and providing on-site support at public agencies and private companies that used Positivo technology.

  2. Feb 2023 to mid 2024

    Technical support (public servant)

    UFMS · Federal University of Mato Grosso do Sul

    A year and a half in support, but beyond just answering tickets, I started automating what slowed down our day-to-day and building what could help us. Even though Agetic (UFMS's technology agency, where I worked) had its own development team, its focus on students kept it from building systems for other areas. This is where I first got the chance to build working systems and flows for the university.

    WhatsApp ↔ GLPI

    Integration between WhatsApp support and GLPI, unifying support channels into a single flow.

    IntegraçãoGLPIWhatsApp

    Rádio UFMS

    System deployed for the university's radio station, making it easier for the team to use and giving them the freedom to broadcast an event from anywhere through secure channels.

    Fullstack

    Forms inteligentes

    Smart forms that resolved GLPI support tickets automatically, with no human in the loop.

    AutomaçãoGLPI
  3. mid 2024 to 2025

    Developer · innovation team

    DIDIA · Directorate of Innovation and AI (UFMS)

    Out of a drive to build systems that could solve real problems (and here I'm truly grateful to those who helped me get that chance), I took part in forming a new team: the innovation team, whose goal was to create innovative solutions for the university and for society. Here I built entire systems, sometimes on my own, from design to deploy. On this team my experience grew a lot: we built scripts, software, plugins and AI, using modern tools and sometimes even creating new ones, making it possible to build complete systems that didn't exist anywhere else (real innovation). In this period alone there were 12 systems in 1 year! Along with them came logos, visual identities and frontends for several other projects. Below are a few of the ones I find most relevant.

    ★ flagship project
    Logo GLPI.IA

    GLPI.IA

    A plugin adding AI layers to GLPI: automatic ticket classification, workload balancing across technicians, smart reply suggestions, a knowledge-base chatbot and sentiment analysis. Built by me, with input from colleagues more experienced in AI.

    It led to articles for the IT community and became the subject of a talk (which I presented myself) at an inter-institutional technology event.

    IANLPGLPIAnálise de sentimento
    Logo Visão UFMS

    Visão UFMS

    An intelligent security center, featuring virtual guards, smart alerts, camera mosaics and computer vision.

    Visão computacionalIATempo real
    Logo Acesso UFMS

    Acesso UFMS

    Electronic lock management across the whole university: access permissions, logs, NFC keys and more.

    IoTNFCControle de acesso
    Logo EnerGys

    EnerGys

    Control of IoT devices and the campus electrical infrastructure with a focus on sustainability: energy meters, electrical panels, poles and lights, plus other devices, all georeferenced on the map.

    IoTSustentabilidade.NETMapa
    Logo SmartCampus

    SmartCampus

    UFMS's smart campus portal, with a dynamic map and a hub giving access to every system built by the team. BFF backend with institutional login.

    Hub de sistemasMapaReactASP.NET
    Logo Recepção UFMS

    Recepção UFMS

    A game-like system for welcoming freshmen, with prizes, rankings and a map-guided flow.

    Gamificação

    Jogos para eventos

    Games in various formats for university events, including Unity development.

    UnityJogos
  4. 2025 · present

    Deputy Manager · Developer

    GERIA · AI Management Office (within DIDIA · UFMS)

    Today I serve as deputy manager of GERIA, the AI management office within DIDIA, where we create innovative systems and solutions for the university. I combine what I learned as a builder with the responsibility of helping to guide a team, without stepping away from the code: I keep building entire systems. A few of them below.

    Logo Loja UFMS

    Loja UFMS

    Sales system for the UFMS bookstore across two channels: e-commerce for the academic community and the general public, and a POS for in-person sales at the store counter, with unified stock, orders and reports. Payments via Mercado Pago, shipping calculation and login through the UFMS SSO, Google or a local account.

    E-commercePDVDjangoNext.js
    Logo Posse Digital

    Posse Digital

    Digital onboarding of newly appointed UFMS staff: the appointee fills in their details, generates the registration form and signs it digitally through gov.br. The system validates the signature, automatically opens the case with the documents attached and sends an e-mail at every completed step.

    FastAPIReactSEIAssinatura digital
    Logo Mapa UFMS

    Mapa UFMS

    Interactive campus map that can be embedded in any website, with an admin panel for places and rooms, institutional login, editor and admin roles and an audit trail.

    DjangoReactLeafletKeycloak

    Design System

    A unified design system for every DIDIA system: color, typography, spacing and shadow tokens (with light and dark themes), a React component library and an interactive documentation site.

    Design systemReactTailwind CSSTokens

03 · projects

Things I built on my own

Personal and client projects, from zero to deploy, each one born from a real problem.

Logo My Dev Team

My Dev Team

★ spotlight

A team of AI agents: you bring the idea, the team delivers the software

An open source plugin for Claude Code that works like a full software house. You only step in as the client: you bring the idea and make the business decisions. The team handles everything else, from gathering requirements to running code, and goes further by enriching your project with innovative ideas researched from similar products, each one with measured impact and effort so you can choose what makes the cut. The specialized agents are: idea analyst, innovation developer, plan writer, designer, backend/database/frontend/AI devs, infra and security. The team right-sizes to the project, brings in only who is needed, designs security in from the start and picks up right where it left off. Install it, describe your idea and watch your team get to work.

Claude CodeAgentes de IAPrompt engineering

My Dev Team — uma lâmpada, da ideia ao código

My Dev Team

Um plugin do Claude Code que conduz um software da ideia ao código executável,
através de um time de papéis seniores — sem cerimônia a mais nem segurança a menos.

analista de ideias · desenvolvedor de ideias · escritor de plano · designer · dev backend · dev banco · dev frontend · dev IA · analista de infra · analista de segurança


Você descreve uma ideia. O plugin conversa para entendê-la de verdade, escreve um plano de implementação dimensionado ao porte do projeto e então constrói o sistema — com testes, segurança em todas as camadas e um caminho claro de “como rodar”. Tudo em português, registrado em arquivos no seu repositório, e retomável em outra sessão.

Você é o cliente; o time faz o resto. Assim como numa software house, você só traz a ideia e toma as decisões de negócio. O time cuida de todo o resto: levanta requisitos, pesquisa o que produtos parecidos já fazem, propõe ideias inovadoras que você nem tinha pensado (cada uma com impacto e esforço medidos, para você escolher o que entra), desenha a interface, planeja as sprints, escreve o código e os testes, sobe a infraestrutura e audita a segurança.

Pensado tanto para uso real quanto como peça de portfólio: demonstra orquestração de múltiplos agentes, engenharia de prompt e práticas modernas de engenharia de software.

⚡ Início rápido

1. Instale (no Claude Code, dentro de qualquer projeto):

/plugin marketplace add enzoterra/claude-project-builder
/plugin install project-builder@claude-project-builder

2. Construa — no projeto onde quer o sistema, descreva a ideia:

/construir-projeto um app para a fila de espera do meu consultório

Pronto. O orquestrador assume a partir daí e conduz as fases, parando para sua aprovação ao fim de cada uma. Você não precisa saber de stack, infra ou arquitetura — ele pergunta o que precisar e recomenda quando você não souber.

Só quer planejar? Use um papel isolado: /analista-de-ideias (brainstorming) ou /escritor-de-plano (documentação técnica). Retomar mais tarde? É só rodar /construir-projeto de novo no mesmo projeto — ele lê o progresso e continua de onde parou.

✨ O que o torna diferente

  • 🧭 Entende a ideia antes de codar. A descoberta não é um formulário: o analista reformula o que entendeu para confirmar, investiga o porquê por trás do pedido (a necessidade real, não só a solução imaginada), lida com respostas vagas ou contraditórias e calibra a linguagem ao seu nível técnico. E um desenvolvedor de ideias dedicado pesquisa o que projetos parecidos fazem e propõe inovações — para o produto e para cada fluxo — com impacto e esforço medidos, pra você decidir.
  • 🧩 Serve a qualquer tipo de projeto. Além do porte, ele detecta a forma do sistema — app fullstack, API sem interface, CLI, biblioteca, projeto de dados/IA/ML (ex.: só Python) — e liga/desliga papéis: sem interface não há design nem frontend; sem persistência não há banco.
  • 🤖 Engenheiro de IA quando faz sentido. Se IA é parte significativa do produto ou dos fluxos, um dev de IA assume a camada de inteligência — escolha de modelo, prompts, RAG/agentes, evals, guardrails, custo e a segurança específica de IA (prompt injection, vazamento de dados). Uma chamada trivial a um modelo continua com o backend — sem peso extra.
  • 🎨 Design antes do código. Quando há interface, um designer desenha o sistema de design (tipografia, cores/tokens, ícones, botões, cards, border-radius, estados) e um mockup de referência — e o frontend implementa fiel a isso. O design desenha; o frontend desenvolve.
  • 📐 Proporcionalidade (right-sizing). Nem todo projeto é corporativo. Ele detecta o porte (enxuto / padrão / robusto) e dimensiona tudo: um protótipo pessoal não carrega Docker, CI nem nove documentos — só o que faz sentido. Sempre com um mínimo inegociável (sem segredos no código, entrada validada, funciona, tem “como rodar”).
  • 🛡️ Segurança e testes por design. Em projetos de produção, segurança e testabilidade são projetadas desde o início (modelo de ameaças, OWASP, authn/authz anti-IDOR, pirâmide de testes, gates de CI) — não viram uma sprint jogada no fim. E um analista de segurança dedicado audita o código de backend e frontend contra as ameaças atuais, conduzindo a correção até zerar falhas altas/críticas.
  • 🔄 Retomável entre sessões. O progresso é um checkpoint vivo: status por sprint e passos concluídos, escritos a cada etapa. Feche o terminal hoje, continue amanhã noutra máquina — nada se perde.
  • 🆕 Stacks modernas e seguras. Antes de fixar qualquer versão de linguagem/framework/lib, ele pesquisa a versão estável atual — nunca confia na memória.
  • ▶️ Pronto para rodar. Quando há Docker/produção, entrega docker compose up + seed de demonstração (com usuários de exemplo) + README de como rodar localmente.

🤝 Você é o cliente

O plugin funciona como uma empresa de software atendendo você: cada papel é um profissional sênior, e você é o cliente que aprova as decisões que importam.

  1. Você conta a ideia. O analista de ideias conversa, entende a necessidade real e, com o desenvolvedor de ideias, traz alternativas e inovações (medidas por impacto × esforço). → Você decide o que entra.
  2. O time monta o plano. O escritor de plano conversa com os devs que vão executar para achar a melhor solução, e tira suas dúvidas quando precisa. → Você aprova o plano.
  3. O time executa. Os especialistas constroem por sprints. Eles não te interrompem a cada passo — só pedem aprovação nos marcos que importam (a direção visual antes de codar a tela, o primeiro fluxo funcionando) e quando surge uma decisão de verdade.
  4. Você valida a entrega. No fim (UAT), você percorre cada fluxo e confirma que atende ao que pediu. Ajustes voltam para o time. → Só é “pronto” quando você aprova.

Os pontos de aprovação são proporcionais: num protótipo enxuto o time confirma só o essencial; num produto de produção há mais checkpoints. Você é incomodado onde sua opinião muda o resultado — não mais.

🧠 Como funciona

O orquestrador construir-projeto conduz quatro fases, delegando a cada especialista:

  💡 Descoberta  →  📝 Planejamento  →  🏗️ Implementação (por sprints)                              →  ✅ Validação
   (entender)        (documentar)         (infra → banco → backend → IA → design → frontend → segurança)   (UAT: validar com o cliente)

A trilha de implementação se adapta à forma do projeto: papéis sem camada correspondente (design e frontend num projeto sem UI, banco num projeto sem persistência, IA quando não há inteligência no produto) simplesmente não entram.

O estado não vive na conversa — vive em arquivos docs/projeto/ do seu repositório. É isso que permite aos subagentes (que começam sem contexto) e a sessões futuras retomarem o trabalho:

ArquivoO que guarda
00-descoberta.mdA ideia entendida: necessidade real, perfil, usuários, restrições
01-plano.md … 09-operacao-e-deploy.mdPlano completo (conforme o porte e a forma): requisitos, arquitetura, banco, sprints, user stories, testes, segurança, operação
10-design.md (+ design/mockup.html)Sistema de design, quando há interface: tipografia, cores/tokens, ícones, componentes, estados
PADROES.mdOs padrões transversais adaptados ao projeto
PROGRESSO.mdCheckpoint vivo: status de cada sprint e passos concluídos

👥 Os papéis

PapelTipoO que faz
analista-de-ideiasskillBrainstorming interativo: necessidade real, porte, tipo/forma do projeto, usuários, dados, segurança, infra, design, inovações. Confirma o entendimento e recomenda quando você não sabe.
desenvolvedor-de-ideiassubagentePesquisa o que projetos parecidos fazem e propõe ideias inovadoras — para o produto e para cada fluxo — com impacto e complexidade medidos. Trabalha junto da analista; você decide o que entra.
escritor-de-planoskillPlano dimensionado ao porte e à forma: objetivos, requisitos (F/NF), arquitetura e stack (versões pesquisadas), banco, sprints, user stories, testes, segurança e operação. Consulta os especialistas para avaliar viabilidade e alternativas antes de fixar.
designersubagenteSistema de design (só se houver interface): tipografia, cores/tokens, ícones, botões, cards, border-radius, estados — e um mockup de referência. O design desenha; o frontend desenvolve.
dev-backendsubagenteAPI, regras de negócio, authn/authz e segurança rota a rota, testes.
dev-banco-de-dadossubagenteSchema, migrações, índices e seed de demonstração.
dev-frontendsubagenteUI moderna, limpa e responsiva, acessível, fiel ao sistema de design, com validação de entradas, integração com a API e testes.
dev-iasubagenteCamada de inteligência quando há IA central: seleção de modelo, prompts versionados, RAG/agentes, evals, guardrails, custo/latência e segurança de IA (prompt injection, vazamento, OWASP LLM). Também lidera pipeline de ML quando o projeto desenvolve modelo.
analista-de-infrasubagenteEsqueleto, Docker/compose, config por env, CI e o caminho “como rodar”.
analista-de-segurancasubagenteAuditoria adversária de backend e frontend contra todas as vulnerabilidades da stack (pesquisando ameaças atuais), conduzindo a correção até zerar falhas altas/críticas.
construir-projetoskill (orquestrador)Conduz todas as fases, dimensiona ao porte e delega aos especialistas.

Todos atuam como seniores na linguagem e framework que você pedir.

📐 Perfis de projeto

O porte é decidido com você na descoberta e respeitado por todos os papéis:

PerfilPara quêO que entrega
EnxutoScript, protótipo, ferramenta pessoal, trabalho de faculdade, MVP de validaçãoRoda local direto, plano leve (1 doc), sem cerimônia. Foco em funcionar rápido.
PadrãoProduto real com usuários, vai a produçãoDocker + seed demo, CI, testes, segurança sólida, plano dividido.
RobustoSistema empresarial ou de missão críticaTudo: observabilidade, SLO, DR, DevSecOps, compliance, multi-ambiente.

🗂️ Estrutura do plugin

.claude-plugin/
  plugin.json          # manifesto do plugin
  marketplace.json     # catálogo (permite adicionar este repo como marketplace)
skills/
  construir-projeto/   # orquestrador + referencia/padroes.md (padrões transversais)
  analista-de-ideias/  # descoberta interativa
  escritor-de-plano/   # planejamento + templates/
agents/
  desenvolvedor-de-ideias.md   designer.md
  dev-backend.md   dev-banco-de-dados.md   dev-frontend.md   dev-ia.md
  analista-de-infra.md   analista-de-seguranca.md

📦 Instalação (detalhes)

Via marketplace (recomendado) — funciona em qualquer projeto após adicionado:

/plugin marketplace add enzoterra/claude-project-builder
/plugin install project-builder@claude-project-builder

(ou aponte para a URL: /plugin marketplace add https://github.com/enzoterra/claude-project-builder)

Importante: Se os comandos desse plugin não aparecerem no Claude Code Desktop App após a instalação, pode ser necessário encerrar o Claude (inclusive pelo ícone da bandeja do sistema, no Windows) e iniciar novamente.

Clonando localmente — útil para inspecionar ou ajustar antes:

git clone https://github.com/enzoterra/claude-project-builder
/plugin marketplace add ./caminho/para/claude-project-builder
/plugin install project-builder@claude-project-builder

Requisitos

  • Claude Code atualizado (suporte a plugins e skills).
  • Um repositório git no projeto-alvo (o plugin oferece git init se não houver).

👤 Autor

Enzo Andrade Terra · enzoterra18@gmail.com

Licença

MIT

Logo Encore

Encore

Your Spotify stats for any period, without your history leaving the device

A Spotify recap whenever you want: top artists, tracks and albums, a heatmap of when you listen, fun facts about yourself and ready-made posters for Stories. Privacy by architecture: the full history is processed in a Web Worker, right in the browser, with no request carrying its content.

Next.js 16React 19TypeScriptWeb WorkersSpotify API

Logo do Encore

Encore

Suas estatísticas do Spotify quando você quiser: tops, horários e curiosidades de qualquer período, sem que o seu histórico saia do aparelho.

encore-stats.vercel.app · ver a demo

Página inicial do Encore: o título "Seu ano em música. Quando você quiser.", o selo "Tudo fica no seu aparelho" e um cartaz de exemplo com artistas inventados

Como é

Todas as imagens abaixo são da demo, com artistas e músicas inventados.

Seu top, seus números e o seu período

Escolha um mês, um ano, desde sempre ou um intervalo. O Encore mostra os artistas, as músicas e os álbuns que você mais ouviu, quantos minutos de música foram e quantos artistas diferentes apareceram.

Painel da demo: a escolha do período (mês, ano, sempre ou intervalo), o seu top de artistas e os minutos de música do ano

Quando você ouve: um mapa de calor com os dias da semana e as horas do dia, destacando o horário em que você mais ouve música

Quando você ouve: os dias e os horários em que a música mais toca, com o seu pico e o horário mais quieto.

Você por você: desde quando você é fã do seu artista nº 1, quanto dos seus plays foram dele, em quantos dias diferentes ele tocou, a música mais pulada e o dia mais musical

Você por você: comparações com o seu próprio histórico, e não com outras pessoas.

Suas curtidas, também no modo Upload

De quem você tem mais músicas curtidas? Junto do histórico, você pode enviar também o arquivo “Dados da conta” do Spotify. Ele é opcional e, dele, o Encore lê só as suas curtidas: o resto do arquivo nem é aberto. Dá para mandar os dois de uma vez ou mandar as curtidas depois, com o painel aberto. O quadro vale para qualquer época, porque o Spotify não informa quando você curtiu cada música.

Suas curtidas: o artista com mais músicas curtidas em destaque e, ao lado, os outros nove do seu top de curtidas, com quantas músicas de cada um

Modo Conectar: o que está tocando agora

Entrando com a conta do Spotify, você vê os tops das últimas 4 semanas, dos últimos 6 meses e do último ano, quem subiu e quem caiu, e de quem você tem mais músicas curtidas (é só tocar em “Descobrir”).

Visão Conectar da demo: os períodos 4 semanas, 6 meses e 1 ano, os atalhos Desde o começo e Selecionar período, o seu top de artistas e o botão Descobrir das curtidas Tendências: artistas em alta, com os novos e os que subiram, e artistas em queda

Pela conexão, o Spotify só mostra esses três períodos. Os atalhos “Desde o começo” e “Selecionar período” explicam isso e levam você ao envio do histórico completo, que libera qualquer período.

Os períodos do modo Conectar e os atalhos "Desde o começo" e "Selecionar período"

Cartazes para compartilhar

Quatro modelos, Line-up, Músicas, Mix e Básico, em Stories (9:16) ou quadrado (1:1). A imagem é feita no seu aparelho, com o período que está na tela, e vai direto para os Stories, o feed ou o WhatsApp.

Os quatro cartazes lado a lado: Line-up, com os artistas como num festival; Músicas, com o top de músicas; Mix, com artistas e músicas; e Básico, com o artista nº 1, os tops e os minutos de música

Janela de compartilhar: a escolha do modelo e do formato, o nome no cartaz (opcional) e a prévia do cartaz Line-up

No celular

Página inicial no celular, com o título, o selo de privacidade e o cartaz de exemplo Painel da demo no celular: período, minutos de música e a barra de baixo com o botão Compartilhar Compartilhar no celular: o cartaz Line-up no formato quadrado, pronto para baixar

Três jeitos de usar

  • Enviar o histórico (modo Upload). Você pede ao Spotify o arquivo com o seu histórico completo (ele chega por e-mail em alguns dias) e escolhe esse arquivo aqui. A conta toda é feita no seu aparelho, e dá para ver qualquer período, desde a primeira música. Se quiser, envie também o “Dados da conta” para ver as suas curtidas. É o modo aberto a todo mundo.
  • Conectar com o Spotify (modo Conectar). Você entra com a sua conta e vê na hora os tops dos últimos meses. Ainda está em fase de testes no Spotify: por enquanto, só funciona para contas convidadas.
  • Demo. Tudo inventado, para conhecer o Encore sem enviar nada e sem entrar com conta.

Privacidade, sem letra miúda

  • Seu arquivo é lido aqui, no seu aparelho, e não é enviado para lugar nenhum.
  • Sem cadastro, sem rastreamento e sem guardar as suas músicas com a gente.
  • No modo Conectar, a sua conexão com o Spotify fica salva neste navegador, trancada de um jeito que só o Encore abre. O que vem da sua conta fica só nesta aba.
  • Os cartazes também são feitos no aparelho. O “Nome no cartaz” só aparece na imagem.
  • Os detalhes estão na política de privacidade.

O Encore não é afiliado ao Spotify.

Para quem desenvolve

Stack: Next.js 16 (App Router, Turbopack) · React 19 · TypeScript 6 (strict) · Tailwind CSS 4 · next-intl (PT-BR e EN) · Zod 4 · Vitest 5 · Playwright + axe. Deploy na Vercel. Sem banco. O plano completo está em docs/projeto/.

Estado: MVP completo (Sprints 0–7: upload, demo, Conectar, cards e auditoria de segurança) e no ar em modo Upload + Demo. A Iteração 8b trouxe os cartazes Músicas e Mix, a logo nova e os textos em linguagem do dia a dia; a Sprint 8 segue com a validação e o Conectar em produção (veja docs/projeto/PROGRESSO.md). Contrato do BFF: docs/api.md.

Por baixo, os três modos funcionam assim:

  • Upload: o .zip do “Histórico de streaming estendido” é processado num Web Worker, no navegador, sem nenhuma requisição com o conteúdo.
  • Conectar: login OAuth no Spotify (até 5 contas na allowlist do Development Mode); um BFF mínimo e sem estado guarda os tokens só num cookie JWE HttpOnly.
  • Demo: dados fictícios e determinísticos, sem rede e sem login.

Pré-requisitos

FerramentaVersãoObservação
Node.js24.x (.nvmrc, engines)O CI e a Vercel usam Node 24; com outra major o pnpm avisa (Unsupported engine).
pnpm10.34.5 (packageManager)Via Corepack (abaixo). A Vercel ainda não suporta pnpm 11+.

O jeito mais simples de ter a versão certa do pnpm é o Corepack, que vem com o Node 24 e lê o campo packageManager do package.json:

corepack enable        # uma vez por máquina; depois, `pnpm` já roda a 10.34.5 neste projeto
pnpm --version         # 10.34.5

Sem Corepack: npm install -g pnpm@10.34.5.

Como rodar localmente

git clone <url-do-repositório> encore && cd encore
pnpm install
cp .env.example .env.local   # pode ficar vazio: Upload e Demo funcionam sem credenciais
pnpm dev                     # http://127.0.0.1:3000 (redireciona para /pt-BR ou /en)

Use http://127.0.0.1:3000, não localhost: o Spotify só aceita redirect URI de loopback com IP literal, e o cookie de sessão depende da mesma origem.

O “seed” local é o modo Demo (gerador determinístico, sem banco e sem rede). Não há credenciais de demonstração: o Demo não exige login.

Telas do frontend

RotaO quê
/{locale}Landing: os 3 modos (Conectar desabilitado sem credenciais), selo de privacidade
/{locale}/onboardingComo pedir o histórico estendido ao Spotify + lembrete .ics gerado no navegador
/{locale}/uploadEnvio do .zip/.json (Web Worker, nada sai do aparelho) e dashboard do histórico
/{locale}/demoDashboard com histórico fictício, com as abas “Visão Upload” e “Visão Conectar”
/{locale}/privacyPolítica de privacidade (LGPD): o que é tratado, onde, por quanto tempo, como revogar
/{locale}/connectModo Conectar: entrada/login, erros do OAuth e dashboard ao vivo (tops, tendências…)

O botão Compartilhar (Upload, Demo e Conectar) abre o modal de cards: Line-up, Músicas, Mix ou Básico, Stories 9:16 ou Quadrado 1:1, com o período/janela selecionado no dashboard (Músicas e Mix ficam desabilitados quando o período não tem músicas). A imagem (PNG) é gerada no navegador (Web Worker com satori + resvg-wasm, carregados só nesse toque) e compartilhado pela Web Share API quando o navegador aceita arquivos; senão, é baixado.

{locale} é pt-BR ou en; o idioma troca pelo menu do cabeçalho sem perder o histórico carregado (o Dataset fica só na memória da aba; recarregar a página exige novo envio).

Para testar o upload sem o seu histórico real, use as fixtures sintéticas de tests/fixtures/ (valid-two-files.zip, ou os maliciosos zip-bomb.zip e path-traversal.zip).

Para rodar os testes E2E pela primeira vez, instale os navegadores do Playwright (Chromium e WebKit, este último para aproximar o Safari/iOS):

pnpm exec playwright install chromium webkit

Scripts

ComandoO que faz
pnpm devServidor de desenvolvimento em 127.0.0.1:3000
pnpm build / pnpm startBuild de produção / servidor de produção em 127.0.0.1:3000
pnpm lintESLint (flat config: Next + typescript-eslint), sem warnings
pnpm format / pnpm format:checkPrettier (com ordenação de classes do Tailwind)
pnpm typecheckGera os tipos de rota do Next e roda tsc --noEmit
pnpm testVitest (unit/integração em Node e componentes em jsdom)
pnpm test:coverageVitest com cobertura (meta ≥ 80% em src/domain)
pnpm test:e2ePlaywright (Chromium + WebKit) + axe; faz build + start se preciso
pnpm fixturesRegenera tests/fixtures/ (zips sintéticos válidos e maliciosos)
UPDATE_CARD_SNAPSHOTS=1 pnpm testRegrava as referências visuais dos cards em tests/snapshots/cards/
pnpm benchBenchmark do upload: ~50 MB sintéticos (meta < 10 s) e troca de período

Variáveis de ambiente

Catálogo completo e comentado em .env.example; validação com Zod em src/server/env.ts (por ambiente na Vercel: veja “Deploy na Vercel”). Resumo:

VariávelObrigatóriaPara quê
SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REDIRECT_URINão (as três juntas)Modo Conectar. Sem elas, o botão fica desabilitado
SESSION_SECRETSe houver Conectar32 bytes em base64url para o cookie JWE
SESSION_SECRET_PREVIOUSNãoRotação do segredo de sessão
NEXT_PUBLIC_SITE_URLEm produçãoURL canônica (local: http://127.0.0.1:3000)
NEXT_PUBLIC_REPO_URLNãoLink “veja o código” do selo de privacidade (só https://)
NEXT_PUBLIC_PRIVACY_CONTROLLER, NEXT_PUBLIC_PRIVACY_CONTACTEm produçãoControlador e e-mail de contato exibidos em /privacy (LGPD)
SPOTIFY_API_BASE, SPOTIFY_ACCOUNTS_BASENãoSó testes: mock do Spotify em loopback (proibidas na Vercel)

Configuração parcial do Spotify (ex.: só o Client ID) falha no next build e na inicialização com a lista das variáveis que faltam (o schema fica em src/server/env-schema.ts, que o next.config.ts também chama). Nunca commite .env.local (já está no .gitignore).

Gerar um SESSION_SECRET:

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Cadastrar o app no Spotify (modo Conectar)

  1. Acesse o Spotify Developer Dashboard com a conta do dono do app. Desde 2025, apps em Development Mode exigem que o dono tenha Spotify Premium.
  2. Create app: nome “Encore”, uma descrição curta, e marque Web API.
  3. Em Redirect URIs, cadastre exatamente:
    • local: http://127.0.0.1:3000/api/auth/callback
    • staging e produção: https://<domínio>/api/auth/callback (veja “Deploy na Vercel”)
  4. Copie o Client ID e o Client Secret para o .env.local (SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET), defina SPOTIFY_REDIRECT_URI com a URI local acima e gere o SESSION_SECRET.
  5. Em User Management, adicione as contas que podem usar o Conectar: em Development Mode o limite é de 5 usuários (o autor + até 4), cada um com nome e e-mail da conta Spotify.
  6. Reinicie o pnpm dev. A página inicial passa a mostrar o Conectar como disponível.

Escopos pedidos (mínimos): user-top-read, user-read-recently-played, user-library-read.

Testar o login real em http://127.0.0.1:3000

  1. Faça os passos acima (app no dashboard, redirect URI http://127.0.0.1:3000/api/auth/callback, sua conta em User Management) e preencha o .env.local:

    SPOTIFY_CLIENT_ID=<client id>
    SPOTIFY_CLIENT_SECRET=<client secret>
    SPOTIFY_REDIRECT_URI=http://127.0.0.1:3000/api/auth/callback
    SESSION_SECRET=<saída do comando node acima>
  2. pnpm dev e abra http://127.0.0.1:3000/pt-BR/connect (use 127.0.0.1, não localhost: os cookies vivem na origem da redirect URI; se abrir localhost, o login te leva para 127.0.0.1).

  3. Clique em Entrar com o Spotify, autorize e volte para a mesma página, que passa a mostrar o dashboard do Conectar (“Oi, …”): janelas de 4 semanas / 6 meses / 1 ano, top artistas e músicas, tendências, tocadas recentemente, gêneros e a varredura de curtidas (“Descobrir”).

  4. Na mesma aba, abra as rotas do BFF para ver os dados reduzidos (JSON): /api/spotify/me, /api/spotify/top?type=artists&range=short_term, /api/spotify/top?type=tracks&range=long_term, /api/spotify/recent, /api/spotify/saved?offset=0.

  5. Nas DevTools (Application → Cookies) o cookie encore_session é HttpOnly e SameSite=Lax, com validade de 30 dias. Localmente ele sai sem Secure e sem o prefixo __Host-, porque o Safari descarta cookies Secure em HTTP e o Chrome rejeita __Host- fora de HTTPS; em produção (HTTPS) o nome é __Host-encore_session, com Secure (detalhes em docs/api.md).

  6. No avatar do cabeçalho, Sair (com confirmação): o cookie some, o cache da aba (TanStack Query + sessionStorage) é apagado e /api/spotify/me passa a responder 401 {"error":{"code":"UNAUTHENTICATED"}}.

Casos para conferir: cancelar no Spotify volta com “Autorização cancelada”; uma conta fora do User Management volta com a tela “Este app ainda está em modo de teste” (?error=not_allowlisted), que explica o limite de 5 contas e oferece Upload e Demo.

Testar o fluxo sem conta do Spotify (mock local)

scripts/spotify-mock-server.ts simula o Accounts e a Web API com dados fictícios (confere o PKCE e rotaciona o refresh token). Em dois terminais:

node scripts/spotify-mock-server.ts     # http://127.0.0.1:4010

No .env.local (credenciais quaisquer, porque o mock não as confere contra nada real):

SPOTIFY_CLIENT_ID=mock
SPOTIFY_CLIENT_SECRET=mock
SPOTIFY_REDIRECT_URI=http://127.0.0.1:3000/api/auth/callback
SESSION_SECRET=<32 bytes em base64url>
SPOTIFY_ACCOUNTS_BASE=http://127.0.0.1:4010
SPOTIFY_API_BASE=http://127.0.0.1:4010/v1

Depois pnpm dev (ou pnpm build && pnpm start) e siga os passos 2–6 acima. Apague as duas linhas SPOTIFY_*_BASE para voltar ao Spotify real.

Variáveis do mock: MOCK_SPOTIFY_DENY=1 (simula “cancelar”), MOCK_SPOTIFY_FORBIDDEN=1 (conta fora da allowlist), MOCK_SPOTIFY_EXPIRES_IN=30 (força refresh a cada chamada), MOCK_SPOTIFY_SAVED_TOTAL=1200 (biblioteca maior) e MOCK_SPOTIFY_SAVED_DELAY_MS=300 (atraso por página de curtidas, para ver o progresso da varredura). As capas do mock (i.scdn.co/image/mock…) não existem de verdade; o dashboard mostra a inicial no lugar.

E2E do Conectar

O pnpm test:e2e sobe três servidores, em ordem (playwright.config.ts): o app em :3000 sem credenciais (faz o build), o mock do Spotify em :4010 e o mesmo build em :3100 com o Conectar ligado contra o mock (SESSION_SECRET descartável, só de teste). e2e/connect-live.spec.ts faz login → dashboard → trocar janela → varredura → logout (cache limpo), mais axe, 360 px, QUOTA e 401. Com o pnpm 10 via Corepack, o pnpm test:e2e sobe os três sozinho também no Windows. Para depurar, dá para subi-los à mão (os servidores já de pé são reaproveitados):

pnpm build
pnpm start                                   # :3000
node scripts/spotify-mock-server.ts          # :4010
SPOTIFY_CLIENT_ID=mock-client SPOTIFY_CLIENT_SECRET=mock-secret   SPOTIFY_REDIRECT_URI=http://127.0.0.1:3100/api/auth/callback   SESSION_SECRET=e2e-only-not-a-secret-000000000000000000000   SPOTIFY_ACCOUNTS_BASE=http://127.0.0.1:4010 SPOTIFY_API_BASE=http://127.0.0.1:4010/v1   NEXT_PUBLIC_SITE_URL=http://127.0.0.1:3100   node node_modules/next/dist/bin/next start --hostname 127.0.0.1 --port 3100

Marca do Spotify

Os logos em public/brand/ são os arquivos oficiais, sem alteração, das Spotify Design Guidelines (developer.spotify.com/documentation/design): Full_Logo_White_RGB.svg do pacote 2024-spotify-full-logo.zip (→ spotify-full-logo-white.svg) e Primary_Logo_White_RGB.svg do pacote 2024-spotify-logo-icon.zip (→ spotify-icon-white.svg), baixados em 2026-09-24. Só aparecem junto de dados vindos da API do Spotify (modo Conectar), nunca no Upload nem no Demo.

Deploy na Vercel

O deploy é sem configuração e sem vercel.json: a Vercel detecta o Next.js, instala com o pnpm 10 (pelo pnpm-lock.yaml + packageManager), usa o Node 24 (pelo engines.node: 24.x) e roda pnpm build. Os cabeçalhos de segurança já vêm do proxy.ts (CSP com nonce) e do next.config.ts; não há rewrite, redirect, cron nem região que precise de vercel.json.

O next build valida as variáveis de ambiente (src/server/env-schema.ts, chamado pelo next.config.ts). Configuração inválida falha o build com a lista dos nomes que faltam ou estão errados (nunca os valores), e o deployment anterior continua no ar.

1. Importar o repositório

  1. Em vercel.com/new, Import Git Repository e escolha este repositório (dê ao app da Vercel no GitHub acesso só a ele).
  2. Framework Preset: Next.js (detectado). Não ligue nenhum Override em Build, Install ou Output. Root Directory: a raiz.
  3. Antes do primeiro deploy, cadastre as variáveis de produção (passo 3). Sem elas, o build de produção falha de propósito.
  4. Depois do deploy, confira no log do build a linha do pnpm (pnpm@10.x) e a do Node (24.x). Opcional: se o pnpm do log for anterior à 10.34.5, crie a variável ENABLE_EXPERIMENTAL_COREPACK=1 (todos os ambientes). Assim a Vercel usa, via Corepack, exatamente a versão do packageManager.

2. Ambientes

AmbienteBranchURLConectarProteção
Produçãomainhttps://<projeto>.vercel.app (ou domínio próprio)SimPública
StagingstagingURL fixa do branch: https://<projeto>-git-staging-<escopo>.vercel.appSim (redirect URI cadastrada no Spotify)Deployment Protection
Previews (PRs etc.)qualquer outrodinâmica, uma por deploymentNão (sem variáveis do Spotify)Deployment Protection
  1. Crie o branch: git switch -c staging && git push -u origin staging. A URL fixa aparece em Deployments → deployment do staging → Domains (a que tem -git-staging-). Para uma URL mais curta, em Settings → Domains adicione um domínio e ligue-o ao branch staging.
  2. Settings → Deployment Protection → Vercel Authentication: ligada, em Standard Protection (protege todos os previews, inclusive o staging, e deixa o domínio de produção público). Quem abre o staging precisa entrar com uma conta Vercel com acesso ao projeto (no Hobby, só 1 usuário externo) ou usar um Shareable Link do deployment.
  3. Settings → Git: confira que o Production Branch é main. Proteja o main no GitHub exigindo os jobs do CI (veja docs/projeto/relatorio-seguranca.md, seção 9).

3. Variáveis de ambiente

Em Settings → Environment Variables. Para o staging, escolha Preview e, em “Select a custom Preview branch”, o branch staging: variáveis de um branch sobrescrevem as de Preview geral. Não crie as variáveis do Spotify em Preview geral: é a ausência delas que desliga o Conectar nos previews dinâmicos (a redirect URI deles não está cadastrada no Spotify).

VariávelProduçãoPreview do stagingPreviews dinâmicosObservação
NEXT_PUBLIC_SITE_URLObrigatória: https://<produção>Recomendada: https://<staging>Deixe vaziaSó HTTPS em deploys
NEXT_PUBLIC_PRIVACY_CONTROLLERObrigatória: Enzo TerraRecomendada: Enzo TerraOpcionalNome do controlador, exibido em /privacy
NEXT_PUBLIC_PRIVACY_CONTACTObrigatória: enzoterra18@gmail.comRecomendada: enzoterra18@gmail.comOpcionalE-mail de contato do titular, exibido em /privacy
SPOTIFY_CLIENT_IDSimSim (o mesmo app)NãoAs três do Spotify vão juntas, ou nenhuma
SPOTIFY_CLIENT_SECRETSim, SensitiveSim, SensitiveNãoSó no servidor
SPOTIFY_REDIRECT_URIhttps://<produção>/api/auth/callbackhttps://<staging>/api/auth/callbackNãoIdêntica à cadastrada no Spotify
SESSION_SECRETSim, Sensitive, só de produçãoSim, Sensitive, outro valorNãoObrigatória quando o Conectar está configurado
SESSION_SECRET_PREVIOUSSó durante uma rotaçãoSó durante uma rotaçãoNãoO segredo anterior, para não derrubar sessões
NEXT_PUBLIC_REPO_URLOpcionalOpcionalOpcionalSó https://
SPOTIFY_API_BASE, SPOTIFY_ACCOUNTS_BASENuncaNuncaNuncaSó mocks locais; o build de deploy falha com elas

VERCEL_ENV é definida pela própria Vercel (production ou preview) e liga as regras de deploy do env.ts: HTTPS obrigatório, mocks proibidos, segredo dos e2e recusado e, em produção, as três primeiras linhas da tabela obrigatórias.

Os valores de privacidade acima foram informados pelo cliente em 2026-09-25 e existem só no painel da Vercel: o código e o .env.example não têm valor padrão para eles.

Gere um SESSION_SECRET por ambiente (rode o comando duas vezes, cole direto no painel e não guarde a saída em outro lugar):

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Rotação: copie o valor atual para SESSION_SECRET_PREVIOUS, gere um novo SESSION_SECRET e faça Redeploy. Apague o SESSION_SECRET_PREVIOUS depois de 30 dias (a validade máxima da sessão).

Mudou alguma variável? Ela só vale para deployments novos: use Redeploy no último deployment. As NEXT_PUBLIC_* entram no JavaScript no build, então também só mudam com um novo build.

4. Spotify Developer Dashboard

  1. No app “Encore”, Edit Settings → Redirect URIs, deixe exatamente:
    • https://<produção>/api/auth/callback
    • https://<staging>/api/auth/callback (a URL fixa do branch, nunca a de um deployment)
    • http://127.0.0.1:3000/api/auth/callback (desenvolvimento local)
  2. User Management: até 5 contas (o dono + 4), com nome e e-mail da conta Spotify de cada uma.
  3. A conta dona do app precisa ter Spotify Premium. Desde fevereiro de 2026, sem Premium o Spotify para o app em Development Mode e todo login volta 403, que o Encore mostra como a tela “Este app ainda está em modo de teste” (ela cita essa causa).

5. Conferir o deploy

curl -s https://<produção>/api/health                 # {"status":"ok"}
curl -sI https://<produção>/pt-BR                     # CSP com nonce, HSTS, nosniff, COOP...
curl -sI https://<produção>/api/spotify/me            # 401, default-src 'none', CORP same-origin
curl -s -o /dev/null -w "%{http_code}\n" https://<produção>/pt-BR/nao-existe   # 404

Depois, no navegador: /pt-BR/privacy mostra o controlador e o contato, e o Conectar faz login e logout com uma conta da allowlist (em produção e no staging).

6. Rollback

Deploys da Vercel são imutáveis e não há banco nem migração, então voltar é instantâneo:

  1. Na visão geral do projeto, no quadro do deployment de produção, clique em Instant Rollback (ou, em Deployments, ⋮ → Instant Rollback). No Hobby, só dá para voltar ao deployment de produção imediatamente anterior.
  2. O deployment volta com as variáveis de ambiente que ele tinha: mudanças posteriores no painel não se aplicam a ele.
  3. Depois de um rollback, a Vercel para de publicar os pushes em main sozinha. Corrigido o problema, use Undo Rollback (ou promova um deployment novo) para reativar a publicação automática.

Antes de cada go-live, anote qual é o deployment de produção atual (o alvo do rollback).

Estrutura

app/[locale]/        páginas (RSC) em /pt-BR e /en
app/api/             BFF (auth e proxy do Spotify) + /api/health
proxy.ts             nonce de CSP, cabeçalhos de segurança e negociação de idioma
src/domain/          lógica pura (histórico, Dataset, stats, demo), testável em Node
src/server/          env validado, cabeçalhos de segurança, sessão JWE, cliente Spotify, logger
src/components/      layout (cabeçalho, rodapé, selo), UI base e a logo (`brand/`)
src/features/        UI por área
src/workers/         Web Workers (processamento do upload e geração dos cards), sem rede
src/config/          dados públicos e fixos (contatos do autor, `author.ts`)
src/i18n/            rotas e mensagens do next-intl
e2e/                 testes Playwright + axe
scripts/             fixtures (`pnpm fixtures`) e mock local do Spotify
tests/               setup, stubs, fixtures, mocks do Spotify e benchmark (`tests/perf`)
docs/api.md          contrato do BFF (rotas, parâmetros, erros, TTLs, OAuth)
docs/readme/         logo e screenshots deste README (demo, dados fictícios)

Alias de import: @/… aponta para src/….

Segurança e CI

  • Cabeçalhos em toda resposta (HSTS, nosniff, Referrer-Policy: no-referrer, Permissions-Policy, COOP) e, nas páginas, CSP com nonce por requisição (strict-dynamic, frame-ancestors 'none'). Confira com curl -I http://127.0.0.1:3000/pt-BR.
  • 'wasm-unsafe-eval' só na CSP dos scripts de Web Worker (/_next/static, via next.config.ts), com connect-src data:: o WASM dos cards roda no worker e nenhum worker faz requisição. As páginas não liberam WASM; o connect-src delas é 'self' + https://i.scdn.co (capa do card do Conectar, ADR 9).
  • GitHub Actions (.github/workflows/ci.yml), com permissions: contents: read e actions fixadas por SHA: lint + formatação, tipos, testes com cobertura, build, e2e + axe (Chromium e WebKit; Conectar contra o mock local), pnpm audit --audit-level=high e dependency-review nos PRs.
  • Dependabot semanal e agrupado (npm e GitHub Actions), com espera de 3 dias.
  • pnpm: scripts de install bloqueados (allowBuilds) e minimumReleaseAge de 1 dia em pnpm-workspace.yaml.

Autor

Feito por Enzo Terra. Fale comigo:

Os mesmos contatos ficam no rodapé de todas as páginas do site:

Rodapé do Encore: "Feito por Enzo Terra" e, ao lado, "Fale comigo" com os links de e-mail, GitHub e site pessoal

Logo MemoryGarden

MemoryGarden

Digital memorials to honor those who passed

A platform where families create memorials with a biography, photos, a timeline and other important information. Friends pay tribute by purchasing digital flower wreaths, and 90% of the value goes to a charity chosen by the family. Funeral homes display the memorials on TVs and tablets, showing who paid tribute without exposing amounts. Everything designed with care, conveying hope and calm in a difficult moment.

Next.jsNestJSPostgreSQLTypeScript
private code

Logo do MemoryGarden

MemoryGarden

Homenagens virtuais para momentos que merecem ser lembrados.


O MemoryGarden (Jardim de Memórias) é uma plataforma de memoriais digitais. A família ou a funerária cria uma página para quem partiu, com foto, biografia, linha do tempo e as informações do velório e do sepultamento. Amigos e parentes prestam homenagem comprando uma coroa de flores digital com uma mensagem, e a maior parte do valor vai automaticamente para uma instituição de caridade escolhida pela família.

Durante a cerimônia, as homenagens aparecem em tempo real numa página de slides exibida em TVs e tablets do velório, mostrando quem homenageou e a mensagem, sem nunca expor valores.

Todo o visual foi pensado com delicadeza: um momento difícil pede calma, esperança e nenhuma fricção.

✨ Como funciona

  1. A funerária (ou a família) cadastra o homenageado: foto, datas, biografia, linha do tempo, local e horário do velório e do sepultamento, e a instituição que receberá as doações.
  2. A página do memorial é compartilhada (/nome-do-homenageado). Qualquer pessoa acessa, lê a história e escolhe enviar uma homenagem.
  3. A homenagem é uma coroa digital: a pessoa escolhe o modelo da coroa, escreve a mensagem e paga via PIX (QR Code ou copia e cola).
  4. O pagamento é confirmado sozinho: o gateway avisa o sistema por webhook e a tela de quem pagou muda para “sucesso” em segundos, sem nenhuma ação manual.
  5. O repasse para a instituição é automático: assim que o pagamento é confirmado, o valor da doação é transferido via PIX para a chave da instituição.
  6. Os slides atualizam em tempo real (/nome-do-homenageado/slides), feitos para ficarem rodando numa TV durante o velório.

👥 Perfis

PerfilO que faz
PúblicoVisita memoriais, envia homenagens e recebe o comprovante da doação
Usuário (família)Cria e edita memoriais, acompanha as homenagens recebidas
Funerária (gestor)Gerencia os homenageados da funerária e a sua equipe
Funerária (equipe)Cadastra e atualiza homenageados no dia a dia
AdministradorInstituições parceiras, modelos de coroa, funerárias, usuários, relatórios e configurações gerais

💸 Pagamento e repasse automático

O fluxo de dinheiro é a parte mais sensível do sistema, e foi desenhado para ser seguro e à prova de falhas:

Pessoa compra a coroa → paga o PIX (QR Code gerado pelo gateway)
→ gateway confirma por webhook
→ sistema confere o status direto na API do gateway (double-check)
→ doação marcada como paga
→ repasse via PIX para a chave da instituição
  • Double-check no webhook: o sistema nunca confia só no corpo do webhook. Ele consulta o status real do pagamento na API do gateway antes de marcar a doação como paga, o que impede que um webhook forjado “pague” uma homenagem.
  • Token no webhook: chamadas sem o token secreto do gateway são recusadas com 401.
  • Validação antes de cobrar: se a instituição não tem chave PIX, a cobrança nem chega a ser criada.
  • Detecção automática do tipo da chave PIX (e-mail, CPF, CNPJ, telefone ou aleatória).
  • Falha no repasse não perde o pagamento: se a transferência falhar, a doação continua paga, o repasse fica marcado como transfer_failed e o erro é registrado para intervenção manual.
  • Rastreabilidade: cada doação guarda o código do pedido, o ID da cobrança, o ID da transferência e o status de cada etapa.
  • Rate limiting na criação de cobranças (10 por minuto por IP).

🛠 Painel administrativo

  • Relatórios de homenagens e doações, com exportação.
  • Taxa configurável: percentual destinado à caridade (padrão de 90%) ou taxa fixa por coroa.
  • Métodos de pagamento ligados e desligados pelo painel, com tempo de expiração do PIX e do pedido.
  • E-mail transacional (SMTP configurável) para confirmações e recuperação de senha.
  • Modo manutenção ativado com um clique.
  • Logs de tudo o que acontece no sistema.

🧱 Stack

CamadaTecnologia
FrontendNext.js 16 + React 19 + Tailwind CSS 4
BackendNestJS 11 + TypeORM
Banco de dadosPostgreSQL (com migrations versionadas)
AutenticaçãoJWT + Passport, com papéis (RBAC)
PagamentosPIX via gateway Asaas (cobrança + transferências)
InfraestruturaDocker, Docker Compose e Nginx com TLS
CI/CDPipeline com testes, análise de segurança, build e scan das imagens

📁 Estrutura

/
├── frontend/          # Next.js (App Router)
│   └── src/app/
│       ├── (public)/      # landing, memorial, envio de homenagem, slides, login
│       ├── (protected)/   # painel da família e da funerária
│       └── (admin)/       # relatórios e configurações
├── backend/           # API NestJS
│   └── src/
│       ├── honorees/      # homenageados + linha do tempo + mídias
│       ├── wreaths/       # modelos de coroa
│       ├── donations/     # homenagens/doações
│       ├── payments/      # PIX, webhook e repasse
│       ├── institutions/  # instituições de caridade
│       ├── managers/      # funerárias
│       ├── reports/       # relatórios
│       └── settings/      # configurações do sistema
└── nginx.conf         # proxy reverso e TLS

🔐 Segurança

  • HTTPS com TLS 1.2/1.3 e HSTS
  • Autenticação JWT com papéis (usuário, gestor e equipe de funerária, administrador) e guards por rota
  • Helmet e rate limiting no backend, incluindo limite por slug nas páginas públicas
  • Validação de entrada com class-validator em todos os DTOs
  • Senhas com bcrypt
  • Exclusão lógica (soft delete) de homenageados e coroas
  • Conformidade com a LGPD

🚀 Rodando localmente

Pré-requisitos: Node.js 20+, Docker e PostgreSQL (local ou via Docker).

# variáveis de ambiente
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env

# dependências (raiz, backend e frontend)
npm run install:all

# banco: migrations e seed
cd backend
npm run migration:run
npm run seed
cd ..

# backend (3001) e frontend (3000) juntos
npm run dev

Acesse http://localhost:3000. Para testar o webhook de pagamento localmente, exponha o backend com um túnel (ex.: ngrok http 3001) e cadastre a URL /payments/webhook no painel do gateway, em modo sandbox.

📄 Licença

Projeto privado — todos os direitos reservados.

Logo Achei Meu Pet

Achei Meu Pet

Community network reuniting lost pets with their owners

Brings together in one place what today gets lost across dozens of WhatsApp groups. Anyone can see lost and found pets nearby, no sign-up needed. When a lost pet is posted, the system automatically looks for similar found pets in the area and notifies the owner. Conversations happen inside the platform without exposing phone numbers, which protects against scams, and every pet gets a QR-code poster to print and share. Nobody's address is ever shown and pet locations are always approximate.

DjangoPostGISReact 19CeleryDocker
private code
Logo Achei Meu Pet

Achei Meu Pet

Plataforma web comunitária para reunir animais perdidos com seus donos, usando a própria vizinhança como rede de busca — pensada para o Mato Grosso do Sul e desenhada mobile-first, para quem cadastra na rua, com uma mão e pressa.

Projeto full-stack completo (backend geoespacial, SPA, infra, CI e testes), construído do zero em sprints. A visão, o modelo de dados e o detalhamento de cada sprint estão em PLANO_ACHEI_MEU_PET.md.


O problema e o objetivo

Quando um animal some, o tempo é tudo — e a informação está fragmentada em dezenas de grupos de WhatsApp. O Achei Meu Pet centraliza isso num feed regional e tenta fechar o ciclo perdido → encontrado com o mínimo de fricção.

A métrica-norte (North Star) do produto é o tempo médio até o primeiro contato qualificado: quanto tempo, depois de um pet ser publicado, até alguém abrir uma conversa sobre ele. O sistema instrumenta essa métrica de ponta a ponta.

Princípios de produto (inegociáveis)

  1. Ver antes de cadastrar — o visitante anônimo vê o feed na hora, sem login.
  2. Login só onde há razão técnica — exigido para publicar (filtro regional, throttling, responsabilização), nunca como pedágio.
  3. Privacidade por padrão — o endereço do usuário nunca é exposto; localização de pets é sempre aproximada.
  4. Clareza acima de completude — cada tela tem um trabalho só.

Principais funcionalidades

  • Feed público regional — qualquer pessoa vê animais encontrados perto de si, sem login, filtrando por raio real (km) e por tipo (perdidos / encontrados / todos).
  • Publicação de perdidos e encontrados — 1–6 fotos, espécie, porte, cores, sexo, data, situação e localização aproximada, com upload resiliente.
  • Match automático geoespacial — publicar um perdido dispara, em background, a comparação com os encontrados compatíveis na região e notifica o dono.
  • “Reconheci este animal” → contato intermediado — abre uma conversa sem expor telefone/e-mail de nenhuma das partes.
  • Cartaz com QR Code — gera um cartaz (PDF/PNG) do pet perdido para impressão e compartilhamento, com QR apontando para a página pública (sem contato pessoal).
  • Compartilhamento para WhatsApp com card rico (Open Graph).
  • Alertas regionais — o usuário define um raio e é avisado de novos animais na sua área (com opt-out).
  • Ciclo de vida do post — status (ativo/resolvido/expirado), expiração automática em 30 dias com avisos em D-3/D-1 e renovação em 1 clique.
  • Perfil completo + LGPD — meus posts, notificações, preferências, exportação e exclusão de dados.

Destaques de engenharia

O que torna o projeto interessante além da lista de telas:

🔒 Privacidade por design (não como recurso, como arquitetura)

  • A coordenada real de um pet e o endereço do usuário ficam em campos que a API pública nunca serializa — só o rótulo aproximado (bairro) sai.
  • EXIF removido de toda foto no upload (a imagem é re-encodada), então o GPS embutido na câmera nunca vaza.
  • Contato sempre intermediado — mensageria interna, mitigando o golpe clássico do “achei seu pet”. E-mail/telefone são redigidos automaticamente nas mensagens.
  • Testes de segurança dedicados garantem essas invariantes (IDOR, vazamento de PII, upload malicioso) e rodam no CI a cada push.

🗺️ Geoespacial de verdade (PostGIS, não cálculo no app)

  • Filtro por raio com ST_DWithin sobre coluna geography indexada (GIST) — comparação em metros, sem trazer a tabela inteira para a aplicação.
  • Heurística de matching com score (espécie + porte + janela de datas + sobreposição de cores + distância), persistida e idempotente (não re-notifica o mesmo par), rodando assíncrona no Celery.

🎨 Identidade com intenção

  • O contador “X dias procurando”, em âmbar, é o elemento de assinatura: ele transforma um dado frio (data do sumiço) em tensão que move a comunidade.
  • Decisão de cor justificada: urgência em âmbar/terracota, nunca vermelho (vermelho amplifica pânico); verde reservado só para resolução.

🛡️ Decisões de stack defensáveis

  • Auth por sessão (cookie httpOnly + SameSite + CSRF) em vez de JWT no localStorage — imune a roubo de token por XSS.
  • Sem SSR/Next.js: o único caso que exigiria SSR (cards Open Graph do WhatsApp) é resolvido por um endpoint dedicado que server-renderiza só as meta tags.
  • Cloudflare R2 para imagens (sem egress fee), via interface S3-compatível.

📈 Observabilidade e operação

  • Métrica North Star instrumentada (persistida por pet + endpoint agregado + log).
  • Healthcheck (banco + Redis), logs estruturados em JSON opcionais, integração com Sentry, backup do Postgres com runbook de restauração testado.

✅ Qualidade levada a sério

  • ~95 testes de backend (regras de negócio, autorização, segurança) + testes de frontend (componentes, acessibilidade com axe).
  • E2E (Playwright) dos fluxos críticos e teste de carga (k6) do feed e da publicação, com thresholds de latência/erro.
  • CI (GitHub Actions) com lint, format, testes e build obrigatórios.

Stack e arquitetura

CamadaTecnologia
BackendDjango 5 + DRF + GeoDjango/PostGIS (Python 3.12)
BancoPostgreSQL 16 + PostGIS
FrontendReact 19 + Vite + TypeScript, Tailwind v4, shadcn/ui
AssíncronoCelery + Redis (matching, e-mails, expiração)
StorageCloudflare R2 (S3-compatível)
EdgeCaddy (TLS automático, reverse proxy, headers de segurança/CSP)
InfraDocker Compose (single-host)
CI / ErrosGitHub Actions · Sentry
                 Internet
                    │
                 ┌──┴──┐
                 │Caddy│  TLS automático, reverse proxy, CSP
                 └──┬──┘
        ┌───────────┼───────────────┐
   app (SPA estático)        api (Django/gunicorn)
                                   │
                  ┌────────────────┼─────────────────┐
            PostgreSQL+PostGIS   Redis        Celery worker + beat
                                   │
                          Cloudflare R2 (imagens)

Modelo de dados (resumo)

User (com localizacao/endereco privados) · Pet + PetFoto (UUID, coordenada real indexada vs. rótulo exibível) · Match (auditável) · Conversa + Mensagem (contato intermediado) · Notificacao · PrimeiroContato (North Star). Detalhes no plano.


Estrutura do repositório

backend/    Django + DRF (API, settings base/dev/prod, Celery, ~95 testes)
frontend/   React 19 + Vite SPA (Tailwind v4, tokens de design, Playwright e2e)
infra/      Caddy (edge), backup/restore do Postgres, testes de carga k6
.github/    GitHub Actions (CI + E2E manual)

Rodando localmente

Pré-requisitos

Docker + Docker Compose, ou Python 3.12 + Node 24 para rodar fora de container.

Demonstração rápida (recomendado)

Sobe a stack completa em modo de desenvolvimento (HTTP puro, hot-reload, sem TLS) já populada com dados de exemplo — nenhum .env necessário:

docker compose -f docker-compose.local.yml up --build

Na subida, o comando manage.py seed_demo cria usuários, posts de perdidos e encontrados (com fotos geradas), matches, notificações e uma conversa intermediada. Login sugerido para explorar tudo:

e-mailsenha
ana@demo.acheimeu.petdemoAcheiPet1

O seed é idempotente: rode docker compose -f docker-compose.local.yml exec api python manage.py seed_demo quando quiser recriar os dados. E-mails (ex.: verificação, match) saem no console do container api.

Produção (Docker + Caddy/TLS)

cp backend/.env.example backend/.env   # ajuste SECRET_KEY etc.
cp .env.example .env
docker compose --env-file backend/.env up --build

Backend sem Docker

cd backend
python -m venv .venv && . .venv/Scripts/activate   # Windows
pip install -r requirements-dev.txt
cp .env.example .env
python manage.py migrate
python manage.py runserver        # http://localhost:8000/api/health

GeoDjango exige as libs GDAL/GEOS/PROJ instaladas no sistema.

Frontend sem Docker

cd frontend
npm install
npm run dev        # http://localhost:5173 (proxy /api -> :8000)

Testes e qualidade

# Backend
cd backend && pytest && ruff check . && black --check .

# Frontend (unit + componentes + acessibilidade)
cd frontend && npm test && npm run lint && npm run build

.github/workflows/ci.yml roda em cada push/PR (lint, testes, build). Os testes E2E rodam sob demanda em .github/workflows/e2e.yml. Nenhum merge sem CI verde.


Observabilidade e LGPD

  • Healthcheck: GET /api/health (banco + cache/Redis).
  • North Star (tempo até o primeiro contato qualificado): GET /api/metrics/north-star (staff) + log estruturado. Logs em JSON via LOG_FORMAT=json.
  • Exportação de dados: GET /api/auth/me/exportar. Exclusão: DELETE /api/auth/me.
  • Política de privacidade na rota /privacidade, linkada do cadastro.

Roadmap (pós-MVP)

Camada de IA, sempre como assistente e nunca como autoridade em casos sensíveis: moderação de imagens/texto com revisão humana, similaridade visual por embeddings, coleira QR preventiva, parceiros verificados (clínicas/ONGs) e mapa interativo com pontos ofuscados por jitter. Detalhes na §10 do plano.


Backup, restore e segredos

Scripts em infra/backup/; o procedimento de restauração — que deve ser testado, não só configurado — está em RESTORE_RUNBOOK.md. Nunca commitar .env; veja *.env.example para a lista de variáveis.

Logo Buraco Zero

Buraco Zero

Civic platform to map and manage pothole repairs

Citizens report a pothole in under 15 seconds, just by marking the spot on the map, Waze-style. Reports of the same pothole are grouped automatically, the city prioritizes and schedules repairs in batches, and every step is recorded until the repair is confirmed. A public dashboard shows residents what has been reported and what has already been fixed.

DjangoPostGISReactMapLibre
private code
Logo Buraco Zero

Buraco Zero

Plataforma colaborativa para mapear e gerir o conserto de buracos em vias públicas — do relato anônimo do cidadão à confirmação do conserto pela prefeitura, com transparência pública. Caso de uso: Campo Grande/MS.

Stack: Django 5 · DRF · GeoDjango/PostGIS · React 18 · Vite · MapLibre · Docker Qualidade: 141 testes automatizados (96 backend / 45 frontend) · revisão de segurança §9/OWASP · CI-ready Status: MVP completo (Sprints 0–7) · pronto para deploy


🖼️ Interface

Identidade visual “asfalto” — grafite escuro + âmbar de marca (Tailwind v4 + shadcn/ui). O app do cidadão é mobile-first: mapa em tela cheia, pins por forma + cor segundo o status, e relato de baixo atrito — posicionar o pino e confirmar.

App do cidadão — mapa com clusters por status    Cidadão posicionando o pino do buraco

Cidadão — mapa com clusters por status · arraste o pino até o buraco e confirme.

Painel da prefeitura (desktop), com a visão certa por papel:

Painel de gestão da prefeitura
Gestão — mapa, filtros e lista priorizada por urgência.
Conserto com staging
Conserto com staging — polígono, rua ou clusters → revisão.

Login da prefeitura
Login da prefeitura — acesso restrito por papel (JWT).

Capturas da stack de demonstração (compose.local.yml) com dados de exemplo em Campo Grande/MS.


🎯 O problema

Buracos em vias públicas são reportados de forma dispersa (redes sociais, telefone, ouvidoria), sem geolocalização agregada. A prefeitura não tem um canal único para visualizar, priorizar e acompanhar consertos. O Buraco Zero centraliza esse ciclo de vida em uma plataforma só, com três públicos:

PúblicoO que faz
👤 Cidadão (anônimo)Registra um buraco em < 15s, sem cadastro (UX referência: alerta do Waze). Foto opcional.
📊 Analista (prefeitura)Prioriza por urgência, filtra, avança status, exporta dados.
🛠️ Funcionário de campoMarca consertos (polígono/rua/clusters), revisa e confirma em lote.
🌐 PopulaçãoPainel público de transparência (consertos, tempo médio, por bairro).

🚀 Demo local em um comando

Pré-requisito: Docker + Docker Compose. Na raiz do repositório:

docker compose -f compose.local.yml up --build

Sobe a stack inteira (PostGIS + Django + PWA atrás do nginx), migra o banco e popula dados de demonstração automaticamente. Depois abra:

👉 http://localhost:8080

RotaO que ver
/App do cidadão: mapa com clusters coloridos por status, botão “Relatar buraco”.
/painelTransparência: métricas e recorte por bairro.
/adminPainel da prefeitura (login abaixo).

Credenciais de demonstração (papéis distintos mudam a experiência):

PapelLoginSenhaVê
Adminadmin@cg.ms.gov.brdemo12345gestão + conserto + arquivar
Analistaanalista@cg.ms.gov.brdemo12345mapa, lista, filtros, status, exportar
Campocampo@cg.ms.gov.brdemo12345conserto com staging (3 métodos)

Encerrar: docker compose -f compose.local.yml down -v.

Diferente do backend/docker-compose.yml (só backend, para desenvolvimento), o compose.local.yml entrega o sistema completo em uma URL, como em produção, mas sem TLS e com dados de exemplo.


✨ Funcionalidades

  • Registro de baixa fricção — o relato é persistido no instante da confirmação; a foto é um enriquecimento assíncrono que nunca bloqueia o fluxo crítico.
  • Deduplicação geoespacial — relatos do mesmo buraco (dentro de um raio) viram um único cluster, a unidade de gestão da prefeitura. Query PostGIS com índice espacial, não cálculo em Python.
  • Clustering visual — Supercluster agrupa pins por zoom, mantendo o mapa legível.
  • Máquina de status com transições validadas no servidor e histórico imutável (base das métricas de SLA).
  • Conserto com staging — o funcionário acumula consertos como rascunho, revisa (clusters em verde) e confirma; só então fecham, em uma transação atômica e idempotente. Três métodos: desenho de polígono, rua e seleção de clusters.
  • Transparência pública — total de consertos, tempo médio de resolução, consertos nos últimos 30 dias e recorte por bairro.
  • Exportação CSV/GeoJSON e arquivamento em lote de relatos expirados.
  • PWA instalável, com cache do último estado do mapa para visualização offline.

🧠 Destaques de engenharia

O que este projeto demonstra além de CRUD:

  • Geoespacial de verdade: GeoDjango + PostGIS com distance_lte/intersects sobre campos geography, índice GIST, e testes rodando contra PostGIS real (não SQLite — queries espaciais não têm equivalente e dariam falso verde).
  • Modelagem orientada ao domínio: o cluster (não o relato) é a entidade central; StatusHistory é append-only; MaintenanceZone nasce como rascunho.
  • Integridade transacional: o commit de consertos é tudo-ou-nada e idempotente (cluster já fechado entre o rascunho e o commit é ignorado sem erro) — coberto por teste que simula falha no meio e verifica o rollback.
  • Segurança desde o primeiro commit: rate limiting no endpoint anônimo, hash salgado de IP (LGPD), strip de EXIF nas fotos, JWT por papel com lockout no login, headers seguros e uma revisão §9/OWASP. Cada feature tem teste de permissão (401/403) e de caminho de abuso.
  • Testes como contrato: 141 testes cobrindo dedup, transições, staging, auth, filtros e os caminhos de risco — não cobertura cega.
  • Acessibilidade: status indicado por forma + cor (não só cor), alvos de toque de 44px, contraste WCAG AA, prefers-reduced-motion.

Um bug real encontrado e corrigido durante o desenvolvimento: o ?format= do endpoint de exportação colidia com a negociação de conteúdo do DRF, devolvendo 404 antes da view rodar — resolvido com URL_FORMAT_OVERRIDE=None. Documentado no código.


🏗️ Arquitetura

                    ┌─────────────────────────── nginx ───────────────────────────┐
   Cidadão ─────────│  /            → PWA (React/Vite, estático)                    │
   Prefeitura ──────│  /api, /admin → proxy → gunicorn + Django/DRF                 │
                    │  /static,/media → volumes                                    │
                    └───────────────────────────────┬──────────────────────────────┘
                                                     │
                                       ┌─────────────▼─────────────┐
                                       │   Django + GeoDjango       │
                                       │   (dedup, staging, auth,   │
                                       │    stats, jobs de cron)    │
                                       └─────────────┬─────────────┘
                                                     │
                                       ┌─────────────▼─────────────┐
                                       │   PostgreSQL 16 + PostGIS  │
                                       └───────────────────────────┘

Decisões registradas em DOCUMENTACAO_SISTEMA.md (a fonte de verdade do produto): por que cluster é a entidade central, dedup em duas camadas, staging no servidor, expiração como flag visual, e mais.


🧩 Stack e estrutura

CamadaTecnologias
BackendDjango 5, DRF, GeoDjango, simplejwt, django-ratelimit, Pillow
BancoPostgreSQL 16 + PostGIS 3.4
FrontendReact 18, Vite, TypeScript, Tailwind v4, shadcn/ui, MapLibre GL, Supercluster, TanStack Query
InfraDocker Compose, nginx, gunicorn, Let’s Encrypt, Sentry (opcional)
Testespytest + pytest-django (PostGIS real), Vitest + Testing Library, Locust
tapa-buraco/
├── backend/         Django + DRF + GeoDjango (API, painel, staging, jobs)
├── frontend/        PWA React (cidadão, analista, campo, painel público)
├── deploy/          produção: nginx+TLS, gunicorn, backups, Let's Encrypt
├── loadtest/        teste de carga (Locust) da superfície pública
├── compose.local.yml          demo da stack completa (este README)
├── DEPLOY.md                  guia de deploy e operação
├── SECURITY_REVIEW.md         revisão §9 + OWASP Top 10
└── DOCUMENTACAO_SISTEMA.md    especificação do produto (fonte de verdade)

🛠️ Desenvolvimento

# Backend (roda via Docker por causa do GeoDjango/GDAL)
cd backend && cp .env.example .env && docker compose up -d --build
docker compose run --rm web pytest            # 96 testes (PostGIS real)

# Frontend
cd frontend && npm install && npm run dev      # http://localhost:5173
npm test                                       # 45 testes

Detalhes em backend/README.md e frontend/README.md.


🚢 Produção

Deploy num VPS único via Docker Compose (nginx + TLS + gunicorn + PostGIS + backups + Sentry) — passo a passo em DEPLOY.md. Segurança em SECURITY_REVIEW.md. Teste de carga em loadtest/.


🗺️ Roadmap (entregue por sprints)

SprintEntregaStatus
0–1Fundação + API pública de relatos (dedup geoespacial, EXIF strip)✅
2Frontend do cidadão (mapa + relato) PWA✅
3Auth JWT + API admin (filtros, paginação, transições)✅
4Painel do analista (mapa + lista + filtros + detalhe + status)✅
5Conserto com staging (polígono/rua/clusters → revisão → commit)✅
6Exportação, painel público e expiração (jobs via cron)✅
7Endurecimento e deploy (TLS, backups, Sentry, carga, docs)✅
5.5Importação CSV de consertos (opcional; depende de OSM + pg_trgm)⏳

Projeto desenvolvido como estudo de uma plataforma cívica real, priorizando domínio geoespacial, integridade transacional, segurança e testes. Construído por sprints, cada uma entregando algo verificável e testado.

Logo Portal de Agência

Portal de Agência

White-label SaaS for digital marketing agencies

Built under contract: an admin panel for the agency and an exclusive client portal. Automatic Google Calendar sync, monthly performance reports with computed ROI, payments with PIX QR codes, a Google Drive media library and granular permissions. In production, actively used by an agency.

Laravel 11PHP 8.2PostgreSQLGoogle APIs
private code

Portal de Clientes - Agência de Marketing

Plataforma SaaS white-label para agências de marketing digital gerenciarem clientes, entregas, finanças e resultados, em um único lugar.


📌 Sobre o projeto

O Portal de Clientes é um sistema web desenvolvido em Laravel 11 para centralizar a gestão de clientes de uma agência de marketing digital. Ele oferece dois ambientes distintos: um painel administrativo para a equipe da agência e um portal exclusivo para os clientes.

A plataforma conecta-se à conta Google da agência para sincronizar automaticamente eventos de reuniões e entregas, permite o envio de relatórios mensais de performance com dados reais de campanhas e redes sociais, e oferece uma área financeira completa com geração de cobranças, QR Code PIX e controle de inadimplência.


✨ Funcionalidades

🔐 Acesso e Permissões

  • Sistema de autenticação com múltiplos perfis: super_admin, admin e client
  • Controle de permissões granular via Spatie Laravel Permission
  • Suporte a múltiplos usuários por empresa-cliente, cada um com acesso controlado à sua conta
  • Proteção anti-IDOR com UUIDs em todas as rotas de recursos sensíveis
  • Soft delete em usuários, clientes e registros financeiros

🗓️ Integração com Google Agenda

  • OAuth 2.0 com a conta Google da agência
  • Sincronização automática a cada 5 minutos via Laravel Scheduler + Cron Job
  • Eventos baixados e armazenados localmente para exibição no portal do cliente
  • Filtro inteligente de eventos por nome da empresa (case-insensitive)
  • Cada cliente vê apenas os eventos relevantes à sua conta

📊 Relatórios de Performance

  • Relatórios mensais com dados de tráfego pago, Instagram e metas
  • Campos disponíveis: investimento, faturamento, CPC, conversas, alcance, publicações, novos seguidores, views, visitas ao perfil
  • Cálculo automático de ROI (coluna gerada no banco)
  • Controle de versões do relatório com histórico de alterações
  • Fluxo de publicação em etapas: rascunho → publicado → arquivado
  • Exportação em PDF via DomPDF
  • Navegação por mês de referência diretamente no portal

💰 Gestão Financeira

  • Cadastro de notas fiscais (Invoice) e cobranças individuais (Payment)
  • Pagamentos com suporte a QR Code PIX gerado dinamicamente
  • Status de pagamento: pending, under_review, paid, overdue
  • Botão “Já paguei” no portal do cliente para notificar a agência sem precisar contato
  • Marcação automática de pagamentos vencidos diariamente (via Scheduler)
  • Painel administrativo com visão consolidada de inadimplência

📁 Biblioteca de Mídia (Google Drive)

  • Links de entregáveis organizados por cliente, mês e ano
  • Suporte a tipos de conteúdo: video, image, document, link
  • Controle de visibilidade pública/privada por item
  • Integrado ao portal do cliente para visualização de entregas

⚙️ Painel Administrativo

  • CRUD completo de clientes com dados: razão social, CNPJ, e-mail, telefone, endereço, vigência de contrato e mensalidade
  • Gerenciamento de usuários e permissões por cliente
  • Dashboard com visão geral de clientes ativos, pagamentos e relatórios
  • Configurações globais do sistema (aparência, textos, comportamentos)
  • Logs de auditoria de ações críticas

💻 Telas do Sistema (algumas para exemplo)

Dashboard do Cliente
Dashboard do Cliente
Calendário de Eventos
Calendário de Eventos
Lista de Pagamentos
Gestão de Pagamentos
Configurações do Admin
Configurações (Painel Admin)

🏗️ Arquitetura

O projeto segue uma arquitetura organizada em domains, separando as responsabilidades por contexto de negócio:

app/
├── Console/
│   └── Commands/
│       └── SyncCalendarCommand.php   # Comando artisan: calendar:sync
├── Domain/
│   ├── Calendar/     # Integração com Google Agenda
│   ├── Client/       # Gestão de clientes
│   ├── Financial/    # Pagamentos e faturas
│   ├── Media/        # Links de entregáveis
│   ├── Report/       # Relatórios de performance
│   └── Setting/      # Configurações do sistema
├── Http/
│   ├── Controllers/
│   │   ├── Admin/    # Controllers do painel da agência
│   │   └── Client/   # Controllers do portal do cliente
│   └── Middleware/
└── Models/           # User, GoogleOAuthToken, AuditLog

🛠️ Stack Tecnológica

CamadaTecnologia
BackendLaravel 11, PHP 8.2
Banco de dadosPostgreSQL
FrontendBlade + Tailwind CSS
PermissõesSpatie Laravel Permission
Google APIsgoogle/apiclient (Calendar + Drive)
PDFbarryvdh/laravel-dompdf
QR Code PIXchillerlan/php-qrcode
Ícones (UI)Heroicons, Solar Icons, Remix Icons
Logs de atividadeSpatie Laravel Activitylog
DeployHostinger (shared hosting)

🚀 Instalação local

Pré-requisitos

  • PHP 8.2+
  • Composer
  • PostgreSQL
  • Node.js + NPM

Passos

# Clone o repositório
git clone https://github.com/enzoterra/portal-agencia.git
cd portal-agencia

# Instale as dependências PHP
composer install

# Instale as dependências JS
npm install

# Configure o ambiente
cp .env.example .env
php artisan key:generate

# Rode as migrations
php artisan migrate --seed

# Inicie o servidor de desenvolvimento
composer run dev

Variáveis de ambiente necessárias

Configure as seguintes entradas no .env:

# Banco de dados
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=portal_agencia
DB_USERNAME=seu_usuario
DB_PASSWORD=sua_senha

# Google OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=
GOOGLE_CALENDAR_ID=

⏰ Tarefas Agendadas

O sistema depende de um Cron Job configurado no servidor para funcionar corretamente. Adicione a seguinte linha ao cron do servidor:

* * * * * /usr/bin/php /caminho/para/artisan schedule:run >> /dev/null 2>&1

O Laravel gerencia internamente os intervalos de cada tarefa:

TarefaFrequênciaDescrição
calendar:syncA cada 5 minutosSincroniza eventos do Google Agenda
Marcar vencidosDiário às 00:05Atualiza status de pagamentos vencidos

📄 Licença

Este projeto foi desenvolvido para uso interno e fins de portfólio.

Logo Easymind

Easymind

Self-managed infrastructure for AI assistants

The VPS running my AI assistant services for clients: provisioning, Docker, reverse proxy, TLS, monitoring and deploys. It is the foundation that keeps projects and services online, where I enjoy owning the full cycle, from code to operations and a smooth experience for the client.

LinuxDockerNginxCI/CD
private code

04 · skills

What I work with

The stack changes with the problem; these are the tools I usually work with.

◆ Frontend & Design

strongest area

Where I am strongest: modern, responsive, accessible interfaces, joining code and design. I care about UI/UX and visual identity, not only the implementation.

Next.js React TypeScript Tailwind CSS Three.js UI/UX Identidade visual Design responsivo Figma

◆ AI & Automation

current focus

Where I have been steering my career: systems with intelligence layers that truly solve problems.

LLMs & Agentes Visão computacional NLP & Análise de sentimento RAG & Bases de conhecimento n8n & Workflows Chatbots & Atendentes IA

Backend

Scalable APIs and architectures, with security and tests from day one.

NestJS Python Django Laravel PHP .NET Java GraphQL

Infra & Data

From code to operations: containers, CI/CD and well-modeled databases.

Docker Nginx CI/CD PostgreSQL PostGIS MongoDB MySQL Redis

Beyond the web

When the problem calls for a different tool.

Unity Flutter Android Design de logos & identidade

05 · services

I also build on demand

Besides my own projects, I provide services to companies and people who need to turn an idea into working software.

Websites & digital presence

Websites built from scratch and design refreshes for existing ones: fast, beautiful and with real SEO.

Custom systems

Complete systems for your business: solving problems and streamlining workflows to boost your team’s productivity.

AI & automation

AI assistants, system integrations and process automation that save your team real hours.

06 · contact

Shall we build something?

I'm always open to good conversations, whether about a role, a project, or an idea that doesn't have a name yet.

enzoterra18@gmail.com