Início / Portfólio / Maré Limpa

Maré Limpa

CLIENTE

Coworking Maré Limpa

TEMPO DE ENTREGA

2 meses

ANO

2025

SERVIÇOS

Arquitetura de sistemas · Integração de pagamentos · Automação operacional

https://marelimpa.com.br

Portal captivo de Wi-Fi para venda de acesso por tempo, com pagamento integrado e liberação automática. O usuário compra um plano, recebe um voucher reutilizável e pode consumir o tempo em sessões diferentes, com controle por dispositivo e acompanhamento operacional em tempo real.

O DESAFIO

Integrar rede local e operação online com segurança e previsibilidade: o MikroTik (RouterOS v7) está na rede local com o roteador atrás de CGNAT e o backend roda em VPS com IP público. Era necessário superar essa camada para que o backend se comunicasse com toda a infra montada na rede local. Além da conectividade, o outro grande desafio foi possibilitar que o usuário pudesse realizar o pagamento pela internet estando conectado a um hotspot que exige que ela pague para que possa ter acesso a internet. Também era necessário garantir controle por tempo, limite de dispositivos e zero intervenção manual no dia a dia.

A ABORDAGEM

Arquitetura ponta a ponta conectando portal captivo, backend e pagamentos. O MikroTik (RouterOS v7) se comunica com a aplicação através de um túnel WireGuard para atravessar o CGNAT com segurança. O pagamento via Mercado Pago (Pix e cartão) é confirmado por webhook assinado; ao aprovar, o sistema gera/atualiza o voucher e libera automaticamente o acesso, com rastreio por MAC/IP e regras de uso por dispositivo. Um painel administrativo consolida indicadores (minutos vendidos vs. usados, performance por plano e valor por minuto) para apoiar decisões operacionais.

Operação automática e previsível em produção.

Liberação 100% automática, operação estável e métricas de uso que orientam decisões comerciais e técnicas.

0%

Liberação automática

0/7

Operação previsível

0

Intervenção manual

INFRA & REDE

Ubuntu 24.04 LTSRouterOS v7WireGuardNginx

BACKEND & DADOS

DjangoPostgreSQLRedis

PAGAMENTOS

Mercado PagoWebhook assinadoPix e Cartão

O que foi entregue nesta solução

Voucher reutilizável por tempo

O usuário compra minutos e consome em sessões diferentes, sem fricção e com regras claras de uso por dispositivo.

Liberação automática pós-pagamento

Após confirmação do webhook, o acesso é liberado sem ação manual, reduzindo erro operacional.

Controle por MAC/IP e limite de dispositivos

A sessão é rastreada com regras de segurança para evitar abuso e preservar previsibilidade da rede.

Painel operacional com indicadores

Métricas de consumo e receita por plano orientam decisões comerciais e técnicas.

CASE TÉCNICO

Controle de acesso Wi-Fi com pagamentos online

Sistema de controle de acesso Wi-Fi desenvolvido para operar sem intervenção manual, conectando uma aplicação Django hospedada em nuvem a um MikroTik instalado em uma rede local atrás de CGNAT.

Django 5.1PostgreSQLRedisFlaskRouterOS v7GunicornNginxWireGuarddjango-apschedulerpytest / asyncioPython logginghttpxMercado Pago

O Contexto

O produto permite que usuários comprem acesso à internet por tempo, recebam um voucher reutilizável e consumam esse saldo em diferentes sessões.

O objetivo principal era eliminar a necessidade de intervenção manual na concessão e controle do acesso Wi-Fi pago.

Restrições Principais

  • MikroTik atrás de CGNAT.
  • Rede local sem IP público acessível diretamente.
  • Necessidade de controlar o acesso remotamente.
  • Pagamento realizado antes da liberação da internet.
  • Controle de tempo de uso.
  • Limitação de dispositivos por voucher.
  • Necessidade de operação automática.
  • Pagamentos reais envolvendo dados de clientes.

O problema técnico central

Controlar dinamicamente um MikroTik localizado atrás de CGNAT a partir de uma aplicação hospedada em uma VPS pública.

