> **Nota (03/11/2026):** o âmbito foi simplificado — ver [ADR-011](adr/ADR-011-simplificacao.md). Este plano é mantido como histórico.

# h2n DataHub — Plano de Implementação

> Estado: **aprovado** · Versão 0.3 · M1 implementado (ver [docs/progresso/M1.md](progresso/M1.md)) · 20/10/2026
> Nenhum código foi escrito. Este documento cobre a fase 1 (revisão de requisitos e arquitectura técnica) e antecipa a fase 2 (modelo de dados e contratos de API).

---

## 1. Ponto de partida

| Item | Situação verificada |
|---|---|
| Pasta do projecto | Vazia; ainda não é repositório Git |
| PHP | 8.5.6 (CLI) |
| Composer | 2.10.0 |
| Node.js | 24.14.0 |
| PostgreSQL | **Não instalado** (o XAMPP traz MariaDB/MySQL, não PostgreSQL) |
| Redis | Não instalado |
| Docker | Não instalado |

Consequência: antes da fase 3 é preciso decidir como correr PostgreSQL localmente (ver decisão D1).

---

## 2. Arquitectura proposta

### 2.1 Visão geral

```
                ┌──────────────────────────────────────────────┐
 Browser ──────▶│  React + TypeScript (SPA, Vite)              │
                │  i18n PT/EN · ECharts · Leaflet · grid layout │
                └───────────────┬──────────────────────────────┘
                                │ HTTPS · cookie de sessão (Sanctum) · CSRF
                ┌───────────────▼──────────────────────────────┐
                │  Laravel — API REST /api/v1                   │
                │  ┌────────────┐ ┌───────────┐ ┌────────────┐  │
                │  │ Auth/RBAC  │ │ Query     │ │ Indicadores│  │
                │  │ Policies   │ │ Engine    │ │ & Qualidade│  │
                │  └────────────┘ └───────────┘ └────────────┘  │
                │  ┌──────────────────────────────────────────┐ │
                │  │ Conectores: Kobo · CSV/Excel · (Graph·SQL)│ │
                │  └──────────────────────────────────────────┘ │
                └──────┬────────────────────────┬──────────────┘
                       │                        │
           ┌───────────▼─────────┐   ┌──────────▼──────────┐
           │ PostgreSQL          │   │ Queue + Scheduler   │
           │ relacional + JSONB  │   │ (database → Redis)  │
           └─────────────────────┘   └──────────┬──────────┘
                                                │
                                     KoboToolbox API v2 (HTTPS)
```

### 2.2 Decisões de arquitectura (a registar como ADRs em `docs/adr/`)

| # | Decisão | Justificação | Alternativa rejeitada |
|---|---|---|---|
| ADR-01 | **Monólito modular Laravel** com a SPA React no mesmo repositório (`resources/js`), servida pelo Laravel | Uma só implementação, um só pipeline, menos superfície de ataque; equipa pequena | Microserviços / frontend e backend em repositórios separados |
| ADR-02 | **Autenticação por sessão com Laravel Sanctum (modo SPA, cookie HttpOnly + CSRF)** | Não expõe tokens ao JavaScript; adequado a SPA no mesmo domínio | Tokens Bearer guardados no browser |
| ADR-03 | **Três camadas de dados separadas**: bruto (imutável) → normalizado → resultados calculados | Rastreabilidade e reprodutibilidade (secção 6 do contexto) | Guardar só os dados transformados |
| ADR-04 | **Motor de consultas declarativo**: o frontend envia uma *query spec* JSON (dataset, dimensões, métricas, filtros); o backend valida contra uma lista branca de campos e gera SQL parametrizado | Agregação no servidor, nenhuma SQL livre, permissões aplicadas num único ponto, reutilizável pela IA mais tarde | SQL livre / envio de linhas brutas para o browser |
| ADR-05 | **Fórmulas de indicadores como DSL estruturada (JSON)**, não texto livre nem expressões avaliadas | Testável, versionável, impossível de injectar | `eval` de expressões, fórmulas em SQL |
| ADR-06 | **Fila em base de dados no MVP**, migração para Redis + Horizon quando o volume o justificar | Evita uma dependência operacional no início | Redis desde o primeiro dia |
| ADR-07 | **Sem PostGIS no MVP**: coordenadas em colunas numéricas; mapas coropléticos por junção com GeoJSON de limites administrativos | Simplicidade; reavaliar se forem precisas consultas espaciais | PostGIS de início |
| ADR-08 | **Conectores como implementações de uma interface comum** (`SourceConnector`: testar ligação, listar formulários, ler esquema, ler registos incrementais) | Novos conectores sem tocar no núcleo | Lógica específica de Kobo espalhada pelo código |

