Saltar a contenido

Búsqueda por campos

Esta guía documenta la superficie de búsqueda field-scoped de GET /v1/brands (y su gemelo MCP search_brands): la lista completa de parámetros, la semántica AND entre campos, la regla q + campos, la normalización de RUT, el nuevo campo matchedFields[] y el orden configurable con sort=. Todos estos parámetros son aditivos y retrocompatibles: una llamada existente se comporta idéntico salvo el cambio deliberado del orden por defecto sin-texto (ver Orden y sort=).

El esquema completo y siempre actualizado por campo vive en la referencia OpenAPI interactiva en /docs (Scalar). Esta guía explica la semántica de la búsqueda combinada y enlaza a esa referencia — no reformula el esquema. Cuando un campo no está documentado aquí, /docs es autoritativo.

A lo largo de la guía la URL base se escribe como $TARNO_BASE_URL y la clave como $TARNO_API_KEY, y cada llamada lleva el encabezado X-API-Key — igual que en la guía de primeros pasos y la guía de autenticación. El endpoint usa el scope brands:read.

Qué cubre esta guía

GET /v1/brands es una única ruta que sirve dos modos que se combinan libremente:

  • Búsqueda de textoq (o nombre) rankea por los canales de denominación (exact / fuzzy / fonético / semántico, fusionados por RRF).
  • Filtrado por campos — predicados exactos, de rango y de persona (clase, estado, nroSolicitud, titular, rut, …) que acotan el conjunto de resultados.

Todos los parámetros presentes se combinan con AND (ver Semántica AND). La respuesta es una página por keyset — la misma forma { items: BrandSummary[], nextCursor } de primeros pasos.

¿Ya tienes una lista de números de solicitud? Si quieres el resumen de varias marcas conocidas (una cartera, una watchlist) de una sola vez, no busques: usa POST /v1/brands/batch, que las resuelve todas en una llamada — ver Búsqueda por lote.

Parámetros

GET /v1/brands acepta estos parámetros de consulta. Todos son opcionales. Un parámetro multi-valor se repite en la query (?clase=9&clase=25) y hace OR dentro del campo; campos distintos hacen AND entre sí.

Param Tipo Multi-valor Alias Notas
q string no Texto libre (FTS español + fuzzy + fonético + semántico, RRF). Se descarta solo cuando llega un campo competidor (nombre/denominacion/nroSolicitud/nroRegistro) → modo avanzado; los filtros de acotar (clase, estado, tipoSigno, fechaPresentacion*, titular, representante, rut) mantienen q y solo lo acotan (ver regla q + campos).
nombre string no denominacion Texto sobre la denominación, con el mismo motor que q.
denominacion string no nombre Alias de nombre.
nroSolicitud string no Número de solicitud, match exacto.
nroRegistro string no Número de registro, match exacto.
titular string Substring sobre el nombre/razón social del titular (rol titular/ambas), insensible a acentos y mayúsculas.
representante string Substring sobre el nombre del representante (rol representante/ambas), insensible a acentos y mayúsculas.
rut string RUT normalizado (ver RUT); match exacto contra titular y representante.
vigencia enum no vigente Acota titular/representante/rut a la titularidad actual (vigente), a la que ya no lo es (historica) o a ambas (todas). Sin uno de esos tres filtros → 400.
clase integer Clase de Niza, 145.
estado string Código de estado (catálogo controlado).
tipoSigno string Código de tipo de signo (catálogo controlado).
fechaPresentacionDesde YYYY-MM-DD no fechaDesde Límite inferior de la fecha de presentación (fecha_presentacion >=), inclusivo.
fechaPresentacionHasta YYYY-MM-DD no fechaHasta Límite superior de la fecha de presentación (fecha_presentacion <=), inclusivo.
sort enum no Orden de los resultados (ver Orden y sort=). relevance (=relevancia, por defecto) · fechaPresentacion:desc/:asc (=recientes/antiguas) · fechaActualizacion:desc/:asc · denominacion:asc/:desc · estado/estado:asc/estado:desc.
limit integer no Tamaño de página. Por defecto 20, máximo 100.
cursor string no Cursor de keyset opaco (ver paginación). Trátalo como una caja negra. Excluyente con offset.
offset integer no Desplazamiento de paginación por offset (ver paginación). 010000. Solo con un sort no de relevancia; excluyente con cursor.