Por que foi difícil?

  • CGNAT impede que o servidor faça conexões de entrada diretamente para o roteador.
  • Expor portas de gerenciamento do RouterOS na internet aumentaria significativamente a superfície de ataque.
  • O sistema precisava reagir a eventos de pagamento e alterações de sessão quase em tempo real.
  • A operação precisava funcionar sem depender de intervenção manual.

Direção Final

O problema foi resolvido utilizando WireGuard para criar um canal privado entre a infraestrutura pública e a rede local.

Arquitetura & Stack Tecnológica

A arquitetura final utiliza um monólito Django

Fluxo simplificado da arquitetura final:

Carregando diagrama de arquitetura...

Componentes da Solução

DjangoDjango 5.1

Aplicação principal, autenticação, administração, regras de negócio, sessões e integração com pagamentos.

PostgreSQLPostgreSQL + psycopg pool

Persistência de usuários, transações, vouchers, sessões e regras de negócio.

RedisCache Memory DB

Cache distribuído e rate limiting com incremento atômico.

GatewayFlask

Serviço isolado responsável por encapsular a comunicação com routeros_api e executar comandos no MikroTik.

WireGuardVPN

Túnel privado entre VPS e rede local, atravessando CGNAT sem expor portas de gerenciamento do RouterOS.

MikroTikRouterOS v7

Controle físico do hotspot, regras de acesso e sessões de usuários.

Mercado PagoMercado Pago SDK

Processamento de pagamentos via Pix e cartão e confirmação através de webhook assinado.

GunicornGunicorn

Servidor WSGI da aplicação Django em produção.

NginxNginx

Reverse proxy e terminação HTTP/HTTPS.

Sentrysentry-sdk

Monitoramento e rastreamento de exceções em produção.

ResendResend API

Infraestrutura transacional para envio de relatórios e alertas operacionais por e-mail.

Ubuntu 24.04 LTS ServerLinux

Ambiente de produção em nuvem para hospedagem do backend, proxy reverso e túnel WireGuard.

Decisões de Arquitetura

commit 75899a8, janeiro/2026

Da separação FastAPI + SPA para Django

O projeto começou com FastAPI no backend e um frontend SPA separado, utilizando JWT para autenticação. Durante a preparação para produção, essa arquitetura foi reavaliada sob a perspectiva de segurança e operação. A aplicação foi então migrada para Django com renderização server-side.

A mudança reduziu a quantidade de infraestrutura de segurança que precisava ser implementada e validada manualmente. Autenticação baseada em sessão, CSRF, autorização e administração passaram a estar integrados ao mesmo ecossistema. O custo foi uma reestruturação significativa do projeto e o abandono da separação entre frontend e backend, mas o resultado foi uma aplicação mais simples de operar e manter.

commits 1f298b9 (polling, tentativa) e f163815 (gateway push, versão definitiva)

De polling para comunicação push através de gateway

A integração inicial com o MikroTik utilizava polling, mas esse modelo introduzia latência e tráfego desnecessário para um fluxo essencialmente orientado a eventos. A solução evoluiu para um gateway HTTP dedicado em Flask, responsável pela comunicação com o RouterOS, enquanto o backend Django se comunica com esse gateway através de um túnel WireGuard.

A arquitetura adicionou um segundo serviço, mas criou uma fronteira clara entre a aplicação e a infraestrutura de rede. Além de reduzir a exposição do RouterOS, isso permitiu manter a integração de rede isolada do restante da aplicação.

commits 3eb2a39 (manobra emergencial para LocMemCache) e 60b1dae (tentativa de DatabaseCache, reversão para Redis)

Redis para rate limiting distribuído

O rate limiting precisava manter estado compartilhado entre múltiplos workers e realizar incrementos atômicos. As alternativas avaliadas foram LocMemCache e DatabaseCache, mas a primeira não oferecia estado distribuído e a segunda não atendia adequadamente ao mecanismo de incremento necessário.

Redis foi adotado como backend de cache, adicionando uma dependência operacional, mas fornecendo o comportamento necessário para um ambiente com múltiplos workers.

Scheduler interno em vez de cron externo

