Saltar a contenido

0011. UUID vs BIGINT: qué identificador usar en cada caso

Estado: Aceptado Fecha: 2026-09-03 Autor: Duval Alcivar

Contexto

Con 14 microservicios y 3 bases de datos, hay que definir claramente cuándo usar UUID y cuándo BIGINT para identificadores. Cada error crea acoplamiento entre servicios o rompe el aislamiento por empresa.

El ADR 0003 ya establece que la correlación entre servicios es por UUID, y el ADR 0004 define id como UUID PK e empresa_id como BIGINT. Este ADR completa la regla con ejemplos concretos.

El aislamiento por empresa (RLS) se trata en el ADR 0012.


Decisión

La clave: ¿Es catálogo o entidad de negocio?

CATÁLOGO = BIGINT (pocos registros, finitos, rara vez cambian)

empresas:     5 registros    → BIGINT
sucursales:   12 registros   → UUID   (*)
paises:       200 registros  → BIGINT
canales:      10 registros   → BIGINT
bodegas:      8 registros    → BIGINT

() Las sucursales son la excepción: aunque son pocas, se referencian entre servicios* (selector de sucursal en el token, consumidores), así que su id es UUID.

¿Por qué? Son pocos, finitos, nunca llegan a millones. BIGINT es más pequeño (8 bytes vs 16 bytes) y más eficiente para filtros.

ENTIDAD DE NEGOCIO = UUID (muchos registros, crece, se referencia entre servicios)

clientes:        10,000+ registros  → UUID
productos:       5,000+ registros   → UUID
facturas:        50,000+ registros  → UUID
pedidos:         100,000+ registros → UUID
movimientos:     500,000+ registros → UUID

¿Por qué? Crecen, pueden ser millones, se referencian entre servicios. UUID es único globalmente y se genera sin conexión a la DB.


Ejemplo simple: Un pedido

CREATE TABLE erp.trx_pedidos (
    -- PK del pedido (entidad de negocio)
    id UUID PRIMARY KEY,  -- UUID: es grande y se referencia entre servicios

    -- Referencia a empresa (catálogo pequeño)
    empresa_id BIGINT NOT NULL,  -- BIGINT: es pequeño

    -- Referencia a cliente (entidad de negocio)
    cliente_id UUID NOT NULL,  -- UUID: es grande y cross-service

    -- Referencia a sucursal (selector de sucursal en el token)
    sucursal_id UUID NOT NULL,  -- UUID: se referencia entre servicios

    -- Referencia a usuario (entidad central, cross-service)
    usuario_id UUID NOT NULL,  -- UUID: se referencia entre servicios

    -- Datos de negocio
    fecha_pedido DATE,
    total NUMERIC(12,2)
);

¿Por qué esta combinación?

Campo Tipo Tabla origen ¿Por qué?
id UUID trx_pedidos PK del pedido (entidad grande)
empresa_id BIGINT empresas Catálogo pequeño (5 empresas)
cliente_id UUID mae_clientes Entidad grande (10k clientes), cross-service
sucursal_id UUID mae_sucursales Se referencia entre servicios
usuario_id UUID mae_usuarios Entidad central, cross-service

¿Por qué NO usar UUID para empresa_id?

Si mae_empresas tuviera UUID:

TABLA: mae_empresas
id = UUID('a1b2c3d4-e5f6-7890-abcd-ef1234567890')  razon_social = "Sigfa S.A."
id = UUID('e5f6g7h8-i9j0-klmn-opqr-stuvwxyz1234')  razon_social = "SigfaPro Cía."

Y en mae_clientes:

TABLA: mae_clientes
id = UUID('x1y2...')  empresa_id = UUID('a1b2c3d4-e5f6-7890-abcd-ef1234567890')

Problemas:

  1. Filtros más lentos — PostgreSQL filtra por empresa_id en CADA query. BIGINT (8 bytes) es más rápido que UUID (16 bytes)
  2. Más espacio — Cada registro guarda 8 bytes extra por cada referencia
  3. Menos legible — ¿Prefieres ver empresa_id = 5 o empresa_id = a1b2c3d4-e5f6-7890-abcd-ef1234567890?
  4. No necesitas unicidad globalempresa_id solo necesita ser único dentro de la tabla mae_empresas, no globalmente

¿Cuándo SÍ usar UUID para referencias?

Cuando la entidad es grande y se referencia entre servicios:

TABLA: mae_clientes
id = UUID('a1b2c3d4-...')  nombre = "Juan Pérez"
id = UUID('e5f6g7h8-...')  nombre = "María García"

Y en trx_pedidos:

TABLA: trx_pedidos
id = UUID('x1y2...')  cliente_id = UUID('a1b2c3d4-...')

