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.
- Abre esa carpeta como proyecto AL en VS Code con AL Language. Comprueba que el rango de objetos no colisiona con las extensiones destino.
- 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. - Descarga símbolos con AL: Download Symbols. Ajusta plataforma, aplicación y runtime a la versión destino si es necesario.
- Ejecuta AL: Package y publica en el sandbox. El proyecto aporta fuentes, no un
.appde check-in compilado y validado. - Asigna
BALTOYA CHECKIN OPSa 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
- Registra una aplicación en Microsoft Entra ID del tenant de destino y anota tenant ID y client ID.
- 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.
- En BC, abre Microsoft Entra Applications, registra el mismo client ID y actívalo. Asigna BALTOYA CHECKIN API únicamente en la empresa destino.
- Obtén el GUID de empresa mediante la API
companiescon una identidad administradora autorizada. No uses el nombre visible comocompanyId.
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.