nexus-app/src/agents/manifest/nexus-patient-onboarding/SKILL.md
# NEXUS Patient Onboarding - Skill **Fluxo Completo de Onboarding de Pacientes** > **Ativa em:** paciente, cadastro, onboarding, NEXUS ID, NXID, consentimento, LGPD, convite, magic link, equipe, roles, permissões, ficha do paciente, novo paciente, baseline, avaliação inicial, primeiro protocolo --- ## Visão Geral Este skill cobre o **ciclo completo de onboarding de pacientes** no Ecossistema NEXUS, desde a geração do NEXUS ID até a primeira avaliação de baseline e início do primeiro protocol
npx skillsauth add JoaoBiomed/NEXUSEcosystem nexus-app/src/agents/manifest/nexus-patient-onboardingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fluxo Completo de Onboarding de Pacientes
Ativa em: paciente, cadastro, onboarding, NEXUS ID, NXID, consentimento, LGPD, convite, magic link, equipe, roles, permissões, ficha do paciente, novo paciente, baseline, avaliação inicial, primeiro protocolo
Este skill cobre o ciclo completo de onboarding de pacientes no Ecossistema NEXUS, desde a geração do NEXUS ID até a primeira avaliação de baseline e início do primeiro protocolo. O sistema é multi-tenant, com suporte a consentimento granular LGPD, controle de roles, e agregação de dados de múltiplos módulos.
Formato Novo: NX-AANNNN (ex: NX-260001)
AA = últimos 2 dígitos do ano (2026 → 26)NNNN = sequencial de 4 dígitos (0001–9999)Formato Legacy: NXID-XXXXX (ex: NXID-00042)
Arquivo: /src/services/firestore/patients.service.ts
export const generateNexusId = async (): Promise<string> => {
try {
const year = new Date().getFullYear().toString().slice(-2);
const counterRef = doc(db, 'system_counters', `patients_nexusid_${year}`);
const newIdNumber = await runTransaction(db, async (transaction) => {
const counterDoc = await transaction.get(counterRef);
let nextNum = 1;
if (counterDoc.exists()) {
const currentVal = counterDoc.data()?.lastSequential || 0;
nextNum = currentVal + 1;
}
// Atomic write — sem race conditions
transaction.set(counterRef, { lastSequential: nextNum }, { merge: true });
return nextNum;
});
return `NX-${year}${String(newIdNumber).padStart(4, '0')}`;
} catch (error) {
throw new FirestoreError(`Falha ao gerar NEXUS ID: ...`);
}
};
Benefícios:
runTransaction garante lock nativo// Valida ambos formatos
export function isValidNexusId(nexusId: string): boolean {
const newFormat = /^NX-\d{6}$/; // NX-260001
const legacyFormat = /^NXID-\d{4,5}$/; // NXID-00001
return newFormat.test(nexusId) || legacyFormat.test(nexusId);
}
// Extrai sequencial: NX-260001 → 1, NXID-00042 → 42
export function extractNexusIdNumber(nexusId: string): number | null { ... }
// Extrai ano: NX-260001 → 2026 (legacy retorna null)
export function extractNexusIdYear(nexusId: string): number | null { ... }
export interface Patient {
// Identidade
id: string; // Firebase Doc ID
nexus_id: string; // NXID-XXXXX ou NX-AANNNN
// Dados Demográficos
name: string;
searchTerm?: string; // name.toLowerCase() para busca
email?: string;
phone?: string;
cpf?: string;
birthDate: Date;
gender: 'M' | 'F' | 'O';
// Antropométricos
height?: number; // cm
weight?: number; // kg
bloodType?: string; // A+, O-, etc.
// Endereço
address?: {
street: string;
number: string;
complement?: string;
neighborhood: string;
city: string;
state: string;
zipCode: string;
};
// Contato de Emergência
emergencyContact?: {
name: string;
relationship: string;
phone: string;
email?: string;
};
// Status
status: 'ACTIVE' | 'INACTIVE' | 'PENDING' | 'ARCHIVED';
riskLevel?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
// Clínica
clinicId?: string; // Multi-tenant
assignedDoctorId?: string;
createdBy?: string; // User que criou
// Timestamps
createdAt: Date;
updatedAt: Date;
}
Arquivo: /src/modules/Patient/components/PatientFormWizard.tsx
Steps:
Validação:
async create(formData: PatientFormData, createdBy: string, clinicId?: string): Promise<Patient> {
// Gera NEXUS ID atomicamente
// Mapeia alergias com ID único
// Seta status = ACTIVE, riskLevel = LOW
// Marca allergiesReviewed = true se noKnownAllergies OU tem alérgias
// Marca conditionsReviewed = true se noKnownConditions OU tem condições
// Registra consentimento geral (legacy)
return createPatientFirestore(patientData);
}
// No Patient:
currentMedicationsStatus?: 'NOT_SET' | 'NONE' | 'HAS_MEDICATIONS';
sleepHoursBaseline?: number; // horas/noite
stressLevelBaseline?: 1 | 2 | 3 | 4 | 5; // escala
exerciseFrequencyBaseline?: 'SEDENTARY' | 'LIGHT' | 'MODERATE' | 'HIGH' | 'ATHLETE';
smokingStatusBaseline?: 'never' | 'former' | 'current';
alcoholBaseline?: 'none' | 'occasional' | 'moderate' | 'heavy';
familyHistoryReviewed?: boolean;
onboardingCompletedAt?: Date;
Arquivo: /src/ui/pages/public/PatientCheckin.tsx
Fluxo:
checkinToken (magic link com expiração)https://nexus.clinic/checkin/{token}collection('checkinTokens', token)USEDToken Validation:
const tokenRef = doc(db, 'checkinTokens', token);
const tokenDoc = await getDoc(tokenRef);
// Validações:
// - Documento existe?
// - status !== 'USED'?
// - expiresAt > agora?
Dados Coletados:
export type ConsentModule =
| 'endoinject' // Injeção de endoterapia
| 'labpro' // Laboratório
| 'imeddis' // Telemedicina
| 'bodyscan' // Scan corporal
| 'lifestyle' // Dados de estilo de vida
| 'pharma' // Farmácia
| 'sample' // Coleta de amostra
| 'aesthetics' // Procedimentos estéticos
| 'cortex_ai' // IA/Cortex
| 'data_sharing'; // Compartilhamento
export type ConsentPurpose =
| 'treatment' // Art. 7, II — Tutela à saúde
| 'sensitive_data' // Art. 11 — Dados sensíveis
| 'ai_processing' // Art. 20 — Decisão automatizada
| 'data_portability'// Art. 18, V — Portabilidade
| 'research'; // Art. 7, IV — Pesquisa
export interface ModuleConsent {
module: ConsentModule;
purpose: ConsentPurpose;
given: boolean; // Consentimento ativo?
grantedAt?: Date; // Quando foi concedido
revokedAt?: Date; // Quando foi revogado
grantedBy?: string; // Nome do paciente
collectedBy?: string; // ID do profissional
version: string; // "1.0" — versão do texto
}
// Array dentro de Patient:
consentGranular?: ModuleConsent[];
Arquivo: /src/services/firestore/consent.service.ts
// Grant — Conceder consentimento
export const grantModuleConsent = async (
patientId: string,
module: ConsentModule,
purpose: ConsentPurpose,
collectedBy: string,
version: string = '1.0'
): Promise<void> => {
// 1. Busca paciente
// 2. Localiza/cria entrada em consentGranular[]
// 3. Seta given=true, grantedAt=now
// 4. Cria log de auditoria
// 5. Atualiza patient.consentGranular
}
// Revoke — Revogar consentimento
export const revokeModuleConsent = async (
patientId: string,
module: ConsentModule,
purpose: ConsentPurpose,
revokedBy: string
): Promise<void> => {
// Seta given=false, revokedAt=now
}
// Check — Verificar consentimento ativo
export const hasModuleConsent = (
consentGranular: ModuleConsent[] | undefined,
module: ConsentModule,
purpose: ConsentPurpose
): boolean => {
const entry = consentGranular?.find(c =>
c.module === module && c.purpose === purpose
);
return entry?.given === true;
}
// Listar todos ativos
export const getActiveConsents = (
consentGranular: ModuleConsent[] | undefined
): ModuleConsent[] => {
return consentGranular?.filter(c => c.given) || [];
}
Agrega dados de TODOS os módulos em um contexto unificado (PatientFullContext) para:
Arquivo: /src/services/PatientContextAggregator.ts
const results = await Promise.allSettled([
this.getMedicationContext(patientNexusId),
this.getLabContext(patientNexusId, patientData.gender),
this.getBodyScanContext(patientNexusId, patientData.weight, patientData.height),
this.getLifestyleContext(patientNexusId),
this.getCrossModuleContext(patientNexusId, patientData.id)
]);
// Cada falha usa fallback — não quebra o fluxo inteiro
MedicationContext:
LabContext:
BodyScanContext:
LifestyleContext:
CrossModuleContext:
const completenessBreakdown = {
patient: calculatePatientCompleteness(patientData), // 25%
lab: labContext.latestResults ? 100 : 0, // 30%
medications: 100, // 15%
bodyScan: currentScan ? 100 : (weight && height ? 50 : 0), // 15%
lifestyle: lifestyleContext.wellnessScore > 0 ? 100 : 0 // 15%
};
const dataCompleteness = Math.round(
(completenessBreakdown.patient * 0.25) +
(completenessBreakdown.lab * 0.30) +
(completenessBreakdown.medications * 0.15) +
(completenessBreakdown.bodyScan * 0.15) +
(completenessBreakdown.lifestyle * 0.15)
);
interface User {
id: string;
email: string;
displayName: string;
role: 'ADMIN' | 'DOCTOR' | 'RECEPTIONIST' | 'PENDING';
clinicId: string;
createdAt: Date;
}
Permissões por Role:
| Role | Criar Paciente | Editar Paciente | Ver Protocolos | Gerar Protocolo | Convidar | |------|---|---|---|---|---| | ADMIN | ✅ | ✅ | ✅ | ✅ | ✅ | | DOCTOR | ✅ | ✅ | ✅ | ✅ | ❌ | | RECEPTIONIST | ✅ | ⚠️* | ❌ | ❌ | ❌ | | PENDING | ❌ | ❌ | ❌ | ❌ | ❌ |
*RECEPTIONIST pode editar dados demográficos apenas
Arquivo: /src/ui/components/RoleGuard.tsx
interface RoleGuardProps {
allowedRoles: string[];
children: React.ReactNode;
}
export function RoleGuard({ allowedRoles, children }: RoleGuardProps) {
const user = useAuthStore(s => s.user);
if (!user || !allowedRoles.includes(user.role)) {
return <AccessDeniedUI />; // Exibe "Acesso Restrito"
}
return <>{children}</>;
}
// Uso:
<RoleGuard allowedRoles={['ADMIN', 'DOCTOR']}>
<ProtocolGenerationPage />
</RoleGuard>
Arquivo: /src/ui/pages/public/TeamInvite.tsx
Etapas:
ADMIN cria invite:
POST /api/team-invites com email, role, clinicIdteamInvites/{token} com expiração (e.g., 7 dias)Email enviado com link:
https://nexus.clinic/team-invite/{token}Usuário clica no link:
Formulário de Registro:
Submissão:
users/{uid} com role e clinicIdteamInvites/{token}.status = 'ACCEPTED'interface TeamInviteData {
email: string;
role: 'DOCTOR' | 'RECEPTIONIST';
clinicId: string;
status: 'PENDING' | 'ACCEPTED';
expiresAt: Timestamp;
createdAt?: Timestamp;
createdBy?: string;
}
// Armazenado em: db.collection('teamInvites').doc(token)
// Token = criado aleatoriamente (UUID v4 recomendado)
┌─────────┐ (onboarding) ┌────────┐ (baseline + ┌────────┐
│ PENDING │ ─────────────────► ACTIVE │ lab/scan) │PROTOCOL│
└─────────┘ └────────┘ ─────────────► READY │
└────────┘
hasModuleConsent(consentGranular, 'endoinject', 'treatment') = trueArquivo: /src/services/protocol/ProtocolContextBuilder.ts
// Transforma PatientFullContext em contexto estruturado para IA/Gemini
public buildFromPatient(patient: Patient, fullContext: PatientFullContext): ProtocolContext {
return {
nexusId: patient.nexus_id,
basicInfo: { name, age, gender, ...},
medicalHistory: { conditions, allergies, ... },
currentMedications: fullContext.medications,
labResults: fullContext.lab,
bodyComposition: fullContext.bodyScan,
lifestyle: fullContext.lifestyle,
dataCompleteness: fullContext.dataCompleteness,
crossModuleSignals: fullContext.crossModule,
riskAssessment: this.calculateRiskProfile(...)
};
}
Checkout de dados:
Patient + PatientContextAggregator.getFullContext()
→ PatientFullContext
Build context:
ProtocolContextBuilder.buildFromPatient(patient, fullContext)
→ ProtocolContext
AI Generation (Gemini):
GeminiProtocolService.generateProtocol(protocolContext)
→ Protocol JSON
Validação e Save:
Protocol → Firestore (collection='protocols')
ProtocolOrchestrator.setActive() → Marca como ativo
O patient armazenado no Firestore recebe enriquecimento em memória quando recuperado:
private enrichPatient(patient: Patient): Patient {
const birthDate = patient.birthDate instanceof Date
? patient.birthDate
: new Date(patient.birthDate);
const age = Math.floor(
(Date.now() - birthDate.getTime()) / (365.25 * 24 * 60 * 60 * 1000)
);
return {
...patient,
age, // Computed
conditions: patient.conditions || [],
modulesLinked: patient.modulesLinked || [],
consent: patient.consent || { given: false },
};
}
NÃO são persistidos no Firestore:
age — sempre recalculadocurrentMedications — buscado do medications.servicelatestLabs — buscado do labResults.servicebodyComposition — buscado do bodyScan.servicelifestyle — buscado do lifestyle.serviceHooks customizados para carregar e gerenciar dados de pacientes em componentes React.
interface PatientStore {
patients: Patient[];
selectedPatient: Patient | null;
loading: boolean;
error: string | null;
fetch: (clinicId?: string) => Promise<void>;
fetchOne: (id: string) => Promise<void>;
create: (data: PatientFormData) => Promise<Patient>;
update: (id: string, data: Partial<Patient>) => Promise<void>;
select: (patient: Patient) => void;
}
const usePatientStore = create<PatientStore>(...)
1. RECEPCIONISTA clica "Novo Paciente"
→ PatientFormWizard abre
2. Preenche Dados Demográficos (Step 1–3)
→ Sistema gera NEXUS ID atomicamente
→ Patient criado com status=PENDING
3. Médico faz Guardian Gate (Baseline Vital)
→ Gera checkinToken com magic link
→ Envia link para paciente via SMS/Email
4. PACIENTE clica link (PatientCheckin)
→ Valida token
→ Preenche Sleep, Stress, Hydration, Adherence
→ Submits em transação (token → USED)
5. MÉDICO completa cadastro
→ Alergias reviewed? Sim
→ Condições reviewed? Sim
→ Endereço preenchido
→ Patient status → ACTIVE
6. CONSENTIMENTO LGPD
→ Médico valida checkboxes (endoinject/treatment, lab/sensitive_data)
→ grantModuleConsent(patientId, 'endoinject', 'treatment', doctorId)
→ consentGranular[] atualizado
7. PRIMEIRA COLETA (opcional mas recomendado)
→ LabPro: solicita exames iniciais
→ Sample: agenda coleta
→ BodyScan: mede composição
8. PROTOCOL GENERATION
→ Médico clica "Gerar Protocolo"
→ Sistema chama PatientContextAggregator.getFullContext()
→ Agrupa dados de todos módulos
→ ProtocolContextBuilder.buildFromPatient()
→ GeminiProtocolService.generateProtocol()
→ Protocol salvo, status=ACTIVE
9. PACIENTE inicia primeiro protocolo
| Arquivo | Responsabilidade |
|---------|------------------|
| /src/types/PatientTypes.ts | Schema de Patient, Alergy, ModuleConsent |
| /src/modules/Patient/PatientService.ts | CRUD de pacientes |
| /src/services/firestore/patients.service.ts | Firestore ops, generateNexusId, fixDuplicates |
| /src/services/PatientContextAggregator.ts | Agregação 360° de dados |
| /src/modules/Patient/components/PatientFormWizard.tsx | Fluxo de registro em 6 steps |
| /src/ui/pages/public/PatientCheckin.tsx | Magic link para baseline (paciente) |
| /src/ui/pages/public/TeamInvite.tsx | Magic link para team (staff) |
| /src/services/firestore/consent.service.ts | Grant/revoke consentimento LGPD |
| /src/ui/components/RoleGuard.tsx | Proteção de rotas por role |
| /src/services/protocol/ProtocolContextBuilder.ts | Transform context para IA |
Q: Paciente com NEXUS ID duplicado?
fixDuplicateNexusIds(keepPatientId?) da patients.serviceQ: Token expirado ou já usado?
POST /api/checkin-tokens com patientIdQ: Como verificar consentimento de um módulo?
hasModuleConsent(patient.consentGranular, 'labpro', 'treatment')given === true e revokedAt === nullQ: Dados de baseline incompletos?
Q: Paciente vê "Acesso Restrito" em página?
RoleGuard.allowedRoles vs user.roleusers/{uid}Status: MVP de onboarding pronto para clínicos Última atualização: 2026-03-07 Versão: 1.0
development
Superdesign is a design agent specialized in frontend UI/UX design. Use this skill before implementing any UI that requires design thinking. Common commands: superdesign create-project --title "X" (setup project), superdesign create-design-draft --project-id <id> --title "Current UI" -p "Faithfully reproduce..." --context-file src/Component.tsx (faithful reproduction), superdesign iterate-design-draft --draft-id <id> -p "dark theme" -p "minimal" --mode branch --context-file src/Component.tsx (design variations), superdesign execute-flow-pages --draft-id <id> --pages '[...]' --context-file src/Component.tsx (extend to more pages). Supports line ranges: --context-file path:startLine:endLine
testing
Motor completo de geracão de protocolos clínicos do Ecossistema NEXUS. Use este skill SEMPRE que o usuário mencionar: protocolo clínico, gerar protocolo, NEXUS, Modus Operandi, EndoInject, LabPro, iMeddis, BodyScan3D, Lifestyle, TRH, objetivo terapêutico, trigger clínico, prescrição médica, farmacopeia, paciente NEXUS ID, contexto clínico, PDF de protocolo, pré-visualização de protocolo, ou qualquer tarefa relacionada a medicina personalizada e protocolos de saúde. Também use quando o usuário quiser entender a lógica clínica do NEXUS, validar regras de decisão, ou criar/editar conteúdo clínico para o ecossistema. Este skill é o cérebro clínico do NEXUS — se a conversa toca em saúde personalizada, protocolos ou o ecossistema NEXUS de qualquer forma, use-o.
testing
Sistema completo de gestão farmacêutica do Ecossistema NEXUS. Use este skill SEMPRE que o usuário mencionar: estoque, farmácia, lotes, validade, Stock-First, insumos, pharma, compostos disponíveis, ordem de compra, fornecedor, inventário, dispensação, movimentação de estoque, entrada de medicamento, saída de medicamento, ajuste de inventário, lote vencido, ou qualquer tarefa relacionada a gestão de medicamentos, vitaminas, suplementos e compostos no NEXUS. Este skill é a plataforma de farmácia do NEXUS — se o usuário menciona qualquer operação de estoque, prescrição baseada em disponibilidade, ou administração de insumos clínicos, use-o imediatamente.
development
# NEXUS Dev Engine **Versão:** 1.0.0 **Última atualização:** Março 2026 **Mantém:** Ecossistema NEXUS - MVP Funcional para Médicos --- ## Visão Geral Você é o **Dev Engine** do Ecossistema NEXUS. Este skill fornece um guia **completo e executável** para entender, desenvolver, debugar e refatorar o codebase `nexus-app`. NEXUS é uma plataforma SaaS clínica multitenant com: - **143.795 linhas de código** em React 18 + TypeScript + Vite - **451 arquivos** de componentes, stores, services, agent