Subir vídeos al reproductor IPFS

IPFS logo

Hola, en esta entrada explicaremos como adaptar nuestros vídeos para subirlos a la red IPFS desde nuestra página de Upload y poder visualizarlos con el reproductor IPFS, utilizaremos el software ffmpeg que suele venir disponible en los repositorios de la distribución que estemos usando

Si tenemos por ejemplo un archivo mp4 y queremos adaptarlo al formato HLS con el siguiente comando:

ffmpeg -i video.mp4 -y -acodec aac -vcodec copy -hls_time 60 -hls_list_size 0 -f hls video.m3u8

Y de esta forma trozeamos el archivo mp4 en partes de 60 segundos con formato hls

Una vez termine la transcodificación del archivo vamos a la página de Upload y subimos el directorio generado

Cuando termine de subirse nos devuelve un hash de la carpeta que contiene el vídeo en formato HLS, este hash es el que tenemos que pasarle al reproductor IPFS

Y con esto ya tenemos nuestro vídeo en la red IPFS disponible para verlo desde el reproductor cuando nos apetezca, advertir que se tenga cuidado con los contenidos subidos ya que no pueden borrarse de la red.

Saludos

Docker con IPv6

docker6

Aquí vamos a explicar como habilitar el protocolo IPv6 en Docker, esto permitirá comunicar nuestros contenedores con el mundo exterior por este protocolo aparte del conocido IPv4, funciona solamente en GNU/Linux

Para esto hay que tener configurado en el servidor anfitrión IPv6 en la interfaz WAN (la que llega a internet), en algunos proveedores puede venir activado por defecto pero no es la norma, para probar si tenemos IPv6 en el anfitrión haremos un ping6

$ ping6 2600::

Si responde correctamente las solicitudes ICMPv6 tendremos IPv6 en la máquina, sino hay que negociar con el proveedor como habilitarlo

En el archivo /etc/docker/daemon.json (si no existe lo creamos) hay que añadir lo siguiente como se indica en la documentación de Docker

{
  "ipv6": true,
  "fixed-cidr-v6": "2001:db8:1::/80"
}

El parámetro fixed-cidr-v6 indica la subred interna que usarán los contenedores, es decir, la subred de la interfaz bridge que normalmente identificamos con docker0, se configurará con la primera dirección de la red disponible

Se elige la máscara de red /80 que es menor que la que hay en la interfaz externa /64 para evitar problemas en el enrutado de los paquetes IPv6, lo recomendable es usar una máscara con un rango menor, si ponemos una máscara /64 o del mismo valor que la externa se tiene que usar algún demonio de enrutado como ndppd

Una vez se reinicia Docker puede verse configurada la interfaz interna de red bridge con IPv6 habilitado y la consiguiente red asignada con el comando

$ docker network inspect bridge

Para usar IPv6 en los contenedores habrá que enrutar el tráfico de vuelta hacia la interfaz externa haciendo masquerading con el comando ip6tables

$ ip6tables -t nat -A POSTROUTING -s 2001:db8:1::/80 -j MASQUERADE

Además hay que configurar el sysctl de la máquina para preparar el kernel con un entorno IPv6, añadimos las siguientes líneas al archivo /etc/sysctl.d/99-sysctl.conf modificando la interfaz de red de salida por la correspondiente (hay que reiniciar la máquina)

net.ipv6.conf.default.accept_ra=0
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.default.forwarding=1
net.ipv6.conf.all.forwarding=1
net.ipv6.bindv6only=0
net.ipv6.conf.default.disable_ipv6=0
net.ipv6.conf.all.disable_ipv6=0
net.ipv6.conf.default.proxy_ndp=0
net.ipv6.conf.all.proxy_ndp=0
net.ipv6.conf.ensXXX.proxy_ndp=1
net.ipv6.conf.ensXXX.accept_ra=2
net.ipv6.conf.ensXXX.forwarding=0

Solo queda instanciar un contenedor y comprobar que tenemos ping6 desde él

$ docker run -ti --rm debian:buster ping6 -c10 2600::
PING 2600::(2600::) 56 data bytes
64 bytes from 2600::: icmp_seq=1 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=2 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=3 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=4 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=5 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=6 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=7 ttl=52 time=141 ms
64 bytes from 2600::: icmp_seq=8 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=9 ttl=52 time=140 ms
64 bytes from 2600::: icmp_seq=10 ttl=52 time=140 ms

--- 2600:: ping statistics ---
10 packets transmitted, 10 received, 0% packet loss, time 20ms
rtt min/avg/max/mdev = 139.906/140.204/141.457/0.475 ms

