Evaluación de proveedores de web search / fetch para Driftless
Base:staging — es donde vive el Radar, el warehouse GTM y los cuatro
Source Packs. main no tiene apps/api/src/radar/.
Estado: el núcleo provider-neutral está implementado y verde; los
adapters y el benchmark no, y no pueden estarlo sin capturas reales contra
cuentas de pago.
Estado por tarjeta
Radar + chat: 705 tests en verde, 0 fallos.
Todo lo implementado es provider-neutral: nada de esto cambia según quién
gane el benchmark, que es exactamente lo que permite decidir el proveedor al
final en vez de al principio.
Fecha de los precios: 2026-08-01, verificables en las fuentes del final.
Proyecto: independiente. No toca la arquitectura DeepSeek/Luna ni el
gateway de inferencia.
Artefactos:
0. Frontera del proyecto (lo primero, porque es lo que más fácil se rompe)
Este proyecto no comparte código, despliegue ni cadencia con la arquitectura de inferencia:- No toca
libs/model-gatewayniapps/api/src/agent-runs/model-gateway.service.ts. Un proveedor de web search no es un modelo. No entra al catálogo de modelos, no usaprovider-credential.service, no aparece enmodel-list, no consume el presupuesto de tokens. - No bloquea el gateway de inferencia. Toda llamada web es asíncrona respecto
del stream del chat y tiene
AbortSignal. Si el proveedor web cae, el chat responde con lo que hay en el almacén y lo dice; nunca se queda esperando. - Vive en
apps/api/src/radar/, bajo el mismo patrón port/adapter que ya existe paraDiscoveryProviderPortyEndpointVerifierPort, y bajo el mismo candado (radar-architecture.spec.ts). - El contrato es provider-neutral por construcción. El propio candado ya
exige que
ports/discovery-provider.port.tsno mencione aparallelni aexa; el nuevo port hereda esa regla.
Nota sobre las referencias del encargo
docs/architecture/commercial-intelligence-chat-plan.md no existe en ninguna
rama. El resto del encargo sí: apps/api/src/radar/** vive en staging
(80 commits por delante de main), y este documento está anclado ahí, más el
contexto Driftless del área InteligenciaComercial
(commercial-intelligence-system-dictionary, fabrica-de-fuentes-arquitectura,
gtm-claim-vocabulary-allowlist, gtm-dynamic-freshness-scheduling,
opportunity-flow-research-contracts, radar-comercial-parallel-roadmap).
Una diferencia que importa para el golden set: COFEPRIS no está en staging
— vive en work/cofepris-warehouse, tres commits por delante. Los Source Packs
registrados en staging son denue, iieg-jalisco, dirind y company-site.
Consecuencia de diseño, no de redacción: la tabla de autoridad por
claim_type (§5.5) se construye desde los packs realmente registrados, no
desde una lista fija. Si no lo hiciera, los diez casos forbidden del golden
set que citan COFEPRIS estarían protegiendo un registro que en staging todavía
no existe — y “protegido por un pack ausente” es indistinguible de “no
protegido”.
0.1 Qué ya existe en staging y qué añade este diseño
Esta sección es la que decide el tamaño real del trabajo. La parte cara ya está
construida.
Ya existe (y el diseño lo reutiliza tal cual)
El hallazgo
ParallelAdapter en staging llama solo a /v1beta/findall/* y
/v1/monitors. Las dos primitivas de $0.001 —Search y Extract— no se usan
en ninguna parte del repo.
Es decir: hoy el sistema solo sabe comprarle a Parallel su producto caro
(FindAll: 0.15/match en core). El comentario de
noteWarehouseCoverage lo dice con la factura en la mano — “a $6 Parallel run
for 59 companies”. Las primitivas baratas ya están contratadas, cubiertas por
la misma PARALLEL_API_KEY desplegada, y sin usar.
Lo que este diseño añade
Cómo encaja con el aviso de cobertura que ya existe
noteWarehouseCoverage() es informativo a propósito: su comentario advierte
que nunca debe bloquear ni descontar la cotización, porque un tropiezo del
warehouse no puede romper el flujo de dinero que tiene al lado. Ese diseño se
respeta.
El router no lo convierte en compuerta. Vive una capa abajo, en adquisición, y
lo que hace es darle al aviso una tercera opción que hoy no existe. Hoy la
cotización es binaria: pagas la búsqueda nueva, o nada. Con las primitivas
baratas hay un escalón intermedio:
Ya tengo 340 empresas de Jalisco en el almacén. · Rellenar huecos de las que ya tengo — ~0 créditos · Búsqueda nueva y exhaustiva — 25 créditosEse escalón intermedio es el producto de este proyecto. El ahorro de centavos por request es secundario; lo que cambia es la conversación.
0.2 Alcance: qué NO hace este proyecto
Escrito explícitamente porque tres ideas adyacentes salieron durante el diseño, son buenas, y ninguna entra. Dejarlas anotadas aquí evita que reaparezcan como “mejoras” a mitad de implementación.
Lo que sí entra: el port, sus adapters, el router de huecos, la ingesta
gobernada y la evaluación. Nada más.
La compuerta de costo, versión mínima
La pregunta “¿cómo hago que Parallel solo se use cuando toca?” se resuelve dentro del router, sin motor de políticas y sin tocar nada compartido. El router arma el cinturón; el modelo no elige escalar:
Dos propiedades que esto compra gratis:
- La escalada se demuestra, no se afirma. El “escalón anterior falló” no es
lo que el agente dice: es una consulta a
gtm_provider_attempts, que ya registraoutcomepor intento. El agente no puede convencerse a sí mismo de que ya intentó. - Compone hacia adelante en vez de refactorizarse. Las herramientas se
declaran con el
costClasscorrecto desde el día uno. El día que se enciendatool-policy.ts—su propio comentario ya promete que los reads irán “bounded by the tool’s outputBudget/costClass”— heredan el comportamiento sin tocarlas.
1. Recomendación comparativa
1.1 El veredicto
1.2 Esta recomendación es provisional y falsable
No se asume que Parallel sea la respuesta. Es la entrada por defecto al benchmark, elegida por precio verificado y por costo de integración cero. El shadow benchmark (§11) la degrada automáticamente si ocurre cualquiera de estas tres cosas:citation_coverage < 0.95sobre el golden set — es decir, sus excerpts no producen spans re-derivables contra el artifact almacenado. Sería fatal:toGatewayResult()descarta las claims sin citación, así que ese gasto se tira entero en la serialización.freshness_accuracy < 0.85—publish_dateequivocado es peor que ausente.costo_por_respuesta_util> 1.5× el par Serper+Jina en las categoríasespanol-mexicanoyambigua. Ahí es donde un índice global suele ser delgado y donde Google es fuerte.
fetch(), no de lo que el proveedor declara.
1.3 Por qué el precio por request no decide
Serper es 3× más barato por request que Parallel turbo. Y aun así no es el principal recomendado. La razón está en el §7 (costo por respuesta útil), pero se resume en una frase: Serper devuelve snippets sin fecha ni span, así que cada resultado necesita una segunda llamada para convertirse en evidencia, y una tercera para fecharla. Parallel devuelve las tres cosas en una. Al precio absoluto de ambos —céntimos por cuenta— la diferencia de precio es ruido frente a la diferencia de pasos y de superficie de fallo. Y hay un costo que la tabla de precios nunca muestra: un proveedor nuevo cuesta una revisión legal, un secreto en producción, un adapter, un candado, un runbook y una entrada en el registro de subprocesadores. Parallel ya pagó todo eso.1.4 Descartados, y por qué
1.5 El dato que justifica todo el diseño
En los últimos doce meses, en esta categoría exacta:- Microsoft retiró las Bing Search APIs por completo (2025-08-11).
- Google cerró Custom Search JSON API a clientes nuevos y anunció su retiro (2027-01-01).
- Brave eliminó su capa gratuita (febrero 2026).
- Exa subió el precio de búsqueda un 40 % (marzo 2026: 7 / 1k).
2. Matriz de capacidades
Precios en USD, verificados al 2026-08-01.n/d = el proveedor no lo publica,
que en este documento cuenta como desconocido, nunca como ilimitado.
2.1 Búsqueda
2.2 Fetch / extract
2.3 Gobernanza: licencia, privacidad, región
Esta tabla es la que decide si los bytes pueden persistirse engtm_artifacts — no es un anexo legal, es un campo del contrato
(WebContentLicense).
Regla de arranque: todo adapter nace declarando
{ mayStoreArtifact: false, mayDisplayExcerpt: false, mayRedistribute: false }.
Cada true requiere una cita del contrato en license_note. Un true por
defecto es cómo se persiste algo que no se podía persistir.
3. Arquitectura
ArtifactStoreService, ObservationStoreService,
EvidenceClaimStoreService), el ledger de obstrucciones, el de intentos de
proveedor, el de créditos y el lifecycle de Source Packs ya existen y ya tienen
tests de integración. Lo único genuinamente nuevo es el port, sus adapters y
el router. Ese es el argumento de integración más fuerte del diseño: la parte
cara ya está construida.
3.1 El contrato
Contrato completo y comentado:web-search/web-search-provider.port.ts.
La forma, en seis líneas:
-
estimate()es síncrono y puro. Corre antes de que el humano vea un precio. Si pudiera llamar a la red podría gastar; si pudiera cobrar, cobraría por una cotización. Es la misma razón por la queDiscoveryProviderPort.draftFromBriefestá documentado como compilación, no como búsqueda. DevuelveusdExpectedyusdWorstCase, porque cotizar el caso esperado y comerse la cola es exactamente cómo el piso de margen del 60 % deja de sostenerse sin que nadie lo note. -
fetch()yextract()son verbos distintos.fetch()= tenemos los bytes, los hasheamos, el artifact es de primera mano y el span se re-deriva del blob almacenado.extract()= el proveedor tuvo los bytes y nos da su lectura; el artifact esprovider_responsey el span apunta al markdown del proveedor. Fusionarlos permitiría que una respuesta de proveedor se hiciera pasar por una página que leímos — el tipo exacto de pudrición de procedencia que el Evidence Ledger existe para impedir. -
capabilities().spanKinds. Un adapter que solo devuelve prosa declara[], y el router deja de enviarle huecos de evidencia: la claim resultante se caería igualmente entoGatewayResult(). Mejor no gastar. -
UntrustedTextes un tipo marcado. Todo string que escribió un tercero entra marcado; la única salida esrenderUntrusted(). No es teatro: la tríada letal aquí es real (el Radar lee criterios privados del workspace, ingiere páginas arbitrarias y puede escribir records), y un tipo marcado es la única defensa que no compila si se olvida. -
partial+missing[]obligatorios. Nunca truncar en silencio. La misma doctrina deWAREHOUSE_MAX_LIMIT(que lanza en vez de recortar) y deGatewayResult.truncated. -
FAILURE_CLASS_OFes una tabla de datos. Cada fallo cae en exactamente una de las 12 clases cerradas de obstrucción. Como dato y no comoswitchdentro de uncatch, porque la revisión matutina agrega por clase y un modo de fallo que no se puede archivar es un modo de fallo que nadie cuenta.
3.2 Lo que el port deliberadamente no hace
El proveedor no asignasource_family. La familia se decide por el host
final tras redirecciones, en el dominio (HostClassification). Dos proveedores
que devuelven eleconomista.com.mx devuelven la misma familia y el mismo
origen; contar eso como corroboración fabricaría confianza a partir de una
decisión de routing. Es exactamente el error que corroboration.ts ya evita para
iieg-jalisco vs denue — y la sindicación lo hace peor: un teletipo de agencia
reproducido por cuatro medios es un origen.
4. Routing recomendado
CreditsService.quote() + aprobación humana explícita. La razón es económica y
está en los números: un hueco cuesta ~6.50. Un router que pueda escalar solo de uno a otro convierte una consulta de
6.50 sin que nadie lo autorice — un factor de mil.
4.1 Stop conditions
Se reutilizaGtmStopReason tal cual está. Sin vocabulario nuevo:
Ese mapeo ya existe en
ResearchRunService.STATUS_FOR_REASON, con su política de
reembolso asociada. No se toca.
4.2 Kill switch
Tres niveles, del más rápido al más lento:- Lifecycle del Source Pack —
transitionLifecycle(pack, 'degraded'). El router deja de seleccionarlo. Sin deploy, sin reinicio. La transición ya está bloqueada por la máquina de estados existente. CircuitBreakerpor adapter (ya existe enadapters/resilience.ts): 5 fallos abren el circuito 30 s. Un proveedor caído falla rápido y visible en vez de que cada run queme su presupuesto de reintentos en paralelo.- Variable de entorno
WEB_SEARCH_ENABLED=false— apaga toda la capa. El chat sigue respondiendo desde el almacén y lo dice.
5. Integración: Artifact → Observation → Evidence Claim
5.1 El mapeo, campo por campo
5.2 modalidad y confidence_method — las dos que se confunden
live_verified no se alcanza nunca desde un índice de búsqueda. Un índice
dice lo que el proveedor vio alguna vez, no lo que la página dice ahora. Confundir
esas dos cosas es la definición operativa de Live Query vs Live Observation
en el diccionario del sistema.
5.3 Frescura — cuatro fechas, cuatro significados
Esta es la parte que la evidencia web rompe más seguido, yGtmClaimInput ya
tiene los cuatro campos:
Poner
retrievedAt en observed_at convierte un boletín de 2019 leído hoy en
“observado hoy”. Es un error de una línea con consecuencias de meses, y por eso
el port marca publishedAt como string | null con el comentario explícito de
que null debe sobrevivir.
5.4 TTL por tipo de claim
Alineado con el criterio ya recogido engtm-dynamic-freshness-scheduling: la
cadencia la manda la tasa de cambio observada, no un calendario fijo. Valores de
arranque, a recalibrar con datos:
5.5 La web no suplanta a un registro oficial — dos mecanismos
Mecanismo 1 — tabla de autoridad porclaim_type. Cada claim_type declara
qué familia lo posee. Una claim derivada de web sobre un claim_type de familia
autoritativa:
- nunca se escribe como reemplazo;
- se escribe como candidata a contradicción (
contradictsClaimIdsapuntando a la claim oficial), conattribution_verdict: 'unchecked'; - no puede subir
confidence_methodpor encima deprovider_reported.
gs-094): LinkedIn declara plantilla global y
desactualizada; DENUE declara rango de personal ocupado de la unidad económica.
Contradicen. La salida correcta son dos claims con la contradicción
registrada — no una que pisa a la otra. EvidenceClaimStore es append-only
justamente por esto: no tiene update() ni patch(), solo insert().
Mecanismo 2 — el allowlist de vocabulario que ya existe.
KNOWN_COMPANY_CLAIM_TYPES en chat-tools.ts es un conjunto curado
(scian_activity, staffing_range, location) deliberadamente desacoplado de
lo que los packs cosechan en crudo. Un claim_type derivado de web no aparece
en el chat hasta que se le escriba una rama de describeClaim() y se le admita
explícitamente. La puerta ya está puesta; solo hay que no abrirla por descuido.
5.6 Que el texto web no ejecute instrucciones
Cuatro capas. Ninguna basta sola; la literatura de 2026 es clara en que la mejor defensa publicada aún deja pasar del orden de una de cada diez inyecciones optimizadas. Por eso la capa 4 es la que realmente importa.- Tipo marcado.
UntrustedTextno puede concatenarse a un prompt sin pasar porrenderUntrusted(), que envuelve en un bloque delimitado con preámbulo explícito de “esto es dato, no instrucción”. Olvidarlo no compila. - El modelo nunca ve markdown crudo. Ve líneas renderizadas al estilo del
describeClaim()que la herramienta del almacén ya usa: nombre de empresa + descripción en lenguaje de negocio. Nunca elclaim_type, el JSON del valor, ni el cuerpo de la página. - Detector + rechazo. Un documento que dispare el detector (comentarios HTML
imperativos, texto oculto por CSS, capas de texto en PDF, campos
instructionsen JSON-LD, o cadenas que imiten al sistema) se descarta, y cualquier claim ya derivada de él se marcaattribution_verdict: 'reject'. Se usa el veredicto que ya existe en vez de inventar una 13.ª clase de obstrucción: la taxonomía está cerrada con un CHECK en la base y ampliarla es una migración más una actualización del topic del que se tomó. Si el volumen llega a justificar un reporte agregado, esa migración es una tarjeta aparte (WS-12b), no un efecto colateral. - La capacidad, no la detección. Una claim derivada de web no puede disparar una escritura. No actualiza un record, no aprueba nada, no dispara outbound. Solo un humano, o una claim fundada en el almacén, puede. Esa es la frontera de autorización que el código del agente no puede cruzar — y es la única capa que sigue en pie cuando la detección falla.
6. Modelo de costos
6.0 El punto de partida real
El comentario denoteWarehouseCoverage() guarda la factura que originó todo
esto: una corrida de 0.10 por empresa, y el
resultado es enumeración, no evidencia citable.
Un hueco resuelto con las primitivas baratas cuesta **0.10 por
empresa para volver a enumerarla es comprar dos veces lo mismo.
6.1 Costo interno por consulta
Un hueco típico de una cuenta: 2 búsquedas + 4 extracciones.6.2 Impacto sobre el catálogo de créditos
radar-pricing.ts fija MARGIN_FLOOR = 0.6 y CREDIT_USD = 0.99. La pregunta
que importa: ¿la búsqueda web mueve el piso de margen?
Operación standard: 25 créditos = 7.00,
margen actual 71.7 %.
Conclusión económica, y es la más importante del documento: en la capa barata,
la elección de proveedor no mueve el precio del producto. La diferencia entre
el más caro y el más barato de la tabla es de 1.2 puntos de margen sobre una
holgura de 11.7. Optimizar de 0.001 por cuenta ahorra 24.75.
Por lo tanto la elección debe hacerse por calidad, frescura y fidelidad de
citación — no por precio, siempre que se mantenga en la capa barata. Y el
control económico que sí importa es el que impide que la capa barata escale sola
a FindAll
pro (1/match): ahí sí se juega el margen entero. Ese control es
quote + aprobación, no una optimización de céntimos.
6.3 GtmCostPolicy por pack
parseCostPolicy() lanza si un pack no tiene política. Eso convierte
“tarifar antes de correr” en una garantía mecánica y gratuita: un proveedor nuevo
no puede ejecutarse hasta que alguien escriba su precio.
Un Source Pack por (proveedor, operación) — no uno por proveedor — para que
computeSourcePackCogs() siga siendo honesto cuando búsqueda y extracción tienen
unidades distintas:
egress_bytes y compute_seconds se miden, no se estiman —
computeSourcePackCogs() los factura del uso real. El costo web es
casi todo provider_usd; verlo descompuesto es lo que permite responder “¿subió
el proveedor o subimos nosotros el volumen?“.
6.4 Ledger de uso — sin tabla nueva
gtm_provider_attempts ya tiene exactamente las columnas necesarias:
capability, provider_class, rank, outcome, latency_ms, cost_usd,
detail. Se usa con capability: 'web_search' | 'web_fetch' | 'web_extract' y
provider_class = WebSearchProviderPort.id.
Y ProviderAttemptService.successRates() responde entonces, gratis, la pregunta
que decide la cascada: cost_per_accepted_usd por proveedor y capacidad. Es
la misma mecánica que ya reordena la cascada de contactos. Dos detalles que ya
están bien resueltos ahí y que aquí importan igual:
- El denominador es
invoked, noattempts: un escalón saltado por falta de clave nunca tuvo oportunidad de acertar, y contarlo en su contra puntuaría al proveedor por nuestra configuración. cost_usdesnullcuando el adapter no puede tarifarse, y0cuando es genuinamente gratis. Colapsarlos haría inútil la métrica.
6.5 Proyección a Stripe
La búsqueda web no es un SKU. No hay pack nuevo, ni precio nuevo, ni línea nueva en el checkout. Es COGS dentro de operaciones que ya se cobran:- Entra en
expectedCogsUsdde las operaciones existentes → §6.2. - La compra de créditos sigue igual:
BillingService→checkout.session.completed→CreditsService.purchase(idempotencyKey = payment event id)conorIgnore(), porque la entrega duplicada de Stripe es la norma, no un caso borde. - El único cambio en la proyección es la recalibración semanal: los COGS
medidos incluyen ahora la línea web, y
MARGIN_FLOORse verifica en el spec — violarlo rompe el build en vez de erosionar el negocio en silencio.
6.6 Idempotencia y reintentos
Las llamadas web no se debitan individualmente del ledger. Son COGS dentro de una operación que ya se cobró condebitAndRun. Esto no es un ahorro contable:
es lo que impide que un reintento por 429 le cueste créditos al cliente.
Donde una llamada web sea iniciada por el usuario de forma independiente, va por
debitAndRun con:
withRetry con backoff exponencial y full jitter (ya existe), solo
para 429 y 5xx. Un 4xx distinto de 429 es nuestro bug y se relanza de
inmediato: reintentarlo quema presupuesto y retrasa el error real. Un reintento
reutiliza siempre la misma llave de idempotencia.
7. Matriz de pruebas
Las nueve clases de fixture, las diez métricas y sus umbrales duros están enweb-search/fixtures-and-metrics.md.
Resumen de lo que se prueba y dónde:
8. Tarjetas Driftless
Proyecto creado en Driftless: Web Search Provider Evaluation (b875dad7-50ef-4550-a2d2-b327d47907ca), con las 17 tarjetas cargadas,
sus dependencias, y validate + acceptance por tarjeta.
Base: staging (§0).
La compuerta de costo (§0.2) no es una tarjeta aparte: vive dentro de WS-09,
que es donde se arma el cinturón.
El orden de construcción
Esto es lo que yo construiría, y por qué en este orden:- WS-01 + WS-04 — las primitivas baratas detrás del port. Es la pieza más pequeña y la de mayor retorno: misma clave, mismo vendor, cero revisión legal, y desbloquea todo lo demás. Hoy el sistema literalmente no sabe hacer una búsqueda de $0.001.
- WS-09 — el router de huecos y el cinturón. Es lo que convierte “DENUE primero” de mensaje en regla, lo que crea el escalón intermedio de la cotización, y donde vive la compuerta de costo de §0.2. Aquí está el cambio de producto, no en el ahorro.
- WS-08 + WS-10 + WS-11 + WS-12 — la ingesta gobernada. Sin esto el trabajo web no entra al almacén y lo vuelves a pagar cada vez; con esto, el almacén compone. WS-11 y WS-12 no son opcionales: son la condición para dejar entrar contenido web a un almacén en el que un cliente se apoya.
- WS-03 + WS-05/06 + WS-07 + WS-15/16 — la evaluación formal. Confirma o desmiente al proveedor por defecto. Va después a propósito: el port ya hace que cambiar cueste una línea, así que la evaluación puede correr sin bloquear el valor.
active sin la frontera de autoridad y la de inyección en su
sitio.
9. Rollout
9.1 CI — fixtures, cero llamadas vivas
No es una convención, es un candado (WS-03): los specs de adapter corren con la red denegada, y cualquier intento de salir falla el test. Además, un escaneo estático prohíbe queWEB_SEARCH_*_API_KEY se lea fuera de adapters/, igual
que radar-architecture.spec ya prohíbe PARALLEL_API_KEY en el dominio.
Motivo: un benchmark que a veces sale a la red produce números que dependen del
día. Y un CI que llama a un proveedor de pago tiene una factura que nadie
presupuestó y una dependencia de disponibilidad externa para mergear.
9.2 Shadow benchmark
Todos los candidatos enlifecycle: 'shadow' — corren y nadie consume su
salida. Mínimo 3 días de tráfico real, con tope de presupuesto duro.
Se mide: las 10 métricas, más la reconciliación de costo contra la factura del
proveedor. Divergencia > 5 % entre lo reportado por el adapter y lo facturado es
bloqueante: todo el modelo de créditos descansa en que el COGS medido sea el
real.
9.3 Canary
transitionLifecycle ya impide saltarse etapas: experimental → shadow → active
es la única ruta, con bloqueo pesimista para que dos transiciones concurrentes no
puedan colar un salto ilegal. El canary es, literalmente, promover un pack a
active y dejar los demás en shadow.
Alcance del canary: un workspace, un Opportunity Flow, presupuesto acotado. Se
promueve al resto cuando cost_per_accepted_usd y citation_coverage se
sostienen 7 días.
9.4 Fallback
La cascada se ordena porcost_per_accepted_usd observado, no por precio de
lista — política en datos, no en código, igual que la cascada de contactos.
Un proveedor cuyo circuito abre sale de la fila hasta que cierre.
9.5 Rollback
En orden de velocidad:
El nivel 4 es la razón por la que
EvidenceClaimStore no tiene update(). Un
rollback que borra evidencia mala destruye también la prueba de que se sirvió —
que es exactamente lo que hace falta para responderle a un cliente.
10. Riesgos
10.1 Legales
10.2 Técnicos
10.3 Económicos
10.4 Dos cosas que cambiaron mientras se escribía esto
Ambas se descubrieron revisando el diseño contra el mundo, no contra el código, y las dos invalidan supuestos del documento. Se dejan aquí con fecha porque volverán a envejecer.La web abierta se cerró (verificado 2026-08-01)
Cloudflare bloquea crawlers de IA por defecto, con tres categorías separables —Search, Agent, Training— disponibles para todos los clientes, incluida la capa gratuita, desde el 1 de julio de 2026. Pay-Per-Crawl (un muro 402) evolucionó a Pay-Per-Use, que paga al publicador cuando la IA usa el contenido en una respuesta, no cuando el bot descarga la página. Y hay una fecha a seis semanas: el 15 de septiembre de 2026 empieza el bloqueo por defecto de crawlers mixed-use en cualquier página con anuncios — que son exactamenteeleconomista.com.mx, elfinanciero.com.mx,
milenio.com. La familia directory de la que depende toda la evidencia de
EVENTOS del golden set.
Tres consecuencias, en orden de incomodidad:
- El escalón cero se identifica honestamente, y eso es justo lo que se
bloquea.
DriftlessRadarBot/1.0en eluser-agentes una declaración spoofable; en este régimen no compra acceso, lo niega. El rung gratis que sostiene el modelo de costo se degrada solo, sin que ningún cambio nuestro lo explique — y sin que nada lo mida (§WS-20). robots.txtdejó de ser donde se decide el acceso. Para cerca de la mitad del tráfico de IA en 2026 es una señal que se ignora, y la aplicación se movió a la capa de red. Lo seguimos honrando —es lo correcto y es nuestra política— pero honrarlo ya no basta para entrar.- Invierte parcialmente el §3.1. Ahí se argumenta que
fetch()es superior en procedencia aextract()porque sostenemos los bytes. Sigue siendo cierto — y si nuestro fetch honesto recibe 403 en una porción creciente de la web, la ruta superior en procedencia es la que no funciona. Eso fortalece el caso de comprar fetch a un proveedor con relaciones de crawl propias, no solo por precio.
Signature-Agent, y un directorio JWKS en
/.well-known/http-message-signatures-directory. Respaldado por Cloudflare,
Amazon, Akamai y OpenAI; spec W3C cerrada en mayo de 2026; grupo IETF
constituido en 2026. Cloudflare tiene una categoría Verified AI Agent y una
acción Challenge Agent que pide firma en vez de CAPTCHA.
El manifiesto del escalón cero ya dice “un scraper que miente sobre quién es no
puede decir que practica cortesía”. Web Bot Auth es esa misma honestidad,
criptográfica en vez de declarativa: deja de ser una afirmación y pasa a ser
verificable. Y el repo ya sirve /.well-known/agent-skills/index.json, así que
publicar el JWKS extiende un patrón que existe.
Lo que la firma NO compra: permiso para ignorar robots.txt. Prueba QUIÉN
somos; no autoriza qué podemos tomar.
El marco legal mexicano es otro (verificado 2026-08-01)
LaLFPDPPP que este documento citaba es la de 2010. Dejó de existir el 21 de
marzo de 2025, cuando entró en vigor una ley nueva publicada en el DOF el 20
de marzo de 2025, producto de la reforma constitucional de diciembre de 2024.
Las decisiones de producto no cambian —seguimos sin construir direcciones, sin
presentar un buzón general como personal, y redactando datos personales de las
fixtures— pero la cita legal que las justifica sí cambia, y una justificación
que apunta a una ley derogada no justifica nada.
11. Cómo se decide, en una frase
El benchmark ordena por costo por respuesta útil (§4.7 del documento de métricas), no por precio por request; descalifica sin promediar a quien falle cualquiera de las cuatro puertas duras (citación ≥ 0.95, abstención = 1.00, contención de inyección = 1.00,partial honesto); y desempata por
precision@10, luego recall, luego freshness_accuracy.
Si Parallel gana, no cambiamos de proveedor y la ganancia es el port, el router y
la gobernanza. Si pierde, cambiamos de proveedor con una línea — que es
exactamente lo que este proyecto existe para hacer posible.
Fuentes de precio (verificadas 2026-08-01)
- Parallel — Pricing · Search API · Extract API · Rate limits
- Exa — Pricing
- Serper.dev · Serper pricing explicado
- Brave Search API — pricing · fin de la capa gratuita, feb. 2026
- Tavily — pricing · comparativa de APIs de búsqueda
- Linkup — pricing
- Perplexity — API pricing
- Firecrawl — pricing
- Jina AI Reader — pricing · Reader API
- Zyte / ScrapingBee — comparativa de precios
- Google Custom Search JSON API — cierre y retiro
- Bing Search APIs — retiro 2025-08-11
- Prompt injection en agentes — panorama 2026
