# Model de dades — diccionari d'entitats

**Fase 1A · Escola BetàniaPatmos · plataforma d'extraescolars**
Diagrames navegables: [`index.html`](index.html) · Estat: **proposta de disseny**, pendent de ratificació

---

## Per què existeix aquest document

La base de dades de **GNA migra cap a la nostra**, no al costat. Això vol dir que el model nou ha de ser un **superconjunt de tot el que el portal antic ja ensenya a un usuari**: la prova no és "cobreix els 25 casos d'ús de la Fase 1A", és **"podem tornar a mostrar el que ja tenien"**. Cada taula d'aquí està dissenyada contra les pantalles velles i contra els casos d'ús, en aquest ordre.

Dues conseqüències que expliquen mig document:

1. **L'històric sobreviu a les regles actuals.** GNA guarda les inscripcions en **quatre** taules (viva + històric × extraescolars + estiu) i no té **cap** clau forana en 174 taules. Hi haurà files òrfenes. Cap columna que l'històric no pugui omplir pot ser obligatòria.
2. **El text per idioma és una taula, no columnes.** GNA fa servir taules laterals `*_idiomes` amb `titol_1..N` / `text_1..N`, i el nombre de ranures viu com a **dada** a `SIS_activitats`. Aquí les traduccions són files amb camps amb nom.

---

## 0. Convencions

### 0.1 Nomenclatura

| Element | Regla | Exemple |
|---|---|---|
| Taula | **català**, `snake_case`, **plural** | `inscripcions`, `horaris`, `tipus_activitats` |
| Columna | **anglès**, `snake_case`; català només quan no hi ha terme fidel | `name`, `date_from`, `total` · `temporada_id`, `curs_id`, `etapa_id`, `bloc_id` |
| Enum | valors en **català**, `snake_case` | `state = 'confirmada'` |
| Clau primària | `id uuid` | |
| Clau forana | `<entitat_singular>_id` | `activitat_id` |

> **Decisió pendent de ratificació.** L'única entitat que ja existeix al backend és `Activitat` (classe en català, propietats en anglès: `Name`, `Description`, `Capacity`). Aquest document estén exactament aquesta barreja. Si es prefereix tot en anglès o tot en català, cal decidir-ho **abans** de la primera migració EF, no després.

### 0.2 Columnes que hi són a totes les taules i no es repeteixen avall

Del `BaseEntity` / `TenantEntity` del backend:

| Columna | Tipus | Notes |
|---|---|---|
| `id` | `uuid` PK | |
| `created_at` | `timestamptz` | segellat a `SaveChangesAsync` |
| `updated_at` | `timestamptz` NULL | |
| `org_id` | `uuid` FK → `orgs` | **només a les taules multi-tenant.** Filtre explícit a cada consulta **i** `HasQueryFilter` global (no-negociable #2). Els catàlegs globals del sistema (`permisos`) hereten `BaseEntity` i no en tenen |

### 0.3 Columnes de procedència, a totes les entitats migrades

| Columna | Tipus | Notes |
|---|---|---|
| `legacy_font` | `text` NULL | taula d'origen a GNA — `inscripcions`, `inscripcions_historic`, `inscripcions_estiu`… El `codi` de GNA **només és únic dins de la seva taula**, per això calen les dues columnes |
| `legacy_codi` | `text` NULL | `codi` original |

Les porten: `usuaris`, `alumnes`, `families`, `activitats`, `tipus_activitats`, `horaris`, `blocs`, `materials`, `cursos`, `etapes`, `inscripcions` i les seves filles. Índex `(legacy_font, legacy_codi)` per reconciliació. Una fila nascuda a la plataforma nova les té a `NULL` — és així com es distingeixen.

### 0.4 Traduccions

Una taula `<entitat>_traduccions` per entitat traduïble, amb `UK (<entitat>_id, locale)` i **camps amb nom**. `locale ∈ {ca, es, en}`; `ca` és obligatori, la resta opcional amb *fallback* a `ca`.

No es fa servir una taula polimòrfica única (`entity_type, entity_id, field, value`): no es pot tipar, no es pot indexar bé i tornaria a portar-nos al problema de les ranures numerades.

**Mapatge de la migració:** `titol_1` → `name` · `titol_2` → `activitat_imatge_traduccions.caption` (a GNA és el *peu de foto*, verificat) · `text_1` → `description`.

### 0.5 Diners, capacitat i dates

- Imports: `numeric(10,2)`, moneda **EUR** implícita a tota la plataforma.
- **La capacitat s'emmagatzema; l'ocupació es calcula.** No hi ha comptadors desnormalitzats de places ocupades (un comptador es desincronitza i es corromp a cada reintent). Amb el pic previst — **300 inscripcions en 3 minuts** — la comprovació de places ha de ser atòmica dins la transacció que crea la inscripció.
- Instants: `timestamptz`. Dates de calendari escolar: `date`. Hores d'horari: `time`.

---

## 1. Tenant, identitat i autorització

### `orgs`
El centre. Arrel del multi-tenant; avui una sola fila.

| Columna | Tipus | Notes |
|---|---|---|
| `name` | `text` | |
| `code` | `text` UK | |
| `timezone` | `text` | `Europe/Madrid` |
| `default_locale` | `text` | `ca` |
| `active` | `bool` | |

### `usuaris`
**El compte que entra i que paga.** No és la persona que s'inscriu — això són els `alumnes`.

| Columna | Tipus | Notes |
|---|---|---|
| `kind` | `enum` | `intern` \| `extern` \| `treballador` |
| `auth_source` | `enum` | `clickedu` \| `local`. **`intern` = té compte de ClickEdu**; `extern` = registre local. És l'única definició operativa d'intern/extern |
| `clickedu_username` | `text` UK NULL | l'identificador de ClickEdu. **Un únic tipus d'identificador**, no quatre |
| `cognito_sub` | `text` UK NULL | |
| `email` | `text` | UK `(org_id, email)` |
| `dni` | `text` UK NULL | validat amb lletra de control. La comprovació de duplicats a l'alta d'externs es fa **només contra la nostra BD**, mai contra ClickEdu |
| `given_name`, `surname_1`, `surname_2` | `text` | |
| `phone`, `phone_alt` | `text` NULL | |
| `address`, `postal_code`, `city` | `text` NULL | |
| `locale` | `text` | |
| `status` | `enum` | `actiu` \| `pendent_validacio` \| `suspes` |
| `is_staff` | `bool` | treballador/a del centre. Arriba de ClickEdu o el marca un administrador |
| `password_hash` | `text` NULL | **no autentica a la Fase 1A.** El desa el Lambda Post-Auth per a una futura migració a autenticació pròpia. Cap contrasenya en clar, mai |

> **Mai es migra cap credencial.** A GNA n'hi ha en quatre llocs: `registre_estiu.password` (en clar), `inscripcions.pass` (comentari de l'autor: `pwd`) i les seves variants `_historic` / `_estiu` / `_cupos`, `accio_inscripcions.password`, `bet_users.password`. Totes es descarten a la importació.

