eBay y Odoo: montando un rastreador de segunda mano en una sesion

De keyset Non Compliant a 65 anuncios reales: credenciales Browse API, un bug de ref() y el primer dispatch real de cuatro scrapers de marketplaces.

He estado construyendo un sistema de busqueda multi-marketplace para productos de segunda mano. La idea es sencilla: describes lo que quieres (una NVIDIA RTX 3060 12GB, por menos de 250 EUR), y cuatro sub-skills salen a buscar en Wallapop, Milanuncios, eBay Espana y Cash Converters. Los resultados aterrizan como registros estructurados en un modulo Odoo 16 llamado ltc_second_hand, puntuados, etiquetados y listos para triaje humano. Este post cubre la sesion donde el sistema funciono de extremo a extremo por primera vez.

Kanban view of second-hand searchesThe search kanban: two active searches grouped by state, with listing counts and hot-lead badges.

El puzzle del keyset de eBay

La Browse API es el unico endpoint publico de busqueda de eBay que todavia funciona. Usa credenciales OAuth 2.0 de tipo client, sin tokens de usuario. Conseguir las keys deberia ser cosa de dos minutos. No lo fue.

Crear la app en developer.ebay.com fue bien. El portal me dio un App ID (Client ID) inmediatamente. Pero cuando intente copiar el Cert ID (Client Secret), la pagina mostro:

Your Keyset is currently disabled. Comply with marketplace deletion/account closure notification process or apply for an exemption.

eBay ahora requiere que cada app gestione o se excluya de las notificaciones de Marketplace Account Deletion. Como nuestro sistema solo lee anuncios publicos via client_credentials y no almacena datos de usuarios de eBay, aplica la exencion. Un clic en "Exempted from Marketplace Account Deletion", guardar, y el keyset se desbloqueo.

Primer token, primera busqueda

Con EBAY_APP_ID y EBAY_CERT_ID configurados en ~/.config/ltc-second-hand/ebay.env, un smoke test rapido confirmo que el flujo OAuth funciona: un Application Access Token de 7200 segundos, y una consulta a la Browse API para "RTX 3060 12GB" en EBAY_ES devolvio 1.518 resultados. El scraper estaba listo.

Search form view with criteria and statsSearch form for RTX 3060 12GB: price target 250 EUR, max 300 EUR, keywords filter, 59 listings and 7 hot leads.

El bug de ref() en la vista de Anuncios

Cuando intente abrir el menu Anuncios en Odoo, la pagina se colgaba. El log de errores no ayudaba al principio, lleno de 404s del favicon por una entrada de filestore que faltaba. El problema real estaba en el context del ir.actions.act_window:

<field name="context">{
    'search_default_hide_reserved': 1,
    'reserved_tag_id': ref('ltc_second_hand.tag_reserved'),
}</field>

El helper ref() solo se evalua durante la carga de datos XML cuando se usa dentro de un atributo eval. Escrito como texto plano del campo, se almacena literalmente como la cadena ref('ltc_second_hand.tag_reserved'). Cuando el cliente web intenta hacer safe_eval de ese context, ref no esta en scope y la accion falla silenciosamente.

La solucion es un cambio de una linea: usar eval en el campo para que el XML ID se resuelva a un entero en tiempo de instalacion:

<field name="context" eval="{
    'search_default_hide_reserved': 1,
    'reserved_tag_id': ref('ltc_second_hand.tag_reserved'),
}"/>

El upgrade que no hizo upgrade

Despues de editar el XML, lance odoo-bin -u ltc_second_hand directamente. Cero output, cero errores, exit code 0. Pero la vista en la base de datos seguia con el context antiguo. Por que?

La configuracion de produccion (odoo.conf) no tiene directiva addons_path. La ruta se construye en el script wrapper /bin/odoo, que recorre /opt/odoo/addons/* y antepone /opt/odoo/custom/apps. Llamar a odoo-bin directamente se saltaba el wrapper, asi que el directorio del modulo era invisible. El log incluso lo decia: "module ltc_second_hand: no manifest file found". Leccion: usar siempre /bin/odoo, nunca odoo-bin directamente.

Primer dispatch real: 65 anuncios, 7 hot leads

With the view fixed and eBay credentials live, I launched the coordinator skill:

/ltc-second-hand
Busco una tarjeta grafica NVidia RTX 3060 12GB, presupuesto < 250 EUR

El dispatcher creo un registro second.hand.search en Odoo y lanzo los cuatro marketplaces en paralelo. Resultados tras 15 segundos:

MarketplaceEstadoAnuncios
Wallapopok40
eBayok19
Milanunciosbloqueado (Cloudflare)0
Cash Convertersok (sin stock)0

Listings kanban grouped by marketplaceListings kanban grouped by marketplace: Wallapop, eBay and Cash Converters cards with thumbnails, prices and scores.

Siete anuncios puntuaron por encima de 0.85 y fueron marcados como hot leads. El mejor resultado: una ASUS RTX 3060 12GB de un ventilador en eBay a 240 EUR, score 0.875. En Wallapop, los precios empezaban en 225 EUR para una Nvidia pelada y 230 EUR para un modelo Zotac. Un anuncio a 70 EUR de una Aorus gritaba estafa o defecto.

Hot lead listing detailDetail of the top hot lead: ASUS RTX 3060 12GB at 240 EUR on eBay, score 0.875, tagged as shop, with one-click actions to open, mark relevant or noise.

Que no funciona todavia

  • Milanuncios is blocked by Cloudflare on every run. The Playwright profile needs a manual session to establish clearance cookies.
  • Cash Converters returns zero results because the site uses Salesforce Commerce Cloud (Demandware) and the URL pattern needs adjustment.
  • Duplicate detection across searches is not implemented yet. Running the same query twice creates duplicate listing records.

Scraping sessions listScraping sessions: OK (green), Blocked by Cloudflare (orange), Error (red). Each row shows marketplace, duration, and listing count.

Lo que aprendi

  1. ref() en XML de Odoo solo se resuelve dentro de eval="...", nunca en contenido de texto plano del campo.
  2. La puerta de compliance de Marketplace Account Deletion de eBay bloquea el acceso a la API hasta que configuras un webhook o solicitas la exencion. Para apps de solo lectura con Browse API, la exencion es el camino correcto.
  3. El wrapper /bin/odoo no es opcional en este servidor. Sin el, el addons path esta incompleto y los upgrades de modulos no hacen nada silenciosamente.
  4. Una RTX 3060 a 70 EUR en Wallapop no es una ganga. Es una trampa.
Cómo personalizo mi Odoo con Claude Code
De 108 adjuntos en una tarea a un buscador en el chatter: 200 líneas de código, dos horas, y las cuatro veces que tropecé por el camino.