Compliance Engine
Relatórios Gerados
Seção intitulada “Relatórios Gerados”| Relatório | Periodicidade | Prazo | Destinatário |
|---|---|---|---|
| SNGPC XML | Por dispensação + batch diário | Até 24h após dispensação | ANVISA (RNDS) |
| BSPO Trimestral | Trimestral | 15 jan / 15 abr / 15 jul / 15 out | ANVISA |
| BSPO Anual | Anual | 31 janeiro | ANVISA |
| KPI Dashboard | Mensal | Último dia do mês | Diretoria |
| DRE + Balanço (CPC 29) | Mensal | Até dia 10 do mês seguinte | Tesouraria / Contador |
| Relatório Judicial | Semestral | jun + dez | Arquivo associação |
| Rastreabilidade Full | Sob demanda | Imediato (geração síncrona) | Auditoria / Fiscalização |
SNGPC XML
Seção intitulada “SNGPC XML”Cada dispensação gera automaticamente um registro SNGPC. O batch diário consolida todas as dispensações do dia em um único XML para envio à RNDS:
// Schema do XML SNGPC por dispensaçãointerface SNGPCDispensacao { cnes: string; // CNES da associação (requer habilitação) cnpj: string; // CNPJ da associação dataDispensacao: string; // YYYY-MM-DD horaDispensacao: string; // HH:MM:SS cpfPaciente: string; // CPF decriptografado apenas neste momento nomePaciente: string; // Nome decriptografado apenas neste momento produto: { descricao: string; // Ex: "Óleo de Cannabis 10mg/mL CBD" concentracao: string; formaFarmaceutica: string; quantidadeDispensada: number; // Em gramas unidadeMedida: "g" | "mL" | "unidade"; lote: string; // ULID do inventory_lot validade: string; // YYYY-MM-DD }; prescricao: { numeroPrescricao: string; dataEmissao: string; crmMedico: string; ufCRM: string; };}Fluxo de geração:
- Dispensação registrada → evento
DispensationRecordedappendado no event store (junto comMemberQuotaConsumed+LotQuantityDeducted, cf. ADR-001) - Worker SNGPC (BullMQ) consome
DispensationRecorded→ gera XML individual - Batch diário às 23:45h consolida XMLs do dia → envia para RNDS
- Resposta da RNDS armazenada no
sngpc_submissionscom status e número de protocolo
Falha na geração/envio do XML não invalida a dispensação registrada — fluxo assíncrono separado do estado regulatório crítico.
BSPO — Balanço de Substâncias Psicoativas e Outras
Seção intitulada “BSPO — Balanço de Substâncias Psicoativas e Outras”Fórmula de Saldo
Seção intitulada “Fórmula de Saldo”Saldo Final = Saldo Inicial + Entradas − Saídas − Perdas DocumentadasO saldo deve bater com SUM(inventory_lots.quantity_g) para cada produto ativo:
-- Verificação de consistência BSPOWITH bspo_calc AS ( SELECT product_type, SUM(CASE WHEN type = 'entrada' THEN quantity_g ELSE 0 END) AS total_entradas, SUM(CASE WHEN type = 'saida' THEN quantity_g ELSE 0 END) AS total_saidas, SUM(CASE WHEN type = 'perda' THEN quantity_g ELSE 0 END) AS total_perdas, first_value(saldo_g) OVER (ORDER BY period_start) AS saldo_inicial FROM bspo_movements WHERE period_start >= $1 AND period_end <= $2 GROUP BY product_type),inventory_actual AS ( SELECT product_type, SUM(quantity_g) AS saldo_atual FROM inventory_lots WHERE status = 'active' GROUP BY product_type)SELECT b.product_type, b.saldo_inicial + b.total_entradas - b.total_saidas - b.total_perdas AS saldo_calculado, i.saldo_atual AS saldo_fisico, ABS((b.saldo_inicial + b.total_entradas - b.total_saidas - b.total_perdas) - i.saldo_atual) AS divergencia_gFROM bspo_calc bJOIN inventory_actual i USING (product_type);Divergência > 0 gera alerta automático para o DPO e responsável técnico.
7 KPIs do Dashboard
Seção intitulada “7 KPIs do Dashboard”KPI 1 — Membros Ativos
Seção intitulada “KPI 1 — Membros Ativos”SELECT COUNT(*) AS membros_ativosFROM membersWHERE status = 'active' AND association_id = $tenant_id;KPI 2 — Dispensações no Mês
Seção intitulada “KPI 2 — Dispensações no Mês”SELECT COUNT(*) AS dispensacoes_mes, SUM(quantity_g) AS total_dispensado_gFROM dispensationsWHERE dispensed_at >= date_trunc('month', now()) AND dispensed_at < date_trunc('month', now()) + INTERVAL '1 month' AND association_id = $tenant_id;KPI 3 — Estoque Disponível (por tipo de produto)
Seção intitulada “KPI 3 — Estoque Disponível (por tipo de produto)”SELECT product_type, SUM(quantity_g) AS estoque_total_g, MIN(expires_at) AS proxima_validadeFROM inventory_lotsWHERE status = 'active' AND expires_at > now() AND association_id = $tenant_idGROUP BY product_type;KPI 4 — Taxa de Ocupação Cultivo
Seção intitulada “KPI 4 — Taxa de Ocupação Cultivo”-- Plantas ativas / capacidade total declarada da área de cultivoSELECT l.name AS area, COUNT(p.id) AS plantas_ativas, l.max_capacity AS capacidade, ROUND(COUNT(p.id)::numeric / l.max_capacity * 100, 1) AS ocupacao_pctFROM grow_locations lLEFT JOIN cultivation_batches cb ON cb.location_id = l.idLEFT JOIN plants p ON p.batch_id = cb.id AND p.destroyed_at IS NULLWHERE l.association_id = $tenant_idGROUP BY l.id, l.name, l.max_capacity;KPI 5 — Colheitas no Trimestre (peso seco total)
Seção intitulada “KPI 5 — Colheitas no Trimestre (peso seco total)”SELECT date_trunc('quarter', harvest_date) AS trimestre, COUNT(*) AS colheitas, SUM(dry_weight_g) AS peso_seco_total_g, ROUND(AVG(dry_weight_g), 2) AS media_por_colheita_gFROM harvest_batchesWHERE association_id = $tenant_id AND harvest_date >= date_trunc('quarter', now()) - INTERVAL '1 quarter'GROUP BY trimestreORDER BY trimestre DESC;KPI 6 — Laudos Pendentes
Seção intitulada “KPI 6 — Laudos Pendentes”SELECT COUNT(*) AS laudos_pendentesFROM lab_samplesWHERE result_received_at IS NULL AND collected_at < now() - INTERVAL '14 days' -- prazo esperado AND association_id = $tenant_id;KPI 7 — Receita Operacional (mensalidades + taxas)
Seção intitulada “KPI 7 — Receita Operacional (mensalidades + taxas)”SELECT date_trunc('month', paid_at) AS mes, SUM(amount) AS receita_total, COUNT(*) AS pagamentos, COUNT(DISTINCT member_id) AS membros_pagantesFROM financial_transactionsWHERE type IN ('membership_fee', 'dispensation_fee') AND status = 'confirmed' AND association_id = $tenant_id AND paid_at >= date_trunc('month', now()) - INTERVAL '3 months'GROUP BY mesORDER BY mes DESC;CPC 29 / IAS 41 — Ativos Biológicos
Seção intitulada “CPC 29 / IAS 41 — Ativos Biológicos”Plantas vivas em cultivo são classificadas como ativo biológico conforme CPC 29 (equivalente ao IAS 41 internacional). Isso impacta o balanço patrimonial da associação:
Reconhecimento no Balanço
Seção intitulada “Reconhecimento no Balanço”Ativo Circulante └── Ativos Biológicos (CPC 29) └── Plantas em Cultivo Mensuração: Valor Justo − Custos de Venda Base: estimativa de produção × preço médio de mercado CBD/THCCálculo de Fair Value por Colheita Estimada
Seção intitulada “Cálculo de Fair Value por Colheita Estimada”-- Estimativa de fair value das plantas ativasSELECT cb.strain_id, s.name AS cepa, COUNT(p.id) AS plantas_ativas, s.avg_yield_g AS rendimento_medio_g_por_planta, COUNT(p.id) * s.avg_yield_g AS producao_estimada_g, -- preço de mercado referência (definido pelo DRE anualmente) mp.price_per_g_brl AS preco_referencia, COUNT(p.id) * s.avg_yield_g * mp.price_per_g_brl AS fair_value_brlFROM cultivation_batches cbJOIN strains s ON s.id = cb.strain_idJOIN plants p ON p.batch_id = cb.id AND p.destroyed_at IS NULLJOIN market_prices mp ON mp.product_type = 'flower' AND mp.valid_at <= now()WHERE cb.association_id = $tenant_idGROUP BY cb.strain_id, s.name, s.avg_yield_g, mp.price_per_g_brl;O relatório DRE + Balanço exporta esses valores em formato compatível com sistemas contábeis (CSV + PDF) para o contador da associação.
RBAC por Relatório
Seção intitulada “RBAC por Relatório”| Relatório | Diretoria | Tesoureiro | Responsável Técnico | DPO | Auditor |
|---|---|---|---|---|---|
| SNGPC XML | Ver | — | Gerar + Ver | Ver | Ver |
| BSPO | Ver | — | Gerar + Ver | Ver | Ver |
| KPI Dashboard | Ver | Ver | Ver | Ver | Ver |
| DRE + Balanço | Ver | Gerar + Ver | — | — | Ver |
| Rastreabilidade Full | — | — | Gerar + Ver | Gerar + Ver | Ver |
| Relatório Judicial | Ver | — | — | Gerar + Ver | Ver |
| Audit Log | — | — | — | Gerar + Ver | Ver |
Permissões gerenciadas via tabela role_permissions — não hardcoded na aplicação.