Skip to content

Schema do Banco D1 ​

O banco de dados do Neemias é um SQLite gerenciado pelo Cloudflare D1. As migrações são aplicadas sequencialmente na pasta migrations/. Abaixo, o schema completo após todas as 26 migrações.

Modelo de escrita atual (v0.62+): o check-in/check-out é o fluxo de presença real — check_in_events (0022) substituiu attendance_events (drop em 0023). O event-sourcing unificado usa a tabela events (0010); as tabelas legadas student_events/user_events foram aposentadas (0026).

Convenções ​

  • UUIDs são armazenados como TEXT PRIMARY KEY (gerados via crypto.randomUUID())
  • Timestamps usam formato ISO 8601 (TEXT)
  • Booleanos são INTEGER (0 = false, 1 = true)
  • JSON é armazenado como TEXT (serializado)
  • Soft-delete usa coluna status (ACTIVE | DELETED)

Tabelas ​

users ​

Usuários do sistema com autenticação PBKDF2 (two-pass, ADR-0031).

ColunaTipoConstraints
user_idTEXTPRIMARY KEY
emailTEXTUNIQUE NOT NULL
display_nameTEXTNOT NULL
usernameTEXT(0013 — autenticação OTP)
statusTEXTNOT NULL, DEFAULT 'ACTIVE'
password_hashTEXT
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

v0.56.0: a coluna role foi removida (0019) — papéis agora vivem em user_roles.

user_roles ​

Tabela de junção usuário↔papel para suporte multi-role (v0.56.0+).

ColunaTipoConstraints
user_idTEXTNOT NULL, REFERENCES users(user_id)
roleTEXTNOT NULL, CHECK IN (ADMIN, CHAMADOR, RELATORIOS, CADASTRO, RESPONSAVEL, VOLUNTARIO, COORDENACAO_KIDS, ADMINISTRATIVO_KIDS)

Primary key: composta (user_id, role).

students ​

Alunos cadastrados com dados completos de cadastro.

ColunaTipoConstraints
student_idTEXTPRIMARY KEY
display_nameTEXTNOT NULL
photo_refTEXT
statusTEXTNOT NULL, DEFAULT 'ACTIVE'
created_atTEXTNOT NULL
updated_atTEXTNOT NULL
deleted_justificationTEXT
created_byTEXTNOT NULL, DEFAULT '' (0020)
image_consent_idTEXT(0020 — opt-in de consentimento)
guardian_nameTEXTNOT NULL, DEFAULT ''
guardian_name_altTEXT
birth_dateTEXTformato YYYY-MM-DD
phonesTEXTNOT NULL, DEFAULT '[]' (array JSON)
address_streetTEXT
address_numberTEXT
address_complementTEXT
address_neighborhoodTEXT
address_cityTEXT
address_stateTEXT
address_zipTEXT
class_idTEXTREFERENCES classes(class_id)
nucleus_participatesINTEGERNOT NULL, DEFAULT 0
nucleus_regionTEXT
nucleus_nameTEXT
allergiesTEXT
special_needsTEXT
family_membership_statusTEXTNOT NULL, DEFAULT 'DESCONHECIDO'

Campos sociodemográficos (0008, pastorais — todos opcionais):

ColunaTipoDefault
family_structureTEXT'DESCONHECIDO'
lives_withTEXT
siblings_countINTEGER
economic_vulnerabilityTEXT'DESCONHECIDO'
cps_involvementINTEGER(1=true, 0=false, NULL=desconhecido)
domestic_violenceTEXT'DESCONHECIDO'
school_statusTEXT'DESCONHECIDO'
family_notesTEXT
child_atypical_notesTEXT

Índices:

  • idx_students_status_updated_at — (status, updated_at DESC)

Valores de family_membership_status: MEMBRO, NAO_MEMBRO, DESCONHECIDO

classes ​

Turmas por faixa etária, com seed de 7 classes padrão.

