Turbo Reflex: un juego de reflejos

Turbo Reflex es un proyecto que convierte una placa Arduino en un juego interactivo de reflejos. La dinámica es sencilla: una pantalla LCD muestra una acción que el jugador debe realizar en un tiempo limitado. Si lo consigue, recibe una señal positiva; si falla, el sistema lo indica con un aviso negativo. Además, al finalizar el juego en caso de superar todos los retos sonará un sonido de victoria y en caso contrario sonará un sonido de derrota

El juego está diseñado para ser dinámico, portable, flexible y desafiante. Incluye distintos modos (individual y multijugador) y cuatro niveles de dificultad (fácil, medio, difícil e insano), lo que permite adaptar la experiencia a diferentes usuarios.

En el modo multijugador, los jugadores deben pasarse el dispositivo tras completar cada reto, lo que añade un componente social y competitivo. Además, las distintas dificultades no solo modifican el tiempo disponible para responder, sino también el número de vidas, haciendo que el juego sea progresivamente más exigente.

A nivel técnico, el sistema integra múltiples componentes como sensores de ultrasonido, infrarrojos, LDR, joystick, entre otros, junto con una pantalla LCD y un buzzer. Esta combinación permite detectar acciones en tiempo real y proporcionar un feedback inmediato al jugador, tanto visual como sonoro.

El funcionamiento sigue un flujo claro: el juego esta inicialmente apagado, tras encenderse el usuario selecciona el modo y el nivel de dificultad, el sistema muestra un reto en pantalla y los sensores verifican si se completa correctamente dentro del tiempo establecido. Los retos se generan de forma aleatoria, lo que aumenta la rejugabilidad.

El sistema también incorpora un control mediante mando que permite apagar el juego en cualquier momento. Al volver a encenderlo, el sistema se reinicia completamente, asegurando una experiencia limpia desde el inicio.

Para gestionar todo esto, el programa se estructura en tres estados principales: apagado, iniciando y jugando. Esto facilita el control del flujo del juego y mejora la organización interna del código.

Durante el desarrollo surgieron desafíos como la limitación de pines del Arduino o conflictos entre componentes (por ejemplo, el uso simultáneo del buzzer y el mando). Estos problemas se resolvieron optimizando el uso de pines —incluso reutilizando pines analógicos como digitales— y simplificando las conexiones.

El resultado final es un prototipo jugable, escalable y entretenido, que demuestra la correcta integración de hardware, programación y sistemas en tiempo real.

Conexiones

Código

#include <Arduino.h>
#include <IRremote.hpp>
#include <LiquidCrystal.h>
#include <TM1637Display.h>

#define NOTE_G4 392
#define NOTE_C5 523
#define NOTE_E5 659
#define NOTE_G5 784
#define NOTE_C6 1047
#define NOTE_E6 1319
#define NOTE_G6 1568
#define NOTE_GS4 415
#define NOTE_DS5 622
#define NOTE_GS5 831
#define NOTE_DS6 1245
#define NOTE_GS6 1661
#define NOTE_AS4 466
#define NOTE_F5 698
#define NOTE_AS5 932
#define NOTE_F6 1397
#define NOTE_AS6 1865
#define NOTE_C7 2093
#define NOTE_D5 587
#define NOTE_D6 1175

//           LCD (RS, E, D4, D5, D6, D7)
LiquidCrystal lcd(12, 11, 10, 9, 8, 13);

const int LDR = A0;
const int BUZZER = 2;
const int TILTBALL = 3;
const int JOYSTICK = 5;

const int pinX = A1;
const int pinY = A2;

const int IR_RECEIVE_PIN = 7;
const int CLK = 6;
TM1637Display display(CLK, A5);
const unsigned long APAGAR = 0xBA45FF00;

enum EstadoSistema { APAGADO,
                     INICIANDO,
                     JUGANDO };
