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í,/docses 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 texto —
q(onombre) 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 | sí | — | Substring sobre el nombre/razón social del titular (rol titular/ambas), insensible a acentos y mayúsculas. |
representante |
string | sí | — | Substring sobre el nombre del representante (rol representante/ambas), insensible a acentos y mayúsculas. |
rut |
string | sí | — | 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 | sí | — | Clase de Niza, 1–45. |
estado |
string | sí | — | Código de estado (catálogo controlado). |
tipoSigno |
string | sí | — | 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). 0–10000. 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=cafesolo ⇒ búsqueda de texto libre clásica (rankea por relevancia, llevascore).?q=cafe&clase=43⇒ acotar (no es modo avanzado):qse mantiene; el resultado es "marcas Café en clase 43" — el texto rankea yclaselo acota —, ordenadas por relevancia salvosort=explícito (ver Orden).?q=cafe&nombre=nescafe⇒ modo avanzado:qse descarta (llegó un campo competidor); manda el texto denombre=combinado por campos, ordenado por el modo de orden vigente (ver Orden).- Para buscar por denominación dentro del modo avanzado, usa
nombre=(noq=):nombre=es un campo competidor y sí participa delAND.
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 (
qonombre) ⇒ relevancia (RRF). - Sin texto ⇒
fecha_presentacion DESC, nro_solicitud ASC(recientes primero).
Cambio de comportamiento. El listado sin-texto (
?clase=,?estado=solos) cambió su orden por defecto denro_solicitud ASCafecha_presentacion DESC, nro_solicitud ASC(recientes primero). Si necesitas el orden antiguo por número de solicitud, hoy no hay un valor desortpara 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áginacursor=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/antiguasreordenan 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=recientesdevuelve 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=sinq) 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 porclase/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⇒ siempreexact.titular,representante⇒fuzzy(substring insensible a acentos/mayúsculas).nombre⇒ el canal dominante cuando matcheó por varios a la vez, según la jerarquíaexact > 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 0–9 o K (mayúscula).
⭐ Cambio del 2026-09-06: el
rutde 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. Unrutbien formado cuyo DV no calcula devuelve ahora un resultado vacío en vez de400.Error
invalid_rut. Unrutcuya forma no es la de un RUT (letras en el cuerpo, o un DV fuera de0–9/K) devuelve400con{ "error": { "code": "invalid_rut" } }— el sobre de error uniforme. Nunca500ni 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
rutválido que no está en el corpus devuelve un resultado vacío — esto es correcto (cobertura parcial), no un error. Combinarutcontitular/nombrecuando 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
nextCursorsin cambios como parámetrocursor. nextCursor: nullsignifica 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:
offsetes el número de resultados a saltar (0–10000);limites el tamaño de página.- En modo offset,
nextCursoresnull: para avanzar, incrementaoffset(p. ej. página 3 conlimit=20⇒offset=40). UsaestimatedTotal(presente en todos los modos) para saber cuántas páginas hay. - El tope de
offsetes 10000 (protege la base de datos): unoffsetmayor devuelve400 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
sorto conrelevancia/recientes/antiguas, y concursor) 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— losBrandSummaryencontrados, en el mismo orden en que pediste losnros. Es el mismo resumen enriquecido (clases,titular,enrichmentPending) que devuelve el listado deGET /v1/brands. Como no hay texto de búsqueda, no llevanscore,matchedBynimatchedFields.notFound— losnrossolicitados 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
nrosrepetidos se colapsan a uno (aparece una sola vez enitemso ennotFound). - 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
nrosdevuelve422 invalid_input. Un cuerpo connrosvacío o malformado devuelve400 validation_error. - Una unidad de cuota por llamada, sin importar cuántos
nrospidas — 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 1–45, 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 0–9/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
- Primeros pasos — URL base, primera llamada, paginación por keyset, sobre de error.
- Autenticación y scopes —
X-API-Key,brands:read, ciclo de vida de la clave. - Conecta tu agente de IA (MCP) — la misma búsqueda vía la herramienta
search_brands. - Ejemplos de código — clientes REST y MCP listos para copiar y pegar.
- La referencia OpenAPI en vivo en
/docs— el esquema autoritativo por campo.