-- 086 — Leitura ("lido") como dimensão própria do contato
--
-- POR QUE UMA COLUNA, E NÃO UM VALOR DE contacts.status:
-- `contacts.status` é o eixo de ENTREGA/COBRANÇA, e está acoplado a duas
-- coisas caras. Um terceiro valor "lido" quebraria as duas:
--   1. estornoAmount = creditosReservados - sentCount (routes.ts). Contato
--      lido deixaria de contar como "enviado" → estorno de crédito indevido.
--   2. `if (contact.status === "enviado") continue;` nos dispatchers RCS/SMS.
--      Contato lido não seria pulado numa retomada → RECEBERIA A MENSAGEM DE
--      NOVO, ferindo a regra de nunca duplicar envio.
-- Além do contrato de callback já publicado na doc da API como tendo apenas
-- enviado/nao_enviado.
--
-- O precedente é o CLIQUE: click_count/first_click_at já vivem fora do status,
-- pelo mesmo motivo. read_at espelha first_click_at deliberadamente.
--
-- BÔNUS ARQUITETURAL: como a leitura não mora no status, ela pode ser carimbada
-- pelo webhook a QUALQUER momento — inclusive com a campanha já fechada. Isso
-- contorna a raiz mais difícil do problema (a finalização só roda enquanto a
-- campanha está "processing", e o SEEN sempre chega depois do DELIVERED).

BEGIN;

ALTER TABLE contacts ADD COLUMN IF NOT EXISTS read_at timestamptz;

COMMENT ON COLUMN contacts.read_at IS
  'Quando o destinatário LEU (WhatsApp SEEN / RCS Visto). NULL = não lido ou '
  'canal sem recibo de leitura (SMS). Primeira leitura vence — nunca é '
  'sobrescrita. Fora de contacts.status de propósito: status rege cobrança e '
  'retomada de disparo.';

-- Índice parcial: as consultas perguntam "quantos leram NESTE lote", então só
-- as linhas com leitura interessam. Mesmo formato do índice de entrega criado
-- na 084.
CREATE INDEX IF NOT EXISTS idx_contacts_campaign_read
  ON contacts(campaign_id) WHERE read_at IS NOT NULL;

COMMIT;
