# Vimine (navegador)

> Referencia de la clase Vimine de @vimine/sdk — inicio de sesión, sesión, llamadas a la API, almacenamiento.

URL: https://developers.vimine.network/es/sdk/browser/

```js
import { Vimine } from '@vimine/sdk';
const vimine = new Vimine(options);
```

`Vimine` gestiona **Iniciar sesión con Vimine** (OAuth 2.1 + PKCE), la sesión del socio y las llamadas a la API de
socios. Una instancia por cliente; créala una vez y reutilízala.

## Opciones

| Opción | Tipo | Por defecto | |
|---|---|---|---|
| `clientId` | `string` | — | tu `vmc_…` (obligatorio) |
| `redirectUri` | `string` | — | la página que llama a `handleRedirect()`, registrada para el cliente (obligatorio) |
| `scopes` | `Scope[]` | `['profile:read', 'membership:read']` | permisos que solicita `signIn()` |
| `storage` | `'session' \| 'local' \| 'memory' \| TokenStorage` | `'session'` | dónde viven la sesión y el inicio de sesión en curso |
| `storageKey` | `string` | `vimine:<clientId>:` | prefijo de las claves en el almacenamiento |
| `locale` | `Locale` | `'it'` | idioma de las páginas de inicio de sesión y de consentimiento |
| `apiUrl` | `string` | `https://api.vimine.network` | origen de la API |
| `appUrl` | `string` | `https://app.vimine.network` | origen de las páginas de inicio de sesión |
| `fetch` | `typeof fetch` | `globalThis.fetch` | para tests y polyfills |
| `navigate` | `(url) => void` | `top.location.assign` | cómo salir de la página |

El constructor lanza un `VimineError` (`invalid_options`) sin `clientId` o `redirectUri`.

## Inicio de sesión

### `signIn(options?)`

Envía la **ventana principal** a la página de inicio de sesión y de consentimiento. Llámalo desde un gesto del
usuario (un clic). Desde dentro de un iframe de otro origen la navegación se rechaza (`framed`): la página de inicio
de sesión nunca se muestra en un marco.

| Opción | |
|---|---|
| `scopes` | permisos de este inicio de sesión |
| `prompt` | `'login'` (credenciales siempre) o `'consent'` (consentimiento siempre) |
| `returnTo` | tu propio estado, guardado localmente y devuelto por `handleRedirect()` — no se envía |

### `createSignInUrl(options?)`

Construye la URL de autorización y recuerda el inicio de sesión en curso (verificador PKCE y `state`) sin navegar.
Útil cuando dibujas tú el enlace.

### `hasRedirectParams(url?)`

`true` cuando la URL (por defecto: la página actual) lleva los parámetros de una vuelta (`code`+`state` o `error`).

### `handleRedirect({ url?, cleanUrl? })`

En la página de callback: comprueba el `state`, intercambia el código por los tokens y guarda la sesión. Devuelve
`{ session, returnTo }`, o `null` cuando la URL no lleva parámetros de inicio de sesión. Con `cleanUrl` (por defecto
`true`) quita `code`, `state` y `error` de la barra de direcciones.

Errores (`VimineError.code`): `access_denied` y los demás errores OAuth cuando el socio no ha autorizado; `no_flow`
cuando no se ha iniciado ningún inicio de sesión desde este navegador (almacenamiento vacío o caducado);
`state_mismatch` cuando la vuelta no corresponde; `invalid_grant` cuando el código ha caducado o ya se ha usado.

## Sesión

### `getSession()` · `isSignedIn()`

La sesión guardada, o `null`.

```ts
interface Session {
    accessToken: string;
    refreshToken: string | null;
    expiresAt: number;            // ms, absoluto
    scope: Scope[];
    sub: string | null;           // el identificador del socio para tu asociación (vms_…)
    mode?: 'live' | 'test';       // 'test' para las sesiones de un cliente de prueba
}
```

### `getAccessToken()`

Un token de acceso válido, renovado cuando caduca en menos de 30 segundos. Lanza `not_signed_in` si no hay sesión.

### `refresh()`

Rota la sesión con el refresh token (una sola petición aunque se llame de forma concurrente). Cuando el refresh token
ya no se puede usar, la sesión se borra y el error se vuelve a lanzar.

### `signOut({ everywhere?, postLogoutRedirectUri?, state? })`

Revoca el refresh token (RFC 7009, en la medida de lo posible) y olvida la sesión. Con `everywhere: true`, después
envía el navegador al `/oauth/logout` de la app de Vimine, que cierra la sesión del socio y vuelve a
`postLogoutRedirectUri` (por defecto `redirectUri`; debe estar en un origen registrado).

### `onChange(listener)`

`listener(session | null)` en cada inicio de sesión, renovación y cierre de sesión; con `storage: 'local'`, también
para los que ocurren en otras pestañas. Devuelve la función para cancelar la suscripción.

## API de socios

### `request(path, init?)`

Una llamada autenticada: `Authorization: Bearer`, JSON de entrada y de salida, errores como `VimineError`, un
reintento después de una renovación cuando la respuesta es `401`. `init.idempotencyKey` se convierte en la cabecera
`Idempotency-Key`.

```js
const events = await vimine.request('/v1/me/membership/events?limit=5');
```

### `me`

