AACsearch
API de Búsqueda

Filtros, Ordenación y Paginación

Referencia completa para expresiones de filtro, opciones de ordenación, facetado y paginación en las consultas de AACsearch.

AACsearch usa la sintaxis de filtro y ordenación de AACSearch con algunas convenciones específicas del esquema de productos. Esta página es una referencia completa para construir expresiones de filtro, configurar órdenes de clasificación y paginar resultados.

Sintaxis de expresión de filtro

Los filtros se especifican como una cadena usando lógica booleana.

Igualdad

availability:=in_stock
brand:=Sony
categories:=Electrónica
locale:=es

Negación

availability:!=out_of_stock
brand:!=Competidores

Rango (numérico)

price:>50
price:<200
price:[50..200]     # rango inclusivo
price:(50..200)     # rango exclusivo

Pertenencia a array

categories:=[Electrónica, Audio]
brand:=[Sony, Bose, Sennheiser]

Contiene cadena / prefijo

AACSearch admite solo coincidencia exacta de cadena para campos con faceta. Para búsqueda de prefijo, use q con queryBy.

Lógica booleana

# AND (ambas condiciones deben coincidir)
availability:=in_stock && price:<200

# OR (cualquier condición coincide)
brand:=Sony || brand:=Bose

# Agrupación
(brand:=Sony || brand:=Bose) && availability:=in_stock && price:<200

Filtro de tenant (automático)

El manejador público antepone automáticamente:

organization_id:={organizationId}

No necesita añadir este filtro manualmente — siempre se aplica.

Campos de filtro disponibles

El esquema de producto predeterminado incluye estos campos filtrables (marcados como facet: true):

CampoTipoEjemplo de filtro
brandstringbrand:=Sony
categoriesstring[]categories:=[Electrónica, Audio]
availabilitystringavailability:=in_stock
pricefloatprice:[10..500]
sale_pricefloatsale_price:&lt;100
localestringlocale:=es

Facetado

Las facetas calculan la distribución de valores para un campo entre los documentos coincidentes. Úselas para renderizar barras laterales de filtros.

{
	"q": "auriculares",
	"facetBy": "brand,categories,availability"
}

La respuesta incluye facetCounts:

{
	"facetCounts": [
		{
			"fieldName": "brand",
			"counts": [
				{ "value": "Sony", "count": 45 },
				{ "value": "Bose", "count": 28 },
				{ "value": "Sennheiser", "count": 19 }
			]
		},
		{
			"fieldName": "availability",
			"counts": [
				{ "value": "in_stock", "count": 89 },
				{ "value": "out_of_stock", "count": 23 }
			]
		}
	]
}

Facetas con filtros aplicados

Cuando un filtro está activo, los recuentos de facetas reflejan el conjunto de resultados filtrado, no todo el índice:

{
	"q": "*",
	"filterBy": "brand:=Sony",
	"facetBy": "categories,availability"
}

Esto devuelve recuentos de categorías y disponibilidad solo para los productos de Sony.

Ordenación

Ordenación por un solo campo

{ "sortBy": "price:asc" }
{ "sortBy": "price:desc" }
{ "sortBy": "created_at:desc" }
{ "sortBy": "sale_price:asc" }

Ordenación por relevancia primero (predeterminado)

{ "sortBy": "_text_match:desc" }

Este es el predeterminado cuando se omite sortBy.

Ordenación por múltiples campos (desempate)

{ "sortBy": "_text_match:desc,price:asc" }

Ordena primero por puntuación de relevancia; dentro de la misma puntuación, ordena por precio ascendente.

Presets de ordenación comunes

EtiquetaValor de sortBy
Más relevante_text_match:desc
Precio: menor a mayorprice:asc
Precio: mayor a menorprice:desc
Más recientes primerocreated_at:desc
Precio de oferta: menor a mayorsale_price:asc

Paginación

{
	"page": 1, // indexado desde 1, predeterminado: 1
	"perPage": 20 // predeterminado: 10, máx.: 100
}

Campos de respuesta para paginación:

{
	"found": 142, // total de documentos coincidentes
	"page": 1, // página actual
	"outOf": 5000 // total de documentos en el índice (sin filtrar)
}

Calcular el total de páginas:

const totalPages = Math.ceil(results.found / perPage);
const hasMore = results.page * perPage < results.found;

Advertencia de paginación profunda

El rendimiento de AACSearch se degrada con valores grandes de page para índices con más de 100K documentos. Para paginación profunda o exportaciones:

  1. Use valores crecientes de page con un perPage fijo
  2. O implemente paginación basada en cursor ordenando por un campo único (id:asc) y usando filterBy: "id:>lastSeenId"

