AIREITER

Cuándo reescribir y cuándo aceptar un puente con el navegador: una escalera de tres niveles para integrar código revertido

Última actualización: 2026-07-31 07:17:32

La primera mitad de la ingeniería inversa —separar mecánicamente el código, identificar la familia del algoritmo y verificarlo de forma diferencial— termina con una conclusión: «entiendo qué calcula». Pero esa conclusión aún no se puede desplegar. La lógica sigue atada al runtime original y hay que decidir cómo integrarla: como una función pura que entre en CI o como un proceso externo que alguien tendrá que vigilar. Equivocarse aquí devuelve, con intereses, todo el tiempo ahorrado antes durante la operación.

La visión general en cuatro etapas resume este punto en una línea: «Etapa 4, degradar por capa de transporte». Aquí vamos a desarrollarlo. La idea central cabe en una frase: cada nivel que bajas multiplica por un orden de magnitud la superficie de dependencias, los modos de fallo y el coste de despliegue; por defecto, hay que luchar por subir.

Tres destinos posibles, en un orden innegociable

Solo hay tres formas de integrar esta lógica, ordenadas de mejor a peor. Ese orden debe formar parte de las convenciones del equipo, no decidirse sobre la marcha según «lo primero que consiga funcionar»:

  1. Reescritura nativa. Se reimplementa en el lenguaje objetivo, sin depender del runtime original y usando únicamente la biblioteca estándar. El requisito previo es haber identificado correctamente la familia del algoritmo: una vez superado el paso de fingerprinting, el 90% se toma de la implementación pública, los puntos donde difiere se resuelven por separado y el resultado queda como función pura.

  2. Motor JS local con un fragmento mínimo. A veces purificar toda la lógica cuesta demasiado a corto plazo. En ese caso, conserva una porción pequeña del JavaScript original y ejecuta las pocas decenas de líneas necesarias en un Node/V8 local, nunca la página completa.

  3. Puente pasivo con el navegador. Hay una clase de estado que solo existe en una página real con una sesión iniciada: una firma entregada en runtime, un identificador dinámico ligado a la sesión. La reconstrucción estática no puede reproducirlo y, por ahora, solo puede leerse dentro del navegador. Es una solución temporal: debe quedar señalada en las notas de interfaz y nunca convertirse en la opción predeterminada.

El coste real de cada nivel

La razón para fijar ese orden es sencilla: los costes de estos tres niveles no crecen de forma lineal. Cada uno supone aproximadamente un orden de magnitud más que el anterior.

Nivel

Superficie de dependencias

Modo de fallo

¿Compatible con CI?

Reescritura nativa

Biblioteca estándar, sin procesos externos

La salida no coincide, localizado con un assert

Sí: es una función pura

Motor JS local

Un runtime adicional de Node; el contexto de V8 no es thread-safe y la concurrencia exige un lock

Versión del motor o ausencia de un global del que depende el fragmento

A duras penas: hay que instalar el motor

Puente pasivo con el navegador

Un Chrome real + extensión + una sesión mantenida por una persona + un canal local de loopback

La página no está abierta, la sesión caducó, cambió la estructura o se cerró la pestaña

No: necesita a una persona activa

Los fallos del primer nivel los detecta una prueba unitaria; el fallo del tercero es «hoy el usuario cerró esa pestaña». Convertir algo que podría ser una función pura en una pieza de tercer nivel liga a una persona a cada llamada. Como referencia, una vez identificada la familia del algoritmo, un SDK de firmas ofuscado puede terminar como una implementación independiente de menos de 600 líneas, que solo depende de crypto integrado y opera en el primer nivel. Eso que parecía requerir obligatoriamente un puente con el navegador suele ser, simplemente, un algoritmo que aún no has identificado por completo.

El límite del puente pasivo: una instantánea, nunca intervención activa

Solo hay una forma de que un puente pasivo se descontrole: que empiece a «ayudar». Renovar sesiones automáticamente, iniciar sesión por su cuenta o esperar cargas de página parece cómodo, pero cada automatismo lo desplaza de reenviador a crawler. El perímetro debe ser extremadamente estrecho. Estas restricciones se aprendieron por las malas:

Una única lectura puntual; nunca modificar la página. Consulta una sola vez las cookies, el estado de sesión y el runtime de una página que ya está abierta. No crees pestañas, no recargues, no navegues, no enfoques la página y no hagas polling mientras esperas. Si falta la página, falta la página: no la abras por el usuario.

Devolver un error explícito en cuanto falte algo. Si no hay una pestaña coincidente, devuelve tab_unavailable; si la página existe pero no hay sesión iniciada, not_logged_in; si existe la sesión pero el runtime no está listo, runtime_unavailable. Cada uno de estos tres códigos representa un estado real y una acción siguiente concreta —esperar la página, iniciar sesión o cambiar de destino—, en lugar de lanzar un genérico «ha fallado» que el consumidor tenga que interpretar.