### 2.3 Pilha tecnológica (versões a confirmar e fixar na fase 3)

- **Backend:** Laravel (versão estável actual, compatível com PHP 8.5), Sanctum, Pest/PHPUnit, Laravel Pint, Larastan.
- **Frontend:** React, TypeScript, Vite, React Router, TanStack Query (cache e estados de carregamento), react-i18next, Apache ECharts, Leaflet (react-leaflet), react-grid-layout (arrastar/redimensionar), Vitest + Testing Library.
- **Base de dados:** PostgreSQL (versão suportada mais recente).
- **Importação de ficheiros:** OpenSpout (leitura em *streaming* de XLSX/CSV, baixo consumo de memória).
- **Testes E2E (a partir da fase 7):** Playwright.

Nenhuma dependência adicional sem justificação registada.

### 2.4 Estrutura de módulos (backend)

```
app/
  Domain/
    Access/         utilizadores, papéis, permissões, policies
    Projects/       projectos, membros
    Sources/        fontes de dados, credenciais, conectores
      Connectors/Kobo/  Csv/  (Graph/ Sql/ mais tarde)
    Ingestion/      sincronização, registos brutos, versões de esquema
    Datasets/       datasets normalizados, mapeamento de variáveis
    Query/          QuerySpec, validador, compilador SQL, supressão de células
    Indicators/     catálogo, versões de fórmula, metas, resultados
    Quality/        regras, execuções, problemas
    Dashboards/     dashboards, widgets, partilhas, publicação
    Audit/          registo de auditoria
  Http/Controllers/Api/V1/ ...
```

---

## 3. Esquema inicial da base de dados

Convenções: chaves `bigint` internas + `uuid` público nas entidades expostas por URL (evita enumeração), `timestamps` em todas as tabelas, *soft delete* apenas onde fizer sentido de negócio, nunca em registos brutos.

### 3.1 Acesso e projectos

| Tabela | Colunas principais |
|---|---|
| `users` | id, uuid, name, email (único, minúsculas), password, locale (`pt`/`en`), **global_role_id** (máx. um papel global), is_active, last_login_at |
| `roles` | id, key (`super_admin`, `org_admin`, `project_manager`, `mel_officer`, `analyst`, `viewer`), scope (`global`/`project`) |
| `permissions` | id, key (ex.: `datasets.view`, `pii.view`, `dashboards.publish`, `exports.raw`) |
| `role_permission` | role_id, permission_id |
| `projects` | id, uuid, code, name, description, donor, start_date, end_date, status |
| `project_members` | project_id, user_id, role_id, **can_view_pii** (concessão individual), granted_by · único (project_id, user_id) |
| `audit_logs` | id, user_id, action, subject_type, subject_id, project_id, ip, user_agent, metadata JSONB, created_at · **só inserção** |

### 3.2 Fontes e ingestão

| Tabela | Colunas principais |
|---|---|
| `data_sources` | id, uuid, project_id, type (`kobo`/`file`/…), name, config JSONB (sem segredos), credential_id, status, sync_schedule, last_synced_at |
| `source_credentials` | id, type, label, secret (**cifrado** com cast `encrypted`), server_url, created_by, last_verified_at |
| `source_forms` | id, data_source_id, external_uid (asset uid no Kobo), name, current_version_id, is_enabled |
| `form_versions` | id, source_form_id, external_version, schema JSONB (perguntas, tipos, opções, grupos de repetição), schema_hash, detected_at |
| `sync_runs` | id, source_form_id, trigger (`manual`/`scheduled`/`webhook`), status, started_at, finished_at, cursor JSONB, fetched, inserted, updated, unchanged, failed, source_count, error JSONB |
| `raw_records` | id, source_form_id, external_id (`_id`), external_uuid (`_uuid`/instanceID), form_version_id, payload JSONB, payload_hash, submitted_at, source_updated_at, first_seen_at, last_seen_at, is_deleted_at_source · único (source_form_id, external_id) |
| `raw_record_revisions` | id, raw_record_id, payload JSONB, payload_hash, captured_at, sync_run_id — histórico **imutável** de cada alteração detectada |
| `file_imports` | id, data_source_id, original_name, stored_path, checksum, rows_total, rows_imported, status, uploaded_by |