volatile EstadoSistema estadoActual = APAGADO;
volatile bool peticionCambio = false;
bool menuMostrado = false;
unsigned long tiempoMenu = 0;
int modoActual = -1;
const int MULTIJUGADOR = 0;
const int SOLITARIO = 1;
const int tiltPin = 13;
int ultimoSegundoSonao = -1;
int canalRetoActual;
const int CANAL_BUZZER = 3;
unsigned long mapaIR[] = {
  0xE916FF00,  // [0]
  0xF30CFF00,  // [1]
  0xE718FF00,  // [2]
  0xA15EFF00,  // [3]
  0xF708FF00,  // [4]
  0xE31CFF00,  // [5]
  0xA55AFF00,  // [6]
  0xBD42FF00,  // [7]
  0xAD52FF00,  // [8]
  0xB54AFF00   // [9]
};



int dificultad;

const int FACIL = 0;
const int MEDIA = 1;
const int DIFICIL = 2;
const int INSANO = 3;
const int trigPin = A3;
const int echoPin = A4;

enum SubMenu {
  MENU_MODO,
  MENU_DIFICULTAD
};

SubMenu subMenu = MENU_MODO;

void setup() {
  Serial.begin(9600);
  IrReceiver.begin(IR_RECEIVE_PIN);
  lcd.begin(16, 2);
  randomSeed(analogRead(3));
  pinMode(JOYSTICK, OUTPUT);
  digitalWrite(JOYSTICK, HIGH);

  pinMode(LDR, INPUT);
  pinMode(TILTBALL, INPUT);
  pinMode(BUZZER, OUTPUT);

  pinMode(A4, OUTPUT);

  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  
  display.setBrightness(7);
  display.clear();
  delay(1000);
}




void loop() {
  // 1. Siempre escuchamos el mando
  if (IrReceiver.decode()) {
    if (IrReceiver.decodedIRData.decodedRawData == APAGAR) {
      peticionCambio = true;
      Serial.println("¡Botón pulsado!");
    }
    IrReceiver.resume();
  }

  // 2. Máquina de Estados
  switch (estadoActual) {

    case JUGANDO:
      if (peticionCambio) {
        // ACCIÓN AL APAGAR
        lcd.clear();
        lcd.noDisplay();  // Apaga el texto
        Serial.println("Entrando en modo APAGADO...");
        estadoActual = APAGADO;
        peticionCambio = false;  // Consumimos la pulsación
      } else {
        iniciarJuego();
      }
      break;

    case APAGADO:
      lcd.clear();
      display.clear();
      // En este estado, el Arduino no hace NADA.
      // Solo espera a que peticionCambio sea true otra vez.
      if (peticionCambio) {
        Serial.println("Despertando y Reiniciando...");
        lcd.display();             // Encendemos el driver del LCD
        estadoActual = INICIANDO;  // Vamos a reiniciar
        peticionCambio = false;    // Consumimos la pulsación
      }
      break;

    case INICIANDO:
      if (subMenu == MENU_MODO) {

        if (!menuMostrado) {
          IrReceiver.resume();
          IrReceiver.stopTimer();
          IrReceiver.start();

          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("TURBO REFLEX");
          lcd.setCursor(0, 1);
          lcd.print("LISTO");

          delay(1000);

          lcd.clear();
          lcd.print("0:Multi 1:Soli");

          tiempoMenu = millis();
          menuMostrado = true;
        }

        if (millis() - tiempoMenu > 20000) {
          menuMostrado = false;
        }

        if (IrReceiver.decode()) {

          uint32_t codigo = IrReceiver.decodedIRData.decodedRawData;
          IrReceiver.resume();

          if (codigo == APAGAR) {
            estadoActual = APAGADO;
            peticionCambio = false;
            menuMostrado = false;
            return;
          }

          if (codigo == mapaIR[0]) {
            modoActual = MULTIJUGADOR;
            lcd.clear();
          lcd.print("Modo multi");
          } else if (codigo == mapaIR[1]) {
            modoActual = SOLITARIO;
            lcd.clear();
            lcd.print("Modo solitario");
          } else {
            return;
          }

          
          delay(800);

          // 👉 pasas a dificultad
          subMenu = MENU_DIFICULTAD;
          menuMostrado = false;
        }
      }

      // =========================
      // DIFICULTAD
      // =========================
      else if (subMenu == MENU_DIFICULTAD) {
        
        if (!menuMostrado) {
          lcd.clear();
          lcd.print("Seleccione la"); 
          lcd.setCursor(0, 1); 
          lcd.print("dificultad"); 
          delay(1000); 
          lcd.clear(); 
          lcd.print("0: facil 1:medio"); 
          lcd.setCursor(0, 1); 
          lcd.print("2:dif 3:insano");
          tiempoMenu = millis();
          menuMostrado = true;
        }

        if (millis() - tiempoMenu > 20000) {
          subMenu = MENU_MODO;
          menuMostrado = false;
        }

        if (IrReceiver.decode()) {

          uint32_t codigo = IrReceiver.decodedIRData.decodedRawData;
          IrReceiver.resume();

          if (codigo == APAGAR) {
            estadoActual = APAGADO;
            peticionCambio = false;
            menuMostrado = false;
            subMenu = MENU_MODO;
            return;
          }

          if (codigo == mapaIR[0]){
            dificultad = FACIL;
            lcd.clear();
            lcd.print("Modo facil");
          } else if (codigo == mapaIR[1]){
            dificultad = MEDIA;
            lcd.clear();
            lcd.print("Modo medio");
          }else if(codigo == mapaIR[2]){
            dificultad = DIFICIL;
            lcd.clear();
            lcd.print("Modo dificil");
          }
          else if(codigo == mapaIR[3]){
            dificultad = INSANO;
            lcd.clear();
            lcd.print("Modo insano");
          }else return;

          delay(800);

          estadoActual = JUGANDO;
          menuMostrado = false;
          subMenu = MENU_MODO;  // reset para la próxima vez
        }
      }
      break;
  }
}

