Saltar a contenido

Integración de check-in con Business Central

Hay dos integraciones distintas: BC provisiona una invitación y envía el correo; después, el servicio entrega los huéspedes a una bandeja de BC. Las credenciales y confirmaciones de cada dirección son diferentes.

Dirección Responsabilidad Estado de las fuentes
BC → servicio Crear invitación con referencia estable, cupo y caducidad; guardar token y enviar correo/QR API disponible; falta integrarla con la reserva y correo reales del camping
Servicio → BC Entregar recibo y datos a la API de bandeja Worker y receptor AL genérico disponibles; pendiente compilación y validación en sandbox
Bandeja BC → negocio Crear/actualizar persona y asociarla a reserva/estancia Falta el adapter de las tablas reales del camping

1. Preparar la reserva y el correo

Desde la extensión que crea la reserva, llama a provisionar una reserva con X-Checkin-Admin-Token. Elige como reservationReference una clave única y estable dentro del establecimiento, no un número reutilizable.

Guarda de forma segura el reservationId, token, referencia, cupo y caducidad devueltos por el 201. El enlace se compone con el dominio de la web y el formato /?site=las-torres#token=<token>. BC es el responsable de enviar el correo y producir su QR; ninguna de esas acciones la ejecuta el worker de recepción de huéspedes.

El evento de reserva, tabla/campos de almacenamiento, plantilla y disparador de correo deben integrarse en la extensión real. No hay código AL listo para copiar que resuelva ese mapeo de negocio. La API no recupera un token perdido, no rota/revoca invitaciones ni actualiza reservas existentes. Un 409 reservation_exists no debe provocar otra provisión con una referencia distinta.

2. Compilar el receptor independiente en sandbox

El proyecto está en business-central/checkin/. Su manifiesto apunta a BC 24, runtime 13 y Cloud, con rango propuesto 50200–50249 y app ID b61c1b30-1303-4d28-8dcc-5e0dc42f37fc. No usa los permisos ni los objetos de certificados de la firma PDF.

  1. Abre esa carpeta como proyecto AL en VS Code con AL Language. Comprueba que el rango de objetos no colisiona con las extensiones destino.
  2. Crea la configuración local de sandbox en .vscode/launch.json, con tenant, environmentName, environmentType: "Sandbox" y página inicial 50201. Mantén credenciales y ajustes locales fuera del repositorio.
  3. Descarga símbolos con AL: Download Symbols. Ajusta plataforma, aplicación y runtime a la versión destino si es necesario.
  4. Ejecuta AL: Package y publica en el sandbox. El proyecto aporta fuentes, no un .app de check-in compilado y validado.
  5. Asigna BALTOYA CHECKIN OPS a los operadores autorizados para esa empresa. Busca Guest Check-in Inbox.

La página API se expone al instalar la extensión; no se publica como SOAP ni necesita una entrada manual en Web Services. Usa la ruta propia indicada abajo. La versión AL y los pasos concretos deben validarse con el tenant destino.

3. Autorizar al servicio con Entra

  1. Registra una aplicación en Microsoft Entra ID del tenant de destino y anota tenant ID y client ID.
  2. Concede a esa aplicación el permiso de aplicación Dynamics 365 Business Central / API.ReadWrite.All y consentimiento administrativo. Crea una credencial y conserva su valor en un gestor de secretos del backend.
  3. En BC, abre Microsoft Entra Applications, registra el mismo client ID y actívalo. Asigna BALTOYA CHECKIN API únicamente en la empresa destino.
  4. Obtén el GUID de empresa mediante la API companies con una identidad administradora autorizada. No uses el nombre visible como companyId.

BALTOYA CHECKIN API concede lectura/inserción sobre la bandeja y ejecución de la página API y del cálculo criptográfico. No concede modificación/borrado ni permisos de las tablas de personas del camping. BALTOYA CHECKIN OPS concede lectura/modificación de bandeja y ejecución de página/importador; el operador necesitará además los permisos mínimos que exija el adapter real. No requiere SUPER ni permisos de firma PDF.

La configuración del servicio usa una ruta por establecimiento, dentro de CHECKIN_BC_ROUTES_JSON:

[
  {
    "site": "las-torres",
    "tenantId": "00000000-0000-0000-0000-000000000001",
    "environment": "Sandbox",
    "companyId": "00000000-0000-0000-0000-000000000002",
    "clientId": "00000000-0000-0000-0000-000000000003",
    "clientSecret": "<valor-del-secreto>"
  }
]

Todos los GUID son marcadores. Configura el JSON real como secreto en una sola variable del backend, junto con CHECKIN_BC_ENABLED=true; sigue la secuencia de despliegue. La ruta exige sitio autorizado, GUIDs no vacíos, secreto y nombre simple de entorno (letras/dígitos/guiones/guion bajo, hasta 80 caracteres). No admite duplicados de sitio. Un JSON inválido o habilitar BC sin ninguna ruta válida impide arrancar.

Puedes rotar client ID/secreto manteniendo la misma empresa. Cambiar tenant, entorno o empresa para recibos ya intentados requiere la recuperación por cambio de destino.

4. Contrato del receptor BC

El worker usa credenciales de cliente y estos hosts públicos fijos, sin redirecciones:

POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
grant_type=client_credentials
scope=https://api.businesscentral.dynamics.com/.default

