# Socios sin código fiscal

> Una persona se identifica con un código fiscal o con su fecha, lugar y país de nacimiento. Qué enviar para las personas nacidas en el extranjero, cómo se confirma un código que no coincide, qué mostrar en la entrada.

URL: https://developers.vimine.network/es/guides/members-without-cf/

Para inscribirse en una asociación **no hace falta un código fiscal italiano** (*codice fiscale*). Lo que hace falta
es que la persona esté identificada, y en Vimine lo está de una de estas dos formas:

- con un **código fiscal**, o
- con sus **datos de nacimiento**: `dob` + `birthplace` + `birth_country` (ISO 3166-1 alpha-2).

Nunca sin ninguno de los dos. El resto de esta guía es lo que cambia, campo por campo, para una integración que
inscribe a personas — estudiantes de intercambio, voluntarios recién llegados, socios que viven en el extranjero.

## Identificar a una persona

`POST /v1/enroll` y `POST /v1/signup` aceptan los mismos campos:

| Campo | |
|---|---|
| `codicefiscale` | opcional. Cuando está presente, se valida y se coteja con el nombre, los apellidos, la fecha y el lugar de nacimiento |
| `dob` | fecha de nacimiento, `YYYY-MM-DD` |
| `birthplace` | el *comune* (el municipio) para quien nació en Italia; la ciudad, como texto libre, para quien nació en el extranjero |
| `birth_country` | país de nacimiento, dos letras (`IT`, `FR`, `SN`…) |
| `gender` | solo demográfico: **ya no se coteja** con el código |

```js
// Sin código fiscal: fecha, lugar y país de nacimiento
await vimineServer.enroll({
    email: 'marie.dupont@example.org',
    first_name: 'Marie',
    last_name: 'Dupont',
    dob: '1985-03-10',
    birthplace: 'Lyon',
    birth_country: 'FR',
    accept_statuto: { version: statuto.version, sha256: statuto.sha256 }
});
```

En `/v1/enroll`, sin código, cada dato de nacimiento que falte es un `422 validation_failed` con una entrada en
`errors[]`:

```json
{ "code": "validation_failed", "errors": [{ "field": "birth_country", "code": "required" }] }
```

`/v1/signup` solo crea la cuenta: ahí los datos de nacimiento siguen siendo opcionales, y harán falta cuando la
persona solicite un carné.

## Nacidos en el extranjero: envía `birth_country`

El código fiscal de una persona nacida en Italia contiene el *comune* de nacimiento; el de una persona nacida en el
extranjero contiene **solo el país**. Por eso, para estas personas el cotejo compara `birth_country` y trata
`birthplace` — la ciudad — como texto libre. Enviar `birth_country` es lo que permite que una persona nacida en el
extranjero con un código válido pase la comprobación: sin él, «Lyon» se buscaría entre los *comuni* italianos y el
código resultaría incoherente. (Un formulario que hasta ahora enviaba el nombre italiano del país como lugar de
nacimiento — `birthplace: 'Francia'` — sigue pasando; `birth_country` es la forma preferible.)

Lo mismo vale para la comprobación pública, que ahora dice también lo que contiene el código:

```js
const r = await publicApi().validateFiscalCode({
    code: 'DPNMRA85C50Z110U', first_name: 'Marie', last_name: 'Dupont', date: '1985-03-10', place: 'Lyon', country: 'FR'
});
// { valid: true, error: null, mismatches: [], hints: [], birth_date: '1985-03-10', birth_country: 'FR' }
```

`birth_date` y `birth_country` se leen del propio código: puedes usarlos para rellenar el formulario de antemano. Este
endpoint sigue comparando `gender`, cuando lo envías; la inscripción y el perfil no.

## Un código que no coincide, y la confirmación

Un código mal escrito (estructura o carácter de control) es `invalid_codicefiscale`: hay que corregirlo, nunca se
puede confirmar. En cambio, un código bien formado que **no coincide** con los datos introducidos responde `422` con
una entrada más completa:

```json
{
  "code": "validation_failed",
  "errors": [{
    "field": "codicefiscale",
    "code": "codicefiscale_mismatch",
    "mismatches": ["place"],
    "hints": [],
    "confirmable": true
  }]
}
```