Regra: `raw_records.payload` nunca é alterado em sítio; uma alteração na fonte cria uma revisão e actualiza o ponteiro para o payload mais recente.

### 3.3 Datasets normalizados e mapeamento

| Tabela | Colunas principais |
|---|---|
| `datasets` | id, uuid, project_id, source_form_id, name, description, refreshed_at |
| `dataset_fields` | id, dataset_id, key, source_path (ex.: `grupo/idade`), label_pt, label_en, data_type (`text`/`integer`/`decimal`/`date`/`datetime`/`select_one`/`select_multiple`/`geopoint`/`boolean`), choices JSONB, is_pii, is_dimension, is_metric, is_enabled |
| `standard_concepts` | id, key (`project`, `province`, `district`, `gender`, `age`, `age_group`, `activity`, `reporting_period`, `beneficiary_id`…), data_type, value_set JSONB |
| `field_mappings` | id, dataset_field_id, standard_concept_id, value_map JSONB (ex.: `{"f":"female","m":"male"}`), mapped_by, approved_by, approved_at |
| `dataset_records` | id, dataset_id, raw_record_id, **colunas padrão tipadas**: project_id, province_code, district_code, gender, age, activity_date, reporting_period, beneficiary_key, latitude, longitude; `values` JSONB (restantes campos normalizados) · índices B-tree nas colunas padrão + GIN em `values` |
| `dataset_record_items` | id, dataset_record_id, repeat_group, item_index, `values` JSONB — linhas de grupos de repetição |
| `admin_areas` | code, level (`province`/`district`/`posto`), name, parent_code, geometry_ref — tabela de referência geográfica |

Notas:
- A normalização é reprodutível: apagar e regenerar `dataset_records` a partir de `raw_records` deve dar o mesmo resultado.
- Campos com nomes parecidos **não** são unidos automaticamente; o mapeamento para conceitos padrão é explícito e aprovado.
- `select_multiple` é guardado como array JSON, permitindo contagens por opção.
- Campos `is_pii = true` são omitidos de todas as respostas a quem não tenha `pii.view`.

### 3.4 Indicadores

| Tabela | Colunas principais |
|---|---|
| `indicators` | id, uuid, project_id (nulo = organizacional), code, name_pt, name_en, definition, unit, measurement_type (`unique_individuals`/`participations`/`estimated_reach`/`percentage`/`ratio`/`sum`/`average`), reporting_frequency, required_disaggregations JSONB, responsible_user_id, status |
| `indicator_versions` | id, indicator_id, version, formula JSONB (DSL), dataset_id, validation_rules JSONB, change_reason, created_by, effective_from · **imutável após uso** |
| `indicator_targets` | id, indicator_id, period_start, period_end, disaggregation JSONB, baseline, target, source_note |
| `indicator_results` | id, indicator_version_id, period_start, period_end, disaggregation JSONB, value, numerator, denominator, is_estimate, filters JSONB, input_fingerprint, computed_at, computed_by, status (`draft`/`validated`/`approved`/`rejected`), approved_by, approved_at |

Exemplo de fórmula (DSL):
```json
{ "type": "count_distinct", "field": "beneficiary_key",
  "filters": [{ "field": "activity", "op": "in", "value": ["formacao_jovens"] }] }
```
Tipos iniciais: `count`, `count_distinct`, `sum`, `avg`, `ratio` (numerador/denominador, cada um uma fórmula). `count_distinct` só é permitido sobre um campo mapeado para `beneficiary_id` — evita confundir participações com indivíduos únicos.

### 3.5 Qualidade de dados

| Tabela | Colunas principais |
|---|---|
| `dq_rules` | id, dataset_id, type (`required`/`range`/`date_valid`/`duplicate`/`consistency`/`coordinates`/`schema_change`/`count_reconciliation`), config JSONB, severity (`info`/`warning`/`critical`), is_active |
| `dq_runs` | id, dataset_id, sync_run_id, started_at, finished_at, issues_found |
| `dq_issues` | id, dq_rule_id, dataset_record_id (nulo em problemas de nível de dataset), details JSONB, status (`open`/`acknowledged`/`resolved`/`false_positive`), first_detected_at, last_detected_at |
| `dq_issue_events` | id, dq_issue_id, user_id, from_status, to_status, comment, created_at |