//Funciones auxiliares
int SetMuxChannel(byte channel) {
  digitalWrite(muxS0, bitRead(channel, 0));
  digitalWrite(muxS1, bitRead(channel, 1));
  digitalWrite(muxS2, bitRead(channel, 2));
  digitalWrite(muxS3, bitRead(channel, 3));
}


void iniciarJuego() {
  int retoActual;
  int retosSuperados = 0;
  int numVidas;
  int tiempoReto;
  bool inicio = true;
  bool superado;

  if (dificultad == FACIL) {
    numVidas = 3;
    tiempoReto = 13000;
  } else if (dificultad == MEDIA) {
    numVidas = 3;
    tiempoReto = 8000;
  } else if (dificultad == DIFICIL) {
    numVidas = 2;
    tiempoReto = 5000;
  } else {
    numVidas = 1;
    tiempoReto = 2500;
  }

  if (IrReceiver.decode()) {
    if (IrReceiver.decodedIRData.decodedRawData == APAGAR) {
      peticionCambio = true;
      Serial.println("Apagar pulsado!");
      IrReceiver.resume();
      return;
    }
    IrReceiver.resume();
  }
  while (retosSuperados != 15 && numVidas != 0) {
    if (!inicio && modoActual == MULTIJUGADOR) {
      lcd.clear();
      lcd.print("Pasalo!");
      delay(500);
    }
    lcd.clear();
    if (inicio) {
      inicio = false;
    }

    retoActual = random(0, 5);
    switch (retoActual) {
      case 0:
        if (retoInclinacion(tiempoReto)) {
          retosSuperados++;
        } else {
          numVidas--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Fallaste");
          lcd.setCursor(0, 1);
          lcd.print(String("Vidas: ") + numVidas);
          delay(1000);
        }

        break;
      case 1:
        if (retoMando(tiempoReto)) {
          retosSuperados++;
        } else {
          numVidas--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Fallaste ");
          lcd.setCursor(0, 1);
          lcd.print(String("Vidas: ") + numVidas);
          delay(1000);
        }

        break;
      case 2:
        if (retoJoystick(tiempoReto)) {
          retosSuperados++;
        } else {
          numVidas--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Fallaste ");
          lcd.setCursor(0, 1);
          lcd.print(String("Vidas: ") + numVidas);
          delay(1000);
        }
        break;
      case 3:
        if (retoLDR(tiempoReto)) {
          retosSuperados++;
        } else {
          numVidas--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Fallaste ");
          lcd.setCursor(0, 1);
          lcd.print(String("Vidas: ") + numVidas);
          delay(1000);
        }
        break;
      case 4:
        if (retoDistancias(tiempoReto)) {
          retosSuperados++;
        } else {
          numVidas--;
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Fallaste ");
          lcd.setCursor(0, 1);
          lcd.print(String("Vidas: ") + numVidas);
          delay(1000);
        }
        break;
    }
    if (peticionCambio) {
      estadoActual = APAGADO;
      peticionCambio = false;
      return;
    }

    display.clear();
    delay(500);
  }
  display.clear();
  if (retosSuperados == 15) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Enhorabuena!");
    lcd.setCursor(0, 1);
    lcd.print(":)");
    playMarioVictory();
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Perdiste");
    lcd.setCursor(0, 1);
    lcd.print(":(");
    playMarioDefeat();
  }
  delay(2000);  //se muestra 2 segundo
  menuMostrado = false;
  estadoActual = INICIANDO;
}

