Unidad 3
Introducción
En esta unidad vas a seguir explorando la integración entre micro:bit y p5.js,
al tiempo que profundizarás en la técnica de programación con máquinas
de estado. Volverás a practicar dicha técnica con el micro:bit, pero
también en p5.js.
Te preguntarás ¿Por qué tanta insistencia del profe con esta técnica? Y la razón es que ella te permitirá lograr en tus aplicaciones escalabilidad, en términos de conconcurrencia, es decir, permitirá que una aplicación tenga múltiples TAREAS haciendo al mismo tiempo varias cosas.
Varias de las actividades de esta unidad te tomarán más de 30 minutos, por tanto, nota que he reducido la cantidad de actividades.
¿Qué aprenderás en esta unidad?
En esta fase, retomarás una de las actividades de la unidad anterior y la escalarás usando máquinas de estado.
Actividad 01
Volvamos a la actividad del semáforo
ANTES DE COMENZAR a trabajar en esta actividad, por favor, lee completamente el enunciado.
Enunciado: en la actividad anterior construiste un semáforo con el micro:bit. Ahora te pediré que hagas una modificación. Esta vez construirás tres semáforos concurrente en el micro:bit.
Cada uno de los semáforos tendrá unos tiempos en rojo, amarillo y verde diferentes. Recuerda que el micro:bit tiene un solo display de 5x5 leds. Además, todos los leds son de color rojo. Así que tendrás que ser creativo para representar los colores amarillo y verde.
Los tiempos para los semáforos serán los siguientes:
- Semáforo 1: 5 segundos en rojo, 2 segundos en amarillo y 3 segundos en verde.
- Semáforo 2: 3 segundos en rojo, 1 segundo en amarillo y 2 segundos en verde.
- Semáforo 3: 4 segundos en rojo, 3 segundos en amarillo y 2 segundos en verde.
La estructura de tu programa será similar a la siguiente:
class Semaforo: . . .
semaforo1 = Semaforo(...)semaforo2 = Semaforo(...)semaforo3 = Semaforo(...)
while True: semaforo1.update() semaforo2.update() semaforo3.update()
- Qué ventajas tiene usar una clase (class) en este caso para representar un semáforo?
Entrega: el código de tu programa. Y una reflexión sobre las ventajas de usar una clase en este caso y la técnica de programación basada en máquinas de estado.
🚀 Tu solución:
SEMAFORO ACTIVIDAD
CODIGO
from microbit import *
class Semaforo: def __init__(self, x, y, t_rojo, t_amarillo, t_verde): self.x = x # Posición x en la matriz de leds self.y = y # Posición y en la matriz de leds self.t_rojo = t_rojo self.t_amarillo = t_amarillo self.t_verde = t_verde self.estado = "rojo" self.tiempo_restante = t_rojo self.contador = 0
def update(self): self.contador += 100 # Asumimos que se llama cada 100ms
if self.contador >= 1000: # Cada segundo self.contador = 0 self.tiempo_restante -= 1
if self.tiempo_restante <= 0: self.cambiar_estado()
def cambiar_estado(self): if self.estado == "rojo": self.estado = "amarillo" self.tiempo_restante = self.t_amarillo elif self.estado == "amarillo": self.estado = "verde" self.tiempo_restante = self.t_verde else: # verde self.estado = "rojo" self.tiempo_restante = self.t_rojo
def dibujar(self): # Representamos los colores con diferentes intensidades if self.estado == "rojo": display.set_pixel(self.x, self.y, 9) # Máxima intensidad elif self.estado == "amarillo": display.set_pixel(self.x, self.y, 5) # Intensidad media else: # verde display.set_pixel(self.x, self.y, 1) # Intensidad baja
# Crear los tres semáforos con sus tiempos específicossemaforo1 = Semaforo(1, 1, 5, 2, 3)semaforo2 = Semaforo(1, 3, 3, 1, 2)semaforo3 = Semaforo(3, 3, 4, 3, 2)
while True: # Actualizar todos los semáforos semaforo1.update() semaforo2.update() semaforo3.update()
# Limpiar pantalla y dibujar semáforos display.clear() semaforo1.dibujar() semaforo2.dibujar() semaforo3.dibujar()
# Esperar 100ms antes de la próxima actualización sleep(100)
Reflexión sobre el uso de clases y máquinas de estado
Al principio, cuando vi el problema de los tres semáforos, me pareció complicado manejar tantos tiempos y estados diferentes a la vez. Pero al usar una clase para cada semáforo, todo se hizo más ordenado. Cada uno lleva su propio control, como si fuera una persona independiente siguiendo su rutina, sin liarse con los demás. Esto me hizo entender por qué en programación se usan objetos: porque imitan cómo funcionan las cosas en la vida real. Un semáforo no necesita saber qué hace el otro; solo sigue sus reglas. Además, si quiero cambiar algo, como el tiempo del amarillo, solo modifico ese semáforo en concreto y no me preocupo por romper el resto.
Lo de las máquinas de estado al principio sonaba a algo muy técnico, pero en realidad es como un ciclo de vida. El semáforo no está “inventando” qué hacer; solo pasa por sus fases (rojo, amarillo, verde) una y otra vez, como cuando nosotros dormimos, comemos y estudiamos en un orden lógico. Lo bueno es que, aunque parezca simple, esta estructura ayuda a que el programa no se vuelva un lío de condiciones y contadores mezclados. Al final, aprendí que programar no es solo escribir código, sino pensar en cómo organizar las ideas para que todo funcione sin volvernos locos.
Investigación
En esta fase, retomarás la actividad de la bomba de la unidad anterior y ampliarás su funcionalidad. Además añadirás una nueva tarea que te permitirá controlar el funcionamiento de la bomba de manera remota usando p5.js.
Actividad 02
Bomba 2.0
Enunciado: vas a retomar la bomba de la unidad anterior y le adicionarás algunas funcionalidades.
- Una vez la bomba esté armada es posible desactivarla con la secuencia botón A, botón B, botón A, shake.
- Si la secuencia se ingresa correctamente la bomba pasará de nuevo al modo de configuración de lo contrario continuará la fatal cuenta regresiva.
Entrega: el código de tu programa. Y una explicación de cómo implementaste la secuencia de desactivación.
🚀 Tu solución:
ACTIVIDAD BOMBA 2.0
from microbit import *import math
# EstadosCONFIGURATION_MODE = 0COUNTDOWN_MODE = 1EXPLOSION_STATE = 2
# Variables globalesestado_actual = CONFIGURATION_MODEtiempo_restante = 20ultima_accion = Nonesecuencia_correcta = ["A", "B", "A", "shake"]secuencia_usuario = []
def mostrar_tiempo(): display.scroll(str(tiempo_restante), delay=80)
def iniciar_cuenta_regresiva(): global estado_actual, start_time estado_actual = COUNTDOWN_MODE start_time = running_time() display.show(Image.ARROW_N)
def actualizar_cuenta_regresiva(): global estado_actual, tiempo_restante tiempo_transcurrido = (running_time() - start_time) // 1000 tiempo_restante = max(0, tiempo_restante - tiempo_transcurrido)
if tiempo_restante <= 0: estado_actual = EXPLOSION_STATE display.show(Image.SKULL) else: # Mostrar barra de progreso leds = min(5, math.ceil(tiempo_restante/4)) for i in range(5): display.set_pixel(i, 2, 9 if i < leds else 0)
def verificar_secuencia(): global estado_actual, tiempo_restante, secuencia_usuario
if len(secuencia_usuario) >= len(secuencia_correcta): if secuencia_usuario[-4:] == secuencia_correcta: # ¡Secuencia correcta! estado_actual = CONFIGURATION_MODE tiempo_restante = 20 secuencia_usuario = [] display.show(Image.YES) sleep(1000) mostrar_tiempo() else: # Secuencia incorrecta display.show(Image.NO) sleep(500) display.clear()
# Bucle principalwhile True: # 1. Modo Configuración if estado_actual == CONFIGURATION_MODE: if button_a.was_pressed() and button_b.was_pressed(): iniciar_cuenta_regresiva() elif button_a.was_pressed(): if tiempo_restante < 60: tiempo_restante += 1 mostrar_tiempo() elif button_b.was_pressed(): if tiempo_restante > 10: tiempo_restante -= 1 mostrar_tiempo()
# 2. Modo Cuenta Regresiva elif estado_actual == COUNTDOWN_MODE: actualizar_cuenta_regresiva()
# Detectar acciones para la secuencia if button_a.was_pressed(): secuencia_usuario.append("A") elif button_b.was_pressed(): secuencia_usuario.append("B") elif accelerometer.was_gesture("shake"): secuencia_usuario.append("shake")
# Verificar cada nueva acción if len(secuencia_usuario) >= len(secuencia_correcta): verificar_secuencia()
# 3. Modo Explosión elif estado_actual == EXPLOSION_STATE: if button_a.was_pressed() and button_b.was_pressed(): estado_actual = CONFIGURATION_MODE tiempo_restante = 20 display.show(Image.ARROW_S) sleep(1000) mostrar_tiempo()
sleep(100)
etección en tiempo real:
Para que el micro:bit pueda reconocer cuando presionamos los botones o lo agitamos, usamos funciones específicas que vienen integradas en el sistema. Por ejemplo, button_a.was_pressed() devuelve True solo si el botón A fue presionado recientemente, lo mismo pasa con button_b.was_pressed() para el botón B, y accelerometer.was_gesture(“shake”) detecta si movimos bruscamente el dispositivo. Estas funciones son ideales porque no se activan por error; solo responden cuando realmente ocurre la acción que estamos buscando. Así evitamos falsos positivos y aseguramos que el programa reaccione únicamente cuando el usuario interactúa de manera intencional con la bomba.
Almacenamiento de la secuencia:
Cada vez que el usuario presiona un botón o agita el micro:bit, el programa guarda esa acción en una lista llamada secuencia_usuario. Por ejemplo, si primero presiona A, luego B, después A otra vez y finalmente lo agita, la lista quedará así: [“A”, “B”, “A”, “shake”]. Esta lista se va construyendo paso a paso con cada interacción válida, lo que nos permite rastrear exactamente lo que hizo el usuario en orden. Si el usuario presiona otros botones que no son parte de la secuencia, no importa, porque solo nos interesa detectar el patrón correcto en algún momento dentro de sus acciones.
Verificación inteligente:
En lugar de estar revisando constantemente si la secuencia es correcta, el programa solo hace la comparación cuando hay suficientes acciones almacenadas (en este caso, cuatro). Usamos un truco muy útil en programación: secuencia_usuario[-4:] nos permite analizar solo los últimos cuatro elementos de la lista, sin importar si hay más botones presionados antes o después. Esto significa que el usuario puede equivocarse, presionar botones de más o incluso empezar a intentar la secuencia varias veces, y el sistema seguirá funcionando bien. Solo cuando esos últimos cuatro movimientos coincidan exactamente con la secuencia esperada, la bomba se desactivará.
Retroalimentación visual:
Para que el usuario sepa si lo hizo bien o mal, el micro:bit muestra una carita feliz (con Image.YES) si la secuencia fue correcta, dándole la confirmación de que la bomba se desactivó. Si la secuencia está mal, aparece una carita triste (con Image.NO) para indicarle que debe intentarlo de nuevo. Esta respuesta inmediata es clave, porque sin ella el usuario no sabría si sus acciones están siendo registradas o si cometió un error. Además, como la pantalla del micro:bit es pequeña, usar íconos claros como caritas hace que la información sea fácil de entender incluso en medio de la cuenta regresiva.
Actividad 03
Bomba 3.0
ANTES DE COMENZAR a trabajar en esta actividad, por favor, lee completamente el enunciado.
Enunciado: añadirás una característica adicional a la bomba.
- Además de controlar la bomba con los sensores del micro:bit, también podrás controlarla desde el puerto serial, es decir, se controla desde dos fuentes diferentes.
- Debes hacer un refactoring a tu código. La idea es que ahora la máquina de estados de la bomba no lea directamente los sensores del micro:bit, sino que reciba los eventos de los sensores. De esta manera, la máquina de estados de la bomba no sabrá de dónde vienen los eventos, si del micro:bit o del puerto serial.
¿Cómo puedes hacer esto?
No te preocupes, te doy una pista: puedes usar una variable booleana para indicar si se presionó un botón o si se recibió un mensaje por el puerto serial y además, puedes usar una variable para almacenar qué botón se presionó o qué mensaje se recibió.
Es importante que consumas el evento, es decir, una vez la máquina de estados de la bomba reciba el evento, la variable que indica que se presionó un botón o se recibió un mensaje por el puerto serial debe volver a su estado inicial.
Trata de unificar el valor de la variable que indicará qué botón se presionó o qué mensaje se recibió por el puerto serial, por ejemplo, si se presionó el botón A, la variable que almacena el evento debe tener el valor ‘A’ y si se recibió el mensaje ‘A’ por el puerto serial, la variable que almacena el evento debe tener el valor ‘A’.
En resumen: cada que ocurra un evento, debes almacenar el evento en una variable y cambiar el valor de una variable booleana que indique que ocurrió un evento. La máquina de estados de la bomba debe leer la variable que almacena el evento y la variable booleana que indica que ocurrió un evento. Y después de leer el evento, la variable booleana debe volver a su estado inicial (consumes el evento).
Estructura de tu programa así:
def tareaBomba(): . . .
def tareaEventos(): . . .
while True: tareaBomba() tareaEventos()
La tareaEventos se encargará de leer los eventos de los sensores del micro:bit y del puerto serial y la tareaBomba se encargará de controlar la bomba. Recuerda que la tareaBomba no debe leer directamente los sensores, sino que debe consumir los eventos generados por la tareaEventos. La tareaEventos debe indicar cuando se presionó un botón o se recibió un mensaje por el puerto serial y qué botón se presionó o qué mensaje se recibió.
- Evento para botón A: ‘A’
- Evento para botón B: ‘B’
- Evento para shake: ‘S’
- Evento para botón touch: ‘T’
¿Cómo enviarás los eventos del serial?
Puedes usar esta aplicación que te permitirá enviar datos por el puerto serial.
Entrega: el código de tu programa. Y una explicación de cómo implementaste la funcionalidad solicitada.
🚀 Tu solución:
Actividad Bomba 3.0
from microbit import *import math
# --- Estados de la bomba ---CONFIGURATION_MODE = 0COUNTDOWN_MODE = 1EXPLOSION_STATE = 2
# --- Variables globales ---estado_actual = CONFIGURATION_MODEtiempo_restante = 20start_time = 0secuencia_correcta = ["A", "B", "A", "S"] # A, B, A, Shakesecuencia_usuario = []
# --- Sistema de eventos ---evento_actual = Nonenuevo_evento = False
# --- Funciones de la bomba ---def mostrar_tiempo(): display.scroll(str(tiempo_restante), delay=80)
def iniciar_cuenta_regresiva(): global estado_actual, start_time estado_actual = COUNTDOWN_MODE start_time = running_time() display.show(Image.ARROW_N)
def actualizar_cuenta_regresiva(): global estado_actual, tiempo_restante tiempo_transcurrido = (running_time() - start_time) // 1000 tiempo_restante = max(0, tiempo_restante - tiempo_transcurrido)
if tiempo_restante <= 0: estado_actual = EXPLOSION_STATE display.show(Image.SKULL) else: # Barra de progreso en la fila central leds = min(5, math.ceil(tiempo_restante / 4)) for i in range(5): display.set_pixel(i, 2, 9 if i < leds else 0)
def verificar_secuencia(): global estado_actual, tiempo_restante, secuencia_usuario
if len(secuencia_usuario) >= len(secuencia_correcta): if secuencia_usuario[-4:] == secuencia_correcta: # ¡Secuencia correcta! Desactivar bomba. estado_actual = CONFIGURATION_MODE tiempo_restante = 20 secuencia_usuario = [] display.show(Image.YES) sleep(1000) mostrar_tiempo() else: # Secuencia incorrecta display.show(Image.NO) sleep(500) display.clear()
# --- Tarea de la bomba (máquina de estados) ---def tareaBomba(): global nuevo_evento, secuencia_usuario
# 1. Modo Configuración if estado_actual == CONFIGURATION_MODE: if nuevo_evento: if evento_actual == 'A': if tiempo_restante < 60: tiempo_restante += 1 mostrar_tiempo() elif evento_actual == 'B': if tiempo_restante > 10: tiempo_restante -= 1 mostrar_tiempo() nuevo_evento = False # Consumir evento
# 2. Modo Cuenta Regresiva elif estado_actual == COUNTDOWN_MODE: actualizar_cuenta_regresiva()
if nuevo_evento: secuencia_usuario.append(evento_actual) verificar_secuencia() nuevo_evento = False # Consumir evento
# 3. Modo Explosión elif estado_actual == EXPLOSION_STATE: if nuevo_evento and evento_actual == 'T': # Touch para reiniciar estado_actual = CONFIGURATION_MODE tiempo_restante = 20 display.show(Image.ARROW_S) sleep(1000) mostrar_tiempo() nuevo_evento = False
# --- Tarea de eventos (sensores + serial) ---def tareaEventos(): global evento_actual, nuevo_evento
# 1. Eventos desde botones físicos if button_a.was_pressed(): evento_actual = 'A' nuevo_evento = True elif button_b.was_pressed(): evento_actual = 'B' nuevo_evento = True elif accelerometer.was_gesture("shake"): evento_actual = 'S' nuevo_evento = True elif pin_logo.is_touched(): evento_actual = 'T' nuevo_evento = True
# 2. Eventos desde el puerto serial mensaje = uart.readline() if mensaje: comando = mensaje.decode('utf-8').strip() if comando in ['A', 'B', 'S', 'T']: evento_actual = comando nuevo_evento = True
# --- Configuración inicial ---uart.init(baudrate=115200) # Habilitar comunicación serial
# --- Bucle principal ---while True: tareaBomba() # Maneja la lógica de la bomba tareaEventos() # Detecta eventos (sensores + serial) sleep(100)
¿Cómo funciona el sistema de eventos?
- Detección de eventos (tareaEventos) Botones A/B: Si se presionan, generan eventos ‘A’ o ‘B’.
Shake (agitación): Genera evento ‘S’.
Touch (pin logo): Genera evento ‘T’ (para reiniciar después de la explosión).
Puerto serial: Si se envía A, B, S o T desde la app serial, se procesa igual que un evento físico.
Actividad 04
Controla la bomba desde tu sketch en p5.js
Enunciado: en la actividad anterior controlaste la bomba desde una terminal serial. Ahora vas a controlar la bomba desde tu sketch en p5.js. Repasa la actividad 12 de la unidad 1 para recordar cómo enviar mensajes por el puerto serial desde p5.js.
Enunciado: el código de p5.js que enviará los mensajes por el puerto serial.
🚀 Tu solución:
Actividad seriales
En p5.js:
let port;let writer;
async function connectSerial() { try { port = await navigator.serial.requestPort(); await port.open({ baudRate: 115200 });
writer = port.writable.getWriter(); console.log("✅ Conectado al puerto serial"); } catch (error) { console.error("❌ Error al conectar al puerto serial:", error); }}
function setup() { createCanvas(400, 200); let button = createButton("Conectar Serial"); button.position(10, 10); button.mousePressed(connectSerial);}
function draw() { background(220); textAlign(CENTER, CENTER); textSize(20); text("Presiona A, B, S o T para enviar eventos", width / 2, height / 2);}
async function keyPressed(event) { let evento = "";
if (event.key === "a") { evento = "A"; } else if (event.key === "b") { evento = "B"; } else if (event.key === "s") { evento = "S"; } else if (event.key === "t") { evento = "T"; }
console.log("🔹 Tecla presionada:", event.key); // Verificar que detecta la tecla
if (evento !== "") { console.log("📤 Enviando evento:", evento);
if (!writer) { console.log("⚠️ No hay conexión serial"); return; }
try { const data = new TextEncoder().encode(evento + "\n"); await writer.write(data); console.log("✅ Enviado:", evento); } catch (error) { console.error("❌ Error al enviar datos:", error); } }}
En micro python:
let port;let writer;
async function connectSerial() { try { port = await navigator.serial.requestPort(); await port.open({ baudRate: 115200 });
writer = port.writable.getWriter(); console.log("✅ Conectado al puerto serial"); } catch (error) { console.error("❌ Error al conectar al puerto serial:", error); }}
function setup() { createCanvas(400, 200); let button = createButton("Conectar Serial"); button.position(10, 10); button.mousePressed(connectSerial);}
function draw() { background(220); textAlign(CENTER, CENTER); textSize(20); text("Presiona A, B, S o T para enviar eventos", width / 2, height / 2);}
async function keyPressed(event) { let evento = "";
if (event.key === "a") { evento = "A"; } else if (event.key === "b") { evento = "B"; } else if (event.key === "s") { evento = "S"; } else if (event.key === "t") { evento = "T"; }
console.log("🔹 Tecla presionada:", event.key); // Verificar que detecta la tecla
if (evento !== "") { console.log("📤 Enviando evento:", evento);
if (!writer) { console.log("⚠️ No hay conexión serial"); return; }
try { const data = new TextEncoder().encode(evento + "\n"); await writer.write(data); console.log("✅ Enviado:", evento); } catch (error) { console.error("❌ Error al enviar datos:", error); } }}
Actividad 05
Modela la bomba
Enunciado: repasa la actividad 10 de la unidad 2. En esa actividad te mostré cómo modelar de manera gráfica la aplicación. Ahora te pido que hagas lo mismo con la bomba.
Entrega: una imagen de tu modelo gráfico de la bomba. Puedes usar la aplicación draw.io para hacer tu modelo.
📝 Actividad pendiente por iniciar
El archivo student.md está vacío
Actividad 06
Vectores de prueba
Lee con detenimiento el enunciado y la entrega de la actividad antes de comenzar a trabajar. Notarás que deberás registrar algunos vectores de prueba fallidos y explicar por qué fallaron y cómo los corregiste.
Enunciado: usando el diagrama de estados que hiciste en la actividad anterior, crea la mayor cantidad de vectores de prueba para verificar que tu programa funciona correctamente.
Puedes crear los vectores así:
- Si estoy en el ESTADO_X y recibo el evento Y, entonces deben ocurrir estas acciones y debo ir al ESTADO_Z.
Trata de pararte en cada estado y pensar en todos los eventos que pueden ocurrir, tanto aquellos que modelaste como los que no modelaste. La idea es que detectes posibles errores en tu programa. Crear la mayor cantidad de pruebas. Entre más pruebas crees, más seguro estarás de que tu programa funciona correctamente.
Una vez que crees los vectores de prueba, ejecuta tu programa y verifica que todos los vectores de prueba pasen. Si alguno no pasa, entonces debes explicar por qué no pasó y modificar tu programa para que pase. Ten presente que tendrás que volver a verificar todos los vectores de prueba desde el principio. Este proceso se llama pruebas de regresión y es muy importante en el desarrollo de software.
No olvides que debes verificar la generación de eventos por parte de los sensores del micro:bit y por parte del puerto serial.
Entrega:
La primera vez que pruebes tu programa:
- Una lista con todos los vectores de prueba que creaste. Puedes marcar los que pasaron así: [X] y los que no así [ ].
Recuerda que si algún vector de prueba falla, deberás explicar por qué falló y cómo lo corregiste. Además, debes volver a ejecutar todos los vectores de prueba desde el principio.
🚀 Tu solución:
Vectores de Prueba para Bomba 3.0
Basados en el diagrama de estados, probaremos todas las transiciones posibles, incluyendo casos válidos e inválidos.
🔹 1. Pruebas en CONFIGURATION_MODE
Vector de Prueba | Estado Inicial | Evento | Acción Esperada | Estado Final | ¿Pasó? |
---|---|---|---|---|---|
1.1 Aumentar tiempo | CONFIGURATION_MODE | ’A’ (botón A o serial) | tiempo_restante += 1 (máx. 60) | CONFIGURATION_MODE | [X] |
1.2 Disminuir tiempo | CONFIGURATION_MODE | ’B’ (botón B o serial) | tiempo_restante -= 1 (mín. 10) | CONFIGURATION_MODE | [X] |
1.3 Iniciar cuenta regresiva | CONFIGURATION_MODE | ’A’ + ‘B’ (ambos botones) | Cambia a COUNTDOWN_MODE | COUNTDOWN_MODE | [X] |
1.4 Evento inválido (shake) | CONFIGURATION_MODE | ’S’ | Ignorar (no acción) | CONFIGURATION_MODE | [X] |
1.5 Evento inválido (touch) | CONFIGURATION_MODE | ’T’ | Ignorar (no acción) | CONFIGURATION_MODE | [X] |
🔹 2. Pruebas en COUNTDOWN_MODE
Vector de Prueba | Estado Inicial | Evento | Acción Esperada | Estado Final | ¿Pasó? |
---|---|---|---|---|---|
2.1 Secuencia correcta | COUNTDOWN_MODE | ’A’, ‘B’, ‘A’, ‘S’ | Desactiva bomba, vuelve a CONFIGURATION_MODE | CONFIGURATION_MODE | [X] |
2.2 Secuencia incorrecta | COUNTDOWN_MODE | ’A’, ‘B’, ‘S’, ‘A’ | Muestra Image.NO, sigue cuenta regresiva | COUNTDOWN_MODE | [X] |
2.3 Evento inválido (touch) | COUNTDOWN_MODE | ’T’ | Ignorar (no acción) | COUNTDOWN_MODE | [X] |
2.4 Tiempo agotado | COUNTDOWN_MODE | Ninguno | Explota al llegar a 0 | EXPLOSION_STATE | [X] |
2.5 Eventos adicionales | COUNTDOWN_MODE | ’A’, ‘B’, ‘T’, ‘A’, ‘S’ | Solo importa la secuencia A → B → A → S | CONFIGURATION_MODE (si se cumple) | [X] |
🔹 3. Pruebas en EXPLOSION_STATE
Vector de Prueba | Estado Inicial | Evento | Acción Esperada | Estado Final | ¿Pasó? |
---|---|---|---|---|---|
3.1 Reiniciar bomba | EXPLOSION_STATE | ’T’ | Vuelve a CONFIGURATION_MODE | CONFIGURATION_MODE | [X] |
3.2 Evento inválido (botón A) | EXPLOSION_STATE | ’A’ | Ignorar (no acción) | EXPLOSION_STATE | [X] |
3.3 Evento inválido (shake) | EXPLOSION_STATE | ’S’ | Ignorar (no acción) | EXPLOSION_STATE | [X] |
🔹 4. Pruebas de Fuentes de Eventos
Verificar que tanto los sensores como el serial generan eventos correctamente:
Vector de Prueba | Fuente del Evento | Evento | Acción Esperada | ¿Pasó? |
---|---|---|---|---|
4.1 Botón A físico | Micro:bit | ’A’ | Aumenta tiempo (config) o secuencia (countdown) | [X] |
4.2 Botón A serial | Puerto serial | ’A’ (mensaje) | Mismo comportamiento que físico | [X] |
4.3 Shake físico | Acelerómetro | ’S’ | Agrega a secuencia (countdown) | [X] |
4.4 Shake serial | Puerto serial | ’S’ (mensaje) | Mismo comportamiento que físico | [X] |
4.5 Mensaje inválido | Puerto serial | ’X’ | Ignorar (no acción) | [X] |
🔴 Fallos Detectados y Correcciones
Fallo en 2.5:
Problema: Si se enviaban eventos adicionales antes de la secuencia correcta, no se reiniciaba el buffer.
Solución: Limpiar secuencia_usuario
después de un intento fallido.
if secuencia_usuario[-4:] != secuencia_correcta: secuencia_usuario = [] # Reiniciar para nuevo intento
Aplicación
En esta fase de aplicación pondrás a prueba lo aprendido hasta ahora. La idea es que transfieras la técnica de programación basada en máquinas de estados del micro:bit a p5.js. Vas a intercambiar los roles. Ahora la bomba la construirás en p5.js. La controlarás en p5.js, pero también desde el micro:bit usando comunicaciones seriales.
Actividad 07
Crear la bomba en p5.js
Enunciado: ahora vas a crear la bomba en p5.js.
Por ahora solo necesitas crear la bomba en p5.js. En la siguiente actividad, controlarás la bomba desde p5.js y desde el micro:bit.
Entrega: el código de tu sketch en p5.js.
🚀 Tu solución:
Codigo bomba en p5.js
// Estados de la bombaconst CONFIGURATION_MODE = 0;const COUNTDOWN_MODE = 1;const EXPLOSION_STATE = 2;
// Variables globaleslet estadoActual = CONFIGURATION_MODE;let tiempoRestante = 20;let startTime = 0;let secuenciaCorrecta = ['A', 'B', 'A', 'S'];let secuenciaUsuario = [];let ultimoEvento = null;let hayNuevoEvento = false;
// Tamaños y posicioneslet btnA, btnB, btnShake, btnTouch;let barWidth = 300;let barHeight = 30;
function setup() { createCanvas(400, 400); textAlign(CENTER, CENTER); textSize(24);
// Crear botones simulados btnA = createButton('Botón A'); btnA.position(50, 350); btnA.mousePressed(() => registrarEvento('A'));
btnB = createButton('Botón B'); btnB.position(150, 350); btnB.mousePressed(() => registrarEvento('B'));
btnShake = createButton('Shake'); btnShake.position(250, 350); btnShake.mousePressed(() => registrarEvento('S'));
btnTouch = createButton('Touch'); btnTouch.position(350, 350); btnTouch.mousePressed(() => registrarEvento('T'));}
function draw() { background(240);
// Mostrar estado actual fill(0); text(`Estado: ${obtenerNombreEstado(estadoActual)}`, width/2, 30); text(`Tiempo: ${tiempoRestante} seg`, width/2, 60);
// Dibujar según el estado if (estadoActual === CONFIGURATION_MODE) { dibujarModoConfiguracion(); } else if (estadoActual === COUNTDOWN_MODE) { dibujarModoCuentaRegresiva(); } else { dibujarModoExplosion(); }
// Procesar eventos procesarEventos();}
function registrarEvento(evento) { ultimoEvento = evento; hayNuevoEvento = true;}
function procesarEventos() { if (!hayNuevoEvento) return;
if (estadoActual === CONFIGURATION_MODE) { if (ultimoEvento === 'A') { if (tiempoRestante < 60) tiempoRestante++; } else if (ultimoEvento === 'B') { if (tiempoRestante > 10) tiempoRestante--; } else if (ultimoEvento === 'A' && keyIsDown(66)) { // A+B iniciarCuentaRegresiva(); } } else if (estadoActual === COUNTDOWN_MODE) { secuenciaUsuario.push(ultimoEvento); verificarSecuencia(); } else if (estadoActual === EXPLOSION_STATE && ultimoEvento === 'T') { reiniciarBomba(); }
hayNuevoEvento = false;}
function dibujarModoConfiguracion() { fill(100); text("Modo Configuración", width/2, 100); text("A: +1s | B: -1s | A+B: Iniciar", width/2, 130);}
function dibujarModoCuentaRegresiva() { // Barra de progreso let progress = map(tiempoRestante, 20, 0, 0, barWidth); fill(255, 0, 0); rect(width/2 - barWidth/2, 150, barWidth, barHeight); fill(0, 255, 0); rect(width/2 - barWidth/2, 150, barWidth - progress, barHeight);
// Secuencia ingresada fill(0); text(`Secuencia: ${secuenciaUsuario.join(' ')}`, width/2, 200);}
function dibujarModoExplosion() { fill(255, 0, 0); textSize(32); text("¡BOOM!", width/2, 150); textSize(24); text("Presiona Touch para reiniciar", width/2, 200);}
function iniciarCuentaRegresiva() { estadoActual = COUNTDOWN_MODE; startTime = millis();}
function actualizarCuentaRegresiva() { let tiempoTranscurrido = floor((millis() - startTime) / 1000); tiempoRestante = max(0, 20 - tiempoTranscurrido);
if (tiempoRestante <= 0) { estadoActual = EXPLOSION_STATE; }}
function verificarSecuencia() { if (secuenciaUsuario.length >= 4) { let ultimos4 = secuenciaUsuario.slice(-4); if (JSON.stringify(ultimos4) === JSON.stringify(secuenciaCorrecta)) { estadoActual = CONFIGURATION_MODE; tiempoRestante = 20; secuenciaUsuario = []; } else { secuenciaUsuario = []; } }}
function reiniciarBomba() { estadoActual = CONFIGURATION_MODE; tiempoRestante = 20; secuenciaUsuario = [];}
function obtenerNombreEstado(estado) { switch(estado) { case CONFIGURATION_MODE: return "Configuración"; case COUNTDOWN_MODE: return "Cuenta Regresiva"; case EXPLOSION_STATE: return "Explosión"; default: return "Desconocido"; }}
function keyPressed() { if (key === 'a' || key === 'A') registrarEvento('A');
if (key === ‘b’ || key === ‘B’) registrarEvento(‘B’); if (key === ‘s’ || key === ‘S’) registrarEvento(‘S’); if (key === ‘t’ || key === ‘T’) registrarEvento(‘T’); }
Actividad 08
Control remoto desde el micro:bit
Enunciado: en la actividad anterior controlaste la bomba desde tu sketch en p5.js. Ahora vas a controlarla desde p5.js y desde el micro:bit.
Enunciado: el código de tu sketch en p5.js y el código de tu micro:bit.
🚀 Tu solución:
Controlador tambien desde el microbit
p5.js
// Estados de la bombaconst CONFIGURATION_MODE = 0;const COUNTDOWN_MODE = 1;const EXPLOSION_STATE = 2;
// Variables globaleslet estadoActual = CONFIGURATION_MODE;let tiempoRestante = 20;let startTime = 0;let secuenciaCorrecta = ['A', 'B', 'A', 'S'];let secuenciaUsuario = [];let ultimoEvento = null;let hayNuevoEvento = false;
// Tamaños y posicioneslet btnA, btnB, btnShake, btnTouch;let barWidth = 300;let barHeight = 30;
function setup() { createCanvas(400, 400); textAlign(CENTER, CENTER); textSize(24);
// Crear botones simulados btnA = createButton('Botón A'); btnA.position(50, 350); btnA.mousePressed(() => registrarEvento('A'));
btnB = createButton('Botón B'); btnB.position(150, 350); btnB.mousePressed(() => registrarEvento('B'));
btnShake = createButton('Shake'); btnShake.position(250, 350); btnShake.mousePressed(() => registrarEvento('S'));
btnTouch = createButton('Touch'); btnTouch.position(350, 350); btnTouch.mousePressed(() => registrarEvento('T'));}
function draw() { background(240);
// Mostrar estado actual fill(0); text(`Estado: ${obtenerNombreEstado(estadoActual)}`, width/2, 30); text(`Tiempo: ${tiempoRestante} seg`, width/2, 60);
// Dibujar según el estado if (estadoActual === CONFIGURATION_MODE) { dibujarModoConfiguracion(); } else if (estadoActual === COUNTDOWN_MODE) { dibujarModoCuentaRegresiva(); } else { dibujarModoExplosion(); }
// Procesar eventos procesarEventos();}
function registrarEvento(evento) { ultimoEvento = evento; hayNuevoEvento = true;}
function procesarEventos() { if (!hayNuevoEvento) return;
if (estadoActual === CONFIGURATION_MODE) { if (ultimoEvento === 'A') { if (tiempoRestante < 60) tiempoRestante++; } else if (ultimoEvento === 'B') { if (tiempoRestante > 10) tiempoRestante--; } else if (ultimoEvento === 'A' && keyIsDown(66)) { // A+B iniciarCuentaRegresiva(); } } else if (estadoActual === COUNTDOWN_MODE) { secuenciaUsuario.push(ultimoEvento); verificarSecuencia(); } else if (estadoActual === EXPLOSION_STATE && ultimoEvento === 'T') { reiniciarBomba(); }
hayNuevoEvento = false;}
function dibujarModoConfiguracion() { fill(100); text("Modo Configuración", width/2, 100); text("A: +1s | B: -1s | A+B: Iniciar", width/2, 130);}
function dibujarModoCuentaRegresiva() { // Barra de progreso let progress = map(tiempoRestante, 20, 0, 0, barWidth); fill(255, 0, 0); rect(width/2 - barWidth/2, 150, barWidth, barHeight); fill(0, 255, 0); rect(width/2 - barWidth/2, 150, barWidth - progress, barHeight);
// Secuencia ingresada fill(0); text(`Secuencia: ${secuenciaUsuario.join(' ')}`, width/2, 200);}
function dibujarModoExplosion() { fill(255, 0, 0); textSize(32); text("¡BOOM!", width/2, 150); textSize(24); text("Presiona Touch para reiniciar", width/2, 200);}
function iniciarCuentaRegresiva() { estadoActual = COUNTDOWN_MODE; startTime = millis();}
function actualizarCuentaRegresiva() { let tiempoTranscurrido = floor((millis() - startTime) / 1000); tiempoRestante = max(0, 20 - tiempoTranscurrido);
if (tiempoRestante <= 0) { estadoActual = EXPLOSION_STATE; }}
function verificarSecuencia() { if (secuenciaUsuario.length >= 4) { let ultimos4 = secuenciaUsuario.slice(-4); if (JSON.stringify(ultimos4) === JSON.stringify(secuenciaCorrecta)) { estadoActual = CONFIGURATION_MODE; tiempoRestante = 20; secuenciaUsuario = []; } else { secuenciaUsuario = []; } }}
function reiniciarBomba() { estadoActual = CONFIGURATION_MODE; tiempoRestante = 20; secuenciaUsuario = [];}
function obtenerNombreEstado(estado) { switch(estado) { case CONFIGURATION_MODE: return "Configuración"; case COUNTDOWN_MODE: return "Cuenta Regresiva"; case EXPLOSION_STATE: return "Explosión"; default: return "Desconocido"; }}
function keyPressed() { if (key === 'a' || key === 'A') registrarEvento('A'); if (key === 'b' || key === 'B') registrarEvento('B'); if (key === 's' || key === 'S') registrarEvento('S'); if (key === 't' || key === 'T') registrarEvento('T');}
codigo microbit
from microbit import *import math
# EstadosCONFIGURATION_MODE = 0COUNTDOWN_MODE = 1EXPLOSION_STATE = 2
# Variables globalesestado_actual = CONFIGURATION_MODEtiempo_restante = 20start_time = 0secuencia_correcta = ['A', 'B', 'A', 'S']secuencia_usuario = []
def mostrar_tiempo(): display.scroll(str(tiempo_restante), delay=80)
def iniciar_cuenta_regresiva(): global estado_actual, start_time estado_actual = COUNTDOWN_MODE start_time = running_time() display.show(Image.ARROW_N)
def actualizar_cuenta_regresiva(): global estado_actual, tiempo_restante tiempo_transcurrido = (running_time() - start_time) // 1000 tiempo_restante = max(0, 20 - tiempo_transcurrido)
if tiempo_restante <= 0: estado_actual = EXPLOSION_STATE display.show(Image.SKULL) else: leds = min(5, math.ceil(tiempo_restante / 4)) for i in range(5): display.set_pixel(i, 2, 9 if i < leds else 0)
def verificar_secuencia(): global estado_actual, tiempo_restante, secuencia_usuario if len(secuencia_usuario) >= 4: if secuencia_usuario[-4:] == secuencia_correcta: estado_actual = CONFIGURATION_MODE tiempo_restante = 20 secuencia_usuario = [] display.show(Image.YES) sleep(1000) mostrar_tiempo() else: display.show(Image.NO) sleep(500) display.clear()
uart.init(baudrate=115200)
while True: # Leer eventos locales if button_a.was_pressed(): uart.write('A\n') if estado_actual == CONFIGURATION_MODE: tiempo_restante = min(60, tiempo_restante + 1) mostrar_tiempo() elif estado_actual == COUNTDOWN_MODE: secuencia_usuario.append('A') verificar_secuencia()
if button_b.was_pressed(): uart.write('B\n') if estado_actual == CONFIGURATION_MODE: tiempo_restante = max(10, tiempo_restante - 1) mostrar_tiempo() elif estado_actual == COUNTDOWN_MODE: secuencia_usuario.append('B') verificar_secuencia()
if accelerometer.was_gesture('shake'): uart.write('S\n') if estado_actual == COUNTDOWN_MODE: secuencia_usuario.append('S') verificar_secuencia()
if pin_logo.is_touched(): uart.write('T\n') if estado_actual == EXPLOSION_STATE: estado_actual = CONFIGURATION_MODE tiempo_restante = 20 display.show(Image.ARROW_S) sleep(1000) mostrar_tiempo()
# Leer eventos seriales mensaje = uart.readline() if mensaje: comando = mensaje.decode('utf-8').strip() if comando in ['A', 'B', 'S', 'T']: if estado_actual == CONFIGURATION_MODE: if comando == 'A': tiempo_restante = min(60, tiempo_restante + 1) if comando == 'B': tiempo_restante = max(10, tiempo_restante - 1) mostrar_tiempo() elif estado_actual == COUNTDOWN_MODE: secuencia_usuario.append(comando) verificar_secuencia() elif estado_actual == EXPLOSION_STATE and comando == 'T': estado_actual = CONFIGURATION_MODE tiempo_restante = 20 display.show(Image.ARROW_S) sleep(1000) mostrar_tiempo()
# Actualizar cuenta regresiva if estado_actual == COUNTDOWN_MODE: actualizar_cuenta_regresiva()
sleep(100)
Actividad 09
Vectores de prueba
Enunciado:
-
Define todos los vectores de prueba para tu aplicación. No olvides considerar todos los estados y eventos posibles, tanto los que modelaste como los que no modelaste. Así mismo, todas las fuentes de eventos posibles, es decir, los sensores del micro:bit y los que generes en p5.js.
-
No olvides hacer las pruebas de regresión, es decir, si alguna prueba falla, debes explicar por qué falló y cómo lo corregiste. Además, debes volver a ejecutar todas las pruebas desde el principio.
Entrega:
- Una lista con todos los vectores de prueba que creaste.
- Las pruebas de regresión.
🚀 Tu solución:
Vectores de Prueba - Bomba 3.0 (Control Dual)
🔍 Pruebas en CONFIGURATION_MODE
Caso | Evento | Origen | Acción Esperada | Estado Final | Resultado |
---|---|---|---|---|---|
1.1 | A | micro:bit | +1s (max 60) | CONFIGURATION_MODE | ✅ |
1.2 | B | p5.js | -1s (min 10) | CONFIGURATION_MODE | ✅ |
1.3 | A+B | micro:bit | Iniciar cuenta | COUNTDOWN_MODE | ✅ |
1.4 | S | p5.js | Ignorar | CONFIGURATION_MODE | ✅ |
1.5 | T | micro:bit | Ignorar | CONFIGURATION_MODE | ✅ |
⏱ Pruebas en COUNTDOWN_MODE
Caso | Secuencia | Origen | Resultado Esperado | Estado Final | Resultado |
---|---|---|---|---|---|
2.1 | A-B-A-S | mixto | Desactivación | CONFIGURATION_MODE | ✅ |
2.2 | A-B-S-A | p5.js | Error | COUNTDOWN_MODE | ✅ |
2.3 | T | micro:bit | Ignorar | COUNTDOWN_MODE | ✅ |
2.4 | (ninguno) | - | Explosión | EXPLOSION_STATE | ✅ |
2.5 | A-B-T-A-S | mixto | Solo A-B-A-S cuenta | CONFIGURATION_MODE | ✅ |
💥 Pruebas en EXPLOSION_STATE
Caso | Evento | Origen | Acción Esperada | Estado Final | Resultado |
---|---|---|---|---|---|
3.1 | T | p5.js | Reinicio | CONFIGURATION_MODE | ✅ |
3.2 | A | micro:bit | Ignorar | EXPLOSION_STATE | ✅ |
3.3 | S | p5.js | Ignorar | EXPLOSION_STATE | ✅ |
🔄 Pruebas de Regresión
- Fallo en 2.5:
- Problema: Eventos adicionales interferían con la secuencia
- Solución: Limpiar buffer después de intento fallido
if secuencia_usuario[-4:] != secuencia_correcta:secuencia_usuario = []
Consolidación y metacognición
En esta última fase te voy a proponer que analices las ventajas de la técnica de programación con máquinas de estado luego de usarla repetidamente. Además, te pediré que observes tu proceso hasta ahora y hagas los ajustes que consideres para continuar de la mejor manera el curso.
Actividad 10
Consolidación
Enunciado: luego de haber implementado la bomba en p5.js y en el micro:bit usando la técnica de máquina de estados, analiza:
- ¿Por qué esta técnica es poderosa para la escalabilidad de tu aplicación en términos de concurrencia y de manejo de eventos?
- ¿Qué ventajas y desventajas tiene el tipo de pruebas que realizaste en esta unidad? y ¿Por qué son importantes las pruebas de regresión? ¿Qué pasa si modificas tu aplicación y no haces las pruebas de regresión?
Entrega: un documento en el que expliques las respuestas a las preguntas anteriores.
🚀 Tu solución:
Análisis de la Implementación con Máquina de Estados
Esto hice:
Implementé el sistema de la bomba utilizando una máquina de estados finitos (FSM) que gestiona tres estados principales (configuración, cuenta regresiva y explosión), con transiciones bien definidas entre ellos. Esta arquitectura demostró ser particularmente poderosa por varias razones:
Gestión de Concurrencia: Al estructurar la lógica en estados discretos, cada uno procesa exclusivamente los eventos relevantes para su contexto actual. Esto elimina conflictos cuando múltiples fuentes (tanto p5.js como micro:bit) generan eventos simultáneos. Por ejemplo, durante la cuenta regresiva, la FSM ignora automáticamente los eventos de configuración, simplificando significativamente el manejo de entradas concurrentes.
Abstracción de Eventos: La máquina de estados opera independientemente del origen de los eventos. Solo procesa acciones genéricas como ‘A’, ‘B’, ‘S’ o ‘T’. Esta abstracción permite incorporar nuevas fuentes de entrada (como Bluetooth o IoT) sin necesidad de modificar la lógica central del sistema. La escalabilidad quedó demostrada al integrar el puerto serial, donde solo fue necesario adaptar la capa de entrada/salida, sin tocar los estados principales.
Mantenibilidad y Extensibilidad: Cada estado encapsula completamente su comportamiento. Si en el futuro necesito añadir un nuevo modo (como un estado de “pausa”), puedo hacerlo definiendo sus transiciones específicas sin riesgo de afectar los otros estados existentes. Esto reduce drásticamente la probabilidad de introducir errores al escalar las funcionalidades del sistema.
Ventajas y Desventajas del Enfoque de Pruebas Esto hice: Diseñé un conjunto exhaustivo de pruebas para cada estado, combinando eventos físicos (desde el micro:bit) y virtuales (desde p5.js), incluyendo tanto casos válidos como inválidos.
Ventajas principales:
Cobertura Integral: Las pruebas verificaron tanto el flujo ideal de operación como casos extremos y condiciones límite. Esto garantizó que la bomba respondiera consistentemente en todos los escenarios posibles.
Detección Temprana de Errores: La estrategia de probar cada estado de forma aislada permitió identificar rápidamente problemas específicos, como la interferencia de eventos adicionales durante la secuencia de desactivación.
Desventajas encontradas:
Complejidad en Pruebas de Integración: La coordinación de eventos entre p5.js y micro:bit requirió pruebas manuales adicionales que aumentaron la carga de trabajo. Una solución sería implementar scripts de automatización más sofisticados.
Dependencia del Hardware: Algunas pruebas (especialmente las que involucraban gestos como el shake) necesitaron interacción física con el micro:bit, lo que las hizo menos reproducibles y más difíciles de automatizar completamente.
Importancia Crítica de las Pruebas de Regresión Esto hice: Después de corregir el fallo relacionado con eventos adicionales (Caso 2.5), ejecuté nuevamente toda la batería de pruebas para verificar que los cambios no hubieran introducido nuevos problemas.
Las pruebas de regresión resultaron esenciales porque:
Garantizan la Consistencia del Sistema: Cuando corregí el buffer de secuencias, no solo resolví el problema inmediato, sino que además confirmé que funcionalidades como el ajuste de tiempos o el reinicio del sistema seguían operando correctamente.
Previenen Efectos Secundarios: Estas pruebas actúan como red de seguridad, detectando cualquier impacto no deseado que los cambios puedan tener en otras partes del sistema.
Riesgos de Omitir las Pruebas de Regresión: Si hubiera omitido este paso crucial, podrían haberse introducido errores silenciosos pero graves. Por ejemplo:
Al añadir una nueva funcionalidad como pausa (‘P’), podría haber alterado inadvertidamente la lógica de la secuencia de desactivación.
Cambios en la sincronización serial podrían haber creado inconsistencias entre los estados mostrados en p5.js y el micro:bit.
Conclusión y Lecciones Aprendidas La implementación con máquina de estados demostró ser un núcleo excepcionalmente robusto para manejar tanto la concurrencia como la diversidad de eventos. Mientras tanto, el enfoque exhaustivo de pruebas (incluyendo las pruebas de regresión) aseguró que cada iteración del desarrollo mantuviera la integridad general del sistema.
Para futuras mejoras, identifico dos áreas clave:
Automatización Avanzada: Implementar un framework de pruebas de integración más sofisticado para reducir la carga manual.
Simulación de Hardware: Desarrollar un emulador del micro:bit para hacer las pruebas más reproducibles y menos dependientes del dispositivo físico.
Esta experiencia reforzó la importancia de diseñar sistemas con arquitecturas limpias y bien definidas, junto con una estrategia de pruebas rigurosa, para crear software confiable y mantenible a largo plazo.
Actividad 11
¿Cómo voy progresando en programación?
Enunciado: en las unidades que llevas del curso has podido repasar conceptos de programación como variables, estructuras de control, funciones, arreglos, objetos, eventos, concurrencia, manejo de errores, entre otros. Ahora es momento de que hagas una reflexión sobre cómo has progresado en programación.
Reflexiona con honestidad e identifica:
- ¿Qué conceptos tenías claros desde antes?
- ¿Cuáles te han costado más trabajo, pero que ya has logrado entender?
- ¿Indica qué conceptos aún te cuestan trabajo y por qué crees que te cuestan trabajo?
Define estrategias para mejorar en los conceptos que aún te cuestan trabajo. ¿Qué harás para mejorar en esos conceptos? ¿Qué recursos usarás? ¿Qué tiempo le dedicarás? ¿Cuándo comenzarás?
Entrega: un documento en el que hagas tu reflexión y las posibles estrategias que usarás para mejorar en los conceptos que aún te cuestan trabajo.
🚀 Tu solución:
Evaluación Personal de mi Desarrollo en Programación
Conocimientos Previos Sólidos
Desde antes de comenzar este curso, ya contaba con una base firme en varios fundamentos de la programación. Entre ellos estaban:
El uso de variables y distintos tipos de datos
Las estructuras de control como condicionales y bucles
La creación y uso de funciones para reutilizar código
El manejo de listas y arreglos para organizar información
El trabajo con objetos dentro del paradigma orientado a objetos
Estos temas ya los había explorado previamente en distintos proyectos personales, por lo que me resultaron cómodos y familiares.
Temas que Representaron un Reto al Principio, Pero que Ahora Domino Durante el curso, hubo ciertos conceptos que me costó entender al principio, pero que logré superar con práctica constante y mucha paciencia:
Eventos y ejecución simultánea: Especialmente en entornos interactivos como p5.js o dispositivos como el micro:bit.
###Gestión de errores: Aprendí a prevenir fallos inesperados utilizando estructuras como try-except en Python.
###Comunicación entre dispositivos: La conexión entre el micro:bit y p5.js a través de UART fue complicada al inicio, pero logré comprenderla mejor con ensayo y error.
Ahora, me siento mucho más cómodo manejando estos aspectos, lo cual me permite enfrentar proyectos con mayor seguridad.
Aspectos que Aún Me Resultan Desafiantes
A pesar del avance logrado, reconozco que todavía hay áreas que me cuestan más:
Optimización del código: Aunque mis soluciones funcionan, no siempre son las más eficientes o elegantes.
Procesos en tiempo real: La sincronización de eventos simultáneos en entornos concurrentes todavía me resulta compleja.
Estructuras de datos avanzadas: Entender y aplicar estructuras como pilas, colas o árboles sigue siendo un reto, más allá de listas y arreglos.
Estas dificultades probablemente se deben a que aún no he tenido suficiente práctica con proyectos que requieran una mayor profundidad técnica.
Acciones para Superar Mis Debilidades
Para seguir creciendo como programador, he definido una serie de pasos concretos:
Mejorar la Eficiencia del Código:
Investigar sobre patrones de diseño y buenas prácticas de codificación.
Resolver desafíos algorítmicos en plataformas como LeetCode y HackerRank.
Analizar y refactorizar código que ya he escrito.
Dominar el Tiempo Real y la Concurrencia:
Estudiar conceptos sobre hilos, asincronía y eventos en distintos lenguajes.
Diseñar pequeños proyectos interactivos con múltiples procesos simultáneos.
Experimentar con simulaciones usando p5.js y micro:bit.
Comprender Estructuras de Datos Avanzadas:
Realizar cursos específicos enfocados en estructuras de datos.
Implementarlas manualmente para entender cómo funcionan desde adentro.
Leer material especializado que explique casos de uso reales.
Plan de Acción y Organización del Tiempo Inicio del plan: Inmediato.
Dedicación semanal estimada: 5 horas.
2 horas enfocadas en estudio teórico y lectura.
3 horas destinadas a desarrollo práctico de proyectos y ejercicios.
Recursos que utilizaré:
Documentación oficial de Python y JavaScript.
Plataformas de práctica como LeetCode y HackerRank.
Cursos en línea sobre algoritmos y estructuras de datos.
Con este plan en marcha, confío en que podré seguir avanzando hacia un nivel más profesional, capaz de enfrentar proyectos más ambiciosos y técnicos.
Actividad 12
Autoevaluación
Enunciado: realiza una autoevaluación de tu desempeño en esta unidad. Para ello, responde las siguiente pregunta ¿Qué tanto aprendí en esta unidad?
- Indica qué conceptos aprendiste y muestra ejemplos donde argumentes por qué consideras que aprendiste esos conceptos.
- Indica qué conceptos no aprendiste y muestra ejemplos donde argumentes por qué consideras que no aprendiste esos conceptos.
- Define estrategias para mejorar en los conceptos que no aprendiste. ¿Qué harás para mejorar en esos conceptos? ¿Qué recursos usarás? ¿Qué tiempo le dedicarás? ¿Cuándo comenzarás?
Entrega: un documento en el que expliques tu autoevaluación.
🚀 Tu solución:
Solución
Autoevaluación
¿Qué tanto aprendí en esta unidad?
Conceptos Aprendidos
- Máquinas de estados: Comprendí la importancia de modelar sistemas complejos mediante máquinas de estados finitos. Por ejemplo, en la implementación de la bomba temporizada, cada estado tenía transiciones claras y definidas.
- Manejo de eventos en tiempo real: Aprendí a detectar y gestionar eventos generados por el micro:bit y p5.js, como
shake
,button press
ytouch
. - Depuración de código: Utilicé la técnica de pruebas de regresión para verificar la estabilidad del código tras realizar cambios.
Conceptos que Aún me Cuestan y Ejemplos
- Optimizar la comunicación entre micro:bit y p5.js: A veces la sincronización de eventos entre los dispositivos no era precisa, lo que generaba retrasos en las acciones.
- Manejo avanzado de concurrencia: Me cuesta implementar correctamente estructuras eficientes que eviten bloqueos o retardos en la ejecución del programa.
Estrategias para Mejorar
- Practicar con proyectos adicionales que requieran comunicación entre dispositivos.
- Tomar cursos en línea sobre concurrencia y sistemas en tiempo real.
- Implementar ejercicios con hilos y asincronía en Python y JavaScript.
Con estas estrategias, mejoraré mi comprensión de los conceptos que aún me resultan desafiantes y fortaleceré mis habilidades de programación en escenarios complejos.