Saltar para o conteúdo
PMPaulo Mota
Voltar à demo

Como foi feito

HACCP digital, por dentro

Duas tabelas, três funções e uma regra que decide tudo: quem regista envia a leitura, nunca o veredicto.

01

O problema

As temperaturas escrevem-se à mão numa folha que vive em cima do frigorífico. Quando a inspecção aparece, falta metade, e a outra metade foi preenchida toda de uma vez na sexta à tarde, com números redondos e a mesma caneta. Isso não é um registo, é um alibi mal feito. E quando um frigorífico avaria a sério, ninguém dá por ela até o peixe cheirar mal.

02

Modelo de dados

Os pontos de controlo são o plano HACCP da casa: o que se mede, onde, entre que limites e em que turnos. São dados de referência, iguais para toda a gente. Os registos pertencem a uma sala, têm dia, turno, leitura, veredicto e o nome de quem assinou. Um índice único impede dois registos do mesmo ponto no mesmo turno do mesmo dia.

TabelaPara que serve
demo_haccp_pointsO plano HACCP da casa: o que se mede, onde, entre que limites e em que turnos. Dados de referência, iguais para todas as salas.
demo_haccp_checksUm registo por ponto, turno e dia: a leitura, o veredicto do servidor, quem assinou e, se falhou, a acção correctiva e quem a assinou.
demo_haccp_deliveriesCada entrada de mercadoria: fornecedor, produto, lote, validade, quantidade e temperatura à chegada, com o que o servidor achou e o que a pessoa decidiu.
demo_roomsA sala de demonstração que liga os dispositivos. Apagada ao fim de seis horas, e com ela os registos.
03

O veredicto nasce no servidor

Esta é a decisão que faz o sistema valer alguma coisa. O telemóvel envia a leitura e o ponto, nunca o cumpre ou não cumpre. A função vai buscar os limites daquele ponto, compara, e escreve o resultado. Quem preenche não pode dizer que está tudo bem: só pode dizer o número que leu. Se um ponto de frio chegar sem leitura, ou com um valor impossível, a função recusa antes de guardar.

sql
-- O telemóvel envia a leitura e o ponto. Nunca o veredicto.
select * into v_point from demo_haccp_points where id = p_point;

if v_point.kind = 'tarefa' then
  v_ok := true;
  p_value := null;
else
  if p_value is null then raise exception 'sem leitura'; end if;
  if p_value < -40 or p_value > 200 then
    raise exception 'leitura impossivel';
  end if;
  -- O cumpre ou não cumpre nasce aqui, e só aqui
  v_ok := p_value >= v_point.min_c and p_value <= v_point.max_c;
end if;

-- Hoje no fuso do restaurante, não no do servidor
v_today := (now() at time zone 'Europe/Lisbon')::date;
04

Um incumprimento não se fecha sozinho

Uma leitura fora dos limites fica aberta até alguém escrever o que fez e assinar. A função recusa uma acção vazia, e recusa também uma acção sobre um registo que cumpre, para o histórico não encher de ruído. Corrigir uma leitura mal metida substitui-a e reabre o incumprimento, em vez de deixar duas versões do mesmo turno a discutir uma com a outra.

sql
-- Um incumprimento só fecha com uma acção escrita e assinada.
if length(btrim(coalesce(p_action, ''))) < 4 then
  raise exception 'accao vazia';
end if;

-- E não se "corrige" o que cumpre: isso seria ruído no registo
if v_row.ok then raise exception 'este registo cumpre'; end if;

-- Corrigir uma leitura mal metida substitui-a e reabre o
-- incumprimento, em vez de deixar duas versões do mesmo turno
on conflict (room_code, point_id, day, shift) do update
   set value_c = excluded.value_c, ok = excluded.ok,
       signed_by = excluded.signed_by, created_at = now(),
       corrective = null, corrected_by = null, corrected_at = null;
05

Quem assinou não é público

O nome de quem registou, a observação e a acção correctiva são dados de pessoas: não saem pela chave pública. A chave que vai no browser lê o ponto, a hora, a leitura e se cumpre, que é o que o ecrã precisa para acender o alerta em tempo real. O resto sai por uma função que exige o código da sala.

sql
-- Um ponto só se regista uma vez por turno e por dia.
create unique index demo_haccp_checks_once_idx
  on demo_haccp_checks (room_code, point_id, day, shift);