El estado sensible no sale nunca del navegador. La extensión no solicita permisos cookies ni webRequest; solo consulta una pestaña coincidente ya abierta, completa la petición dentro del contexto de esa página y elimina campos antes de devolver el resultado. Las cookies y el estado de firma de la página no salen de Chrome en ningún momento, y el canal se conecta por defecto a una dirección local de loopback. El puente transporta resultados, no credenciales.

Por qué 15 scopes apenas cubren unas pocas plataformas

El puente pasivo reenvía peticiones incluidas en una lista blanca: un adaptador restringe ruta, parámetros y referer. La granularidad es la parte menos intuitiva: hay 15 scopes para apenas unas pocas plataformas porque los scopes se definen por «contexto de página», no por «plataforma». El mismo TikTok tiene Creative Center, Top Ads, Creator platform, influencer library y Ads Manager como cinco scopes independientes: cinco estados de sesión y cinco runtimes de página distintos. Haber iniciado sesión en el backend publicitario no te da el runtime de Creator platform. Si cortas una sola porción por plataforma, el primer caso de «tengo sesión en el subsite A, pero no puedo atender el subsite B» te obliga a rehacerlo. Xiaohongshu también se divide en tres scopes: su sitio principal, las rutas equivalentes a la app y su marketplace para creadores.

La planificación sigue la misma granularidad: el lock se aplica únicamente a nivel de familia de plataforma. Las peticiones de una misma familia, como la familia Douyin, se ejecutan en serie porque reutilizan la misma pestaña real y las llamadas concurrentes dentro de un único contexto de página interfieren entre sí. En cambio, familias distintas, como Douyin y Xiaohongshu, corren en paralelo porque son pestañas independientes. Dentro de cada familia, añade además un intervalo mínimo entre peticiones. Si el alcance es demasiado amplio, serializas trabajo que podría ejecutarse en paralelo; si es demasiado fino, chocan las peticiones que comparten pestaña. La familia de plataforma es justo el límite natural de «comparte el mismo runtime de página».

Entrega explícita del inicio de sesión: la única intervención humana permitida

El puente pasivo no maneja la página, pero las sesiones caducan. La solución es concentrar la intervención humana en una sola acción explícita y puntual: un comando interactivo inicia una entrega, el programa abre mediante el sistema operativo la página de negocio correspondiente, espera a que inicies sesión manualmente y a que la página esté lista, y después repite la petición original. Durante todo el proceso, la extensión no pulsa botones, no rellena formularios ni exporta cookies. El inicio de sesión lo haces tú en un navegador real; el programa solo retoma la petición cuando has terminado.

La condición crítica es que nunca debe activarse de forma implícita. Un comando no interactivo —CI o una tarea programada— jamás abre un navegador: devuelve limpiamente un error de sesión y deja que la capa superior decida. La entrega también debe evitar falsos positivos: tras iniciar sesión con éxito, el runtime dispone como máximo de una breve espera adicional —20 segundos en la implementación— antes de emitir un veredicto. Si la página de negocio ya ha redirigido a una página de cuenta que no contiene el contexto de interfaz objetivo, termina inmediatamente la etapa actual. No confundas un «no se puede llegar ahí» determinista con un «todavía está cargando» y esperes inútilmente. Un flujo que permite degradación registra esta etapa como unavailable y continúa, en vez de hacer fallar todo el proceso.

Estados de etapa: completada, omitida a propósito o pendiente de sesión

Todo lo anterior descansa sobre una misma regla: ninguna etapa puede devolver únicamente «éxito» o «fallo». En un pipeline de orquestación, el resultado de cada etapa tiene seis formas: completed (terminada), empty (se ejecutó, pero no hubo datos), ready (preparada, pendiente de envío), skipped (omitida deliberadamente por una regla), unavailable (no disponible en este momento, normalmente porque hace falta una sesión) y blocked (no se cumple una precondición).

«Omitida deliberadamente», «requiere una sesión» y «realmente vacía» son tres señales completamente diferentes. Si devuelves un único resultado opaco, no sabrás si ese vacío era el esperado o si la sesión murió sin que nadie se enterara. El pipeline deja de ser operable. Modela el estado de cada etapa como un enum finito y la capa de orquestación —un script o un modelo— podrá decidir si debe degradar, volver a autenticarse o abortar. Es el mismo principio de los tres códigos de error del puente pasivo, elevado al nivel del flujo.

Usar el modelo para decidir en qué nivel debe vivir una capacidad