Un valor de sort fuera del enum devuelve 400 validation_error (el sobre de error uniforme). Un rut sintácticamente inválido devuelve 400 invalid_rut (ver RUT). Combinaciones de paginación inválidas (p. ej. cursor + offset, u offset con sort=relevance) devuelven 400 validation_error (ver paginación).

Titularidad actual, y cómo pedir la anterior

titular, representante y rut casan por defecto con quien lo es hoy. Una marca que cambió de manos deja de aparecer al buscar por su dueño anterior — antes aparecía, y era una respuesta equivocada: devolvía una cartera que esa persona ya no tiene.

vigencia cambia la pregunta:

valor qué devuelve
vigente (defecto) lo que esa persona tiene ahora
historica lo que tuvo y ya no tiene
todas ambas — el comportamiento anterior, ahora explícito
curl -sS "$TARNO_BASE_URL/v1/brands?titular=Kangol&vigencia=historica" -H "X-API-Key: $TARNO_API_KEY"

Sólo acota esos tres filtros, así que sin ninguno de ellos la respuesta es 400: no filtraría nada, y quien pidiera historica y recibiera el defecto leería marcas actuales como si fueran el histórico.

Quién fue titular antes de una marca concreta vive en personasHistoricas del detalle — ver informes.

Semántica AND

Todos los parámetros presentes se combinan con AND. Cada campo acota más el resultado:

# Marcas en clase 9 O 25 (OR dentro de clase), Y cuyo titular contenga "sonda",
# Y presentadas desde 2020 en adelante (AND entre campos).
curl -sS "$TARNO_BASE_URL/v1/brands?clase=9&clase=25&titular=sonda&fechaPresentacionDesde=2020-01-01" \
  -H "X-API-Key: $TARNO_API_KEY"
  • OR dentro de un campo multi-valor: ?clase=9&clase=25 = clase 9 o 25.
  • AND entre campos distintos: añadir titular= acota a los que además cumplen ese predicado.

No hay OR entre campos ni un DSL con paréntesis en v1 — el AND por campos cubre el caso motivador (abogados de PI acotando por titular/clase/fecha). El conjunto vacío de parámetros field-scoped no cambia el comportamiento respecto del contrato base.

Regla q + campos (modo avanzado)

q se descarta solo cuando llega junto con un campo competidor de texto/identidad — nombre/denominacion (alias), nroSolicitud o nroRegistro—: entonces se activa el modo avanzado y manda la búsqueda combinada por campos. Los filtros de acotar (clase, estado, tipoSigno, fechaPresentacion*, titular, representante, rut) no descartan q: mantienen su búsqueda de texto y solo la acotan con AND. Esta es la interpretación autoritativa del contrato (heredada de la versión de búsqueda combinada):

  • ?q=cafe solo ⇒ búsqueda de texto libre clásica (rankea por relevancia, lleva score).
  • ?q=cafe&clase=43acotar (no es modo avanzado): q se mantiene; el resultado es "marcas Café en clase 43" — el texto rankea y clase lo acota —, ordenadas por relevancia salvo sort= explícito (ver Orden).
  • ?q=cafe&nombre=nescafemodo avanzado: q se descarta (llegó un campo competidor); manda el texto de nombre= combinado por campos, ordenado por el modo de orden vigente (ver Orden).
  • Para buscar por denominación dentro del modo avanzado, usa nombre= (no q=): nombre= es un campo competidor y participa del AND.

En otras palabras: q es el modo simple de una sola caja de texto; en cuanto combinas texto con un campo competidor (nombre/nroSolicitud/nroRegistro), cambia a nombre para el texto. Acotar con clase/estado/fechas/personas no requiere cambiar nada: q sigue vigente y solo se acota.

Orden y sort=

El parámetro sort= elige el orden de los resultados. sort= explícito manda siempre sobre el orden automático:

sort Orden Paginación Notas
relevance (=relevancia, por defecto) Por relevancia (RRF) cuando hay texto; si no, fecha_presentacion DESC keyset Con texto rankea por los canales de denominación; sin texto cae a fecha DESC de forma silenciosa (no es error).
fechaPresentacion:desc (=recientes) fecha_presentacion DESC, nro_solicitud ASC keyset u offset Manda aunque haya texto: el texto sigue filtrando, pero el orden final es por fecha.
fechaPresentacion:asc (=antiguas) fecha_presentacion ASC, nro_solicitud ASC keyset u offset Espejo determinista.
fechaActualizacion:desc / :asc Por última actualización del expediente (updated_at) offset El campo updated_at se refresca en cada re-scrape/upsert de la marca.
denominacion:asc / :desc Alfabético por denominación (insensible a acentos y mayúsculas) offset Orden acento/caso-plegado (Ñ, tildes normalizadas).
estado / estado:asc / estado:desc Agrupación por ciclo de vida del trámite offset No alfabético. Orden: en trámite → observación de fondo → publicada → oposición → concedida → registrada → esperando renovación → rechazada → denegada → desistida → abandonada → anulada → caducado → vencida; estado desconocido/nulo al final.

Un sort fuera de la lista devuelve 400 validation_error. Los alias relevancia/recientes/antiguas siguen siendo válidos (equivalen a relevance/fechaPresentacion:desc/fechaPresentacion:asc).

Orden por defecto (sin sort=):

  • Con texto (q o nombre) ⇒ relevancia (RRF).
  • Sin textofecha_presentacion DESC, nro_solicitud ASC (recientes primero).

Cambio de comportamiento. El listado sin-texto (?clase=, ?estado= solos) cambió su orden por defecto de nro_solicitud ASC a fecha_presentacion DESC, nro_solicitud ASC (recientes primero). Si necesitas el orden antiguo por número de solicitud, hoy no hay un valor de sort para ello; el orden estable disponible es por fecha. Los cursores de keyset en vuelo de listados sin-texto se reinician una vez tras el cambio (una página cursor= vieja de un listado sin-texto vuelve a la página 1; simplemente reinicia la paginación). Ver el changelog para la nota de versión.

Las marcas con fecha_presentacion nula quedan al final en ambos modos (recientes y antiguas).

Techo de alcance con texto (F-4). Cuando hay texto (q/nombre/denominacion), recientes/antiguas reordenan por fecha el conjunto fusionado acotado por relevancia (≈600 marcas: los mejores candidatos de cada canal), no todo el corpus que matchea. Es decir, ?q=<término común>&sort=recientes devuelve las más nuevas dentro de los matches de mayor relevancia, no las más nuevas de todos los matches. El listado sin texto (sort= sin q) no tiene este techo: pagina el corpus completo por fecha sobre el índice de fecha. Si necesitas paginación por fecha sobre todo el corpus, lista sin texto (o acota por clase/estado/fechaPresentacion*).

matchedFields[] — por qué salió esta marca

Cada elemento de items[] puede incluir matchedFields[], un arreglo aditivo que responde a "¿por qué apareció esta marca?". Es opcional y se omite cuando no hay match de identidad/texto (p. ej. un listado puro por ?clase=9, que solo acota).

Cada entrada es { field, kind }:

  • field — el campo de identidad/búsqueda que matcheó. Enum cerrado: nombre, nroSolicitud, nroRegistro, titular, representante, rut. Los filtros de acotar (clase, estado, tipoSigno, fechaPresentacion*) nunca aparecen — son criterios de narrowing, no de match.
  • kind — el canal por el que matcheó. Enum: exact, fuzzy, phonetic, semantic.
  • nroSolicitud, nroRegistro, rut ⇒ siempre exact.
  • titular, representantefuzzy (substring insensible a acentos/mayúsculas).
  • nombre ⇒ el canal dominante cuando matcheó por varios a la vez, según la jerarquía exact > fuzzy > phonetic > semantic. Una sola entrada por campo (nunca una por canal).
{
  "nroSolicitud": "123456",
  "denominacion": "SONDA",
  "score": 0.91,
  "matchedFields": [
    { "field": "nombre", "kind": "exact" },
    { "field": "titular", "kind": "fuzzy" }
  ]
}