Para executar o garbage collector, foi considerada a utilização de um cron externo na VPS para acionar periodicamente a aplicação hospedada no Railway. Embora essa abordagem favorecesse um modelo de scale-to-zero, a economia estimada era de aproximadamente US$ 0,044 por mês em um plano de custo fixo.

Nesse cenário, a economia não justificava a complexidade adicional. O scheduler foi mantido dentro do próprio monólito utilizando django-apscheduler, executando a rotina a cada cinco minutos e mantendo toda a lógica operacional dentro da aplicação.

Commit: 2368044

Da PaaS (Railway) para VPS própria

A plataforma inicial (Railway) limitava o controle de rede necessário para estabelecer e manter o túnel WireGuard persistente com a infraestrutura local.

A aplicação foi migrada para uma VPS própria rodando Ubuntu 24.04 LTS, Gunicorn e Nginx. Essa mudança garantiu o controle necessário sobre a infraestrutura de rede e trouxe previsibilidade operacional para a comunicação com o RouterOS.

Operação e Resiliência em Produção.

Resultados operacionais observados desde a implantação da arquitetura atual.

fev 2026

Em Produção Desde

+0

Vendas Aprovadas

+0

Clientes

  • Não foram registrados incidentes de segurança.
  • Não foram registrados vazamentos de dados.
  • Não foram registradas quedas causadas por falha da aplicação.
  • A única interrupção registrada ocorreu por falha de infraestrutura de terceiro durante a hospedagem inicial no Railway.

Segurança

Como o sistema processa pagamentos reais e controla uma infraestrutura física, segurança foi tratada como parte da arquitetura desde a implementação.

A autenticação utiliza sessão e CSRF do próprio Django. Operações sensíveis possuem rate limiting e bloqueio progressivo. Webhooks do provedor de pagamento são validados e processados de forma idempotente, evitando concessões duplicadas. Dados desnecessários dos payloads não são persistidos, e valores financeiros são representados com DecimalField.

Na camada de infraestrutura, o MikroTik não possui portas de gerenciamento expostas à internet. A comunicação acontece através de WireGuard.

Casos e Desafios Técnicos

O problema do CGNAT

O plano original exigia que a aplicação na nuvem se conectasse diretamente ao MikroTik local. Na prática, o equipamento estava isolado atrás de um CGNAT, bloqueando qualquer acesso externo. A solução não foi uma gambiarra na rede, mas uma pivotagem arquitetural.

O MikroTik foi atualizado e passou a iniciar um túnel seguro (WireGuard) de dentro para fora. A aplicação passou a controlar o roteador por esse canal, sem expor portas na internet.

Lição aprendida: A restrição física da rede deixou de ser um obstáculo a ser "contornado" e virou o novo requisito. A arquitetura deve respeitar a realidade do ambiente, não o contrário.

commit b0a60ec, fevereiro/2026

Quando limitar dispositivos não era suficiente

O primeiro mecanismo de proteção contabilizava endereços MAC para impedir que um voucher fosse compartilhado entre vários dispositivos. Na prática, essa regra tratava todos os dispositivos da mesma maneira e não representava corretamente o comportamento esperado.

A solução passou a classificar o dispositivo a partir do user-agent, utilizando ua-parser, e aplicar limites por categoria. Assim, computadores e dispositivos móveis passaram a consumir slots diferentes dentro da regra de proteção.

BUG_FIX_CONSUMO_TEMPO.md e commit 1d3830a

O voucher de 1 minuto que permitia uso infinito

Um bug crítico permitia que usuários comprassem apenas 1 minuto de acesso e navegassem indefinidamente. O motivo: a rotina de limpeza atualizava o banco em lote (QuerySet.update()) focando em velocidade, o que ignorava completamente as validações do modelo (que barravam saldo negativo).

A correção sacrificou a performance de banco em favor da integridade: o processamento passou a ser iterativo, garantindo que o cálculo (min(solicitado, restante)) passasse pelas regras de negócio antes do salvamento.

Lição aprendida: Eficiência de banco de dados é inútil se ela armazena uma transação financeiramente inválida. A integridade das regras de domínio sempre tem precedência sobre a performance da query.

payments/models.py, linha 337; refatoração de idempotência formalizada no commit 411c129

Webhook duplicado não pode conceder acesso duas vezes

A liberação do acesso dependia dos webhooks do Mercado Pago. Como redes falham, esses avisos frequentemente são reenviados (retries). Se o sistema confiasse cegamente no evento, um reenvio acidental duplicaria o saldo do cliente.

O fluxo foi redesenhado: a aplicação agora verifica de forma atômica o estado access_granted_at. Se a liberação já ocorreu no banco, o novo aviso é respondido com sucesso (HTTP 200), mas ignorado no domínio.

Lição aprendida: Nunca confie no "exactly-once delivery" da rede. Eventos externos são, por definição, mentirosos ou duplicados. A idempotência precisa morar no coração da transação.

infra/vps-gateway/app.py, linha 313

O RouterOS dizia 200, mas a regra continuava ativa

Ao encerrar o tempo de um cliente, a aplicação pedia ao MikroTik para remover a permissão de acesso. O roteador respondia "HTTP 200 OK", mas demorava milissegundos para aplicar a regra fisicamente, permitindo que o cliente continuasse navegando.

A solução foi mudar a fonte da verdade: a integração parou de confiar na resposta HTTP e passou a sondar ativamente o roteador (até 8 tentativas em 200ms) até confirmar que o MAC Address realmente havia desaparecido das regras ativas.

Lição aprendida: Status 200 significa "eu ouvi seu pedido", não "a tarefa está concluída". Ao integrar hardware ou sistemas externos lentos, o estado real observado é a única prova de sucesso.

commits 3eb2a39 e 60b1dae

Quando o Redis caiu

Quando o provedor inicial do Redis apresentou instabilidade, tentamos usar a memória local (LocMemCache) e o banco de dados (DatabaseCache) como saídas de emergência para manter o Rate Limiting ativo. Ambas as trocas falharam silenciosamente.

O algoritmo exigia incrementos atômicos e estado distribuído — propriedades que a memória local fragmentava entre os workers e o banco relacional não garantia sem onerar as transações. O Redis teve que voltar como backend definitivo.

Lição aprendida: Substituir uma peça de infraestrutura exige muito mais do que assinar a mesma interface genérica de "Cache". É mandatório garantir que as propriedades físicas (atomicidade, concorrência) das quais o algoritmo depende sejam preservadas.

MERCADO_PAGO_FIXES.md

De 57 para 85 no score de integração

A primeira versão da integração apresentava erros 404 ocasionais, principalmente no fluxo do Card Payment Brick, e registrava um score de qualidade de integração de aproximadamente 57/100 no Mercado Pago.

O diagnóstico mostrou divergências entre os payloads formatados pela aplicação e os schemas estritos esperados pelo provedor.

A integração foi refatorada para normalizar os dados, separar explicitamente DDD e telefone, adequar os metadados e tipar corretamente o array de itens. Como resultado, os erros 404 foram eliminados e o score subiu para aproximadamente 85/100.

Lição aprendida: Integrações de terceiros exigem validação rigorosa dos contratos da API e capacidade de investigar além do código da própria aplicação.

Atuação e Aprendizados

Fui responsável pela arquitetura e desenvolvimento da aplicação, conectando o backend, as regras de negócio e a integração com o Mercado Pago à infraestrutura de rede (cuja configuração física contou com a colaboração de um especialista). Ao longo desse processo, com apoio de IA na implementação, conduzi o diagnóstico de problemas em produção e tomei todas as decisões arquiteturais, de segurança e de migração.

A principal lição dessa jornada é que restrições físicas exigem pivotagens reais na arquitetura, não apenas ajustes superficiais de código. A experiência validou que a eficiência de queries no banco de dados nunca deve se sobrepor à integridade das regras do domínio; que eventos externos devem ser sempre tratados como duplicados ou imprecisos; e que um status "HTTP 200 OK" não garante que o estado do hardware realmente mudou. Escolhas pragmáticas, como consolidar o projeto no Django ou manter o Redis como dependência obrigatória, provaram que a tecnologia deve ser selecionada pelas propriedades operacionais fundamentais que ela garante diante do comportamento real do sistema.

Veja o resultado final.

https://marelimpa.com.br