Es aquí, y solo aquí, donde el modelo entra en juego con un papel acotado: no purifica la lógica por ti; te ayuda a decidir si debes hacerlo y hasta qué punto. Es una decisión de arquitectura, no una ruptura de firmas. Todo parte de una pregunta: ¿el estado del que depende puede reconstruirse estáticamente o solo existe en runtime? A partir de ahí, se compara el esfuerzo de purificación con la frecuencia de cambio. Distintos pasos exigen cosas distintas al modelo:

Paso

Capacidad necesaria

Elección

model id

Leer el módulo completo para entender la superficie de dependencias

Contexto largo, capaz de leer el grafo de llamadas de una pasada

Kimi K3

kimi-k3

Defender ambos lados de la decisión y cuestionar el «haz que funcione sin más»

Razonamiento sólido; argumenta por qué merece la pena dedicar un día extra a purificar

Claude Opus 5

claude-opus-5

Triaje inicial masivo de decenas o cientos de capacidades

Barato y con alta concurrencia

Claude Sonnet 5

claude-sonnet-5

Atribución tras una degradación

Razonamiento intermedio; explica a partir de un registro de fallos

GPT-5.6 Sol

gpt-5.6-sol

El segundo caso es el más importante. El error más habitual al asignar un nivel es que el modelo adopte tu tono de «haz que funcione sin más» y responda «el puente con el navegador es lo más fácil». No está calculando el coste a largo plazo: te está haciendo eco. Un modelo de razonamiento sólido replica: «este segmento es un hash estándar con una perturbación constante; merece un día de trabajo para dejarlo como función pura y no debería ir al puente». No hace falta creerme: pruébalo. Elige 3 piezas de lógica, al menos 1 cuya ubicación ya sepas que es correcta como control, envía a claude-opus-5 y gpt-5.6-sol el mismo prompt —«propón un nivel, arguméntalo y rebate una migración prematura al puente con el navegador»— y observa una sola cosa: ¿pelea por subirte un nivel o se acomoda en el tercero?

El verdadero freno es el coste de cambiar

Cuatro niveles de modelos de tres proveedores implican tres SDK, tres esquemas de autenticación y tres formatos de error. No compensa reescribir el cliente para cambiar de modelo entre pasos. Por eso la mayoría acaba usando un único modelo para todo y, precisamente en el juicio de ubicación que más necesita razonamiento, utiliza un modelo que solo le da la razón.

AIReiter aplana esa capa: una clave, una interfaz compatible con OpenAI y los cuatro niveles detrás. Cambiar de modelo consiste simplemente en modificar el campo model del cuerpo de la petición.

# Placement argument: the reasoning tier
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<placement prompt + the reversed logic fragment + dependency list>"}]
  }'

# Bulk first-pass triage: change one field
#   "model": "claude-sonnet-5"
# Degradation attribution:
#   "model": "gpt-5.6-sol"

¿Ya usas el SDK de OpenAI? Apunta base_url a https://aireiter.com/api/v1. ¿Usas el SDK de Anthropic? Llama a POST /api/v1/messages con la misma clave. En precio, los modelos Claude tienen un 30% de descuento sobre la tarifa de lista y los modelos GPT cuestan la mitad. El coste de este flujo se concentra en dos puntos: el triaje inicial masivo de decenas o cientos de capacidades —Sonnet, con alto volumen de llamadas— y la entrada larga necesaria para leer un módulo completo y analizar su superficie de dependencias —Kimi, con muchos tokens por llamada—. El triaje masivo usa un modelo Claude, así que el descuento se aplica justo donde está la mayor densidad de trabajo; la argumentación de ubicación mediante razonamiento también usa Claude, con un 30% de descuento. El nivel de contexto largo de Kimi K3 está disponible con la misma clave.

Para cerrar

Integrar lógica obtenida por ingeniería inversa no es un problema técnico: es un problema de costes. La escalera de tres niveles —reescritura nativa > motor JS local > puente pasivo con el navegador— no se puede invertir, porque cada descenso cambia una función pura por un proceso con dependencias externas, intervención humana y una pestaña activa. El puente pasivo no es terreno prohibido, sino un componente temporal con límites estrictos: una instantánea, nunca actividad; un error explícito en cuanto falte la página; el estado sensible no sale del navegador; el inicio de sesión solo llega mediante una entrega explícita; y el estado de las etapas siempre se puede interpretar. Si mantienes esas reglas, es una solución provisional fiable. Si incumples una sola, se convierte en una caja negra que nadie quiere mantener. El modelo ayuda a decidir en qué nivel ubicar una capacidad y a resistir la inercia de «haz que funcione sin más»; para determinar si cada reescritura es correcta, el árbitro es la prueba diferencial, no el modelo.