# Usuarios externos

Identifica a las personas de tu aplicación en UTILIA OS con identify y lístalas: es el primer paso de tickets, facturas, tarjetas y suscripciones.

El servicio de usuarios da de alta en UTILIA OS a las personas que usan tu aplicación. Todo lo demás cuelga de ellas: sus tickets de soporte, sus facturas, sus tarjetas guardadas, sus autorizaciones de cobro y sus suscripciones. Por eso `identify` es casi siempre la primera llamada de una integración. Funciona con la clave de tu aplicación externa, siempre desde tu servidor.

**Qué cuelga de un usuario identificado**

En el centro: **Usuario externo**. Una persona de tu aplicación, reconocida por su externalId.

Recibe de:

- **Tu aplicación**: Lo crea o lo actualiza con identify, con su NIF y su dirección si los tienes.

Envía a:

- **CRM**: Se vincula a una ficha de cliente: la que indiques con clientId o la que UTILIA OS crea o reutiliza la primera vez que le facturas o le cobras.
- [**Tickets de soporte**](https://os.utilia.ai/docs/developers/sdk/tickets): Sus tickets y conversaciones con el equipo.
- [**Facturas**](https://os.utilia.ai/docs/developers/sdk/invoices): Las facturas que emites a su nombre, con sus datos fiscales.
- [**Métodos de pago**](https://os.utilia.ai/docs/developers/sdk/payment-methods): Las tarjetas que guarda para pagar.
- [**Suscripciones**](https://os.utilia.ai/docs/developers/sdk/subscriptions): Sus cuotas y la autorización que permite cobrarlas solas.

## Antes de empezar

| Método | Para qué | Scope que exige |
| - | - | - |
| `users.identify(datos)` | Crear o actualizar un usuario | `crm:users:write` |
| `users.list(página, límite)` | Listar los usuarios de tu aplicación | `crm:users:read` |

Sin el scope, la respuesta es un 403 con `errorCode` `INSUFFICIENT_SCOPE`, cuyo mensaje y su campo `requiredScopes` dicen qué permiso falta (el SDK lo deja en `error.requiredScopes` desde la 4.6.0). Antes que el scope se comprueba el plan: sin el aplicativo Integraciones, la respuesta es un 403 `MODULE_NOT_ENABLED`.

> **Atención: identify exige scope desde septiembre de 2026**
>
> Si tu integración llamaba a `identify` antes de esa fecha, comprueba que la aplicación tiene concedido `crm:users:write`: sin él, las altas y actualizaciones de usuarios fallan.

## Identificar a un usuario

`identify` funciona como «crear o actualizar»: si ya existe un usuario con ese `externalId` en tu aplicación, actualiza los campos que mandes; si no, lo crea. Puedes llamarlo cada vez que alguien inicia sesión en tu aplicación para mantener sus datos al día.

| JavaScript | Python | Obligatorio | Qué es |
| - | - | - | - |
| `externalId` | `external_id` | Sí | Identificador de la persona en tu sistema, 255 caracteres como máximo. |
| `email` | `email` | No | Correo. El SDK comprueba el formato. |
| `name` | `name` | No | Nombre de la persona o razón social. |
| `avatarUrl` | `avatar_url` | No | Dirección de su imagen, 2048 caracteres como máximo. |
| `metadata` | `metadata` | No | Datos libres de clave y valor. |
| `taxId` | `tax_id` | No | NIF o CIF, 50 caracteres como máximo. |
| `billingAddress` | `billing_address` | No | Dirección de facturación: `street`, `city`, `postalCode` (`postal_code`) y `country` obligatorios; `province` opcional. |
| `clientId` | `client_id` | No | Ficha de cliente del CRM a la que vincularlo, si tu aplicación la creó antes. |

`taxId` y `billingAddress` pasan a la ficha de cliente del CRM y a la de la pasarela de pago: son los datos que salen impresos en el PDF de sus facturas. Mándalos en cuanto los tengas, antes de emitir la primera, para no tener que rectificarla después.

Sin `clientId`, UTILIA OS crea o reutiliza la ficha de cliente por su cuenta la primera vez que le facturas, le guardas una tarjeta o le creas una suscripción. Con `clientId`, solo acepta fichas que tu propia aplicación creó (con el servicio de clientes de la API externa) y que no estén vinculadas a un usuario de otra aplicación; en cualquier otro caso responde 404 con `errorCode` `CLIENT_NOT_ACCESSIBLE`.

### JavaScript

```typescript
const user = await sdk.users.identify({
  externalId: 'usr-4821',
  email: 'laura.garcia@ejemplo.com',
  name: 'Estudio Marea SL',
  taxId: 'B76543210',
  billingAddress: {
    street: 'Avenida de Canarias 12, planta 3',
    city: 'Las Palmas de Gran Canaria',
    postalCode: '35001',
    country: 'España',
    province: 'Las Palmas',
  },
  metadata: { plan: 'premium' },
});

console.log(user.id); // identificador interno en UTILIA OS
console.log(user.clientId); // ficha del cliente en el CRM
```

### Python

```python
from utilia_sdk import CreateInvoiceBillingAddress, IdentifyUserInput

user = await sdk.users.identify(
    IdentifyUserInput(
        external_id="usr-4821",
        email="laura.garcia@ejemplo.com",
        name="Estudio Marea SL",
        tax_id="B76543210",
        billing_address=CreateInvoiceBillingAddress(
            street="Avenida de Canarias 12, planta 3",
            city="Las Palmas de Gran Canaria",
            postal_code="35001",
            country="España",
            province="Las Palmas",
        ),
        metadata={"plan": "premium"},
    )
)

print(user.id)  # identificador interno en UTILIA OS
print(user.client_id)  # ficha del cliente en el CRM
```

La respuesta es el usuario (`ExternalUser`): `id`, `appId` (`app_id`), `externalId`, `email`, `name`, `avatarUrl`, `metadata`, `clientId`, `lastSeenAt` (fecha de la última actividad) y, cuando el servidor lo incluye, el número de tickets en `_count.tickets` (`count.tickets` en Python).

## Listar usuarios

`list` devuelve los usuarios de tu aplicación por páginas. Por defecto, la página 1 con 20 elementos. La respuesta trae `data` y `pagination` (`page`, `limit`, `total` y `totalPages`).

### JavaScript

```typescript
let page = 1;
let totalPages = 1;
do {
  const result = await sdk.users.list(page, 100);
  for (const user of result.data) {
    console.log(`${user.name} (${user.externalId}): ${user._count?.tickets ?? 0} tickets`);
  }
  totalPages = result.pagination.totalPages;
  page++;
} while (page <= totalPages);
```

### Python

```python
page, total_pages = 1, 1
while page <= total_pages:
    result = await sdk.users.list(page=page, limit=100)
    for user in result.data:
        tickets = user.count.tickets if user.count else 0
        print(f"{user.name} ({user.external_id}): {tickets} tickets")
    total_pages = result.pagination.total_pages
    page += 1
```

## Siguiente paso

Con el usuario identificado, usa su `externalId` en el resto de servicios:

- [Tickets de soporte](https://os.utilia.ai/docs/developers/sdk/tickets): Abre tickets en su nombre, añade mensajes y sigue las respuestas del equipo.
- [Facturas](https://os.utilia.ai/docs/developers/sdk/invoices): Emite facturas a su nombre con los datos fiscales que acabas de enviar.
