Adapter que encapsula la integración con la plataforma de entregas de última milla (Bringoz, con Nexus como alternativa) detrás de una API interna consumida por el CSB vía Cloud Tasks, y que convierte los callbacks del proveedor en eventos de Pub/Sub.
NestJS 11 · Node 20Bringoz (default)Nexus (feature flag)Bringoz QAS cableado (6 endpoints)Nexus en stubCloud Run · puerto 8080env: —v—
Expone crear / consultar / cancelar orden de entrega con un patrón Strategy por proveedor, recibe webhooks de Bringoz y publica el estado resultante en Pub/Sub. Toda petición lleva X-Correlation-ID (recibido o generado) y queda registrada con método, ruta, código y duración.
Qué falta
El provider de Nexus (sigue en stub), la consulta de estado de orden en Bringoz (track, no cubierta por la doc de Bebbia), la publicación en Pub/Sub tras create-order, la validación de firma de los webhooks y la cola de Cloud Tasks del lado del CSB. Ver pendientes.
El CSB encola una tarea de Cloud Tasks hacia POST /delivery/create-order firmada con la SA csb-dlv-adapter-<env>-tasks-sa (única con run.invoker).
El adapter selecciona el proveedor activo (DELIVERY_PROVIDER) y responde con el DeliveryResult en la misma llamada.
Cuando el proveedor cambia el estado de la entrega, llama a POST /webhooks/bringoz; el adapter publica el resultado en delivery.completed.v1.
El CSB consume la suscripción y continúa el flujo de negocio.
Webhooks e ingress interno
Con ingress INTERNAL_LOAD_BALANCER, Bringoz no puede alcanzar /webhooks/bringoz desde internet. Hará falta exponer esa ruta por un balanceador externo (Cloud Armor + NEG serverless, como hace el CSB) y validar la firma del webhook antes de abrirla.
Módulos internos
Módulo
Responsabilidad
Archivos clave
DeliveryModule
Endpoints /delivery/*; DeliveryService elige el proveedor al arrancar y delega.
Implementan DeliveryProvider (createOrder, trackOrder, cancelOrder) y, Bringoz, también SchedulingProvider (depots, cotización, polling, dispatch). Bringoz llama a sandbox.bringoz.com vía BringozClient; Nexus sigue en stub.
loadDeliveryConfig() dual-mode env / Secret Manager; parseProvider() normaliza el flag.
config/app.config.ts
Bootstrap (main.ts): ValidationPipe global (transform, whitelist), GlobalExceptionFilter, CORS restringido a CORS_ORIGINS con métodos GET/POST y headers Content-Type, X-Correlation-ID, Authorization.
Flujos
A · Crear orden (Cloud Tasks → proveedor)
Cloud Tasks → POST /delivery/create-orderCuerpo DeliveryPayloadDto. El middleware fija X-Correlation-ID (del header o UUID nuevo) y lo devuelve en la respuesta.
Validaciónenvelope.id y envelope.data obligatorios; el resto opcional. Campos desconocidos se descartan.
Provider activoDeliveryService resolvió el proveedor al arrancar (provider_selected). createOrder(envelope.data).
Respuesta 200{ status: "ok", provider, result }. Con Bringoz: result.status es accepted (2xx de Bringoz) o rejected (4xx, con el body de Bringoz en raw); 5xx agotados tras los reintentos → 500. No se publica en Pub/Sub en este flujo.
B · Crear orden desde un push de Pub/Sub
Pub/Sub → POST /delivery/eventsPush subscription con OIDC de sa-csb-api-<env> (tiene run.invoker sobre el servicio). El envelope CloudEvents v1.0 viaja en message.data (base64).
Decodificación y validacióndecodeEventEnvelope(): base64 → JSON → EventEnvelopeDto (id, type, source, specversion y data obligatorios). Cualquier defecto → 400.
Filtro por tipoSolo delivery.order.requested.v1 y order.placed.v1 (ORDER_EVENT_TYPES). Otro tipo → 400: Pub/Sub no reintenta un 4xx, así que un mensaje irrecuperable no se queda en bucle.
Mismo camino que create-ordertaskMetadata sintético — taskName = message.messageId, retryCount 0, queueName = subscription. Respuesta 204 sin cuerpo (ack); un 500 sí se reintenta hasta el ack deadline.
C · Webhook de Bringoz → Pub/Sub
Bringoz → POST /webhooks/bringozWebhookEventDto: eventType y orderId obligatorios.
{
"message": {
// EventEnvelope (CloudEvents v1.0) serializado como JSON y en base64"data": "eyJpZCI6ImExYjJjM2Q0LWU1ZjYtNzg5MC1hYmNkLWVmMTIzNDU2Nzg5MCIsIC4uLn0=",
"messageId": "11209384756102",
"publishTime": "2026-09-11T18:20:00Z"
},
"subscription": "projects/rtp-transversal-dev/subscriptions/csb-delivery-orders-sub"
}
// message.data decodificado — EventEnvelopeDto:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "delivery.order.requested.v1",
"source": "//bebbia/order-service",
"specversion": "1.0",
"time": "2026-09-11T18:19:58Z",
"entityid": "FC-1001",
"data": { … mismo body Bringoz que envelope.data de create-order … }
}
Desenvuelve el push, valida el envelope y — si type es delivery.order.requested.v1 u order.placed.v1 — lo procesa igual que create-order, sintetizando taskMetadata (taskName = messageId, retryCount 0, queueName = subscription).
204 sin cuerpo400 envelope inválido o type no soportado500 error del proveedor
Todo lo irrecuperable responde 400 a propósito: Pub/Sub no reintenta un 4xx. Un 500 sí se reintenta hasta el ack deadline de la suscripción.
{
"status": "ok",
"provider": "bringoz",
"result": {
"orderId": "FC-1001-Installation",
"status": "accepted",
"providerRef": "FC-1001-Installation",
"raw": { … respuesta de Bringoz … }
}
// 4xx de Bringoz → status "rejected", providerRef "" y su body en raw (sigue siendo HTTP 200)
}
200400 envelope inválido500 error del proveedor
GET/delivery/track/:orderIdsin endpoint en la docCSB
La doc "Conexión Bringoz QAS" no incluye consulta de orden por externalId (el cierre llega por webhook). Devuelve { status: "ok", result: { orderId, status: "pending", providerRef: orderId } } sin llamar a Bringoz hasta confirmar el endpoint.
Cancela una orden en Bringoz (compensación). :orderId es el externalId{IDFluent}-{Kindflag}. Cuerpo opcional { "reason": "Cancelacion-Flow_failed" }; reason y description van al body de Bringoz. Devuelve { status: "ok", result: { orderId, status: "cancelled", providerRef: orderId, raw } }.
Depots candidatos del área. :orderId = {IDFluent}-{Kindflag}, :lineId = IDFluent. Body opcional { "taskPurpose": "Dropoff", "zoneId": "America/Mexico_City" }. Estrategia LOCATION_DEPOT_IN_OPERATIONAL_UNIT. Se filtran como Bebbia: areaIds no vacío y externalId de 8 caracteres (el locationRef con el que se consulta inventario en Fluent). Devuelve { status, provider, depots: [{ id, externalId, zoneId, raw }] }.
Asigna el slot elegido (optionId de la cotización). Sin body. Devuelve { status, provider, result: { orderId, optionId, status: "assigned" | "rejected", providerRef: orderExternalId, raw } }. Un 404 de Bringoz (HTTP o embebido en un 200 con "status":404) se devuelve como rejected para que el CSB vuelva a cotizar y revierta suscripción/mantenimiento.
POST/webhooks/bringozpublica en Pub/SubBringoz (callback)
status: "ok" si el proveedor activo tiene host configurado, "not_configured" si no. Siempre HTTP 200. Terraform apunta ambos probes a /health, no a esta ruta.
Portal, Swagger UI (assets desde cdnjs), OpenAPI 3.1 y guía de despliegue. GET / redirige a /docs. Activos fuera de production; DOCS_ENABLED fuerza el estado.
Contratos y payloads
DeliveryPayloadDto
Campo
Tipo
Req.
Descripción
envelope.id
string
sí
ID del evento; se registra como event_id.
envelope.entityId
string
no
Entidad de negocio (ordering key en el CSB).
envelope.correlationId
string
no
Correlación de negocio; el header X-Correlation-ID es independiente.
envelope.data
object
sí
Body de Bringoz ya mapeado por el CSB: externalId ({IDFluent}-{Kindflag}), fulfillmentLines, itemLines, metaData, tags. accountId se completa desde BRINGOZ_ACCOUNT_ID si no viene. El orderId de la respuesta es el externalId.
taskMetadata.queueName
string
no (sí si se envía taskMetadata)
Nombre de la cola.
taskMetadata.retryCount
number
no
Intentos previos.
Diferencia con los otros adapters
CRM y ERP exigen un envelope CloudEvents completo (source, type, specversion, entityid) y taskMetadata.retryCount. Aquí el envelope es mínimo y taskMetadata es opcional. Al publicar @rtp-csb/contracts conviene alinear los tres.
400DTO inválido. El detalle de campos no viaja en la respuesta (solo error: "Bad Request Exception"); está en el log unhandled_exception.
500Excepción del proveedor o de Pub/Sub. Con los stubs actuales solo puede ocurrir en /webhooks/bringoz (publicación).
200Operación aceptada; el estado de negocio va en result.status.
La política de reintentos la define la cola de Cloud Tasks del CSB (pendiente de crear). Los webhooks dependen de la política de reintento de Bringoz. El adapter no deduplica: un webhook repetido publica dos mensajes.
Integración con el CSB: cómo lo consume el bus
De extremo a extremo: qué sistema dispara, por qué puerta entra al Core Service Bus, cómo llega hoy el bus a Bringoz (directo, sin este adapter), qué ya está cableado hacia el adapter, qué falta para que los flujos pasen por él y cómo la tienda o Data Mesh usan el resultado. Fuente: rtp-csb en dev (seed, seed-data/*, infra/envs/dev) al 10 sep 2026.
Mapa de extremo a extremo
Situación real: Bringoz es hoy un nodo external-api de los pipelines Bebbia. Este adapter está desplegado, publicado en el balanceador y cableado por remote state en el CSB, pero ningún flujo lo llama y sus providers son stubs. Esta página documenta ambos estados para que la migración sea un cambio de flujo, no un rediseño.
Outputs del adapterrtp-delivery-adapter/infra/terraform/environments/dev expone service_url, service_account_email, cloud_tasks_invoker_email (csb-dlv-adapter-dev-tasks-sa), cloud_tasks_queue y result_topic (commit 63e28d4). Estado en gs://rtp-delivery-adapter-dev-tf-state/terraform/state.
Remote state en el CSBdata "terraform_remote_state" "delivery_adapter" → local.adapters.delivery = { url, queue = null, tasks_sa, result_topic = null } (commit 40352ce). Esos null ya son corregibles: el adapter expone cloud_tasks_queue y result_topic; falta que el CSB los lea del remote state.
Variable de entornoDELIVERY_ADAPTER_URL llega a csb-api y al job de seed, pero el seed no la lee todavía.
Identidadsa-csb-api-dev@… tiene iam.serviceAccountUser sobre csb-dlv-adapter-dev-tasks-sa, que a su vez tiene run.invoker en el adapter. csb-api ya puede encolar tareas con OIDC hacia /delivery/*.
RegistryEl seed registra la ApiDefinition Bringoz Logistics v2 (proveedor externo, no el adapter): POST /auth/token, POST /v2/oms/depots, PUT /v2/oms/orders-v2, GET /v2/oms/orders/{orderId}, GET …/options, POST …/dispatch/{slotId}; API key x-api-key desde el secreto csb-dev-bringoz-credential. No hay Adapter ni Connector de delivery.
ResultadoEl adapter publica en PUBSUB_RESULT_TOPIC solo desde /webhooks/bringoz. El topic ya lo declara este repo (Terraform) y se expone como output result_topic; falta la suscripción del lado del CSB.
Token y API key de Bringoz solo en el adapter (Secret Manager del adapter); el CSB usa OIDC.
Un DeliveryResult normalizado para Bringoz y Nexus; cambiar de proveedor es un secreto, no un flujo.
Los webhooks de Bringoz entran por /webhooks/bringoz y salen como evento Pub/Sub, en lugar de que Bringoz firme HMAC contra un flujo concreto.
Cancelación y tracking (/delivery/cancel/:id, /delivery/track/:id) como pasos reutilizables en cualquier flujo.
Costo: implementar BringozProvider real, publicar el resultado en create-order, y exponer la cola y el topic en los outputs del adapter para que el remote state del CSB los lea.
Qué pasa con el resultado
Hoy
Los flujos publican su propio resultado (bebbia.work.order.created.v1, bebbia.installation.times.*, inventory.asset.status.validated.v1) y los consumers del CSB (audit, notification) o el backend Bebbia los leen. Bringoz reporta cambios de estado con el WebHook HMAC del flujo CSB-01.
Vía adapter
/webhooks/bringoz convierte cada callback en un mensaje JSON en delivery.completed.v1 (eventId, result.status ∈ accepted · rejected · pending · delivered · cancelled, atributos webhookEvent, status). El CSB necesitaría el topic en Terraform, una suscripción (csb-subscription) y un flujo disparado por ese topic (por ejemplo CSB-01 con origin: "BRINGOZ").
Cómo lo consume un externo (tienda Bebbia, Data Mesh, Portal)
Necesidad del externo
Patrón en el CSB
Ejemplo vivo
Pasos
La tienda quiere que una orden pagada termine agendada con un instalador
Publicar bebbia.order.paid.v1; la cadena de orquestadores hace Fluent + Bringoz + mantenimiento + SAP en paralelo
crear-orden-trabajo-bebbia (4 orquestadores encadenados por topics)
pubsub.publisher, cumplir el JSON Schema (order_number, store_key, payment_id)
La tienda quiere horarios y elegir uno con respuesta inmediata
API trigger: ejecutar el flujo con su inputTemplate y leer el resultado síncrono
Terraform sobre delivery.completed.v1 (cuando exista) o sobre bebbia.installation.times.assigned.v1
Cuándo · para qué · por qué
Cuándo (evento)
Para qué (resultado)
Por qué pasa por el CSB (y por qué convendría el adapter)
Orden pagada y validada
Orden de campo en Bringoz ligada a la orden Fluent, mantenimiento y suscripción
Logística y finanzas en paralelo con compensación por etapa; un fallo de Bringoz cancela solo la orden de campo. El adapter aislaría credenciales y el formato Bringoz del grafo
El cliente elige horario
Slot despachado y confirmado por correo
Reserva de equipo antes del dispatch y rollback declarado si el slot ya no está; el adapter daría track/cancel reutilizables
Bringoz cierra la orden
Activo en casa del cliente en SAP ECC, Data Mesh y Portal
Se verifica el cierre en Bringoz antes de dispersar; idempotencia por eventId; cada destino falla por separado
Paso a paso: conectar este adapter en el CSB
Leer los outputs del adaptercloud_tasks_queue y result_topic ya salen de infra/terraform/environments/<env>/outputs.tf (hecho del lado del adapter). Falta en el CSB mapear el remote state a local.adapters.delivery.queue/result_topic en vez de null.
Proveedor realHecho: BringozProvider cableado contra QAS con Basic estático (sin /auth/token) — PUT /v2/oms/orders-v2, cancelación, depots, cotización, polling y dispatch. Falta publicar DeliveryResult en Pub/Sub también tras create-order y confirmar el endpoint de consulta (track).
Sembrar el registryEn seed.ts: DELIVERY: process.env.DELIVERY_ADAPTER_URL en pipelineBases, ApiDefinition Delivery Adapter (Cloud Run) con /delivery/create-order, /delivery/events, /delivery/cancel/{orderId} y las rutas de agendado /delivery/{orderId}/lines/{lineId}/…, Connector bringoz-delivery-connector y Adapter csb-delivery-adapter (cloudRunUrl = DELIVERY_ADAPTER_URL, probe /health).
Identidad de los workflowscsb-api ya puede firmar como csb-dlv-adapter-dev-tasks-sa; las SAs de Cloud Workflows necesitan run.invoker en csb-delivery-adapter-dev-api.
Cambiar el paso del flujoEn crear-orden-trabajo-bebbia reemplazar el try Bringoz por {{DELIVERY}}/delivery/create-order con auth: OIDC, compensación {{DELIVERY}}/delivery/cancel/{orderId}; ajustar condiciones a body.result.status.
Cerrar el ciclo con los webhooksPublicar /webhooks/bringoz por el balanceador (ruta solo para ese prefijo, firma de Bringoz), crear en el CSB la suscripción a delivery.completed.v1 y disparar CSB-01 desde ahí.
Deploy, ejecución y observaciónPreview GCP → Deploy → POST /api/provisioning/flows/{id}/execute con el inputTemplate; ver /monitoring y en el adapter create_order_received → create_order_completed con el X-Correlation-ID.
Brechas entre lo que el CSB necesita y lo que el adapter expone
deudaEl CSB modela Bringoz distinto a como es. El seed y mock-bringoz-api asumen x-api-key → POST /auth/token → Bearer y rutas /v2/oms/depots, /v2/oms/times. La doc "Conexión Bringoz QAS" (Bebbia, 10 sep 2026) fija Basic estático, x-bringoz-tenant-id, siteId en query y las rutas orders-v2/…/options/locations|summary. Este adapter ya sigue la doc; el CSB debe migrar sus flujos a él o corregir su ApiDefinition y mock.
bloqueanteSin registro en el CSB. No hay Adapter, Connector ni ApiDefinition del adapter en el seed; DELIVERY_ADAPTER_URL llega a csb-api pero nadie la usa.
resueltoOutputs de cola y topic. Terraform ya declara la cola csb-delivery-adapter-<env>-queue y el topic de resultados, y los expone como cloud_tasks_queue / result_topic (63e28d4). Pendiente del lado del CSB: consumirlos por remote state en vez de dejarlos en null.
deudaContrato de envelope distinto por Cloud Tasks.DeliveryPayloadDto (id, data, taskMetadata opcional) no coincide con el SyncPayloadDto CloudEvents de CRM/ERP ni con @rtp-csb/contracts. Por Pub/Sub ya no aplica: POST /delivery/events acepta el envelope CloudEvents v1.0 tal cual (EventEnvelopeDto).
deudaWebhooks./webhooks/bringoz no valida firma y el servicio tiene ingress interno: Bringoz no puede llegar. Mientras tanto Bringoz usa el WebHook HMAC del flujo CSB-01.
deudaPush a /events: falta el prefijo. El adapter ya expone POST /delivery/events (envelope CloudEvents, 204/400), pero un edge topic→adapter en el editor sigue provisionando la suscripción push a <cloudRunUrl>/events. Alinear el editor a /delivery/events (o montar un alias) y usar OIDC con sa-csb-api-<env>, que ya tiene run.invoker.
hechoCloud Run desplegado con VPC y monitoring, documentación pública, remote state en el CSB con URL y SA de tasks, actAs de csb-api sobre la SA de tasks, pipelines Bebbia funcionando contra Bringoz directo con compensaciones.
Configuración
loadDeliveryConfig() lee todo de variables de entorno con USE_SECRET_MANAGER=false; con true lee los secretos csb-<env>-* por API (los de Nexus con tolerancia a fallo). Terraform además monta seis de esos secretos (los cinco de Bringoz y delivery-provider) como variables de entorno vía secret_key_ref, así que en Cloud Run los valores llegan por dos caminos; gana la lectura por API.
Requeridos con Secret Manager (fallan el arranque si no existen). QAS: host https://sandbox.bringoz.com, tenant rotoplas; BRINGOZ_BASIC_AUTH es base64(id:secret) (el prefijo Basic es opcional, se normaliza), estático (sin OAuth ni refresh).
BRINGOZ_ZONE_ID / BRINGOZ_TIMEOUT_MS
—
America/Mexico_City / 30000
Zona horaria por defecto (zoneId de depots y cotización) y timeout por request.
NEXUS_HOST / NEXUS_API_KEY / NEXUS_CLIENT_ID
nexus-host, nexus-api-key, nexus-client-id
—
Opcionales.
PUBSUB_RESULT_TOPIC
— (ya no sale de un secreto)
delivery.completed.v1
Topic de resultados. Terraform lo declara (google_pubsub_topic.result, variable result_topic_name) e inyecta su nombre como env var plana. El secreto csb-<env>-pubsub-result-topic se sigue creando pero ya no se usa.
GOOGLE_CLOUD_PROJECT
—
—
Proyecto para Secret Manager y Pub/Sub.
USE_SECRET_MANAGER
—
false
true en Cloud Run.
CORS_ORIGINS
—
vacío (ninguno)
Lista separada por comas.
PORT / NODE_ENV
—
8080 / development
Terraform: dev, qa, o production para prd.
DOCS_ENABLED
—
auto
true/false fuerza el portal. Sin definir: activo salvo production.
Sufijo de ambiente
Terraform usa el ambiente prd, pero el código deriva el sufijo de secretos de NODE_ENV: production → prod. En producción los secretos deberán llamarse csb-prod-* aunque el resto de la infra use prd.
Infraestructura (Terraform)
Módulos networking, secrets, compute y monitoring en infra/terraform/modules, con ambientes dev, qa y prd. Es el único adapter con VPC propia y alertas declaradas.
terraformImporta los secretos existentes al state si faltan, luego plan + apply con image_tag=$SHORT_SHA. Aquí Terraform sí actualiza la imagen del servicio (no hay paso --no-traffic: la revisión nueva recibe tráfico directamente).
health-checkComprueba que latestCreatedRevisionName == latestReadyRevisionName vía Admin API.
Documentación en cada deploysrc/docs/public/ (portal, guía, Swagger UI, openapi.json) se copia a dist/docs/public/ en nest build y viaja en la imagen. Un commit a dev publica la documentación con la siguiente revisión.
Las alertas de 5xx, latencia p95 e instancias y el uptime check están en el módulo monitoring.
Runbook
La revisión no queda Ready
Faltan secretos bringoz-* o delivery-provider: loadDeliveryConfig lanza al arrancar. Crear versiones (bash scripts/bootstrap.sh dev) y redeplegar.
Terraform intenta montar secret_key_ref de un secreto sin versiones: la revisión falla antes de arrancar el contenedor.
/health/ready devuelve not_configured
El proveedor activo no tiene host. Revisar DELIVERY_PROVIDER y el secreto *-host correspondiente. Cloud Run no reinicia por esto (los probes usan /health).
Webhook responde 500
Fallo al publicar: la SA sin pubsub.publisher, topic inexistente en el proyecto o GOOGLE_CLOUD_PROJECT vacío. Buscar unhandled_exception con el correlationId de la respuesta.
Probar un webhook a mano
curl -s -X POST "$DELIVERY_ADAPTER_URL/webhooks/bringoz" \
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" -H "X-Correlation-ID: test-$(date +%s)" \
-d '{"eventType":"order_delivered","orderId":"ORD-TEST-1"}'# requiere run.invoker y una ruta de red interna al servicio
Cambiar de proveedor
printf 'nexus' | gcloud secrets versions add csb-dev-delivery-provider --data-file=-
# redeploy: el proveedor se resuelve una sola vez al arrancar
Ver la documentación desplegada
El servicio no es alcanzable desde internet. En dev está publicada por el balanceador del CSB: csb-dev.rotoplas.com/adapters/delivery/docs (solo el prefijo /docs; los endpoints de negocio no se exponen). En local: npm run start:dev y abrir http://localhost:8080/docs.
Desarrollo local y pruebas
Comandos
npm ci
cp .env.example .env # DELIVERY_PROVIDER, BRINGOZ_*, USE_SECRET_MANAGER=false
npm run start:dev # → http://localhost:8080/docs
npm test · npm run test:cov · npm run test:e2e · npm run lint · npm run build
docker compose up # app + emulador de Pub/Sub (:8085)
Integración con mocks
Desde rotoplas/, docker-compose.integration.yml levanta CSB + adapters + mock Bringoz (:4003). Este adapter queda en http://localhost:8083, Swagger en /docs/api. Ver INTEGRATION.md.
Pruebas
23 unitarias (delivery.service, providers, webhooks.service, pubsub.service) + e2e de health (test/health.e2e-spec.ts, requiere jest --config test/jest-e2e.json). El módulo docs agrega sus propias suites. INSTALL.md menciona un mínimo de cobertura del 80 % que el pipeline no aplica.
Convenciones
feature/* → dev → main; nunca push directo a main.
Conventional Commits; secretos solo en Secret Manager; recursos GCP solo por Terraform.
Tipos compartidos desde @rtp-csb/contracts cuando se publique.
Pendientes y deuda técnica
hechoBringoz QAS cableado (10 sep 2026) a partir de la doc "Conexión Bringoz QAS" de Bebbia: crear, cancelar, depots, cotización, polling y dispatch contra sandbox.bringoz.com, con los mismos reintentos por endpoint que Bebbia.
pendienteConsulta de orden en Bringoz (track): la doc de Bebbia no la incluye; confirmar endpoint con Bringoz (el CSB-01 asume GET /v2/oms/orders/{orderId}). Spec de Nexus sigue pendiente.
pendientePublicar resultado tras create/cancel: hoy solo los webhooks publican en Pub/Sub; el CSB no recibe evento por una orden creada.
hechoCola de Cloud Tasks y topic de resultados declarados en este repo (63e28d4): csb-delivery-adapter-<env>-queue, topic result_topic_name, run.invoker y cloudtasks.enqueuer para sa-csb-api-<env>, más los outputs para el remote state del CSB.
hechoEntrada por Pub/Sub: POST /delivery/events acepta el envelope CloudEvents v1.0 de los topics del CSB (204 / 400 sin reintento). Queda alinear también el DeliveryPayloadDto de Cloud Tasks con @rtp-csb/contracts.
pendienteSeguridad del webhook: firma/secreto compartido y exposición controlada por balanceador externo.
pendienteConvención de naming (revisión de Hugo, 26 ago 2026): secretos delivery-<env>-bringoz-* y Artifact Registry propio delivery-adapter; las rutas ya cumplen (/delivery/* en inglés).
deudaAtributo provider del mensaje Pub/Sub se infiere de providerRef (siempre bringoz/unknown), no del proveedor real.
deudaPipeline sin canary (--no-traffic) ni umbral de cobertura; e2e no se ejecutan en CI.
hechoScaffold completo: strategy de proveedores, webhooks → Pub/Sub, middlewares de correlación y logging, Terraform con VPC/secrets/monitoring por ambiente, pipeline, portal de documentación.