Sistema completo de gestão farmacêutica do Ecossistema NEXUS Clinical. 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.
Sistema integrado de gestão de estoque farmacêutico do Ecossistema NEXUS. O Pharma Manager garante que toda prescrição seja validada contra estoque real em tempo real antes de ser ofertada ao paciente. Integra-se ao Protocol Engine, LabPro e iMeddis.
Princípio Central: Não prescrever o que não temos em estoque.
Quando o Protocol Engine gera uma prescrição, o Pharma Manager intercepta e verifica disponibilidade. Se indisponível:
| Coleção | Documentos | Função |
|---|---|---|
| pharma_stock | PharmaItem (id: compoundId) | Estoque consolidado por composto |
| pharma_batches | Batch | Rastreabilidade por lote + FIFO |
| pharma_movements | Movement | Auditoria imutável de movimentações |
| pharma_suppliers | Supplier | Cadastro de fornecedores |
| pharma_purchase_orders | PurchaseOrder | Workflow de compras |
| stock_reservations | StockReservation | Reservas ligadas a protocolos |
{
id: string; // Auto-gerado pelo Firestore
compoundId: string; // ID único do composto (ex: "cipionato-testosterona")
name: string; // Nome exibição (ex: "Cipionato de Testosterona")
category: PharmaCategory; // 'hormone' | 'vitamin' | 'medication' | 'supplement' | 'nutraceutical' | 'injectable'
// Estoque
quantityAvailable: number; // Quantidade física disponível
quantityReserved: number; // Quantidade reservada por protocolos não-dispensados
unit: string; // 'mg' | 'ml' | 'UI' | 'units' | 'caps' | 'g'
minStockLevel: number; // Limite mínimo para alerta
maxStockLevel?: number; // Capacidade máxima (opcional)
// Controle
status: StockStatus; // 'OK' | 'LOW' | 'OUT' | 'EXPIRED' | 'RESERVED'
// Metadados
createdAt: Date;
updatedAt: Date;
lastStockUpdate?: Date;
notes?: string;
}
{
id: string;
compoundId: string;
compoundName: string;
batchNumber: string; // Número único do lote
quantity: number; // Quantidade inicial
quantityRemaining: number; // Quantidade atual (FIFO decreases)
expiryDate: Date;
manufacturingDate: Date;
supplierId: string;
supplierName: string;
costPerUnit: number;
status: BatchStatus; // 'ACTIVE' | 'EXPIRED' | 'DEPLETED' | 'QUARANTINE'
purchaseOrderId?: string;
createdAt: Date;
updatedAt: Date;
}
{
id: string;
type: MovementType; // 'IN' | 'OUT' | 'ADJUSTMENT' | 'EXPIRED' | 'RESERVED' | 'CONSUMED'
compoundId: string;
compoundName: string;
batchId?: string;
batchNumber?: string;
quantity: number;
date: Date;
userId: string; // Quem realizou
userName: string;
notes: string;
protocolId?: string; // Se vinculado a protocolo
patientId?: string;
patientNexusId?: string;
auditHash: string; // Hash para auditoria
createdAt: Date;
// IMUTÁVEL: sem update/delete (compliance)
}
{
id: string;
orderNumber: string; // NEXUS-PO-XXXXX
supplierId: string;
supplierName: string;
items: PurchaseOrderItem[]; // Array de itens
status: PurchaseOrderStatus; // 'PENDING' | 'APPROVED' | 'SENT' | 'RECEIVED' | 'CANCELLED'
totalValue: number;
createdAt: Date;
createdBy: string;
updatedAt: Date;
receivedAt?: Date;
notes?: string;
}
1. [Admin/Doctor] Cria PurchaseOrder com itens e fornecedor
→ Status: PENDING
2. [Admin] Aprova ordem
→ Status: APPROVED
3. [Admin] Marca como enviada ao fornecedor
→ Status: SENT
4. [Admin/Doctor] Recebe estoque
→ receivePurchaseOrder() é chamado
→ Para cada item:
- Cria Batch (com batchNumber, validade, quantidade, custo)
- Atualiza quantityAvailable em pharma_stock
- Registra Movement type='IN'
→ Status: RECEIVED
Serviço: addStockWithBatch() em pharmaStock.service.ts
1. [Doctor] Gera protocolo com compostos
→ Pharma Manager verifica Stock-First
→ reserveStock() para cada composto com quantidade necessária
→ Status da reserva: 'active'
2. [Paciente/Staff] Retira estoque (dispensa)
→ consumeStockFromBatch() é chamado
→ getBatch FIFO + decrementQuantity do batch
→ Atualiza quantityAvailable em pharma_stock
→ Registra Movement type='OUT'
→ Update na reserva: status 'consumed'
Serviço: consumeStockFromBatch() em pharmaStock.service.ts
1. [Admin/Doctor] Realiza contagem física
→ Diferença descoberta (ex: -5 unidades)
2. Registra ajuste via recordMovement()
→ type='ADJUSTMENT'
→ notes='Contagem física: -5 unidades'
3. reconcileInventory() valida:
→ Quantidade esperada (soma de movimentos)
vs. Quantidade real (física)
Serviço: reconcileInventory() em pharmaMovements.service.ts
ANTES DE PRESCREVER:
1. Protocol Engine chama checkAvailability(compounds)
→ Retorna StockCheckResult[] com status de cada composto
2. Se canFulfill=false:
→ Busca substituto via findSubstitute()
→ Oferece ao médico com evidência bioequivalência
3. Se aceita prescrição:
→ reserveStock(compoundId, quantity, protocolId, patientId)
→ Cria StockReservation com expiração em 7 dias
→ Incrementa quantityReserved em pharma_stock
NO DISPENSA:
5. Consome estoque via consumeStockFromBatch()
→ Decrementa quantityReserved
SE PROTOCOLO CANCELADO:
6. releaseReservation(reservationId)
→ Libera quantityReserved
Serviço: checkAvailability(), reserveStock(), releaseReservation()
em pharmaStock.service.ts
Firestore Rules (firestore.rules):
match /pharma_stock/{itemId} {
allow read: if isDoctor(); // Leitura: Doctor+
allow write: if isDoctor(); // Escrita: Doctor+ (cria/atualiza estoque)
}
match /pharma_batches/{batchId} {
allow read: if isDoctor();
allow write: if isDoctor();
}
match /pharma_movements/{movementId} {
allow read: if isDoctor();
allow create: if isDoctor();
allow update: if false; // IMUTÁVEL (auditoria)
allow delete: if false; // IMUTÁVEL (auditoria)
}
match /pharma_suppliers/{supplierId} {
allow read: if isDoctor();
allow write: if isAdmin(); // Apenas Admin pode gerenciar fornecedores
}
match /pharma_purchase_orders/{orderId} {
allow read: if isDoctor();
allow create: if isDoctor(); // Doctor cria O.C.
allow update: if isDoctor(); // Doctor aprova/recebe
allow delete: if isAdmin(); // Admin deleta
}
match /stock_reservations/{reservationId} {
allow read: if isDoctor();
allow write: if isDoctor();
}
| Tipo | Origem | Significado |
|---|---|---|
| IN | Entrada | Recebimento de O.C., aumento de estoque |
| OUT | Saída | Dispensação, diminuição de estoque |
| ADJUSTMENT | Ajuste | Inventário físico, correção |
| EXPIRED | Expiração | Lote venceu, removido de uso |
| RESERVED | Reserva | Composto reservado por protocolo (não efetivo) |
| CONSUMED | Consumo | Composto foi efetivamente usado/dispensado |
Substitutos conhecidos para Stock-First (em pharmaStock.service.ts):
cipionato-testosterona → [enantato-testosterona, undecanoato-testosterona]
enantato-testosterona → [cipionato-testosterona, undecanoato-testosterona]
estradiol-valerato → [estradiol-benzoato, estradiol-cypionato]
vitamina-d3 → [vitamina-d2, calcitriol]
b12-cianocobalamina → [b12-metilcobalamina, b12-hidroxocobalamina]
Quando composto não tem estoque, o sistema busca automaticamente substituto com bioequivalência comprovada, oferecendo ao médico com base em evidência.
Cada documento em pharma_* pode ter clinicId:
{
compoundId: "cipionato-testosterona",
name: "Cipionato de Testosterona",
quantityAvailable: 50,
clinicId: "clinic-001" // Isolamento de dados
}
Firestore queries usam where('clinicId', '==', userClinicId) para garantir
que estoque de uma clínica não é visível em outra.
checkAvailability()reserveStock()releaseReservation()consumeStockFromBatch()Dividido em 4 sub-stores (não é mais um "God Store"):
Leia references/stock-first-philosophy.md para entender a integração.
const results = await checkAvailability([
{ compoundId: 'cipionato-testosterona', quantity: 100 },
{ compoundId: 'estradiol-valerato', quantity: 2 }
]);
results[0] = {
compoundId: 'cipionato-testosterona',
available: 45, // Menos que solicitado!
canFulfill: false,
substitute: {
compoundId: 'enantato-testosterona', // Alternativa
available: 30
}
}
await receivePurchaseOrder(orderId, [
{
itemId: 'item-001',
batchNumber: 'BATCH-2024-001',
receivedQuantity: 50,
expiryDate: new Date('2026-03-15'),
manufacturingDate: new Date('2023-01-15')
}
]);
// Automático:
// 1. Cria Batch
// 2. Atualiza pharma_stock.quantityAvailable
// 3. Registra Movement type='IN'
await consumeStockFromBatch(
compoundId: 'cipionato-testosterona',
quantity: 100,
userId: 'user-001',
userName: 'Dr. João Silva',
notes: 'Dispensação protocolo PRO-001'
);
// Automático:
// 1. Busca lote FIFO mais antigo
// 2. Decrementa quantityRemaining do batch
// 3. Atualiza pharma_stock.quantityAvailable
// 4. Registra Movement type='OUT' com auditHash
const reconciliation = await reconcileInventory(
compoundId: 'vitamina-d3',
actualQuantity: 95 // Contagem física
);
// Retorna:
// {
// expectedQuantity: 100,
// actualQuantity: 95,
// difference: -5,
// movements: [...]
// }
// Se diferença, registrar ajuste:
await recordMovement({
type: 'ADJUSTMENT',
compoundId: 'vitamina-d3',
quantity: -5,
notes: 'Contagem física realizada em 2024-03-07'
});
Leia os arquivos em references/ para detalhes:
→ Verificar checkAvailability(), consultar findSubstitute(), oferecer alternativa
→ Query getExpiringItems(daysAhead), alertar e isolar em QUARANTINE
→ reconcileInventory(), registrar ADJUSTMENT, auditar movimentações
→ releaseReservation() ao cancelar protocolo