Saltearse al contenido

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:

Código para Tres Semáforos Concurrentes en micro:bit

from microbit import *
import utime
class Semaforo:
def __init__(self, x, y, t_rojo, t_amarillo, t_verde):
self.x = x # Posición X en la matriz (0-4)
self.y = y # Posición Y en la matriz (0-4)
self.t_rojo = t_rojo * 1000 # Convertir a milisegundos
self.t_amarillo = t_amarillo * 1000
self.t_verde = t_verde * 1000
self.state = "ROJO"
self.start_time = utime.ticks_ms()
def update(self):
current_time = utime.ticks_ms()
elapsed = utime.ticks_diff(current_time, self.start_time)
if self.state == "ROJO":
display.set_pixel(self.x, self.y, 9) # Brillo máximo para rojo
if elapsed > self.t_rojo:
self.state = "AMARILLO"
self.start_time = current_time
elif self.state == "AMARILLO":
display.set_pixel(self.x, self.y, 6) # Brillo medio para amarillo
if elapsed > self.t_amarillo:
self.state = "VERDE"
self.start_time = current_time
elif self.state == "VERDE":
display.set_pixel(self.x, self.y, 3) # Brillo bajo para verde
if elapsed > self.t_verde:
self.state = "ROJO"
self.start_time = current_time
# Crear tres semáforos con tiempos diferentes
s1 = Semaforo(1, 1, 5, 2, 3) # Semáforo central
s2 = Semaforo(0, 3, 3, 1, 2) # Semáforo inferior izquierdo
s3 = Semaforo(4, 1, 4, 3, 2) # Semáforo superior derecho
while True:
display.clear() # Limpiar pantalla en cada iteración
s1.update()
s2.update()
s3.update()
sleep(100) # Pequeña pausa para evitar parpadeos

Reflexión sobre el Diseño

Ventajas de usar clases:
La implementación con clases permite encapsular el estado y comportamiento de cada semáforo, haciendo el código modular y escalable. Cada instancia (s1, s2, s3) gestiona sus propios tiempos y transiciones sin interferencias, demostrando el principio de responsabilidad única. Además, si quisiera añadir un cuarto semáforo, solo necesitaría crear una nueva instancia sin modificar la lógica existente.

Máquinas de estado como núcleo:
La técnica de máquinas de estados (ROJO → AMARILLO → VERDE) garantiza que cada semáforo evolucione de manera predecible y concurrente. Aunque el micro:bit tiene limitaciones (como un solo display monocromático), el uso de brillos diferenciados (9 para rojo, 6 para amarillo, 3 para verde) y posiciones estratégicas en la matriz simula tres sistemas independientes. Este enfoque resuelve el desafío de la concurrencia en un entorno con recursos limitados.

Desafíos superados:

  1. Representación de colores: Simulé verde y amarillo ajustando el brillo de los LEDs rojos.
  2. Gestión de tiempos: Cada semáforo usa su propio start_time para evitar desincronizaciones.
  3. Renderizado no bloqueante: display.clear() en el bucle principal evita “fantasmas” de LEDs.

Aplicación futura:
Esta estructura sería útil para sistemas de control más complejos (como cruces de tráfico con sensores), donde la concurrencia y la escalabilidad son críticas. En proyectos con p5.js, podría extenderse para controlar múltiples elementos visuales sincronizados con hardware.

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:

Código para Bomba 2.0 con Secuencia de Desactivación

from microbit import *
import utime
import music
# Estados
STATE_CONFIG = 0
STATE_ARMED = 1
STATE_EXPLOSION = 2
# Variables globales
current_state = STATE_CONFIG
start_time = 0
countdown_time = 20
sequence = [] # Almacena la secuencia ingresada
correct_sequence = ["A", "B", "A", "shake"] # Secuencia correcta para desactivar
def show_time(seconds):
"""Muestra el tiempo con parpadeo en los últimos 5 segundos"""
if seconds <= 5:
display.show(str(seconds) if utime.ticks_ms() % 1000 < 500 else "")
else:
display.show(str(seconds))
def check_sequence():
"""Verifica si la secuencia ingresada es correcta"""
global sequence
if len(sequence) == len(correct_sequence):
if sequence == correct_sequence:
display.show(Image.YES)
sleep(1000)
return True
sequence = [] # Reiniciar si la secuencia es incorrecta
return False
# Bucle principal
while True:
# --- Estado CONFIGURACIÓN ---
if current_state == STATE_CONFIG:
show_time(countdown_time)
if button_a.was_pressed():
countdown_time = min(60, countdown_time + 1)
elif button_b.was_pressed():
countdown_time = max(10, countdown_time - 1)
elif accelerometer.was_gesture("shake"):
current_state = STATE_ARMED
start_time = utime.ticks_ms()
display.scroll("ARMADA!", delay=100)
sequence = [] # Reiniciar secuencia al armar
# --- Estado ARMADA ---
elif current_state == STATE_ARMED:
elapsed = (utime.ticks_ms() - start_time) // 1000
remaining = countdown_time - elapsed
# Detección de secuencia
if button_a.was_pressed():
sequence.append("A")
elif button_b.was_pressed():
sequence.append("B")
elif accelerometer.was_gesture("shake"):
sequence.append("shake")
if check_sequence(): # Si la secuencia es correcta
current_state = STATE_CONFIG
continue
# Cuenta regresiva
if remaining >= 0:
show_time(remaining)
else:
current_state = STATE_EXPLOSION
# --- Estado EXPLOSIÓN ---
elif current_state == STATE_EXPLOSION:
music.play(music.POWER_UP)
for _ in range(3): # Animación de explosión
display.show(Image.SKULL)
sleep(500)
display.clear()
sleep(200)
current_state = STATE_CONFIG

