API Identifier Strategy — Discussion Document

Deciding how QIIUB exposes resource identifiers in API routes and responses

Created: 2026-04-24 11:55 AST · Last Updated: 2026-04-27 11:24 AST · Status: For team review — pre-ADR

Contents

14
Plataformas
investigadas
2
ADRs en
contradicción
246
Endpoints a
migrar
8
Formatos
distintos en Square
1
Regla uniforme
propuesta
26
Chars del suffix
propuesto

§0 — TL;DR

§1 El problema

QIIUB hoy tiene dos fuentes de verdad contradictorias sobre cómo deben verse los identificadores en las rutas de API:

ADR-0005 (GlobalId como sync identity)

Dice: "GlobalId prepares for a future migration to GUID-based API routes (replacing {Id} in URLs)."

Apunta explícitamente a URLs con GUID completo de 36 caracteres como destino futuro.

ADR-0029 (PublicId 8-hex para Central)

Dice: "GUIDs are 36 characters — too long for URLs, JWTs, and human communication. An 8-char hex is memorable and typeable."

Rechaza GUID en URLs por ser largo. Adopta 8 chars hex para Merchant únicamente.

Ambos fueron aceptados el mismo día (2026-03-12) y nunca se resolvió la inconsistencia. El resultado: Phase 18 cerró con 246 endpoints y solo 2 entidades (Merchant, Partner) usando el PublicId. Todo lo demás — productos, ventas, clientes, empleados, purchase orders, shifts, gift cards, etc. — todavía expone el bigint Id interno.

Los dos problemas reales a resolver:
  • Exponer bigint auto-increment filtra volumen de negocio (¿cuántas ventas, cuántos clientes?) y permite enumeración trivial.
  • No hay una regla única aplicable a todas las entidades — lo que garantiza drift al estilo Square si no decidimos ahora.

§2 Estado actual de QIIUB

CapaRealidad hoyRige en
Clave primaria internabigint Id (IDENTITY)Todas las entidades
Sync identityGlobalId (uniqueidentifier, GUIDv4)Todas las entidades de Merchant DB
ID externo Central DBPublicId (char(8) hex)Solo Merchant + Partner
ID externo Merchant DB— (no existe — se expone Id)~230 endpoints
ID externo resto Central DB— (no existe — se expone Id)MerchantGroup, PlatformUser, Invitation, Template, Server, etc.

Ejemplos reales del código actual

// src/QIIUB.Api/Features/Products/GetProduct/GetProductEndpoint.cs:57
public override void Configure() {
    Get("/products/{Id}");    // ← expone bigint
}

// src/QIIUB.Api/Features/Sales/GetSale/GetSaleEndpoint.cs:90
Get("/sales/{Id}");             // ← expone bigint

// src/QIIUB.Api/Features/Platform/Merchants/GetMerchant/GetMerchantEndpoint.cs:57
Get("/platform/merchants/{Id}");   // ← acepta Id O PublicId (8-hex)

// src/QIIUB.Api/Features/Platform/Partners/GetPartner/GetPartnerEndpoint.cs:38
Get("/platform/partners/{PublicId}"); // ← solo PublicId (único consistente)

Total endpoints con bigint en ruta: ~240 de 246. Solo los endpoints de Partner y algunos de Merchant usan PublicId.

§3 Qué hacen otros POS y plataformas

Research completo en docs/research/research-api-identifier-format.md (14 plataformas + 5 especificaciones, con fuentes verificables). Resumen ejecutivo por categoría:

