v2 Documentación técnica · Mundial 2026

¿Qué es SACS Arena?

SACS Arena es una API REST que ejecuta un flujo de búsqueda inteligente en 7 pasos contra un índice Azure Cognitive Search, usando OpenAI para clasificar la intención del usuario y devolver resultados contextuales para el Mundial 2026.

El flujo completo ocurre en el servidor — el frontend solo necesita enviar el query y reaccionar al decision que llega en la respuesta para saber qué mostrar. El campo decision puede ser CasoA (servicio dominante), CasoC (torneo dominante) o CasoD (ambiguo / navegar por filtros).

URL base

Producción
https://eventos.sacspro.com

Flujo de llamadas (máximo 3)

Llamada 1: GET /api/cities — ciudades disponibles del sitio
Llamada 2: POST /api/search — query inicial
Llamada 3a: POST /api/search/refine — usuario elige intent (filter1)
Llamada 3b: POST /api/search/filter2 — filtro directo por intent + entidad

Las llamadas 3a y 3b son opcionales según lo que devuelva el servidor en filter1.show y suggestFilter2.show.

Configuración

Todas las variables se configuran en Azure App Service → Configuration → Application Settings. No incluir valores reales en appsettings.json del repositorio.

Variables requeridas

Azure Search

VariableDescripción
AzureSearch__EndpointURL del servicio Azure Search (ej: https://mi-servicio.search.windows.net). La app no arranca si está vacío.
AzureSearch__ApiKeyAPI Key de Azure Search. La app no arranca si está vacío.
AzureSearch__IndexNameNombre del índice en Azure Search donde están los documentos.

OpenAI

VariableDescripción
OpenAI__ApiKeyAPI Key de OpenAI. Requerida — la app no arranca si no está configurada a nivel global o por sitio.
OpenAI__ModelModelo a usar (ej: gpt-4o-mini, gpt-4o). Default: gpt-4o-mini.

Sitios (mínimo un sitio)

VariableDescripción
Sites__0__SiteIdID numérico del sitio. Se envía en cada request como siteId.
Sites__0__NameNombre descriptivo del sitio (solo para logs y dashboard).
Sites__0__ApiKeyAPI Key para autenticar clientes de este sitio. Se envía en el header X-Api-Key.
Sites__0__AllowedOrigins__0Dominio autorizado para CORS. Agregar tantos como sean necesarios incrementando el índice (0, 1, 2…).

Variables opcionales

Búsqueda — cantidad de resultados

VariableDefaultDescripción
AzureSearch__ExploratorySearchTop15Documentos retornados en la búsqueda exploratoria léxica inicial (sin filtros de intent).
AzureSearch__FinalSearchTop10Documentos retornados en la búsqueda final (con filtros OData aplicados). Afecta el total en results.items.
AzureSearch__DirectSearchTop5Candidatos para búsqueda directa de texto en título/intro (evaluación de relevancia directa).
AzureSearch__PrimarySemanticTop20Documentos retornados en la búsqueda semántica primaria (con reranker). Base para la decisión UX.
AzureSearch__SemanticSupplementTop5Documentos adicionales de búsqueda semántica complementaria cuando el resultado principal es escaso.
AzureSearch__ConsoleDisplayTop20Resultados mostrados en la consola CLI del proyecto (no afecta la API).

Semantic (requiere Azure Search tier S1+)

VariableDefaultDescripción
AzureSearch__SemanticConfigurationName""Nombre de la configuración semántica en Azure Search. Si está vacío, el reranker semántico se desactiva completamente. Este valor es global y no se puede sobreescribir por sitio.
AzureSearch__SemanticOnExploratoryfalseActiva el reranker semántico en la fase exploratoria. Aumenta latencia y consumo de cuota semántica.
AzureSearch__SemanticOnFinalSearchfalseActiva el reranker semántico en la búsqueda final (resultados que se muestran al usuario).
AzureSearch__SemanticCallsLoggingEnabledfalseHabilita log detallado de cada llamada semántica (endpoint, score, ms) para auditoría.

Vector Search (Hybrid Search)

VariableDefaultDescripción
AzureSearch__UseVectorSearchfalseActiva búsqueda vectorial (hybrid: léxica + vectorial). Requiere que el índice tenga campos de embeddings configurados con un vector profile.
AzureSearch__VectorProfileName""Nombre del vector profile configurado en Azure Search (ej: default-vector-profile). Requerido si UseVectorSearch es true.
AzureSearch__VectorFieldName"titleVector"Nombre del campo en el índice que contiene los embeddings vectoriales (ej: titleVector).

Scoring

VariableDefaultDescripción
AzureSearch__ScoringProfileName""Nombre del scoring profile en Azure Search (ej: editorial-boost). Aplica boosting editorial a documentos con contentPriority alto y a contenido reciente.

Umbrales de decisión

VariableDefaultDescripción
AzureSearch__RelevanceRerankerThreshold2.0Score mínimo del reranker semántico (escala 0–4) para considerar un documento como relevante. Documentos por debajo de este umbral se descartan del resultado principal.
AzureSearch__StrongRerankerThreshold3.0Score muy alto del reranker. Si el top doc supera este umbral, se muestra directamente sin necesitar confirmar dominancia en facets.
AzureSearch__AmbiguityDominanceThreshold0.40Ratio mínimo de dominancia del intent principal (docs_del_intent_top / total_docs). Si es menor a este valor y no hay score fuerte → flujo va a Filter1 (ambigüedad → CasoD).
AzureSearch__DirectAnswerMinScore0.85Score mínimo para mostrar una respuesta extractiva directa (directAnswer). Si el extracto semántico supera este umbral, se presenta como respuesta destacada antes de los resultados.
AzureSearch__TitleMatchMinRerankerScore2.0Score mínimo del reranker (0–4) para validar que un documento con match en título es suficientemente relevante.
AzureSearch__TitleMatchMinLexicalScore5.0Score mínimo léxico (@search.score) para validar match en título vía búsqueda léxica.
AzureSearch__SemanticSupplementMinScore2.5Score mínimo del reranker para incluir un documento en la búsqueda semántica complementaria. Documentos con score menor se descartan del suplemento.

Rate Limiting

VariableDefaultDescripción
RateLimit__RequestsPerMinute60Requests permitidos por minuto por API Key. Al exceder retorna HTTP 429.

Redis (caché distribuida)

VariableDefaultDescripción
Redis__EnabledfalseActiva Redis como caché distribuida. Si es false, se usa caché en memoria (no se comparte entre instancias). Recomendado activar en producción con múltiples instancias.
Redis__ConnectionString""Connection string de Azure Cache for Redis (ej: mi-redis.redis.cache.windows.net:6380,password=...,ssl=True). Requerido si Redis__Enabled es true.

Slack (notificaciones)

VariableDefaultDescripción
Slack__EnabledfalseActiva envío de notificaciones a Slack (errores críticos, alertas de cobertura, métricas). Si es false, los errores solo se logean localmente.
Slack__WebhookUrl""URL del Incoming Webhook de Slack. Requerido si Slack__Enabled es true.

Monitoreo

VariableDefaultDescripción
ApplicationInsights__ConnectionString""Connection string de Azure Application Insights. Si está vacío, la telemetría de App Insights se desactiva.

Variables de override por sitio

Cada sitio puede sobreescribir los valores globales. Si una variable del sitio no se define → usa el valor global. Para agregar múltiples sitios incrementar el índice (0, 1, 2…).

Excepción: SemanticConfigurationName y las credenciales de Azure Search (Endpoint, ApiKey) son siempre globales — no se pueden sobreescribir por sitio.
Variable por sitioDescripción
Sites__N__OpenAIApiKeyAPI Key de OpenAI específica para este sitio (sobreescribe la global).
Sites__N__OpenAIModelModelo OpenAI para este sitio (ej: gpt-4o para mejor calidad).
Sites__N__ExploratorySearchTopTop exploratorio para este sitio.
Sites__N__FinalSearchTopTop resultados finales para este sitio.
Sites__N__DirectSearchTopTop búsqueda directa para este sitio.
Sites__N__PrimarySemanticTopDocs en búsqueda semántica primaria para este sitio.
Sites__N__SemanticOnExploratoryActivar reranker semántico en exploratoria para este sitio.
Sites__N__SemanticOnFinalSearchActivar reranker semántico en búsqueda final para este sitio.
Sites__N__ScoringProfileNameScoring profile de Azure Search para este sitio.
Sites__N__UseVectorSearchActivar vector search (hybrid) para este sitio.
Sites__N__VectorProfileNamePerfil de vector search para este sitio.
Sites__N__VectorFieldNameCampo de embeddings para este sitio (ej: titleVector).
Sites__N__RelevanceRerankerThresholdUmbral mínimo de reranker (0–4) para considerar doc relevante.
Sites__N__StrongRerankerThresholdUmbral de reranker "muy alto" para mostrar doc sin confirmar dominancia.
Sites__N__AmbiguityDominanceThresholdRatio de dominancia mínimo; si menor → CasoD (ambigüedad).
Sites__N__DirectAnswerMinScoreScore mínimo para mostrar respuesta extractiva directa.
Sites__N__TitleMatchMinRerankerScoreUmbral reranker para validar match de título.
Sites__N__TitleMatchMinLexicalScoreUmbral léxico para validar match de título.
Sites__N__SemanticCallsLimitCuota mensual (30 días) de llamadas semánticas para este sitio. Default: 80000.
Sites__N__WelcomeTemplateTemplate de la pantalla de bienvenida del widget (template1, default, etc.).
Sites__N__GamificationEnabledActiva gamificación en el widget (devuelto en /api/site-config).
Sites__N__NewsletterEnabledActiva suscripción a newsletter en el widget.
Sites__N__ShowFixturesMuestra fixtures/calendario en el widget.
Sites__N__TimeZoneIdTimezone IANA o Windows para calcular la fecha local del sitio (ej: Central Standard Time (Mexico)).
Sites__N__EnableFeedbackHabilita feedback de usuario sobre los resultados en este sitio.
Azure App Service · Application Settings — ejemplo dos sitios
# Sitio 0 (primer sitio)
Sites__0__SiteId                  = 61
Sites__0__Name                    = Posta MX
Sites__0__ApiKey                  = sf-postamx-xxxxxxxx
Sites__0__AllowedOrigins__0       = https://www.postamx.com
Sites__0__SemanticOnExploratory   = true
Sites__0__SemanticOnFinalSearch   = true
Sites__0__SemanticCallsLimit      = 80000
Sites__0__TimeZoneId             = Central Standard Time (Mexico)
Sites__0__GamificationEnabled    = true

# Sitio 1 (segundo sitio — con overrides)
Sites__1__SiteId                  = 42
Sites__1__Name                    = Otro Sitio
Sites__1__ApiKey                  = sf-otrop-xxxxxxxx
Sites__1__AllowedOrigins__0       = https://www.otrosite.com
Sites__1__OpenAIModel             = gpt-4o        # override del modelo global
Sites__1__FinalSearchTop          = 30            # override del top global
Sites__1__UseVectorSearch         = true          # hybrid search para este sitio

Notas de seguridad

Nunca incluir valores reales en appsettings.json del repositorio. Usar siempre variables de entorno en Azure App Service.
Generar API Keys únicas por cliente.
PowerShell · generar API Key
$site = "nombrecliente"
$key  = [System.Guid]::NewGuid().ToString("N").Substring(0, 8)
Write-Host "sf-$site-$key"
Rotar las API Keys periódicamente. Al rotar: actualizar en Azure App Service → Restart → notificar al cliente el nuevo valor.
AllowedOrigins debe contener solo dominios de producción. Para desarrollo local agregar http://localhost:3000 y recordar quitarlo antes del deploy a producción.

Autenticación

Todos los endpoints de búsqueda requieren dos headers HTTP:

HeaderDescripciónEjemplo
X-Api-KeyAPI Key asignada al sitiosf-postamx-a3b5c7d9
X-Site-IdID numérico del sitio61
Excepción: Los endpoints /api/dashboard/* solo requieren X-Api-Key, no X-Site-Id.

Códigos de error

CódigoDescripción
400Query vacío, demasiado largo o SiteId inválido
401API Key no enviada
403API Key inválida o dominio no autorizado
404SiteId no encontrado en configuración
429Rate limit excedido (60 req/min por API Key)
500Error interno del servidor

Formato de error

JSON · Error 401
{
  "error": "API Key requerida",
  "header": "X-Api-Key"
}

Flujo de búsqueda — 7 pasos

El flujo completo se ejecuta en SearchFlowOrchestrator.ExecuteAsync(). Cada paso alimenta al siguiente. El frontend recibe un único JSON con todos los bloques y solo necesita respetar los flags show de cada sección.

Optimizaciones automáticas: si el request llega con userExplicitlySelected: true y un intent ya elegido, el orquestador hace early return directo a la búsqueda final sin ejecutar QU ni exploratoria completa. Si viene de una sugerencia cacheada o paginación, también toma atajos para reducir latencia y consumo de cuota semántica.
1

Query Understanding — OpenAI

QueryUnderstandingService.AnalyzeAsync() envía el query a OpenAI y recibe:

CampoDescripción
intentPrimaryIntent principal del torneo detectado (del catálogo de 23 valores)
intentServiceIntent de servicio detectado (del catálogo de 15 valores), si aplica
isServiceContenttrue si el query busca información de servicio local (movilidad, fan zones, etc.)
confidenceConfianza del modelo en la clasificación (0–1). >= 0.70 se considera confiable.
teamsEquipos detectados en el query (ej: ["México", "Uruguay"])
placesCiudades/sedes detectadas (ej: ["Monterrey"])
peoplePersonas detectadas (jugadores, entrenadores, etc.)

Si la llamada a OpenAI falla, el orquestador hace fallback a LocalQueryClassifier.Classify() — un clasificador por reglas heurísticas que garantiza que siempre haya un intent mínimo para continuar el flujo.

El paso 1 y el paso 2 se ejecutan en paralelo para minimizar latencia.

2

Búsqueda semántica primaria

AzureSearchService.ExecutePrimarySemanticAsync() ejecuta una búsqueda contra Azure Search con filtro base siteid eq {SiteId} (sin filtros de intent) usando el reranker semántico si está configurado.

Retorna:

  • Documentos con rerankerScore (escala 0–4) y score léxico
  • Facets de intentPrimary e intentService — distribución de intents en resultados
  • Extracto de respuesta directa (captions) si el reranker semántico lo genera
3

Facets globales (caché Redis)

Se obtienen los facets globales del sitio (distribución de documentos por intent en todo el índice) desde caché Redis. Se usan para construir las opciones de navegación en filter1.otherOptions — la sección "otras categorías" que aparece cuando el usuario navega sin un query específico.

Si Redis no está configurado, se calculan en memoria por cada request (mayor latencia). El TTL de caché de facets globales es de varios minutos para balancear frescura y rendimiento.

4

SemanticDecisionEngine → FlowState

SemanticDecisionEngine.Decide() evalúa los resultados del paso 2 y la clasificación del paso 1 para producir un FlowState que determina qué bloque mostrar.

FlowStateCondiciónDecision final
DirectAnswerExtracto semántico con score >= DirectAnswerMinScoreCasoC (con bloque directAnswer visible)
Results (servicio)Dominancia >= umbral o QU confidence >= 0.70, intent es servicioCasoA (isServicePath: true)
Results (torneo)Dominancia >= umbral o QU confidence >= 0.70, intent es torneoCasoC (isServicePath: false)
Filter1Hay docs pero dominancia ambigua (< AmbiguityDominanceThreshold)CasoD (navegación por intents)
EmptyCascadeSin docs relevantes o intent detectado no tiene coberturaCasoD (cascade + OtherOptions)

La dominancia se calcula como: count(docs con intent top) / total_docs_semánticos. Si el intent que detectó QU no aparece en los facets semánticos y confidence >= 0.70 → fuerza EmptyCascade.

5

EmptyCascade (fallback)

Cuando el FlowState es EmptyCascade, el orquestador construye un resultado alternativo:

  • Si QU detectó entidades (equipos, ciudades, personas) → busca por esas entidades directamente
  • Si no hay entidades → presenta OtherOptions con los intents de mayor cobertura del sitio
  • El bloque noContent en el response tiene show: true con mensaje y contenido relacionado
6

FilterSuggestions (chips de refinamiento)

Se construyen chips de refinamiento a partir de los facets semánticos del paso 2: top 4 intents de torneo + top 2 intents de servicio. También se agregan sugerencias QA generadas por QU si no están ya repetidas en los facets.

Estos chips aparecen en el response como filterSuggestions.semantic[] y son opcionales para el frontend — funcionan como atajos para refinar el query activo.

7

Construcción del response

Según el FlowState del paso 4, el orquestador llama al constructor correspondiente:

  • BuildDirectAnswerResult() → CasoC con directAnswer.show: true
  • BuildResultsResult() → CasoA o CasoC con results.items poblados
  • BuildFilter1Result() → CasoD con filter1.show: true y opciones de intent
  • BuildEmptyCascadeResultAsync() → CasoD con noContent.show: true + OtherOptions

Todos los constructores populan siempre filterSuggestions, availableCities y debug independientemente del caso.

Tabla de decisiones completa

CondiciónFlowStateDecisionBloques visibles
Extracto semántico >= DirectAnswerMinScoreDirectAnswerCasoCdirectAnswer, results
Top reranker >= StrongRerankerThresholdResultsCasoA / CasoCresults, filterSuggestions
Dominancia >= AmbiguityDominanceThreshold + clarityResultsCasoA / CasoCresults, filterSuggestions
QU confidence >= 0.70 + intent en facetsResultsCasoA / CasoCresults, filterSuggestions
Hay docs, dominancia ambiguaFilter1CasoDfilter1, results (parciales)
Sin docs relevantes o intent sin coberturaEmptyCascadeCasoDnoContent, filter1.otherOptions

POST /api/search/refine

POST /api/search/refine

Llamar cuando el usuario selecciona una opción del Filter1 (intent). Requiere userSelection no nulo. Devuelve resultados filtrados por ese intent y, si hay entidades disponibles, muestra suggestFilter2.

Llamar cuando filter1.show = true en el response anterior y el usuario hace clic en una de las opciones de filter1.relatedOptions[] o filter1.otherOptions[].
JavaScript · construir userSelection desde filter1
const opcion = data.filter1.relatedOptions[0]

const body = {
  siteId: 61,
  query:  "resultado mexico uruguay",
  activeCity: null,
  userSelection: {
    selectedIntent:         opcion.intent,   // "Resultados y marcadores"
    isServiceIntent:        opcion.isService, // false
    userExplicitlySelected: true,
    selectedTeam:           null,
    selectedPlace:          null,
    selectedPerson:         null
  }
}

Response tras elegir Filter1

filter1.show = false (ya fue elegido)
suggestFilter2.show = true si hay entidades disponibles (equipos, ciudades, personas)
results.items contiene resultados filtrados por el intent elegido

POST /api/search/filter2

POST /api/search/filter2

Filtro directo por intent + entidad (equipo, ciudad o persona) sin re-ejecutar Query Understanding. Más eficiente que /refine cuando ya se conoce el intent y la entidad exacta. Siempre retorna resultados finales (filter1.show y suggestFilter2.show = false).

CampoTipoDescripción
siteIdintegerID del sitio
intentstringIntent del catálogo (ej: "Resultados y marcadores")
isServicebooleantrue si es intent de servicio
activeCitystring?Ciudad activa del filtro (CanonicalValue)
entityTeamstring?Equipo a filtrar (ej: "México"). Solo uno de team/place/person.
entityPlacestring?Ciudad/sede a filtrar (ej: "Monterrey")
entityPersonstring?Persona a filtrar (jugador, entrenador, etc.)
JSON · request filter2 por equipo
{
  "siteId":     61,
  "intent":     "Resultados y marcadores",
  "isService":  false,
  "activeCity": null,
  "entityTeam": "México",
  "entityPlace":null,
  "entityPerson":null
}

GET /api/cities

GET /api/cities

Retorna la lista de ciudades disponibles para filtrar en este sitio. Llamar al inicializar el widget para poblar el selector de ciudad. No requiere parámetros — usa el X-Site-Id del header.

JSON · response
[
  {
    "label":          "Ciudad de México",
    "canonicalValue": "Ciudad de México"   // usar este valor en activeCity
  },
  {
    "label":          "Monterrey",
    "canonicalValue": "Monterrey"
  }
]

GET /api/suggestions

GET /api/suggestions?city=Monterrey

Retorna sugerencias de autocompletado para el campo de búsqueda. Incluye sugerencias textuales, facets recientes y distribución de intents para mostrar al usuario antes de escribir.

ParámetroTipoDescripción
citystring?Filtrar sugerencias por ciudad activa (CanonicalValue)

GET /api/today-news

GET /api/today-news?city=Monterrey

Retorna las últimas 6 noticias del día del sitio, ordenadas por fecha de creación. Usado para poblar la pantalla de bienvenida del widget antes de que el usuario escriba.

GET /api/tag-cloud

GET /api/tag-cloud?city=Monterrey

Retorna una nube de tags con la distribución de documentos por intent del sitio. Usado para mostrar categorías al usuario en la pantalla inicial del widget.

GET /api/site-config

GET /api/site-config

Retorna la configuración pública del sitio: template de bienvenida, flags habilitados (gamificación, newsletter, fixtures), ciudades disponibles. Llamar al inicializar el widget.

JSON · response
{
  "siteId":             61,
  "welcomeTemplate":   "template1",
  "gamificationEnabled":true,
  "newsletterEnabled":  true,
  "showFixtures":       true,
  "enableFeedback":     false,
  "availableCities": [
    { "label": "Ciudad de México", "canonicalValue": "Ciudad de México" }
  ]
}

POST /api/subscribe

POST /api/subscribe

Suscribe un email al newsletter del sitio. Solo activo si NewsletterEnabled: true.

CampoTipoDescripción
emailstringEmail del suscriptor
namestring?Nombre (opcional)
citystring?Ciudad de interés
siteIdintegerID del sitio

Response: { "ok": true, "alreadySubscribed": false }

POST /api/feedback

POST /api/feedback

Registra feedback del usuario sobre los resultados de búsqueda. Solo activo si EnableFeedback: true en la configuración del sitio.

POST /api/telemetry/*

POST /api/telemetry/click
POST /api/telemetry/zero-click

Endpoints de tracking para análisis de comportamiento. El widget los llama automáticamente — no requieren integración manual adicional por parte del cliente.

/click — registra cuando el usuario hace clic en un resultado.
/zero-click — registra cuando la búsqueda no produjo ningún clic.

Modelo: SearchRequest

CampoTipoRequeridoDescripción
siteIdintegerID del sitio
querystringTexto de búsqueda (máx 500 chars). Puede ser "*" para listar sin filtro de texto.
activeCitystring?NoCiudad activa del filtro. Valor de canonicalValue retornado por /api/cities. Si cambia con query activa, el widget relanza la búsqueda.
pageinteger?NoPágina de resultados (default: 1). Solo aplica cuando directFilter: true.
directFilterboolean?Notrue para paginación directa sin re-ejecutar QU ni semántica.
userSelectionUserSelection?NoSiempre null en la primera búsqueda. Ver campos abajo.
suggestionIdinteger?NoID de sugerencia seleccionada del autocompletado. Usa caché si está disponible.
fromQuestionboolean?Notrue cuando el query viene de una pregunta relacionada del response anterior.

Campos de UserSelection

CampoTipoDescripción
selectedIntentstringIntent elegido (valor de option.intent del filter1)
isServiceIntentbooleantrue si es intent de servicio
userExplicitlySelectedbooleanSiempre true cuando el usuario elige. Indica al servidor que use filtro OData exacto (sin OR flexible).
selectedTeamstring?Equipo seleccionado en Filter2 (null en Filter1)
selectedPlacestring?Ciudad/sede seleccionada en Filter2
selectedPersonstring?Persona seleccionada en Filter2

Modelo: SearchResponse

CampoTipoDescripción
siteIdintegerID del sitio
querystringQuery tal como fue recibido
decisionstringCasoA | CasoC | CasoD. Identifica el tipo de resultado para analytics.
decisionReasonstringRazón técnica de la decisión (ej: "dominant_facets:0.72")
activeCitystring?Ciudad activa del filtro aplicado
activeCityLabelstring?Label legible de la ciudad activa
hasCityConflictbooleantrue si el filtro de ciudad activa no produjo resultados (la ciudad no tiene cobertura para este query)
availableCitiesCityDto[]Lista de ciudades configuradas del sitio (igual a /api/cities)
directAnswerDirectAnswerDto?Respuesta extractiva directa. Ver campos abajo.
filter1Filter1BlockNavegación por intents. Ver campos abajo.
suggestFilter2SuggestFilter2DtoSugerencia para refinar por entidad (equipo/ciudad/persona). Ver campos abajo.
filterSuggestionsFilterSuggestionsDtoChips de refinamiento por intent. Ver campos abajo.
noContentNoContentDtoBloque de "sin resultados". Ver campos abajo.
resultsResultsBlockResultados principales. Ver campos abajo.
debugDebugBlockInformación de diagnóstico (QU, métricas, filtro OData final)

DirectAnswerDto

CampoTipoDescripción
showbooleanMostrar el bloque de respuesta directa
textstringTexto de la respuesta extractiva
highlightsstring[]Fragmentos destacados del texto fuente
friendlystring?Slug del documento fuente para construir la URL
othersDocumentDto[]Documentos relacionados complementarios

Filter1Block

CampoTipoDescripción
showbooleanMostrar el bloque de navegación por intents
isNavigationStepboolean?true cuando el usuario navega sin query activo
uiTextstring?Texto introductorio del bloque
relatedTitlestringTítulo para opciones relacionadas con el query (mayor relevancia)
relatedOptionsFilter1Option[]Opciones de intent relacionadas con el query actual
otherTitlestringTítulo para "otras categorías" (opciones de menor relevancia)
otherOptionsFilter1Option[]Resto de intents disponibles del sitio

Filter1Option

CampoTipoDescripción
numberintegerNúmero secuencial de la opción
intentstringValor del intent (del catálogo)
isServicebooleantrue si es intent de servicio
isRecommendedbooleantrue si el motor recomienda esta opción
documentCountintegerCantidad de documentos disponibles para este intent
questionTemplatestringPregunta en lenguaje natural para mostrar al usuario

SuggestFilter2Dto

CampoTipoDescripción
showbooleanHay entidades disponibles para refinar
intentstring?Intent activo (para construir el request filter2)
isServicebooleantrue si el intent activo es de servicio

FilterSuggestionsDto

CampoTipoDescripción
semanticSuggestionChip[]Chips de intent primario/servicio (top facets semánticos). Siempre presentes.
qaSuggestionChip[]Sugerencias de preguntas relacionadas generadas por QU

NoContentDto

CampoTipoDescripción
showbooleanMostrar bloque "sin resultados"
detectedIntentstring?Intent que detectó QU pero sin cobertura en el índice
messagestringMensaje de UI para el usuario
cityFilterActivebooleantrue si el filtro de ciudad está activo (puede ser la causa de sin resultados)
activeCityLabelstring?Ciudad activa (para mostrar en el mensaje)
relatedContentDocumentDto[]Contenido alternativo sugerido cuando no hay resultados exactos

ResultsBlock

CampoTipoDescripción
showbooleanMostrar bloque de resultados
uiTextstringTexto introductorio (ej: "Esto es lo que encontré para…")
totalintegerCantidad de items en esta respuesta
itemsDocumentDto[]Lista de documentos. El primer elemento (items[0]) se renderiza con card destacada.
hasMorebooleantrue si hay más páginas disponibles
isServicePathbooleantrue cuando el resultado es de tipo servicio (CasoA). Afecta el estilo visual del widget.

Modelo: DocumentDto

Cada elemento en results.items, directAnswer.others y noContent.relatedContent:

CampoTipoDescripción
idstringID único del documento en el índice
titlestringTítulo de la nota
introstring?Introducción breve (primeros ~150 chars)
introSearchstring?Variante de intro optimizada para búsqueda (puede diferir del intro editorial)
intentstring?Intent principal del documento (intentPrimary o intentService)
intentPrimarystring?Intent de torneo del documento
intentTopicsstring[]Intents de torneo secundarios (sub-categorías)
intentServicestring?Intent de servicio del documento
intentServiceTopicsstring[]Intents de servicio secundarios
isServiceContentbooleantrue = contenido de servicio local; false = contenido de torneo
citystring?Sede editorial del documento (ciudad donde ocurre el evento)
statestring?Estado/región relacionado
entitiesTeamsstring[]Equipos mencionados en el contenido
entitiesPlacesstring[]Lugares mencionados en el contenido
entitiesPeoplestring[]Personas mencionadas (jugadores, entrenadores, etc.)
imagenstring?URL de imagen principal
friendlystring?Slug amigable para construir la URL del artículo
creationDatedatetime?Fecha de publicación en ISO 8601 UTC
isEvergreenbooleantrue = contenido atemporal (no penalizado por antigüedad en scoring)
contentPrioritydouble?Prioridad editorial (0–1). Usada por el scoring profile para boosting.
scoredouble?Score léxico de Azure Search (@search.score)
rerankerScoredouble?Score del reranker semántico (0–4). Presente solo si semantic está activo.
relatedQuestionsTitlestring?Título del bloque de preguntas relacionadas
relatedQuestionsstring[]Lista de preguntas relacionadas generadas editorialmente

Modelo: Filter2Request

Request para POST /api/search/filter2:

CampoTipoRequeridoDescripción
siteIdintegerID del sitio
intentstringIntent del catálogo (ej: "Resultados y marcadores")
isServicebooleantrue si es intent de servicio
activeCitystring?NoCiudad activa del filtro
entityTeamstring?NoEquipo a filtrar. Solo uno de team/place/person debe ser non-null.
entityPlacestring?NoCiudad/sede a filtrar
entityPersonstring?NoPersona a filtrar

Endpoints de Dashboard

Solo requieren el header X-Api-Key. No requieren X-Site-Id.

MétodoEndpointDescripción
GET /api/dashboard/overview?siteId=61 KPIs: total noticias, tendencias activas, producción reciente
GET /api/dashboard/stats?siteId=61 Cobertura de intents: documentos por categoría (torneo y servicio)
GET /api/dashboard/production?siteId=61&days=15 Producción diaria por intent (heatmap de los últimos N días)
GET /api/dashboard/coverage?siteId=61 Intents con baja cobertura (alertas editoriales)
GET /api/dashboard/alerts?siteId=61&staleDays=7 Intents con contenido desactualizado hace más de N días
GET /api/dashboard/sites Lista todos los sitios configurados (sin contenido sensible)