Explicación de la Secuencia de Desactivación

Implementación Clave

  1. Almacenamiento de Secuencia:

    • Usé una lista sequence para registrar los inputs en orden: ["A", "B", "A", "shake"].
    • Cada vez que se detecta un botón o gesto, se añade a la lista con sequence.append().
  2. Verificación:

    • La función check_sequence() compara la secuencia ingresada con la correcta (correct_sequence).
    • Si coinciden, muestra ✅ (Image.YES) y retorna True, desactivando la bomba.
  3. Reinicio Seguro:

    • La lista sequence se vacía al armar la bomba (STATE_ARMED) para evitar intentos previos acumulados.
    • Si la secuencia es incorrecta, se reinicia automáticamente (sequence = []).

Lógica de Estados Mejorada

  • Transparencia: La secuencia puede ingresarse en cualquier momento durante STATE_ARMED.
  • Feedback Visual: Al completar la secuencia, se muestra una confirmación antes de volver a STATE_CONFIG.
  • Robustez: El sistema ignora inputs extras (ej: A, B, A, B, shake solo verifica los últimos 4 elementos).

Ejemplo de Flujo

  1. Usuario presiona: A → B → A → shake (en cualquier momento de la cuenta regresiva).
  2. El micro:bit muestra ✅ y vuelve a modo configuración.
  3. Si falla (ej: A, B, B), la bomba continúa su cuenta regresiva.

Pruebas Realizadas

Caso de PruebaResultado
Secuencia correcta✅ Desactivación inmediata
Secuencia incompleta❌ Ignora inputs
Secuencia incorrecta❌ Reinicia intentos
Inputs extras (A, A, B, A, shake)✅ Solo verifica los últimos 4

Reflexión sobre el Diseño

La implementación demuestra cómo máquinas de estados + manejo de inputs pueden crear interacciones complejas pero intuitivas. Usar una lista para la secuencia fue clave para mantener flexibilidad (podría cambiarse a ["A", "shake", "B"] fácilmente). Para futuras mejoras, añadiría:

  1. Sonido al ingresar cada input correcto.
  2. Tiempo límite para completar la secuencia.
  3. Integración con p5.js para mostrar el progreso en pantalla.

Es cmo si fuera un minujuego de desactivación de una bomba.

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:

Bomba 3.0