- `mismatches` — lo que no coincide: `lastName`, `firstName`, `date`, `place`;
- `hints` — `names_swapped` cuando el código coincide al intercambiar apellidos y nombre: ofrecer el intercambio
  suele bastar;
- `confirmable` — si la persona puede **confirmar** que ese es el código de su tarjeta sanitaria. Ocurre:
  *comuni* que ya no existen, transliteraciones, nombres registrados en otro orden.

Cuando `confirmable` es `true`, pregunta a la persona y repite la solicitud con `codicefiscale_confirmed: true`.
Cuando no coinciden ni el nombre, ni los apellidos, ni la fecha, ya nada vincula ese código con esa persona:
`confirmable` es `false` y la única salida es corregirlo.

```js
import { VimineError } from '@vimine/sdk/server';

try {
    await vimineServer.enroll(input);
} catch (err) {
    const entry = err instanceof VimineError && err.errors?.find((e) => e.code === 'codicefiscale_mismatch');
    if (!entry) throw err;
    if (entry.confirmable && (await askThePerson(entry.mismatches, entry.hints))) {
        await vimineServer.enroll({ ...input, codicefiscale_confirmed: true });
    }
}
```

No envíes `codicefiscale_confirmed` por iniciativa propia: es una declaración de la persona, y se registra como tal.

## Añadir el código más tarde

Quien se inscribió sin código puede añadirlo cuando lo tenga, desde su propio perfil:

```js
const me = await vimine.me.update({ codicefiscale: 'DPNMRA85C50Z110U' });
me.codicefiscale_check;   // 'matched'
```

`PATCH /v1/me` acepta `birth_country` y `codicefiscale_confirmed`, que es solo de escritura; `codicefiscale` también
puede volver a ponerse a `null`. `GET /v1/me` dice cómo está identificada la persona:

| `codicefiscale_check` | |
|---|---|
| `matched` | un código fiscal que coincide con los datos de nacimiento de la persona |
| `mismatch_confirmed` | un código que no coincide, confirmado por la persona |
| `absent` | ningún código: la identifican la fecha, el lugar y el país de nacimiento |
| `null` | todavía no identificada: no puede solicitar un carné |

`matched` significa **coherente**, no verificado en el registro tributario: un código fiscal sigue siendo un dato
declarado.

## `identification_required`

Una cuenta puede existir sin estar identificada — por ejemplo, alguien que se registró solo para iniciar sesión.
Cuando esa persona solicita un carné, la API lo dice e indica lo que falta:

- **`POST /v1/me/membership`** → `422 identification_required`, con `errors[]` = los datos de nacimiento que faltan
  (`{ field: 'birthplace', code: 'required' }`…). Envía a la persona a completar su perfil con `PATCH /v1/me`
  (permiso `profile:write`) y repite la solicitud.
- **`POST /v1/enroll/confirm`** — el enlace «confirma tu solicitud» que recibe el titular de una cuenta existente →
  `409 identification_required`, con el mismo `errors[]`. Pide los datos de nacimiento en la página y vuelve a enviar
  el token con `identification`:

```js
await publicApi().confirmEnrollment(token, { dob: '1985-03-10', birthplace: 'Lyon', birth_country: 'FR' });
// POST /v1/enroll/confirm  { token, identification: { dob, birthplace, birth_country, codicefiscale?, codicefiscale_confirmed? } }
```

Los datos de nacimiento viajan **en el cuerpo de la petición, nunca en el enlace**: en el enlace nunca hay nada
personal. `identification` solo completa los campos vacíos del perfil, no sobrescribe lo que ya hay; si los datos no
cuadran, la respuesta es un `422 validation_failed` como el de arriba.

