PTENES
MÓDULO 2.3

🎯 Coordenadas, selectores y bounding boxes

El detalle que le da un aspecto profesional: el bbox real se convierte en el objetivo del cursor en el canvas. Aquí aprenderás el mapeo de coordenadas, los selectores robustos y la región de zoom/resaltado.

7
Temas
~30
Minutos
Inter.
Nivel
Precisión
Tipo
bbox → centro → cursor VIEWPORT 1280×800 {x,y,w,h} WIN_L + sx SHOT_T + sy CANVAS 1920×1080 punta en el centro el bbox real = objetivo del cursor, del resaltado y del zoom
1

📍 Coordenadas relativas al viewport

Las bboxes son relativas al viewport. En el video, se convierten en coordenadas del canvas al sumar el offset de la ventana.

Concepto principal

La captura se ejecuta en un viewport fijo (p. ej., 1280×800). Todas las bbox vienen de ese espacio. En el canvas 1920×1080, la captura de pantalla se dibuja dentro de la ventana y cada punto se mapea.

El mapeo (punto del shot → canvas)
// WIN_L = (1920 - VW) / 2 · SHOT_T = WIN_T + TITLE_H
canvasX = WIN_L + sx;
canvasY = SHOT_T + sy;
// el centro del bbox mapeado = hacia donde se dirige el cursor
📐
WIN_L
centra X
📏
SHOT_T
parte superior + título
🎯
centro
objetivo del cursor
2

📐 Capturar la bbox de cada objetivo

Para cada paso con target, capture.mjs lee el rect y redondea a {x,y,w,h}.

Cómo capture lee la bbox (fragmento real)
const box = evalJSON(`(()=>{
const el = finder(target);
if(!el) return null;
const r = el.getBoundingClientRect();
return{x:Math.round(r.x), y:Math.round(r.y),
w:Math.round(r.width), h:Math.round(r.height)};
})()`);
⚠️
Objetivo no encontrado = bbox null

Si el selector no coincide, capture graba target: null e imprime ! alvo não encontrado no passo X. Es la señal clara de que el selector es incorrecto — corrígelo antes de renderizar.

3

🧷 Selectores robustos

data-testid, role y el texto visible sobreviven al cambio de estado. Los refs @eN no.

✓ Selectores estables
  • ✓ [data-testid="gerar"]
  • ✓ role + nombre accesible
  • ✓ Texto visible: {tag:"button",text:"Gerar"}
  • ✓ Atributos: input[name=height]
✗ Selectores frágiles
  • ✗ Refs @eN en pantallas que cambian
  • ✗ Clases generadas (CSS-in-JS) que cambian en el build
  • ✗ nth-child posicional en un layout dinámico
  • ✗ El texto con acentos y espacios no coincide exactamente
💡
Las refs cambian después de «Generar»

En el POC, cuando apareció el enlace "descargar PNG", todas las refs se desplazaron. Por eso, en un estado mutable, usa CSS selectors o {tag,text} — y vuelve a tomar una captura cuando el DOM cambie mucho.

4

🎯 El cursor apunta al centro del cuadro

El cursor es un SVG global con el hotspot en la punta. El tween compensa el offset para que la punta caiga en el centro.

Mecánica del cursor
1
El cursor es global, no por escena

Un único #cursor animado en la timeline principal — se desliza continuamente mientras cambian las capturas de pantalla.

2
Hotspot al final

La punta queda en ~(6,3) dentro del SVG de 42px. El tween usa x = alvoX - 6, y = alvoY - 3.

3
Clic = pulse + ripple

scale:.82 yoyo + um #ripple ámbar que se expande sobre el objetivo en el instante del clic.

💡
El easing curvo evita que se vea robótico

El movimiento usa duration:.7, ease:"power3.inOut", comenzando ~0,35s después de que aparece la escena. El cursor llega al target antes de la explicación principal.

5

🔍 Región de zoom / resaltado

La misma bbox posiciona el anillo de resaltado y el acercamiento del zoom. Una coordenada correcta sirve para los tres efectos.

📊 Tres usos de la misma bbox
Cursor
La punta cae en el centro de la caja.
Resaltado (.hlbox)
Anillo ámbar con glow, ~6px de margen, posicionado en la box.
Zoom
transformOrigin en el centro del objetivo.
Zoom en el resultado (zoom:true)
// acercamiento cinematográfico en el «¡ta-chán!»
gsap.a(img, { escala: 1.12,
transformOrigin: `${cx}px ${cy}px`, duration: narrDur });
💡
El resaltado enfoca sin cubrir

O .hlbox es solo box-shadow (anillo + glow): aparece con back.out y pulsa, dirigiendo la mirada al control activo sin tapar nada.

6

📜 Gestionar un objetivo fuera de la pantalla

Si el objetivo está debajo del pliegue, desplázate hasta él antes de capturar y medir; haz ambas cosas después del mismo desplazamiento.

⚠️
El desplazamiento sigue siendo manual

capture.mjs actualmente no desplaza la página. En pantallas largas, controla agent-browser manualmente (scrollIntoView) antes de la captura de pantalla. Agregar scroll/scrollTo es el elemento nº 1 del backlog.

💡
Las bboxes son relativas al viewport

Como la bbox se mide en el viewport actual, coincide con el screenshot de ese scroll, siempre que la captura y la medición se hagan después de lo mismo scrollIntoView.

7

✅ Comprobar que se capturó el elemento correcto

Compara la bbox con el screenshot antes de renderizar. capture imprime cada bbox en el log para una verificación rápida.

Registro de capture.mjs (verificación)
> viewport 1280x800; abriendo http://localhost:8000/
[00] home: shot=00-home.png target=—
[01] prompt: shot=01-prompt.png target=129,252 482x96
[02] size: shot=02-size.png target=179,501 44x26
! objetivo no encontrado en el paso height
✓ Comprobar en la captura
  • ✓ El bbox cae sobre el control visible en el shot
  • ✓ Ningún aviso de "objetivo no encontrado"
  • ✓ w/h plausibles (ni 0×0 ni toda la pantalla)
✗ Señales de problemas
  • ✗ target=— donde debería haber un bbox
  • ✗ El bbox queda sobre el elemento equivocado
  • ✗ Coordenadas fuera de los límites de la captura

🎯 Resumen del módulo

  • ✓ bbox relativo al viewport → canvasX = WIN_L + sx, canvasY = SHOT_T + sy
  • ✓ capture lee getBoundingClientRect; objetivo sin match = bbox null + aviso
  • ✓ prefiere data-testid / role / texto visible a refs @eN
  • ✓ cursor global con hotspot en la punta; clic = pulse + ripple
  • ✓ el mismo bbox sirve para el cursor, el resaltado y el zoom; revísalo en el registro antes de renderizar
Próximo módulo
2.4
⏳ Páginas largas, inputs de React y múltiples estados
Los casos difíciles de las demos reales y lo que ya funciona frente a lo que está en el roadmap.
Ir al módulo 2.4 →