Capa de interoperabilidad · Prueba técnica de arquitectura

ADR-026: los identificadores de la orden tienen formato declarado y salen del detector de tarjetas

Aceptado el 14/11/2026 por Andrés. Lo implementa el carril `detector-tarjetas`.

Contexto

08-seguridad.md promete que la API rechaza campos con forma de número de tarjeta, por si un partner envía uno por error. order-service lo cumplía revisando todo el cuerpo de POST /v1/orders: cualquier texto con 13 a 19 dígitos seguidos (con o sin espacios o guiones) que pasaran el algoritmo de Luhn (la suma de verificación con la que todo número de tarjeta cierra su último dígito) recibía 400 VALIDATION_ERROR. Solo el teléfono quedaba fuera.

Un dígito de control acierta por azar una de cada diez veces, así que la regla adivina: no distingue una tarjeta de un identificador que por casualidad tiene la misma forma. El carril f9-idempotencia (14/11/2026, commit 3460176) lo encontró en la tienda, no en un partner:

Un servicio de órdenes no puede rechazar una orden válida. Un partner con identificadores numéricos largos (los de muchos marketplaces son solo dígitos) lo vería en una de cada diez órdenes, no en una de cada 450.

Opciones

Opción A favor En contra
A. Los campos de identificador salen del detector y el contrato declara su formato; el detector sigue en el texto libre Ningún identificador válido se rechaza por azar; el formato es una regla exacta que el partner conoce antes de integrarse, y además frena basura y caracteres de control en campos que van a logs y eventos Una tarjeta puesta a propósito en un identificador pasa; el contrato se vuelve más estricto en campos que antes eran texto sin forma
B. Mantener el detector en todo el cuerpo y pedir a la tienda otro formato de externalOrderId No cambia la capa Arregla solo a la tienda; cualquier partner con identificadores numéricos sigue con rechazos al azar. Mueve la carga de un defecto de la capa a cada canal
C. Quitar el detector Cero rechazos falsos Una tarjeta enviada por error en la dirección o el nombre entraría y quedaría guardada, cifrada, en order-service: la capa saldría del alcance reducido de PCI DSS que fija 08-seguridad.md
D. Exigir además un prefijo de emisor (BIN, bank identification number: los primeros dígitos de la tarjeta, que identifican al banco emisor) o un largo exacto Menos falsos positivos Sigue adivinando; solo baja la tasa. El identificador de un partner puede empezar por 4 o por 5 igual que una tarjeta

Decisión

Opción A.

  1. Formato de los identificadores. El contrato define el schema Identifier: texto de 1 a 64 caracteres, letras ASCII, dígitos y ., _, : y -, que empieza por letra o dígito (^[A-Za-z0-9][A-Za-z0-9._:-]*$). Lo usan los seis campos de identificador de CreateOrderRequest: externalOrderId, channel, customer.customerId, fulfillment.storeId, lines[].sku y lines[].priceVersion. Un valor fuera del formato recibe 400 VALIDATION_ERROR con el campo en errors[].field.
  2. El formato acepta lo que hoy se envía, verificado contra los datos y no supuesto: WEB-<uuid> de la tienda, MKP-778899 y mkp-caribe-<corrida>-<n> de los partners de prueba y de carga, los 1 143 SKU de la semilla (SKU-1001, AR-CIN-001-COG-85, hasta 18 caracteres), las 5 ubicaciones (BOG-01, CD-BOG), los 4 canales (ecommerce, mkp-andino), los 200 customerId del CRM (cus_8f3a21) y las versiones de precio del erp-adapter (pv_<ULID>). Supuesto S27: los identificadores de orden de los partners caben en ese alfabeto; si uno trae otros caracteres, el partner los traduce, igual que traduce sus estados.
  3. El detector revisa solo el texto libre: nombre, correo, dirección (line1, line2, city, region, postalCode) y payment.token. El token sigue en el detector aunque es un identificador de la pasarela, porque es justo el campo donde un partner pondría la tarjeta por error, y los tokens de las pasarelas en uso (tok_..., pm_... y los del simulador) no son solo dígitos. El teléfono sigue fuera, como antes: es numérico por naturaleza.
  4. Una sola fuente. La lista de campos con formato de identificador vive en un solo lugar del servicio (CreateOrderRequest.IDENTIFIERS), que alimenta a la vez la validación de formato y las exclusiones del detector. make verify compara esa lista y el patrón con el contrato y falla si difieren.
  5. Compatibilidad. Agregar un patrón a un campo de petición restringe el contrato. Se acepta sin versión nueva porque order-service ya limitaba esos campos a 64 caracteres, ningún partner real está integrado (el demo es el único cliente) y todo lo que hoy se envía cumple el formato (punto 2).

Qué se sacrifica