Utilizando Firebase Database con React Native

Introducción

Hay muchas maneras de manejar los datos en React Native echando mano de el state que funge como un almacenamiento a la mano para manipular nuestros datos, estas maneras pueden ir cambiando en complejidad y tamaño en su "boilerplate", algunas son mucho más populares que otras por su curva de aprendizaje más corta y algunas más son muy usadas por startups y por ende demandadas. Algunas de estas son:

Redux : Es la opción más comun y es muy util para manejar los datos de apps muy amplias. Provee de un "store" que mantiene el state de la app centralizado, aprende más de Redux aquí


GraphQL: Es un lenguaje que permite actualizar datos desde un servidor-compilador GraphQL, es usado para una comunicación cliente servidor que a diferencia de las RESTApis requiere de menores peticiones, tiene un mejor desempeño y menos endpoints aprende más de GraphQL aquí


Realm: Esta librería se usa como una base de datos local escrita desde cero en C para ser compatible con iOS y Android, soporta más de 10,000 registros, con Realm las búsquedas se vuelven increíblemente rápidas y el uso offline se vuelve trivial, aprende más de Realm aquí


Firebase: Es un servicio en la nube para almacenar y sincronizar datos entre diferentes plataformas, Firebase es una excelente opción cuando no tienes un backend o un servidor con un API para trabajar. Esto permite crear aplicaciones mobiles sin tener ninguna infraestructura. Firebase ofrece una sincronización de datos en tiempo real impresionante entre miles de clientes. El servicio también provee un web/dashboard para ver/editar/eliminar datos, cuentas de usuario y autenticación, analytics y crash reporting.


En este post vamos a crear una aplicación llamada ReactFirebase que utilizará una base de datos Firebase para guardar una lista de compras.
Si aún no tienes instalado react-native cli puedes checar nuestro post de cómo comenzar con React Native en solo 3 pasos aquí

1.- Creando el App

Nos ubicamos en la carpeta que contendrá nuestro proyecto y una vez que tenemos instalado en React Native CLI ejecutamos los siguientes comandos:

react-native init ReactFirebase  

La mayúscula es importante, recordemos que trabajaremos con clases y es una convención

React Native por default crea un conjunto de carpetas con las que trabaja y puede compilar los archivos necesarios para ejecutar nuestra app en iOs y Android de manera independiente, pero esto crea un doble trabajo ya que tenemos un archivo index.ios.js y un archivo index.android.js lo que implicaría tener que modificar 2 archivos y terminaríamos creando 2 apps diferentes, por suerte si sabemos usar javascript podemos echar mano de la importación de archivos para resolver esto y quedarnos solo con un archivo principal para las 2 paltaformas.

