def trainGrid(self):
        # procesamos los parámetros para convertirlos en patrones de entrada
        def_weights = self.current_weights
        x = np.linspace(-2, 2, 20)
        y = np.linspace(-2, 2, 20)
        xx, yy = np.meshgrid(x, y)

        coords = []
        for a, b in zip(xx, yy):
            for ax, bx in zip(a, b):
                coords.append((ax, bx))

        for patron in coords:
            entradas = [1, patron[0], patron[1]]
            # procesamos capa oculta
            hidden = []
            for n in range(self.hidden_layer_length):
                neurona = Neuron(entradas, def_weights[n], 0)
                neurona.y = self.logisticFunc(neurona.getV())
                hidden.append(neurona)
            
            next_inputs = []
            for n in hidden:
                next_inputs.append(n.y)
            
            # calculamos su resultado en base a la neurona de salida
            neurona_salida = Neuron(next_inputs, def_weights[len(def_weights)-1], 0)
            neurona_salida.y = self.logisticFunc(neurona_salida.getV())

            if neurona_salida.y >= 0.5:
                plt.scatter( entradas[1], entradas[2], color="#48E723", s=10 )
            else:
                plt.scatter( entradas[1], entradas[2], color="#FBC402", s=10 )
    def runTraining(self):
        epoca = 0
        cantidad_patrones = len(self.inputs)

        # PASO 1: inicializamos pesos aleatorios, por cada neurona en toda la red
        for i in range(self.hidden_layer_length+1):
            self.current_weights.append([
                getRandomFloat(-1, 1, DECIMALES),
                getRandomFloat(-1, 1, DECIMALES),
                getRandomFloat(-1, 1, DECIMALES),
            ])

        execute = True
        while execute:
            errores = []
            resultados = []

            # PASO 2: el entrenamiento se divide en 3 etapas, por cada conjunto de patrones de entradas 
            for i in range(cantidad_patrones):
                # if i == 1:
                #     break
                x = self.inputs[i]
                d = self.outputs[i]

                # -> etapa hacia adelante: calcular la salida de la red
                # formamos y procesamos las neuronas de la capa oculta
                hidden_layer = []
                for j in range(self.hidden_layer_length):
                    nrn = Neuron(x, self.current_weights[j], d)
                    # calculamos salida y
                    nrn.y = round(self.logisticFunc(nrn.getV()), DECIMALES)
                    # # calculamos error e
                    # nrn.e = round((nrn.d - nrn.y), DECIMALES)
                    hidden_layer.append(nrn)

                # procesamos la capa de salida, la cual en esta implementación, consta de una sola neurona
                output_x = []
                for neuron in hidden_layer:
                    inp = neuron.y
                    output_x.append(inp)

                output_nrn = Neuron(output_x, self.current_weights[ len(self.current_weights) - 1 ], d)
                # calculamos salida y
                output_nrn.y = round(self.logisticFunc(output_nrn.getV()), DECIMALES)
                # # calculamos error e
                output_nrn.e = round((output_nrn.d - output_nrn.y), DECIMALES)
                errores.append(output_nrn.e)

                # -> etapa hacia atrás: ajuste de pesos
                # comenzamos con la capa de salida
                output_local_gradient = self.outputLayer_localGradient(output_nrn.getDy(), output_nrn.e)

                # print("\npesos de salida ------------->", output_nrn.w)
                output_nrn.w = self.adjustWeights(output_nrn.w, output_local_gradient, output_x)
                # print("pesos de salida ajustados --->", self.adjustWeights(output_nrn.w, output_local_gradient, output_x))

                # continuamos con la capa oculta
                pos = 0
                for neuron in hidden_layer:
                    hidden_local_gradient = self.hiddenLayer_localGradient(
                        neuron.getDy(), output_nrn.w[pos], output_local_gradient
                    )
                    # print("neurona oculta {index} ----------->".format(index=pos), neuron.w)
                    neuron.w = self.adjustWeights(neuron.w, hidden_local_gradient, x)
                    # print("neurona oculta ajustada {index} -->".format(index=pos), neuron.w)
                    pos += 1

                # almacenamos los pesos ajustados para la siguiente iteración
                self.current_weights.clear()
                for neuron in hidden_layer:
                    self.current_weights.append(neuron.w)
                self.current_weights.append(output_nrn.w)

                # guardamos los resultados de esta iteración -> [entradas, salida_d, salida_y, error, pesos]
                resultados.append([x, d, output_nrn.y, output_nrn.e, output_nrn.w])
            epoca += 1

            if epoca == self.epoch_limit:
                self.showResults(epoca, resultados, final=True)    
                print("\n [!] LÍMITE DE EPOCAS ALCANZADO.\n")
                break

            # mostramos los resultaddos de la época
            if epoca % MOD_VAL == 0:
                self.showResults(epoca, resultados, final=False)

            # PASO 3: verificar la condición de parada   
            execute = self.checkErrors(errores)
            if execute == False:
                self.showResults(epoca, resultados, final=True)    
                print("\n [!] ALGORITMO FINALIZADO.\n")