Selección de campos

Reduzca el tamaño de la respuesta seleccionando solo los campos que necesita:

{
	"includeFields": "id,title,price,brand,availability",
	"excludeFields": "description,tags"
}

Use excludeFields para eliminar campos de texto grandes de las respuestas cuando no los renderiza (por ejemplo, description en un autocompletado de búsqueda mientras se escribe que solo muestra títulos).

Combinar filtros con tokens con ámbito

Cuando se usa un token con ámbito, su scopedFilter se combina con AND con el filterBy del llamador:

scopedFilter del token:  "price:<100"
filterBy del llamador:   "brand:=Sony && availability:=in_stock"
filterBy efectivo:       "brand:=Sony && availability:=in_stock && price:<100"

El filtro del token no puede ser anulado por el filterBy del llamador.

Escaping values

Filter values are parsed by AACSearch's filter expression engine. Most strings work as-is, but a few characters have special meaning and must be backtick-quoted (or backslash-escaped) when they appear inside a value:

CharacterMeaning in the grammarInside a value, write as
,Array element separatorWrap value in `…`
&&AND operatorWrap value in `…`
||OR operatorWrap value in `…`
( )GroupingWrap value in `…`
[ ]Range / array delimitersWrap value in `…`
:Field/operator separatorWrap value in `…`
`The quote character itselfEscape with backslash: \`

Examples:

# Brand name contains a comma — must be quoted
brand:=`Ben & Jerry's`

# Category contains the AND operator literally
categories:=`Home && Garden`

User-controlled values must always be escaped before concatenation. Never interpolate user input into the filter string directly — use the safe filter builder.

Safe filter builder

Hand-concatenating a filter string from user input is a category-1 injection bug. Always build filters from a structured representation. A minimal TypeScript builder:

type AtomicFilter =
  | { field: string; op: "="; value: string | number | boolean }
  | { field: string; op: "!="; value: string | number | boolean }
  | { field: string; op: ">" | "<" | ">=" | "<="; value: number }
  | { field: string; op: "in"; values: Array<string | number> }
  | { field: string; op: "range"; min: number; max: number; inclusive?: boolean };

type FilterTree = AtomicFilter | { and: FilterTree[] } | { or: FilterTree[] };

const FIELD_RE = /^[a-z_][a-z0-9_]*$/;

function escapeValue(v: string | number | boolean): string {
  if (typeof v === "number" || typeof v === "boolean") return String(v);
  return "`" + String(v).replace(/`/g, "\\`") + "`";
}

function fieldOrThrow(field: string): string {
  if (!FIELD_RE.test(field)) throw new Error(`invalid field name: ${field}`);
  return field;
}

export function buildFilter(node: FilterTree): string {
  if ("and" in node) return node.and.map(buildFilter).join(" && ");
  if ("or" in node) return "(" + node.or.map(buildFilter).join(" || ") + ")";
  const f = fieldOrThrow(node.field);
  if (node.op === "in") return `${f}:=[${node.values.map(escapeValue).join(", ")}]`;
  if (node.op === "range") {
    const [lo, hi] = node.inclusive === false ? ["(", ")"] : ["[", "]"];
    return `${f}:${lo}${node.min}..${node.max}${hi}`;
  }
  return `${f}:${node.op}${escapeValue(node.value)}`;
}

The builder enforces two invariants: field names match a strict identifier regex, and every string value is backtick-quoted with inner backticks escaped. Replicate the same shape in your server SDK.

E-commerce filter recipes

Category page (in-stock only)

{
  "filterBy": "categories:=`Audio` && availability:=in_stock",
  "facetBy": "brand,price",
  "sortBy": "_text_match:desc,popularity_score:desc"
}

Multi-brand drill-down

filterBy: "categories:=[`Electronics`, `Audio`] && brand:=[`Sony`, `Bose`] && availability:=in_stock"

Price range with "on sale" branch

filterBy: "categories:=`Audio` && availability:=in_stock && (sale_price:[10..100] || (sale_price:<0 && price:[10..100]))"

Locale-aware listing

filterBy: "locale:=`en` && categories:=`Audio` && availability:=in_stock"

"More like this" (vector hybrid)

{
  "q": "*",
  "vectorQuery": "embedding:([…current vector…], k:50)",
  "filterBy": "id:!=`product-123` && availability:=in_stock && price:[1000..15000]",
  "perPage": 12
}

On this page