Gabriel Rocha
Projetos

STELLA

Plataforma web de acompanhamento de tratamento de FIV para pacientes da Clínica AMARE, com agenda, medicações e diário.

2026RepositórioDemo

O STELLA é uma aplicação web de acompanhamento de tratamento de Fertilização In Vitro (FIV), feita em equipe (Grupo 10 da CESAR School) para pacientes da Clínica AMARE. A plataforma centraliza consultas, exames, medicações, sintomas e materiais educativos da jornada, devolvendo à paciente controle sobre o próprio tratamento. Nesta página eu detalho a API Django, que é onde está a maior parte da lógica do sistema, e dou uma visão rápida do front-end.

Arquitetura

O monorepo tem backend/, frontend/ e e2e/:

Modelos

O app Django tratamento concentra os modelos:

Um signal garante que todo usuário novo já nasça com perfil:

@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
    if created:
        UserProfile.objects.create(user=instance)

Autenticação

O login é um endpoint próprio (/api/auth/login/) em vez do view padrão do Django REST, porque o login é por email. Ele acha o usuário pelo email (case-insensitive), autentica com a senha e devolve um token do authtoken:

user = User.objects.filter(email__iexact=email).first()
if user is None:
    return Response({'detail': 'E-mail ou senha inválidos.'},
                    status=status.HTTP_401_UNAUTHORIZED)

authenticated_user = authenticate(request, username=user.username, password=password)
if authenticated_user is None:
    return Response({'detail': 'E-mail ou senha inválidos.'},
                    status=status.HTTP_401_UNAUTHORIZED)

token, _ = Token.objects.get_or_create(user=authenticated_user)
return Response({'token': token.key, 'user': { ... }})

As views protegidas usam TokenAuthentication + IsAuthenticated, e o front manda o token no header.

As views da API

O perfil (UserProfileRetrieveUpdateAPIView) devolve o usuário com o perfil aninhado e, no PATCH/PUT, atualiza os dois: primeiro o User, depois o UserProfile criado sob demanda com get_or_create. A foto tem um endpoint separado que exige o arquivo em request.FILES.

O diário é o único recurso claramente por usuário: a view filtra pelos check-ins do usuário logado e salva com paciente=self.request.user:

class DiarioListCreate(generics.ListCreateAPIView):
    serializer_class = DiarioSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return Diario.objects.filter(paciente=self.request.user)

    def perform_create(self, serializer):
        serializer.save(paciente=self.request.user)

Já agenda, fases e medicações usam queryset = Model.objects.all() sem autenticação e sem dono, como um conjunto global compartilhado:

class EventoAgendaListCreate(generics.ListCreateAPIView):
    queryset = EventoAgenda.objects.all()
    serializer_class = EventoAgendaSerializer

O convite de co-piloto devolve o token na resposta para o React montar o link (/convite/<token>), e o ConviteCopilotoSerializer valida o nome_identificador.

Configuração

O settings.py segue o padrão de 12-factor: SECRET_KEY, DEBUG, ALLOWED_HOSTS e CORS vêm de variáveis de ambiente com fallbacks para o desenvolvimento. O banco, porém, tem o engine fixo em SQLite, só trocando o caminho via DB_PATH:

_db_path = os.environ.get('DB_PATH', str(BASE_DIR / 'db.sqlite3'))
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': _db_path,
    }
}

Os CSRF_TRUSTED_ORIGINS apontam para os domínios do backend e do front na Azure.

Testes, CI/CD e deploy

São 32 testes de API (Django APITestCase) e 23 testes E2E com Selenium. O GitHub Actions roda a suíte a cada push na main e automatiza o deploy: o front vai para a Azure Static Web Apps e o backend para o Azure App Service.

Particularidades e pontos que revisaria

  1. Escopo e autorização inconsistentes entre recursos. O diário é filtrado pelo usuário autenticado, mas FaseTratamento, EventoAgenda e Medicacao expõem objects.all() sem autenticação e sem dono. Para uma plataforma individual, esses dados deveriam ser por paciente; hoje qualquer requisição (até sem login) lista tudo. O contraste entre as duas abordagens fica claro no código e é o que eu trataria primeiro.

  2. Convite de co-piloto com usuário “mock”. O ConviteCopilotoCreate ignora quem está logado e grava o convite para User.objects.first():

    def perform_create(self, serializer):
        usuario_teste = User.objects.first()   # Mock user
        serializer.save(paciente=usuario_teste)

    É um atalho de desenvolvimento que ficou no código: em produção o convite não seria do paciente certo. Além disso, as permissões do co-piloto (perm_ver_*) ficam no modelo, mas a API não tem endpoints de leitura que as apliquem, o vínculo e as permissões existem, só o controle fino por permissão não.

  3. SQLite também na “produção”. O README fala em banco Azure, mas o engine é SQLite em qualquer ambiente, só muda o caminho do arquivo. Numa hospedagem que recria o disco em redeploy, o arquivo pode ser perdido; um Postgres gerenciado evitaria esse risco.

  4. Segredos com fallback arriscado. O SECRET_KEY tem um valor fixo de fallback e o DEBUG é True por padrão quando a env não vem. Em produção as variáveis são setadas, mas subir o app sem elas deixa o servidor com debug ligado e chave conhecida.

  5. Token fixo sem expiração. O login usa Token.objects.get_or_create, então o mesmo token vale para sempre, sem expiração nem rotação, e não há endpoint de logout que o invalide (só apagando na mão). Para um MVP passa, mas um JWT com expiração seria mais adequado.

  6. Media servida pelo próprio Django. A rota re_path(r'^media/...', serve) serve os uploads (fotos de perfil) direto pelo Django; em produção é comum delegar isso a um storage externo. A validação da foto também checa só a extensão, não o conteúdo nem o tamanho.

  7. Dependências órfãs no requirements.txt. django-ninja e pydantic aparecem listados, mas o código usa o Django REST Framework; provavelmente sobraram de uma tentativa anterior.

Pontos de melhoria