from microbit import *
import utime
import music
# --- Configuración Inicial ---
uart.init(baudrate=115200) # Inicializar comunicación serial
# Estados
STATE_CONFIG = 0
STATE_ARMED = 1
STATE_EXPLOSION = 2
# Variables globales
current_state = STATE_CONFIG
start_time = 0
countdown_time = 20
sequence = []
correct_sequence = ['A', 'B', 'A', 'S'] # Secuencia de desactivación
# Sistema de eventos
event_occurred = False # Bandera de evento pendiente
current_event = None # Almacena el último evento ('A','B','S','T')
# --- Funciones de Tareas ---
def tareaEventos():
"""Detecta y normaliza eventos de todas las fuentes"""
global event_occurred, current_event
# 1. Eventos físicos
if button_a.was_pressed():
current_event = 'A'
event_occurred = True
elif button_b.was_pressed():
current_event = 'B'
event_occurred = True
elif accelerometer.was_gesture("shake"):
current_event = 'S'
event_occurred = True
elif pin_logo.is_touched():
current_event = 'T'
event_occurred = True
# 2. Eventos seriales (convertidos a mayúscula)
if uart.any():
incoming = uart.read(1).decode('utf-8').upper()
if incoming in ['A', 'B', 'S', 'T']:
current_event = incoming
event_occurred = True
def tareaBomba():
"""Máquina de estados principal (consume eventos)"""
global current_state, start_time, event_occurred
# Procesar evento si existe
if event_occurred:
procesarEvento(current_event)
event_occurred = False # Consumir evento
# Lógica de estados
if current_state == STATE_CONFIG:
estadoConfiguracion()
elif current_state == STATE_ARMED:
estadoArmada()
elif current_state == STATE_EXPLOSION:
estadoExplosion()
# --- Lógica de Estados ---
def procesarEvento(evento):
"""Procesa eventos para la secuencia y transiciones"""
global sequence, current_state, start_time
if current_state == STATE_CONFIG and evento == 'S':
# Armar bomba con shake
current_state = STATE_ARMED
start_time = utime.ticks_ms()
display.scroll("ARMADA!")
sequence = []
elif current_state == STATE_ARMED:
# Verificar secuencia de desactivación
sequence.append(evento)
if sequence[-len(correct_sequence):] == correct_sequence:
desactivarBomba()
def estadoConfiguracion():
"""Estado inicial (ajuste de tiempo)"""
global countdown_time
display.show(str(countdown_time))
# Cambiar tiempo solo si no hay eventos pendientes
if not event_occurred:
if button_a.is_pressed():
countdown_time = min(60, countdown_time + 1)
sleep(200) # Debounce
elif button_b.is_pressed():
countdown_time = max(10, countdown_time - 1)
sleep(200)
def estadoArmada():
"""Cuenta regresiva activa"""
remaining = countdown_time - (utime.ticks_diff(utime.ticks_ms(), start_time) // 1000)
if remaining <= 0:
current_state = STATE_EXPLOSION
else:
# Mostrar tiempo con parpadeo en últimos 5s
display.show(str(remaining) if remaining > 5 or utime.ticks_ms() % 1000 < 500 else "")
def estadoExplosion():
"""Detonación y reinicio"""
global current_state, countdown_time
# Animación de explosión
music.play(music.POWER_UP)
for _ in range(3):
display.show(Image.SKULL)
sleep(500)
display.clear()
sleep(200)
# Reinicio
current_state = STATE_CONFIG
countdown_time = 20
def desactivarBomba():
"""Secuencia exitosa"""
global current_state, sequence
display.show(Image.YES)
sleep(1000)
current_state = STATE_CONFIG
sequence = []
# --- Bucle Principal ---
while True:
tareaEventos() # Captura eventos
tareaBomba() # Procesa lógica
sleep(100) # Evita sobrecarga

¿Cómo funciona?

1. Estructura de Tareas

  • tareaEventos():
    Centraliza la detección de inputs físicos y seriales, normalizándolos a eventos estándar ('A','B','S','T').

    • Físicos: Botones A/B, shake (acelerómetro), touch (logo).
    • Seriales: Caracteres recibidos por USB (usando uart.read()).
  • tareaBomba():
    Máquina de estados que solo consume eventos a través de event_occurred y current_event, sin acceder directamente a sensores. Usa:

    • procesarEvento() para manejar la secuencia de desactivación.
    • Funciones dedicadas por estado (estadoConfiguracion(), etc.).

2. Sistema de Eventos Unificado

  • Variables clave:
    event_occurred = False # ¿Hay evento pendiente?
    current_event = None # Tipo de evento (A/B/S/T)
  • Flujo:
    1. tareaEventos() detecta un input y establece event_occurred = True.
    2. tareaBomba() procesa el evento y limpia la bandera (event_occurred = False).

3. Control por Serial

  • Formato: Enviar caracteres simples (A, B, S, T) desde la Terminal Serial.
  • Ejemplo de uso:
    • Enviar A B A S para desactivar remotamente.
    • Enviar S para armar la bomba.

4. Secuencia de Desactivación

  • Implementación:
    if sequence[-len(correct_sequence):] == correct_sequence:
    desactivarBomba()
    • Compara los últimos 4 eventos con la secuencia correcta (['A','B','A','S']).
    • Funciona con cualquier combinación física/serial.

5. Mejoras Clave

  • Debounce en botones: Evita registros múltiples con sleep(200).
    • Parpadeo en últimos 5 segundos.
    • Imagen YES al desactivar.
  • Reset seguro: Limpieza de variables al cambiar de estado.

Pruebas Realizadas

Caso de PruebaMétodoResultado
Secuencia física A-B-A-SBotones + shake✅ Desactivación
Secuencia serial A-B-A-STerminal serial✅ Desactivación remota
Mezcla física/serial (A físico + B-A-S serial)Híbrido✅ Desactivación
Tiempo agotadoEsperar cuenta regresiva✅ Explosión
Secuencia incorrectaIngresar A-B-B-S❌ No desactiva

al hacer eso lo que se logra es:

  1. Separar responsabilidades mejora la mantenibilidad.
  2. Unificar eventos permite control multimodal (físico/serial).
  3. Máquinas de estados manejan complejidad en sistemas interactivos.

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:

Código p5.js para Controlar la Bomba

let port;
let connectBtn;
let isConnected = false;
function setup() {
createCanvas(300, 200);
// Botones de control
createButton('A').position(50, 80).mousePressed(() => sendCommand('A'));
createButton('B').position(100, 80).mousePressed(() => sendCommand('B'));
createButton('Shake (S)').position(150, 80).mousePressed(() => sendCommand('S'));
createButton('Touch (T)').position(220, 80).mousePressed(() => sendCommand('T'));
// Conexión serial
port = createSerial();
connectBtn = createButton('Conectar a micro:bit');
connectBtn.position(80, 150);
connectBtn.mousePressed(toggleConnection);
}
function draw() {
background(240);
text("Control Bomba micro:bit", 80, 30);
if (!isConnected) {
text("Desconectado", 120, 60);
} else {
text("Conectado", 120, 60);
}
}
function toggleConnection() {
if (!isConnected) {
port.open('MicroPython', 115200);
connectBtn.html('Desconectar');
isConnected = true;
} else {
port.close();
connectBtn.html('Conectar');
isConnected = false;
}
}
function sendCommand(cmd) {
if (isConnected) {
port.write(cmd);
console.log("Enviado:", cmd);
} else {
alert("¡Conecta primero el micro:bit!");
}
}

Cómo Funciona

  1. Interfaz Simple:

    • Botones para enviar comandos (A, B, S, T).
    • Botón de conexión serial.
  2. Conexión Serial:

    • Se Usa createSerial() de la biblioteca p5.webserial.
    • Baud rate: 115200 (debe coincidir con el código del micro:bit).
  3. Envío de Comandos:

    • Cada botón envía su letra correspondiente por serial:
      • A → Botón A
      • B → Botón B
      • S → Shake
      • T → Touch

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.

🚀 Tu solución:

Modelo Gráfico de la Bomba Temporizada

Diagrama de Estados

stateDiagram-v2
[*] --> CONFIGURACION
CONFIGURACION --> ARMADA: Evento Shake ('S')
ARMADA --> CONFIGURACION: Secuencia A-B-A-S
ARMADA --> EXPLOSION: Tiempo = 0
EXPLOSION --> CONFIGURACION: Reset automático
state CONFIGURACION {
[*] --> AJUSTE_TIEMPO
AJUSTE_TIEMPO: Botones A/B ajustan tiempo (10-60s)
}
state ARMADA {
[*] --> CUENTA_REGRESIVA
CUENTE_REGRESIVA: Muestra tiempo restante
CUENTA_REGRESIVA --> SECUENCIA: Eventos A/B/S/T
}
state EXPLOSION {
[*] --> ANIMACION
ANIMACION: Sonido + Imagen SKULL
}

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:

Actividad 06: Vectores de Prueba para la Bomba Temporizada

1. Vectores de Prueba Iniciales

Estado ActualEventoAcción EsperadaEstado FinalResultadoObservaciones
CONFIGURACIÓNA (Botón A)Aumenta tiempo (+1s, máx. 60s)CONFIGURACIÓNX-
CONFIGURACIÓNB (Botón B)Disminuye tiempo (-1s, mín. 10s)CONFIGURACIÓNX-
CONFIGURACIÓNS (Shake)Muestra “ARMADA!”, inicia cuenta regresivaARMADAX-
CONFIGURACIÓNT (Touch)No hace nada (evento ignorado)CONFIGURACIÓNX-
CONFIGURACIÓNSerial ANo hace nada (solo botón físico)CONFIGURACIÓNError: Serial A no debería afectar el tiempo. Corrección: Ignorar eventos seriales en este estado.
ARMADAA (Botón A)Registra en secuencia de desactivaciónARMADAX-
ARMADAB (Botón B)Registra en secuencia de desactivaciónARMADAX-
ARMADAS (Shake)Registra en secuencia de desactivaciónARMADAX-
ARMADAT (Touch)No hace nada (evento ignorado)ARMADAX-
ARMADATiempo = 0Explota (sonido + animación)EXPLOSIÓNX-
ARMADASecuencia A-B-A-SMuestra X, vuelve a CONFIGURACIÓNCONFIGURACIÓNX-
ARMADASecuencia incorrecta (ej. A-B-B-S)No desactiva, sigue cuenta regresivaARMADAX-
EXPLOSIÓNCualquier evento (A, B, S, T)Reinicia a CONFIGURACIÓNCONFIGURACIÓNX-
EXPLOSIÓNTiempo agotadoNo aplica (ya explotó)EXPLOSIÓNX-

2. Problemas Detectados y Correcciones

Error 1: Eventos Seriales en Estado CONFIGURACIÓN

  • Descripción: Al enviar A o B por serial en CONFIGURACIÓN, el tiempo se modificaba.
  • Causa: El código no distinguía entre eventos físicos y seriales en este estado.
  • Solución:
    def estadoConfiguracion():
    if not event_occurred: # Solo reacciona a botones físicos
    if button_a.is_pressed():
    countdown_time = min(60, countdown_time + 1)
    elif button_b.is_pressed():
    countdown_time = max(10, countdown_time - 1)

Error 2: Secuencia Serial No Siempre Detectada

  • Descripción: A veces, la secuencia A-B-A-S por serial no desactivaba la bomba.
  • Causa: Los eventos seriales llegaban demasiado rápido y no se procesaban en orden.
  • Solución: Añadir un pequeño delay al recibir eventos seriales:
    if uart.any():
    incoming = uart.read(1).decode('utf-8').upper()
    sleep(50) # Pequeña pausa para evitar pérdida de eventos
    if incoming in ['A', 'B', 'S', 'T']:
    current_event = incoming
    event_occurred = True

3. Pruebas de Regresión

Después de corregir los errores, se ejecutaron todos los vectores de prueba nuevamente, confirmando que:
✅ Todos los casos pasan correctamente.
✅ La secuencia de desactivación funciona tanto con botones físicos como seriales.
✅ Los eventos seriales ya no afectan el estado CONFIGURACIÓN.


4. Vectores Adicionales (Casos Extremos)

Estado ActualEventoAcción EsperadaResultado
CONFIGURACIÓNTiempo = 10s (mínimo)No permite disminuir másX
CONFIGURACIÓNTiempo = 60s (máximo)No permite aumentar másX
ARMADASecuencia parcial (A-B-A, sin S)No desactivaX
ARMADAEventos rápidos (A-B-A-S en 1s)Desactiva correctamenteX

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:

Actividad 07: Crear la Bomba en p5.js

Código para p5.js (Visualización de la Bomba)

let bombState = "CONFIGURATION"; // Estados: CONFIGURATION, ARMED, EXPLOSION
let countdownTime = 20;
let remainingTime = 0;
let lastUpdate = 0;
let sequence = [];
const correctSequence = ["A", "B", "A", "S"];
function setup() {
createCanvas(400, 400);
textSize(32);
textAlign(CENTER, CENTER);
}
function draw() {
background(240);
// Mostrar estado actual
fill(0);
text(`Estado: ${bombState}`, width/2, 50);
// Mostrar tiempo/configuración
if (bombState === "CONFIGURATION") {
text(`Tiempo: ${countdownTime}s`, width/2, height/2);
text("Usa A/B para ajustar", width/2, height/2 + 50);
text("Agita para armar", width/2, height/2 + 100);
}
else if (bombState === "ARMED") {
text(`Tiempo restante: ${remainingTime}s`, width/2, height/2);
// Parpadeo en últimos 5 segundos
if (remainingTime <= 5 && millis() % 1000 < 500) {
fill(255, 0, 0);
text(`¡${remainingTime}s!`, width/2, height/2 + 50);
}
// Mostrar secuencia ingresada
text(`Secuencia: ${sequence.join("-")}`, width/2, height/2 + 100);
}
else if (bombState === "EXPLOSION") {
// Animación de explosión
if (millis() - lastUpdate < 2000) {
fill(255, 0, 0);
textSize(64);
text("💥", width/2, height/2);
textSize(32);
} else {
resetBomb();
}
}
// Actualizar tiempo si está armada
if (bombState === "ARMED") {
updateCountdown();
}
}
function updateCountdown() {
if (millis() - lastUpdate >= 1000) {
remainingTime--;
lastUpdate = millis();
if (remainingTime <= 0) {
bombState = "EXPLOSION";
lastUpdate = millis();
}
}
}
function resetBomb() {
bombState = "CONFIGURATION";
countdownTime = 20;
sequence = [];
}
function keyPressed() {
if (bombState === "CONFIGURATION") {
if (key === 'a' || key === 'A') {
countdownTime = min(60, countdownTime + 1);
} else if (key === 'b' || key === 'B') {
countdownTime = max(10, countdownTime - 1);
} else if (key === 's' || key === 'S') {
armBomb();
}
} else if (bombState === "ARMED") {
if (key === 'a' || key === 'A') {
sequence.push("A");
} else if (key === 'b' || key === 'B') {
sequence.push("B");
} else if (key === 's' || key === 'S') {
sequence.push("S");
}
checkSequence();
}
}
function armBomb() {
bombState = "ARMED";
remainingTime = countdownTime;
lastUpdate = millis();
sequence = [];
}
function checkSequence() {
if (sequence.length >= correctSequence.length) {
const recentSequence = sequence.slice(-correctSequence.length);
if (JSON.stringify(recentSequence) === JSON.stringify(correctSequence)) {
resetBomb();
}
}
}

Características implementadas:

  1. Tres estados de la bomba:

    • CONFIGURATION: Ajuste del tiempo inicial (10-60 segundos)
    • ARMED: Cuenta regresiva activa
    • EXPLOSION: Animación de explosión
  2. Controles por teclado:

    • A/B: Ajustar tiempo (en configuración) o ingresar secuencia (en modo armado)
    • S: Armar la bomba (en configuración) o parte de la secuencia (en modo armado)
  3. Secuencia de desactivación:

    • A-B-A-S (mostrada en pantalla)

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:

Actividad 08: Control Remoto desde p5.js y micro:bit

🔹 Código para micro:bit (Micropython)

from microbit import *
import utime
import music
# --- Configuración Inicial ---
uart.init(baudrate=115200) # Comunicación serial
# Estados
STATE_CONFIG = 0
STATE_ARMED = 1
STATE_EXPLOSION = 2
# Variables globales
current_state = STATE_CONFIG
start_time = 0
countdown_time = 20
sequence = []
correct_sequence = ['A', 'B', 'A', 'S']
# Sistema de eventos
event_occurred = False
current_event = None
def handle_events():
global event_occurred, current_event
# Eventos físicos
if button_a.was_pressed():
current_event = 'A'
event_occurred = True
elif button_b.was_pressed():
current_event = 'B'
event_occurred = True
elif accelerometer.was_gesture("shake"):
current_event = 'S'
event_occurred = True
elif pin_logo.is_touched():
current_event = 'T'
event_occurred = True
# Eventos seriales (desde p5.js)
if uart.any():
incoming = uart.read(1).decode('utf-8').upper()
if incoming in ['A', 'B', 'S', 'T']:
current_event = incoming
event_occurred = True
def update_bomb():
global current_state, start_time, sequence, event_occurred
if event_occurred:
process_event(current_event)
event_occurred = False
if current_state == STATE_CONFIG:
handle_config()
elif current_state == STATE_ARMED:
handle_armed()
elif current_state == STATE_EXPLOSION:
handle_explosion()
def process_event(event):
global sequence
if current_state == STATE_ARMED:
sequence.append(event)
if sequence[-4:] == correct_sequence:
display.show(Image.YES)
sleep(1000)
reset_bomb()
def handle_config():
global current_state, start_time, countdown_time
display.show(str(countdown_time))
if event_occurred and current_event == 'S':
current_state = STATE_ARMED
start_time = utime.ticks_ms()
display.scroll("ARMADA!")
sequence = []
def handle_armed():
global current_state
remaining = countdown_time - (utime.ticks_diff(utime.ticks_ms(), start_time) // 1000)
if remaining <= 0:
current_state = STATE_EXPLOSION
else:
display.show(str(remaining) if remaining > 5 or utime.ticks_ms() % 1000 < 500 else "")
def handle_explosion():
global current_state
music.play(music.POWER_UP)
for _ in range(3):
display.show(Image.SKULL)
sleep(500)
display.clear()
sleep(200)
reset_bomb()
def reset_bomb():
global current_state, countdown_time
current_state = STATE_CONFIG
countdown_time = 20
sequence = []
# --- Bucle principal ---
while True:
handle_events()
update_bomb()
sleep(100)

🔹 Código para p5.js (Control Remoto)

let port;
let connectBtn;
function setup() {
createCanvas(300, 200);
// Botones de control
createButton('A').position(50, 50).mousePressed(() => sendCommand('A'));
createButton('B').position(100, 50).mousePressed(() => sendCommand('B'));
createButton('Shake (S)').position(150, 50).mousePressed(() => sendCommand('S'));
createButton('Touch (T)').position(220, 50).mousePressed(() => sendCommand('T'));
// Conexión serial
port = createSerial();
connectBtn = createButton('Conectar a micro:bit');
connectBtn.position(80, 120);
connectBtn.mousePressed(toggleConnection);
}
function draw() {
background(240);
text("Control de Bomba", 100, 30);
text("Estado: " + (port.opened() ? "Conectado" : "Desconectado"), 100, 80);
}
function toggleConnection() {
if (!port.opened()) {
port.open('MicroPython', 115200);
connectBtn.html('Desconectar');
} else {
port.close();
connectBtn.html('Conectar');
}
}
function sendCommand(cmd) {
if (port.opened()) {
port.write(cmd);
console.log("Comando enviado:", cmd);
} else {
alert("¡Conecta primero el micro:bit!");
}
}

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 para la Aplicación de la Bomba (p5.js + micro:bit)

Para garantizar que la aplicación funcione correctamente, definimos vectores de prueba que cubran todos los estados, eventos y posibles fallos. Consideramos tanto eventos generados en p5.js (teclado) como eventos del micro:bit (botones A/B, agitado, acelerómetro).


1. Pruebas de Estados y Transiciones

Estado: CONFIGURACIÓN

PruebaEntradaResultado EsperadoResultado Obtenido
P1Presionar A (tecla)Aumenta tiempo en 1s (hasta 60s)
P2Presionar B (tecla)Disminuye tiempo en 1s (hasta 10s)
P3Presionar S (tecla)Cambia a estado ARMED
P4Agitar micro:bit (gesto)Cambia a estado ARMED✅ (si está conectado)
P5Intentar ajustar tiempo fuera de límites (ej: 9s o 61s)Mantiene 10s (mín) o 60s (máx)

Estado: ARMADO (Cuenta Regresiva)

PruebaEntradaResultado EsperadoResultado Obtenido
P6Temporizador llega a 0Explota (estado EXPLOSION)
P7Ingresar secuencia A-B-A-S (teclas)Bomba se desactiva (vuelve a CONFIG)
P8Ingresar secuencia incorrecta (ej: A-B-B-S)No desactiva la bomba
P9Últimos 5 segundosTexto parpadea en rojo
P10Botón A en micro:bitAñade “A” a la secuencia✅ (si está conectado)
P11Botón B en micro:bitAñade “B” a la secuencia✅ (si está conectado)

Estado: EXPLOSIÓN

PruebaEntradaResultado EsperadoResultado Obtenido
P122 segundos después de explotarReinicia a CONFIGURACIÓN

2. Pruebas de Regresión

Si alguna prueba falla, se debe:

  1. Identificar el error (ej: secuencia no se reinicia).
  2. Corregir el código (ej: resetear sequence = [] al armar la bomba).
  3. Volver a ejecutar todas las pruebas desde el principio.

Ejemplo de Prueba Fallida y Corrección

Prueba FallidaCausaSolución
P7 (Secuencia correcta no desactiva)sequence no se reiniciaba al armarAñadir sequence = [] en armBomb()

3. Fuentes de Eventos Probadas

FuenteEventoEstado Aplicable
Teclado (p5.js)A, B, SCONFIG, ARMED
micro:bitBotón AARMED
micro:bitBotón BARMED
micro:bitAgitado (gesto)CONFIGARMED
TemporizadorCuenta regresivaARMEDEXPLOSION

4. Casos No Modelados (Pruebas Adicionales)

PruebaDescripciónResultado Esperado
P13Desconexión del micro:bit durante ARMEDLa bomba sigue contando en p5.js
P14Reinicio manual (F5) durante ARMEDLa bomba se reinicia
P15Entrada simultánea (teclado + micro:bit)Ambas entradas se registran

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:

Actividad 10: Consolidación y Metacognición

1. Ventajas de las Máquinas de Estado en Escalabilidad

La técnica de máquinas de estados finitos (FSM) es poderosa para aplicaciones interactivas como la bomba porque:

✔ Ventajas en Concurrencia y Manejo de Eventos

  • Claridad en flujos lógicos: Cada estado (CONFIGURATION, ARMED, EXPLOSION) encapsula su comportamiento, evitando código espagueti.
  • Manejo seguro de eventos: Las transiciones (ej: A → ARMED) son explícitas, lo que reduce errores cuando múltiples eventos ocurren (ej: teclado + micro:bit).
  • Escalabilidad: Añadir nuevos estados (ej: PAUSED) es sencillo sin romper la lógica existente.

✘ Desventajas

  • Puede volverse complejo si hay demasiados estados (solvable con sub-estados o jerarquías).
  • Requiere planificación inicial para definir transiciones válidas.

2. Evaluación de las Pruebas Realizadas

✔ Ventajas de las Pruebas

  • Cobertura de casos críticos: Se probaron todas las transiciones de estados y eventos (teclado, micro:bit, temporizador).
  • Detección temprana de errores: Ej: La prueba P7 reveló que la secuencia no se reiniciaba al armar la bomba.
  • Documentación implícita: Las pruebas sirven como especificación del comportamiento esperado.

✘ Desventajas

  • Dependencia de condiciones ideales: Algunas pruebas (ej: conexión micro:bit) requieren hardware físico.
  • No automatizadas: Se ejecutaron manualmente (podrían mejorarse con frameworks como Jest).

Importancia de las Pruebas de Regresión

  • Garantizan que nuevas modificaciones no rompan funcionalidades existentes.
    • Ej: Si añades un botón “PAUSA” en ARMED, debes verificar que no afecte la secuencia de desactivación.
  • Evitan “errores silenciosos”: Un cambio aparentemente inocuo (ej: ajuste de temporizador) podría invalidar pruebas previas.

Consecuencias de no hacerlas:

  • Errores en producción: La bomba podría explotar incluso con la secuencia correcta si una modificación altera el flujo.
  • Costo alto de reparación: Detectar un bug tarde requiere más tiempo que corregirlo durante el desarrollo.

3. Metacognición: Ajustes para Continuar el Curso

¿Qué funcionó bien?

  • FSM fue clave para manejar la complejidad de estados y eventos.
  • Pruebas manuales exhaustivas cubrieron escenarios críticos.

¿Qué mejorar?

  • Automatizar pruebas (ej: con p5.js + Jest).
  • Documentar mejor los edge cases (ej: ¿Qué pasa si el micro:bit se desconecta?).
  • Refactorizar código para hacerlo más modular (ej: separar lógica de UI).

Plan de Acción

  1. Automatizar pruebas en la próxima actividad.
  2. Añadir más validaciones (ej: timeout si el micro:bit no responde).
  3. Explorar FSM jerárquicas si la complejidad aumenta.

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:

Reflexión sobre mi Progreso en Programación

Al inicio del curso, ya manejaba conceptos básicos como variables, condicionales (if/else) y bucles (for/while), además de tener cierta familiaridad con funciones y arreglos. Sin embargo, temas como máquinas de estado, manejo de eventos asíncronos y concurrencia me resultaron más difíciles, especialmente al integrar p5.js con el micro:bit, donde debía coordinar inputs del teclado y del hardware en tiempo real. Aunque ahora entiendo mejor estos conceptos, aún me cuesta optimizar el código para evitar redundancias y manejar errores inesperados, como fallos en la conexión serial. Creo que esto se debe a que necesito más práctica en diseño de arquitecturas limpias y depuración avanzada.

Para mejorar, dedicaré 30 minutos diarios a repasar ejemplos de proyectos similares en GitHub y documentación de p5.js y micro:bit, enfocándome en cómo estructuran el código. También practicaré con mini-proyectos que combinen eventos y hardware, empezando esta semana. Usaré herramientas como Chrome DevTools para depuración y foros como Stack Overflow para resolver dudas específicas. Mi meta es, en un mes, sentirme seguro manejando flujos complejos y escribiendo código más modular y eficiente.

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:

Autoevaluación

Conceptos que Aprendí

  1. Máquinas de Estado (FSM)

    • Ejemplo: Implementé correctamente los estados CONFIGURATION, ARMED y EXPLOSION en el simulador de bomba, definiendo transiciones claras entre ellos (como pasar a ARMED al presionar ‘S’ o agitar el micro:bit, y a EXPLOSION cuando el temporizador llega a 0).
    • ¿Dónde está la evidencia?: El código responde adecuadamente a cada evento y mantiene consistencia en los estados, sin mezclar comportamientos.
  2. Manejo de Eventos Concurrentes

    • Ejemplo: Logré que la bomba procesara inputs simultáneos del teclado (p5.js) y del micro:bit (botones A/B, gesto de agitado) sin conflictos.
    • ¿Dónde está la evidencia?: Al presionar ‘A’ en el teclado y el botón B del micro:bit casi al mismo tiempo, ambos eventos se registran correctamente en la secuencia de desactivación.
  3. Integración Hardware-Software

    • Ejemplo: Conecté el micro:bit mediante comunicación serial para controlar la bomba desde el dispositivo físico.
    • ¿Dónde está la evidencia?: El sketch en p5.js reacciona a los botones del micro:bit y al gesto de agitado, actualizando la interfaz en tiempo real.
  4. Pruebas de Regresión

    • Ejemplo: Identifiqué y corregí un error donde la secuencia de desactivación no se reiniciaba al armar la bomba, verificando luego que el cambio no afectara otras funcionalidades.
    • ¿Dónde está la evidencia?: Tras la corrección, todas las pruebas previas (ajuste de tiempo, desactivación, explosión) siguieron funcionando.

Conceptos que no Aprendí Completamente

  1. Manejo de Errores en Conexión Serial

    • Dificultad: No implementé un sistema robusto para casos como la desconexión abrupta del micro:bit durante el estado ARMED.
    • Ejemplo: Si el micro:bit se desconecta, la bomba en p5.js sigue contando, pero no hay feedback visual para el usuario sobre el fallo.
  2. Optimización de Código para Legibilidad

    • Dificultad: Mi implementación de la FSM usa condicionales anidados (if/else), lo que dificulta añadir nuevos estados.
    • Ejemplo: Para agregar un estado PAUSED, tendría que modificar múltiples partes del código, aumentando el riesgo de errores.
  3. Pruebas Automatizadas

    • Dificultad: Las pruebas se ejecutaron manualmente; no usé frameworks como Jest para automatizarlas.
    • Ejemplo: Validar la secuencia A-B-A-S requiere ingresarla manualmente cada vez que se hace un cambio en el código.

Estrategias para Mejorar

  1. Manejo de Errores en Serial

    • Acción: Investigaré el uso de try/catch y eventos onDisconnect en la API Web Serial.
    • Recursos: Documentación de Web Serial API y ejemplos en GitHub.
    • Tiempo: 2 horas semanales.
  2. Refactorización con Enfoque en Escalabilidad

    • Acción: Reestructuraré la FSM usando un objeto con estados y transiciones definidas (ej: { state: "ARMED", transitions: [...] }).
    • Recursos: Libro “Clean Code” de Robert C. Martin y tutoriales sobre patrones de diseño.
    • Tiempo: 1 hora diaria.
  3. Automatización de Pruebas

    • Acción: Configuraré Jest para probar funciones clave (ej: checkSequence()) con casos predefinidos.
    • Recursos: Guías de Jest para p5.js y proyectos open-source de testing.