As regras **sinalizam**; nunca apagam nem corrigem registos.

### 3.6 Dashboards

| Tabela | Colunas principais |
|---|---|
| `dashboards` | id, uuid, project_id, name, description, layout JSONB, global_filters JSONB, owner_id, visibility (`private`/`project`/`shared`/`public`) |
| `dashboard_widgets` | id, dashboard_id, type (`kpi`/`bar`/`line`/`area`/`pie`/`scatter`/`table`/`map`), title, query_spec JSONB, options JSONB, grid_position JSONB |
| `dashboard_shares` | id, dashboard_id, user_id, permission (`view`/`edit`) |
| `dashboard_publications` | id, dashboard_id, public_token, approved_by, approved_at, min_cell_size, revoked_at, snapshot JSONB |

---

## 4. Endpoints da API (v1) e módulos

Todas as rotas sob `/api/v1`, autenticadas, autorizadas por *Policy*, validadas por *Form Request*, paginadas.

| Módulo | Endpoints principais |
|---|---|
| Autenticação | `GET /sanctum/csrf-cookie` · `POST /auth/login` · `POST /auth/logout` · `GET /auth/me` · `POST /auth/forgot-password` · `POST /auth/reset-password` |
| Utilizadores | `GET/POST /users` · `GET/PATCH /users/{uuid}` · `POST /users/{uuid}/deactivate` |
| Projectos | `GET/POST /projects` · `GET/PATCH /projects/{uuid}` · `GET/POST/DELETE /projects/{uuid}/members` |
| Fontes | `GET/POST /projects/{uuid}/sources` · `POST /sources/{uuid}/test-connection` · `GET /sources/{uuid}/remote-forms` · `POST /sources/{uuid}/forms` |
| Credenciais | `POST /credentials` · `PATCH /credentials/{id}` (o segredo nunca é devolvido) |
| Sincronização | `POST /forms/{id}/sync` · `GET /forms/{id}/sync-runs` · `GET /sync-runs/{id}` |
| Importação | `POST /sources/{uuid}/imports` (upload) · `GET /imports/{id}` · `POST /imports/{id}/confirm` |
| Datasets | `GET /projects/{uuid}/datasets` · `GET /datasets/{uuid}` · `GET /datasets/{uuid}/fields` · `PATCH /dataset-fields/{id}` · `PUT /dataset-fields/{id}/mapping` · `GET /datasets/{uuid}/records` (com PII filtrada) |
| Consulta | `POST /query` (corpo: QuerySpec) → resultado agregado + metadados (fonte, filtros, período, células suprimidas) |
| Indicadores | `GET/POST /indicators` · `GET/PATCH /indicators/{uuid}` · `POST /indicators/{uuid}/versions` · `GET/POST /indicators/{uuid}/targets` · `POST /indicators/{uuid}/calculate` · `GET /indicators/{uuid}/results` · `POST /indicator-results/{id}/approve` |
| Qualidade | `GET/POST /datasets/{uuid}/dq-rules` · `POST /datasets/{uuid}/dq-runs` · `GET /dq-issues` · `PATCH /dq-issues/{id}` |
| Dashboards | `GET/POST /dashboards` · `GET/PUT/DELETE /dashboards/{uuid}` · `POST /dashboards/{uuid}/duplicate` · `PUT /dashboards/{uuid}/shares` · `POST /dashboards/{uuid}/publish` · `DELETE /dashboards/{uuid}/publication` |
| Público | `GET /public/dashboards/{token}` (sem autenticação; só agregados com supressão de células pequenas) |
| Exportação | `POST /exports` (QuerySpec ou dataset + formato) · `GET /exports/{id}/download` (ligação temporária assinada) |
| Auditoria | `GET /audit-logs` (só administradores) |

