# Integração com o portal WINKER (condomínio do apartamento)

Integra o portal **Winker** (que administra o condomínio) ao Nexus, por **engenharia
reversa** da API do app oficial — a conta do MORADOR lendo os próprios dados, mesmo
espírito do InControl/CELESC/SolarMan. Confirmado por HAR (captura do app rodando).

**O que dá para integrar** (tudo visto respondendo no HAR): **chaves** (portas — inclusive
ABRIR), **câmeras**, **reservas**, **boletos** (com linha digitável), **documentos**,
**históricos** de acesso, **visitantes**, **mural/mensagens**, **certidões** e **prestação de
contas**.

---

## 1. Como ligar (no `.env`) e validar

Preencha no `api/.env` (só o login do morador; a integração é inerte sem `WINKER_ENABLED`):

```
WINKER_ENABLED=true
WINKER_USERNAME=seu-email@exemplo.com
WINKER_PASSWORD=sua-senha-do-winker
WINKER_ID_PORTAL=13949        # opcional: fixa o condomínio (veja no teste abaixo)
# WINKER_KEY=...              # só se o login falhar sem ela (campo "key" do HAR)
```

Valide no servidor (roda mesmo com `WINKER_ENABLED=false`):

```
php api/scripts/winker_test.php
```

✅ **Deu certo se:** aparecem os **portais** (condomínios), as **chaves/portas**, os
**boletos** e as **reservas**. Anote o `id_portal` do seu condomínio e ponha em
`WINKER_ID_PORTAL`. Para explorar um caminho específico:

```
php api/scripts/winker_test.php GET /v1/camera "id_portal=13949&active=1"
```

---

## 2. Modelo da API (o que todo endpoint compartilha)

- **Base:** `https://api.winker.com.br`
- **Login:** `POST /v1/auth/login` com corpo JSON `{username, password, key, version, device}`.
  A resposta é um **envelope em base64(JSON)** e o token de verdade é o **JWT** no campo
  `token` (há também `session`, `id_user`, `name`, `photo`…).
- **Autenticação das demais chamadas:** header **`Authorization: <JWT>`** — ⚠ **SEM** o
  prefixo "Bearer" (o JWT vai cru).
- **Headers obrigatórios** em toda chamada: `device` (o mesmo JSON do login), `app-version`
  (ex.: `2.2.117`), `X-Requested-With: br.com.winker`, `has-category:` (vazio).
- **⚠ TODO corpo de resposta é base64(JSON).** Decodifique base64 → depois JSON. (`204 No
  Content` = lista vazia.) No código isso está em `WinkerClient::decodeBody()`.
- **Multi-portal:** cada condomínio é um **`id_portal`**, passado por `?id_portal=` na maioria
  das rotas (é o padrão multi-UC da CELESC). `POST /v1/me/change-portal {id_portal}` troca o
  portal ativo da sessão; `GET /v1/me/portals` lista os do usuário.
- **Reautenticação:** JWT expira → em `401/403` o cliente refaz o login uma vez.

Tudo isso vive em **`App\Winker\WinkerClient`** (cURL puro, JWT em cache de arquivo,
best-effort com `IntegrationLog`). Config em `api/config/winker.php`. Módulo `winker` no
kill-switch (`/modulos`).

---

## 3. Endpoints por feature (confirmados no HAR)

### Perfil / portais
- `GET /v1/me/portals?page` — condomínios do usuário (`id_portal`, `name`, endereço).
- `POST /v1/me/change-portal` `{id_portal}` — troca o portal ativo.
- `GET /v1/me?id_portal&id_user` — dados do morador no portal.
- `GET /v1/me/preference?group` — preferências.

### Chaves (portas / controle de acesso)
- `GET /v1/access-control/user/devices?id_portal` — lista as portas do morador:
  `{id_server, name_server, id_device, name_device, id_zone, name_zone, state, event}`.
- **`POST /v1/access-control/user/device/open`** `{device:{…}}` — **ABRE a porta** (reenvia o
  objeto `device` da lista). Resposta traz `event: unlocked`.
- `GET /v1/access-control/user/get-facial-access?id_portal&id_user` — situação do facial.
- `GET /v1/access-control/access-key/preferences/{id}` — preferências da chave.
- `GET /v1/portal/{id}/camera-by-access-key/{key}` — câmera vinculada a uma porta.