### `rols`
11 rols predefinits, editables en execució.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | = grup de Cognito |
| `name`, `description` | `text` | |
| `is_predefined` | `bool` | |
| `suspended_at` | `timestamptz` NULL | suspensió temporal |
| `cloned_from_rol_id` | `uuid` FK NULL | clonar un rol predefinit |

No es pot esborrar un rol assignat a usuaris actius.

### `permisos`
Catàleg **tancat i contractual** de 19 permisos. Global (`BaseEntity`, sense `org_id`).

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | `gestionar_usuaris`, `assignar_rols`, `configurar_sistema`, … |
| `name` | `text` | |
| `level` | `enum` | `baix` \| `mitja` \| `mitja_alt` \| `alt` \| `molt_alt`. Tot `alt` / `molt_alt` genera registre d'auditoria |

### `rol_permisos`
La matriu. PK composta `(rol_id, permis_id)`.

| Columna | Tipus | Notes |
|---|---|---|
| `rol_id` | `uuid` FK | |
| `permis_id` | `uuid` FK | |
| `scope` | `enum` | `si` \| `parcial` \| `lectura` \| `assignades` \| `propies` \| `del_dia` \| `disponibles` \| `crear` \| `agregat` |

Els permisos efectius són la **unió** dels rols; amb dos àmbits diferents **guanya el més ampli**; un permís **no es resta mai** (es treu el rol). Els canvis són efectius a la petició següent, sense tornar a iniciar sessió — la matriu es cacheja a l'arrencada i s'invalida per SNS.

### `usuari_rols`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id`, `rol_id` | `uuid` FK | PK composta |
| `assigned_at` | `timestamptz` | |
| `assigned_by_usuari_id` | `uuid` FK | |

---

## 2. Famílies i persones inscribibles

> **Tres nivells, i no s'han de barrejar:** el **compte que paga** (`usuaris`) → els **familiars que s'inscriuen** (`alumnes`) → les seves **activitats** (`inscripcions`). Els familiars inscribibles són **només fills**: el titular del compte no és una entitat inscribible.

### `families`
| Columna | Tipus | Notes |
|---|---|---|
| `clickedu_family_id` | `text` UK NULL | |
| `label` | `text` | nom de la unitat familiar, per a la cerca administrativa |

### `familia_usuaris`
Quins comptes pertanyen a una família i amb quin paper.

| Columna | Tipus | Notes |
|---|---|---|
| `familia_id`, `usuari_id` | `uuid` FK | PK composta |
| `relation` | `enum` | `pare` \| `mare` \| `tutor` |
| `is_primary` | `bool` | |
| `custody_exclusive` | `bool` | **les confirmacions van als dos tutors** excepte si ClickEdu marca custòdia exclusiva |

### `alumnes`
La persona inscribible. Pot venir de ClickEdu (intern) o haver-la creat el titular del compte (extern).

| Columna | Tipus | Notes |
|---|---|---|
| `familia_id` | `uuid` FK | |
| `given_name`, `surname_1`, `surname_2` | `text` | |
| `birth_date` | `date` | filtre per edat al catàleg i regles de curs |
| `dni` | `text` NULL | |
| `curs_id` | `uuid` FK NULL | |
| `clickedu_student_id` | `text` UK NULL | |
| `origin` | `enum` | `clickedu` \| `local`. Determina qui és el `font de veritat` de cada camp |
| `health_card_number` | `text` NULL | `catsalut`. **Dada sensible** |
| `active` | `bool` | |

### `alumne_salut`
1:1 amb `alumnes`, **taula separada a propòsit**: retenció, xifrat i consentiment propis.

| Columna | Tipus | Notes |
|---|---|---|
| `alumne_id` | `uuid` PK FK | |
| `allergies` | `text` NULL | |
| `obs_alimentacio` | `text` NULL | |
| `obs_natacio` | `text` NULL | |
| `malalties` | `text` NULL | |
| `share_with_monitors` | `bool` | **consentiment explícit** per compartir al·lèrgies amb els monitors; enllaça a `consentiment_atorgaments` |

> A GNA aquestes dades són a `fills_registre` **i també** desnormalitzades sobre cada fila d'`inscripcions` (`alergies`, `obs_alimentacio`, `obs_natacio`). Aquí viuen en un sol lloc; la inscripció no en guarda còpia.

---

## 3. Estructura acadèmica i calendari

### `temporades`
El curs escolar. A GNA és `temporada`, calculat de setembre a agost.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | `2026-2027` |
| `date_from`, `date_to` | `date` | |
| `is_current` | `bool` | |
| `promotion_date` | `date` | dia de la promoció interna de cursos, per defecte 1 de juny |

### `etapes`
| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | |
| `ordre` | `int` | |
| `active` | `bool` | |

`etapa_traduccions`: `name`.

### `cursos`
| Columna | Tipus | Notes |
|---|---|---|
| `etapa_id` | `uuid` FK | |
| `code` | `text` UK | |
| `ordre` | `int` | |
| `next_curs_id` | `uuid` FK NULL | **el +1 de la promoció interna es declara, no es calcula.** L'últim curs de l'etapa apunta al primer de la següent, o a `NULL` |
| `enrolment_opens_at`, `enrolment_closes_at` | `timestamptz` NULL | dates d'inscripció per defecte **per curs**; una activitat les pot sobreescriure |
| `active` | `bool` | |

`curs_traduccions`: `name`.

> L'etapa `Altres` conté els cursos d'adults del catàleg (**Externs · Antics alumnes i familiars · Altres**). Una entitat inscribible sense `curs_id` vàlid **no es pot inscriure**, per molt visible que sigui al selector.

### `festius`
| Columna | Tipus | Notes |
|---|---|---|
| `temporada_id` | `uuid` FK | |
| `date` | `date` | |
| `name` | `text` | |

---

## 4. Catàleg d'activitats

### `tipus_activitats`
La categoria. A GNA és `w_tipus_activitats` i està **fitada per secció** — el desplegable de categories també ho ha d'estar aquí.

| Columna | Tipus | Notes |
|---|---|---|
| `seccio` | `enum` | `extraescolars` \| `altres` |
| `code` | `text` | UK `(org_id, seccio, code)` |
| `responsible_name` | `text` NULL | |
| `ordre` | `int` | |
| `active` | `bool` | |

`tipus_activitat_traduccions`: `name`, `description`.
`tipus_activitat_responsables`: `tipus_activitat_id`, `email` — els destinataris de les còpies de confirmació.

### `activitats`

