# BD e Endpoints — Todas as variáveis Pluralo organizadas

Objetivo: **guardar todas as variáveis Pluralo numa BD nossa**, de forma organizada, e expor **endpoints únicos** que leem da BD.

**Referência completa de valores, extras, itinerários, IVA, grupos etários:** [REFERENCIA_VALORES_EXTRAS_ITINERARIOS.md](REFERENCIA_VALORES_EXTRAS_ITINERARIOS.md).

---

## Codificação: português e acentos (UTF-8)

- **Documentação:** Todos os ficheiros `.md` estão em **UTF-8**. Abrir/guardar com codificação UTF-8 para ver corretamente caracteres portugueses (ç, ã, ê, é, á, etc.).
- **Base de dados:** Tabelas e ligações usam **utf8mb4** e **utf8mb4_unicode_ci** (suporte completo a português e emojis). A configuração em `config/database.php` define `charset=utf8mb4` no DSN e executa `SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci` na ligação.
- **Import:** O script `import_pluralo_to_bd.php` usa a mesma ligação; os textos (descrições, nomes, notas) são gravados em UTF-8.

---

## 1. Esquema da BD (tabelas nossas)

Schema sugerido: **gocore** (ou um schema dedicado, ex.: `exp_data`). Prefixo das tabelas: **`exp_pluralo_`**.

### 1.1 `exp_pluralo_suppliers`

Uma linha por supplier. Colunas para **todas** as variáveis summary/detail + JSON bruto.

| Coluna | Tipo | Origem Pluralo |
|--------|------|----------------|
| id | INT PK | summary.id / detail.id |
| brand | VARCHAR(255) | summary.brand |
| company_name | VARCHAR(255) | summary.companyName |
| company_email | VARCHAR(255) | summary.companyEmail |
| company_phone | VARCHAR(50) | summary.companyPhone |
| company_address | VARCHAR(500) | summary.companyAddress |
| postal_code | VARCHAR(20) | summary.postalCode |
| location | VARCHAR(255) | summary.location |
| reservation_email | VARCHAR(500) | summary.reservationEmail |
| reservation_phone | VARCHAR(100) | summary.reservationPhone |
| website | VARCHAR(500) | summary.website |
| image_logo | VARCHAR(500) | summary.imageLogo |
| country_id | INT | summary.country.id |
| country_code2 | CHAR(2) | summary.country.code2 |
| country_name | VARCHAR(100) | summary.country.name |
| timezone | VARCHAR(100) | summary.timezone |
| currency | VARCHAR(10) | summary.currency |
| biography | TEXT | summary.biography |
| invitation_status | INT | summary.invitationStatus |
| commission_average | DECIMAL(10,2) | summary.commissionAverage |
| connected_from_agent | TINYINT(1) | summary.connectedFromAgent |
| protocol_status_description | VARCHAR(255) | detail.protocolStatusDescription |
| category_list_json | JSON | detail.categoryList |
| payload_json | LONGTEXT | Objeto completo summary+detail |
| fetched_at | DATETIME | fetchedAt do latest.json |
| updated_at | TIMESTAMP | |

### 1.2 `exp_pluralo_products`

Uma linha por produto (experiência). Colunas principais + JSON bruto.

| Coluna | Tipo | Origem Pluralo |
|--------|------|----------------|
| id | INT PK | product.id |
| supplier_id | INT | FK → exp_pluralo_suppliers.id |
| code | VARCHAR(50) | product.code |
| name | VARCHAR(255) | product.name |
| active | TINYINT(1) | product.active |
| location | VARCHAR(255) | product.location |
| location_completed_name | VARCHAR(255) | product.locationCompleted.name |
| description_brief | TEXT | product.descriptionBrief |
| description | TEXT | product.description |
| description_more | TEXT | product.descriptionMore |
| internal_note | TEXT | product.internalNote |
| image_logo | VARCHAR(500) | product.imageLogo |
| capacity | INT | product.capacity |
| duration | VARCHAR(50) | product.duration ou product.routes[0].duration |
| min_pax | INT | product.minPax |
| max_pax | INT | product.maxPax |
| currency | VARCHAR(10) | product.currency |
| payment | INT | product.payment |
| payment_value | DECIMAL(10,2) | product.paymentValue |
| deposit | INT | product.deposit |
| deposit_description | VARCHAR(255) | product.depositDescription |
| booking_question | TEXT | product.bookingQuestion |
| booking_question_active | TINYINT(1) | product.bookingQuestionIsActive |
| booking_question_mandatory | TINYINT(1) | product.bookingQuestionIsMandatory |
| has_manual_confirmation | TINYINT(1) | product.hasManualConfirmation |
| hide_on_calendar | TINYINT(1) | product.hideOnCalendar |
| show_on_calendar_when_had_bookings | TINYINT(1) | product.showOnCalendarWhenHadBookings |
| availability_type | INT | product.availabilityType |
| external_booking_system_code | VARCHAR(100) | product.externalBookingSystemCode |
| latitude | DECIMAL(10,6) | product.coordinates.latitude |
| longitude | DECIMAL(10,6) | product.coordinates.longitude |
| category_list_json | JSON | product.categoryList |
| extras_json | JSON | product.extras |
| groups_json | JSON | product.groups (schedules + productGenderPrices) |
| routes_json | JSON | product.routes (itinerários) |
| update_flags_json | JSON | product.updateFlags (pickups, minMaxRules, etc.) |
| payload_json | LONGTEXT | Objeto completo product |
| updated_at | TIMESTAMP | |

