Creación de una aplicación de chat en tiempo real con React y Firebase

Índice
  1. Pero primero, ¿qué es Firebase?
  2. Esto es lo que estamos haciendo
  3. configurando
  4. Autenticación de correo electrónico y contraseña
  5. Configurar la aplicación web
  6. Incorporemos Firebase a la aplicación
  7. El siguiente paso es el enrutamiento
  8. Comprobando la autenticación
  9. Registrar usuarios con correo electrónico y contraseña
  10. Autenticarse con una cuenta de Google
  11. Autenticarse con una cuenta de GitHub
  12. Leyendo datos de Firebase
  13. Escribir datos en Firebase
  14. ¡Hora de la demostración!
  15. ¡Disfruta de tu nueva aplicación de chat!

En este artículo, cubriremos conceptos clave para autenticar a un usuario con Firebase en una aplicación de chat en tiempo real. Integraremos proveedores de autenticación de terceros (por ejemplo, Google, Twitter y GitHub) y, una vez que los usuarios hayan iniciado sesión, aprenderemos cómo almacenar datos de chat de usuarios en Firebase Realtime Database, donde podemos sincronizar datos con una nube NoSQL. base de datos.

La aplicación cliente se construirá en React, ya que es uno de los marcos de JavaScript más populares que existen, pero los conceptos también se pueden aplicar a otros marcos.

Pero primero, ¿qué es Firebase?

Firebase es la plataforma móvil de Google para desarrollar aplicaciones rápidamente. Firebase proporciona un conjunto de herramientas para autenticar aplicaciones, crear aplicaciones cliente reactivadas, realizar análisis de informes y una serie de otros recursos útiles para administrar aplicaciones en general. También proporciona administración de back-end para web, iOS, Android y Unity, una plataforma de desarrollo 3D.

Firebase incluye listas de funciones para usar que ayudan a los desarrolladores como nosotros a concentrarnos en crear aplicaciones mientras manejamos toda la lógica del lado del servidor. Cosas como:

  • Autenticación : esto incluye soporte para autenticación de correo electrónico y contraseña, así como capacidades de inicio de sesión única (a través de Facebook, Twitter y Google).
  • Base de datos en tiempo real : Se trata de una base de datos “NoSQL” que se actualiza en tiempo real.
  • Funciones en la nube : ejecutan una lógica adicional del lado del servidor.
  • Alojamiento estático : esta es una forma de servir recursos prediseñados en lugar de renderizarlos en tiempo de ejecución.
  • Almacenamiento en la nube: esto nos brinda un lugar para almacenar activos multimedia.

Firebase ofrece un generoso nivel gratuito que incluye autenticación y acceso a su base de datos en tiempo real. Los proveedores de autenticación que cubriremos, correo electrónico y contraseña (Google y GitHub), también son gratuitos en ese aspecto. La base de datos en tiempo real permite hasta 100 conexiones simultáneas y 1 gigabyte de almacenamiento por mes. Puede encontrar una tabla completa de precios en el sitio web de Firebase.

Esto es lo que estamos haciendo

Vamos a crear una aplicación llamada Chatty. Permitirá que solo los usuarios autenticados envíen y lean mensajes y los usuarios podrán registrarse proporcionando su correo electrónico y creando una contraseña, o autenticándose a través de una cuenta de Google o GitHub. Consulte el código fuente si desea consultarlo o eche un vistazo mientras comenzamos.

Terminaremos con algo como esto:

configurando

Necesitará una cuenta de Google para usar Firebase, así que obtendrá una si aún no la tiene. Y una vez que lo hagas, podremos patear oficialmente los neumáticos de esta cosa.

En primer lugar, dirígete a Firebase Console y haz clic en la opción “Agregar proyecto” .

A continuación, ingresamos un nombre para el proyecto. Voy con Chatty.

Puede optar por agregar análisis a su proyecto, pero no es obligatorio. De cualquier manera, haga clic en continuar para continuar y Firebase tardará unos segundos en delegar recursos para el proyecto.

Una vez que gira, nos lleva al panel de Firebase. Pero, antes de que podamos comenzar a usar Firebase en nuestra aplicación web, debemos registrar los detalles de configuración de nuestro proyecto. Entonces, haga clic en el ícono web en el tablero.

Luego, ingrese un nombre para la aplicación y haga clic en Registrar aplicación .

