Unidad 7
Introducción 📜
¿Qué aprenderás en esta unidad? 💡
Actividad 01
Observa funcionando el caso de estudio
👣 Pasos:
- Prepara el proyecto:
- Descarga o clona el código del caso de estudio en tu computador. El código está en este repositorio.
- Abre una terminal en la carpeta raíz del proyecto.
- Ejecuta
npm install
para instalar las dependencias (express
,socket.io
). Haz esto solo la primera vez.
- Inicia el servidor local:
- Abre la carpeta del proyecto en VS Code.
- Abre una terminal integrada en VS Code (View > Terminal).
- En la terminal, ejecuta
npm start
. - Deberías ver el mensaje:
Server is listening on http://localhost:3000
. ¡Pero aún no accedas a esa URL!
- Expón el servidor con Dev Tunnels:
- Selecciona PORTS. Click en Forward a Port (Dev Tunnels).
- En el número de puerto, selecciona
3000
(¿Por qué este?) - En la columna Visibility, selecciona
Public
. Esto permitirá que el túnel sea accesible desde cualquier lugar. - Copia la URL que aparece en la columna Forwarded Address. Esta URL es la que usarás para acceder al servidor desde tu celular.
- Envía esta URL a tu celular. Se verá algo como
https://TU-TENDRAS-UNA-DIFERNTE.use2.devtunnels.ms/
.
- Accede a las aplicaciones:
- En tu Computador: abre un navegador web y ve a la URL:
http://localhost:3000/desktop/
. Deberías ver el canvas de p5.js con un círculo rojo. - En tu Celular: abre un navegador web y ve a la URL que enviaste pero añadiendo /mobile/ al final. Algo así como esto:
https://TU-TENDRAS-UNA-DIFERNTE.use2.devtunnels.ms//mobile/
(Asegúrate de añadir/mobile/
al final). Deberías ver el canvas de p5.js con el texto “Touch to move the circle”.
- En tu Computador: abre un navegador web y ve a la URL:
- Prueba la interacción:
- Toca y mueve el dedo sobre la pantalla de tu celular.
- Observa el navegador de tu computador. El círculo rojo debería moverse siguiendo tu dedo.
- Observa la terminal donde corre
server.js
. Deberías ver mensajes “New client connected”, “Received message => …”, y “Client disconnected” cuando cierras las pestañas.
- Cierra el Port: una vez termines de hacer las pruebas NO OLVIDES CERRAR el puerto.
- ¿Si cerraste el puerto?
Investigación 🔎
Actividad 02
Conceptos clave: Dev Tunnels, JSON y eventos táctiles
👣 Pasos: (Explicación conceptual)
-
El problema de la conexión directa:
- Cuando ejecutas
npm start
, el servidor escucha enlocalhost:3000
.localhost
(o127.0.0.1
) es una dirección especial que siempre se refiere a tu propia máquina. - Si intentaras acceder a
http://localhost:3000
desde tu celular, este buscaría un servidor en el propio celular, no en tu computador. - Podrías usar la IP local de tu computador (ej.
192.168.1.X
), pero esto solo funciona si ambos dispositivos están en la misma red Wi-Fi y no hay firewalls bloqueando. No funcionaría si tu celular usa datos móviles o está en otra red. - Necesitamos una dirección pública y accesible desde cualquier lugar.
- Cuando ejecutas
-
La solución: VS Code Dev Tunnels (Port Forwarding):
- Dev Tunnels actúa como un intermediario seguro. Crea un túnel desde una URL pública en Internet (la que obtuviste, como
https://TU-TENDRAS-UNA-DIFERNTE.use2.devtunnels.ms/
) hasta el puerto3000
de tulocalhost
. - Cuando tu celular (o cualquier cliente en Internet) se conecta a la URL pública de Dev Tunnels, el servicio de Dev Tunnels reenvía esa conexión de forma segura a través del túnel hasta tu servidor Node.js local.
- Del mismo modo, las respuestas de tu servidor local viajan de vuelta por el túnel hasta el servicio Dev Tunnels, que las entrega al cliente (celular/escritorio).
- Analogía: Es como tener un número de teléfono público (la URL de Dev Tunnels) que redirige las llamadas a tu teléfono privado en casa (
localhost:3000
), sin exponer directamente tu número privado.
- Dev Tunnels actúa como un intermediario seguro. Crea un túnel desde una URL pública en Internet (la que obtuviste, como
-
Enviando datos estructurados: JSON:
- Queremos enviar más que un simple número o texto. Necesitamos enviar la posición táctil, que tiene coordenadas
x
ey
. Podríamos enviarlos como “120,250”, pero es mejor estructurarlo. Recuerdas los protocolos ASCII y binario de las unidades anteriores? - Creamos un objeto JavaScript en el cliente móvil:
let touchData = { type: 'touch', x: mouseX, y: mouseY };
. Esto es claro y extensible (podríamos añadir más datos en el futuro). - Sin embargo, Socket.IO (y muchas comunicaciones en red) envían datos como strings (cadenas de texto). No podemos enviar un objeto JavaScript directamente.
JSON.stringify(touchData)
: Convierte el objeto JavaScript en una cadena de texto con formato JSON. Ejemplo:'{"type":"touch","x":120,"y":250}'
. Esta cadena SÍ puede enviarse por la red.JSON.parse(data)
: En el cliente receptor (escritorio), se recibe la cadena JSON. Esta función la convierte de nuevo en un objeto JavaScript utilizable:{ type: 'touch', x: 120, y: 250 }
.
- Queremos enviar más que un simple número o texto. Necesitamos enviar la posición táctil, que tiene coordenadas
-
Capturando la entrada: eventos táctiles en p5.js (
mobile/sketch.js
):- p5.js ofrece funciones específicas que se ejecutan automáticamente cuando ocurren eventos táctiles en un dispositivo móvil (o simulación en escritorio).
touchMoved()
: es la función clave aquí. Se llama continuamente mientras el usuario mantiene un dedo presionado y lo mueve sobre el canvas. Dentro de esta función,mouseX
ymouseY
contienen las coordenadas actuales del toque.- Optimización (
threshold
): el código no envía un mensaje en cada pequeño movimiento detectado portouchMoved()
. Comprueba si el movimiento desde la última vez que se envió (lastTouchX
,lastTouchY
) supera un umbral (threshold
). Esto evita inundar la red con mensajes si el dedo tiembla o se mueve mínimamente, enviando solo cambios significativos. - Otras funciones útiles (no usadas en este caso base, pero relevantes):
touchStarted()
: se llama una vez cuando el usuario toca la pantalla por primera vez.touchEnded()
: se llama una vez cuando el usuario levanta el dedo de la pantalla.
🚀 Tu solución:
¿Por qué es necesario Dev Tunnels y cómo funciona?
Mi servidor corre en localhost:3000
, que solo existe dentro de mi máquina. Dev Tunnels me da una URL pública que cualquiera puede abrir; ese túnel toma las peticiones de Internet y las reenvía a mi puerto 3000. Así mi celular, esté donde esté, llega a mi servidor sin que yo abra puertos ni cambie el router.
¿Por qué uso JSON.stringify en el emisor y JSON.parse en el receptor?
Socket.IO viaja con texto. Con JSON.stringify
convierto mi objeto {x, y}
en una cadena. Cuando llega, JSON.parse
la vuelve a un objeto usable. Evito concatenar valores manualmente y puedo añadir más campos sin romper el formato.
Función de touchMoved y el umbral (threshold)
touchMoved()
se ejecuta cada vez que arrastro el dedo. Si mando un mensaje en cada pixel se saturaría la red; por eso guardo la última posición y solo emito si me moví más que threshold
, enviando menos, pero relevantes.
Otros eventos táctiles en p5.js y posibles usos
touchStarted()
para detectar un tap o presionar un botón virtual.
touchEnded()
para soltar y, por ejemplo, disparar una acción al levantar el dedo.
doubleClicked()
(no táctil puro, pero útil) para gestos tipo doble tap.
Con ellos puedo hacer botones, sliders o reconocer gestos sencillos sin librerías extras.
Dev Tunnels vs. IP local Con IP local funciona rápido y sin terceros, pero solo dentro de la misma red y a veces el firewall bloquea. Dev Tunnels atraviesa redes y datos móviles, no requiere configurar el router, pero depende de un servicio externo y añade un poco de latencia.
Actividad 03
Análisis del servidor puente (server.js
)
👣 Pasos: (Análisis del código)
const express = require('express'); // Framework para servidor webconst http = require('http'); // Módulo HTTP base de Node.jsconst socketIO = require('socket.io'); // Librería para WebSockets
const app = express(); // Crea la aplicación Expressconst server = http.createServer(app); // Crea el servidor HTTP usando Expressconst io = socketIO(server); // Vincula Socket.IO al servidor HTTPconst port = 3000; // Puerto en el que escuchará LOCALMENTE
// Sirve archivos estáticos desde la carpeta 'public'app.use(express.static('public'));
// Evento: se dispara cuando un nuevo cliente (navegador) se conecta vía Socket.IOio.on('connection', (socket) => { console.log('New client connected - ID:', socket.id);
// Evento: se dispara cuando ESE cliente envía un mensaje llamado 'message' socket.on('message', (message) => { console.log(`Received message => ${message} from ID: ${socket.id}`); // Retransmite el mensaje recibido a TODOS los OTROS clientes conectados socket.broadcast.emit('message', message); });
// Evento: se dispara cuando ESE cliente se desconecta socket.on('disconnect', () => { console.log('Client disconnected - ID:', socket.id); });});
// Inicia el servidor y lo pone a escuchar en el puerto local especificadoserver.listen(port, () => { console.log(`Server is listening on http://localhost:${port}`);});
🚀 Tu solución:
Análisis del código server.js
Setup inicial del servidor
const express = require('express');const http = require('http');const socketIO = require('socket.io');
const app = express();const server = http.createServer(app);const io = socketIO(server);const port = 3000;
✅ ¿Qué está pasando aquí?
express
: crea el servidor web.http
: permite crear un servidor base compatible con WebSockets.socket.io
: añade soporte para comunicación en tiempo real.- Se establece el puerto 3000 como punto de acceso local (que será publicado por Dev Tunnels).
Servir archivos estáticos
app.use(express.static('public'));
✅ ¿Qué hace esto?
Esta línea le dice a Express que sirva automáticamente los archivos de la carpeta public/
. Es decir, si visitas http://localhost:3000/desktop/index.html
, el servidor busca directamente public/desktop/index.html
.
🔁 Comparación con app.get()
(Unidad 6):
app.get('/ruta', ...)
sirve rutas específicas y manuales.express.static()
automatiza todo el proceso: se sirve todo el contenido estático según las carpetas y archivos.
🔌 Conexión de clientes vía WebSockets
io.on('connection', (socket) => { console.log('New client connected - ID:', socket.id);
socket.on('message', (message) => { console.log(`Received message => ${message} from ID: ${socket.id}`); socket.broadcast.emit('message', message); });
socket.on('disconnect', () => { console.log('Client disconnected - ID:', socket.id); });});
✅ Explicación paso a paso:
-
io.on('connection')
: se ejecuta cada vez que un cliente se conecta al servidor. -
El parámetro
socket
representa la conexión de ese cliente específico. -
socket.on('message')
:- Se activa cuando ese cliente envía un mensaje (por ejemplo, desde el móvil).
- El servidor recibe y retransmite el mensaje a los otros clientes, usando
socket.broadcast.emit(...)
.
-
socket.broadcast.emit('message', message)
:- Reenvía el mensaje a todos los clientes conectados excepto al que lo envió.
-
socket.on('disconnect')
:- Se ejecuta cuando el cliente se desconecta.
▶️ Iniciar el servidor
server.listen(port, () => { console.log(`Server is listening on http://localhost:${port}`);});
El servidor empieza a escuchar conexiones entrantes en el puerto 3000. Cuando lo hace correctamente, imprime un mensaje de confirmación.
🧩 Respuestas a preguntas de reflexión
❓ ¿Cuál es la función principal de express.static('public')
?
Sirve automáticamente los archivos estáticos (HTML, JS, CSS, imágenes) ubicados en la carpeta public
. Es mucho más simple que definir manualmente cada ruta con app.get(...)
, especialmente para proyectos con muchas páginas o archivos.
❓ Flujo del mensaje táctil
-
Cliente móvil (JS): detecta un toque y envía un mensaje al servidor con
socket.emit('message', datos)
. -
Servidor (
server.js
):- Recibe el mensaje mediante
socket.on('message', callback)
. - Lo reenvía a los demás clientes con
socket.broadcast.emit('message', datos)
.
- Recibe el mensaje mediante
-
Cliente escritorio: está suscrito al evento
socket.on('message', callback)
y lo recibe.
🔁 ¿Por qué usar socket.broadcast.emit
?
Porque queremos que todos los demás clientes (escritorio u otros móviles) reciban el mensaje, excepto el que lo envió (el móvil).
socket.emit
: solo al cliente actual.io.emit
: a todos, incluido el que envió.socket.broadcast.emit
: a todos menos el que envió. ¡Perfecto para nuestro caso!
❓ ¿Qué pasa si conectas dos escritorios y un móvil?
Si el móvil envía un mensaje táctil:
- Ambos escritorios recibirán el mensaje (porque no lo originaron).
- El móvil no lo recibe de vuelta (ya lo tiene localmente).
Esto se debe a que se usa socket.broadcast.emit(...)
, que excluye al cliente emisor.
❓ ¿Para qué sirven los console.log
?
- Informan cuando alguien se conecta o desconecta, lo cual ayuda a monitorear el estado del servidor.
- Muestran los mensajes recibidos y quién los envió (
socket.id
). - Son útiles para depuración y verificar que el flujo de mensajes funcione correctamente.
✅ Resumen general del server.js
Sección | Función principal |
---|---|
express.static() | Sirve archivos cliente desde public/ automáticamente |
io.on('connection') | Gestiona nuevos clientes y su comunicación |
socket.on('message') | Recibe mensajes del cliente |
socket.broadcast.emit() | Retransmite mensajes a otros clientes |
server.listen() | Inicia el servidor en el puerto 3000 |
Actividad 04
Análisis del cliente móvil (mobile/sketch.js
) y de escritorio (desktop/sketch.js
)
👣 Pasos: (Análisis del código)
1. Cliente móvil (mobile/sketch.js
) - El Emisor
// mobile/sketch.js (partes clave)let socket;let lastTouchX = null;let lastTouchY = null;const threshold = 5; // Umbral para evitar enviar demasiados mensajes
function setup() { // ... createCanvas, background ... socket = io();
socket.on('connect', () => console.log('Connected to server')); // ... otros listeners de socket ('message', 'disconnect', 'connect_error') ...}
function touchMoved() { // Función especial de p5.js para eventos táctiles if (socket && socket.connected) { // Calcula si el movimiento supera el umbral let dx = abs(mouseX - lastTouchX); let dy = abs(mouseY - lastTouchY);
if (dx > threshold || dy > threshold || lastTouchX === null) { // Enviar si supera umbral o es el primer toque let touchData = { type: 'touch', // Tipo de mensaje (podríamos tener otros) x: mouseX, // Coordenada X del toque (relativa al canvas móvil) y: mouseY // Coordenada Y del toque }; // Envía el objeto como una cadena JSON al servidor socket.emit('message', JSON.stringify(touchData));
// Actualiza la última posición registrada lastTouchX = mouseX; lastTouchY = mouseY; } } return false; // Evita comportamiento default del navegador en móviles}
2. Cliente de Escritorio (desktop/sketch.js
) - El Receptor
// desktop/sketch.js (partes clave)let socket;let circleX = 200; // Posición inicial Xlet circleY = 200; // Posición inicial Y
function setup() { // ... createCanvas, background ... socket = io();
socket.on('connect', () => console.log('Connected to server'));
// Listener clave: se ejecuta cuando llega un mensaje del servidor socket.on('message', (data) => { console.log(`Received message: ${data}`); try { // Intenta convertir la cadena JSON de vuelta a un objeto let parsedData = JSON.parse(data); // Verifica si es un mensaje de tipo 'touch' if (parsedData && parsedData.type === 'touch') { // Actualiza las coordenadas del círculo con los datos recibidos // ¡Ojo! Las coordenadas vienen del canvas móvil. // Aquí simplemente las usamos, pero en un caso real podríamos necesitar mapearlas // si los canvas tuvieran tamaños diferentes. circleX = parsedData.x; circleY = parsedData.y; } } catch (e) { console.error("Error parsing received JSON:", e); } });
// ... otros listeners ('disconnect', 'connect_error') ...}
function draw() { background(220); fill(255, 0, 0); ellipse(circleX, circleY, 50, 50); // Dibuja el círculo en la posición actualizada}
📝 Actividad pendiente por iniciar
El archivo student.md está vacío
Aplicación 🛠
Actividad 05
Aplica lo aprendido
👣 Pasos:
- Analiza la siguiente aplicación:
let particles = [];
function setup() { createCanvas(windowWidth, windowHeight); background(0, 20);}
function draw() { if (mouseIsPressed) { particles.push(new Particle(mouseX, mouseY)); }
for (let i = particles.length - 1; i >= 0; i--) { particles[i].update(); particles[i].display(); if (particles[i].isDead()) { particles.splice(i, 1); } }
if (particles.length === 0) background(0, 20);}class Particle { constructor(x, y) { this.pos = createVector(x, y); this.vel = p5.Vector.random2D(); this.lifespan = 255; this.size = random(5, 20); this.noiseOffset = random(1000); }
update() { // Se utiliza ruido Perlin para definir la dirección de la fuerza let angle = noise(this.pos.x * 0.005, this.pos.y * 0.005, this.noiseOffset) * TWO_PI * 2; let force = p5.Vector.fromAngle(angle); force.mult(0.5); // Se aplica la fuerza a la velocidad this.vel.add(force); this.vel.limit(4); this.pos.add(this.vel); // La vida de la partícula disminuye gradualmente this.lifespan -= 3; }
display() { fill(150, 100, 255, this.lifespan); ellipse(this.pos.x, this.pos.y, this.size); }
isDead() { return this.lifespan < 0; }}
function keyPressed() { console.log(`particle size: ${particles.length}`);}
-
Vas a modificar la aplicación anterior para que se convierta en la aplicación de escritorio.
- El móvil enviará la posición del toque (X/Y) al servidor.
- El escritorio recibirá la posición y dibujará una partícula en esa posición.
- La partícula se comportará como en el código original, pero ahora su posición inicial será la del toque del móvil.
- El móvil enviará el color y si las partículas se pintan o no con stroke.
-
Ten presente que la aplicación móvil enviará la posición del toque (X/Y), el color de la partícula y si se pinta o no con stroke. El escritorio recibirá estos datos y los usará para dibujar la partícula en la posición del toque.
📝 Actividad pendiente por iniciar
El archivo student.md está vacío
Consolidación y metacognición 🤔
Actividad 06
Consolidación de lo aprendido
👣 Pasos:
- Identifica los componentes clave: lista todos los elementos principales involucrados en la comunicación:
- Aplicación cliente móvil (navegador +
mobile/sketch.js
) - Servidor Node.js (
server.js
+ Express + Socket.IO) - Servicio de VS Code Dev Tunnels (actuando como proxy/puente público)
- Aplicación cliente de escritorio (navegador +
desktop/sketch.js
) - El usuario (interactuando con el móvil)
- Aplicación cliente móvil (navegador +
- Dibuja el diagrama:
- Representa cada componente como una caja o nodo en tu diagrama.
- Usa flechas para indicar el flujo de la información principal (el evento de toque, color, stroke).
- Etiqueta las flechas para indicar qué tipo de información o evento representan en cada paso (ej: “Evento touch (x, y)”, “socket.emit(‘message’, JSON)”, “Petición HTTP/WebSocket”, “socket.broadcast.emit(‘message’, JSON)”, “Actualización de coordenadas”, “Dibujo en canvas”).
- Asegúrate de mostrar claramente cómo interviene el servicio Dev Tunnels entre internet y tu servidor local.
- Añade explicaciones: debajo o al lado del diagrama, escribe una breve descripción del rol de cada componente principal en el proceso general.
📝 Actividad pendiente por iniciar
El archivo student.md está vacío
Actividad 07
Autoevaluación: ¿Qué he aprendido?
👣 Pasos:
- Revisa los objetivos: vuelve a leer la sección
¿Qué aprenderás en esta unidad? 💡
. - Evalúa tu confianza: para cada objetivo de aprendizaje, evalúate honestamente usando una escala simple (ej: 2=Necesito repasar mucho, 3=Lo entiendo pero con dudas, 4=Lo entiendo bien, 5=Podría explicarlo a un compañero).
- Configurar y usar VS Code Dev Tunnels: [Tu Puntuación]
- Implementar arquitectura cliente-servidor (móvil->servidor): [Tu Puntuación]
- Usar Socket.IO para retransmitir datos (servidor->escritorio): [Tu Puntuación]
- Capturar y procesar eventos en el móvil (p5.js): [Tu Puntuación]
- Modificar sistema interactivo para crear la experiencia: [Tu Puntuación]
- Analizar y explicar flujo de datos completo (móvil->servidor->escritorio): [Tu Puntuación]
- Reflexiona sobre el proceso:
- ¿Qué concepto o actividad de esta unidad te resultó más fácil de entender o realizar? ¿Por qué crees que fue así?
- ¿Qué concepto o actividad te presentó mayor dificultad? ¿Qué pasos seguiste para intentar superarla? ¿Qué recursos o estrategias te fueron más útiles?
- Describe con tus propias palabras, como si se lo explicaras a alguien que no tomó el curso, cuál es el flujo principal de información en la aplicación que construimos (desde la interacción del usuario en el móvil hasta la imagen en el escritorio). ¿Qué rol juega cada tecnología (Node.js, Socket.IO, Dev Tunnels, p5.js)?
- ¿Cómo crees que podrías aplicar lo aprendido en esta unidad (usar un móvil como controlador, comunicación en tiempo real, túneles) en otros proyectos o contextos?
📝 Actividad pendiente por iniciar
El archivo student.md está vacío
Actividad 08
Mejorando juntos: feedback de la unidad
👣 Pasos:
- Reflexiona sobre la unidad en general: piensa en todo el proceso, desde la introducción hasta la consolidación.
- Considera los siguientes aspectos y proporciona comentarios específicos para cada uno:
- Claridad de los objetivos: ¿Fueron claros los objetivos de aprendizaje desde el principio? ¿Sientes que los alcanzaste?
- Caso de estudio: ¿Fue útil el caso de estudio proporcionado? ¿Fue demasiado simple, demasiado complejo o adecuado?
- Conceptos nuevos (Dev Tunnels): ¿Fue clara la explicación y la necesidad de usar Dev Tunnels? ¿Tuviste dificultades con su configuración o uso?
- Análisis del código (SEEK): ¿Fueron útiles las explicaciones detalladas del código del servidor y los clientes? ¿Los experimentos propuestos te ayudaron a entender mejor? ¿Algo fue confuso?
- Actividad de aplicación (APPLY): ¿Te pareció interesante y motivadora la actividad de modificar la interacción? ¿Fue un reto adecuado?
- Actividades de reflexión (REFLECT): ¿Te ayudaron las actividades de consolidación y autoevaluación a reforzar tu aprendizaje?
- Ritmo y carga de trabajo: ¿Cómo te pareció el ritmo de la unidad? ¿La cantidad de trabajo fue razonable?
- Recursos: ¿Fueron útiles los enlaces y recursos proporcionados? ¿Echaste en falta algún recurso adicional?
- Sugerencias de mejora: ¿Tienes alguna sugerencia específica sobre cómo se podría mejorar esta unidad? (Ej: más ejemplos, explicaciones alternativas, diferentes tipos de actividades, etc.)
- Comentario general: cualquier otro comentario o aspecto que quieras destacar sobre tu experiencia en esta unidad.
📝 Actividad pendiente por iniciar
El archivo student.md está vacío