### 1.3 `exp_pluralo_product_categories`

Categorias do produto (normalizado a partir de categoryList).

| Coluna | Tipo |
|--------|------|
| id | INT PK AUTO_INCREMENT |
| product_id | INT (FK) |
| category_id | INT |
| name | VARCHAR(255) |
| description | VARCHAR(255) |

### 1.4 `exp_pluralo_product_schedules`

Horários extraídos de product.groups[].schedules (para calendário e listagens).

| Coluna | Tipo |
|--------|------|
| id | INT PK AUTO_INCREMENT |
| product_id | INT (FK) |
| group_index | INT |
| date_from | DATE |
| date_to | DATE |
| start_time | TIME |
| duration | VARCHAR(20) |
| repeats_at_week_days | JSON (array 0-6) |

### 1.5 `exp_pluralo_product_prices`

Preços por grupo e audiência (adulto/criança/bebé) de product.groups[].productGenderPrices (ou product.productGenderPrices).

| Coluna | Tipo | Origem Pluralo |
|--------|------|----------------|
| id | INT PK AUTO_INCREMENT | |
| product_id | INT (FK) | |
| group_index | INT | índice do group |
| value | DECIMAL(10,2) | productGenderPrices[].value |
| currency | VARCHAR(10) | productGenderPrices[].currency |
| grossprice | DECIMAL(10,2) | productGenderPrices[].grossprice |
| netprice | DECIMAL(10,2) | productGenderPrices[].netprice |
| audience_type | INT | productGenderPrices[].audienceType |
| audience_description | VARCHAR(255) | productGenderPrices[].audienceDescription (Adulto, Criança, Bebé) |
| gender_description | VARCHAR(255) | productGenderPrices[].genderDescription (Per Person, etc.) |
| price_description | VARCHAR(255) | grossPriceDescription / netPriceDescription |
| vat_type | INT | productGenderPrices[].vatType |
| price_include_taxes | TINYINT(1) | productGenderPrices[].priceIncludeTaxes |

### 1.6 `exp_pluralo_product_extras`

Uma linha por extra do produto (opções pagas: nome, preço, IVA, obrigatório). Ver [REFERENCIA_VALORES_EXTRAS_ITINERARIOS.md](REFERENCIA_VALORES_EXTRAS_ITINERARIOS.md).

| Coluna | Tipo | Origem Pluralo |
|--------|------|----------------|
| id | INT PK AUTO_INCREMENT | |
| product_id | INT (FK) | |
| extra_id | INT | extras[].id |
| position | INT | índice no array |
| is_mandatory | TINYINT(1) | extras[].isMandatory |
| quantity | INT | extras[].quantity |
| capacity_unlimited | TINYINT(1) | extras[].capacityUnlimited |
| item_id | INT | extras[].item.id |
| item_code | VARCHAR(50) | extras[].item.code |
| item_name | VARCHAR(255) | extras[].item.name |
| item_billing_name | VARCHAR(255) | extras[].item.billingName |
| item_description | TEXT | extras[].item.description |
| item_price_total | DECIMAL(10,2) | extras[].item.priceTotal |
| item_vat_type | INT | extras[].item.vatType |
| item_price_include_taxes | TINYINT(1) | extras[].item.priceIncludeTaxes |
| item_netprice | DECIMAL(10,2) | extras[].item.netprice |
| item_grossprice | DECIMAL(10,2) | extras[].item.grossprice |
| selected_price_value | DECIMAL(10,2) | selectedPrice.value |
| selected_price_netprice | DECIMAL(10,2) | selectedPrice.netprice |
| selected_price_grossprice | DECIMAL(10,2) | selectedPrice.grossprice |
| prices_json | JSON | extras[].item.prices[] |
| payload_json | JSON | Objeto completo do extra |