matchedFields[] es puramente aditivo: la forma existente de items[] no cambia en ningún otro aspecto. Un cliente que no lo lea sigue funcionando igual.

RUT: normalización, invalid_rut y cobertura

El parámetro rut matchea exacto contra persona.identificador de titular y representante (AND con el resto de predicados). El servidor normaliza el valor antes de comparar, así que aceptamos los tres formatos habituales:

Entrada Se normaliza a
76.123.456-7 76123456-7
761234567 76123456-7
76123456-7 76123456-7

El DV puede ser 09 o K (mayúscula).

Cambio del 2026-09-06: el rut de búsqueda ya NO exige que el módulo 11 cuadre. El corpus guarda 576 personas cuyo identificador no pasa el dígito verificador —arrastran 4.631 marcas, 686 como titular vigente— así que el único valor capaz de encontrarlas era exactamente el que la API rechazaba. El trabajo de un filtro es encontrar lo que está almacenado; el DV es una propiedad del dato guardado, no de la consulta. Un rut bien formado cuyo DV no calcula devuelve ahora un resultado vacío en vez de 400.

Error invalid_rut. Un rut cuya forma no es la de un RUT (letras en el cuerpo, o un DV fuera de 09/K) devuelve 400 con { "error": { "code": "invalid_rut" } } — el sobre de error uniforme. Nunca 500 ni un conjunto de resultados vacío silencioso. Esta validación y este código de error son idénticos en REST y en MCP.

Cobertura parcial del RUT. No todas las marcas del corpus tienen un RUT asociado a su persona. A modo orientativo, alrededor de un ~43% de las marcas tienen RUT en el titular y un ~71% lo tienen en el representante. Un rut válido que no está en el corpus devuelve un resultado vacío — esto es correcto (cobertura parcial), no un error. Combina rut con titular/nombre cuando quieras cubrir marcas sin RUT registrado.

Paginación: keyset y offset

Hay dos modos de paginación. Elige uno; mezclarlos (cursor y offset en la misma petición) devuelve 400 validation_error.

Keyset (por cursor) — el modo por defecto

Estable y sin saltos, para sort=relevance (o sin sort) y para los órdenes por fecha (fechaPresentacion:* / recientes / antiguas). Cada respuesta lleva un nextCursor:

  • Para la página siguiente, reenvía nextCursor sin cambios como parámetro cursor.
  • nextCursor: null significa fin de los resultados — detente.
  • El cursor es opaco (base64url de las claves de orden). Trátalo como una caja negra; su forma interna depende del modo de orden vigente y puede cambiar sin previo aviso.

sort=relevance (o sin sort) pagina solo por keyset: no admite offset.

Offset (por desplazamiento) — para saltar a una página N

Para los órdenes fechaActualizacion:*, denominacion:* y estado[:*] —y, si lo prefieres, también los de fecha— usa offset + limit:

  • offset es el número de resultados a saltar (010000); limit es el tamaño de página.
  • En modo offset, nextCursor es null: para avanzar, incrementa offset (p. ej. página 3 con limit=20offset=40). Usa estimatedTotal (presente en todos los modos) para saber cuántas páginas hay.
  • El tope de offset es 10000 (protege la base de datos): un offset mayor devuelve 400 validation_error. Para llegar más lejos, acota con filtros (clase/estado/fechas/texto).

Reglas (todo lo inválido es 400 validation_error, nunca se ignora en silencio):

Combinación Resultado
cursor y offset a la vez 400 validation_error
offset con sort=relevance (o sin sort y con texto) 400 validation_error (relevancia es solo keyset)
cursor con fechaActualizacion/denominacion/estado 400 validation_error (esos órdenes son solo offset)
offset > 10000 400 validation_error

Compatibilidad: las llamadas actuales (sin sort o con relevancia/recientes/antiguas, y con cursor) siguen funcionando exactamente igual — el modo offset es puramente aditivo.