FamiliaEjemploUsado porAdecuado para POS moderno
Bigint secuencial 12345 Shopify REST, Revel, Epos Now, Lightspeed R-Series ❌ Anti-pattern. Todos migrando afuera.
UUID v4 (GUID) 550e8400-e29b-41d4-a716-446655440000 Toast, Lightspeed X-Series, Linear (canónico), Square Loyalty ✓ Safe, boring, mainstream
Opaque base32 corto 3D19QN31ANYR5 Clover, Square Merchant/Location ⚠ Funciona pero sin tipado
Opaque base62 medio bP9mAsEMYPUGjjGNaNO5ZDVyLhSZY Square Payment/Order/TeamMember ⚠ Sin tipado, legible sí
Typed prefixed (Stripe-style) cus_NffrFeUfNV2Hib Stripe, Clerk, Linear (parcial), Square Invoice (inv:0-…), Square Gift Card (gftc:…) ✓ Emergiendo como estándar
UUIDv7 + prefix (TypeID spec) user_01j8m3xk7q9vy2p4n6r8s0t1w3 Bindings: Go, Rust, Python, C#, Java, TS, Ruby…
Adoptadores: Jetify, proyectos nuevos post-2023
✓ Mejor combinación disponible

Hallazgos claves

§4 El caso Square — nuestro cautionary tale

⚠ Square tiene al menos 8 formatos distintos en la misma API

Un survey directo de 14 tipos de recursos de Square encontró que no hay una sola regla — cada equipo dentro de Square eligió un formato distinto en su propio momento. El resultado es imposible de unificar ahora sin romper contratos con integradores.

RecursoEjemplo verbatimFormato
MerchantDM7VKY8Q63GNP13 chars uppercase
LocationL88917AVBK2S513 chars uppercase
Catalog itemW62UWFY35CWMYGVWK6TWJDNI24 chars uppercase
CustomerQ8002FAM9V1EZ0ADB2T5609X6NET1H026-31 chars variable
Order (old)CAISENgvlJ6jLWAzERDzjyHVybY27 chars, sospecha protobuf
Order (new)d7eKah653Z579f3gVtjlxpSlmUcZY29 chars mixed case
PaymentbP9mAsEMYPUGjjGNaNO5ZDVyLhSZY29 chars mixed case
Team Member1yJlHapkseYnNPETIU1B20 chars mixed case
RefundbP9mAsEMYPUGjjGNaNO5ZDVyLhSZY_69MmgHubkLqx9wGhnmenRUHOaKitE6llfZuxcWYjGxd73 chars compuesto
Loyalty Account79b807d2-d786-46a9-933b-918028d7a8c5UUID v4 completo
Invoiceinv:0-ChCHu2mZEabLeeHahQnXDjZQECYTyped prefixed con version marker
Gift Cardgftc:00113070ba5745f0b2377c1b9570cb03Typed prefixed con hex
Lección para QIIUB: estamos hoy exactamente donde Square estaba alrededor de 2015 — 2 formatos convivieron en paz, luego cada equipo que construyó una feature nueva eligió lo que le pareció mejor. Si no adoptamos una regla explícita para todas las entidades ahora, la trayectoria a 3-4-8 formatos es predecible.

§5 La pregunta del nombre

Punto válido del team: ¿no podría confundirse "TypeID" con campos como ProductTypeId, PaymentTypeId, UserTypeId — FKs a tablas de enums o discriminadores de tipo?

Sí, absolutamente. "TypeID" como vocabulario del proyecto sería confuso. Comparación rápida:

TérminoProblemaVeredicto
TypeIDSe confunde con FKs a enums (ProductTypeId, PaymentTypeId)❌ Rechazado
PrefixIdNo existe en ningún sistema real. Invención.❌ Rechazado
SlugYa significa handle SEO (tylenol-500mg-100ct). Conflicto directo con ProductWebListing.Slug.❌ Rechazado
ExternalIdEn Stripe/Toast significa "ID provisto por el integrador externo". Conflicto semántico.❌ Rechazado
GlobalIdYa existe y ya significa "sync identity". Reutilizar cambia contrato interno.⚠ Reservado para sync
PublicIdYa existe en QIIUB (Merchant/Partner). Ya significa "ID expuesto en API pública". Consistente.✓ Correcto
Propuesta: la columna se llama PublicId (extendiendo la convención existente de ADR-0029). El formato de esa columna sigue el spec TypeID (UUIDv7 + prefijo). "TypeID" aparece solo en el ADR como referencia al spec que adoptamos — no en código, no en nombres de columnas, no en vocabulario del equipo.
// C# entity
public class Product : BaseEntity
{
    public long Id { get; set; }            // interno, bigint
    public Guid GlobalId { get; set; }       // sync identity — sin cambios
    public string PublicId { get; set; }      // "prod_01j8m410fpqm..." — expuesto en API
    public int MerchantId { get; set; }
    // ...
}

