Atualizado em: 2026-07-25
erDiagram
User ||--o| GamificationProfile : has
GamificationProfile ||--o{ Submission : makes
GamificationProfile ||--o{ Transaction : logs
GamificationProfile ||--o{ GamificationProfileBadge : unlocks
GamificationProfile ||--o{ MissionSubmission : makes
GamificationProfile ||--o{ TrackEnrollment : enrolls
GamificationProfile ||--o{ TrackItemCompletion : completes
GamificationProfile ||--o{ CourseReview : writes
GamificationProfile ||--o{ TrackSuggestion : suggests
GamificationProfile ||--o{ ContributionReport : reports
Activity ||--o{ Submission : targets
Badge ||--o{ GamificationProfileBadge : owned_by
Mission ||--o{ MissionSubmission : receives
User ||--o{ Submission : "reviews (Moderator)"
User ||--o{ Event : organizes
User ||--o{ EventSubscription : subscribes
User ||--o{ Notification : receives
User ||--o| NotificationPreference : configures
LearningTrack ||--o{ TrackSection : contains
LearningTrack ||--o{ TrackItem : contains
LearningTrack ||--o{ TrackEnrollment : enrolled_in
LearningTrack ||--o| LearningTrack : "requires (pre-req)"
TrackSection ||--o{ TrackItem : contains
TrackItem ||--o{ TrackItemCompletion : completed_by
Course ||--o{ CourseReview : reviewed_by
Course ||--o{ TrackItemCourse : linked_in
Event ||--o{ EventSubscription : has
RankingSnapshot }o--|| GamificationProfile : snapshots
User {
int id PK
string email
string password
string firstName
string lastName
enum role "user | admin | moderator"
enum status "active | banned"
int lastNotifiedLegalVersion "Nullable"
}
GamificationProfile {
uuid id PK
int userId FK "Unique"
string username "Unique (@handle)"
string githubUsername "Nullable"
string bannerPreset "Default: default"
string avatarConfig "Nullable - JSON config do avatar"
boolean showFullName "Default: false"
int totalXp
int currentMonthlyXp
int currentYearlyXp
int gratitudeTokens "Saldo disponivel para doar"
int gratitudeTokensReceived "Contador historico de tokens recebidos"
int journeyXp "XP de Jornada via trilhas"
string currentLevel "Computed from totalXp"
}
Activity {
uuid id PK
string title
string description "Markdown"
int fixedReward "XP concedido na aprovacao"
boolean isHidden "Se nao aparece no catalogo publico"
string secretCode "Nullable - slug para eventos/QR Code"
boolean requiresProof "Se exige proofUrl na submissao"
boolean requiresDescription "Se exige descricao na submissao"
int cooldownHours "Anti-farming"
json effortTiers "Nullable - array de {level: P|M|G|EPICO, label, example, xp}"
boolean isFreeform "Default: false"
datetime createdAt
datetime updatedAt
}
Submission {
uuid id PK
uuid profileId FK
uuid activityId FK
string proofUrl "Nullable"
string description "Markdown, max 2000 chars, nullable"
enum status "PENDING | APPROVED | REJECTED"
string feedback "Nullable"
int awardedXp
int reviewerId FK "Nullable -> User"
datetime reviewedAt "Nullable"
uuid trackItemId FK "Nullable - marco de trilha vinculado"
boolean isTestOut "Default: false - pulou o marco autodeclarando dominio"
enum contributionKind "COMMUNITY_ACTIVITY | TRACK_PROGRESS"
datetime createdAt
datetime updatedAt
}
Transaction {
uuid id PK
uuid profileId FK
int amount "Positivo ou negativo"
enum category "XP_REWARD | TOKEN_REWARD | TOKEN_TRANSFER | AUDITOR_REWARD | PENALTY | MANUAL_ADJUSTMENT | STORE_PURCHASE"
string description "Nullable - motivo legivel"
datetime createdAt
}
Badge {
uuid id PK
string name
string description
string imageUrl "Nullable"
enum category "MILESTONE | RANKING | PARTICIPATION | SPECIAL"
enum criteriaType "AUTOMATIC | MANUAL"
json criteriaConfig "Nullable - config do criterio automatico"
boolean isActive "Default: true"
datetime createdAt
datetime updatedAt
}
GamificationProfileBadge {
uuid profileId FK
uuid badgeId FK
datetime unlockedAt
}
Mission {
uuid id PK
string title
string description "Markdown, nullable"
string requirements "Markdown, nullable"
int xpReward "XP concedido ao vencedor"
enum status "OPEN | CLOSED"
uuid winnerId "Nullable - profileId do vencedor"
boolean isSecret "Default: false"
datetime createdAt
datetime updatedAt
}
MissionSubmission {
uuid id PK
uuid missionId FK
uuid profileId FK
string proofUrl "Nullable"
string description "Markdown, max 2000 chars, nullable"
enum status "PENDING | APPROVED | REJECTED"
string feedback "Nullable"
int awardedXp
int reviewerId FK "Nullable -> User"
datetime reviewedAt "Nullable"
datetime createdAt
datetime updatedAt
}
LearningTrack {
uuid id PK
string slug "Unique"
string title
string description "Nullable"
string area "Ex: backend, frontend"
enum tier "ALICERCE | PILAR | ARCO"
enum status "DRAFT | PUBLISHED | ARCHIVED"
uuid requiresTrackId FK "Nullable - pre-requisito"
datetime createdAt
datetime updatedAt
}
TrackSection {
uuid id PK
uuid trackId FK
string title
string description "Nullable"
float position "Indice fracionario de ordenacao"
enum status "ACTIVE | ARCHIVED"
uuid badgeId FK "Nullable - badge concedido ao concluir etapa"
datetime createdAt
datetime updatedAt
}
TrackItem {
uuid id PK
uuid trackId FK
uuid sectionId FK
enum type "RESOURCE | TEXT | PROOF | COURSE_COMPLETION | EVENT | MISSION | CHECKPOINT"
string title
string body "Nullable - texto inline do marco"
float position "Indice fracionario de ordenacao"
enum status "ACTIVE | ARCHIVED"
enum proofFormat "LINK | PHOTO | EITHER"
boolean isOptional "Default: false"
boolean allowsTestOut "Default: false - permite pular com prova"
int journeyXp "XP de Jornada concedido ao concluir"
boolean grantsCommunityXp "Default: false"
int communityXpReward "XP de Comunidade quando grantsCommunityXp=true"
uuid activityId FK "Nullable"
uuid missionId FK "Nullable"
uuid courseId FK "Nullable"
json config "Nullable - configuracao especifica do tipo"
datetime createdAt
datetime updatedAt
}
TrackEnrollment {
uuid id PK
uuid trackId FK
uuid profileId FK "Unique com trackId"
enum status "ACTIVE | COMPLETED | ABANDONED"
datetime startedAt
datetime completedAt "Nullable"
datetime createdAt
datetime updatedAt
}
TrackItemCompletion {
uuid id PK
uuid itemId FK "Unique com profileId"
uuid profileId FK
enum status "COMPLETED | SKIPPED_TESTOUT | IN_REVIEW"
uuid submissionId FK "Nullable"
int awardedJourneyXp
datetime completedAt
datetime createdAt
datetime updatedAt
}
TrackSuggestion {
uuid id PK
uuid profileId FK
uuid trackId FK "Nullable"
string title "Nullable - nome sugerido para trilha nova"
string message
enum status "PENDING | REVIEWED"
uuid reviewedByProfileId FK "Nullable"
datetime reviewedAt "Nullable"
datetime createdAt
datetime updatedAt
}
Course {
uuid id PK
string title
string description "Nullable"
string provider "Nullable - ex: Udemy, Coursera"
string url
boolean isFree "Default: true"
numeric price "Nullable"
string language "Nullable"
uuid submittedByProfileId FK "Nullable"
enum status "PENDING | VERIFIED | REJECTED"
numeric averageRating "Nullable - media 1-5 com 2 casas decimais"
int totalReviews "Nullable"
datetime createdAt
datetime updatedAt
}
CourseReview {
uuid id PK
uuid courseId FK "Unique com profileId"
uuid profileId FK
int rating "1-5"
string comment "Nullable"
boolean provenCompletion "Default: false"
datetime createdAt
datetime updatedAt
}
TrackItemCourse {
uuid id PK
uuid trackItemId FK "Unique com courseId"
uuid courseId FK
uuid submittedByProfileId FK "Nullable"
datetime createdAt
}
Event {
uuid id PK
string title "Max 150 chars"
string description
enum category "MEETUP | WORKSHOP | HACKATHON | PALESTRA | CURSO | OUTRO"
enum modality "ONLINE | PRESENCIAL | HIBRIDO"
datetime startAt
datetime endAt "Nullable"
string location "Nullable - endereco fisico"
string locationMapUrl "Nullable - link do mapa"
string onlineUrl "Nullable - link da transmissao"
string externalUrl "Nullable - pagina oficial"
enum status "PENDING | APPROVED | REJECTED | CANCELLED"
string rejectionReason "Nullable"
int organizerId FK "-> User"
int reviewerId FK "Nullable -> User"
datetime reviewedAt "Nullable"
uuid coverImageId FK "Nullable -> File"
datetime createdAt
datetime updatedAt
}
EventSubscription {
uuid id PK
uuid eventId FK "Unique com userId"
int userId FK
datetime createdAt
}
RankingSnapshot {
uuid id PK
uuid profileId FK "Unique com periodType+periodKey"
enum periodType "monthly | annual"
string periodKey "Ex: 2026-07"
int position
int xpAtSnapshot
datetime createdAt
}
Notification {
uuid id PK
int userId FK
enum type "SUBMISSION_APPROVED | MISSION_WON | SUBMISSION_REJECTED | CONTRIBUTION_REPORT_UPHELD | CONTRIBUTION_REPORT_RECEIVED | TRACK_MILESTONE_APPROVED | TRACK_BADGE_GRANTED | LEGAL_DOCUMENT_UPDATED"
string title
string body
boolean isRead "Default: false"
datetime readAt "Nullable"
string relatedId "Nullable"
datetime createdAt
}
NotificationPreference {
uuid id PK
int userId FK "Unique"
boolean emailOnSubmissionApproved "Default: true"
boolean emailOnMissionWon "Default: true"
}
ContributionReport {
uuid id PK
uuid reporterProfileId FK
uuid submissionId FK
string reason
enum status "PENDING | UPHELD | DISMISSED"
uuid reviewedByProfileId FK "Nullable"
datetime reviewedAt "Nullable"
datetime createdAt
datetime updatedAt
}
Focado estritamente na identidade digital do membro. Gerenciado pelo módulo de autenticação do boilerplate NestJS.
role(Enum:user,admin,moderator) — controla acesso às rotas protegidasstatus(Enum:active,banned) — membros banidos não conseguem logarlastNotifiedLegalVersion(Int, Nullable) — versão do documento legal mais recente para o qual o usuário já foi notificado
A representação gamificada do membro da comunidade Devs Tocantins.
username(String, Unique) — @handle para menções e transferências de tokensgithubUsername(String, Nullable) — exibido no perfil públicobannerPreset(String) — preset de banner visual no perfil (ex:default,gold)avatarConfig(String, Nullable) — configuração JSON do avatar personalizadoshowFullName(Boolean) — setrue, exibe nome completo no perfil público em vez de apenas o usernametotalXp(Int) — XP histórico acumulado; base para o level e o ranking globalcurrentMonthlyXp(Int) — XP do ciclo atual; reseta dia 1 de cada mês via croncurrentYearlyXp(Int) — XP do ano corrente; reseta dia 1 de janeiro via crongratitudeTokens(Int) — cota disponível para transferir; reseta dia 1 de cada mêsgratitudeTokensReceived(Int) — contador histórico total de tokens recebidos (distinto degratitudeTokens, que é o saldo disponível para doar)journeyXp(Int) — XP de Jornada, acumulado ao completar marcos em trilhas de aprendizado (cresce monotonicamente, nunca reseta)currentLevel(String) — calculado a partir detotalXp:
| Nível | XP mínimo |
|---|---|
| Novato | 0 |
| Contribuidor | 500 |
| Colaborador Ativo | 2.000 |
| Referência | 6.000 |
| Mentor | 15.000 |
| Lenda | 35.000 |
Atividades pré-mapeadas disponíveis para submissão.
description— markdown; renderizado com MarkdownContent no frontendfixedReward(Int) — XP concedido ao aprovar; o moderador pode ajustar na revisãoisHidden(Boolean) — setrue, não aparece emGET /activities; só viasecretCodesecretCode(String, Nullable) — slug único para acesso offline/QR Codes em eventosrequiresProof(Boolean) — se exige proofUrl na submissãorequiresDescription(Boolean) — se exige descrição textual na submissãocooldownHours(Int) — sistema anti-farming; bloqueia nova submissão da mesma atividade pelo mesmo perfileffortTiers(JSON, Nullable) — array de faixas de esforço{ level: 'P'|'M'|'G'|'EPICO', label, example, xp }; senull, a atividade usa XP fixo (fixedReward)isFreeform(Boolean) — setrue, é a atividade genérica de formato livre
Quando o usuário executa uma Activity e solicita reconhecimento. A mesma
tabela também registra provas/test-out de marcos de trilha (trackItemId
preenchido) — o pipeline de moderação é compartilhado, mas as duas coisas
são semanticamente diferentes (ver contributionKind abaixo).
description(String, Nullable) — markdown sanitizado (max 2000 chars); aceita apenas ASCII imprimível + Latin Extended (sem emojis/Unicode especial)awardedXp(Int) — XP concedido (geralmente herda deActivity.fixedReward, mas o moderador pode sobrescrever)reviewerId(Int, FK -> User) — ID do moderador que revisoutrackItemId(UUID, FK -> TrackItem, Nullable) — quando a submissão é feita no contexto de um marco de trilhaisTestOut(Boolean) —truequando o usuário pulou o marco autodeclarando domínio (nunca gera XP, semprestatus = APPROVEDimediatamente, sem moderação)contributionKind(EnumSubmissionContributionKind) — formaliza a distinção que antes só existia implicitamente pela nulidade detrackItemId:COMMUNITY_ACTIVITY— contribuição real à comunidade (trackItemIdnulo). Conta para selos de contribuição (ex: "Primeira Missão") e aparece na categoria "Voluntariado" do perfil público.TRACK_PROGRESS— progresso pessoal de uma trilha de aprendizado (prova aprovada ou test-out). XP é para o próprio usuário, não é contribuição para a comunidade — nunca deve contar para selos de contribuição, mesmo quandostatus = APPROVED.
- Ao aprovar uma submissão
COMMUNITY_ACTIVITY: geraTransaction(XP_REWARD)para o submitter, credita XP no perfil, geraTransaction(AUDITOR_REWARD)para o moderador
Motor financeiro dos pontos. Toda mutação no perfil gera uma Transaction.
amount(Int) — positivo (crédito) ou negativo (débito)category(EnumTransactionCategoryEnum):XP_REWARD— XP de atividade/curso aprovadoTOKEN_REWARD— XP ganho ao receber tokens de gratidão de outro membroTOKEN_TRANSFER— débito/crédito degratitudeTokensna transferência entre membrosAUDITOR_REWARD— XP ganho pelo moderador ao revisar uma submissãoPENALTY— XP deduzido pelo admin via modal de penalidadeMANUAL_ADJUSTMENT— ajuste manual de XP/tokens pelo adminSTORE_PURCHASE— reservado para uma futura loja de resgate de tokens (categoria existe no enum, sem uso ativo hoje)
description(String, Nullable) — motivo legível da transação
category(Enum):MILESTONE— marcos de XP ou contribuições acumuladasRANKING— posições em rankings mensais/anuaisPARTICIPATION— participação em eventos ou missõesSPECIAL— badges manuais para casos excepcionais
criteriaType(Enum):AUTOMATIC— verificado pelo cron ou ao aprovar submissãoMANUAL— concedido pelo admin viaPOST /badges/grant
criteriaConfig(JSON, Nullable) — configuração para critérios automáticos:{ "type": "submissions_approved", "threshold": 10 } { "type": "total_xp", "threshold": 500 } { "type": "monthly_ranking_top", "threshold": 3 }submissions_approvedconta apenasSubmissioncomstatus = APPROVED,isTestOut = falseecontributionKind = COMMUNITY_ACTIVITY(badge-evaluator.service.ts). Test-out e provas de trilha aprovadas nunca contam para esse critério — não são contribuição para a comunidade, mesmo que gerem XP pessoal.
Tabela associativa Many-to-Many registrando quando cada badge foi desbloqueado por um perfil.
Desafio com recompensa de alto valor e vencedor único.
descriptionerequirements— markdown; renderizados no frontend com MarkdownContentstatus(Enum):OPEN(aceitando submissões) |CLOSED(encerrada)winnerId(UUID, Nullable) — profileId do vencedor; preenchido ao aprovar uma submissãoisSecret(Boolean) — setrue, não aparece emGET /missions- Ao aprovar uma submissão: define
winnerId, setastatus: CLOSED, creditaxpRewarddiretamente emtotalXp/currentMonthlyXpdo vencedor (sem gerar registro emTransaction), disparaNotification(MISSION_WON), e rejeita automaticamente todas as outras submissões pendentes da missão
Análoga à Submission, mas vinculada a uma Mission em vez de uma Activity.
description(String, Nullable) — markdown sanitizado, max 2000 charsawardedXp(Int) — herda deMission.xpRewardao aprovar- Ao aprovar: credita o XP da missão diretamente no perfil do vencedor e dispara
Notification(MISSION_WON)(sem registro emTransaction)
Representa uma trilha de aprendizado estruturada, organizada em etapas e marcos.
slug(String, Unique) — identificador URL-friendly (ex:backend-inicial)area(String) — área de conhecimento (ex:backend,frontend,devops)tier(Enum):ALICERCE(fundamentos) |PILAR(intermediário) |ARCO(avançado)status(Enum):DRAFT|PUBLISHED|ARCHIVEDrequiresTrackId(UUID, FK, Nullable) — trilha exigida como pré-requisito
Subdivisão de uma LearningTrack agrupando marcos relacionados.
position(Float) — índice fracionário para ordenação flexível dentro da trilhastatus(Enum):ACTIVE|ARCHIVEDbadgeId(UUID, FK, Nullable) — badge concedido automaticamente ao concluir todos os marcos da etapa
Unidade atômica dentro de uma etapa — cada marco é uma tarefa, leitura, prova ou checkpoint.
type(Enum):RESOURCE— link externo ou material de referênciaTEXT— marco de leitura longa em markdown (conteúdo no campobody)PROOF— exige envio de prova (submissão com comprovante)COURSE_COMPLETION— conclusão de um curso vinculadoEVENT— participação em evento vinculadoMISSION— conclusão de uma missão vinculadaCHECKPOINT— marco automático/verificação
proofFormat(Enum):LINK|PHOTO|EITHER— formato aceito de comprovante para marcos PROOFbody(String, Nullable) — texto inline do marco; usado principalmente pelo tipoTEXTposition(Float) — índice fracionário para ordenação dentro da etapaisOptional(Boolean) — setrue, não é obrigatório para concluir a etapaallowsTestOut(Boolean) — setrue, permite ao aluno pular o marco provando domíniojourneyXp(Int) — XP de Jornada concedido ao concluirgrantsCommunityXp(Boolean) — setrue, também credita XP de ComunidadecommunityXpReward(Int) — valor de XP de Comunidade quandograntsCommunityXpétrueactivityId/missionId/courseId(UUID, Nullable) — vínculos opcionais com atividade, missão ou cursoconfig(JSON, Nullable) — configuracao especifica do tipo (critérios de aceitação, URL do recurso, etc.)
Registra a inscrição de um perfil em uma trilha de aprendizado.
- Constraint unique em
(trackId, profileId)— um perfil só pode estar matriculado uma vez por trilha status(Enum):ACTIVE|COMPLETED|ABANDONEDstartedAt/completedAt— timestamps do ciclo de vida da matrícula
Registra quando um perfil conclui um marco específico de uma trilha.
- Constraint unique em
(itemId, profileId)— cada marco só pode ser concluído uma vez por perfil status(Enum):COMPLETED— concluído normalmenteSKIPPED_TESTOUT— pulado via test-out (provou domínio)IN_REVIEW— aguardando revisão do moderador
submissionId(UUID, Nullable) — submissão vinculada quando a prova passou pela moderaçãoawardedJourneyXp(Int) — XP de Jornada efetivamente concedido
Permite que membros sugiram melhorias ou novas trilhas.
trackId(UUID, Nullable) — referência a uma trilha existente (quando é sugestão para trilha existente)title(String, Nullable) — nome sugerido para uma trilha nova (quandotrackIdé nulo)message(String) — texto da sugestãostatus(Enum):PENDING|REVIEWED
Catálogo de cursos externos compartilhados pela comunidade.
provider(String, Nullable) — plataforma de origem (ex: Udemy, Coursera)url(String) — link para o cursoisFree(Boolean) — se o curso é gratuitoprice(Numeric, Nullable) — preço quando pagolanguage(String, Nullable) — idioma do cursosubmittedByProfileId(UUID, FK, Nullable) — perfil que sugeriu o cursostatus(Enum):PENDING(aguardando verificação) |VERIFIED(aprovado) |REJECTEDaverageRating(Numeric, Nullable) — média das avaliações (1-5), precisão de 2 casas decimaistotalReviews(Int, Nullable) — total de avaliações recebidas
Avaliação de um curso por um membro da comunidade.
- Constraint unique em
(courseId, profileId)— cada perfil avalia um curso uma única vez rating(Int) — nota de 1 a 5comment(String, Nullable) — comentário textualprovenCompletion(Boolean) — se o autor comprovou ter concluído o curso
Tabela associativa que vincula um marco de trilha do tipo COURSE_COMPLETION a um curso específico.
- Constraint unique em
(trackItemId, courseId) submittedByProfileId(UUID, FK, Nullable) — perfil que sugeriu o vínculo
Eventos organizados pela comunidade com fluxo de moderação.
category(Enum):MEETUP|WORKSHOP|HACKATHON|PALESTRA|CURSO|OUTROmodality(Enum):ONLINE|PRESENCIAL|HIBRIDOstatus(Enum):PENDING|APPROVED|REJECTED|CANCELLEDlocation/locationMapUrl— endereço físico e link do mapa (para presenciais/híbridos)onlineUrl— link da transmissão/reunião (para online/híbridos)externalUrl— página oficial ou link de inscrição externoorganizerId(Int, FK -> User) — criador/organizador do eventoreviewerId(Int, FK -> User, Nullable) — moderador que revisoucoverImageId(UUID, FK -> File, Nullable) — imagem de capa do evento- Índice composto em
(status, startAt)para consultas de eventos futuros aprovados
Registra a inscrição de um usuário em um evento.
- Constraint unique em
(eventId, userId)— um usuário só se inscreve uma vez por evento - Cascade delete: removida quando o evento é deletado
Captura posições históricas de ranking para preservar o hall da fama.
periodType(Enum):monthly|annualperiodKey(String) — identificador do período (ex:2026-07para mensal,2026para anual)position(Int) — posição no ranking naquele períodoxpAtSnapshot(Int) — XP do perfil no momento da captura- Constraint unique em
(profileId, periodType, periodKey) - Índices em
(periodType, periodKey)e(profileId)para consultas eficientes
Notificações in-app para o usuário.
type(Enum):SUBMISSION_APPROVED|MISSION_WON|SUBMISSION_REJECTED|CONTRIBUTION_REPORT_UPHELD|CONTRIBUTION_REPORT_RECEIVED|TRACK_MILESTONE_APPROVED|TRACK_BADGE_GRANTED|LEGAL_DOCUMENT_UPDATEDisRead(Boolean) — se o usuário já leureadAt(Datetime, Nullable) — timestamp da leiturarelatedId(String, Nullable) — ID da entidade relacionada (submissão, badge, etc.)
Preferências individuais de notificação por e-mail.
- Constraint unique em
userId— um registro por usuário emailOnSubmissionApproved(Boolean) — receber e-mail quando submissão é aprovadaemailOnMissionWon(Boolean) — receber e-mail quando vence uma missão
Permite que membros denunciem submissões suspeitas para revisão.
reporterProfileId(UUID, FK) — perfil que fez a denúnciasubmissionId(UUID, FK) — submissão denunciadareason(String) — motivo da denúnciastatus(Enum):PENDING|UPHELD(confirmada) |DISMISSED(descartada)
Este módulo não possui entidade própria no banco. Funciona como um serviço cron (LegalDocumentsNotificationCronService) que verifica se há atualizações em documentos legais (termos de uso, política de privacidade) e dispara notificações do tipo LEGAL_DOCUMENT_UPDATED para usuários que ainda não foram notificados da versão mais recente. O controle de versão é feito via constantes no código e o campo lastNotifiedLegalVersion em User.
Este módulo não possui entidade própria no banco. Integra com a API Baileys (WhatsApp Web) para envio de mensagens automáticas. As configurações relevantes vivem no .env (WHATSAPP_*). O campo whatsappNumber de contato dos membros não é armazenado diretamente — o módulo consome dados do perfil do usuário e das preferências de notificação para envio quando configurado.
Este módulo não possui entidade própria no banco de dados. O endpoint GET /api/v1/profile-portfolio/:profileId agrega dados de TrackItemCompletion (marcos PROOF concluídos) para montar um portfólio público de comprovantes do membro, retornando um DTO ProofPortfolioItem com referências à trilha, etapa e marco.