A continuación, copiaremos y almacenaremos los detalles de configuración en la siguiente pantalla en un lugar seguro. Eso será útil en el siguiente paso.

Nuevamente, autenticaremos a los usuarios mediante correo electrónico y contraseña, con opciones adicionales para el inicio de sesión única con una cuenta de Google o GitHub. Necesitamos habilitarlos desde la pestaña Autenticación en el panel, pero los revisaremos uno por uno.

Autenticación de correo electrónico y contraseña

Hay una pestaña Método de inicio de sesión en el panel de Firebase. Haga clic en la opción Correo electrónico/Contraseña y habilítela.

¡Ahora podemos usarlo en nuestra aplicación!

Configurar la aplicación web

Para nuestra aplicación web, usaremos React, pero la mayoría de los conceptos se pueden aplicar en cualquier otro marco. Necesitaremos Node.js para configurar React, así que descárgalo e instálalo si aún no lo has hecho.

Usaremos create-react-app para iniciar un nuevo proyecto de React. Esto descarga e instala los paquetes necesarios para una aplicación React. En la terminal, cddonde desea que vaya nuestro proyecto Chatty y ejecute esto para inicializarlo:

npx create-react-app chatty

Este comando realiza la configuración inicial de nuestra aplicación de reacción e instala las dependencias en package.json. También instalaremos algunos paquetes adicionales. Entonces, entremos cden el proyecto en sí y agregamos paquetes para React Router y Firebase.

cd chattyyarn add react-router-dom firebase

Ya sabemos por qué necesitamos Firebase, pero ¿por qué React Router? Nuestra aplicación de chat tendrá un par de vistas. Podemos usar React Router para manejar la navegación entre páginas.

Una vez hecho esto, podemos iniciar oficialmente la aplicación:

yarn start

Esto inicia un servidor de desarrollo y abre una URL en su navegador predeterminado. Si todo se instaló correctamente, deberías ver una pantalla como esta:

Si observa la estructura de carpetas, verá algo similar a esto:

Para nuestra aplicación de chat, esta es la estructura de carpetas que usaremos:

  • /components: contiene widgets reutilizables utilizados en diferentes páginas
  • /helpers: un conjunto de funciones reutilizables
  • /pages: las vistas de la aplicación
  • /services: servicios de terceros que utilizamos (por ejemplo, Firebase)
  • App.js: el componente raíz

Cualquier otra cosa en la carpeta es innecesaria para este proyecto y se puede eliminar de forma segura. Desde aquí, agreguemos algo de código src/services/firebase.jspara que la aplicación pueda comunicarse con Firebase.

import firebase from 'firebase';

Incorporemos Firebase a la aplicación

Importaremos e inicializaremos Firebase usando los detalles de configuración que copiamos anteriormente al registrar la aplicación en el panel de Firebase. Luego, exportaremos los módulos de autenticación y base de datos.

const config = {  apiKey: "ADD-YOUR-DETAILS-HERE",  authDomain: "ADD-YOUR-DETAILS-HERE",  databaseURL: "ADD-YOUR-DETAILS-HERE"};firebase.initializeApp(config);export const auth = firebase.auth;export const db = firebase.database();

Importemos nuestras dependencias en src/App.js:

import React, { Component } from 'react';import {  Route,  BrowserRouter as Router,  Switch,  Redirect,} from "react-router-dom";import Home from './pages/Home';import Chat from './pages/Chat';import Signup from './pages/Signup';import Login from './pages/Login';import { auth } from './services/firebase';

Estas son importaciones de ES6. Específicamente, estamos importando React y otros paquetes necesarios para desarrollar la aplicación. También estamos importando todas las páginas de nuestra aplicación que configuraremos más tarde a nuestro enrutador.

El siguiente paso es el enrutamiento

Nuestra aplicación tiene rutas públicas (accesibles sin autenticación) y una ruta privada (accesible solo con autenticación). Debido a que React no proporciona una forma de verificar el estado autenticado, crearemos componentes de orden superior (HOC) para ambos tipos de rutas.

Nuestros HOC:

  • envolver un Route,
  • pasar accesorios del enrutador al Route,
  • renderizar el componente dependiendo del estado autenticado, y
  • redirigir al usuario a una ruta específica si no se cumple la condición