// Ruta
Get("/products/{PublicId}");

// Cliente HTTP
GET /products/prod_01j8m410fpqm4r6t8v0x1a3c5e7

§6 Arquitectura: dónde vive cada ID

QIIUB tiene tres tipos de identificadores con tres roles bien diferenciados. Antes de discutir formato, hay que tener clara la arquitectura porque el formato del PublicId solo aplica a parte del sistema — no al hot path del cashier.

Las tres columnas en cada entidad

public class Product : BaseEntity
{
    public long Id { get; set; }            // (A) INTERNO — PK clustered (MerchantId, Id), FKs, joins. Nunca expuesto.
    public Guid GlobalId { get; set; }       // (B) SYNC — dedup en push offline. En body MessagePack del sync engine.
    public string PublicId { get; set; }      // (C) EXTERNO — único en URL/response de API pública. "prod_01j8m..."
    public int MerchantId { get; set; }
    // ...
}

El flujo completo

POS Terminal (Windows app)
UI del cashier — checkout, sale entry, lookup de productos.
Lee/escribe solo SQLite local.
Latencia objetivo: <50ms (local I/O). No HTTP.
SQLite local (POS DB)
Guarda: Id local + GlobalId (GUID).
NO guarda PublicId.
El cashier nunca ve el PublicId.
Sync Engine (proceso aparte)
Lee SQLite, push/pull a la nube en batches.
Body en MessagePack con GlobalId.
Servidor hace upsert por (MerchantId, GlobalId).
⟷ local SQL local solo
(Id + GlobalId)
⟷ sync push/pull MessagePack body
con GlobalId
⟷ HTTP API URL + JSON
con PublicId
Cloud API (Azure)
Endpoints públicos: portal admin, portal merchant.
URL: /products/prod_01j8m...
Aquí es donde el PublicId existe.
Cloud-required POS ops
POS llama API para: returns, loyalty, gift-card balance, AR charge, multi-location stock.
URL no se ve al cashier, pero el PublicId está en el response.
Aparece en logs / Sentry breadcrumbs / mensajes de error.
Merchant DB (Azure SQL)
Guarda las 3 columnas: Id + GlobalId + PublicId.
Índices: (MerchantId, Id) clustered, (MerchantId, GlobalId) + (MerchantId, PublicId) non-clustered.
PK clustered Id intacto (ADR-0001).

Matriz: dónde se ve cada ID

Caso de uso Id (bigint) GlobalId (UUID) PublicId (string)
Cashier UI (checkout, sale entry, lookup local) SQLite local solo SQLite local solo No existe
Sync engine push/pull (batch background) Nunca SÍ (body MessagePack) Nunca
POS llama cloud (returns, loyalty, AR, gift-card balance) Nunca A veces (request) SÍ (URL + response)
Admin portal (qiiub-admin) — humanos BCPOS / Partners Nunca Nunca SÍ (URL + response)
Merchant portal (qiiub-portal) — humanos del merchant Nunca Nunca SÍ (URL + response)
Logs del servidor / Sentry / mensajes de error Nunca A veces
Webhooks / integraciones terceras (futuro) Nunca Nunca
FKs y joins en SQL Solo aquí No No
Implicación clave: el PublicId está en el camino del POS solo para operaciones cloud-roundtrip (returns, loyalty, etc.). El cashier no lo ve visualmente — pero está en logs, breadcrumbs de Sentry, y mensajes de error de la nube. Esto refuerza que el formato debe ser legible en debugging aunque no sea ultra-corto: el cashier nunca lo va a teclear, pero soporte sí lo va a leer.

