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 rows1. Claves de tu cuenta
Sección titulada «1. Claves de tu cuenta»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:
curl -H "Authorization: Bearer $LICENSE" \ https://imports.your-infra.example/api/import/v1/license/verifyUna buena respuesta es "verified": true. Cualquier otra cosa — véase
Solución de problemas de despliegue.
2. Publica un catálogo y luego incrústalo
Sección titulada «2. Publica un catálogo y luego incrústalo»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.
3. Registrar el webhook de apply
Sección titulada «3. Registrar el webhook de apply»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.
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.
4. Ejecutar un import
Sección titulada «4. Ejecutar un import»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.
5. Verificar la firma y luego escribir
Sección titulada «5. Verificar la firma y luego escribir»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.
6. Confirmar que funcionó
Sección titulada «6. Confirmar que funcionó»- El widget disparó
import-appliedcon unrowCount. - Tu handler registró un
deliveryIdy 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