# Archivos adjuntos

Sube archivos para adjuntarlos a tickets y mensajes, obtén enlaces temporales de descarga y consulta la cuota de almacenamiento de tu aplicación.

El servicio de archivos sube capturas, registros o documentos para adjuntarlos después a un [ticket de soporte](https://os.utilia.ai/docs/developers/sdk/tickets) o a uno de sus mensajes. Los archivos se guardan en el almacenamiento de archivos de tu organización, asociados a tu aplicación externa. Funciona con la clave de tu aplicación externa, siempre desde tu servidor.

**Del archivo al ticket**

1. **Subir** (files.upload): Devuelve el id del archivo, su tipo y su tamaño.
2. [**Adjuntar**](https://os.utilia.ai/docs/developers/sdk/tickets) (attachmentIds): Pasa el id al abrir el ticket o al añadir un mensaje.
3. **Descargar** (files.getUrl): Pide un enlace temporal cuando haga falta verlo.

## Antes de empezar

| JavaScript | Python | Para qué | Scope |
| - | - | - | - |
| `files.upload(archivo, opciones?)` | `files.upload(archivo, *, name, metadata, on_progress)` | Subir un archivo | `fm:files:write` |
| `files.getUrl(fileId)` | `files.get_url(file_id)` | Enlace temporal de descarga | `fm:files:read` |
| `files.getQuota()` | `files.get_quota()` | Espacio usado y límites | `fm:files:read` |

Antes que el scope se comprueba el plan: sin el aplicativo Gestor de archivos, la respuesta es un 403 `MODULE_NOT_ENABLED`.

## Subir un archivo

Qué puedes pasar como archivo depende del lenguaje:

- **JavaScript**: un `File` (en el navegador o en Node.js 20 o superior) o un `Blob`. Si es un `Blob` sin nombre, el SDK usa el de `name` o, si no lo das, `file`.
- **Python**: una ruta (`str` o `Path`), que el SDK abre y cierra por ti, o un objeto de archivo binario (`IO[bytes]`, por ejemplo `BytesIO`).

Opciones comunes: `name` (nombre que se guardará, 255 caracteres como máximo), `metadata` (datos libres) y una función de progreso, `onProgress` (`on_progress`), que recibe el porcentaje de 0 a 100.

### JavaScript

```typescript
import { readFile } from 'node:fs/promises';

const blob = new Blob([await readFile('./registro-errores.log')], { type: 'text/plain' });

const uploaded = await sdk.files.upload(blob, {
  name: 'errores-produccion.log',
  metadata: { origen: 'sistema-pagos' },
  onProgress: (percent) => console.log(`Subida: ${percent}%`),
});

console.log(uploaded.id); // úsalo en attachmentIds
```

### Python

```python
from pathlib import Path

uploaded = await sdk.files.upload(
    Path("./registro-errores.log"),
    name="errores-produccion.log",
    metadata={"origen": "sistema-pagos"},
    on_progress=lambda percent: print(f"Subida: {percent}%"),
)

print(uploaded.id)  # úsalo en attachment_ids
```

La respuesta trae `id`, `originalName` (`original_name`), `mimeType` (`mime_type`), `size` en bytes, `downloadUrl` (`download_url`) y `createdAt` (`created_at`). Pasa el `id` en `attachmentIds` al [abrir un ticket](https://os.utilia.ai/docs/developers/sdk/tickets#abrir-un-ticket) o al añadir un mensaje.

## Descargar y consultar la cuota

`getUrl` devuelve directamente la dirección temporal de descarga, como texto. `getQuota` devuelve el espacio usado y el total (`usedBytes` y `maxBytes`), el tamaño máximo de cada archivo (`maxFileSizeBytes`) y el porcentaje de uso (`usagePercent`).

### JavaScript

```typescript
const url = await sdk.files.getUrl(uploaded.id);

const quota = await sdk.files.getQuota();
console.log(`Uso: ${quota.usagePercent}%`);
console.log(`Máximo por archivo: ${(quota.maxFileSizeBytes / 1024 / 1024).toFixed(1)} MB`);
```

### Python

```python
url = await sdk.files.get_url(uploaded.id)

quota = await sdk.files.get_quota()
print(f"Uso: {quota.usage_percent}%")
print(f"Máximo por archivo: {quota.max_file_size_bytes / 1024 / 1024:.1f} MB")
```

El tamaño máximo por archivo y el espacio total son los de tu aplicación externa. Un archivo que los supera se rechaza con un error de validación que dice cuál es el máximo permitido.

## Siguiente paso

- [Tickets de soporte](https://os.utilia.ai/docs/developers/sdk/tickets): Adjunta los archivos al abrir un ticket o al responder.
- [Límites y validaciones](https://os.utilia.ai/docs/developers/sdk/limits): Qué comprueba el SDK antes de subir y qué rechaza el servidor.