§7 Recomendación: dos decisiones separadas Propuesta

El HTML original las trataba como una sola decisión. En realidad son dos decisiones independientes con perfiles de debate muy distintos.

Decisión A — interno

Upgrade GlobalId de UUIDv4 a UUIDv7

Qué cambia: el generador de la columna existente GlobalId uniqueidentifier. Hoy es UUIDv4 (NEWSEQUENTIALID() en server, Guid.NewGuid() en POS offline). Pasaría a UUIDv7 en ambos lados.

Qué NO cambia: el contrato de API. GlobalId sigue siendo interno al sync engine. El body MessagePack del sync sigue llevando un GUID de 16 bytes.

Por qué: UUIDv7 es time-ordered globalmente → arregla fragmentación del índice no-clustered en (MerchantId, GlobalId) sin necesidad de NEWSEQUENTIALID(). Sync determinismo: cuando el servidor recibe 500 sales offline, ordena por GlobalId y obtiene orden cronológico estable sin depender del clock del cliente.

Costo: reemplazar 2-3 callsites donde generamos el GUID. Cero migración de data (UUIDs viejos siguen siendo válidos — UUIDv7 es un superset de la spec UUID).

Debate esperado: bajo. Es upgrade técnico sin impacto a clientes externos.

Decisión B — externo (la discusión real)

Formato del PublicId expuesto en API

Qué cambia: agregamos columna PublicId varchar(40) a cada entidad expuesta. Migramos ~240 endpoints de {Id} a {PublicId}.

Recomendación: formato siguiendo el spec TypeID — prefijo tipado (2-5 chars) + _ + 26 chars Crockford base32 del UUIDv7 (que es el mismo valor del GlobalId, encodeado).

Qué NO cambia: Id bigint sigue siendo PK clustered. GlobalId sigue siendo sync identity. PublicId es nuevo, separado, opt-in por entidad.

Por qué este formato específicamente: ver §8 — análisis comparativo de NanoID vs UUIDv7 vs alternativas.

Debate esperado: alto. Las 7 preguntas abiertas (§9) atacan principalmente esta decisión.

Comparación visual: hoy vs propuesto

Hoy — expone bigint
GET /products/12345
GET /sales/987654
GET /customers/4521
GET /employees/89
GET /purchase-orders/2341
GET /shifts/17823
GET /platform/users/47
Propuesto — PublicId con prefijo
GET /products/prod_01j8m410fpqm4r6t8v0x1a
GET /sales/sale_01j8m411npqr5s7u9w1y2b
GET /customers/cust_01j8m412bpqs6t8v0x1a3c
GET /employees/emp_01j8m413npqt7u9w1y3b5d
GET /purchase-orders/po_01j8m414fpqu8v0x2a4c6e
GET /shifts/shft_01j8m415npqv9w1y3b5d7f
GET /platform/users/usr_01j8m416fpqw0x2a4c6e8g

§8 Por qué UUIDv7 específicamente y no algo más corto

Pregunta válida del equipo: "¿Por qué UUIDv7 y no algo más corto, dado que ya tenemos prefijo tipado para diferenciar tipos?". Aquí el análisis honesto con números reales.

Las opciones reales (con cuenta correcta)

Asumiendo prefijo de 5 chars (prod_):

Opción suffixSuffixTotalEntropíaTime-orderedSpec abierto
Stripe-style 12-char base621217~71 bitsparcialno — Stripe propietario
NanoID 16 chars1621~96 bitsno
NanoID 21 (default)2126~126 bitsno
UUIDv7 base622227128 bitsUUIDv7 sí, encoding no
UUIDv7 Crockford base32 (TypeID)2631128 bitssí (ambos)
ULID Crockford base322631128 bits
KSUID base622732160 bitssí (1s)
UUIDv7 hex3237128 bits
UUID v4 hex (GUID actual)3641122 bitsno

Diferencias clave:

Los 5 factores que importan PARA QIIUB específicamente