bool retoInclinacion(int tiempoReto) {
  lcd.setCursor(0, 1);
  lcd.print("Inclina!");
  delay(1000);
  unsigned long tInicio = millis();

  while (millis() - tInicio <= tiempoReto) {  // 10 segundos
    mostrarCuentaAtras(tInicio, tiempoReto);
    int estado = digitalRead(TILTBALL);

    if (IrReceiver.decode()) {
      if (IrReceiver.decodedIRData.decodedRawData == APAGAR) {
        peticionCambio = true;
        Serial.println("Apagar pulsado!");
        IrReceiver.resume();
        return false;
      }
      IrReceiver.resume();
    }

    if (estado == LOW) {
      lcd.clear();
      lcd.print("Superado!");
      delay(1000);
      return true;
    }
    delay(10);  // <-- clave para estabilidad
  }


  return false;
}

bool retoMando(int tiempoReto) {
  int pulsarNum = random(0, 10);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(String("Pulsa ") + pulsarNum);
  Serial.println(pulsarNum);
  delay(1000);

  unsigned long tInicio = millis();
  while (millis() - tInicio <= tiempoReto) {  //le damos 20 segundos
    mostrarCuentaAtras(tInicio, tiempoReto);
    if (IrReceiver.decode()) {
      uint32_t codigo = IrReceiver.decodedIRData.decodedRawData;
      Serial.println(codigo);
      IrReceiver.resume();
      if (codigo == 0xFFFFFFFF || codigo == 0x0) {  //ignorar ruido
        continue;
      }

      if (codigo == mapaIR[pulsarNum]) {
        lcd.clear();
        lcd.print("Superado!");
        delay(1000);
        return true;
      } else if (codigo == APAGAR) {
        peticionCambio = true;
        Serial.println("Apagar pulsado!");
        return false;
      } else {
        return false;
      }
    }
    delay(10);
  }

  return false;
}

bool retoJoystick(int tiempoReto) {
  String objetivo;
  int tipo = random(0, 4);

  switch (tipo) {
    case 0: objetivo = "IZQUIERDA"; break;
    case 1: objetivo = "DERECHA"; break;
    case 2: objetivo = "ARRIBA"; break;
    case 3: objetivo = "ABAJO"; break;
  }

  lcd.clear();
  lcd.print("Haz: ");
  lcd.setCursor(0, 1);
  lcd.print(objetivo);

  unsigned long tInicio = millis();

  while (millis() - tInicio < tiempoReto) {
    mostrarCuentaAtras(tInicio, tiempoReto);
    if (IrReceiver.decode()) {
      if (IrReceiver.decodedIRData.decodedRawData == APAGAR) {
        peticionCambio = true;
        Serial.println("Apagar pulsado!");
        IrReceiver.resume();
        return false;
      }
      IrReceiver.resume();
    }

    String accion = leerJoystick();

    if (accion == objetivo) {
      lcd.clear();
      lcd.print("Correcto!");
      delay(800);
      return true;
    } else if (accion != "CENTRO") {
      return false;
    }

    delay(50);
  }

  lcd.clear();
  lcd.print("Tiempo!");
  delay(800);
  return false;
}