Contrato do `QuerySpec` (esboço):
```json
{ "dataset": "uuid", "dimensions": ["province_code", "gender"],
  "metrics": [{ "op": "count_distinct", "field": "beneficiary_key", "as": "beneficiarios" }],
  "filters": [{ "field": "activity_date", "op": "between", "value": ["2026-01-01", "2026-06-30"] }],
  "sort": [{ "by": "beneficiarios", "dir": "desc" }], "limit": 500 }
```
O backend: (1) verifica acesso ao projecto do dataset; (2) confirma que cada campo existe, está activo e não é PII (ou que o utilizador tem `pii.view`); (3) compila para SQL parametrizado; (4) aplica limite máximo de linhas; (5) devolve metadados de proveniência.

---

## 5. Segurança e autorização

### 5.1 Modelo de autorização
- **Global:** `super_admin` (tudo), `org_admin` (utilizadores, projectos, credenciais).
- **Por projecto:** `project_manager`, `mel_officer`, `analyst`, `viewer`.
- **Permissões sensíveis separadas:** `pii.view`, `exports.raw`, `dashboards.publish`, `indicators.approve`.
- Cada pedido resolve o projecto **a partir do recurso na base de dados**, nunca a partir de parâmetros enviados pelo cliente. Um `dashboard_widget` que referencia um dataset de outro projecto é recusado na gravação e na execução.
- Testes automáticos de autorização obrigatórios para cada endpoint (utilizador sem acesso → 403/404; utilizador com acesso de leitura → sem escrita).

### 5.2 Controlos
| Área | Controlo |
|---|---|
| Sessões | Cookie `HttpOnly`, `Secure`, `SameSite=Lax`; regeneração ao iniciar sessão; expiração por inactividade |
| Palavras-passe | Hash bcrypt/argon2; política de comprimento; limite de tentativas (*rate limiting*) no login; 2FA (TOTP) recomendado para administradores |
| Segredos | Tokens Kobo cifrados (`encrypted` cast); `APP_KEY` gerida fora do repositório; nunca registados em logs nem devolvidos pela API |
| Entrada | Form Requests; QuerySpec com lista branca; consultas parametrizadas; validação de tipo/tamanho de ficheiros |
| Web | HTTPS obrigatório; HSTS; CSP restritiva; protecção CSRF; cabeçalhos de segurança |
| PII | Campos marcados `is_pii`; mascaramento por omissão; exportações de dados brutos exigem `exports.raw` e ficam auditadas |
| Painéis públicos | Aprovação explícita; apenas agregados; supressão de células com n < limiar (proposta: 5); filtros públicos limitados a dimensões aprovadas; sem acesso a registos |
| IA (fase 11) | Só através do motor de consultas; recebe agregados, nunca PII; desligável sem afectar o núcleo |
| Auditoria | Login, alterações de permissões, credenciais, sincronizações, exportações, aprovações, publicações |
| Retenção | Política configurável por fonte (a definir com a organização); cópias de segurança cifradas e teste de reposição documentado |

---

## 6. Integração KoboToolbox — estratégia (a confirmar na fase 5)

Com base no conhecimento actual da API v2 — **todos os pontos serão verificados contra a documentação oficial e um servidor real antes de implementar**:

- Autenticação por token (`Authorization: Token …`), por servidor (`kf.kobotoolbox.org`, `eu.kobotoolbox.org` ou instância própria).
- Listagem de formulários: `/api/v2/assets/`; esquema: detalhe do asset (conteúdo do XLSForm e versões implantadas).
- Submissões: `/api/v2/assets/{uid}/data/` com paginação (`limit`/`start`) e filtro `query` (por ex. `_submission_time` > último cursor).
- **Sincronização incremental** pelo cursor de tempo de submissão + janela de sobreposição; **edições** detectadas por comparação de `payload_hash` (e por campos de edição, se a API os expuser — a verificar).
- **Eliminações na fonte** detectadas por reconciliação periódica da lista completa de `_id`; marcadas, nunca apagadas localmente.
- **Quase tempo real:** agendamento incremental (proposta: cada 15 min) + possível uso de *REST Services* do Kobo como gatilho de sincronização (a verificar disponibilidade e fiabilidade; o webhook apenas dispara a sincronização, não é tratado como fonte de verdade).
- Tolerância a falhas: *retry* com recuo exponencial, respeito por respostas 429, retoma a partir do cursor, idempotência por (formulário, `_id`).
- Alterações de esquema: novo `form_versions` + problema de qualidade `schema_change`.