1Offline-first sync (decisivo)
Los POS terminals crean records offline (sales, customers) y los pushean cuando recuperan red. Cuando un push tiene ack perdido, el terminal reintenta — el servidor debe deduplicar por ID. Esto requiere:
  • IDs globalmente únicos sin coordinación con servidor (descarta Snowflake-puro, sequential)
  • ≥96 bits de entropía para evitar colisiones birthday-paradox cuando ~1,000 merchants × ~50 terminals × ~10 años generan IDs simultáneamente offline
2Time-ordering para sync determinista (decisivo)
Cuando el servidor recibe un batch de 500 sales offline y necesita aplicarlas en orden cronológico, hay dos opciones: (a) confiar en CreatedAtUtc del cliente — pero los relojes de POS pueden estar mal calibrados, y dos sales con el mismo timestamp necesitan tiebreaker. (b) Que el ID mismo sea time-ordered → ordenamiento estable sin depender del clock.

UUIDv7/ULID/KSUID dan esto. NanoID/UUIDv4 no. Este argumento aplica a QIIUB y no a Stripe (Stripe nunca opera offline).

Nota: Este factor justifica UUIDv7 para el GlobalId (Decisión A). Para el PublicId (Decisión B), aplica indirectamente — es práctico que ambos compartan el mismo valor encodeado distinto.
3Index fragmentation en tablas high-volume
La tabla Sale proyecta ~100M+ rows agregados a 5-10 años. El índice no-clustered en (MerchantId, GlobalId) se fragmenta con UUIDs aleatorios — exactamente el problema que ADR-0005 mitigó con NEWSEQUENTIALID(). UUIDv7 lo arregla globalmente (server y client). NanoID y UUIDv4 reintroducen el problema.

Aplica primariamente a Decisión A. Si PublicId es derivado de GlobalId, hereda el beneficio.
4Entropía requerida por la tabla más grande
Regla: la entropía debe servir la tabla MÁS grande, no la promedio. Para QIIUB:
  • Merchant: ~1,000 entradas → 32 bits sobra
  • Product/Customer: ~100K por merchant → 64 bits sobra
  • Sale + InventoryMovement + AuditEntry: 10M-100M+ por merchant → 96 bits es el piso, 128 bits es el techo cómodo
Stripe acepta ~71 bits porque hace collision-check on insert (transacción + retry). Para QIIUB en el sync push del POS, cada milisegundo extra de DB roundtrip cuenta — 128 bits elimina la necesidad de collision check.
5Spec abierto vs roll-your-own
TypeID v0.3.0 tiene: librería C# oficial, spec publicado y referenciable en ADR, validación de prefijos definida, 12+ bindings de lenguaje, escape hatch documentado. NanoID-corto + prefijo + collision-check sería roll-your-own — funcional, pero deuda técnica. Cuando el equipo crece, la próxima persona pregunta "¿por qué este formato y no UUIDv7?" y no hay spec que apuntar.

Cuándo la opción más corta SÍ sería correcta

Sería honesto elegir NanoID-16 (total 21 chars) si:
  • QIIUB no fuera offline-first → descarta factores 1+2
  • No tuviéramos tablas tipo Sale a escala 100M+ → descarta factor 4
  • Tuviéramos collision-check infrastructure ya construida → mitiga factor 4
  • Estuviéramos optimizando agresivamente bytes en JWT/URL/logs (no el caso)
Para una API SaaS pure-cloud sin offline (Stripe, e-commerce simple), NanoID-16 sería respuesta correcta. Para QIIUB no — los 5-10 chars extra de UUIDv7 compran sync determinismo, performance en Sale, y future-proofing por costo despreciable.

Resumen comparativo concreto

// Más corto (NanoID-16, 21 chars):
prod_V1StGXR8_Z5jdHi6B-

// UUIDv7 base62 (27 chars):
prod_2x4y6z8a0b1c2d3e4f5g6h

// TypeID Crockford base32 (31 chars) — recomendado:
prod_01j8m410fpqm4r6t8v0x1a