### Câmeras
- `GET /v1/camera?active&id_portal` — `{id_camera, name, status, url, authorization (Bearer!),
  transport, unique_id}`. O **stream** é servido por um servidor à parte
  (ex.: `http://189.90.58.186:18085/api/servers/{GUID}/cameras/...`), que entrega
  **snapshots JPEG** (polling), usando o `authorization` próprio de cada câmera.

### Reservas
- `GET /v1/booking?id_portal&with_photo` — recursos reserváveis (`id_resource`, `name`,
  `reservation_type`, `duration_schedule_per_day`).
- `GET /v1/booking/mybookings?id_portal&show_overdue` — minhas reservas.
- `GET /v1/booking/{id}/busyDays?date&visualization_type&with_billing` — dias/horários ocupados.
- `GET /v1/booking/units?id_portal&id_resource` · `GET /v1/booking/getBookingReservationFee`.

### Boletos
- `GET /v1/billing_unit/units?id_portal` — unidades de cobrança.
- `GET /v1/billing_unit?id_portal&id_unit[&history&start_date]` — boletos: `{digitable_line
  (linha digitável), reference, due_date, payment_date, value, situation (paid/open),
  can_download_paid_billing}`.
- `GET /v1/billing_unit/{id}/file?id_portal&id_unit&reference` → `{url (PDF tokenizado em
  app.winker.com.br), type, original_name}` — baixa o PDF a partir dessa `url`.

### Documentos
- `GET /v1/document-type?id_portal[&active]` — categorias.
- `GET /v1/document?id_portal&page[&id_document_type]` — lista.
- `GET /v1/document/{id}/file` → `{url, …}` — baixa o arquivo.

### Históricos e visitantes
- **`POST /v1/access-control/access-report?id_portal`** — histórico de **acessos**. Corpo é um
  envelope de "event": `{event:"Winker\\Vendor\\Api\\AccessControl\\Report\\Access",
  replace_url_params:{id_portal}, body:{type:"access", date_start, date_end}}` → `[{time,
  zone_name, person_name, direction, event_type}]`.
- `GET /v1/access-control/access-report/get-units?id_portal` — unidades p/ o relatório.
- `GET /v1/gatekeeper?id_portal&visitant_type&with_visits` — **visitantes** previstos
  (`visitor`, `prevision_start/end`, `id_unit`). `GET /v1/gatekeeper/list` — variação.

### Mural / mensagens
- `GET /v1/message?box&id_portal&situation&page` · `GET /v1/message/boxes` ·
  `GET /v1/message/fixed/get-all?box&id_portal` · `GET /v1/message/moderate`.

### Prestação de contas (financeiro do condomínio)
- `GET /v1/portal/{id}/general_ledger_transaction/realize_by_month?start_date&end_date`
- `.../groups_realize_by_month` · `.../financial_account_by_month` · `.../list_transactions`
  `?id_group&id_divisions&month&year&type&page` · `GET /v1/portal/{id}/budget/comment?month&year`.

### Certidões / outros
- `GET /v1/certificate?id_portal` · `GET /v1/certificate/units` · `POST /v1/certificate`.
- `GET /v1/portal/{id}/about` · `GET /v1/users/active?id_portal&filter` ·
  `POST /v1/config?id_portal` · `PUT /v1/me/device/token` (push).

---

## 4. Escrita (confirmada no 2º HAR — `winker2.har`)

Já implementadas com o corpo fiel ao app:

- **Reserva:** `POST /v1/booking/book` (taxa de `getBookingReservationFee`, unidade de
  `get-units`) e `DELETE /v1/booking/{id}/cancel`.
- **Visitante:** `POST /v1/gatekeeper` (cadastra), `POST /v1/gatekeeper/list` (gera o **texto +
  link de convite do WhatsApp**) e `DELETE /v1/gatekeeper/{id}`.
- **Certidão:** `POST /v1/certificate {id_unit}` (emite).
- **Datas -03:** o app manda a data local 00:00 como `…T03:00:00.000Z`. Reproduzido no código.

## 5. O que resta (recurso de síndico / sem HAR)

- `POST /v1/access-control/occurrence-report` retorna **500 "Você não possui acesso"** — é
  recurso de síndico (o nosso `access-report`, que funciona, é outro endpoint).
- Detalhe do recado do mural, download da certidão emitida e as transações item-a-item da
  prestação de contas (`list_transactions`, com anexos) ficam para um HAR que os capture.

Para fechar, capture um HAR fazendo a ação no app e me mande — confirmo o corpo e implemento
(mesma disciplina do InControl/CELESC).