-- Um ponto de temperatura sem limites não é um ponto de controlo
constraint demo_haccp_limits_needed
  check (kind = 'tarefa'
         or (min_c is not null and max_c is not null
             and min_c <= max_c));

-- Quem assinou não sai pela chave pública: chega o que o ecrã
-- precisa para acender o alerta em tempo real.
grant select (id, room_code, point_id, day, shift, value_c, ok,
              corrected_at, created_at)
  on demo_haccp_checks to anon;
typescript
// O ecrã do responsável acende sozinho quando a equipa
// regista uma leitura fora dos limites, do outro lado da casa.
const channel = supabase
  .channel(`demo-haccp-${room}`)
  .on("postgres_changes",
      { event: "*", schema: "public", table: "demo_haccp_checks",
        filter: `room_code=eq.${room}` },
      () => void load(room))
  .subscribe();

// Incumprimentos por resolver: fora dos limites e sem acção escrita
const openFails = checks.filter((c) => !c.ok && !c.corrected_at);
06

Verificar não é o mesmo que corrigir

O ciclo não fecha na acção correctiva. Fecha quando outra pessoa confere que a acção foi mesmo feita e assina. A função recusa verificar o que ainda não tem acção, e uma restrição na tabela garante o mesmo mesmo que alguém escreva direto na base de dados. Se a leitura for corrigida, a acção e a verificação caem as duas: dizem respeito a um número que já não está lá. Nesta demonstração qualquer visitante assina os dois papéis; num restaurante, verificar é do responsável, não de quem registou.

sql
-- Não se verifica o que ainda não foi corrigido.
-- Na função, para dar um erro legível:
if v_row.corrected_at is null then
  raise exception 'ainda sem accao correctiva';
end if;

-- E na tabela, para valer mesmo que alguém escreva direto:
alter table demo_haccp_checks
  add constraint demo_haccp_verify_order
  check (verified_at is null or corrected_at is not null);

-- Uma leitura nova apaga a acção e a verificação: referem-se
-- a um número que já não está lá.
on conflict (room_code, point_id, day, shift) do update
   set value_c = excluded.value_c, ok = excluded.ok,
       corrective = null, corrected_by = null, corrected_at = null,
       verified_by = null, verified_at = null;
07

A rastreabilidade começa na doca

Fornecedor, produto, lote, validade e temperatura à chegada. É isto que permite responder quando um fornecedor manda recolher um lote. Os limites de recepção são do servidor: refrigerado até 4 graus, congelado até -18. O servidor diz se estava dentro; aceitar ou recusar continua a ser decisão de quem está na doca, e é registada como tal. Aceitar uma entrega fora do limite é uma decisão legítima em certos casos, e fica escrita como decisão, não escondida. Recusar sem escrever porquê é recusado por uma restrição.

sql
-- Os limites de recepção são do servidor, não do telemóvel.
v_max := case p_kind when 'refrigerado' then 4.0
                     when 'congelado' then -15.0
                     else null end;

v_temp_ok := p_temp <= v_max;
v_date_ok := p_expiry >= v_today;

-- Repare no que NÃO acontece aqui: o servidor não decide aceitar.
-- Diz se estava dentro; aceitar é de quem está na doca, e fica
-- registado como decisão dessa pessoa.

-- Recusar sem dizer porquê não é recusar, é esconder:
constraint demo_delivery_reason_needed
  check (accepted
         or (reject_reason is not null
             and length(btrim(reject_reason)) >= 4));
08

O que este software não faz

Não inventa limites críticos nem valida um plano HACCP. Os pontos de controlo, os limites e as frequências são configuração, escrita a partir do plano da casa, que é responsabilidade de quem percebe do assunto. O software documenta, mede contra o que está configurado, e guarda a prova. Dizer a um cliente que um software o torna conforme seria mentira, e um cliente que já teve uma inspecção percebe logo.

09

O que faria diferente num cliente real

Sondas com leitura automática nos frigoríficos, para o número não depender de alguém se lembrar. Alerta por telemóvel quando um ponto passa dos limites fora de horas. Fotografia anexada ao registo de recepção de mercadoria. Exportação do mês em PDF para entregar à inspecção sem imprimir nada à mão. E autenticação a sério: aqui qualquer visitante assina com o nome que quiser porque é uma demonstração; numa cozinha, cada pessoa entra com o seu código.