// UUIDv4 actual GUID (41 chars):
prod_550e8400-e29b-41d4-a716-446655440000

5-10 chars extra para offline-determinismo + fragmentation-fix + spec-based. Trade reasonable. No es "UUIDv7 porque sí" — es "UUIDv7 porque QIIUB es offline-first y tiene Sale".

Las 7 razones (originales, refinadas)

  1. Resuelve la contradicción ADR-0005 vs ADR-0029. UUIDv7 cumple sync (colisión-libre offline) y el suffix de 26 chars cumple "corto" de ADR-0029.
  2. UUIDv7 arregla la fragmentación de índice que ADR-0005 mitigó con NEWSEQUENTIALID(). Time-ordered globalmente, server y client.
  3. El prefijo paga en logs y debugging. El POS llama cloud para returns/loyalty/AR — los IDs aparecen en Sentry breadcrumbs y mensajes de error. sale_01j8m... se entiende solo; 987654 no.
  4. Spec open-source, no roll-your-own. Adoptamos estándar emergente con lecciones de Stripe ya incorporadas.
  5. Evita el escenario Square. Una regla explícita ahora, antes de crecer a 500+ endpoints.
  6. Forward-compatible con webhooks. IDs prefijados son mandatorios cuando el payload no tiene URL context.
  7. Consistente con la convención PublicId existente de Merchant/Partner. Extendemos, no inventamos.

§9 Decisiones a confirmar por el equipo Abiertas

Q1
¿Adoptamos TypeID o plain UUID v7?
TypeID = prefijo + base32 (26 chars). Plain UUIDv7 = hex con guiones (36 chars). TypeID gana en debugging/logs/webhooks; plain UUID gana en simplicidad de implementación (reutiliza GlobalId directo).
Q2
¿Qué hacemos con Merchant.PublicId + Partner.PublicId (8-hex actuales)?
Los 8-hex ya están en JWTs activos, emails de invitación, QR codes. Propuesta: dejarlos como legacy aceptado (excepción documentada en ADR). Alternativa: migrarlos al formato nuevo con script + invalidar sesiones.
Q3
¿Una columna nueva PublicId o renderizado computed desde GlobalId?
Opción A: columna stored PublicId varchar(40). Opción B: computed column desde GlobalId. A es más flexible (podemos cambiar formato sin regenerar); B es más limpio (single source of truth).
Q4
¿Big-bang o dual-route durante migración?
Big-bang: cambia los 240 endpoints en un PR antes de Phase 19. Dual-route: acepta ambos {Id} y {PublicId} por 2-3 semanas. Como aún no hay clientes terceros, big-bang es más limpio.
Q5
¿Qué prefijos exactamente?
Si adoptamos TypeID, cada entidad necesita un prefijo corto (2-5 letras). Propuesta preliminar abajo — necesita review. Regla: minúsculas, mnemonic, ≤5 chars, nunca cambiar.
Q6
¿Incluimos version marker como Square?
Square usa inv:0-… dejando la puerta abierta a inv:1-…. Stripe NO hace esto y nunca ha cambiado un prefijo. Recomendación: no incluir — complica más de lo que ayuda.
Q7
¿Exponemos un concepto de ExternalId para que integradores traigan su propio ID?
Toast y Stripe sí lo hacen (independiente de la decisión principal). Permite a un ERP mapear sus productos sin storage extra. Decisión independiente — puede posponerse.

Prefijos propuestos (preliminar — sujeto a Q5)