String leerJoystick() {
  int x = analogRead(pinX);
  int y = analogRead(pinY);
  // Mostrar valores crudos
  Serial.print("X: ");
  Serial.print(x);
  Serial.print(" | Y: ");
  Serial.println(y);

  delay(200);

  if (x < 300) return "IZQUIERDA";
  if (x > 700) return "DERECHA";
  if (y > 700) return "ABAJO";
  if (y < 300) return "ARRIBA";


  return "CENTRO";
}

bool retoLDR(int tiempoReto) {
  

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Tapa el LDR");
  lcd.setCursor(0, 1);
  lcd.print("Rapido!");

  delay(1000);

  unsigned long tInicio = millis();

  while (millis() - tInicio <= tiempoReto) {  // 10 segundos
    mostrarCuentaAtras(tInicio, tiempoReto);
    int valorLDR = analogRead(LDR);
    Serial.println(valorLDR);

    if (IrReceiver.decode()) {
      uint32_t codigo = IrReceiver.decodedIRData.decodedRawData;
      IrReceiver.resume();

      if (codigo == APAGAR) {
        peticionCambio = true;
        Serial.println("Apagar pulsado!");
        return false;
      }
    }

    if (valorLDR <= 220) {
      delay(700);
      lcd.clear();
      lcd.print("Superado!");
      delay(1000);
      return true;
    }


    delay(50);
  }

  // ⏱️ Tiempo agotado
  lcd.clear();
  lcd.print("Tiempo!");
  delay(800);
  return false;
}

bool retoDistancias(int tiempoReto) {
  int objetivo = random(5, 51);  // 1 a 50 cm

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Ponte a:");
  lcd.setCursor(0, 1);
  lcd.print(objetivo);
  lcd.print(" cm");

  delay(2000);

  unsigned long tInicio = millis();

  while (millis() - tInicio <= tiempoReto) {
    mostrarCuentaAtras(tInicio, tiempoReto);
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);

    long duracion = pulseIn(echoPin, HIGH, 30000);  // timeout

    int distancia = duracion * 0.034 / 2;

    Serial.print("Distancia: ");
    Serial.println(distancia);

    // Control IR
    if (IrReceiver.decode()) {
      uint32_t codigo = IrReceiver.decodedIRData.decodedRawData;
      IrReceiver.resume();

      if (codigo == APAGAR) {
        peticionCambio = true;
        return false;
      }
    }

    if (abs(distancia - objetivo) <= 2) {
      lcd.clear();
      lcd.print("Superado!");
      delay(1000);
      return true;
    }

    delay(100);
  }

  lcd.clear();
  lcd.print("Tiempo!");
  delay(800);
  return false;
}
void mostrarCuentaAtras(unsigned long tiempoInicio, unsigned long duracion) {
  unsigned long tiempoTranscurrido = millis() - tiempoInicio;
  int segundosRestantes = (duracion - tiempoTranscurrido) / 1000;

  if (segundosRestantes < 0) segundosRestantes = 0;

  display.showNumberDec(segundosRestantes, true);
}