ColunaTipoConstraints
class_idTEXTPRIMARY KEY
nameTEXTNOT NULL UNIQUE
age_minINTEGER
age_maxINTEGER
statusTEXTNOT NULL, DEFAULT 'ACTIVE'
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Índices:

  • idx_classes_status — (status, name)

Seed: Berçário (1-2), Maternal Infantil (3), Jardim de Infância (4-5), Infantil (6), Juniores 1 (7), Juniores 2 (8), Pré-Adolescentes (9-11)

nuclei ​

Núcleos (células/pequenos grupos) organizados por região.

ColunaTipoConstraints
nucleus_idTEXTPRIMARY KEY
regionTEXTNOT NULL
nameTEXTNOT NULL
statusTEXTNOT NULL, DEFAULT 'ACTIVE'
created_atTEXTNOT NULL, DEFAULT datetime('now')
updated_atTEXTNOT NULL, DEFAULT datetime('now')

Índices:

  • idx_nuclei_region — (region)
  • idx_nuclei_status — (status)

A tabela foi recriada com defaults datetime('now') na migração 0023 (schema espelhando NUCLEI_MIGRATION_SQL do módulo nucleus).

guardians ​

Vínculo de usuários responsáveis (papel RESPONSAVEL) aos seus alunos. Alimenta o sistema de escopo (ADR-0016).

ColunaTipoConstraints
idTEXTPRIMARY KEY
user_idTEXTNOT NULL
student_idTEXTNOT NULL
relationshipTEXTNOT NULL, DEFAULT 'outro' — CHECK IN (pai,mãe,avô,avó,tio,tia,outro)
created_atTEXTNOT NULL

Índices: idx_guardians_user (user_id) · idx_guardians_student (student_id) — UNIQUE (user_id, student_id)

class_assignments ​

Vínculo de voluntários/professores (papel VOLUNTARIO) às suas turmas. Alimenta o sistema de escopo (ADR-0016).

ColunaTipoConstraints
idTEXTPRIMARY KEY
user_idTEXTNOT NULL
class_idTEXTNOT NULL
roleTEXTNOT NULL, DEFAULT 'voluntario' — CHECK IN (professor,voluntario,auxiliar)
created_atTEXTNOT NULL

Índices: idx_class_assignments_user (user_id) · idx_class_assignments_class (class_id) — UNIQUE (user_id, class_id)

check_in_events ​

Registros imutáveis de check-in/check-out (event sourcing, ADR-0026). Cada check-in gera um código diário determinístico HMAC(church_secret, student_id + session_id + date) — o código não é armazenado, é calculado sob demanda.

ColunaTipoConstraints
idTEXTPRIMARY KEY
student_idTEXTNOT NULL, FK → students
session_idTEXTNOT NULL, FK → class_sessions
event_typeTEXTNOT NULL — CHECK_IN | CHECK_OUT | CHECKOUT_ATTEMPT | EMERGENCY_CHECKOUT
daily_codeTEXTcódigo de 6 chars (só CHECK_IN)
actor_user_idTEXTNOT NULL
actor_roleTEXTNOT NULL
pickup_personTEXTnome do responsável (CHECK_OUT / EMERGENCY)
pickup_phone_suffixTEXTúltimos 4 dígitos verificados
attempt_codeTEXTcódigo tentado (só CHECKOUT_ATTEMPT)
attempt_numberINTEGERcontador de tentativas (1-based)
override_reasonTEXTjustificativa (EMERGENCY ou admin-forced)
created_atTEXTNOT NULL — timestamp ISO-8601

Índices:

  • idx_checkin_events_student — (student_id, created_at)
  • idx_checkin_events_session — (session_id, created_at)
  • idx_checkin_active — UNIQUE PARCIAL (student_id, session_id) WHERE event_type = 'CHECK_IN' (um check-in ativo por aluno/sessão)

class_slots ​

Espelha os slots de aula do IndexedDB no D1 (sync bidirecional e relatórios).