| Columna | Tipus | Notes |
|---|---|---|
| `temporada_id` | `uuid` FK | |
| `tipus_activitat_id` | `uuid` FK | |
| `seccio` | `enum` | `extraescolars` \| `altres`. Substitueix `codi_seccio` 1/2. **`estiu` era un nom de fitxer, no una etiqueta** |
| `audience` | `enum` | `interns` \| `externs` \| `tots`. A GNA (`activitat_per_interns_o_externs`) viu sobre l'**activitat**, no sobre l'usuari; es manté així |
| `date_from`, `date_to` | `date` | període en què es fa l'activitat |
| `enrolment_opens_at`, `enrolment_closes_at` | `timestamptz` NULL | `NULL` = hereta del curs |
| `state` | `enum` | `esborrany` \| `publicada` \| `validada` \| `finalitzada` \| `arxivada` |
| `published_admin_only` | `bool` | publicada només per a alta manual |
| `competition` | `bool` | |
| `payment_card` | `enum` | `obligatori` \| `opcional` \| `no_aplica` — **per activitat**, i només aplica a externs |
| `min_period_value` | `int` NULL | `NULL` = hereta de la configuració global |
| `min_period_unit` | `enum` NULL | `cap` \| `dia` \| `setmana` \| `mes` \| `trimestre` \| `curs` |
| `places_shared` | `bool` | places compartides entre cursos o per curs |
| `duplicated_from_activitat_id` | `uuid` FK NULL | duplicar = còpia completa |
| `active` | `bool` | |

`activitat_traduccions`: `name` **(obligatori en `ca`)**, `description`, `conditions`.

> **Camps obligatoris a la creació: decisió del client, encara oberta.** GNA **no valida res** — ni títol, ni tipus, ni dates; l'única guarda substitueix la categoria buida per `0` i desa igualment. Portar-ho literalment vol dir permetre una activitat sense nom. Mínim proposat: `name` en `ca`, `tipus_activitat_id`, `seccio` i el rang `date_from`–`date_to`.

### `activitat_cursos`
Quins cursos poden inscriure-s'hi. PK `(activitat_id, curs_id)`.

### `activitat_imatges`
| Columna | Tipus | Notes |
|---|---|---|
| `activitat_id` | `uuid` FK | |
| `storage_key` | `text` | S3 |
| `ordre` | `int` | **l'ordre del portal és aquest ordre**: moure una imatge al panell la mou al catàleg |
| `is_cover` | `bool` | |

`activitat_imatge_traduccions`: `caption` — el destí de `titol_2` de GNA (*text peu de foto*).

### `activitat_responsables`
| Columna | Tipus | Notes |
|---|---|---|
| `activitat_id` | `uuid` FK | |
| `email` | `text` | |
| `kind` | `enum` | `responsable` \| `competicio` — llista d'adreces que creix, no un camp de text |

### `materials`
| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` | |
| `price` | `numeric(10,2)` | |
| `min_qty`, `max_qty` | `int` | `peu_minim` / `peu_maxim` |
| `ordre`, `active` | | |

`material_traduccions`: `name`.
`activitat_materials`: `activitat_id`, `material_id`, `required`.

> A GNA `w_activitats.material` i `.documents` són **cadenes d'identificadors separats per `#`**, no claus foranes. La migració les ha de partir cap a aquestes taules d'unió.

### `autoritzacions`
Caselles de verificació amb traçabilitat, **sense PIN**.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` | |
| `ordre`, `active` | | |

`autoritzacio_traduccions`: `name`, `text`.
`activitat_autoritzacions`: `activitat_id`, `autoritzacio_id`, `mandatory` — **una autorització obligatòria bloqueja la confirmació**.

### `documentacions`
Dues categories, com al panell: la que es demana i la que es lliura.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` | |
| `kind` | `enum` | `requerida` \| `lliurament` |
| `ordre`, `active` | | |

`documentacio_traduccions`: `name`, `instructions`.
`activitat_documentacions`: `activitat_id`, `documentacio_id`, `required`, `blocks_confirmation`.

> **Els PDF no bloquegen per defecte** — s'envien recordatoris. La documentació legalment obligatòria (sortida internacional) permet acabar la inscripció però la deixa **pendent**, amb recordatoris a 30 i 15 dies a les famílies i a 10 dies a secretaria.

### `activitat_adjunts`
PDF que l'administració adjunta i s'envien amb la confirmació.

| Columna | Tipus | Notes |
|---|---|---|
| `activitat_id` | `uuid` FK | |
| `storage_key`, `name` | `text` | |
| `prefill` | `bool` | s'omple amb les dades de la inscripció |

---

## 5. Horaris, places i preus

> Aquí és on GNA es desplega pitjor: `w_activitats_horaris` té `dia_1..7` × (`hora_inici`, `hora_fi`, `cupo`, `places`, `places_minimes`, `preu`) = **49 columnes per a una sola taula filla**, i la secció `altres` ho fa amb **taules físicament diferents**. Les dues formes es plegen aquí en una.

### `horaris`
| Columna | Tipus | Notes |
|---|---|---|
| `activitat_id` | `uuid` FK | |
| `curs_id` | `uuid` FK NULL | `NULL` = vàlid per a tots els cursos de l'activitat |
| `kind` | `enum` | `fix` \| `a_convenir` |
| `places`, `places_minimes`, `cupo` | `int` NULL | capacitat a nivell d'horari quan no és per dia |
| `ordre`, `active` | | |

### `horari_dies`
Una fila per dia real. Substitueix les 49 columnes.

| Columna | Tipus | Notes |
|---|---|---|
| `horari_id` | `uuid` FK | |
| `weekday` | `int` | 1–7. Els horaris `a_convenir` no tenen files aquí |
| `time_from`, `time_to` | `time` | |
| `places`, `places_minimes`, `cupo` | `int` | **la capacitat es guarda; l'ocupació es calcula** |
| `price` | `numeric(10,2)` | |

### `horari_combinacions` + `horari_combinacio_dies`
Combinacions de dies amb preu propi (`preuc1..3` i `w_activitats_horaris_combinacions` a GNA).

| Columna | Tipus | Notes |
|---|---|---|
| `horari_id` | `uuid` FK | |
| `label` | `text` | |
| `price` | `numeric(10,2)` | |
| `mandatory_days` | `bool` | dies obligatoris, es fan complir a la inscripció |

`horari_combinacio_dies`: `combinacio_id`, `weekday`.

### `blocs`
Períodes — les setmanes d'estiu de la secció `altres`.

| Columna | Tipus | Notes |
|---|---|---|
| `temporada_id` | `uuid` FK | |
| `code` | `text` | |
| `date_from`, `date_to` | `date` | |
| `ordre` | `int` | |

`bloc_traduccions`: `name`.

### `horari_blocs` + `horari_bloc_franges`
| Columna | Tipus | Notes |
|---|---|---|
| `horari_id`, `bloc_id` | `uuid` FK | |
| `places`, `cupo` | `int` | |

`horari_bloc_franges`: `horari_bloc_id`, `ordre` (1–3), `time_from`, `time_to`, `price` — normalitza `hora_inici_1..3` / `preu_1..3`.

### `preu_trams`
Preus per tipus d'usuari. Si un tram no està definit, **s'aplica el base**.

| Columna | Tipus | Notes |
|---|---|---|
| `activitat_id` | `uuid` FK | |
| `horari_id` | `uuid` FK NULL | `NULL` = val per a tota l'activitat |
| `target` | `enum` | `base` \| `treballador` \| `fill_treballador` \| `familiar_treballador` \| `extern` |
| `mode` | `enum` | `fix` \| `percentatge` |
| `value` | `numeric(10,2)` | |