Y colorín colorado este post se ha terminado, esperamos os haya gustado, nos vemos en la próxima

Usa Virt-Manager NO VirtualBox

logo-virt-manager

Desde que se lanzó la versión 10 de Debian GNU/Linux (Buster) en su página nos recomiendan dejar de usar VirtualBox por la aplicación de escritorio Virt-Manager

apt install virt-manager

Ya conocemos desde hace años VirtualBox de Oracle para trabajar con máquinas virtuales, es OpenSource aunque el producto está controlado por la misma empresa. Como alternativa ahora se utiliza Virt-Manager que está basado en KVM y libvirt, aunque también puede gestionar Xen o LXC directamente

Como podéis ver el entorno nos recuerda mucho a otras aplicaciones de virtualización como VMware o VirtualBox, al crear una máquina virtual nos da a elegir el tipo de instalación que queremos realizar

Una vez configurados los parámetros del menú wizard, se generan los recursos necesarios para arrancar la máquina virtual, esto podemos realizarlo también mediante comandos pero la GUI nos facilita mucho la tarea

También hay que configurar las interfaces de red que se asignarán a las máquinas virtuales, aunque la aplicación lo hace todo a golpe de ratón con permisos de root

Como se puede ver el menú nos recuerda mucho a otras aplicaciones de virtualización, aunque difieren muchas de las configuraciones, por ejemplo no hay límites a la hora de asignar los núcleos del procesador a una máquina virtual, pero la aplicación nos avisa que puede incurrir en un bajo rendimiento

Os animo a probarlo y utilizarlo como alternativa a otras aplicaciones de virtualización tanto comerciales como libres y comprobar la potencia que nos ofrece la virtualización del Kernel de GNU/Linux, como ya recomendó hace años fanta en su blog, saludos

Haraka Wildduck

duck

En esta entrada vamos a completar el taller que dimos hace poco de como montar un servidor de correo electrónico completo y autogestionado en NodeJS

Tenemos que bajar el repositorio git que montamos para desplegar la aplicación en contenedores Docker por lo que necesitaremos tener instalado docker y docker-compose

Una vez descargado, generamos los certificados seguros para la aplicación en la carpeta secure con el script start.sh también hay que modificar el dominio sobre el que vamos a tener el correo en el archivo .env, además el DNS del dominio tiene que tener un registro MX apuntando a la máquina donde se despliegue el servidor

Arrancamos los servicios con el siguiente comando

docker-compose up -d

Si queremos mantener la persistencia deberemos copiar las base de datos

docker cp mongo:/data/db ./mongodb && chown -R 999.999 ./mongodb
 docker cp redis:/data ./redis && chown -R 999.999 ./redis

Descomentar las lineas del archivo docker-compose.yml

docker-compose down && docker-compose up -d

Y tendremos un servidor de correo en NodeJS nativo funcionando bajo nuestro control, saludos

Montar una instancia Nextcloud en Docker

nextcloud

En esta entrada vamos a explicar brevemente como montar una instancia de Nextcloud en Docker, Nextcloud es un potente servicio de software libre de cloud (nube), sirve para almacenar, sincronizar y compartir archivos principalmente aunque con sus plugins se pueden añadir muchas funcionalidades, reemplaza al viejo proyecto ownCloud.

Para ello utilizaremos la imagen oficial de Nextcloud que podemos encontrar en el hub de Docker, esto nos facilitará el despliegue y es más fácil que mantener una imagen propia, podemos encontrar distintas versiones de la imagen aunque nos basaremos en la última latest, además contamos con las últimas actualizaciones de seguridad.

Usaremos el programa docker-compose para el despliegue de la instancia, con el que configuramos los contenedores necesarios con nuestras opciones, en vez de hacerlo directamente desde la línea de comandos.

version: '2'

services:
  nextcloud:
    image: nextcloud
    restart: always
    container_name: nextcloud
    hostname: nextcloud
    environment:
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_PASSWORD=xxx
      - MYSQL_HOST=mariadb-nextcloud
    depends_on:
      - mariadb-nextcloud
    expose:
      - 80
    links:
      - mariadb-nextcloud
    volumes:
      - ./nextcloud/html/custom_apps:/var/www/html/custom_apps
      - ./nextcloud/html/config:/var/www/html/config
      - ./nextcloud/html/data:/var/www/html/data
      - ./nextcloud/html/themes:/var/www/html/themes
    networks:
      - cloudnet:

  mariadb-nextcloud:
    image: mariadb
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    restart: always
    container_name: mariadb-nextcloud
    hostname: mariadb-nextcloud
    volumes:
      - ./nextcloud/mysql:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=yyy
      - MYSQL_PASSWORD=xxx
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
    networks:
      - cloudnet:

networks:
  cloudnet:

Teniendo este archivo en una carpeta, con el comando docker-compose up -d nos bajaremos la última versión de las imágenes de Nextcloud y MariaDB desde el hub de Docker, y seguidamente se instanciarán ambos contenedores. Hemos preferido compartir los volúmenes de los contenedores con el sistema de archivos local para mantener la persistencia por si se elimina el contenedor, y configurar las imágenes con las variables de entorno necesarias para el correcto funcionamiento del servicio.

También hemos relacionado los contenedores (links) y expuesto el puerto 80/tcp, ya que nuestro servicio está detrás de un proxy nginx, del contenedor Nextcloud para utilizarlo aunque pueden incluirse los que se necesiten.

Una vez se levanten los contenedores podremos acceder al servicio por el puerto correspondiente y comenzar a configurar la instancia con las opciones que necesitemos.

Esperamos que esta entrada os haya gustado y os sirva para futuros despliegues, saludos

Tuneando PostgreSQL

PostgreSQL

Después de una larga ausencia en el blog, vamos a explicar una configuración para la base de datos PostgreSQL que pueda soportar altas cargas y aumentar la disponibilidad del servicio, comentaremos sus opciones y justificaremos los valores que decidimos aplicar en función de los recursos de la máquina.

Nuestra máquina es una servidor cloud que tiene 4 núcleos y 8G de memoria RAM, la máquina actualmente está compartida con contenedores docker de otros servicios por lo que en este contenedor vamos a consumir como máximo 3G de RAM y un pool de 250 conexiones, ya que el software que la usa (Elixir en este caso) es muy rápido y necesita de una alta disponibilidad, así mismo precisa de un alto número de conexiones concurrentes para no saturarla.

Opciones de configuración

Estas opciones las hemos sacado de la página https://pgtune.leopard.in.ua que nos ayudará a afinar la configuración en función de los valores de versión, núcleos, memoria y conexiones que necesitemos, hemos decidido usar el perfil online transaction processing system para nuestro caso.

# DB Version: 12
# OS Type: linux
# DB Type: oltp
# Total Memory (RAM): 3 GB
# CPUs num: 4
# Connections num: 250

max_connections = 250 # máximo número de conexiones concurrentes
shared_buffers = 768MB # cantidad de memoria dedicada a datos en caché
effective_cache_size = 2304MB # cantidad de memoria disponible para memoria intermedia en el disco
maintenance_work_mem = 192MB # cantidad de memoria usada para operaciones de mantenimiento
checkpoint_completion_target = 0.9 # permite escribir lentamente en la instancia: checkpoint_completion_target * checkpoint_timeout (5min)
wal_buffers = 16MB # pequeña memoria que sincroniza los datos, aumentándola se permiten inserciones más grandes
default_statistics_target = 100 # recolecta estadísticas de cada una de las tablas para decidir como se ejecutarán las consultas sobre ellas
random_page_cost = 1.1 # sugiere al optimizador cuanto tiempo le llevará al disco encontrar una página aleatoria de disco
effective_io_concurrency = 200 # número de operaciones de disco I/O concurrentes
work_mem = 1572kB # para operaciones complejas realiza ordenamientos más distendidos en memoria
min_wal_size = 2GB # cantidad mínima de la memoria usada para integridad de los datos
max_wal_size = 4GB # cantidad máxima de la memoria usada para integridad de los datos
max_worker_processes = 4 # cantidad máxima de procesos
max_parallel_workers_per_gather = 2 # cantidad máxima de subprocesos en paralelo por nodo, no puede exceder a max_parallel_workers
max_parallel_workers = 4 # cantidad máxima de subprocesos en paralelo

Con esta configuración el contenedor de PostgreSQL 12 está preparado para trabajar con 250 conexiones concurrentes, pudiendo gestionar correctamente la carga de las consultas que se realicen y mantener la estabilidad del sistema a lo largo del tiempo.

Esperamos que os haya gustado, nos vemos en la próxima entrada, saludos

Java con un poco de SpringBoot2

En esta entrada vamos a presentar un poco el framework SpringBoot2 para Java, es un software que como objetivo tiende a sustituir y eliminar los tediosos archivos XML del framework de Spring por las anotaciones, que básicamente son clases programadas que modifican el comportamiento de las clases, métodos y parámetros sobre los que están puestos, y se distinguen en el código por tener una @ delante del nombre de la anotación.