¿Por qué UUID aquí?

  1. 10,000+ clientes — necesitas unicidad global
  2. svc-pedidos referencia a svc-clientes — cross-service
  3. UUID se genera sin DB — puedes crearlo en el frontend
  4. UUID no revela volúmenes — no sabes cuántos clientes tienes

Resumen: UUID vs BIGINT

Pregunta Respuesta
¿Es un catálogo pequeño (< 1000 registros)? BIGINT
¿Se referencia entre servicios? UUID
¿Necesitas unicidad global? UUID

Ejemplos por servicio

1. svc-identidad: empresas, usuarios, sucursales

Tabla core.identidad.mae_empresas:

CREATE TABLE core.identidad.mae_empresas (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,  -- BIGINT identity (catálogo pequeño)
    razon_social TEXT NOT NULL,
    ruc TEXT NOT NULL,
    is_activo BOOLEAN DEFAULT TRUE
);

Tabla core.identidad.mae_usuarios:

CREATE TABLE core.identidad.mae_usuarios (
    id UUID PRIMARY KEY,        -- UUID (entidad central)
    username TEXT UNIQUE NOT NULL,
    email TEXT UNIQUE NOT NULL,
    password TEXT NOT NULL,     -- hash Argon2id
    password_temp BOOLEAN NOT NULL DEFAULT TRUE,
    entidad_id UUID NOT NULL,   -- FK → mae_entidades (persona natural o jurídica, ADR 0022)
    is_activo BOOLEAN DEFAULT TRUE
);

Tabla core.identidad.rel_usuario_sucursal (relación N:M):

CREATE TABLE core.identidad.rel_usuario_sucursal (
    id UUID PRIMARY KEY,          -- UUID (relación)
    usuario_id UUID NOT NULL,     -- FK intra-servicio (mismo servicio)
    sucursal_id UUID NOT NULL,  -- FK intra-servicio (mismo servicio)
    is_activo BOOLEAN DEFAULT TRUE
);

2. svc-clientes (CRM): mae_clientes

CREATE TABLE crm.clientes.mae_clientes (
    id UUID PRIMARY KEY,        -- UUID (entidad central)
    empresa_id BIGINT NOT NULL, -- BIGINT (catálogo pequeño)
    nombre TEXT NOT NULL,
    email TEXT,
    telefono TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    deleted_at TIMESTAMPTZ NULL
);

3. svc-pedidos (ERP): trx_pedidos

CREATE TABLE erp.pedidos.trx_pedidos (
    id UUID PRIMARY KEY,            -- UUID (entidad del pedido)
    empresa_id BIGINT NOT NULL,     -- BIGINT (catálogo pequeño)
    cliente_id UUID NOT NULL,       -- UUID de svc-clientes (cross-service)
    vendedor_id UUID NOT NULL,      -- UUID de svc-fuerza-ventas (cross-service)
    sucursal_id UUID NOT NULL,      -- UUID de svc-identidad
    usuario_id UUID NOT NULL,       -- UUID de svc-identidad (entidad central)
    fecha_pedido DATE NOT NULL,
    total NUMERIC(12,2) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

¿Por qué cliente_id y sucursal_id son UUID?

Campo Tipo Origen Por qué
cliente_id UUID svc-clientes (otro servicio) Cross-service, entidad grande
vendedor_id UUID svc-fuerza-ventas (otro servicio) Cross-service, entidad grande
sucursal_id UUID svc-identidad Se referencia entre servicios (token/consumidores)
usuario_id UUID svc-identidad (entidad central) Cross-service, se referencia desde muchos servicios

Reglas no negociables

  1. PK = UUID en toda tabla; única excepción el catálogo de tenencia mae_empresas → BIGINT identity
  2. empresa_id = BIGINT en toda tabla
  3. Cross-service = UUID — nunca FK entre servicios
  4. Dentro del mismo servicio = el tipo de la PK destino: FK a mae_empresas es BIGINT (empresa_id); el resto de FKs son UUID

Consecuencias

  • Toda tabla nueva debe tener id UUID PK e empresa_id BIGINT NOT NULL
  • Toda referencia a entidad de otro servicio usa UUID, nunca BIGINT
  • Toda FK dentro del mismo servicio usa BIGINT (catálogo) o UUID (entidad)
  • El frontend recibe UUID en respuestas, nunca IDs numéricos de otros servicios
  • El modelo de datos de svc-identidad que aplica estas reglas está definido en los ADRs 0005 (mae_usuarios UUID PK, rel_usuario_sucursal UUID PK), 0007 (cat_permisos UUID PK) y 0004 (mae_empresas BIGINT PK) de svc-identidad.