# Iniciar sesión con Vimine

> El flujo OAuth 2.1 + PKCE del clic al callback, la sesión, las llamadas a la API y el cierre de sesión, con y sin el SDK.

URL: https://developers.vimine.network/es/guides/sign-in/

Los socios de una asociación inician sesión en tu sitio con su cuenta de Vimine y te autorizan, con un
consentimiento explícito, a usar algunos de sus datos. Es OAuth 2.1 con PKCE: la contraseña solo se escribe en la
página de Vimine, nunca en la tuya; tu sitio recibe tokens limitados a los permisos concedidos.

:::note[Antes de empezar]
Necesitas un cliente **Sitio web** o **App nativa** con tu `redirect_uri` registrado — consulta
[Registrar un cliente](https://developers.vimine.network/es/start/register-a-client/) — y el SDK instalado ([Instalar el SDK](https://developers.vimine.network/es/start/install/)).
Para desarrollar sin tocar datos reales, usa un cliente de **test** y el [sandbox](https://developers.vimine.network/es/guides/sandbox/).
:::

## El flujo

1. A partir del **clic** de un socio, tu sitio envía el navegador (la ventana principal, nunca un iframe) a la
   página de autorización:
   ```
   https://app.vimine.network/authorize
       ?client_id=vmc_…&redirect_uri=https://www.example.org/vimine/callback
       &response_type=code&state=…&code_challenge=…&code_challenge_method=S256
       &scope=profile:read%20membership:read
   ```
2. El socio inicia sesión (si no lo había hecho ya) y ve **a quién** concede **qué**. Confirma o cancela.
3. Vimine devuelve el navegador a tu `redirect_uri` con `code` y `state` (o `error=access_denied`).
4. Tu sitio intercambia el código en `POST https://api.vimine.network/v1/oauth/token`
   (`grant_type=authorization_code`, `code`, `code_verifier`, `client_id`, `redirect_uri`) y recibe un token de
   acceso (10 minutos) y un refresh token (rotativo; 30 días, 14 de inactividad).

El SDK hace todo esto por ti.

## Con el SDK

```js title="cualquier página"
import { Vimine } from '@vimine/sdk';

const vimine = new Vimine({
    clientId: 'vmc_…',
    redirectUri: 'https://www.example.org/vimine/callback',
    scopes: ['profile:read', 'membership:read']
});

// desde un clic: `returnTo` es tu propio estado, te lo devuelven después del callback
document.querySelector('#sign-in').addEventListener('click', () => vimine.signIn({ returnTo: location.pathname }));
```

```js title="página de callback"
const result = await vimine.handleRedirect();   // null cuando la URL no lleva parámetros de inicio de sesión
if (result) location.replace(result.returnTo || '/');
```

Opciones de `signIn()`:

| Opción | Efecto |
|---|---|
| `scopes` | los permisos que se solicitan en este inicio de sesión (por defecto: los del constructor, o `profile:read membership:read`) |
| `prompt: 'login'` | fuerza el formulario de credenciales aunque haya una sesión de Vimine activa |
| `prompt: 'consent'` | muestra siempre la pantalla de consentimiento |
| `returnTo` | tu propio estado (por ejemplo, la página desde la que salió el socio), guardado localmente y devuelto por `handleRedirect()` |

`handleRedirect()` lanza un [`VimineError`](https://developers.vimine.network/es/sdk/errors/) con `code` `access_denied` cuando el socio ha
cancelado, y `state_mismatch` o `no_flow` cuando la vuelta no corresponde a un inicio de sesión empezado desde este
navegador.

## La sesión

- Los tokens viven en `sessionStorage` (por pestaña). `storage: 'local'` los comparte entre pestañas y sincroniza el
  cierre de sesión; `'memory'` no los guarda nunca. Puedes pasar un adaptador propio.
- `vimine.getAccessToken()` renueva solo 30 segundos antes de la caducidad; una llamada que recibe 401 se repite una
  vez después de la renovación. Un refresh token **reutilizado** (uno copiado, por ejemplo) revoca toda la familia de
  sesiones.
- `vimine.onChange(session => …)` notifica el inicio de sesión, la renovación y el cierre de sesión.
- `vimine.getSession().sub` es el identificador del socio **para tu asociación** (`vms_…`): estable en el tiempo,
  distinto en cada asociación, nunca un id interno.

## Qué puedes pedir

```js
await vimine.me.get();                                     // profile:read
await vimine.me.update({ phone: '+39 …' });                // profile:write
await vimine.membership.get();                             // membership:read — status, verdict, expires_at, statuto, card
await vimine.membership.events({ limit: 20 });             // historial de estados
await vimine.membership.statuto();                         // el estatuto en vigor y si el socio lo ha aceptado
await vimine.membership.acceptStatuto({ version: 3 });     // statuto:accept
await vimine.membership.request({ accept_statuto: { version: 3 }, privacy_notice: { version: 2 } });   // membership:request → carné pendiente
await vimine.membership.card();                            // el token del carné (QR) y su ventana
await vimine.legal.acceptances();                          // legal:read → lo que ha aceptado y lo que aún está pendiente
await vimine.legal.record({ kind: 'privacy_notice', version: 2 });   // legal:accept → acusa recibo de la política de la asociación
await vimine.connections.list();  await vimine.connections.revoke('vmc_…');
```
:::note[Documentos legales]
`legal.acceptances()` enumera las aceptaciones de la persona y, en `pending`, los documentos que aún debe confirmar en
su versión en vigor: las condiciones de la cuenta y la política de privacidad de la plataforma (`platform`), la
política de privacidad de esta asociación para sus socios (`privacy_notice`). Muestra el texto (`GET /v1/legal/{kind}`,
`GET /v1/aps/{slug}/privacy-notice`) y registra la confirmación con `legal.record()`: la versión debe ser la vigente;
si no, `422 <kind>_outdated`.
:::
:::note[Activación]
Lo que le ocurre a una solicitud (`membership.request()`) depende de **cómo admite la asociación a sus socios** — `admission` en el perfil público, explicado en [Admisión y activación](https://developers.vimine.network/es/guides/admission/). Si decide su junta directiva, queda `pending` con `pending_reasons` ordenados (`approval`, luego `payment`) y la cuota se paga solo después de la admisión (`payment_open`); si no, nace **activa** cuando el tipo es gratuito, o cuando se registra la cuota. Una inscripción activa puede llevar un `valid_from` en el futuro: el carné vale en la entrada a partir de ese momento. Muestra al socio `quota` y `payment_instructions` siempre que esté presente `payment`.
:::

`verdict` es lo que cuenta en la entrada: `valid`, `expired`, `statuto_required` (ha vencido el plazo para volver a
aceptar el estatuto) o `inactive`. Quien todavía no es socio también puede iniciar sesión y usar
`membership.request`: el consentimiento a los permisos de inscripción también lo pueden dar quienes no son socios;
los demás permisos requieren un carné en esa asociación.

Cada método se detalla en [`Vimine`](https://developers.vimine.network/es/sdk/browser/); las respuestas, en [Tipos](https://developers.vimine.network/es/sdk/types/).

## Cerrar la sesión

```js
await vimine.signOut();                        // revoca la sesión de la API y la olvida
await vimine.signOut({ everywhere: true });    // además cierra la sesión de Vimine del socio y vuelve a tu sitio
```

Con `everywhere`, el navegador pasa por
`https://app.vimine.network/oauth/logout?client_id=…&post_logout_redirect_uri=…`; la dirección de vuelta debe estar
en un origen registrado para el cliente. Cuando el socio cierra la sesión de Vimine, se revocan todas las sesiones de
la API de sus apps (cierre de sesión único); lo mismo ocurre cuando revoca una app desde **Apps conectadas** o
restablece su contraseña.

## Sin el SDK

Si no puedes usar el SDK (otro lenguaje, una plataforma low-code), el flujo es OAuth 2.1 estándar.

**Autorización** — `GET https://app.vimine.network/authorize`

| Parámetro | |
|---|---|
| `client_id` | tu `vmc_…` |
| `redirect_uri` | uno de los registrados, idéntico |
| `response_type` | `code` |
| `state` | valor aleatorio, para comparar a la vuelta |
| `code_challenge`, `code_challenge_method` | PKCE, solo `S256` |
| `scope` | permisos separados por espacios |
| `prompt` | opcional: `login`, `consent` |
| `ui_locales` | opcional: uno de los idiomas de Vimine — `it` (por defecto), `en`, `zh-Hans`, `zh-Hant`, `fr`, `es`, `de`, `pt-BR` |

**Intercambio** — `POST https://api.vimine.network/v1/oauth/token`, cuerpo `application/x-www-form-urlencoded`:

```
grant_type=authorization_code&code=…&code_verifier=…&client_id=vmc_…&redirect_uri=…
```

```json title="respuesta"
{ "access_token": "…", "token_type": "Bearer", "expires_in": 600, "refresh_token": "…",
  "scope": "profile:read membership:read", "sub": "vms_…" }
```

**Renovación** — el mismo endpoint con `grant_type=refresh_token&refresh_token=…&client_id=…`: la respuesta lleva un
refresh token **nuevo** (el antiguo sigue siendo válido 15 segundos para las peticiones concurrentes; después,
reutilizarlo revoca toda la familia).

**Revocación** — `POST /v1/oauth/revoke` con `token=…&token_type_hint=refresh_token` (RFC 7009).

**Descubrimiento** — `GET https://api.vimine.network/.well-known/oauth-authorization-server` (RFC 8414) enumera los
endpoints, los scopes y los métodos admitidos.

Los errores de estos endpoints usan el formato OAuth: `{ "error": "invalid_grant", "error_description": "…" }`.