Tenemos decenas de anotaciones en el framework que nos van a ayudar a la hora de implementar un proyecto de forma muy rápida sin tener que reescribir el código necesario para que todo funcione, parece magia ya que todo es transparente para el programador, aunque nos obliga a formarnos, tiene una curva de aprendizaje muy rápida y sus funcionalidades pueden ayudarnos a la hora de integrar la lógica de la aplicación.

@anotaciones

@Autowired // Instancia una variable y sus @Beans
@Bean // Crea una instancia singleton
@SpringBootApplication // Instancia una aplicación SpringBoot2
@Controller // Clase controlador
@Service // Clase servicio
@Repository // Clase repositorio
@SpringBootTest // Instancia una bateria de pruebas
@RequesMapping // Prepara un método para un endpoint
@GetMapping // Mismo que el anterior HTTP GET
@PostMapping // Mismo que el anterior HTTP POST
@Async // Clase asíncrona
@EnableAsync // Instancia una aplicación asíncrona

Las anotaciones son muy potentes, nos permiten añadir funcionalidad al código sin tener que implementarla, podemos pasar parámetros de entrada en la anotación entre paréntesis () o varios entre llaves ({}) eso dependerá según para lo que esté programada la anotación.

@GetMapping({ "/", "/index" }) // dos parámetros
@PostMapping("/new") // un parámetro
@GetMapping("/{id}/edit") // podemos especificar parámetros enlazados con variables de entrada

Como vemos con estas herramientas fácilmente podríamos implementar una aplicación con el patrón MVC y añadir las funcionalidades necesarias como un simple CRUD

Spring Initializr para SpringBoot2

Para iniciar una aplicación en SpringBoot2 vamos a la web start.spring.io y seleccionamos el tipo de proyecto que vamos a desplegar y las librerías que vamos a utilizar

Una vez terminamos nos descargamos el proyecto y lo descomprimimos en el workspace para empezar a trabajar con el IDE que más os guste

Espero haber animado a usar este framework que cada día se utiliza más en aplicaciones, y ofrece características como la asincronía o la programación reactiva que no deja indiferente a la hora de poner aplicaciones en producción con altas cargas de peticiones.

Aquí os dejo un pom.xml de prueba

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-webflux</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>io.projectreactor</groupId>
			<artifactId>reactor-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Saludos

Empezando con Docker

docker

¿Qué es Docker?

Docker es un proyecto de código abierto que automatiza el despliegue de aplicaciones dentro de contenedores de software, proporcionando una capa adicional de abstracción y automatización de virtualización de aplicaciones en múltiples sistemas operativos.

Docker utiliza características de aislamiento de recursos del kernel Linux, tales como cgroups y espacios de nombres (namespaces) para permitir que “contenedores” independientes se ejecuten dentro de una sola instancia de Linux, evitando la sobrecarga de iniciar y mantener máquinas virtuales.

¿Qué NO es Docker?

Docker no es como otros sistemas de virtualización completa de un sistema operativo, no emula el hardware necesario para arrancarlo, utiliza el de la máquina anfitriona compartiendo los recursos de la misma entre los contenedores.

En resumen Docker es una herramienta que nos da la capacidad de trabajar con contenedores (máquinas virtuales) que comparten un mismo Kernel.

Vamos a ver un poco las partes básicas de Docker para hacer funcionar un sistema, y luego explicaremos como podemos automatizarlo con docker-compose.

Contenedores

Es el espacio donde se almacenan los datos de la máquina virtual, contiene todos los archivos necesarios para funcionar independientemente del sistema anfitrión. Un contenedor es una instancia de una imagen.

Imagen

Es un prototipo de un contenedor, así como una clase es el prototipo de un objeto y este una instancia de la misma, un contenedor es una instancia de una imagen. Varios contenedores pueden compartir una imagen, pero una imagen solo se corresponde con un contenedor.

Volumen

Es un espacio de almacenamiento que comparte el contenedor con la máquina anfitriona para tener persistencia de los datos, los volúmenes pueden ser de varios tipos como los internos (que los gestiona Docker) o pueden usarse mayormente carpetas compartidas.

Red

Las interfaces de red pueden configurarse según se necesite, tenemos varias como host, que comparte con la máquina anfitriona, el bridge que genera un puente de red o null que no tiene red.

Automatizar Docker con docker-compose

Docker compose es una herramienta creada por Docker, que permite crear una pila de contenedores intercomunicados, partiendo de distintas imágenes.

Docker compose se basa en un fichero con extensión yml donde vamos a indicar que imagen queremos desplegar, cómo se va a configurar y de qué depende.