En este post usaré sintaxis ES6 (EcmaScript2015) ya qué React Native CLI ya creo el archivo con todo lo necesario para transpilar y compilar nuestro código a los lenguajes necesarios y podemos aprovechar para programar de forma más moderna, cómoda y ¡orientado a objetos! =D

  • así que comencemos creando nuestro archivo principal.
    en la carpeta de nuestro proyecto FixterGeek vamos a crear la carpeta components y dentro crearemos el archivo App.js la mayúscula es importante ya que contendrá nuestra primer clase.

    mkdir components  
    touch App.js  
    

    esto puedes hacerlo también directamente en tu editor de código ;)

  • Ahora creamos nuestro Hola mundo super básico, dentro de nuestro archivo App.js, para esto puedes usar tu editor de texto favorito, yo usaré Brackets el cual es gratuito, puedes usar sublimeText o a mi parecer el mejor para React que es Webstorm (solo que es de costo U_U)

    import React, { Component } from 'react';  
    import { View, Text, StyleSheet } from 'react-native';
    
    
    class App extends Component {  
        render(){
            return(
                <View style={styles.container}>
                    <Text >FixterGeek</Text>
                </View>
            );
        }
    }
    
    const styles = StyleSheet.create({  
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
        }
    });
    
    export default App;  
    

    Podemos notar varias cosas en este pequeño componente:

    Estamos importando componentes que ya tiene react-native, que funcionan para ambas plataformas (iOS y Android) desde 'react-native'


    dentro de la función return de render, estamos devolviendo nuestro código Jsx con una simple View y un Text dentro (View es el equivalente genérico de un div usando react para web)


    Estamos agregando estilos con el formato más utilizado de React Native el cual es en forma de objeto, no hay gran diferencia con el css tradicional solo hay que recordar que NO es css y que al ser un objeto de JS se escribe en camelCase puedes saber más aquí


    Estamos utilizando FlexBox para acomodar nuestors elementos, es bueno que aprendas Flex ya que es ampliamente utilizado sobre React Native para resolver posiciones (grid)


    Al final del archivo exportamos como default la clase App (esto es importante)

  • Continuamos editando nuestros archivos index.ios.js e index.android.js vamos a sustituir la clase que tienen dentro por la importación de la nueva clase App que viven en el archivo App.js asi:

    index.android.js

    import { AppRegistry } from 'react-native';  
    import ReactFirebase from './components/App';
    
    AppRegistry.registerComponent('ReactFirebase', () => ReactFirebase);
    

    Notese que estamos importando la clase App que exportamos como default en App.js pero estamos nombrandole igual que nuestro proyecto creado con react-native init ReactFirebase, por eso le llamamos ReactFirebase


    De esta manera el método AppRegistry sigue registrando correctamente nuestra clase con el nombre de nuestro proyecto.


  • Hacemos lo mismo con index.ios.js (puedes omitir esto si no estás en Mac)

    index.ios.js

    import { AppRegistry } from 'react-native';  
    import ReactFirebase from './components/App';
    
    AppRegistry.registerComponent('ReactFirebase', () => ReactFirebase);
    

  • Podemos probar!! =D vamos a correr nuestra app, nuevamente y si todo esta correcto, podremos ver nuestro unico componente en ambas plataformas, corremos:

    react-native run-android  
    

    y si estamos en Mac también podemos correr:

    react-native run-ios  
    

    y veríamos algo como esto:

    ¡Genial! lo lograste! ahora nuestras 2 apps nativas corren el mismo componente =D y solo tenemos que editar App.js

    2.- Platform Targeting con NativeBase

    Instalamos NativeBase

    npm install native-base --save  
    

    posteriormente linkeamos todos las dependencias que esta librería utiliza, cómo los iconos. con un comando muy sencillo:

    react-native link  
    

    Y estamos listos para utilizar algunos de los componentes de esta increible librería

  • Vamos nuevamente a nuestra clase App en nuestro archivo App.js que está dentro de components/

    import React, { Component } from 'react';  
    import { View, Text, StyleSheet } from 'react-native';  
    import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon } from 'native-base';
    
    class App extends Component {  
        render(){
            return(
                <Container>
                    <Header>
                        <Left>
                            <Button transparent>
                                <Icon name='menu' />
                            </Button>
                        </Left>
                        <Body>
                            <Title>Header</Title>
                        </Body>
                        <Right />
                    </Header>
                    <Content>
                        <View style={styles.container}>
                            <Text>FixterGeek</Text>
                        </View>
                    </Content>
                    <Footer>
                        <FooterTab>
                            <Button full>
                                <Text>Footer</Text>
                            </Button>
                        </FooterTab>
                    </Footer>
                </Container>
            );
        }
    }
    
    const styles = StyleSheet.create({  
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
        }
    });
    
    export default App;
    

    De este código notese:

    Hemos importado una lista de componentes que vienen desde la librería NativeBase


    Los hemos usado para darle forma a nuestra app


    nuestro View y Text que teníamos originalmente quedarón dentro del componente Content

  • Es hora de probar nuestra pequeña modificación.Para esto es necesario volver a correr el comando:

    react-native run-android  
    ó
    react-native run-ios  
    

    Esto porque debe cargar la nueva librería de componentes, posteriormente bastaría con ir al emulador que está corriendo y hacer cmd + r en iOS y presionar doble vez R en Android para ver los cambios reflejarse.

    Como puedes ver, tenemos la misma app renderizada de forma diferente por cada sistema operativo, es momento de crear nuestro proyecto en Firebase

    3.- Instalando Firebase en el App

  • Para instalar Firebase en nuestra App basta con correr este comando en nuestra carpeta ReactFirebase:

    npm install --save firebase  
    

  • Abrimos nuestro archivo App.js que está dentro de components/ y agregamos esta linea hasta arriba del archivo:

    import * as firebase from 'firebase';  
    

  • Despues debajo de nuestro componente agrega la inizialicación de Firebase:

    // Initialize Firebase
    const firebaseConfig = {  
      apiKey: "<your-api-key>",
      authDomain: "<your-auth-domain>",
      databaseURL: "<your-database-url>",
      storageBucket: "<your-storage-bucket>",,
    };
    const firebaseApp = firebase.initializeApp(firebaseConfig);  
    

  • Todo nuestro archivo App.js quedaría así:

    import * as firebase from 'firebase';  
    import React, { Component } from 'react';  
    import { View, Text, StyleSheet } from 'react-native';  
    import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon } from 'native-base';
    
    class App extends Component {  
        render(){
            return(
                <Container>
                    <Header>
                        <Left>
                            <Button transparent>
                                <Icon name='menu' />
                            </Button>
                        </Left>
                        <Body>
                            <Title>Header</Title>
                        </Body>
                        <Right />
                    </Header>
                    <Content>
                        <View style={styles.container}>
                            <Text>FixterGeek</Text>
                        </View>
                    </Content>
                    <Footer>
                        <FooterTab>
                            <Button full>
                                <Text>Footer</Text>
                            </Button>
                        </FooterTab>
                    </Footer>
                </Container>
            );
        }
    }
    
    const styles = StyleSheet.create({  
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
        }
    });
    
    export default App;
    
    // Initialize Firebase
    const firebaseConfig = {  
      apiKey: "<your-api-key>",
      authDomain: "<your-auth-domain>",
      databaseURL: "<your-database-url>",
      storageBucket: "<your-storage-bucket>",,
    };
    const firebaseApp = firebase.initializeApp(firebaseConfig);  
    

    Hay que sustituir los <your-api-key> por llaves reales de tu proyecto, para ello vamos a la consola de Firebase a crear nuestro proyecto.

    4.- Abriendo la consola de Firebase

  • Visitamos console.firebase.google.com recuerda que para crear una sesión en Firebase solo necesitas una cuenta de Google (gmail, docs etc...)

  • Añadimos un proyecto nuevo y lo nombramos ReactFirebase

  • Copiamos nuestras referencias desde el boton "Añade Firebase a tu aplicación web"

  • Y ahora podemos sustituirla en nuestro archivo App.js:

    // Initialize Firebase
    const firebaseConfig = {  
        apiKey: "AIzaSyCfGksHS2BpYH6BXrqznpZWMlAwzrmtttU",
        authDomain: "reactfirebase-b16aa.firebaseapp.com",
        databaseURL: "https://reactfirebase-b16aa.firebaseio.com",
        projectId: "reactfirebase-b16aa",
        storageBucket: "reactfirebase-b16aa.appspot.com",
        messagingSenderId: "113538498979"
    };
    const firebaseApp = firebase.initializeApp(firebaseConfig);  
    

    Listo!! uff!! tenemos nuestro boilerplate listo es hora de crear la lógica de nuestra app.

    5.- Creamos ListComponent.js y RowComponent.js

    No voy a profundizar mucho en teoría sobre React y sus tipos de componentes, eso lo dejaré para otro post, por ahora nuestro archivo App.js será nuestro componente contenedor (en él habitarán todos nuestros métodos y el state) y construiremos un componente dentro de nuestra carpeta components/ llamado ListComponent.js que funcionará como componente de presentación para nuestra UI (user interface) que mostrará nuestra lista de compras.

    y quedará así:
    Listcomponent.js

    import React, { Component } from 'react';  
    import { Container, Content, List, ListItem, Text, Icon, Badge, Left, Body, Right, Switch } from 'native-base';  
    import RowComponent from './RowComponent';
    
    
    class ListComponent extends Component {  
        render() {
            return (
                <Container>
                    <Content>
                        {this.props.lista.map(
                            item => <RowComponent
                                        key={item.id}
                                        item={item}
                                        changeDone={this.props.changeDone}
                                              />
                                            )}
                    </Content>
                </Container>
            );
        }
    }
    
    export default ListComponent;  
    

    Notese que estoy importando un RowComponent el cual se va a encargar de dibujar cada fila de la lista gracias al método map. también pasamos una función changeDone que crearemos en el componente contenedor App.js en un momento.

    Para que esto funcione debemos crear el componente RowComponent de la siguiente manera (estoy implementando una funcionalidad más avanzada para detectar el clic sostenido, pero puedes simplificarlo si quieres).

    RowComponent.js

    import React, { Component } from 'react';  
    import { Container, Content, List, ListItem, Text, Icon, Badge, Left, Body, Right, Switch } from 'native-base';  
    import { TouchableWithoutFeedback, Animated, Alert } from 'react-native';
    
    const ACTION_TIMER = 400;
    
    class RowComponent extends Component {  
        constructor(props){
            super(props);
            this.state = {
                done: false,
                pressAction:  new Animated.Value(0),
                item: null
            };
        }
    
        componentWillMount() {
            this._value = 0;
            this.state.pressAction.addListener((v) => this._value = v.value);
        }
    
    
        changeDone = (item) => {
    //        this.setState({done:!this.state.done});
            console.log(item);
    
        }
        pressIn = () => {
            Animated.timing(this.state.pressAction,{
                duration: ACTION_TIMER,
                toValue: 1
            }).start(this.animationActionComplete);
        }
    
        animationActionComplete =() => {
        const message = '¿Seguro que quieres eliminarlo?';
        if (this._value === 1) {
            Alert.alert(
                'ELIMINAR',
                message
              );
        }
        }
    
    
    
        render() {
            return (
                            <TouchableWithoutFeedback
                                onPressIn={this.pressIn}
                                onPressOut={this.pressOut}
                                >
                                <ListItem icon>
                                    <Left>
                                        <Icon name="pizza" />
                                    </Left>
                                    <Body>
                                      <Text>{this.props.item.name}</Text>
                                    </Body>
    
                                    <Right>
                                        <Switch onValueChange={() => {
                                                this.props.item.done = !this.props.item.done;
                                                this.props.changeDone(this.props.item)
                                            }} value={this.props.item.done} />
                                    </Right>
                                </ListItem>
                            </TouchableWithoutFeedback>
    
                        );
                }
    
    }
    
    export default RowComponent;  
    

    En este componente hay un montón de cosas en las que no voy a profundizar pero, si sigues la misma estructura tendrás la funcionalidad de borrar si mantienes presionada cada fila de la lista al terminar nuestro proyecto.


    Observa que modificamos el estado del item cuando en el componente Switch es cambiado el valor y lo enviamos a la función que hemos estado pasando por los props changeDone la cual crearemos ahora en App.js

    6.- Modificamos App.js

    Es hora de modificar un poco App.js para que la funcionalidad de nuestra app esté lista.

    App.js

    import * as firebase from 'firebase';  
    import React, { Component } from 'react';  
    import { View, Text, StyleSheet } from 'react-native';  
    import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon } from 'native-base';  
    import ListComponent from './ListComponent';
    
    class App extends Component {  
        constructor(){
            super();
    
            this.state = {
                lista: [
                            {
                                id: 1,
                                name: 'pollo',
                                done: false
                            },
                            {
                                id: 2,
                                name: 'sopa',
                                done: false
                            },
                            {
                                id: 3,
                                name: 'ropa',
                                done: false
                            }
                        ]
            }
    
        }
    
        changeDone = (item) => {
            console.log(item);
            this.state.lista = this.state.lista.filter(i => i !== item);
            this.state.lista.push(item);
            this.setState({lista: this.state.lista});
        }
    
        render(){
            return(
                <Container>
                    <Header>
                        <Left>
                            <Button transparent>
                                <Icon name='menu' />
                            </Button>
                        </Left>
                        <Body>
                            <Title>Header</Title>
                        </Body>
                        <Right />
                    </Header>
                    <Content>
                        <View style={styles.container}>
                            <ListComponent
                                lista={this.state.lista}
                                changeDone={this.changeDone}
                                />
                        </View>
                    </Content>
                    <Footer>
                        <FooterTab>
                            <Button full>
                                <Text>Footer</Text>
                            </Button>
                        </FooterTab>
                    </Footer>
                </Container>
            );
        }
    }
    
    const styles = StyleSheet.create({  
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'stretch',
        }
    });
    
    export default App;
    
    // Initialize Firebase
    const firebaseConfig = {  
        apiKey: "AIzaSyCfGksHS2BpYH6BXrqznpZWMlAwzrmtttU",
        authDomain: "reactfirebase-b16aa.firebaseapp.com",
        databaseURL: "https://reactfirebase-b16aa.firebaseio.com",
        projectId: "reactfirebase-b16aa",
        storageBucket: "reactfirebase-b16aa.appspot.com",
        messagingSenderId: "113538498979"
    };
    const firebaseApp = firebase.initializeApp(firebaseConfig);  
    

    Basicamente agregamos un método constructor que inicializa nuestro state con una pequeña lista de objetos, importamos ListComponent y lo usamos dentro del componente Content y le pasamos como props la lista y la función changeDone que obviamente también creamos.


    La función changeDone esta construida con la sintaxis del "arrow function" la cual nos permite de una manera elegante relacionar el contexto de nuestro contenedor con el del componente anidado. En otras palabras; evitamos usar el .bind(this)

    Hasta ahora nuestra app, debería funcionar apagando y prendiendo nuestros 3 items, aún no elimina pero lanza un alert cuando sostenemos el clic sobre un item, si todo sale bien veríamos algo como la imagen siguiente y podríamos proceder a conectar con Firebase.

    7.- Botón agregar y Firebase push

    Vamos a crear el boton y el método que nos permita agregar un nuevo item a la lista y de paso subirlo a firebase, comencemos agregando el botón a nuestro archivo App.js que en realidad solo debemos modificar porque ya existe, recuerdas el componente Footer? solo modificaremos la leyenda dentro de <Text></Text> y agregaremos a onPress la funcion this.agregarItem

    <Footer>  
      <FooterTab>
          <Button 
             full
             onPress={this.agregarItem}
           >
             <Text>Agregar</Text>
                </Button>
      </FooterTab>
    </Footer>  
    

    Posteriormente importaremos los componentes Input de 'native-base', nuestro import quedaría así:

    import { Container, Header, Title, Content, Footer, FooterTab, Button, Left, Right, Body, Icon, Input } from 'native-base';  
    

    Ahora agregaremos un Input dentro <Content></Content>justo antes de nuestra <View></View>que contiene a <ListComponent> así:

                    <Content>
    
                            <Input
                                value={this.state.nuevo}
                                placeholder='Que otra cosa necesitas?'
                                onChangeText={nuevo=>this.setState({nuevo})}
                                />
    
    
                        <View style={styles.container}>
                            <ListComponent
                                lista={this.state.lista}
                                changeDone={this.changeDone}
                                borrar={this.borrar}
                                />
                        </View>
                    </Content>
    

    Observa que agregamos un método onChange para guardar lo que el usuario escriba en el state con el nombre nuevo y seteamos el value a este mismo valor, lo que significa que en nuestro constructor debemos inicializar este valor así:

    constructor(){  
            super();
    
            this.state = {
                nuevo: '',
                lista: [
                            {
                                id: 1,
                                name: 'pollo',
                                done: false
                            },
                            {
                                id: 2,
                                name: 'sopa',
                                done: false
                            },
                            {
                                id: 3,
                                name: 'ropa',
                                done: false
                            }
                        ]
            }
    
        }
    

    Es momento de crear nuestro método agregarItem que es llamado por nuestro botón en el footer, y quedaría así:

        agregarItem = () => {
            let nuevo = this.state.nuevo
            nuevo = {id:nuevo,name:nuevo,done:false};
            firebase.database().ref('items').push(nuevo);
            this.state.lista.push(nuevo);
            this.setState({lista: this.state.lista});
            console.log(nuevo);
    
    
        }
    

    Por ahora intentando simplificar el ejemplo, no estoy manejando los errores, por eso solo llamamos a firebase.database().ref('items').push(nuevo); directamente y sin promesas, este método estaría agregando a nuestra base de datos en Firebase nuestro nuevo item, ¡solo hay algo que se nos escapa!


    Para poder guardar datos en la versión 3.x de Firebase en este ejemplo; debemos ir a las reglas de la base de datos y marcar la escritura como verdadero, ya que no estamos haciendo un login para autenticar al usuario, debemos de dar permiso para que un usuario anónimo pueda escribir:

    Y estamos listos para probar nuestra app una vez más.
    Después de que probamos guardando un item nuevo vamos al dashboard de nuestra base de datos y podemos ver que se ha guardado nuestro nuevo item, ¡Estamos por terminar! ahora solo nos falta obtener esta lista de items desde firebase y sustituir nuestra lista hardcodeada.

    8.- Recuperar los datos

    Para ello creamos un método llamado listenForItems y agregamos el método del ciclo de vida de nuestro componente componentDidMount el cual es ejecutado automáticamente por React una vez que el componente se ha renderizado.

        listenForItems = (itemsRef) => {
        itemsRef.on('value', (snap) => {
    
          // get children as an array
          var lista = [];
          snap.forEach((child) => {
            lista.push({
              name: child.val().name,
              done: child.val().done,  
              id: child.key
            });
          });
    
          this.setState({
            lista: lista
          });
    
        });
      }
    
        componentDidMount() {
            const itemsRef = firebase.database().ref('items');
            this.listenForItems(itemsRef);
          }
    

    Con este listener mirando a la referencia de la rama de nuestra base de datos aprovechamos al máximo el tiempo real de Firebase para encontrar cambios instantáneamente, esto nos permite que si guardamos un item nuevo desde iOS podamos verlo reflejado al instante en android, y viceversa.


    ¡Muy bien! solo falta una cosa; ¡Borrar!

    9.- Terminando

    Para borrar nuestro item, vamos a echar mano de nuestra función que escucha si el usuario presiona durante un segundo cada RowComponent mostrándole un alert, vamos a modificar este alert para recibir la confirmación del usuario y borrar el item presionado de la lista y de Firebase.
    Escribimos un método más en App.js que se llame borrar (nuestra clase App está creciendo con una gran cantidad de métodos, pero no te preocupes esto es una buena práctica ya que es el componente contenedor y se encarga de manejar toda la lógica, mientras los componente hijos reciben estas funciones solo en forma de props)

        borrar = (item) => {
            let updates = {};
            updates['/items/' + item.id] = null;
            firebase.database().ref().update(updates);
    
        }
    

    con esta funcion podemos recibir un objeto item, tomar su id y setear su contenido a null lo cual es equivalente a eliminarlo en Firebase


    Luego lo pasamos en forma de props al componente ListComponent

                        <View style={styles.container}>
                            <ListComponent
                                lista={this.state.lista}
                                changeDone={this.changeDone}
                                borrar={this.borrar}
                                />
                        </View>
    

    Una vez que lo pasamos a ListComponent este a su vez debe pasarlo a RowComponent
    ListComponent:

            return (
                <Container>
                    <Content>
                        {this.props.lista.map(
                            item => <RowComponent
                                        key={item.id}
                                        item={item}
                                        changeDone={this.props.changeDone}
                                        borrar={this.props.borrar}
                                              />
                                            )}
                    </Content>
                </Container>
            );
    

    Y finalmente en RowComponent modificamos nuestro método que levanta el alert animationActionComplete y agregamos un método local borrar que solo llama al método que llegó por los porps con el mismo nombre y le pasa el item que se encuentra también en los props:

        borrar = () => {
            this.props.borrar(this.props.item);
        }
    
        animationActionComplete =() => {
        const message = '¿Seguro que quieres eliminarlo?';
        if (this._value === 1) {
            Alert.alert(
                'ELIMINAR',
                message,
                [
                    {
                        text: 'Borrar',
                        onPress: this.borrar
                    },
                    {
                        text: 'Cancelar',
                        onPress: null
                    }
                ]
              );
        }
        }
    

    De esta manera nuestra aplicación, ya puede eliminar un item presionándolo por un segundo.
    Uff! haz trabajado un montón, y esto solo es la punta del iceberg con Firebase y React Native, ya que hemos omitido la autenticación, seguridad y el manejo de errores, así como la edición del item, además hemos usado un par de componentes sin estilizar a profundidad, nos llevaríamos mucho tiempo con todos esos detalles, pero prometo próximamente grabar un tutorial más completo, por ahora dejémoslo aquí.
    Te dejo el repo de todo el proyecto por si algo no quedó claro y agradezco tu tiempo, nos estamos leyendo ;)


    Repo en Github

    Solo debes clonar y hacer npm i