> **Els preus només es mostren amb la sessió iniciada** i **la disponibilitat és un indicador derivat, mai un número cru** — verd/vermell, amb el recompte exacte només per sota de 5 i `+5` per sobre. A GNA els valors crus de `cupo`/`places` viuen dins d'un *popover* **comentat**, és a dir, deliberadament ocults. Cal mantenir-ho.

---

## 6. Inscripcions

### `inscripcions`
Una fila per (`alumne` × `activitat` × `temporada`). A GNA són **54 columnes** amb els **dos progenitors i el fill desnormalitzats sobre cada fila**, i l'històric és una taula física a part, no un registre d'auditoria: "esborrar llistes" copia i **esborra de la taula viva**.

| Columna | Tipus | Notes |
|---|---|---|
| `temporada_id` | `uuid` FK | |
| `alumne_id` | `uuid` FK | |
| `activitat_id` | `uuid` FK | |
| `horari_id` | `uuid` FK NULL | |
| `horari_combinacio_id` | `uuid` FK NULL | |
| `usuari_id` | `uuid` FK | el compte titular, qui paga |
| `created_by_usuari_id` | `uuid` FK | qui la va crear — pot ser administració |
| `es_administrativa` | `bool` | alta manual; permet excedir restriccions amb avís |
| `admin_reason` | `text` NULL | motiu registrat de l'excepció |
| `state` | `enum` | veure sota |
| `price_base` | `numeric(10,2)` | |
| `discount_pct` | `numeric(5,2)` | |
| `discount_reason` | `text` NULL | treballador, multi-fill, multi-activitat |
| `total` | `numeric(10,2)` | |
| `enrolled_at` | `timestamptz` | |
| `effective_from`, `effective_to` | `date` | |
| `baixa_requested_at` | `timestamptz` NULL | |
| `baixa_effective_at` | `date` NULL | segons el dia límit configurat: abans → mes actual, després → mes següent |
| `baixa_reason` | `text` NULL | el motiu que escriu la família |
| `previous_inscripcio_id` | `uuid` FK NULL | cadena del canvi d'activitat |
| `ip` | `inet` NULL | traçabilitat de les autoritzacions |
| `comments` | `text` NULL | |

**`state`** — `preinscripcio` · `pendent_de_completar` · `confirmada` · `pendent_validacio_documents` · `baixa` · `cancellada`.

> **Tres d'aquests estats no estan definits enlloc a l'especificació** (`pendent_de_completar`, `confirmada`, `PREINSCRIPCIÓ`). Estan aquí perquè el model els necessita, però **la definició es deu**: qui els posa, què els fa avançar, i què veu la família a cada un.

> **La baixa no esborra.** A GNA `EsborraElement` fa un `DELETE` dur sobre `inscripcions` sense cap comprovació prèvia, i el camí d'estiu deixa horaris i inscripcions òrfens. Aquí una baixa és un canvi d'estat amb data efectiva, i la plaça s'allibera pel càlcul d'ocupació.

> **El canvi d'activitat és atòmic**: mai 0 ni 2 inscripcions, i mai dos càrrecs dins del mateix mes. Això és, tot sol, la raó per la qual no es pot reutilitzar l'esquema de GNA: MyISAM no té transaccions.

### `inscripcio_dies`
Els dies escollits, **materialitzats**. Fan falta per validar solapaments i per tornar a dibuixar l'històric tal com el va veure la família.

| Columna | Tipus | Notes |
|---|---|---|
| `inscripcio_id` | `uuid` FK | |
| `weekday` | `int` | |
| `time_from`, `time_to` | `time` | |

### `inscripcio_blocs`
`inscripcio_id`, `bloc_id` — les setmanes escollides.

### `inscripcio_preferencies_horari`
Horari a convenir: fins a **3** preferències, la coordinació assigna.

| Columna | Tipus | Notes |
|---|---|---|
| `inscripcio_id` | `uuid` FK | |
| `ordre` | `int` | 1–3 |
| `horari_id` | `uuid` FK NULL | |
| `note` | `text` NULL | |
| `assigned` | `bool` | |

### `inscripcio_materials`
`inscripcio_id`, `material_id`, `qty`, `unit_price`.

### `inscripcio_autoritzacions`
| Columna | Tipus | Notes |
|---|---|---|
| `inscripcio_id`, `autoritzacio_id` | `uuid` FK | |
| `granted` | `bool` | |
| `granted_at` | `timestamptz` | |
| `granted_by_usuari_id` | `uuid` FK | |
| `ip` | `inet` | **traçabilitat: IP + segell de temps.** És el que substitueix el PIN |

### `inscripcio_documents`
| Columna | Tipus | Notes |
|---|---|---|
| `inscripcio_id`, `documentacio_id` | `uuid` FK | |
| `storage_key` | `text` | S3 xifrat, URL signada |
| `file_hash` | `text` | |
| `uploaded_at` | `timestamptz` | |
| `state` | `enum` | `pendent` \| `pujat` \| `validat` \| `rebutjat` |
| `reviewed_by_usuari_id` | `uuid` FK NULL | |
| `review_note` | `text` NULL | |

Retenció: cicle de vida de la inscripció **+ 1 any**; s'esborra en un dret de supressió.

### `inscripcio_estats`
El registre de canvis d'estat. **Substitueix l'arxiu físic de GNA**: aquí l'històric no es mou de taula.

| Columna | Tipus | Notes |
|---|---|---|
| `inscripcio_id` | `uuid` FK | |
| `from_state`, `to_state` | `enum` | |
| `at` | `timestamptz` | |
| `by_usuari_id` | `uuid` FK NULL | `NULL` = el sistema |
| `reason` | `text` NULL | |

---

## 7. Absències

### `motius_absencia`
Catàleg editable. `code`, `ordre`, `active` + `motiu_absencia_traduccions.name`.

### `absencies`
| Columna | Tipus | Notes |
|---|---|---|
| `alumne_id` | `uuid` FK | |
| `inscripcio_id` | `uuid` FK | |
| `date` | `date` | |
| `motiu_absencia_id` | `uuid` FK | |
| `note` | `text` NULL | |
| `communicated_at` | `timestamptz` | |
| `communicated_by_usuari_id` | `uuid` FK | |

Per defecte l'assistència queda com a **justificada**. Una absència comunicada **inhibeix** l'avís immediat a secretaria de primària. El temps mínim d'antelació, els cursos que disparen l'avís i els destinataris són configuració global.

> El marcatge d'assistència (passa-llista) és **Fase 1B**. Aquí només hi ha la comunicació prèvia i el catàleg de motius.

---

## 8. Pagaments

> **Els interns no paguen a la plataforma.** El cobrament dels interns el gestionen secretaria i ClickEdu; per a ells el mètode és `fora_de_plataforma` i no hi ha proveïdor. Stripe és **només per a externs**.

### `pagaments`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id` | `uuid` FK | |
| `method` | `enum` | `targeta` \| `transferencia` \| `fora_de_plataforma` |
| `provider` | `enum` NULL | `stripe` \| `NULL` |
| `amount` | `numeric(10,2)` | |
| `state` | `enum` | `pendent` \| `pagat` \| `fallit` \| `retornat` |
| `stripe_payment_intent_id` | `text` NULL | |
| `stripe_subscription_id` | `text` NULL | recurrent mensual |
| `paid_at` | `timestamptz` NULL | |

