# Integración de Wiky Designer en otras plataformas

Wiky Designer se puede incrustar en otra aplicación web y gobernar desde ella con mensajes. La plataforma anfitriona inicializa el motor con un escaneo o un diseño, le da nombre y versión al proyecto, y aprueba cada exportación indicando dónde deben entregarse los archivos.

Demo interactiva: `integration/demo.html` (en la misma URL donde esté desplegado el motor).

## 1. Cómo incrustarlo

```html
<iframe id="wiky" src="https://wikyburns.github.io/DesignEngine/" allow="fullscreen"></iframe>
<script>
  const frame = document.getElementById('wiky');
  window.addEventListener('message', (e) => {
    if (e.source !== frame.contentWindow) return;
    const msg = e.data; // { type: 'wiky:...', ... }
    if (msg.type === 'wiky:ready') {
      frame.contentWindow.postMessage({
        type: 'wiky:init',
        projectName: 'Paciente-1234',
        version: '3',
        caseType: 'prosthetic_bk',
        legSide: 'left',
        scanUrl: 'https://tu-plataforma.com/escaneos/1234.obj',
      }, 'https://wikyburns.github.io');
    }
  });
</script>
```

Si el motor se carga en la misma página (sin iframe), los mismos mensajes van como `CustomEvent`:

```js
window.addEventListener('wiky:ready', () => {
  window.dispatchEvent(new CustomEvent('wiky:init', { detail: { projectName: 'Paciente-1234', version: '3', scanUrl: '…' } }));
});
window.addEventListener('wiky:export:request', (e) => { const msg = e.detail; /* … */ });
```

En ambos casos el contenido es el mismo: un objeto con `type` y el resto de campos.

### Orígenes permitidos

Por defecto el motor acepta `wiky:init` desde cualquier origen y responde siempre a la ventana y origen que lo inicializó. Para restringirlo, antes de cargar el motor define en su propia página:

```js
window.WIKY_ALLOWED_ORIGINS = ['https://tu-plataforma.com'];
```

## 2. Mensajes del anfitrión al motor

### `wiky:init`

Inicializa el motor. Debe enviarse después de recibir `wiky:ready`.