version: '3'
services:
  web:
    build: .
    ports:
    - "80:80"
    volumes:
    - ./html:/var/www/html
    networks:
      net:

networks:
  net:

En el anterior ejemplo vemos como se crea un contenedor que compila un Dockerfile de la carpeta local, que comparte el volumen de la carpeta local html y también crea la configuración de red.

Conclusión

Las principales ventajas de la virtualización basada en contenedores son:

  • Menos recursos, ahorro en costes. En una misma máquina pueden desplegarse más contenedores que máquinas virtuales tradicionales. Las exigencias en el proceso de inicio y espacio en disco son menores y más rápidas.
  • Gestión TI más fácil, aumento de la productividad TI. La creación de contenedores permite estandarizar los despliegues ya que son entornos repetibles para tareas de desarrollo, prueba y producción. La compatibilidad con todos los sistemas de implantación elimina un valioso tiempo de configuración. Son elementos totalmente portables. Con Docker la implementación se realiza en segundos.
  • Múltiples aplicaciones independientes en un mismo host. Cada aplicación se ejecuta en su contenedor o clúster de contenedores de forma independiente, sin entrar en conflicto con el resto de aplicaciones que aloje el host que ejecutarán mediante sus propios contenedores. Esto garantiza un entorno seguro y eficiente.

Cuando una aplicación ya no es necesaria, simplemente se elimina su contenedor sin dejar huella en el sistema donde se ejecutaba.

Map, filter y reduce (JavaScript)

javascript

Hola, en esta entrada vamos a explicar un poco los métodos nativos map, filter y reduce, que encontramos en los objetos Arrays de JavaScript y como pueden ayudar a hacer más eficiente la implementación, ya que son más rápidos que los bucles tradicionales.

  • map([🌽, 🐮, 🐔], cook) => [🍿, 🍔, 🍳]
  • filter([🍿, 🍔, 🍳], isVegetarian) => [🍿, 🍳]
  • reduce([🍿, 🍳], eat) => 💩

Map

El método map() devuelve un nuevo Array como resultado de la función que se está aplicando a cada elemento del Array. Esto significa que cada elemento del Array va a ser devuelto en la misma posición del Array resultante pasando por la función del método map(), como lo haría un iterador.

// Declaramos un Array
> var a = new Array(1, 2, 3) || [1, 2, 3]
// Mapeamos el Array con una función para elevar al cuadrado el contenido
> var resultado = a.map(elemento => Math.pow(elemento, 2))
// Se imprime el resultado
> console.log(resultado)
> [1, 4, 9]

Finalmente el método map() devuelve un nuevo Array de la misma dimensión que el original pero modificando cada elemento por el de la salida de la función que estemos aplicando, por lo que no se modifica el Array original.

Filter

El método filter() del objeto Array, como su nombre indica se utiliza para filtrar los elementos del Array con las condiciones lógicas que se tenga en la función que implementa, si la condición no pasa el filtro este elemento no es devuelto en el Array resultante, quedando una dimensión menor que el original.

// Declaramos un Array
> var a = new Array(1, 2, 3) || [1, 2, 3]
// Filtramos el Array con una función para obviar el número 2
> var resultado = a.filter(elemento => elemento !== 2)
// Se imprime el resultado
> console.log(resultado)
> [1, 3]

Vemos como el filtro ignora el número 2 ya que la condición es que sea distinto del número 2, por lo que este número si se encuentra en el Array no es devuelto en el resultante y su posición se obvia, tampoco se modifica el Array original.

Reduce

El método reduce() del objeto Array, este método reduce el Array a un único elemento, ejecuta la función que tiene el método en cada uno de los elementos del Array y el resultado de cada elemento se almacena en un acumulador que será el resultado total.

// Declaramos un Array
> var a = new Array(1, 2, 3) || [1, 2, 3]
// Reducimos el Array a un solo elemento sumando los cuadrados de los elementos del Array
> var resultado = a.reduce((acumulador, elemento) => acumulador + Math.pow(elemento,2))
// Se imprime el resultado
> console.log(resultado)
> 14

Como vemos el resultado en este caso no es un nuevo Array sino el acumulador de la función que apliquemos, tampoco se modifica el Array original.

Conclusiones

Para terminar añadir que ninguno de estos tres métodos utiliza un elemento que no esté definido, en caso de que sea undefined o null se obvia la función aplicada.

Es una buena práctica utilizar los métodos nativos de los Arrays en vez de los bucles tradicionales, ya que son mucho más rápidos a la hora de iterar y si estamos aplicando funciones se hace todo con una sola instrucción y es más limpio.