### `pagament_linies`
Una cistella paga diverses inscripcions. `pagament_id`, `inscripcio_id`, `amount`.

### `devolucions`
`pagament_id`, `amount`, `reason`, `stripe_refund_id`, `at`, `by_usuari_id`.

### `descomptes`
| Columna | Tipus | Notes |
|---|---|---|
| `code` | `enum` | `treballador` \| `multi_fill` \| `multi_activitat` |
| `mode` | `enum` | `fix` \| `percentatge` |
| `value` | `numeric(10,2)` | treballador per defecte 50 % |
| `active` | `bool` | |

Un canvi amb impacte econòmic genera registre d'auditoria.

---

## 9. Comunicacions

### `plantilles_email`
Els cossos dels correus del sistema, editables per administració sense desenvolupament.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | |
| `event` | `enum` | `confirmacio` \| `baixa` \| `recordatori` \| `assignacio_horari` \| `preinscripcio` \| `document_pendent` |
| `active` | `bool` | |
| `auto_send` | `bool` | l'administració pot desactivar-lo temporalment per a proves |

`plantilla_email_traduccions`: `subject`, `body_html`.

Marcadors des d'un selector: `{{nom_alumne}}`, `{{activitat}}`, `{{data}}`, `{{horari}}`, `{{text_activitat}}` — l'últim viu a l'activitat i se substitueix o s'elimina.

### `enviaments`
| Columna | Tipus | Notes |
|---|---|---|
| `plantilla_id` | `uuid` FK | |
| `to_email` | `text` | |
| `inscripcio_id`, `usuari_id` | `uuid` FK NULL | |
| `state` | `enum` | `en_cua` \| `enviat` \| `fallit` |
| `provider_message_id` | `text` NULL | SES |
| `sent_at` | `timestamptz` NULL | |
| `error` | `text` NULL | |

Amb el pic d'inscripcions la confirmació pot arribar amb ~30 s de retard: la cua és part del disseny, i `en_cua` és un estat visible.

---

## 10. Configuració, consentiments i privacitat

### `configuracions`
**Res codificat.** Tot paràmetre operatiu s'edita en execució.

| Columna | Tipus | Notes |
|---|---|---|
| `key` | `text` | UK `(org_id, key)` |
| `value` | `jsonb` | |
| `group` | `enum` | `calendari` \| `dates_inscripcio` \| `descomptes` \| `cataleg` \| `textos_legals` \| `idioma` |
| `updated_by_usuari_id` | `uuid` FK | |

Claus de dates d'inscripció: dia límit d'alta/baixa (p. ex. 25), marge entre individual i grup **amb unitat** (`hores` \| `dies` \| `setmanes`), període mínim **amb unitat**. La pantalla ha de mostrar un exemple calculat en viu — sense això la família no entén què vol dir el número.

### `consentiments`
Catàleg, **no una llista al codi**.

| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` | |
| `ambit` | `enum` | `registre` \| `inscripcio` \| `ambdos` — on apareix |
| `mandatory` | `bool` | |
| `ordre`, `active` | | |

`consentiment_traduccions`: `name`, `text`.

### `consentiment_atorgaments`
| Columna | Tipus | Notes |
|---|---|---|
| `consentiment_id` | `uuid` FK | |
| `usuari_id` | `uuid` FK | |
| `alumne_id` | `uuid` FK NULL | p. ex. compartir al·lèrgies d'un fill amb els monitors |
| `granted` | `bool` | |
| `at` | `timestamptz` | |
| `ip` | `inet` | |
| `revoked_at` | `timestamptz` NULL | dret d'oposició / limitació |

### `textos_legals`
`code` (`rgpd` \| `condicions` \| `cookies`) + `text_legal_traduccions.body_html`.

### `sollicituds_rgpd`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id` | `uuid` FK | |
| `kind` | `enum` | `acces` \| `rectificacio` \| `oposicio` \| `supressio` |
| `state` | `enum` | `rebuda` \| `en_analisi` \| `aprovada` \| `denegada` \| `executada` |
| `requested_at`, `resolved_at` | `timestamptz` | revisió manual, ≤ 30 dies |
| `resolved_by_usuari_id` | `uuid` FK NULL | |
| `legal_retention_note` | `text` NULL | què es conserva per obligació legal |
| `export_storage_key` | `text` NULL | JSON + PDF llegible, generats en < 1 min |

---

## 11. Auditoria i processos

### `audit_logs`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id` | `uuid` FK NULL | |
| `ip` | `inet` | |
| `action` | `text` | |
| `entity_type`, `entity_id` | `text` / `uuid` | |
| `permission_level` | `enum` | s'hi registra **tot** `alt` i `molt_alt` |
| `before`, `after` | `jsonb` | |
| `at` | `timestamptz` | |

**Immutable**, retenció **≥ 5 anys**, i **consultar-lo també queda registrat**. És el sistema de registre de l'RGPD — no hi ha una pantalla d'administració d'RGPD a part.

### `exportacions`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id` | `uuid` FK | |
| `kind` | `enum` | `inscripcions` \| `usuaris` \| `pagaments` \| `audit` |
| `filters` | `jsonb` | |
| `row_count` | `int` | |
| `storage_key` | `text` | |
| `at` | `timestamptz` | |

**Tota exportació queda registrada.** El llistat d'inscripcions s'exporta al preu unitari **de la data d'exportació** i **no calcula sessions facturables ni aplica descomptes ni excepcions** — això és Fase 1B.

### `processos` + `execucions_proces`
| Columna | Tipus | Notes |
|---|---|---|
| `code` | `text` UK | `promocio_cursos` \| `sincronitzacio_clickedu` \| `recordatoris_documents` \| `expiracio_activitats` |
| `schedule` | `text` | editable |
| `enabled` | `bool` | |
| `last_run_at`, `next_run_at` | `timestamptz` | |

`execucions_proces`: `proces_id`, `started_at`, `finished_at`, `state`, `summary` (`jsonb`), `error`.

> La promoció interna avança tots els alumnes +1 curs **només a la nostra BD**. ClickEdu segueix sent el `font de veritat` fins a la promoció oficial de setembre. Les correccions de repetidors es fan a mà en qualsevol moment.

---

## 12. ClickEdu

> **Només lectura.** Res del que es guarda aquí és font de veritat, i res s'hi escriu. Un compte de ClickEdu **no es pot convertir en local**.

### `clickedu_sincronitzacions`
| Columna | Tipus | Notes |
|---|---|---|
| `trigger` | `enum` | `login` \| `manual` |
| `started_at`, `finished_at` | `timestamptz` | |
| `state` | `enum` | `ok` \| `parcial` \| `fallida` |
| `students_seen`, `changes_applied` | `int` | |
| `errors` | `jsonb` | API caiguda → 3 reintents i avís a administració; dades incoherents → registre i avís, **mai sobreescriure** |

### `clickedu_cache`
| Columna | Tipus | Notes |
|---|---|---|
| `usuari_id` | `uuid` FK | |
| `payload` | `jsonb` | credencials **no**; alumnes, cursos, etapes, famílies, contacte, al·lèrgies |
| `fetched_at`, `expires_at` | `timestamptz` | |

Una sessió costa 1 `login` + 1 `credentials` + 1 `getInfo`, i després **5 crides per fill**. Sense cau, l'inici de sessió d'una família de tres fills són 18 crides. GNA ja ho cachejava (`clickedu_info`); aquí també, amb caducitat explícita i amb la consciència que **el cau conté dades personals** i té retenció pròpia.

---

## 13. Migració — capa d'aterratge i reconciliació

> **No es reutilitza l'esquema de GNA.** L'objectiu és que la transició sigui transparent per a l'usuari, no que les taules s'assemblin. Quatre bloquejos concrets, verificats contra el bolcat real: MyISAM **no té transaccions** i el canvi d'activitat ha de ser atòmic · GNA **no té columna de tenant** i `OrgId` + filtre global és no-negociable · `dia_1..7` × 7 grups = **49 columnes** en una taula filla · el nombre de columnes `titol_1..N` **és una dada** (viu a `SIS_activitats.titols_elements`), i un esquema el nombre de columnes del qual depèn d'una fila no es pot mantenir.
>
> El camí és: **importar GNA literalment a `legacy_*` → transformar cap al model net → conservar `legacy_font` + `legacy_codi` per reconciliar.** El que fa demostrable que no s'ha perdut res **no és** que els esquemes coincideixin, és el **mapatge camp a camp**.

### 13.1 `legacy_*` — còpia literal, sense transformar

Una taula per taula d'origen dins l'abast, amb totes les columnes com a `text`, **sense claus foranes, sense enums, sense obligatorietat**. L'única feina d'aquesta capa és que el bolcat entri sencer i es pugui consultar.

`legacy_inscripcions` · `legacy_inscripcions_historic` · `legacy_inscripcions_estiu` · `legacy_inscripcions_estiu_historic` · `legacy_w_activitats` · `legacy_w_activitats_idiomes` · `legacy_w_activitats_horaris` · `legacy_w_activitats_horaris_combinacions` · `legacy_w_activitats_horaris_estiu_nou` · `legacy_w_activitats_horaris_estiu_nou_blocs_preus` · `legacy_w_activitats_fotos` · `legacy_w_activitats_fotos_idiomes` · `legacy_w_activitats_docs` · `legacy_w_tipus_activitats(+_idiomes)` · `legacy_w_cursos(+_idiomes)` · `legacy_w_etapes(+_idiomes)` · `legacy_w_material(+_idiomes)` · `legacy_w_documentacio(+_idiomes)` · `legacy_w_blocs(+_idiomes)` · `legacy_w_horaris_convenir_taula` · `legacy_registre_estiu` · `legacy_fills_registre` · `legacy_antics_alumnes` · `legacy_SIS_activitats`

**Fora de l'abast, i s'ha de dir explícitament:** les ~150 taules `rt_*` són un mòdul d'hostaleria empaquetat, no aquest sistema.

**Mai s'importen** — còpies i esborranys que viuen dins la base de dades de producció amb noms indistingibles dels reals:
`fills_registre_copy` · `fills_registre_nous` · `inscripcions_copy` · `inscripcions_proves` · `inscripcions_estiu_old` · `inscripcions_estiu_backup_22052015_1347` · `registre_estiu_copy` · `registre_estiu_nous` · `w_activitats_copy` · `w_activitats_horaris_copia_22072016` · `w_activitats_horaris_combinacions_copia_22072016` · `w_activitats_horaris_estiu_nou_blocs_preus_backup_22052015_1347` · `w_horaris_convenir_taula_copy` · `w_horaris_convenir_taula2_copy_copy_copy`

### 13.2 `migracio_execucions`
| Columna | Tipus | Notes |
|---|---|---|
| `source_hash` | `text` | resum del bolcat d'origen — una execució identifica **quin** bolcat va llegir |
| `started_at`, `finished_at` | `timestamptz` | |
| `state` | `enum` | `en_curs` \| `ok` \| `amb_incidencies` \| `fallida` |
| `counts` | `jsonb` | llegides / creades / omeses / deduplicades per taula |

### 13.3 `migracio_files` — el mapa de files
Fa possible la reconciliació i fa la importació **repetible**.

| Columna | Tipus | Notes |
|---|---|---|
| `execucio_id` | `uuid` FK | |
| `legacy_font`, `legacy_codi` | `text` | UK `(legacy_font, legacy_codi)` |
| `target_table` | `text` | |
| `target_id` | `uuid` | |
| `action` | `enum` | `creada` \| `actualitzada` \| `omesa` \| `fusionada` |

### 13.4 `migracio_incidencies`
Amb **0 claus foranes** a l'origen, les files òrfenes són pràcticament segures: inscripcions que apunten a activitats esborrades, `codi_fill` sense fila a `fills_registre`, `codi_curs_bloc_horari` sense horari.

| Columna | Tipus | Notes |
|---|---|---|
| `execucio_id` | `uuid` FK | |
| `legacy_font`, `legacy_codi` | `text` | |
| `severity` | `enum` | `avis` \| `error` |
| `code` | `enum` | `referencia_penjada` \| `duplicat` \| `codificacio` \| `valor_invalid` \| `credencial_descartada` |
| `detail` | `jsonb` | |

### 13.5 Riscos ja identificats al bolcat real

| Risc | Dada | Què implica |
|---|---|---|
| Codificació | **146 taules `utf8` + 28 `latin1`**, i `inscripcions` és **`latin1`** | el text català (à è í ó ç ·) és exactament on apareix el *mojibake*. Cal detectar els valors ja doblement codificats **abans** de convertir |
| Volum no creïble | `registre_estiu` ~**201.000** files i `fills_registre` ~**112.000** per a una escola de ~1.438 alumnes | gairebé segur registres abandonats o brossa. Importar-ho tot contaminaria la plataforma nova i eixamplaria enormement la superfície RGPD. **Cal un recompte real de files a l'escola abans de decidir** |
| Clau no única | `codi` és únic **només dins de cada taula**, i l'històric són quatre taules | d'aquí les dues columnes `legacy_font` + `legacy_codi` |
| Verificador mort | `registre_antics_alumnes` **no existeix** com a taula | el primer dels tres verificadors d'autenticació de GNA no ha funcionat mai. Autentiquen només `registre_estiu` (extern) i ClickEdu (intern) |
| Dades sensibles més amples del que semblava | `inscripcions` porta `alergies`, `obs_alimentacio`, `obs_natacio`, `catsalut`, `dni_pare`, `dni_mare` | la decisió de retenció de salut i documents d'identitat s'ha de prendre **abans** d'importar, no després |

### 13.6 Transició

El sistema antic queda **en només lectura** durant el traspàs, la importació és repetible i amb còpia de seguretat, i cal conservar deliberadament la **lògica combinatòria i el control de solapament d'horaris** de GNA.

---

## 14. Superfícies de GNA que el model encara no cobreix

Presents a l'esquema real, absents de l'especificació de la Fase 1A. **Cada una necessita una decisió abans de tancar el model** — no s'han inventat taules per a elles.