EntidadPrefijoEjemploEntidadPrefijoEjemplo
Central DB Merchant DB — ops
Merchantmrchmrch_01j8m3x… Productprodprod_01j8m41…
Partnerprtnprtn_01j8m3y… Salesalesale_01j8m41…
PlatformUserusrusr_01j8m3z… Customercustcust_01j8m41…
MerchantGroupgrpgrp_01j8m40… Employeeempemp_01j8m41…
Invitationinvinv_01j8m40… PurchaseOrderpopo_01j8m41…
Templatetmpltmpl_01j8m40… Shiftshftshft_01j8m41…
Serversrvsrv_01j8m40… GiftCardgcgc_01j8m41…
DeviceRegistrationdevdev_01j8m40… LoyaltyAccountloyloy_01j8m41…
AiCreditTransactionaitait_01j8m40… Suppliersupsup_01j8m41…
Esta tabla es preliminar — los prefijos son irreversibles una vez publicados (Stripe nunca ha cambiado uno). Team debe revisar cada uno antes de la ADR.

§10 Alternativas viables

Opción A — TypeID (recomendada) Recomendada

PublicId = prefix + _ + 26 chars base32 de un UUIDv7. Uniforme en todas las entidades.
✓ Open spec · ✓ librería C# disponible · ✓ time-ordered · ✓ tipado en logs · ✓ forward-compatible webhooks · ✓ alineado con dirección Square nuevos APIs
✗ Más trabajo de implementación · ✗ Requiere decidir prefijos · ✗ Romper 240 endpoints a la vez

Opción B — Plain UUIDv7 (GlobalId expuesto) Alternativa safe

Reutilizar GlobalId directamente. Upgrade a UUIDv7. Rutas: /products/{GlobalId}.
✓ Menor trabajo · ✓ Cero columnas nuevas · ✓ Toast y Lightspeed X lo operan en POS sin drama · ✓ Safe, boring, industry-tested
✗ URLs de 36 chars "feas" · ✗ Sin beneficio de tipado · ✗ Mezcla concepto sync con concepto API

Opción C — Extender 8-hex a todas las entidades No recomendada

Aplicar el char(8) hex actual de Merchant/Partner a todas las entidades.
✓ Consistente con lo existente · ✓ URLs cortas
✗ 4.3B valores por tabla es inadecuado para Sale (millones de rows/merchant) · ✗ Sin tipado · ✗ Sin time-ordering · ✗ No usado por ningún POS reconocido

Opción D — Status quo (no hacer nada) No recomendada

Mantener bigint Id en rutas. Aceptar la contradicción ADR-0005/0029 indefinidamente.
✓ Cero trabajo inmediato
✗ Filtra business volume · ✗ Enumeration trivial · ✗ Trayectoria confirmada al escenario Square · ✗ Más caro migrar en cada trimestre que pasa · ✗ Anti-patrón industry-wide

§11 Lo que NO debe hacerse Anti-patrones

§12 Próximos pasos

  1. Discusión de equipo sobre este documento. Respuestas a Q1–Q7.
  2. Escribir ADR-0044 — "Uniform API identifier strategy — prefixed PublicId across Central and Merchant DBs". Supersede la línea de API routes de ADR-0005, extender scope de ADR-0029.
  3. Task file en C:\temp\.claude\qiiub\ con plan de migración detallado: prefijos definitivos, librería C# (Jetify/TypeID bindings), estrategia de migración de data, actualización de 240 endpoints, ajuste de admin portal + mockups, tests de integración.
  4. Actualizar docs afectados:
    • docs/adr/0005-globalid-guid-sync-identity.md — reemplazar nota "under review" con referencia a ADR-0044
    • docs/adr/0029-publicid-central-api-routes.md — marcar como supersedida por ADR-0044 (manteniendo el 8-hex para Merchant/Partner como legacy)
    • docs/qiiub-context-for-ai.md §API Identifier Strategy — reemplazar con la regla final
    • Memoria feedback_api_identifiers.md — actualizar con la regla final
  5. Implementación antes de Phase 19 (Merchant Portal frontend).
Timing crítico: Phase 19 introduce el portal de merchants, que expondrá más endpoints y tendrá más observadores. Cada endpoint nuevo construido con el patrón viejo es trabajo doble. Decidir antes de Phase 19 es 10× más barato que decidir después.

Research completo con fuentes verificables → docs/research/research-api-identifier-format.md

QIIUB — API Identifier Strategy Discussion · 2026-04-24 AST