ColunaTipoConstraints
slot_idTEXTPRIMARY KEY
day_of_weekINTEGERNOT NULL — 0=domingo .. 6=sábado
start_timeTEXTNOT NULL — "HH:MM"
labelTEXTNOT NULL
session_dateTEXTopcional (vagas avulsas)
statusTEXTNOT NULL, DEFAULT 'ACTIVE'
capacityINTEGERNOT NULL, DEFAULT 0 (0018)
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Índices: idx_class_slots_status (status)

class_sessions ​

Sessões de aula (semanais ou avulsas) referenciadas pelo check-in.

ColunaTipoConstraints
session_idTEXTPRIMARY KEY
slot_idTEXTNOT NULL, FK → class_slots(slot_id)
session_dateTEXTNOT NULL
created_atTEXTNOT NULL

Índices: idx_class_sessions_slot (slot_id, session_date) · idx_class_sessions_date (session_date)

session_capacity ​

Contagem de capacidade por sessão (dashboard de capacidade ao vivo, SDD-149).

ColunaTipoConstraints
session_idTEXTPRIMARY KEY
slot_idTEXTNOT NULL
total_spotsINTEGERNOT NULL, DEFAULT 0
occupiedINTEGERNOT NULL, DEFAULT 0
updated_atTEXTNOT NULL

Índices: idx_session_capacity_slot (slot_id, updated_at)

student_credentials ​