| Taula de GNA | Què sembla | Decisió pendent |
|---|---|---|
| `a_avisos`, `a_avisos_idiomes`, `a_avisos_usuari` | avisos a usuaris — una superfície de notificació dins el portal | ¿migra, o el substitueix el correu? |
| `w_instruments` (+ `_idiomes`, `_docs`, `_fotos`, `SIS_instruments`) | un catàleg sencer d'**instruments musicals** | ¿és una activitat més, o un domini propi? |
| `inscripcions_cupos`, `w_activitats_horaris_cupos` | variants de quota / llista d'espera; `inscripcions_cupos` és gairebé un clon d'`inscripcions` | la llista d'espera és Fase 1B: ¿s'importa l'històric? |
| `accio_inscripcions` | registre d'accions sobre inscripcions; **conté credencials** i `instruments` (`longblob`) | ¿té valor històric, o es descarta sencer? |
| `w_activitats_inscripcions` | taula d'unió activitat ↔ inscripció | ¿redundant amb `inscripcions.codi_activitat`? |
| `texts_confirmacio_inscripcio` | text del correu de confirmació | probablement plega cap a `plantilles_email` |
| `cursos_pi`, `cursos_pi_estiu` | propòsit no clar | |
| `img_activitats(_privat/_public)`, `pdfs_activitats` | mitjans, amb separació públic/privat | ¿la separació té significat funcional? |
| `w_seccions_generals*`, `w_texts_generals*`, `SIS_MENU_*`, `SIS_banners`, `SIS_xarxes_socials` | maquinària CMS de l'època Joomla | gairebé segur fora d'abast — **confirmar abans d'excloure** |

---

## 15. Decisions obertes que canvien taules

| # | Decisió | A qui toca | Què depèn |
|---|---|---|---|
| 1 | **Fidelitat de l'històric**: una inscripció del 2019 s'ha de tornar a dibuixar **exactament** com la va veure la família, o només ha de ser **llegible**? | client + arquitectura | Si ha de ser exacta, les columnes desnormalitzades de GNA (`nom_pare`, `dni_pare`, `nom_fill`, adreça…) viatgen cap aquí i el model les arrossega per sempre. Si només llegible, es normalitza i les files velles es resolen a través de les entitats d'avui. **Aquesta és la decisió que dóna forma a mitja base de dades** |
| 2 | **Camps obligatoris a crear una activitat** | client | GNA no en té cap. Veure `activitats` |
| 3 | **Recompte real de files** de `registre_estiu` i `fills_registre` | escola | Decideix si s'importa tot, una part o res |
| 4 | **Retenció de salut i documents d'identitat** | client + legal | `alumne_salut`, `alumnes.health_card_number`, `usuaris.dni` |
| 5 | **Nomenclatura**: taules en català + columnes en anglès, o unificar | Paras / Jaume | Tot el model. Cal decidir-ho abans de la primera migració EF |
| 6 | **Definició dels tres estats d'inscripció** que l'especificació no defineix | client | `inscripcions.state`, `inscripcio_estats`, plantilles de correu |
| 7 | Les superfícies del §14 | client | Fins a 5 dominis nous |

---

## 16. Mapatge camp a camp — GNA → model nou