| | Scope | |
|---|---|---|
| `me.get()` | `profile:read` | el perfil (`Me`), con `codicefiscale_check`: cómo está identificada la persona (`null` = todavía no) |
| `me.update(patch)` | `profile:write` | teléfono, sexo, y la identificación: fecha, lugar y país de nacimiento (`birth_country`), código fiscal — opcional, se puede añadir más tarde. Un código que no coincide responde `422` con una entrada `codicefiscale_mismatch`; cuando es `confirmable`, la persona lo confirma con `codicefiscale_confirmed: true` ([guía](https://developers.vimine.network/es/guides/members-without-cf/)) |

### `membership`

| | Scope | |
|---|---|---|
| `membership.get()` | `membership:read` | el carné: `status`, `verdict`, `expires_at`, `statuto`, `card` |
| `membership.request(body, { idempotencyKey? })` | `membership:request` | solicitud de inscripción; se genera una clave de idempotencia si falta. `422 identification_required` cuando el perfil no está identificado: `err.errors` indica lo que falta, que se completa con `me.update()` |
| `membership.events({ limit?, cursor? })` | `membership:read` | historial de estados, paginado |
| `membership.statuto()` | `membership:read` | el estatuto en vigor y la aceptación del socio |
| `membership.acceptStatuto({ version, sha256? })` | `statuto:accept` | registra la aceptación |
| `membership.card()` | `membership:read` | el token del carné (QR) y su ventana; `409 not_active` salvo que la inscripción esté activa |
| `membership.renew({ version, sha256? })` | `POST /v1/me/membership/renew` | renovación por cuenta propia: inmediata si es gratuita y sin aprobación; si no, una solicitud a la secretaría |
| `membership.setNetworkOptIn(bool)` | `PATCH /v1/me/membership/network-opt-in` | consentimiento al Network (`network:manage`) |
| `membership.googleWalletUrl()` | `GET /v1/me/membership/wallet/google` | un enlace «Guardar en Google Wallet»; `409 not_active` salvo que la inscripción esté activa |
| `membership.cardSvg()` | `GET /v1/me/membership/card.svg` | el carné en SVG (con el QR); `409 not_active` salvo que la inscripción esté activa |
| `membership.emailCard()` | `POST /v1/me/membership/card/email` | envía al socio su carné por correo |

### `events`

Los eventos publicados de la asociación, incluidos los que son solo para socios (`events:read`). Los públicos no
necesitan iniciar sesión: `vimine.public.events`.

| | |
|---|---|
| `events.list({ filter?, from?, to?, limit? })` | `filter`: `upcoming` (por defecto), `past`, `cancelled`, `all` |
| `events.get(slug)` | contenido, imágenes, tipos de inscripción con las plazas que quedan |
| `events.eligibility(slug)` | ¿puede inscribirse el socio? por tipo, con los motivos (`membership_required`, `sold_out`, …) |

### `registrations`

Las inscripciones del socio (`registrations:read` / `registrations:write`). Consulta la guía [Eventos e inscripciones](https://developers.vimine.network/es/guides/events/).

| | |
|---|---|
| `registrations.create(slug, { registration_type, note?, quantity? }, opts?)` | tipo gratuito → `confirmed` con el `token` del QR; con contribución → `reserved` con `payment_instructions`; `422 not_eligible` con los motivos; `Idempotency-Key` generada |
| `registrations.list({ filter? })` · `registrations.get(id)` | las inscripciones del socio (`filter: 'upcoming'`) |
| `registrations.cancel(id)` | anula dentro de la ventana del evento (después, `409 window_closed`) |
| `registrations.tokenPayload(id)` | el contenido del QR de una inscripción confirmada, o `null` |

### `connections`

| | |
|---|---|
| `connections.list()` | las apps que el socio ha autorizado en esta asociación |
| `connections.revoke(clientId)` | retira el consentimiento a una app (sus sesiones terminan) |

### `public`

Los endpoints públicos, sin credenciales: consulta [`publicApi`](https://developers.vimine.network/es/sdk/public/).

## Almacenamiento

| `storage` | Dónde | Cuándo |
|---|---|---|
| `'session'` (por defecto) | `sessionStorage`: por pestaña, desaparece al cerrarla | la opción por defecto más segura para un cliente público |
| `'local'` | `localStorage`: compartido entre pestañas, sobrevive a los reinicios; el cierre de sesión se propaga a las demás pestañas | apps en las que el socio espera seguir con la sesión iniciada |
| `'memory'` | solo en memoria | SSR, tests, quioscos |
| `TokenStorage` | un objeto tuyo `{ get, set, remove }` | Node, almacenamientos cifrados, apps nativas |

Cuando el almacenamiento del navegador no está disponible (modo privado, `about:blank`), el SDK recurre a la memoria.
Se exporta `MemoryStorage`.

## Utilidades

- `createPkce()` → `{ verifier, challenge, method: 'S256' }`, `createVerifier()`, `createChallenge(verifier)`,
  `createState()`, `base64url(bytes)` — PKCE y `state` sobre WebCrypto, si construyes el flujo a mano.
- `DEFAULT_API_URL`, `DEFAULT_APP_URL`.
- `publicApi(options?)` — consulta [API pública](https://developers.vimine.network/es/sdk/public/).
- `VimineError` — consulta [Errores](https://developers.vimine.network/es/sdk/errors/).