Consulta primeros pasos para el patrón completo de paginación.

Búsqueda por lote (batch)

Cuando ya tienes una lista de números de solicitud —una cartera, una watchlist— y quieres el resumen de todas de una vez, usa POST /v1/brands/batch en lugar de N llamadas a GET /v1/brands/{nroSolicitud}. Resuelve hasta 100 solicitudes en una llamada que consume una sola unidad de cuota (no una por marca). Mismo scope brands:read.

Cuerpo (application/json): { "nros": [ … ] }.

curl -sS -X POST "$TARNO_BASE_URL/v1/brands/batch" \
  -H "X-API-Key: $TARNO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"nros":["1286842","823470","9999999"]}'

Respuesta (200):

{
  "items": [
    { "nroSolicitud": "1286842", "denominacion": "…", "estado": "…", "clases": [9, 42], "titular": ["…"], "enrichmentPending": false },
    { "nroSolicitud": "823470",  "denominacion": "…", "estado": "…", "clases": [30],     "titular": [],    "enrichmentPending": false }
  ],
  "notFound": ["9999999"],
  "dataAsOf": "2026-07-20T03:17:00.000Z"
}
  • items — los BrandSummary encontrados, en el mismo orden en que pediste los nros. Es el mismo resumen enriquecido (clases, titular, enrichmentPending) que devuelve el listado de GET /v1/brands. Como no hay texto de búsqueda, no llevan score, matchedBy ni matchedFields.
  • notFound — los nros solicitados que no existen en el corpus (en orden de solicitud). No es un error: pediste una marca inexistente y te lo decimos explícitamente.
  • dataAsOf — el mismo sello de frescura opcional que el resto del contrato.

Reglas:

  • Los nros repetidos se colapsan a uno (aparece una sola vez en items o en notFound).
  • Devuelve resumen, no detalle. Para el detalle completo de una marca (clases + personas + anotaciones) usa GET /v1/brands/{nroSolicitud}; el batch está pensado para revisar muchas marcas a la vez, no para el expediente de una.
  • Tope 100. Un batch con más de 100 nros devuelve 422 invalid_input. Un cuerpo con nros vacío o malformado devuelve 400 validation_error.
  • Una unidad de cuota por llamada, sin importar cuántos nros pidas — ese es justamente el punto: evita el N+1 (y el gasto de N unidades de cuota) de N llamadas individuales.

Paridad MCP

REST es equivalente a MCP: misma clave, mismos scopes, mismos esquemas de Zod, misma capa de consultas. La herramienta MCP search_brands acepta exactamente los mismos parámetros field-scoped documentados aquí — incluidos sort, la normalización de rut y el error invalid_rut — con idéntica semántica. La respuesta (incluidos matchedFields[] y el orden) es byte-idéntica a la de REST, verificado con un test de paridad deep-equal contra Postgres real. La búsqueda por lote tiene su propio gemelo, get_brands_batch, con la misma forma { items, notFound, dataAsOf } y el mismo tope de 100 (invalid_input).

Herramienta MCP Equivalente REST
search_brands GET /v1/brands
get_brands_batch POST /v1/brands/batch

Consulta la guía de MCP para el endpoint, el transporte y la autenticación.

Errores

Todos los errores usan el sobre uniforme { error: { code, message, requestId } } — descrito en primeros pasos.

HTTP code Cuándo
400 validation_error Parámetros inválidos (p. ej. sort fuera del enum, clase fuera de 145, fecha malformada, cuerpo de batch con nros vacío).
400 invalid_rut Un rut cuya forma no es la de un RUT (letras en el cuerpo, DV fuera de 09/K). Un DV que no calcula ya no es un error: devuelve vacío.
422 invalid_input Un batch (POST /v1/brands/batch) con más de 100 nros (ver Búsqueda por lote).
401 unauthorized Clave de API ausente, malformada, desconocida o revocada.
403 forbidden Falta el scope brands:read requerido.
429 rate_limited / quota_exceeded Se excedió el límite de ráfaga o la cuota mensual.

Ver también