Carteirinha permanente do aluno (QR/barcode, issue #467) — separada dos códigos diários de check-in.

ColunaTipoConstraints
credential_idTEXTPRIMARY KEY
student_idTEXTNOT NULL, FK → students
credential_valueTEXTNOT NULL UNIQUE
formatTEXTNOT NULL, DEFAULT 'QR' — 'QR' | 'BARCODE'
labelTEXTDEFAULT ''
issued_byTEXTNOT NULL (actor_user_id)
issued_atTEXTNOT NULL
revoked_atTEXT(soft-revoke; nunca reutilizado)

Índices: idx_student_credentials_student (student_id, revoked_at) · idx_student_credentials_value (credential_value)

waiting_list ​

Fila FIFO de responsáveis quando a turma está cheia (issue #567).

ColunaTipoConstraints
idTEXTPRIMARY KEY
class_idTEXTNOT NULL, FK → classes(class_id)
guardian_idTEXTNOT NULL, FK → guardians(id)
child_idTEXTNOT NULL, FK → students
created_atTEXTNOT NULL
notifiedINTEGERNOT NULL, DEFAULT 0

Índices: idx_waiting_list_class (class_id, created_at) — UNIQUE (class_id, child_id)

Links únicos de auto-onboarding gerados pelo admin para pais/responsáveis.

ColunaTipoConstraints
idTEXTPRIMARY KEY
class_idTEXTNOT NULL
created_byTEXTNOT NULL
hashTEXTNOT NULL UNIQUE
expires_atTEXTNOT NULL
max_usesINTEGERNOT NULL, DEFAULT 1
use_countINTEGERNOT NULL, DEFAULT 0
is_activeINTEGERNOT NULL, DEFAULT 1
created_atTEXTNOT NULL

Índices: idx_onboarding_links_hash (hash)

onboarding_drafts ​

Rascunhos de cadastro submetidos pelos pais, pendentes de aprovação.

ColunaTipoConstraints
idTEXTPRIMARY KEY
hashTEXTNOT NULL
class_idTEXTNOT NULL
invited_byTEXTNOT NULL
expires_atTEXTNOT NULL
guardian_phoneTEXT
guardian_emailTEXT
otp_hashTEXT
otp_attemptsINTEGERNOT NULL, DEFAULT 0
otp_expires_atTEXT
otp_verified_atTEXT
student_dataTEXTNOT NULL (JSON)
consent_lgpdINTEGERNOT NULL, DEFAULT 0
consent_imageINTEGERNOT NULL, DEFAULT 0
statusTEXTNOT NULL, DEFAULT 'PENDING' — CHECK IN (PENDING,APPROVED,REJECTED,EXPIRED)
reviewed_byTEXT
reviewed_atTEXT
review_notesTEXT
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Índices: idx_onboarding_drafts_hash (hash) · idx_onboarding_drafts_status (status) · idx_onboarding_drafts_class (class_id)

events ​

Tabela unificada de event-sourcing (append-only) — fonte única da verdade para eventos imutáveis. Substitui as tabelas legadas student_events/user_events (aposentadas em 0026).

ColunaTipoConstraints
event_idTEXTPRIMARY KEY (UUID — chave de idempotência)
entity_typeTEXTNOT NULL — STUDENT | ATTENDANCE | CLASS | ...
entity_idTEXTNOT NULL
versionINTEGERNOT NULL — monotônico por (entity_type, entity_id)
action_typeTEXTNOT NULL — CREATED | UPDATED | DELETED | RESTORED
payloadTEXTNOT NULL (JSON — delta dos campos)
actor_idTEXTNOT NULL
actor_roleTEXTNOT NULL (auditoria)
justificationTEXT(obrigatório para DELETED)
created_atTEXTNOT NULL, DEFAULT datetime('now')

Índices: uniq_events_entity_version UNIQUE (entity_type, entity_id, version) · idx_events_actor (actor_id) · idx_events_created (created_at)

A partir da migração 0016_events_unique_entity_version.sql, a unicidade de (entity_type, entity_id, version) é garantida pelo banco, não só pela checagem de aplicação em applyEvent. O índice não-único idx_events_entity foi derrubado ao mesmo tempo — o único cobre os mesmos planos de consulta. É o que fecha a janela read-then-write: dois pedidos concorrentes podiam ler a mesma versão máxima e gravar a mesma versão seguinte.

church_events ​

Eventos da igreja com capacidade, preço e suporte a recorrência (SDD-153 §3).

ColunaTipoConstraints
idTEXTPRIMARY KEY
titleTEXTNOT NULL
descriptionTEXT
date_startTEXTNOT NULL
date_endTEXT
locationTEXT
capacityINTEGERNOT NULL, DEFAULT 0 (0 = ilimitado)
price_in_centsINTEGERNOT NULL, DEFAULT 0 (0 = grátis)
pix_keyTEXT
pix_qr_codeTEXT
statusTEXTNOT NULL, DEFAULT 'DRAFT' — CHECK IN (DRAFT,OPEN,CLOSED,CANCELLED,FINISHED)
recurrenceTEXTJSON: { type, interval, count, until }
template_idTEXT
church_idTEXTNOT NULL, DEFAULT 'default'
created_byTEXTNOT NULL
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Índices: idx_church_events_status (status) · idx_church_events_church (church_id)

event_registrations ​

Inscrições públicas em eventos com fila de aprovação e status de pagamento.

ColunaTipoConstraints
idTEXTPRIMARY KEY
event_idTEXTNOT NULL, FK → church_events(id)
student_idTEXTNULL se não-cadastrado
guardian_nameTEXTNOT NULL
guardian_phoneTEXTNOT NULL
guardian_emailTEXT
participant_nameTEXTNOT NULL
participant_ageINTEGER
allergiesTEXT
special_needsTEXT
referralTEXT"Quem te falou?"
heard_fromTEXT"Onde ouviu falar?"
payment_proofTEXTURL R2
payment_statusTEXTNOT NULL, DEFAULT 'PENDING' — CHECK IN (PENDING,PAID,WAIVED,REFUNDED)
statusTEXTNOT NULL, DEFAULT 'PENDING' — CHECK IN (PENDING,APPROVED,REJECTED,WAITING_LIST,CANCELLED,CONFIRMED)
reviewed_byTEXT
reviewed_atTEXT
review_notesTEXT
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Índices: idx_registrations_event (event_id) · idx_registrations_status (status) · idx_registrations_payment (payment_status)

notifications ​

Feed de notificações para telão (SSE polling, SDD-146).

ColunaTipoConstraints
idTEXTPRIMARY KEY
church_idTEXTNOT NULL, DEFAULT 'default'
typeTEXTNOT NULL, DEFAULT 'alert' — CHECK IN (alert,info,emergency)
child_nameTEXTNOT NULL
child_ageINTEGER
messageTEXTNOT NULL, DEFAULT 'Procure a recepção kids'
created_byTEXTNOT NULL
created_atTEXTNOT NULL
read_atTEXT

Índices: idx_notifications_church (church_id, created_at)

church_settings ​

Configurações do feed da igreja.

ColunaTipoConstraints
church_idTEXTPRIMARY KEY, DEFAULT 'default'
feed_tokenTEXTNOT NULL, DEFAULT ''
broadcast_enabledINTEGERNOT NULL, DEFAULT 0
checkin_secretTEXTNOT NULL, DEFAULT '' (0022 — derivação de códigos diários)

church_config ​

Store chave-valor de configurações por igreja (modo de pickup, override de emergência, limites de tentativa, labels).

ColunaTipoConstraints
church_idTEXTNOT NULL, FK → church_settings(church_id)
config_keyTEXTNOT NULL
config_valueTEXTNOT NULL
updated_atTEXTNOT NULL

Primary key: composta (church_id, config_key) — adicionar uma opção é um INSERT, sem migração.

auth_refresh_sessions ​

Sessões de refresh token com rotação e detecção de comprometimento.

ColunaTipoConstraints
session_idTEXTPRIMARY KEY
user_idTEXTNOT NULL, REFERENCES users(user_id) ON DELETE CASCADE
refresh_token_hashTEXTNOT NULL UNIQUE
csrf_tokenTEXTNOT NULL
expires_atTEXTNOT NULL
revoked_atTEXT
replaced_byTEXTREFERENCES auth_refresh_sessions(session_id)
rotated_atTEXT
suspected_compromise_atTEXT
created_atTEXTNOT NULL

Índices:

  • idx_auth_refresh_sessions_user_id — (user_id, created_at DESC)
  • idx_auth_refresh_sessions_expires_at — (expires_at)

idempotency_ledger ​

Registro de idempotência para detecção de replay de mutações.

ColunaTipoConstraints
keyTEXTPRIMARY KEY (composta com user_id)
user_idTEXTPRIMARY KEY (composta com key)
payload_hashTEXTNOT NULL
status_codeINTEGERNOT NULL
response_bodyTEXTNOT NULL (JSON)
created_atTEXTNOT NULL

Índices:

  • idx_idempotency_ledger_created_at — (created_at DESC)

audit_logs ​

Trilha de auditoria para conformidade LGPD.

ColunaTipoConstraints
audit_idTEXTPRIMARY KEY
correlation_idTEXTNOT NULL
actor_user_idTEXTNOT NULL
actor_roleTEXTNOT NULL
endpointTEXTNOT NULL
outcomeTEXTNOT NULL
detailsTEXTNOT NULL (JSON)
created_atTEXTNOT NULL

roles ​

Catálogo de papéis com permissões granulares.

ColunaTipoConstraints
nameTEXTPRIMARY KEY
display_nameTEXTNOT NULL
permissionsTEXTNOT NULL, DEFAULT '[]' (array JSON)
is_systemINTEGERNOT NULL, DEFAULT 0
statusTEXTNOT NULL, DEFAULT 'ACTIVE' (0021 — soft-delete)
deleted_atTEXT(0021)
deleted_byTEXT(0021)
created_atTEXTNOT NULL
updated_atTEXTNOT NULL

Seed (papéis de sistema, is_system=1): ADMIN, CHAMADOR, RELATORIOS, CADASTRO, VOLUNTARIO, COORDENACAO_KIDS, ADMINISTRATIVO_KIDS, RESPONSAVEL (0017; VOLUNTEER/VOLUNTARIO_KIDS fundidos em VOLUNTARIO na 0018_merge). Lista completa em packages/permissions/index.ts.

rate_limits ​

Controle de rate limiting baseado em D1.

ColunaTipoConstraints
keyTEXTPRIMARY KEY
countINTEGERNOT NULL, DEFAULT 1
window_startINTEGERNOT NULL
expires_atINTEGERNOT NULL

Tabelas removidas ​

TabelaRemovida emSubstituída por
attendance_events0023 (drop)check_in_events (0022) — o check-in/check-out substituiu a marcação de presença (issue #477)
student_events0026 (retire)events (0010) — event-sourcing unificado (#608)
user_events0026 (retire)events (0010) — event-sourcing unificado (#608)

Histórico de Migrações ​

⚠️ Consolidado no v1.0.0 (2026-08-12): as migrações 0001–0029 (incl. os 0018/0023 duplicados e o backfill-events.sql) foram substituídas por uma única baseline — migrations/0001_init.sql — equivalente ao schema final (export do D1 remoto pós-0029). A tabela abaixo é histórico (o que cada migração pré-1.0 fazia); bancos novos aplicam somente a baseline e migrações futuras começam em 0002. Ver docs/operations/migrations.md.

MigraçãoArquivoDescrição
00010001_init.sqlSchema core: users, students, attendance_events, student_events, user_events, idempotency_ledger, audit_logs + índices
00020002_auth_sessions.sqlTabela auth_refresh_sessions com rotação e revogação
00030003_student_fields.sqlExpansão de students (guardião, telefones, endereço) + novas tabelas classes e nuclei + seed de classes
00040004_family_membership.sqlColuna family_membership_status em students
00050005_compromise_detection.sqlColuna suspected_compromise_at em auth_refresh_sessions
00060006_roles.sqlTabela roles + seed dos 4 papéis de sistema
00070007_rate_limits.sqlTabela rate_limits para controle de rate
00080008_sociodemographic.sqlCampos sociodemográficos em students (9 campos pastorais)
00090009_atomic_token_rotation.sqlRotação atômica de refresh token com detecção de comprometimento
00100010_events.sqlTabela unificada events de event-sourcing (append-only)
00110011_class_slots_sessions.sqlclass_slots e class_sessions (semanais + avulsas)
00120012_onboarding.sqlFila de aprovação de onboarding de pais + links de registro (onboarding_links, onboarding_drafts)
00130013_username.sqlColuna username em users para autenticação OTP
00140014_guardians.sqlguardians + class_assignments (sistema de escopo, ADR-0016)
00150015_events.sqlchurch_events + event_registrations (eventos com capacidade, preço e aprovação)
00160016_notifications.sqlnotifications (feed SSE) + church_settings
00170017_roles_seed.sqlSeed de papéis de sistema adicionais (VOLUNTARIO, RESPONSAVEL, etc.)
00180018_capacity.sql + 0018_merge_voluntario.sqlLimites de capacidade em class_slots + session_capacity; merge VOLUNTEER/VOLUNTARIO_KIDS → VOLUNTARIO (#362)
00190019_user_roles.sqlTabela user_roles (multi-role). DROP COLUMN users.role. Migra papéis existentes.
00200020_student_columns.sqlColunas created_by e image_consent_id em students (projeção event-sourcing, #391)
00210021_role_soft_delete.sqlSoft-delete em roles (status, deleted_at, deleted_by) — convergência de papéis (Fase 4)
00220022_check_in.sqlcheck_in_events + church_config + church_settings.checkin_secret — check-in/check-out com códigos diários (#144, ADR-0026)
00230023_drop_attendance.sql + 0023_nuclei.sqlDrop de attendance_events (#477); recriação de nuclei com defaults (módulo nucleus, #478)
00240024_student_credentials.sqlstudent_credentials — carteirinha permanente QR/barcode (#467)
00250025_waiting_list.sqlwaiting_list — fila FIFO quando a turma está cheia (#567)
00260026_retire_legacy_events.sqlAposenta student_events/user_events — todo write-path usa events (#608)

Total: 26 migrações (0001–0026) no período pré-1.0 — squashed em 0001_init.sql no v1.0.0. 0018_merge_voluntario.sql e 0023_nuclei.sql eram migrações companheiras ao lado de 0018_capacity.sql e 0023_drop_attendance.sql.

Fonte: migrations/

Distribuído sob licença MIT.