> **Aquesta taula és l'autoritat.** Els diagrames «abans i després» d'[`index.html`](index.html#abans) en són la
> versió visual i resumida; el que fa **demostrable** que la migració no perd res és aquest mapatge, no que els
> esquemes s'assemblin. Tot el que hi ha a la columna GNA està verificat contra el bolcat real (estructura només).

### 16.1 Activitats i text per idioma

| GNA | Model nou |
|---|---|
| `w_activitats.codi` | `activitats.legacy_codi` (+ `legacy_font`) |
| `w_activitats.codi_seccio` 1 / 2 | `activitats.seccio` = `extraescolars` / `altres` |
| `w_activitats.codi_categoria` | `activitats.tipus_activitat_id` |
| `w_activitats.activitat_per_interns_o_externs` 0 / 1 / 2 | `activitats.audience` = `tots` / `interns` / `externs` |
| `w_activitats.data_1..4`, `hora_1..4` | `activitats.date_from` / `date_to` + `horari_dies` |
| `w_activitats.material` (cadena separada per `#`) | `activitat_materials` (files) |
| `w_activitats.documents` (cadena separada per `#`) | `activitat_documentacions` (files) |
| `w_activitats.actiu` / `validada` / `finalitzada` / `llistes_eliminades` | `activitats.state` + `activitats.active` |
| `w_activitats.cursos` | **no es migra** — columna òrfena, cap `Set_*` hi escriu |
| `w_activitats_idiomes.titol_1` | `activitat_traduccions.name` |
| `w_activitats_idiomes.titol_2` (*text peu foto*) | `activitat_imatge_traduccions.caption` |
| `w_activitats_idiomes.text_1` | `activitat_traduccions.description` |
| `w_activitats_idiomes.codi_idioma` | `activitat_traduccions.locale` |
| `SIS_activitats.titols_elements` / `textos_elements` | **desapareix** — el nombre de camps deixa de ser una dada |
| `w_activitats_fotos.nom_foto` / `principal` / `ordre` | `activitat_imatges.storage_key` / `is_cover` / `ordre` |
| `w_activitats_fotos_idiomes.titol_1` | `activitat_imatge_traduccions.caption` |
| `w_tipus_activitats.emails_responsables` | `tipus_activitat_responsables.email` (files) |

### 16.2 Horaris, dies, places i preus

| GNA | Model nou |
|---|---|
| `w_activitats_horaris.dia_N` | `horari_dies.weekday` — **una fila per dia** |
| `hora_inici_N` / `hora_fi_N` | `horari_dies.time_from` / `time_to` |
| `cupo_N` / `places_N` / `places_minimes_N` | `horari_dies.cupo` / `places` / `places_minimes` |
| `preu_N` | `horari_dies.price` |
| `preuc1..3` | `horari_combinacions.price` |
| `w_activitats_horaris_combinacions.dia_1..7` | `horari_combinacio_dies.weekday` |
| `horari_convenir` | `horaris.kind = a_convenir` |
| `w_horaris_convenir_taula` | `inscripcio_preferencies_horari` |
| `places_combinades` / `places_combinades_cursos` | `activitats.places_shared` + `horaris.curs_id` |
| `w_activitats_horaris_estiu_nou` | les mateixes `horaris` (amb `activitats.seccio = altres`) |
| `..._estiu_nou.activitat_sense_horari` | `horaris.kind = a_convenir` amb zero `horari_dies` |
| `..._blocs_preus.codi_bloc` / `places` / `cupos` | `horari_blocs.bloc_id` / `places` / `cupo` |
| `..._blocs_preus.hora_inici_1..3` / `preu_1..3` | `horari_bloc_franges.time_from` / `price`, `ordre` 1–3 |
| `w_blocs` | `blocs` (lligats a `temporada_id`) |
| `w_activitats_horaris_combiancions` (`DELETE` mal escrit) | **no es migra** — codi mort, mai va esborrar res |

### 16.3 Inscripcions

| GNA | Model nou |
|---|---|
| `inscripcions.codi` | `inscripcions.legacy_codi` + `legacy_font` (únic només per taula) |
| `eliminada` / `data_eliminada` | `state = baixa` + `baixa_effective_at` |
| `comentaris` (motiu de la família) | `baixa_reason` / `comments` |
| `user` / `pass` | **es descarten** |
| `dni_pare` / `nom_pare` / `cognoms_pare` / `email_pare` / `telefon_pare` | `usuari_id` → `usuaris` |
| tot el joc `_mare` | `familia_usuaris.relation = mare` |
| `adresa` / `codi_postal` | `usuaris.address` / `postal_code` |
| `codi_fill` / `nom_fill` / `cognom1_fill` / `cognom2_fill` / `email_fill` | `alumne_id` → `alumnes` |
| `catsalut` | `alumnes.health_card_number` |
| `alergies` / `obs_alimentacio` / `obs_natacio` | `alumne_salut` |
| `temporada` (text calculat al PHP) | `temporada_id` → `temporades` |
| `codi_activitat` | `activitat_id` |
| `codi_curs_bloc_horari` / `codi_curs` / `codi_bloc` / `codi_combinacio` | `horari_id` / `horari_combinacio_id` / `inscripcio_blocs` |
| `curs` / `lletra` / `cursos` | resolts des d'`alumnes.curs_id` |
| `horari` / `dies` / `hores` (cadenes) | `inscripcio_dies` |
| `horari_convenir1..3` | `inscripcio_preferencies_horari.ordre` |
| `material` (cadena d'ids) | `inscripcio_materials` |
| `dte` / `total` / `total_dte` | `discount_pct` / `price_base` / `total` |
| `confirmar_paga_senyal` | `pagaments.state` |
| `ip` / `data_modificacio` / `modificada` | `ip` / `updated_at` |
| `codi_idioma` | `usuaris.locale` |
| `tipus` / `professor` | **decisió pendent** — cap ús trobat al codi extret |
| `inscripcions_historic`, `inscripcions_estiu_historic` | la mateixa `inscripcions` + `inscripcio_estats` |
| `inscripcions_estiu.string_horaris_blocs` / `string_horari` | `inscripcio_blocs` / `inscripcio_dies` |

### 16.4 Usuaris i credencials

| GNA | Model nou |
|---|---|
| `registre_estiu` (verificador local) | `usuaris` amb `kind = extern`, `auth_source = local` |
| ClickEdu (verificador remot) | `usuaris` amb `kind = intern`, `auth_source = clickedu` |
| `registre_antics_alumnes` | **no existeix** al bolcat — verificador mort, no es migra |
| `registre_estiu.password` (en clar) | **es descarta** — Cognito autentica |
| `registre_estiu.usuari` | `usuaris.clickedu_username` o `email` segons l'origen |
| `registre_estiu.dni` / `nom` / `cognom1` / `cognom2` / `email` / `telefon*` / `mobil` | les columnes homònimes a `usuaris` |
| `registre_estiu.adresa` / `cp` / `poblacio` | `usuaris.address` / `postal_code` / `city` |
| `registre_estiu.fills` | derivat de `families` → `alumnes` |
| `registre_estiu.major_edat` / `estat_civil` / `conegut` / `professio` / `sexe` / `pais` | **decisió pendent** — cap ús a la Fase 1A |
| `fills_registre.codi_pare` | `alumnes.familia_id` |
| `fills_registre.nom` / `cognom1` / `cognom2` / `data_naixement` / `curs` | `alumnes.*` |
| `fills_registre.malalties` / `obs_alimentacio` / `obs_natacio` | `alumne_salut` |
| `antics_alumnes.nif` | `usuaris.dni` (**retenció a decidir**) |
| `antics_alumnes.promocio` | `cursos` de l'etapa `Altres`, o metadada del compte |
| `clickedu_info.token` / `credentials` | **es descarten** |
| `clickedu_info.info` / `students_data` / `stages_data` / `classes_data` | `clickedu_cache.payload` amb caducitat |
| `bet_users.*` | **no es migra** — usuaris de Joomla |
| `accio_inscripcions.password` | **es descarta** |

### 16.5 Catàlegs

| GNA | Model nou |
|---|---|
| `w_<cat>` + `w_<cat>_idiomes` + `SIS_<cat>` | `<cat>s` + `<cat>_traduccions` |
| `w_material.preu` / `peu_minim` / `peu_maxim` | `materials.price` / `min_qty` / `max_qty` |
| `w_documentacio` | `documentacions` amb `kind` = `requerida` / `lliurament` |
| `w_cursos` / `w_etapes` | `cursos` (+ `next_curs_id`) / `etapes` |
| `w_horaris_defecte` / `w_horaris_convenir` | plantilles d'`horaris` |
| `w_tipus_activitats.codi_seccio` | `tipus_activitats.seccio` (segueix fitat) |
| `SIS_*.titols_elements` i companyia | **desapareix** — cap configuració decideix una estructura |
| `textes.text2_$idm` (modal de condicions) | `textos_legals` + `text_legal_traduccions` |
| `w_instruments` i tot el seu joc | **decisió pendent** (§14) |
| — | `consentiments` + `consentiment_atorgaments` (**nou**) |
| — | `motius_absencia` (**nou**) |
| — | `rols` / `permisos` / `rol_permisos` / `usuari_rols` (**nou**) |
| — | `configuracions` (**nou**) |
| — | `audit_logs` / `exportacions` (**nou**) |
| — | `pagaments` i companyia (**nou** — a GNA el cobrament és fora del sistema) |

---

## 17. Traçabilitat

| Domini | Casos d'ús de la Fase 1A | Origen a GNA |
|---|---|---|
| Identitat i autorització | UC-1A-01, 02, 04 · UC-1A-NEW-02 | `registre_estiu`, `bet_users`, ClickEdu |
| Famílies i alumnes | UC-1A-03, 06 | `fills_registre`, `registre_estiu`, `antics_alumnes` |
| Estructura acadèmica | UC-1A-05, 06 | `w_cursos`, `w_etapes`, `getTemporadaActual()` |
| Catàleg | UC-1A-07, 08 | `w_activitats(+_idiomes)`, `w_tipus_activitats`, `w_material`, `w_documentacio` |
| Horaris i preus | UC-1A-07, 13 | `w_activitats_horaris`, `_combinacions`, `_estiu_nou`, `_blocs_preus`, `w_horaris_convenir_taula`, `w_blocs` |
| Inscripcions | UC-1A-10, 11, 12, 14, 15, 16, 17 · UC-1A-NEW-04, NEW-07 | `inscripcions` × 4 taules |
| Absències | UC-1A-NEW-05 | — (nou) |
| Pagaments | UC-1A-18 | — (nou; a GNA el cobrament és fora del sistema) |
| Comunicacions | UC-1A-09 · §4.8 | `texts_confirmacio_inscripcio` |
| Configuració i privacitat | UC-1A-NEW-01, NEW-06 | `SIS_activitats`, `textes` |
| Auditoria | UC-1A-NEW-03 | — (nou) |
| Migració | §4.8.1 | tot l'anterior |
