Ir al contenido
Empezar

Tu primer import, de extremo a extremo

La visión general te deja un motor sano. Esta página es el resto de las primeras dos horas: entra un archivo, alguien lo revisa, y filas firmadas llegan a tu backend.

No escribes una UI de mapeo ni declaras el esquema en el código anfitrión. El esquema es un catálogo publicado. El widget lo carga. Apply es un webhook firmado — ese es el predeterminado.

your page Mildport engine your backend
───────── ─────────────── ────────────
<mildport-import ingest → match → review
catalog-key="crm"> ─────────► ─────────► POST /apply
HMAC-signed rows

Necesitas dos valores, ambos de tu cuenta Mildport:

Qué Dónde Dónde va
Clave de licencia Licencias — una vez al emitir license-key del widget
Clave pública Self-hosting (IMPORT_LICENSE_PUBLIC_KEY) el motor, ya, si el preflight de la visión general pasó

Confirma que el motor acepta la clave:

Terminal window
curl -H "Authorization: Bearer $LICENSE" \
https://imports.your-infra.example/api/import/v1/license/verify

Una buena respuesta es "verified": true. Cualquier otra cosa — véase Solución de problemas de despliegue.

El esquema vive en el editor de catálogos/admin/catalogs en tu motor, o <mildport-catalog-editor> en tu propio admin. Redacta campos desde una especificación OpenAPI o un registro de ejemplo, marca como required los que tu API rechazaría vacíos, y publica. Dale al catálogo una clave estable (crm, contacts, …).

Fija esa clave en el widget. Los cambios de esquema después son un publish en el editor, no un release del anfitrión:

<mildport-import
api-base-url="https://imports.your-infra.example"
license-key="SIGNED_TENANT_KEY"
catalog-key="crm"
></mildport-import>

Un catálogo publicado y sin catalog-key también vale — el widget lo adopta solo. Fija la clave cuando una página deba importar siempre a un esquema concreto.

¿Esquema calculado por usuario en tiempo de ejecución, o versionado con tu app? Ese es el camino esquema-como-código. Sáltalo hasta que tengas un motivo.

Apply es por defecto un POST firmado a tu backend. Registra la URL una vez; la respuesta lleva el secreto de firma exactamente una vez — guárdalo.

Terminal window
curl -X POST https://imports.your-infra.example/api/import/v1/webhooks \
-H "Authorization: Bearer $LICENSE" \
-H "Content-Type: application/json" \
-d '{ "url": "https://api.your-app.example/mildport/apply" }'

¿Una primera mirada sin backend? el.applyMode = 'browser' y lee filas de onResults. Producción es el webhook. Detalles: Eventos y apply y la referencia de webhooks.

Abre la página, suelta un CSV (o una hoja desordenada, o un PDF con una tabla), confirma el mapeo sugerido, corrige lo que marque la cuadrícula de revisión, y apply.

El widget habla con tu motor para ingest y mapeo. No llamas ingest tú mismo salvo que construyas un flujo headless, sin widget.

Cada entrega va firmada HMAC. Verifica el cuerpo crudo antes de fiarte, rechaza marcas de tiempo viejas, y deduplica en x-import-delivery-id — los reintentos son normales.

import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const parts = Object.fromEntries(header.split(',').map(kv => kv.split('=')));
const expected = createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const got = Buffer.from(parts.v1 ?? '', 'hex');
const exp = Buffer.from(expected, 'hex');
return got.length === exp.length && timingSafeEqual(got, exp);
}

El cuerpo son filas con la forma de tu catálogo (contact.email, …) más un mapping. Devuelve 2xx cuando las hayas persistido. Un 4xx es terminal; un 5xx se reintenta. Payload y cabeceras: Webhooks de apply.

  • El widget disparó import-applied con un rowCount.
  • Tu handler registró un deliveryId y escribió esas filas.
  • Una segunda entrega con el mismo id es un no-op de tu lado.

¿Atascado? Solución de problemas de despliegue cubre imágenes que no hacen pull, 401 de licencia, CORS, y un pod que nunca pasa a Ready.

Siguiente: Configuración · Licencias · Catálogos