---

## 2. Organização dos endpoints

Todos os endpoints que servem dados Pluralo passam a poder ler **da BD** (com fallback opcional para JSON).

### 2.1 Endpoints BD (novos) — base path `/api/bd/`

| Método | Endpoint | Descrição |
|--------|----------|-----------|
| GET | `/api/bd/suppliers` | Lista suppliers (todos os campos da tabela) |
| GET | `/api/bd/suppliers?limit=N` | Idem com limite |
| GET | `/api/bd/suppliers/{id}` | Um supplier por id |
| GET | `/api/bd/experiences` | Lista experiências (produtos) |
| GET | `/api/bd/experiences?supplierId=N` | Experiências de um supplier |
| GET | `/api/bd/experiences/{id}` | Uma experiência (com categories, extras, groups em JSON) |
| GET | `/api/bd/calendar` | Calendário: ?supplierId=&productId=&date=&dateEnd= |
| POST | `/api/bd/import` | Disparar importação latest.json → BD (opcional, pode ser só CLI) |

### 2.2 Endpoints atuais (JSON) — manter como fallback ou alternativos

| Endpoint | Descrição |
|----------|-----------|
| `/api/products_for_list.php` | Lista completa a partir de latest.json |
| `/api/suppliers_list.php` | Lista slim (Grupo 1) a partir de JSON |
| `/api/experiences_list.php` | Lista experiências (Grupo 3) a partir de JSON |
| `/api/calendar_extract.php` | Calendário a partir de latest.json |
| `/api/sync_latest_json.php` | Sincronizar latest.json da 11_experiences |

Estratégia: as **páginas** chamam primeiro `/api/bd/*`; se a BD não estiver configurada ou devolver erro, fallback para os endpoints que leem do JSON. A **Lista de experiências** (`views/experiences_list.html`) já usa este fluxo: tenta `api/bd/experiences.php` e, em caso de 503 ou sem dados, usa `api/experiences_list.php`.

---

## 3. Fluxo de dados

```
latest.json (ou API Pluralo)
        ↓
  [Import script]
        ↓
  exp_pluralo_suppliers
  exp_pluralo_products
  exp_pluralo_product_categories
  exp_pluralo_product_schedules
  exp_pluralo_product_prices
        ↓
  Endpoints /api/bd/*
        ↓
  Frontend (listas, detalhe, calendário)
```

---

## 4. Ficheiros criados (experiences)

| Ficheiro | Descrição |
|----------|-----------|
| `docs/BD_ESQUEMA_E_ENDPOINTS.md` | Este documento |
| `scripts/etl/schema_exp_pluralo.sql` | CREATE TABLE das tabelas (todas as variáveis com coluna de destino) |
| `scripts/etl/schema_exp_pluralo_alter_v3.sql` | ALTER: colunas para variáveis que faltavam (suppliers country_*, products color/widget/changes, categories status/settings, extras flags) |
| `docs/MAPEAMENTO_VARIAVEL_COLUNA_BD.md` | Mapeamento completo variável API → coluna BD |
| `config/database.php` | Configuração PDO (exp_get_pdo) |
| `config/db.env.example` | Exemplo de credenciais (copiar para db.env) |
| `scripts/etl/import_pluralo_to_bd.php` | Import latest.json → BD |
| `api/bd/suppliers.php` | GET suppliers da BD |
| `api/bd/experiences.php` | GET experiences da BD |
| `api/bd/calendar.php` | GET calendar da BD |
| `views/experience_detail.html` | Página de detalhe (descrição, extras, preços por audiência, itinerários); usa `api/bd/experiences.php?id=` |

---

## 5. Aplicar o schema (tabelas e colunas)

Para evitar erros 500/503 nas páginas e APIs BD, aplicar o schema completo. Ver **[APLICAR_SCHEMA_BD.md](APLICAR_SCHEMA_BD.md)** para instruções passo a passo (base nova vs. base já existente).

## 6. Configuração BD

Em `config/database.php` (ou `.env`) definir:

- `DB_HOST`, `DB_NAME`, `DB_USER`, `DB_PASSWORD`
- Opcional: `DB_CHARSET=utf8mb4`

Se a app experiences usar a mesma BD que a 11_experiences (gocore), pode reutilizar as credenciais já existentes no projeto pai. Após aplicar o schema e importar, as páginas **Lista de suppliers** e **Lista de experiências** mostram "Fonte: BD"; em caso de BD indisponível, fazem fallback para JSON (latest.json).