La admisión no cambia: los `pending_reasons` son los que diga el modelo de admisión de la asociación, para todo el
mundo (consulta [Admisión y activación](https://developers.vimine.network/es/guides/admission/)).

## En la entrada: la comprobación de identidad

Un código fiscal que coincide con los datos de nacimiento se puede contrastar con ellos; los datos de nacimiento
solos, o un código confirmado por la persona, no. Para estos socios, la asociación mira un documento **una vez**, la
primera vez que la persona se presenta. Quien integra un escáner lo ve en `POST /v1/checkins` (permiso
`checkins:write`), en las respuestas para un carné:

```js
const r = await vimineServer.checkins.create('vmt_…');
// { kind: 'card', verdict: 'valid', …, identity_check: 'pending' }
if (r.identity_check === 'pending') show('Primera entrada: pide un documento');
```

| `identity_check` | |
|---|---|
| `pending` | el socio no tiene un código fiscal comprobable y nadie de la asociación ha mirado todavía un documento: **muéstralo** a quien atiende la entrada |
| `verified` | alguien de la asociación ha mirado un documento |
| `flagged` | alguien lo ha mirado y no coincidía: se encarga la secretaría |
| `null` | nada que comprobar |

Es una **nota**, no forma parte de `verdict` y **no restringe nada**: un carné `valid` con
`identity_check: 'pending'` entra. El resultado se registra desde la consola de la asociación, no desde la API. La
verificación pública `GET /v1/verify/{card_token}` nunca la incluye: quien comprueba un carné desde fuera solo ve el
estado y la caducidad.

## La dirección

Algunas asociaciones guardan la dirección de sus socios en su libro de socios, otras no. La decisión es de la
asociación y vale para **todos los socios o para ninguno**; el perfil público la declara:

```
GET https://api.vimine.network/v1/aps/{slug}
→ { "aps": { …, "member_address": "optional" } }
```

| `member_address` | |
|---|---|
| `none` | no la pidas: un `address` enviado de todas formas no se guarda |
| `optional` | pídela, sin hacerla obligatoria |
| `required` | sin `address`, la inscripción responde `422` con `{ field: 'address', code: 'required' }` |

```js
await vimineServer.enroll({
    …,
    address: { kind: 'residence', line: 'Via Roma 1', postal_code: '20121', city: 'Milano', province: 'MI', country: 'IT' }
});
```

`line` y `city` son obligatorios dentro de `address`; `kind` es `residence` (el valor por defecto) o `domicile`,
cuando difiere de la residencia; `country` es `IT` si se omite; `province` solo afecta a las direcciones italianas.
`/v1/signup` no tiene `address`: la dirección pertenece a la inscripción, no a la cuenta.

## Una asociación no puede exigir el código

No existe la opción «código fiscal obligatorio». Una asociación no puede exigir un código fiscal a todos los
solicitantes: quien no lo tiene se identifica con sus datos de nacimiento, en todas partes. Si tu formulario lo hace
obligatorio, es una decisión de tu formulario — y deja fuera a personas que la asociación podría acoger.

## Qué ha cambiado (versión 2026-09-19)

Todo es aditivo: **un formulario que siempre envía un código fiscal sigue funcionando sin cambios**. Lo que una
integración existente puede notar:

- `codicefiscale` ya no es obligatorio en `POST /v1/enroll`; sin código, hacen falta `dob`, `birthplace` y
  `birth_country` (`422` `required` por cada uno que falte);
- nuevos campos opcionales `birth_country`, `codicefiscale_confirmed` (enroll, signup, `PATCH /v1/me`) y `address`
  (enroll);
- `gender` ya no se coteja con el código: ya no llega un `422` solo por el sexo;
- un código que no coincide sigue respondiendo `codicefiscale_mismatch`, ahora con `hints` y `confirmable` junto a
  `mismatches`; un código mal formado sigue siendo `invalid_codicefiscale`;
- `GET`/`PATCH /v1/me` tienen ahora un esquema declarado (`Me`), con `birth_country` y `codicefiscale_check`;
- un error nuevo, `identification_required`: `422` en `POST /v1/me/membership`, `409` en `POST /v1/enroll/confirm`
  (que acepta `identification` en el cuerpo);
- `POST /v1/validate/fiscal-code` acepta `country` y responde también `hints`, `birth_date`, `birth_country`; `error`
  es `invalid`, `mismatch` o `null`;
- `POST /v1/checkins` añade `identity_check` a las respuestas para un carné; `GET /v1/verify/{card_token}` sigue igual;
- el perfil público añade `aps.member_address`.