---

## 7. Âmbito do MVP

**Incluído**
1. Autenticação, utilizadores, projectos, papéis e permissões, auditoria.
2. Conector KoboToolbox (manual + agendado), armazenamento bruto e revisões, estado das sincronizações.
3. Normalização, mapeamento de variáveis para conceitos padrão, grupos de repetição, `select_multiple`, datas, geopontos.
4. Regras de qualidade: obrigatórios, intervalos, datas inválidas, duplicados, coordenadas, mudança de esquema, reconciliação de contagens.
5. Motor de consultas + construtor de dashboards: KPI, barras, linhas, área, circular, dispersão, tabela, mapa de pontos e coroplético por província/distrito; filtros globais e por widget; arrastar/redimensionar; guardar, duplicar, partilhar internamente.
6. Catálogo de indicadores com versões de fórmula, metas, cálculo, comparação meta vs. realizado e aprovação.
7. Importação controlada de CSV/Excel (pré-visualização, mapeamento de colunas, confirmação).
8. Exportação CSV/XLSX de resultados agregados autorizados.
9. Interface em Português e Inglês.

**Fora do MVP:** Microsoft Graph (SharePoint/OneDrive), conectores SQL, IA, junções entre fontes, modelação semântica avançada, painéis públicos (proposto para o fim do MVP ou logo após, dependendo da decisão D7).

---

## 8. Fases, dependências e marcos

```
F1 Requisitos/Arquitectura ─▶ F2 Modelo de dados + contratos API ─▶ F3 Fundação
                                                                      │
                                                                      ▼
                                                    F4 Auth, Projectos, RBAC  ◀── Marco M1
                                                                      │
                                     ┌────────────────────────────────┤
                                     ▼                                ▼
                         F5 Kobo + Sincronização           (F9a CSV/Excel import —
                                     │                      pode começar após F6)
                                     ▼
                         F6 Normalização + Qualidade  ◀── Marco M2
                                     │
                     ┌───────────────┴───────────────┐
                     ▼                               ▼
          F7 Motor de consultas + Dashboards   F8 Indicadores (usa o motor de F7)
                     │                               │
                     └───────────────┬───────────────┘
                                     ▼
                       F9 Importações/Exportações  ◀── Marco M3 = MVP
                                     ▼
                       F10 Graph + SQL ─▶ F11 IA ─▶ F12 Auditoria de segurança,
                                                     desempenho, implantação
```

Dependências críticas:
- F5 depende de F4 (credenciais e fontes pertencem a projectos).
- F7 depende de F6 (o motor consulta `dataset_records`, não dados brutos).
- F8 depende do motor de F7 (indicadores e dashboards partilham o mesmo compilador — um só ponto de cálculo e autorização).
- F11 depende de F7/F8 (a IA só chama funções aprovadas).
- Segurança, testes e documentação são transversais; F12 é a verificação final, não o primeiro momento em que se pensa em segurança.

| Marco | Conteúdo | Demonstração |
|---|---|---|
| **M1** | F3 + F4 | Aplicação a correr localmente com login, projectos e permissões testadas |
| **M2** | F5 + F6 | Formulário Kobo de teste sincronizado, normalizado, com relatório de qualidade |
| **M3 (MVP)** | F7 – F9 | Dashboards e indicadores sobre dados sincronizados, importação de CSV/Excel, exportações |

---

## 9. Primeiro marco (M1) — critérios de aceitação

**Âmbito:** fundação da aplicação + autenticação + projectos + papéis e permissões. Sem integração Kobo.

### Fundação (F3)
1. Repositório Git inicializado com `README`, `.editorconfig`, `.env.example` (sem segredos) e estrutura de `docs/` (incluindo ADR-01 a ADR-08).
2. Laravel + React/TypeScript a correr localmente contra PostgreSQL; `composer test` e `npm test` executam sem falhas.
3. Ferramentas de qualidade configuradas e a passar: Pint, Larastan (nível acordado), ESLint, `tsc --noEmit`.
4. i18n configurado: todos os textos visíveis vêm de ficheiros de tradução `pt` e `en`; troca de idioma persiste no perfil do utilizador.
5. *Layout* base: barra lateral de navegação, cabeçalho, página inicial vazia com estado “sem dados”, páginas de erro 403/404/500.
6. Seeder com dados **sintéticos claramente identificados** (ex.: projecto “DEMO — Projecto Sintético”, utilizadores `@example.test`).