| Campo | Tipo | Descripción |
|---|---|---|
| `projectName` | string | Nombre del proyecto. Se usa en los nombres de archivo exportados. |
| `version` | string o número | Versión. También va en los nombres de archivo. |
| `caseType` | `prosthetic_bk` · `mask` · `cranial` | Tipo de diseño. Por defecto `mask`. |
| `legSide` | `left` · `right` | Solo prótesis. |
| `scanUrl` | string | URL del escaneo (.obj, .stl o .zip con obj+mtl+textura). El motor la descarga con `fetch`: el servidor debe permitir CORS. |
| `scanFile` | File o Blob | Alternativa a `scanUrl`: el archivo directamente (postMessage admite `File`). |
| `scanFiles` | File[] | Varios archivos a la vez (obj + mtl + imagen). |
| `projectUrl` | string | URL de un diseño `.mdproj` guardado antes. Tiene prioridad sobre el escaneo. |
| `projectFile` | File o Blob | Alternativa a `projectUrl`. |
| `inputs` | objeto `{ id: valor }` | Valores iniciales de campos de la interfaz, por su `id` (por ejemplo `{ "thickness": 3.5, "brim-split-enabled": true }`). |
| `tab` | string | Paso inicial: `scan`, `align`, `mesh`, `shell`, `flares`, `valve`, `lace`, `finishing`, `fabrication`. |
| `exportTimeoutMs` | número | Cuánto espera el motor la aprobación de una exportación. Por defecto 120000. |
| `projectId` | string | Identificador estable del diseño en tu plataforma. Lo usa el autoguardado (si no, se usa `projectName`). |
| `autosave` | `true` u objeto | Recibir copias autoguardadas del diseño mientras el usuario trabaja. Ver [Autoguardado](#6-autoguardado). |

Nombres de archivo resultantes: `<proyecto>_v<version>_<parte>.<ext>`, por ejemplo `Paciente-1234_v3_shell.stl`, `Paciente-1234_v3_borde.stl`, `Paciente-1234_v3_malla.stl`, `Paciente-1234_v3_diseno.mdproj`, `Paciente-1234_v3_informe.html`. Sin nombre de proyecto se usan los nombres genéricos (`shell.stl`, …).

### `wiky:export:approve`

Respuesta a `wiky:export:request`. Autoriza la exportación y dice cómo entregar los archivos.

| Campo | Descripción |
|---|---|
| `requestId` | El de la petición. Obligatorio. |
| `deliver` | `upload` (subir a una URL), `message` (recibir los bytes por mensaje) o `download` (descarga en el navegador del usuario). Si se omite: `upload` cuando hay URL, si no `message`. |
| `uploadUrl` | URL de subida común. Puede contener `{fileName}`, que se sustituye por el nombre de cada archivo. |
| `method` | `PUT` (por defecto) o `POST`. |
| `headers` | Cabeceras extra, por ejemplo `{ "Authorization": "Bearer …" }`. |
| `multipart` | Con `POST`, `true` envía el archivo como `multipart/form-data` en el campo `fieldName` (por defecto `file`). |
| `files` | Opcional, por archivo: `{ "<fileName o kind>": { url, method, headers, multipart } }`. Tiene prioridad sobre los campos globales. |

Con `upload`, el motor hace una petición por archivo con el cuerpo en binario y `Content-Type` según el tipo (`model/stl`, `application/octet-stream`, `text/html`). El servidor debe aceptar CORS desde el origen del motor.

### `wiky:export:reject`

`{ type: 'wiky:export:reject', requestId, reason }`. El motor cancela la exportación y se lo dice al usuario.

### `wiky:set` (opcional)

`{ type: 'wiky:set', inputs: { id: valor } }`. Cambia campos de la interfaz en caliente.

### `wiky:command` (opcional)

`{ type: 'wiky:command', command: 'build' | 'export' | 'save' }`. Construye la pieza, abre el flujo de exportación (con aprobación) o guarda el diseño.

### `wiky:ping`

El motor responde `wiky:pong`.

### `wiky:project:saved` / `wiky:project:save-error`

Confirmación de una copia autoguardada recibida por mensaje: `{ type: 'wiky:project:saved', projectId, hash }` cuando la has guardado, o `{ type: 'wiky:project:save-error', projectId, hash, message }` si no. Ver [Autoguardado](#6-autoguardado).

## 3. Mensajes del motor al anfitrión

| Tipo | Cuándo | Campos |
|---|---|---|
| `wiky:ready` | Al arrancar. A partir de aquí acepta `wiky:init`. | `caps` |
| `wiky:loaded` | Escaneo o diseño cargado. | `kind` (`scan`/`project`), `name`, `caseType`, `triangles` |
| `wiky:built` | Pieza construida por orden del anfitrión. | `triangles`, `brim` |
| `wiky:export:request` | El usuario ha pulsado Exportar. Se espera `approve` o `reject`. | `requestId`, `projectName`, `version`, `files: [{ kind, fileName, size, mime }]`, `validation` (si se exporta la pieza: `{ passed, total, warnings, failures, override: { reason } \| null, checks: [{ id, title, status: 'ok'\|'warn'\|'fail'\|'info', detail }] }`) |
| `wiky:export:file` | Solo con `deliver: 'message'`: un mensaje por archivo. | `requestId`, `kind`, `fileName`, `mime`, `size`, `bytes` (ArrayBuffer) |
| `wiky:export:done` | Todos los archivos entregados. | `requestId`, `results: [{ fileName, kind, ok, status, url }]` |
| `wiky:export:error` | Alguno falló. | `requestId`, `results` (con `error` en los fallidos) |
| `wiky:error` | Fallo en la inicialización. | `stage`, `message` |
| `wiky:pong` | Respuesta a `ping`. | |
| `wiky:project:autosave` | Copia autoguardada del diseño (solo si el `init` trae `autosave`). | `projectId`, `projectName`, `version`, `name`, `savedAt`, `hash`, `size`, `delivered` (`message`/`upload`), `bytes` (ArrayBuffer, solo en modo `message`) |

`kind` de los archivos: `shell` (socket o mascarilla), `brim` (borde separable, solo si está activado), `scan` (malla modificada), `project` (`.mdproj`), `report` (informe HTML listo para imprimir a PDF).

Todos los mensajes llevan además `protocol: 'wiky'` y `protocolVersion: 1`.

## 4. Flujo completo

1. El anfitrión carga el iframe y espera `wiky:ready`.
2. Envía `wiky:init` con nombre, versión y escaneo o diseño.
3. El motor responde `wiky:loaded`. El usuario trabaja en el motor.
4. El usuario pulsa Exportar. El motor prepara los archivos en memoria y envía `wiky:export:request` con la lista.
5. El anfitrión decide: `wiky:export:approve` con la forma de entrega, o `wiky:export:reject`.
6. El motor entrega los archivos y envía `wiky:export:done` (o `wiky:export:error`).

Mientras espera la aprobación el motor muestra "Esperando la aprobación de la plataforma para exportar…". Si no llega respuesta en `exportTimeoutMs`, la exportación se cancela con aviso.

## 5. Ejemplo de servidor de subida

Con `deliver: 'upload'` y `uploadUrl: 'https://api.tu-plataforma.com/disenos/1234/archivos/{fileName}'`, el motor hará, por cada archivo:

```
PUT /disenos/1234/archivos/Paciente-1234_v3_shell.stl
Content-Type: model/stl
Authorization: Bearer …          (si se pasó en headers)
<bytes del STL>
```

El servidor debe responder con `2xx` y aceptar CORS (`Access-Control-Allow-Origin` con el origen del motor y `Access-Control-Allow-Headers` con las cabeceras usadas).

## 6. Autoguardado

El motor guarda solo el diseño mientras el usuario trabaja: unos segundos después de cada cambio hace una copia en el navegador (OPFS + IndexedDB) y, si la plataforma lo pide, le envía también la copia. Esa es la copia "real": la del navegador puede borrarse (ventanas privadas, limpieza de datos, Safari a los 7 días sin uso).

Para recibirla, añade `autosave` al `wiky:init`:

| `autosave` | Qué hace |
|---|---|
| `true` | Modo `message`: cada copia llega en `wiky:project:autosave` con los `bytes` (un `.mdproj`, el mismo formato que la exportación). Guárdala y responde `wiky:project:saved` con el mismo `projectId` y `hash`. Sin respuesta en `ackTimeoutMs` (30 s) se considera no guardada y se reintenta. |
| `{ deliver: 'upload', uploadUrl, method, headers }` | El motor sube cada copia con `PUT` (o `method`) a `uploadUrl`, que puede llevar `{projectId}` y `{hash}`. Cabeceras: `X-Wiky-Hash`, `X-Wiky-Saved-At` más las tuyas. Una respuesta `2xx` cuenta como guardada; después se avisa con `wiky:project:autosave` (sin bytes). |
| `minIntervalMs` | Mínimo entre dos envíos (por defecto 30000). Al ocultar o cerrar la pestaña se envía en el momento. |
| `ackTimeoutMs` | Solo en modo `message`: cuánto espera la confirmación (por defecto 30000). |

Ejemplo:

```js
iframe.contentWindow.postMessage({ type: 'wiky:init', projectId: 'p-1234', projectName: 'Paciente-1234', version: 3,
  projectUrl: 'https://…/p-1234/ultimo.mdproj', autosave: true }, origin);

window.addEventListener('message', async (e) => {
  if (e.data?.type !== 'wiky:project:autosave') return;
  const { projectId, hash, bytes } = e.data;
  try {
    await guardarEnTuServidor(projectId, new Blob([bytes]));          // tu API
    e.source.postMessage({ type: 'wiky:project:saved', projectId, hash }, e.origin);
  } catch (err) {
    e.source.postMessage({ type: 'wiky:project:save-error', projectId, hash, message: err.message }, e.origin);
  }
});
```

- `hash` identifica el contenido: si llega uno que ya tienes, basta con confirmar.
- La próxima vez, manda la última copia en `projectUrl`/`projectFile`. Si en el equipo del usuario hay una copia más reciente que nunca te llegó (se cerró el navegador antes del envío), el motor le ofrece recuperarla.
- El usuario ve el estado arriba a la derecha: "Guardado en local", "Guardado en local · subiendo…", "Sincronizado en la nube".