Escribamos el código para nuestro PrivateRouteHOC.

function PrivateRoute({ component: Component, authenticated, ...rest }) {  return (    Route      {...rest}      render={(props) = authenticated === true        ? Component {...props} /        : Redirect to={{ pathname: '/login', state: { from: props.location } }} /}    /  )}

Recibe tres accesorios: el componente que se representará si la condición es verdadera, el authenticatedestado y el operador de extensión de ES6 para obtener los parámetros restantes pasados ​​desde el enrutador. Comprueba si authenticatedes verdadero y muestra el componente pasado; de lo contrario, redirige a/iniciar sesión.

function PublicRoute({ component: Component, authenticated, ...rest }) {  return (    Route      {...rest}      render={(props) = authenticated === false        ? Component {...props} /        : Redirect to='/chat' /}    /  )}

Es PublicRoutemás o menos lo mismo. Representa nuestras rutas públicas y redirige a la /chatruta si el estado autenticado se vuelve verdadero. Podemos usar los HOC en nuestro método de representación:

render() {  return this.state.loading === true ? h2Loading.../h2 : (    Router      Switch        Route exact path="/" component={Home}/Route        PrivateRoute path="/chat" authenticated={this.state.authenticated} component={Chat}/PrivateRoute        PublicRoute path="/signup" authenticated={this.state.authenticated} component={Signup}/PublicRoute        PublicRoute path="/login" authenticated={this.state.authenticated} component={Login}/PublicRoute      /Switch    /Router  );}

Comprobando la autenticación

Sería bueno mostrar un indicador de carga mientras verificamos si el usuario está autenticado. Una vez completada la verificación, representamos la ruta adecuada que coincida con la URL. Contamos con tres rutas públicas — Home, Loginy— Signupy una privada llamada Chat.

Escribamos la lógica para verificar si el usuario está realmente autenticado.

class App extends Component {  constructor() {    super();    this.state = {      authenticated: false,      loading: true,    };  }}export default App;

Aquí estamos configurando el estado inicial de la aplicación. Luego, usamos el componentDidMountenlace del ciclo de vida para verificar si el usuario está autenticado. Entonces, agreguemos esto después del constructor:

componentDidMount() {  auth().onAuthStateChanged((user) = {    if (user) {      this.setState({        authenticated: true,        loading: false,      });    } else {      this.setState({        authenticated: false,        loading: false,      });    }  })}

Firebase proporciona un método intuitivo llamado onAuthStateChangedque se activa cuando cambia el estado autenticado. Usamos esto para actualizar nuestro estado inicial. useres nulo si el usuario no está autenticado. Si useres cierto, actualizamos autenticados en true; de lo contrario lo configuramos en false. También configuramos la carga en falsecualquier dirección.

Registrar usuarios con correo electrónico y contraseña

Los usuarios podrán registrarse en Chatty mediante correo electrónico y contraseña. La helperscarpeta contiene un conjunto de métodos que usaremos para manejar cierta lógica de autenticación. Dentro de esta carpeta, creemos un nuevo archivo llamado auth.jsy agreguemos esto:

import { auth } from "../services/firebase";

Importamos el módulo de autenticación del servicio que creamos anteriormente.

export function signup(email, password) {  return auth().createUserWithEmailAndPassword(email, password);}
export function signin(email, password) {  return auth().signInWithEmailAndPassword(email, password);}

Tenemos dos métodos aquí: signupy signin:

  • signupcreará un nuevo usuario utilizando su correo electrónico y contraseña.
  • signininiciará sesión como usuario existente creado con correo electrónico y contraseña.

Creemos nuestra Signuppágina creando un nuevo archivo Signup.jsen la carpeta de páginas. Este es el marcado para la interfaz de usuario:

import React, { Component } from 'react';import { Link } from 'react-router-dom';import { signup } from '../helpers/auth';
export default class SignUp extends Component {
  render() {    return (      div        form onSubmit={this.handleSubmit}          h1            Sign Up to          Link to="/"Chatty/Link          /h1          pFill in the form below to create an account./p          div            input placeholder="Email" name="email" type="email" onChange={this.handleChange} value={this.state.email}/input          /div          div            input placeholder="Password" name="password" onChange={this.handleChange} value={this.state.password} type="password"/input          /div          div            {this.state.error ? p{this.state.error}/p : null}            button type="submit"Sign up/button          /div          hr/hr          pAlready have an account? Link to="/login"Login/Link/p        /form      /div    )  }}