POST https://api.businesscentral.dynamics.com/v2.0/{tenantId}/{environment}/api/baltoya/checkin/v1.0/companies({companyId})/guestCheckins
Authorization: Bearer {access_token}
Content-Type: application/json

El token OAuth se obtiene con client_id y client_secret. Esto no usa BC_BEARER_TOKEN: ese bearer es de BC hacia los endpoints de firmas. Tampoco usa el secreto administrativo de check-in. El adapter actual apunta a BC Online en la nube pública Microsoft; no acepta una URL arbitraria ni cubre on-premises o nubes soberanas.

El cuerpo de inserción es un registro plano:

Campo Significado
externalReceiptId UUID del recibo guardado por el servicio
site Establecimiento de origen
payloadVersion 1
payloadHash SHA-256, hexadecimal en mayúsculas, de los bytes UTF-8 exactos de payloadJson
payloadJson Cadena JSON, no objeto anidado, con el sobre de negocio

El sobre decodificado contiene mode, reservationReference, createdAt y guest. Los campos de guest son los de Datos del huésped, ya normalizados; las fechas opcionales ausentes son null. reservationReference es la referencia original con la que BC provisionó la reserva, o null en recepción. No inventes una reserva para registros de recepción.

La bandeja guarda el texto completo en un Blob UTF-8 y verifica su hash. No reserialices ni reformatees el texto antes de comprobarlo. externalReceiptId es único por empresa BC; el OData key es el id/SystemId propio de BC. El estado de importación lo controla el servidor, y la API no permite PATCH/DELETE.

El worker solo reconoce una recepción cuando coinciden recibo, sitio, versión, hash, SystemId y estado válido. Tras un timeout conserva el recibo y programa otro intento; mientras no conozca el SystemId puede repetir el POST. Si BC responde con un posible duplicado (400/409/412), o el SystemId ya es conocido, busca el registro confirmado:

GET {guestCheckins-url}?$filter=externalReceiptId eq {receipt-guid}&$select=id,externalReceiptId,site,payloadVersion,payloadHash,importStatus&$top=2

Debe encontrar exactamente un resultado coincidente. Un HTTP 2xx cualquiera, HTML del proxy o un error de duplicado no prueban recepción. Los recibos conocidos se consultan de nuevo sin volver a insertarlos.

El detalle literal del sobre y el ejemplo completo están en business-central/checkin/README.md; los esquemas de transporte siguen documentados en openapi.yaml y docs/API_CONTRACT.md.

5. Completar el adapter del camping

El evento Baltoya Check-in Import.OnImportGuest recibe ExternalReceiptId, Site, PayloadJson, Handled y BusinessReference. El ejemplo business-central/checkin/examples/LasTorresCheckInAdapter.al.example está excluido de compilación y falla intencionadamente hasta implementar el mapeo. Cópialo y adáptalo en la extensión que conoce las tablas reales; no publiques el ejemplo como si ya importase huéspedes.

El suscriptor debe validar el sitio, deserializar el sobre, encontrar la reserva/estancia correcta, aplicar las reglas reales de coincidencia de persona, escribir sus datos y relación con la estancia, y solo entonces marcar Handled := true y devolver la referencia de negocio.

Campo del servicio Decisión necesaria en la extensión real
reservationReference Tabla/clave de reserva estable y asociación permitida al establecimiento
Nombres y domicilio Campos y longitudes reales; rechazar desbordamientos, no truncar silenciosamente
birthDate, holderRelationship Campos de nacimiento y relación al titular; edad derivada
sex Conversión explícita de male/female/unspecified al enum real
nationality, documentCountry, country Catálogo BC para nacionalidad, emisor y residencia; no confundirlos ni deducir códigos de tres letras
documentType Conversión de dni/passport/nie a las tres opciones reales
Documento, soporte y fechas Campos exactos, validación y reglas para menores/documentos extranjeros
phone, email Datos de contacto y reglas de negocio
ExternalReceiptId Clave de operación importada; una persona puede tener varias estancias

El importador abre una transacción de escritura y bloquea la bandeja. Las escrituras del adapter y el estado Imported se confirman juntos. Un error revierte las escrituras de negocio; la acción de operador registra business_import_failed. No ejecutes Commit, un Codeunit.Run con resultado Boolean anidado, correo ni efectos externos irreversibles dentro del suscriptor.

Sin suscriptor, Import / Retry conserva Pending y establece adapter_not_configured. El estado Imported no se obtiene automáticamente al insertar en la API.

6. Verificar y operar

En Guest Check-in Inbox, selecciona el recibo y ejecuta Import / Retry después de instalar/corregir el adapter. Verifica persona, asociación de estancia y referencia de negocio, además del estado. El servicio consulta los recibos de bandeja pendientes cada cinco minutos y muestra inbox_received, import_error o imported; una comprobación administrativa puede adelantar la consulta.

Antes de usar datos reales, prueba en sandbox: primera entrega, duplicado idéntico/conflictivo, texto con acentos y más de 2048 caracteres, campos inválidos, PATCH/DELETE prohibidos, aislamiento de empresa, interrupción/reinicio, 429/Retry-After, falta de adapter, importación completa y rollback de un error. Verifica también reintentos simultáneos, rechazo de commits del adapter y observación del estado final desde el servicio.

Estas verificaciones de runtime BC siguen pendientes. La compilación o las pruebas locales del backend no sustituyen esas pruebas ni validan el mapeo de las tablas del camping. Para incidencias concretas usa Estados y operación.