Saltar a contenido

Esquema de la base de datos

El corpus de IRIS Core es SQL plano (sin ORM): las migraciones bajo ../db/migrations/ son la única fuente de verdad. Esta página es una referencia legible por humanos generada a partir de esas migraciones. El corpus es neutral — cero campos ni referencias cruzadas de RMC (impuesto por pnpm neutrality).

  • Motor: PostgreSQL 16 + pgvector (halfvec + HNSW), pg_trgm, fuzzystrmatch, unaccent, búsqueda de texto completo en español.
  • Herramienta de migración: dbmate (cada archivo tiene -- migrate:up / -- migrate:down).

Diagrama entidad–relación

erDiagram
    instancia ||--o{ instancia_clase : "classes"
    instancia ||--o{ instancia_persona : "owners/agents"
    instancia ||--o{ anotacion : "events"
    instancia |o--o| instancia : "renewal chain"
    persona ||--o{ instancia_persona : "role"
    clase ||--o{ instancia_clase : ""
    estado ||--o{ instancia : "status"
    tipo_signo ||--o{ instancia : "sign type"
    tipo_anotacion ||--o{ anotacion : "type"
    pais ||--o{ persona : "country"
    raw_source ||--o{ dead_letter : "re-derive handle"
    consumer ||--o{ api_key : "owns"

Corpus (entidades neutrales)

instancia — la solicitud de marca

La entidad central. Clave natural nro_solicitud (D-01); denominacion es un atributo, nunca una clave (D-02). Enriquecida de forma autoritativa por el Buscador; sembrada (solo nulos) por Sheets.

Columna Type Notas
nro_solicitud text PK — clave natural
nro_registro text aparece solo al otorgarse, nullable
denominacion text NOT NULL — el texto de la marca
tipo_signo text tipo_signo(codigo)
estado text estado(codigo)
tipo_nombre, subtipo_nombre text tipo/subtipo de INAPI (D-14)
fecha_presentacion, fecha_publicacion, fecha_registro, fecha_vencimiento date las cuatro fechas explícitas (D-14)
traduccion, descripcion_etiqueta, protection_description, imagen_url text atributos de detalle (D-14)
renovada_de, renovada_por text self-FK → instancia(nro_solicitud) — cadena de renovación (D-17)
embedding halfvec(1024) vector semántico, índice HNSW
search_vector tsvector generado — FTS en español sobre unaccent(denominacion)
content_hash text idempotencia (INGEST-05)
created_at, updated_at timestamptz

persona — titulares y agentes

Columna Type Notas
id bigint PK (identidad)
pais text pais(codigo)
identificador text RUT (Chile) o id extranjero, nullable
nombre, apellido text
region, comuna text opcional (D-16)
search_vector tsvector generado, FTS
created_at timestamptz

Clave única uq_persona_pais_identificador (pais, identificador) donde identificador IS NOT NULL; las personas sin id se reconcilian con mejor esfuerzo por (pais, lower(nombre)).

instancia_persona — puente de rol

Columna Type Notas
instancia_nro_solicitud text PK, → instancia
persona_id bigint PK, → persona
rol text titular | representante | ambas

instancia_clase — clases de Niza

Columna Type Notas
instancia_nro_solicitud text PK, → instancia
clase_numero int PK, → clase (1..45)
descripcion, estado text descripción/estado por clase

anotacion — eventos del Estado-Diario (solo-anexado)

Columna Type Notas
id bigint PK (identidad)
instancia_nro_solicitud text instancia (un padre faltante se convierte en stub/se pone en cuarentena)
tipo text tipo_anotacion(codigo) — M1..M14
fecha, fecha_vencimiento date
observacion, seccion, seccion_nombre text
created_at timestamptz

Clave de deduplicación uq_anotacion_event (instancia_nro_solicitud, tipo, fecha, md5(coalesce(observacion,''))) — un evento repetido es un DO NOTHING limpio (los eventos nunca mutan).


Catálogos

Tabla PK Otros
estado codigo nombre — conjunto controlado de estados
tipo_signo codigo nombre
tipo_anotacion codigo seccion_nombre — M1..M14
pais codigo nombre — conjunto ISO completo + códigos obsoletos
clase numero nombre — clases de Niza, CHECK (numero BETWEEN 1 AND 45)

Un valor de catálogo desconocido se acepta como null + se registra en log (D-09) — un nuevo valor de INAPI nunca detiene la ingesta.


Ingesta / operaciones

Tabla Clave Propósito
sync_state source (PK) cursor de reanudación por fuente (cursor jsonb)
movement_log id feed de cambios solo-anexado (entity_type, entity_id, change_type, source, occurred_at, content_hash) — alimenta la lista de trabajo del Buscador
raw_source id bytes en crudo retenidos (storage_path bajo RAW_STORAGE_DIR, content_type, size) — re-derivabilidad (D-10)
dead_letter id cuarentena (source, reason, raw_source_idraw_source, payload jsonb) — no aborta (D-07)
sync_run id historial de ejecuciones (source, started_at, finished_at, status ok\|failed\|running, processed, changed, quarantined, error) — OBS-03

Control de acceso

Tabla Clave Propósito
consumer id organización/tenant (name UNIQUE)
api_key id consumer_idconsumer; key_hash (sha256, UNIQUE — nunca en texto plano), prefix, scopes text[], monthly_quota int (>= 0), expires_at, rotated_at, revoked_at

Scopes: brands:read (/v1/brands, MCP search_brands/get_brand_detail), insights:read (/v1/freshness, /v1/sync-runs). Scopes vacíos = sin restricción. Los contadores de cuota viven en Redis (compartidos REST ↔ MCP), no en Postgres, de modo que la ruta de lectura se mantiene solo-SELECT.


Índices clave

  • FTS: GIN sobre instancia.search_vector y persona.search_vector (español, insensible a acentos vía unaccent).
  • Fuzzy: pg_trgm GIN/GiST sobre la denominación para búsqueda tolerante a errores tipográficos.
  • Vector: HNSW sobre instancia.embedding (halfvec, coseno) para similitud semántica.
  • Reconcile: parcial idx_persona_noident_reconcile (pais, lower(nombre)) WHERE identificador IS NULL.
  • Claves naturales / dedup: uq_anotacion_event, uq_persona_pais_identificador, más las PKs de las tablas anteriores.

Las migraciones se aplican vía dbmate up; el DDL completo (índices, columnas generadas, orden de FK, configuración de FTS en español) vive en ../db/migrations/.