// --- MELODÍA DE VICTORIA (BANDERA) ---
void playMarioVictory() {

  // Primer arpegio (Do Mayor)
  tone(BUZZER, NOTE_G4, 100);
  delay(120);
  tone(BUZZER, NOTE_C5, 100);
  delay(120);
  tone(BUZZER, NOTE_E5, 100);
  delay(120);
  tone(BUZZER, NOTE_G5, 100);
  delay(120);
  tone(BUZZER, NOTE_C6, 100);
  delay(120);
  tone(BUZZER, NOTE_E6, 100);
  delay(120);
  tone(BUZZER, NOTE_G6, 300);
  delay(350);
  tone(BUZZER, NOTE_E6, 300);
  delay(350);

  // Segundo arpegio (Sol Sostenido Mayor)
  tone(BUZZER, NOTE_GS4, 100);
  delay(120);
  tone(BUZZER, NOTE_C5, 100);
  delay(120);
  tone(BUZZER, NOTE_DS5, 100);
  delay(120);
  tone(BUZZER, NOTE_GS5, 100);
  delay(120);
  tone(BUZZER, NOTE_C6, 100);
  delay(120);
  tone(BUZZER, NOTE_DS6, 100);
  delay(120);
  tone(BUZZER, NOTE_GS6, 300);
  delay(350);
  tone(BUZZER, NOTE_DS6, 300);
  delay(350);

  // Tercer arpegio (Si Bemol Mayor)
  tone(BUZZER, NOTE_AS4, 100);
  delay(120);
  tone(BUZZER, NOTE_D5, 100);
  delay(120);
  tone(BUZZER, NOTE_F5, 100);
  delay(120);
  tone(BUZZER, NOTE_AS5, 100);
  delay(120);
  tone(BUZZER, NOTE_D6, 100);
  delay(120);
  tone(BUZZER, NOTE_F6, 100);
  delay(120);
  tone(BUZZER, NOTE_AS6, 300);
  delay(350);

  // Final triunfal
  tone(BUZZER, NOTE_AS6, 100);
  delay(120);
  tone(BUZZER, NOTE_AS6, 100);
  delay(120);
  tone(BUZZER, NOTE_AS6, 100);
  delay(120);
  tone(BUZZER, NOTE_C7, 600);
  delay(800);

  noTone(BUZZER);

}

// --- MELODÍA DE DERROTA (GAME OVER / LOSE LIFE) ---
void playMarioDefeat() {
  Serial.print("llegaaa");
  
  // Notas rápidas descendentes clásicas
  tone(BUZZER, 1047, 100);
  delay(150);  // C6
  tone(BUZZER, 784, 100);
  delay(150);  // G5
  tone(BUZZER, 659, 100);
  delay(150);  // E5

  tone(BUZZER, 880, 150);
  delay(200);  // A5
  tone(BUZZER, 988, 150);
  delay(200);  // B5
  tone(BUZZER, 880, 150);
  delay(200);  // A5

  tone(BUZZER, 831, 150);
  delay(200);  // G#5
  tone(BUZZER, 932, 150);
  delay(200);  // A#5
  tone(BUZZER, 831, 150);
  delay(200);  // G#5

  tone(BUZZER, 784, 300);
  delay(400);  // G5
  noTone(BUZZER);

}

Explicación del código

El código implementa la lógica completa del juego Turbo Reflex utilizando Arduino. Se apoya en varias librerías para gestionar los distintos componentes: la pantalla LCD, el receptor infrarrojo (mando) y el display de 7 segmentos.

La estructura principal del programa se basa en una máquina de estados, con tres modos definidos: apagado, iniciando y jugando. Esto permite organizar el flujo del sistema de forma clara. En estado apagado, el dispositivo no interactúa; en iniciando, el jugador selecciona modo y dificultad mediante el mando; y en jugando, se ejecutan los retos.

El mando infrarrojo tiene un papel clave, ya que permite tanto controlar los menús como apagar el sistema en cualquier momento. Cuando se apaga y se vuelve a encender, el juego se reinicia completamente, volviendo al estado inicial.

El núcleo del juego está en la función iniciarJuego(), donde se gestionan las partidas: se establecen las vidas y el tiempo según la dificultad, y se ejecutan retos aleatorios hasta ganar (superando 15 retos) o perder (quedarse sin vidas).

Los retos están implementados en funciones independientes, cada una usando un sensor distinto:

  • Inclinación (tilt ball)
  • Mando (botones)
  • Joystick (direcciones)
  • Luz (LDR)
  • Distancia (sensor ultrasónico)

Cada reto debe completarse dentro de un tiempo límite, que se muestra con una cuenta atrás en el display. Además, el sistema comprueba constantemente si el jugador quiere apagar el juego.

Finalmente, el buzzer se utiliza para dar feedback sonoro, reproduciendo una melodía diferente según se gane o se pierda la partida. En conjunto, el código demuestra una correcta integración de sensores, control en tiempo real y organización modular mediante funciones y estados.

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *