TL;DR: Construímos do zero uma plataforma completa de live shopping em 2 semanas. Resultado: R$ 680.000 em pedidos durante 5 horas de transmissão ao vivo, com mais de 200 usuários simultâneos, capacidade para até 12.000 conexões WebSocket ativas, mais de 120 pedidos e zero downtime.
📖 O Contexto: Um Prazo Impossível
Na Alva eu brinco que existe o tempo Alva. Ele corre em um rítimo diferente do resto do mundo. Enquanto no mundo real passaram-se 8 horas, na Alva já se passaram 30 dias. Isso é bastante insano em vários momentos mas também é o que permite que coisas incríveis aconteçam. Foi neste contexto que recebi o briefing, a mensagem era clara: precisávamos de uma plataforma de live shopping pronta em 2 semanas. Não era uma landing page simples. Era um sistema completo para uma live de B2B, isso mesmo ATACADO, e ela deveria contar com:
- Transmissão ao vivo com produtos em tempo real
- Carrinho de compras anônimo (sem fricção de cadastro)
- Checkout simplificado sem burocracia
- Painel de gestão para controlar a live, pedidos e atendimento
- Chat em tempo real entre clientes e gestores
- Notificações instantâneas
- Tudo isso escalável para suportar milhares de usuários simultâneos
A primeira reação foi: “Impossível”, precisamos encontrar parceiros que nos atendam em tempo hábil. E Spoiler, feito contato com 3 plataformas a primeira nos respondeu apenas 3 dias antes do evento. Se dependêssemos disso não teria ocorrido a live. Então se a primeira reação foi “impossível”, a segunda foi: “Vamos fazer”.
🎯 O Desafio Real: Não Era Só Código
O maior desafio não era apenas escrever código rapidamente. Era tomar decisões arquiteturais corretas desde o início, porque não teríamos tempo para refatorações significativas. Cada escolha técnica precisava ser:
- Rápida de implementar (prazo de 2 semanas)
- Escalável (suportar milhares de usuários simultâneos)
- Confiável (zero downtime em uma live de R$ 600k)
- Segura (proteger dados de clientes e transações)
Vamos mergulhar nas decisões técnicas que tornaram isso possível.
🏗️ Arquitetura: A Fundação do Projeto
Nessas horas ter alguns anos desenvolvendo soluções nos ajudam, minha líder direta que não é da área de TI brinca que sou muito “pragmático”, e como ela está certa comecei com um plano em um Board que já nasceu com 190 tarefas. A primeira foi definir a Stack.
A Stack Escolhida
Optamos por uma arquitetura moderna e battle-tested, nada de Hype, nada de novidade mirabolante do vídeo do youtube de ontem. Focamos em esencial:
Frontend:
- Next.js 15 com React 19 e TypeScript
- TailwindCSS para UI rápida e responsiva
- WebSocket nativo para comunicação em tempo real
- Context API para gerenciamento de estado global
Backend:
- Django 5.2 com Django REST Framework
- Django Channels 4.0 para WebSocket assíncrono
- PostgreSQL 16 como banco de dados relacional
- Redis 6+ para cache e channel layer (Pub/Sub)
Infraestrutura:
- AWS EC2 t3.2xlarge (8 cores, 32GB RAM)
- RDS PostgreSQL Multi-AZ para alta disponibilidade
- AWS S3 para armazenamento de imagens
- Vercel para deploy do frontend
- Docker Compose para orquestração
- StreamYard para fazer o swithcing do Stream
- Youtube como Broadcaster
Por Que Django + Next.js?
Django foi escolhido por:
- Admin panel pronto (economizamos 10 dias de desenvolvimento)
- ORM robusto para modelagem rápida
- Ecossistema maduro (DRF, Channels, SimpleJWT)
- Suporte nativo a WebSocket via Channels
Next.js 15 foi escolhido por:
- Server-side rendering para SEO e performance
- App Router para rotas dinâmicas (
/[slug]) - Deploy zero-config na Vercel
- Excelente developer experience
Por mais que eu não goste de desenvolver em Javascript e menos ainda com TypeScript não há como negar que a vercel faz um baita trabalho e a um custo muito baixo quando falamos de Resiliencia e Disponibilidade sem contar a facilidade do deploy.
⚡ O Maior Desafio Técnico: Tempo Real em Escala
O Problema
Em um live shopping, tudo precisa ser instantâneo:
- Produtos aparecem e desaparecem em segundos
- Estoques mudam a cada compra
- Gestores precisam ver pedidos em tempo real
- Clientes enviam mensagens e esperam respostas imediatas
- Banners promocionais precisam aparecer e desaparecer conforme necessidade de momento.
- Não havia uma previsão exata de participantes, poderiam ser 50, poderiam ser 1000.
HTTP tradicional com polling seria um pesadelo de performance. Precisávamos de WebSocket.
A Solução: 3 Canais WebSocket Especializados
Implementamos uma arquitetura com 3 canais WebSocket independentes:
1. Canal Geral (Live Shopping)
Atualiza produtos, estoques e status da live para TODOS os clientes conectados.
# backend/consumers.py
class LiveShoppingConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.group_name = "live_shopping_updates"
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
async def product_updated(self, event):
"""Envia atualizações de produtos para todos os clientes"""
await self.send(text_data=json.dumps({
"type": "product_updated",
"product": event["product"]
}))
No frontend, uma reconexão automática garante zero perda de mensagens:
// frontend/hooks/useWebSocket.ts
useEffect(() => {
const ws = new WebSocket(`${WS_URL}/ws/live-shopping/`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'product_updated') {
setProducts(prev =>
prev.map(p => p.id === data.product.id ? data.product : p)
);
}
};
ws.onclose = () => {
// Reconexão automática após 3 segundos
setTimeout(() => connectWebSocket(), 3000);
};
}, []);
2. Canal de Chat Individual
Cada conversa cliente-gestor tem seu próprio canal isolado, usando UUID em vez de telefone ou outro ID facilmente reconhecível(privacidade LGPD).
# backend/routing.py
websocket_urlpatterns = [
path("ws/chat/<str:slug>/<uuid:conversation_id>/", ChatConsumer.as_asgi()),
]
# backend/consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.conversation_id = self.scope['url_route']['kwargs']['conversation_id']
self.group_name = f"chat_{self.slug}_{self.conversation_id}"
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
3. Canal de Notificações Globais (Gestor)
Gestores recebem notificações em tempo real com autenticação JWT:
# backend/consumers.py
class NotificationConsumer(AsyncWebsocketConsumer):
async def connect(self):
# Extrai token JWT da query string
query_string = self.scope.get("query_string", b"").decode()
query_params = parse_qs(query_string)
token = query_params.get("token", [None])[0]
if not token:
await self.close(code=4001) # Unauthorized
return
# Valida token
try:
access_token = AccessToken(token)
user_id = access_token["user_id"]
user = await self.get_user(User, user_id)
if not user or not user.is_staff:
await self.close(code=4003) # Forbidden
return
self.user_id = user_id
self.group_name = f"notifications_{user_id}"
await self.channel_layer.group_add(
self.group_name,
self.channel_name
)
await self.accept()
except Exception:
await self.close(code=4001)
No frontend:
// frontend/hooks/useNotifications.ts
const token = localStorage.getItem("gestor_access_token");
const ws = new WebSocket(`${WS_URL}/ws/notifications/?token=${token}`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'new_message') {
setUnreadCount(prev => prev + 1);
}
};
Broadcast Inteligente com Django Signals
Para evitar código duplicado, usamos signals como single source of truth para WebSocket:
# backend/signals.py
from django.db.models.signals import post_save
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
@receiver(post_save, sender=Message)
def message_saved(sender, instance, created, **kwargs):
if not created:
return
channel_layer = get_channel_layer()
# 1. Envia para o canal do chat
async_to_sync(channel_layer.group_send)(
f"chat_{instance.conversation.live_shopping.slug}_{instance.conversation.conversation_id}",
{
"type": "chat_message",
"message": MessageSerializer(instance).data
}
)
# 2. Se não é do gestor, notifica gestores
if not instance.is_from_gestor:
async_to_sync(channel_layer.group_send)(
f"notifications_{assigned_user_id}",
{
"type": "new_message_notification",
"conversation_id": str(instance.conversation.conversation_id)
}
)
# 3. Invalida cache de contadores
cache.delete(f'conversation_counts_{instance.conversation.live_shopping.slug}')
Resultado: Zero mensagens duplicadas, broadcast consistente e código centralizado.
🚀 Escalabilidade: Suportando até 12.000 Conexões WebSocket
O Problema WSGI vs ASGI
Django tradicionalmente roda com WSGI (Gunicorn), que é síncrono e não suporta WebSocket. Para WebSocket, precisamos de ASGI.
Mas rodar tudo em ASGI seria desperdício de recursos para requisições HTTP simples. Aqui para quem já roeu algum osso vem também o medo do “pode dar m*rda”. Como AWS permite qualquer tamanho de máquina por qualquer tempo optamos por ir acima do que morrer por economizar uns trocados.
A Solução: Arquitetura Híbrida (Gunicorn + Daphne)
Separamos as responsabilidades:
Gunicorn (WSGI) – Porta 8000
- 8 workers × 2 threads = 16 workers lógicos
- Capacidade: 120 req/s
- Uso: ~25-37% CPU (2-3 cores)
- Serve: API REST síncrona (
/api/*)
Daphne (ASGI) – Porta 8001
- 6 réplicas configuráveis via Docker Compose
- Cada réplica: ~2.000 conexões WebSocket
- Total: 12.000 conexões simultâneas
- Uso: ~50% CPU (4 cores)
- Serve: WebSocket (
/ws/*)
Nginx – Roteamento inteligente
# /api/* → Gunicorn (backend:8000)
location /api/ {
proxy_pass http://backend:8000;
}
# /ws/* → Daphne com load balancing (upstream)
upstream daphne_backend {
least_conn;
server daphne_1:8001;
server daphne_2:8001;
server daphne_3:8001;
server daphne_4:8001;
server daphne_5:8001;
server daphne_6:8001;
}
location /ws/ {
proxy_pass http://daphne_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Redis – Pub/Sub entre processos
# backend/settings.py
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [(os.getenv("REDIS_HOST", "redis"), 6379)],
"capacity": 15000,
"expiry": 60,
},
},
}
Resumo do Docker Compose: Orquestração com Réplicas
# docker-compose.prod.yml
services:
backend:
build: .
command: gunicorn fast_shop.wsgi:application --bind 0.0.0.0:8000 --workers 8 --threads 2
ports:
- "8000:8000"
environment:
- DAPHNE_REPLICAS=6
daphne:
build: .
command: daphne -b 0.0.0.0 -p 8001 fast_shop.asgi:application
deploy:
replicas: 6 # 6 instâncias Daphne
environment:
- REDIS_HOST=redis
redis:
image: redis:6-alpine
command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
ports:
- "6379:6379"
Resultado:
- Suporte a 1.000+ usuários simultâneos sem lag
- Suporte a 12.000 conexões WebSocket ativas
- CPU: 87% (distribuído entre Daphne 50%, Gunicorn 25-37%, Redis 6%)
- RAM: 11-17GB de 32GB disponíveis
- Em uma T3.X2Large
🔐 Segurança: Zero Compromissos
Desafio: Checkout Sem Cadastro (mas Seguro)
A demanda do Comercial e do Marketing da Alva foi, o cliente precisa de zero fricção: sem login, sem senha, sem confirmação de email. Fora isso, com essa “falta de burocracias” e API pública como garantir que cada rato não fosse para o queijo do vizinho? Aqui novamente usamos o básico, UUID, Tokens e etc…
A Solução: Sistema de 3 Tokens
1. Session Token (Carrinhos)
Quando o cliente cria um carrinho, geramos um token seguro:
# backend/views.py
import secrets
def create_cart(request):
session_id = request.data.get("session_id") # UUID do navegador
cart = SessionCart.objects.create(
session_id=session_id,
session_token=secrets.token_urlsafe(32), # Token de 32 bytes
live_shopping=live
)
return Response({
"session_id": cart.session_id,
"session_token": cart.session_token # Retorna uma única vez
})
Todas as operações subsequentes exigem o token no header:
// frontend/services/api.ts
export const addToCart = async (productId: number, quantity: number) => {
const sessionToken = localStorage.getItem('live_shopping_session_token');
const response = await fetch(`${API_URL}/cart/items/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-Token': sessionToken // Validado no backend
},
body: JSON.stringify({ product_id: productId, quantity })
});
return response.json();
};
No backend, validamos cada request:
# backend/views.py
@api_view(['POST'])
@throttle_classes([CartThrottle]) # 120 req/min
def add_cart_item(request):
session_token = request.headers.get('X-Session-Token')
if not session_token:
return Response({"error": "Token obrigatório"}, status=401)
try:
cart = SessionCart.objects.get(
session_id=request.data['session_id'],
session_token=session_token
)
except SessionCart.DoesNotExist:
return Response({"error": "Carrinho inválido"}, status=403)
# Processa item...
2. Read Token (Pedidos)
Após o checkout, geramos um token de leitura para o cliente acompanhar o pedido:
# backend/views.py
def checkout(request):
order = Order.objects.create(
code_order=generate_order_code(),
read_token=secrets.token_urlsafe(32),
customer_name=request.data['name'],
# ...
)
return Response({
"code_order": order.code_order,
"read_token": order.read_token,
"redirect_url": f"/order/{order.code_order}?token={order.read_token}"
})
3. JWT Tokens (Gestor)
Para o painel administrativo, usamos JWT com refresh tokens:
# backend/views_gestor.py
from rest_framework_simplejwt.tokens import RefreshToken
@api_view(['POST'])
@throttle_classes([LoginThrottle]) # 5 req/min (anti brute-force)
def gestor_login(request):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(username=username, password=password)
if user and user.is_staff:
refresh = RefreshToken.for_user(user)
return Response({
"access": str(refresh.access_token), # 8 horas
"refresh": str(refresh), # 7 dias
})
return Response({"error": "Credenciais inválidas"}, status=401)
Rate Limiting Agressivo
Protegemos cada endpoint com throttling personalizado:
# backend/settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_RATES": {
"anon": "1000/hour", # ~16 req/min para clientes
"user": "5000/hour", # Gestores autenticados
"login": "5/minute", # Anti brute-force
"checkout": "20/hour", # Max 20 checkouts por IP/hora
"cart": "120/minute", # ~2 req/seg
"messages": "20/minute", # Proteção contra spam
}
}
Headers de Segurança
# backend/settings.py
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
🗄️ AWS RDS: Por Que PostgreSQL Multi-AZ?
A Decisão
Inicialmente considerei PostgreSQL local no EC2. Mas com R$ 600k em jogo, não havia margem para erro. Não tínhamos tempo nem equipe para criar uma solução elástica e escalável em tão pouco tempo. Por isso RDS da AWS nos atendia como uma luva.
AWS RDS Multi-AZ oferecia:
- Backups automáticos diários
- Failover automático em caso de falha (< 2 minutos)
- Replicação síncrona para zona secundária
- Snapshots manuais antes da live
- Connection pooling para otimizar conexões
Otimizações Básicas de Performance
# backend/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'HOST': os.getenv('DB_HOST'),
'NAME': os.getenv('DB_NAME'),
'CONN_MAX_AGE': 60, # Connection pooling (60s)
'OPTIONS': {
'connect_timeout': 10,
'options': '-c statement_timeout=30000' # 30s timeout
}
}
}
Índices Estratégicos
Criamos índices compostos para queries críticas:
# backend/models.py
class Conversation(models.Model):
conversation_id = models.UUIDField(db_index=True)
live_shopping = models.ForeignKey(LiveShopping, on_delete=models.CASCADE)
session_id = models.UUIDField(db_index=True)
status = models.CharField(max_length=20, db_index=True)
class Meta:
indexes = [
models.Index(fields=['live_shopping', 'session_id', 'status']),
]
Resultado: Queries de conversas otimizadas de 200ms para 15ms.
📊 Otimização de Performance: De 50KB para 100 Bytes
Nem tudo foram flores, em alguns testes operacionais observamos que o Websocket estáva rescebendo muitas requisições. Sem tempo para criar grandes rotinas de testes automatizados foi o bom console do browser e analisando log do Django Channels e do Django que observamos que várias telas faziam conexões desnecessárias a API e ao websocket.
Um exemplo de Problema
No painel do gestor, cada mudança de filtro dos pedidos entre as telas de Novos, Em Atendimento fazia 2 requests:
- GET
/orders/– Carregava TODOS os pedidos (~50KB) - Frontend filtrava localmente
- Frontend calculava contadores localmente
Em paralelo, um segundo endpoint com filtro específico era chamado trazendo somente os itens desejados. Isso acabou ficando assim pois durante o desenvolvimento usamos um contador para trazer o total de pedidos novos, em andamento e finalizados. Obviamente com a insanidade e o tempo isso ficou ali como uma dívida ténica, e 4 dias antes da live observamos isso.
Além de tudo isso ainda tinhamos nesta tema um auto-refresh a cada 10s, isso gerava 36MB de tráfego por hora.
A Solução: Filtros Server-Side + Endpoint de Contadores
Filtros no Backend
# backend/views_gestor.py
@api_view(["GET"])
@permission_classes([IsAuthenticated])
@gestor_required
def gestor_orders_list(request, slug):
live = LiveShopping.objects.get(slug=slug)
# Filtro server-side
status_filter = request.GET.get("status")
orders = Order.objects.filter(live_shopping=live).select_related("live_shopping")
if status_filter:
orders = orders.filter(status=status_filter) # Filtro no PostgreSQL
return Response(OrderSerializer(orders, many=True).data)
Endpoint Otimizado de Contadores
# backend/views_gestor.py
from django.db.models import Count, Q
@api_view(["GET"])
@permission_classes([IsAuthenticated])
@gestor_required
def gestor_orders_counts(request, slug):
live = LiveShopping.objects.get(slug=slug)
# Uma única query SQL com agregação
counts_data = Order.objects.filter(live_shopping=live).aggregate(
novo=Count("id", filter=Q(status="novo")),
em_atendimento=Count("id", filter=Q(status="em_atendimento")),
concluido=Count("id", filter=Q(status="concluido")),
todos=Count("id"),
)
return Response(counts_data)
# Resposta: {"novo": 5, "em_atendimento": 12, "concluido": 89, "todos": 106}
# Tamanho: ~100 bytes
Frontend Otimizado
// frontend/app/gestor/painel/pedidos/page.tsx
useEffect(() => {
if (selectedLive) {
loadOrders(selectedLive.slug, orderFilter); // Carrega apenas filtro ativo
}
}, [selectedLive, orderFilter]); // Reage a mudança de filtro
useEffect(() => {
if (selectedLive) {
loadOrderCounts(selectedLive.slug); // Carrega contadores
}
}, [selectedLive]); // Reage APENAS à mudança de live
Resultados da Otimização
| Métrica | Antes | Depois | Melhoria |
|---|---|---|---|
| Payload contadores | ~50KB | ~100 bytes | 99.8% menor |
| Query SQL | SELECT * | COUNT(*) | 5000x menos dados |
| Requests/mudança filtro | 2 | 1 | 50% redução |
| Tráfego/hora | 36MB | 720KB | 98% redução |
☁️ Vercel: Deploy de Frontend em 30 Segundos
Por Que Vercel?
Com prazo apertado, não tínhamos tempo para configurar Nginx, SSL, CDN, etc. Vercel resolveu tudo:
- Deploy automático via Git push
- CDN global (Edge Network)
- SSL automático (Let’s Encrypt)
- Previews de branches (QA instantâneo)
- Rollback com 1 clique
- Zero configuração
Configuração Mínima
# .env.production
NEXT_PUBLIC_API_BASE_URL=https://DOMINIODALIVE/api
NEXT_PUBLIC_WS_URL=wss://DOMINIODALIVE
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs"
}
Resultado: Deploy em produção em menos de 1 minuto, com performance de CDN global.
🎬 O Dia da Live: 5 Horas de Adrenalina
Preparação
24 horas antes:
- Snapshot do RDS PostgreSQL
- Teste de carga com 500 usuários simultâneos (k6)
- Monitoramento CloudWatch configurado
- Plano de contingência documentado
1 hora antes:
- Health check: ✅ Backend, ✅ WebSocket, ✅ Redis, ✅ RDS
- CPU EC2: 12% (idle)
- Memória: 4GB/32GB (idle)
- Conexões PostgreSQL: 8/100
Durante a Live
Picos observados:
- 212 usuários simultâneos (pico às 15:15h)
- 2.000 conexões WebSocket ativas
- CPU: 87-99% (Daphne 50%, Gunicorn 37%)
- Memória: 6GB/32GB (estável)
- Latência média WebSocket: 45ms
- Latência média API: 120ms
💭 Reflexão Final
Desenvolver uma plataforma que processa R$ 600k em 5 horas, em apenas 2 semanas, foi uma das experiências mais gratificantes da minha carreira. Mas o que mais me surpreendeu não foi o resultado financeiro, foi como conseguimos isso com apenas 3 pessoas: 1 Sênior/CTO, 1 desenvolvedor júnior frontend e 1 frontend júniero que ajudou parcialmente em QA.
Sem DevOps dedicado. Sem QA formal. Sem designers full-time. E zero downtime durante a live.
O sucesso veio de três pilares que aprendi ao longo dos anos:
1. Experiência em Arquitetura Vale Mais que Quantidade de Gente
Quando você já viveu problemas similares, não precisa testar 5 abordagens para descobrir qual funciona. Você já sabe que WebSocket precisa de ASGI mas HTTP comum roda melhor em WSGI. Já sabe que Redis resolve broadcast entre processos. Já sabe que rate limiting se configura antes da live, não depois.
Essa experiência me permitiu tomar decisões arquiteturais corretas na primeira tentativa, economizando dias (talvez semanas) de refatoração. Não há atalho para isso, é conhecimento acumulado resolvendo problemas similares em diferentes contextos. Não foi sorte. Foi arquitetura correta desde o início valendo 10x mais que velocidade sem direção.
2. Ferramentas Comprovadas > Hype
Eu poderia ter escolhido tecnologias hypadas ou construir tudo do zero. Não fiz nada disso. Escolhi ferramentas boring: Django (20 anos), PostgreSQL (29 anos), Redis (16 anos), AWS RDS, Vercel (talvez a mais Hypada delas).
Cada uma já provou funcionar em escalam Instagram usa (ou dizem que usou) Django Channels, Netflix usa PostgreSQL, Twitter usa Redis. Meu trabalho foi combiná-las de forma inteligente. Não precisei provar que funcionam. Só precisei construir.
Quando você escolhe ferramentas com milhões de horas de produção validando sua eficiência, você não perde tempo debugando problemas obscuros. Você resolve o problema do cliente.
3. Foco no Essencial (e Cortar o Resto)
Com 3 pessoas e 2 semanas, cortamos tudo que não impactava a live: autenticação complexa (usamos checkout anônimo), painel customizado (Django Admin adaptado), animações sofisticadas (TailwindCSS simples), testes unitários completos (focamos em carga), CI/CD complexo (deploy manual).
Focamos 100% em: WebSocket robusto, segurança (tokens, rate limiting), performance (12.000 conexões), monitoramento (CloudWatch) e plano de contingência.
Experiência ensina a diferenciar o crítico do cosmético. E isso salvou o prazo.
O verdadeiro segredo? Não foi trabalhar 16 horas por dia, isso também claro (rs) mas foi ter experiência para tomar decisões certas rapidamente, humildade para usar ferramentas comprovadas, e disciplina para focar no que importa.
R$ 600k em 5 horas não veio de ter a melhor equipe do mercado ou recursos ilimitados. Veio de 3 pessoas bem alinhadas com as ferramentas certas resolvendo o problema certo.
E se você está enfrentando prazos impossíveis: não procure mais gente. Procure experiência arquitetural, ferramentas boring e foco implacável. Refatorar sob pressão é 10x mais difícil que planejar bem desde o início.
E não menos importante 3 pessoas conseguem fazer muito mais do que você imagina.