### Autenticação, projectos e permissões (F4)
7. Login e logout por sessão Sanctum; palavra-passe errada devolve erro genérico; 5 tentativas falhadas por minuto → bloqueio temporário (testado).
8. Recuperação de palavra-passe por e-mail (em desenvolvimento, via *mail log*/Mailpit).
9. CRUD de utilizadores (só `org_admin`/`super_admin`); desactivação impede login imediato e invalida sessões.
10. CRUD de projectos e gestão de membros com papel por projecto.
11. Um utilizador **só vê os projectos de que é membro**; o acesso directo por URL/UUID a um projecto alheio devolve 404 (testado por API, não só pela interface).
12. Matriz papel × acção coberta por testes automáticos (mínimo: cada endpoint de M1 × cada papel).
13. Registo de auditoria para login, logout, falha de login, criação/alteração de utilizadores, alteração de membros e papéis.
14. Testes de frontend para formulário de login, guarda de rotas e mudança de idioma.
15. Documento de resultados (`docs/progresso/M1.md`) com os comandos executados e o resultado real dos testes.

**Fora de M1:** 2FA (fica para F12, salvo decisão contrária), SSO Microsoft (ver D4).

---

## 10. Riscos principais

| Risco | Mitigação |
|---|---|
| Dados pessoais de beneficiários em ambientes de desenvolvimento | Só dados sintéticos; política escrita; nenhuma cópia de produção sem autorização formal |
| Inexistência de identificador único de beneficiário nos formulários Kobo | Indicadores de indivíduos únicos ficam bloqueados até existir campo mapeado para `beneficiary_id`; reportar como participações |
| Limites e comportamento real da API Kobo diferentes do esperado | Verificação na fase 5 com servidor real antes de desenhar o *job* final |
| Formulários com estruturas muito diferentes entre projectos | Mapeamento explícito por dataset; nada é unido automaticamente |
| Desempenho de agregações sobre JSONB | Colunas padrão tipadas e indexadas; tabelas de resumo/vistas materializadas quando medido como necessário |
| Âmbito excessivo do construtor de dashboards | MVP limitado a um dataset por widget, sem junções |

---

## 11. Decisões críticas em aberto

| # | Decisão | Proposta por omissão |
|---|---|---|
| D1 | ~~PostgreSQL local~~ | **Decidido (20/10/2026):** PostgreSQL via Homebrew, como serviço local |
| D2 | Fornecedor de *cloud* e região de alojamento (residência de dados de beneficiários) | Decisão institucional; a arquitectura funciona em qualquer VM/PaaS com PostgreSQL gerido |
| D3 | ~~Servidor Kobo~~ | **Decidido (20/10/2026):** `eu.kobotoolbox.org`. Tipo de conta (de serviço, só de leitura) por confirmar. Factos da API em [integracoes/kobo-api-v2.md](integracoes/kobo-api-v2.md) |
| D4 | ~~Autenticação~~ | **Decidido (20/10/2026):** contas locais |
| D5 | ~~Papéis~~ | **Decidido (20/10/2026):** papéis da secção 5.1 confirmados; `pii.view` atribuído individualmente |
| D6 | Fonte oficial dos limites administrativos de Moçambique (províncias/distritos) e códigos | Limites administrativos publicados (ex.: INE / OCHA COD-AB), sujeitos a verificação de licença |
| D7 | Painéis públicos dentro do MVP e limiar de supressão | Após o MVP; limiar n < 5 |
| D8 | ~~Repositório~~ | **Decidido (20/10/2026):** Git local por agora; migração posterior para GitHub |
| D9 | Existe identificador único de beneficiário nos formulários actuais? | A confirmar com a equipa MEL |
| D10 | Frequência de sincronização desejada e política de retenção de dados | 15 min incremental; retenção a definir pela organização |

---

## 12. Próximo passo

Após aprovação deste plano e resposta às decisões D1, D4, D5 e D8 (as que bloqueiam M1), avança-se para:
1. Fase 2 detalhada: migrações, diagrama ER e especificação OpenAPI dos endpoints de M1.
2. Fase 3 + 4: implementação do Marco M1 e respectivo relatório de testes.