Los campos de formulario y de entrada están vinculados a un método que aún no hemos creado, así que solucionémoslo. Justo antes del render()método, agregaremos lo siguiente:

constructor(props) {  super(props);  this.state = {    error: null,    email: '',    password: '',  };  this.handleChange = this.handleChange.bind(this);  this.handleSubmit = this.handleSubmit.bind(this);}

Estamos configurando el estado inicial de la página. También vinculamos los métodos handleChangey handleSubmital alcance de este componente.

handleChange(event) {  this.setState({    [event.target.name]: event.target.value  });}

A continuación, agregaremos el handleChangemétodo al que están vinculados nuestros campos de entrada. El método utiliza propiedades calculadas para determinar dinámicamente la clave y establecer la variable de estado correspondiente.

async handleSubmit(event) {  event.preventDefault();  this.setState({ error: '' });  try {    await signup(this.state.email, this.state.password);  } catch (error) {    this.setState({ error: error.message });  }}

Para handleSubmit, estamos evitando el comportamiento predeterminado para los envíos de formularios (que simplemente recarga el navegador, entre otras cosas). También estamos limpiando la variable de estado de error y luego usamos el método signup() importado de helpers/auth para pasar el correo electrónico y la contraseña ingresados ​​por el usuario.

Si el registro se realiza correctamente, los usuarios son redirigidos a la ruta /Chats. Esto es posible con la combinación de onAuthStateChanged y los HOC que creamos anteriormente. Si el registro falla, configuramos la variable de error que muestra un mensaje a los usuarios.

Autenticar usuarios con correo electrónico y contraseña

La página de inicio de sesión es idéntica a la página de registro. La única diferencia es que usaremos el signinmétodo de los ayudantes que creamos anteriormente. Dicho esto, creemos otro archivo nuevo en el directorio de páginas, esta vez llamado Login.js, con este código:

import React, { Component } from "react";import { Link } from "react-router-dom";import { signin, signInWithGoogle, signInWithGitHub } from "../helpers/auth";
export default class Login extends Component {  constructor(props) {    super(props);    this.state = {      error: null,      email: "",      password: ""    };    this.handleChange = this.handleChange.bind(this);    this.handleSubmit = this.handleSubmit.bind(this);  }
  handleChange(event) {    this.setState({      [event.target.name]: event.target.value    });  }
  async handleSubmit(event) {    event.preventDefault();    this.setState({ error: "" });    try {      await signin(this.state.email, this.state.password);    } catch (error) {      this.setState({ error: error.message });    }  }
  render() {    return (      div        form          autoComplete="off"          onSubmit={this.handleSubmit}                  h1            Login to            Link to="/"              Chatty            /Link          /h1          p            Fill in the form below to login to your account.          /p          div            input              placeholder="Email"              name="email"              type="email"              onChange={this.handleChange}              value={this.state.email}            /          /div          div            input              placeholder="Password"              name="password"              onChange={this.handleChange}              value={this.state.password}              type="password"            /          /div          div            {this.state.error ? (              p{this.state.error}/p            ) : null}            button type="submit"Login/button          /div          hr /          p            Don't have an account? Link to="/signup"Sign up/Link          /p        /form      /div    );  }}

De nuevo, muy similar a antes. Cuando el usuario inicia sesión correctamente, se le redirige a /chat.

Autenticarse con una cuenta de Google

Firebase nos permite autenticar a los usuarios con una cuenta de Google válida. Tenemos que habilitarlo en el panel de Firebase tal como lo hicimos para el correo electrónico y la contraseña.

En esa misma página, también debemos desplazarnos hacia abajo para agregar un dominio a la lista de dominios autorizados para acceder a la función. De esta forma evitamos el spam de cualquier dominio que no esté en la lista blanca. Para fines de desarrollo, nuestro dominio es localhost, así que usaremos ese por ahora.

Podemos volver a nuestro editor ahora. Agregaremos un nuevo método para helpers/auth.jsmanejar la autenticación de Google.

export function signInWithGoogle() {  const provider = new auth.GoogleAuthProvider();  return auth().signInWithPopup(provider);}

Aquí, estamos creando una instancia de GoogleAuthProvider. Luego, llamamos signInWithPopupcon el proveedor como parámetro. Cuando se llama a este método, aparecerá una ventana emergente que guiará al usuario a través del flujo de inicio de sesión de Google antes de redirigirlo nuevamente a la aplicación. Probablemente lo hayas experimentado tú mismo en algún momento.

Usémoslo en nuestra página de registro importando el método:

import { signin, signInWithGoogle } from "../helpers/auth";

Luego, agreguemos un botón para activar el método, justo debajo del botón Registrarse :

pOr/pbutton onClick={this.googleSignIn} type="button"  Sign up with Google/button

A continuación, agregaremos el onClickcontrolador:

async googleSignIn() {  try {    await signInWithGoogle();  } catch (error) {    this.setState({ error: error.message });  }}

Ah, y debemos recordar vincular el controlador al componente:

constructor() {  // ...  this.githubSignIn = this.githubSignIn.bind(this);}

¡Eso es todo lo que necesitamos! Cuando se hace clic en el botón, lleva a los usuarios a través del flujo de inicio de sesión de Google y, si tiene éxito, la aplicación redirige al usuario a la ruta de chat.

Autenticarse con una cuenta de GitHub

Vamos a hacer lo mismo con GitHub. También puede brindarles a las personas más de una opción de cuenta.

Sigamos los pasos. Primero, habilitaremos el inicio de sesión de GitHub en el panel de Firebase, como lo hicimos con el correo electrónico y Google.

Notará que tanto el campo ID del cliente como el campo secreto del cliente están vacíos, pero tenemos nuestra URL de devolución de llamada de autorización en la parte inferior. Cópielo, porque lo usaremos cuando hagamos lo siguiente, que es registrar la aplicación en GitHub.

Una vez hecho esto, obtendremos un ID de cliente y un secreto que ahora podemos agregar a Firebase console.

Volvamos al editor y agreguemos un nuevo método a helpers/auth.js:

export function signInWithGitHub() {  const provider = new auth.GithubAuthProvider();  return auth().signInWithPopup(provider);}

Es similar a la interfaz de inicio de sesión de Google, pero esta vez estamos creando un archivo GithubAuthProvider. Luego, llamaremos signInWithPopupcon el proveedor.

En pages/Signup.js, actualizamos nuestras importaciones para incluir el signInWithGitHubmétodo:

import { signup, signInWithGoogle, signInWithGitHub } from "../helpers/auth";

Agregamos un botón para registrarse en GitHub:

button type="button" onClick={this.githubSignIn}  Sign up with GitHub/button

Luego agregamos un controlador de clic para el botón que activa el flujo de registro de GitHub:

async githubSignIn() {  try {    await signInWithGitHub();  } catch (error) {    this.setState({ error: error.message });  }}

Recordemos nuevamente vincular el controlador al componente:

constructor() {  // ...  this.githubSignIn = this.githubSignIn.bind(this);}

Ahora obtendremos el mismo flujo de inicio de sesión y autenticación que tenemos con Google, pero con GitHub.

Leyendo datos de Firebase

Firebase tiene dos tipos de bases de datos: un producto al que llaman Realtime Database y otro llamado Cloud Firestore. Ambas bases de datos son bases de datos similares a NoSQL, lo que significa que la base de datos está estructurada como pares clave-valor. Para este tutorial, usaremos la base de datos en tiempo real.

Esta es la estructura que usaremos para nuestra aplicación. Tenemos un nodo raíz chatscon nodos secundarios. Cada niño tiene un contenido, una marca de tiempo y una identificación de usuario. Una de las pestañas que notará es Reglas, que es cómo configuramos los permisos sobre el contenido de la base de datos.

Las reglas de la base de datos de Firebase también se definen como pares clave-valor. Aquí, estableceremos nuestras reglas para permitir que solo los usuarios autenticados lean y escriban en el nodo de chat. Hay muchas más reglas de base de fuego. vale la pena echarle un vistazo.

Escribamos código para leer de la base de datos. Primero, cree un nuevo archivo llamado Chat.jsen la carpeta de páginas y agregue este código para importar React, autenticación de Firebase y Realtime Database:

import React, { Component } from "react";import { auth } from "../services/firebase";import { db } from "../services/firebase"

A continuación, definamos el estado inicial de la aplicación:

export default class Chat extends Component {  constructor(props) {    super(props);    this.state = {      user: auth().currentUser,      chats: [],      content: '',      readError: null,      writeError: null    };  }  async componentDidMount() {    this.setState({ readError: null });    try {      db.ref("chats").on("value", snapshot = {        let chats = [];        snapshot.forEach((snap) = {          chats.push(snap.val());        });        this.setState({ chats });      });    } catch (error) {      this.setState({ readError: error.message });    }  }}

La verdadera lógica principal tiene lugar en componentDidMount. db.ref("chats")una referencia a la ruta de los chats en la base de datos. Escuchamos el evento de valor que se activa cada vez que se agrega un nuevo valor al nodo de chats. Lo que se devuelve de la base de datos es un objeto similar a una matriz que recorremos e insertamos cada objeto en una matriz. Luego, configuramos la variable de estado de los chats en nuestra matriz resultante. Si hay un error, configuramos la readErrorvariable de estado en el mensaje de error.

Una cosa a tener en cuenta aquí es que se crea una conexión entre el cliente y nuestra base de datos de Firebase porque usamos el .on()método. Esto significa que cada vez que se agrega un nuevo valor a la base de datos, la aplicación cliente se actualiza en tiempo real, lo que significa que los usuarios pueden ver nuevos chats sin necesidad de actualizar la página. ¡Bien!.

Después componentDidMount, podemos renderizar nuestros chats así:

render() {  return (    div      div className="chats"        {this.state.chats.map(chat = {          return p key={chat.timestamp}{chat.content}/p        })}      /div      div        Login in as: strong{this.state.user.email}/strong      /div    /div  );}

Esto representa la variedad de chats. Representamos el correo electrónico del usuario actualmente conectado.

Escribir datos en Firebase

Por el momento, los usuarios sólo pueden leer de la base de datos pero no pueden enviar mensajes. Lo que necesitamos es un formulario con un campo de entrada que acepte un mensaje y un botón para enviar el mensaje al chat.

Entonces, modifiquemos el marcado así:

return (    div      div className="chats"        {this.state.chats.map(chat = {          return p key={chat.timestamp}{chat.content}/p        })}      /div      {# message form #}      form onSubmit={this.handleSubmit}        input onChange={this.handleChange} value={this.state.content}/input        {this.state.error ? p{this.state.writeError}/p : null}        button type="submit"Send/button      /form      div        Login in as: strong{this.state.user.email}/strong      /div    /div  );}

Hemos agregado un formulario con un campo de entrada y un botón. El valor del campo de entrada está vinculado a nuestra variable de estado contenty lo llamamos handleChangecuando cambia su valor.

handleChange(event) {  this.setState({    content: event.target.value  });}

handleChangeobtiene el valor del campo de entrada y lo establece en nuestra variable de estado. Para enviar el formulario, llamamos a handleSubmit:

async handleSubmit(event) {  event.preventDefault();  this.setState({ writeError: null });  try {    await db.ref("chats").push({      content: this.state.content,      timestamp: Date.now(),      uid: this.state.user.uid    });    this.setState({ content: '' });  } catch (error) {    this.setState({ writeError: error.message });  }}

Configuramos los errores anteriores en null. Creamos una referencia al chatsnodo en la base de datos y la usamos push()para crear una clave única y enviarle el objeto.

Como siempre, tenemos que vincular nuestros métodos al componente:

constructor(props) {  // ...  this.handleChange = this.handleChange.bind(this);  this.handleSubmit = this.handleSubmit.bind(this);}

¡Ahora un usuario puede agregar nuevos mensajes a los chats y verlos en tiempo real! ¿Cuan genial es eso?

¡Hora de la demostración!

¡Disfruta de tu nueva aplicación de chat!

¡Felicidades! Acaba de crear una herramienta de chat que autentica a los usuarios con correo electrónico y contraseña, además de opciones para autenticarse a través de una cuenta de Google o GitHub.

Espero que esto le dé una buena idea de lo útil que puede ser Firebase para poner en marcha la autenticación en una aplicación. Trabajamos en una aplicación de chat, pero la verdadera joya son los métodos de registro e inicio de sesión que creamos para acceder a ella. Eso es algo útil para muchas aplicaciones.

¿Preguntas? ¿Pensamientos? ¿Comentario? ¡Házmelo saber en los comentarios!

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

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

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion