Django es un framework de aplicaciones web gratuito y de código abierto que está construido en el Python lenguaje de programación. Django es superrápido, seguro y altamente escalable. En manos de un desarrollador experimentado, Django puede establecer rápidamente un sitio web potente. Puede integrarse perfectamente con servidores web populares (Apache, Nginx), y bases de datos (MySQL, MariaDB, PostgreSQL, Oracle, y SQLite), etc. Django impulsa algunos de los sitios web más grandes del mundo como Instagram, Mozilla y la NASA. Esta guía demuestra cómo configurar la base de una aplicación web con la ayuda de Django con PostgreSQL, Nginx y Gunicorn en Ubuntu 20.04.
Requisitos previos
Esta guía requiere que esté ejecutando un servidor Ubuntu 20.04 configurado con un cortafuegos básico y un usuario no raíz con privilegios sudo. Consulte esta guía detallada sobre cómo configurar un servidor Ubuntu. Siga este tutorial para configurar un usuario no raíz con privilegios sudo. También puede configurar un cortafuegos Iptables siguiendo los pasos de esta guía.
Instalaremos Django dentro de un entorno virtual. Tener un entorno específico para el proyecto permite una gestión más sencilla de múltiples proyectos desde el mismo servidor. Una vez que las bases de datos y las aplicaciones estén en su lugar, implementaremos el servidor de aplicaciones Gunicorn. Gunicorn será la interfaz de la aplicación que traduce las solicitudes de los clientes de HTTP a llamadas de Python que nuestra aplicación pueda utilizar. Luego, implementaremos Nginx frente a Gunicorn por su rápido rendimiento en el manejo de conexiones y sus características de seguridad fáciles de implementar.
Instalación de los paquetes necesarios
Primero, comience instalando todos los paquetes necesarios. Afortunadamente, todos estos paquetes están disponibles directamente en los repositorios oficiales de paquetes de Ubuntu. Abra la terminal y actualice la caché de paquetes de APT:
|
1 |
sudo apt update |
La lista de paquetes depende de si la aplicación web va a utilizar Python 2 o Python 3. Ejecute el siguiente comando para instalar Django con Python 3:
|
1 |
sudo apt install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl |
Django 1.11 LTS es la última versión de Django que admitirá Python 2. Si tiene la intención de utilizar Django con Python 2, instale los siguientes paquetes:
|
1 |
sudo apt install python-pip python-dev libpq-dev postgresql postgresql-contrib nginx curl |
Base de datos y usuario de PostgreSQL
En cuanto a la solución de base de datos, utilizaremos PostgreSQL. Es un sistema de base de datos objeto-relacional potente y de código abierto. PostgreSQL ofrece confiabilidad, robustez y rendimiento. Para obtener pasos detallados sobre cómo configurar PostgreSQL, consulte esta guía sobre cómo configurar PostgreSQL en el servidor Ubuntu. Para la presente guía, configuraremos una base de datos y un usuario dedicados para nuestra aplicación Django.
Por defecto, PostgreSQL implementa la “autenticación de pares” como esquema de autenticación para conexiones locales. En resumen, la “autenticación de pares” autenticará el inicio de sesión si el nombre de usuario del sistema operativo coincide con un nombre de usuario de PostgreSQL válido. Durante la instalación, PostgreSQL configuró un usuario del sistema operativo postgres para corresponder al usuario administrador de PostgreSQL postgres. Inicie sesión en la sesión interactiva de la consola de PostgreSQL como postgres utilizando el siguiente comando:
|
1 |
sudo -u postgres psql |
Llegará a la consola de PostgreSQL. El primer paso es crear una base de datos dedicada para el proyecto. Para la demostración, la base de datos se llamará viktor_project:
|
1 |
CREATE DATABASE viktor_project; |
El siguiente paso es crear un usuario dedicado para la base de datos del proyecto. El usuario debe tener un nombre de usuario seguro. Para la demostración, el nombre de usuario será viktor_project_user:
|
1 |
CREATE USER viktor_project_user WITH PASSWORD 'password123'; |
Ahora, modificaremos algunos parámetros:
- Ciertos parámetros de conexión. En resumen, no será necesario consultar y establecer los valores correctos cada vez que se establezca una conexión. Mejora enormemente el rendimiento de la base de datos.
- Codificación predeterminada a
UTF-8. Es una codificación universal y Django la espera. - Esquema de aislamiento de transacciones predeterminado a “read committed”. Bloquea la lectura de transacciones no confirmadas.
- Zona horaria a
UTC.
Todos estos cambios de parámetros son recomendados por el propio proyecto Django. Para implementar estos cambios, ejecute los siguientes comandos. No olvide cambiar el nombre de usuario de la base de datos por el correcto:
|
1 2 3 |
ALTER ROLE viktor_project_user SET client_encoding TO 'utf8'; ALTER ROLE viktor_project_user SET default_transaction_isolation TO 'read committed'; ALTER ROLE viktor_project_user SET timezone TO 'UTC'; |
Cambie el administrador de la base de datos al usuario de base de datos dedicado:
|
1 |
GRANT ALL PRIVILEGES ON DATABASE viktor_project TO viktor_project_user; |
Nuestro trabajo con PostgreSQL ha terminado por ahora. Salga de la consola interactiva de PostgreSQL:
|
1 |
\q |
Entorno virtual de Python
Con la base de datos preparada, ahora podemos enfocarnos en establecer el resto de los requisitos del proyecto. Para una gestión más sencilla, estableceremos un entorno virtual e instalaremos todos los requisitos de Python allí. Para generar un entorno virtual, necesitamos virtualenv. Se puede instalar fácilmente con pip.
Los siguientes comandos actualizarán pip e instalarán virtualenv. Para Python 3, ejecute los siguientes comandos:
|
1 2 |
sudo -H pip3 install --upgrade pip sudo -H pip3 install virtualenv |
Para Python 2, ejecute los siguientes comandos en su lugar:
|
1 2 |
sudo -H pip install --upgrade pip sudo -H pip install virtualenv |
Una vez que virtualenv esté instalado, es hora de crear un entorno virtual. A continuación, cree un directorio dedicado para el entorno virtual:
|
1 |
mkdir -v ~/viktor_project |
Después de eso, cambie el directorio activo actual al directorio dedicado para el entorno virtual:
|
1 |
cd ~/viktor_project |
Dentro del directorio, ejecute el siguiente comando. La herramienta virtualenv creará un entorno virtual con el nombre del proyecto:
|
1 |
virtualenv viktor_project |
Creará un subdirectorio con el nombre del proyecto. El subdirectorio contendrá una versión local de Python y pip. Ofrece flexibilidad para instalar y configurar un entorno de Python aislado para el proyecto.
El siguiente comando activará el entorno virtual:
|
1 |
source viktor_project/bin/activate |
El indicador de la terminal cambiará indicando que está operando dentro de un entorno virtual de Python. Ahora que estamos dentro del entorno virtual, instalaremos los requisitos de Python necesarios. Necesitamos Django, Gunicorn y psycopg2 (adaptador de PostgreSQL). El siguiente comando le indicará al pip local que instale los componentes:
|
1 |
pip install django gunicorn psycopg2-binary |
Incluso si está utilizando Python 3, pip es el comando correcto. Esto se debe a que dentro del entorno virtual, pip3 pasa a llamarse pip.
Nuevo proyecto Django
Con los componentes de Python listos, podemos comenzar a trabajar con los archivos reales del proyecto Django.
-
Creación de un proyecto Django
El directorio del proyecto ya está establecido. Le diremos a Django que instale sus archivos allí. Este procedimiento generará un directorio de segundo nivel que contendrá el código real. El directorio también contendrá un script de administración. La clave es que le estamos indicando a Django el directorio de destino de forma explícita en lugar de dejar que decida el directorio relativo al actual:
|
1 |
django-admin.py startproject viktor_project ~/viktor_project |
Django creará el proyecto en consecuencia. Aquí están algunos de los archivos y directorios importantes en los que nos enfocaremos. Los nombres de directorios y archivos se utilizan de acuerdo con la demostración.
~/viktor_project/manage.py: El script de gestión de proyectos de Django.~/viktor_project/viktor_project/: Es el paquete que contiene el proyecto Django. Debe contener los archivos __init__.py, settings.py, urls.py, asgi.py y wsgi.py.
-
Ajustando la configuración del proyecto
Una vez creado el proyecto, lo primero que hay que hacer es ajustar su configuración. Abre settings.py en un editor de texto:
|
1 |
nano ~/viktor_project/viktor_project/settings.py |
La primera directiva que buscamos es ALLOWED_HOSTS. Define los servidores o nombres de dominio que pueden conectarse a la instancia de Django. Si alguna solicitud entrante con una cabecera Host no coincide con la lista de ALLOWED_HOSTS, creará una excepción. Django lo recomienda para evitar ciertos tipos de vulnerabilidades de seguridad:
|
1 |
ALLOWED_HOSTS = ['<server_ip_or_domain_name_1>',' server_ip_or_domain_name_2','localhost'] |
La siguiente sección en la que nos enfocaremos es DATABASE. Gestiona el acceso a la base de datos. Por defecto, contiene la configuración para el motor de base de datos SQLite. Sin embargo, vamos a utilizar la base de datos PostgreSQL para el proyecto. Django utilizará el adaptador psycopg2 para comunicarse con PostgreSQL:
|
1 2 3 4 5 6 7 8 9 10 |
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': 'viktor_project', 'USER': 'viktor_project_user', 'PASSWORD': 'password123', 'HOST': 'localhost', 'PORT': '', } } |
Ahora, ve al final del archivo. Agrega las siguientes líneas para indicar la ubicación de los archivos estáticos. Esto ayuda a Nginx a manejar las solicitudes de estos elementos:
|
1 2 |
import os STATIC_ROOT = os.path.join(BASE_DIR, 'static/') |
Nuestro trabajo con settings.py ha terminado por ahora. Guarda el archivo y cierra el editor.
-
Finalizando la configuración inicial del proyecto
Ahora podemos migrar el esquema inicial de la base de datos a la base de datos PostgreSQL dedicada. Ejecuta el siguiente comando:
|
1 2 |
~/viktor_project/manage.py makemigrations ~/viktor_project/manage.py migrate |
A continuación, necesitamos crear un superusuario para el proyecto. Para generar un superusuario, ejecuta el siguiente comando:
|
1 |
~/viktor_project/manage.py createsuperuser |
Recopila todos los archivos estáticos en la ubicación que especificamos en settings.py. Los archivos estáticos se recopilarán en un directorio separado llamado “static” dentro del directorio del proyecto:
|
1 |
~/viktor_project/manage.py collectstatic |
Ahora, necesitamos hacer algunos ajustes en el firewall del servidor. Si seguiste la guía de configuración del servidor, ya tendrás UFW configurado y activado. Crearemos una excepción para el puerto 8000. Este es el puerto predeterminado que utiliza Django. Consulta esta guía para obtener más información sobre los conceptos básicos y el uso del firewall UFW.
|
1 |
sudo ufw allow 8000 |
A continuación, verifica la acción:
|
1 |
sudo ufw status |
Finalmente, podemos probar el servidor en acción. Inicia el servidor de desarrollo de Django:
|
1 |
~/viktor_project/manage.py runserver 0.0.0.0:8000 |
Si la configuración se realizó correctamente, el servidor de desarrollo de Django debería iniciarse y aceptar solicitudes entrantes. Abre un navegador y ve a la IP/dominio de tu servidor en el puerto 8000:
|
1 |
http://<server_or_domain>:8000 |
Deberías llegar a la página de inicio predeterminada de Django. Para acceder al panel de administración, añade /admin a la URL. El panel de administración solo es accesible para el superusuario que creamos previamente:
|
1 |
http://<server_or_domain>:8000/admin |
Después de iniciar sesión, llegará a la interfaz de administración predeterminada de Django:
Hemos terminado las pruebas por ahora. Para detener el servidor, presione “Ctrl + C” en la ventana de la terminal.
-
Probando Gunicorn
Antes de salir del entorno virtual, queremos asegurarnos de que Gunicorn pueda servir las aplicaciones. La forma de probarlo es usando Gunicorn para cargar el módulo WSGI del proyecto.
El comando Gunicorn se encuentra dentro del directorio del proyecto:
|
1 2 |
cd ~/viktor_project gunicorn --bind 0.0.0.0:8000 viktor_project.wsgi |
Esto iniciará Gunicorn en la misma interfaz en la que se estaba ejecutando Django. Podemos probar la aplicación nuevamente desde un navegador web normal. Tenga en cuenta que la interfaz de administración no tendrá ningún estilo aplicado porque Gunicorn todavía no sabe cómo encontrar los contenidos CSS estáticos:
|
1 |
http://<server_or_domain>:8000 |
Cuando termine, presione “Ctrl + C” en la ventana de la terminal para detener el servidor Gunicorn.
-
Salir del entorno virtual
La configuración de la aplicación Django ha finalizado. Ejecute el siguiente comando para salir del entorno virtual:
|
1 |
deactivate |
Archivos de socket y servicio de Gunicorn
Hemos verificado que Gunicorn puede interactuar con la aplicación Django. Sin embargo, necesitamos una forma más robusta de administrar el servidor de aplicaciones. Aquí entra systemd en la ecuación. Systemd es uno de los sistemas de inicio más populares disponibles en Linux. Aquí tiene una guía detallada sobre cómo administrar servicios y unidades de systemd.
Podemos crear archivos de socket y de servicio para Gunicorn para permitir que systemd lo administre como si fuera un servicio. Al arrancar, se generará el socket de Gunicorn. El socket escuchará las conexiones entrantes. Cuando se produzca una conexión, systemd iniciará los procesos de Gunicorn para manejar la conexión.
-
Socket de Gunicorn
Comencemos creando un socket de Gunicorn. El archivo debe crearse con privilegios de sudo:
|
1 |
sudo nano /etc/systemd/system/gunicorn.socket |
Introduzca el siguiente código dentro del archivo:
|
1 2 3 4 5 6 7 |
[Unit] Description=gunicorn socket [Socket] ListenStream=/run/gunicorn.sock [Install] WantedBy=sockets.target |
Como puede ver, hay tres secciones en el código.
[Unit]:Esta sección describe el socket.[Socket]:Define la ubicación del socket.[Install]:Esta parte asegura que systemd cree el socket en el momento adecuado.
Guarde el archivo y cierre el editor.
-
Servicio de Gunicorn
A continuación, crearemos un archivo de servicio para Gunicorn. Al igual que el archivo de socket, también debe crearse con privilegios de sudo:
|
1 |
sudo nano /etc/systemd/system/gunicorn.service |
Introduzca el siguiente código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Unit] Description=gunicorn daemon Requires=gunicorn.socket After=network.target [Service] User=cloudsigma Group=www-data WorkingDirectory=/home/cloudsigma/viktor_project ExecStart=/home/cloudsigma/viktor_project/viktor_project/bin/gunicorn \ --access-logfile - \ --workers 3 \ --bind unix:/run/gunicorn.sock \ viktor_project.wsgi:application [Install] WantedBy=multi-user.target |
El código contiene múltiples secciones:
[Unit]:Esta sección especifica metadatos y dependencias. También describe que se inicie solo después de que se alcance el objetivo de red.[Service]:Esta sección especifica el usuario y el grupo bajo los cuales se ejecutará el proceso. La propiedad del grupo se establece en “www-data” para que Nginx pueda comunicarse con Gunicorn. También mapea los directorios de trabajo y especifica los comandos de inicio.[Install]:Esta sección le indica a systemd con qué vincular este servicio si está habilitado en el arranque. Debería iniciarse después de que el sistema multiusuario normal comience a funcionar.
A continuación, guarde el archivo y cierre el editor.
-
Habilitar el socket de Gunicorn
El socket de Gunicorn está listo para usarse. Por lo tanto, puede ejecutar los siguientes comandos. Esto iniciará y habilitará el socket. El archivo del socket se creará en /run/gunicorn.sock en el arranque. Cuando se realice una conexión al socket, systemd iniciará el servicio Gunicorn para manejarla:
|
1 2 |
sudo systemctl start gunicorn.socket sudo systemctl enable gunicorn.socket |
Verifique el estado del socket de Gunicorn:
|
1 |
sudo systemctl status gunicorn.socket |
Ahora, verifique la existencia del archivo del socket:
|
1 |
file /run/gunicorn.sock |
Si el estado de systemctl indica un error o no se encontró el archivo gunicorn.sock, esto indica que el socket no se creó correctamente. Revise el registro detallado para obtener pistas:
|
1 |
sudo journalctl -u gunicorn.socket |
No olvide volver a revisar el archivo gunicorn.socket en busca de posibles errores.
-
Activación del socket
Hemos iniciado el gunicorn.socket hasta ahora. Sin embargo, sin ninguna solicitud de conexión, gunicorn.service no se activará. A continuación, verifique el estado de Gunicorn:
|
1 |
sudo systemctl status gunicorn |
Podemos probar el mecanismo de activación del socket enviando una solicitud de conexión usando curl:
|
1 |
curl --unix-socket /run/gunicorn.sock localhost |
Debería obtener una salida HTML de la aplicación. Esto indica que Gunicorn se inició correctamente y pudo servir la aplicación Django. Verifique el estado actual del servicio Gunicorn:
|
1 |
sudo systemctl status gunicorn |
Si hay algún comportamiento o salida inesperada (que indique un error), revise los registros detallados para obtener pistas:
|
1 |
sudo journalctl -u gunicorn |
Si se realizaron cambios en el archivo gunicorn.service, entonces debe recargar el demonio para volver a leer la definición del servicio. También requiere reiniciar el servicio Gunicorn:
|
1 2 |
sudo systemctl daemon-reload sudo systemctl restart gunicorn |
Configuración de Nginx
Ahora, configuraremos Nginx para pasar el tráfico entrante al proceso. Primero, cree un nuevo bloque de servidor en Nginx:
|
1 |
sudo nano /etc/nginx/sites-available/viktor_project |
Luego, ingrese el siguiente código:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
server { listen 80; server_name 31.171.250.71; location = /favicon.ico { access_log off; log_not_found off; } location /static/ { root /home/cloudsigma/viktor_project; } location / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; } } |
Aquí hay múltiples bloques en la configuración:
servicio:Este bloque define que el servidor debe escuchar normalmente en el puerto 80 y debe responder al nombre de dominio o dirección IP del servidor.location:Esta es la primera entrada de ubicación. Define dónde encontrar los activos estáticos.location:Esta es la segunda entrada de ubicación. Este bloque define los parámetros estándar del proxy y cómo pasar el tráfico al socket de Gunicorn.
Guarde el archivo y cierre el editor. Vincule el archivo al directorio “sites-enabled” para activarlo:
|
1 |
sudo ln -s /etc/nginx/sites-available/viktor_project /etc/nginx/sites-enabled |
Después de eso, pruebe si hay algún error de sintaxis en la configuración de Nginx:
|
1 |
sudo nginx -t |
Si no encontró ningún error, entonces reinicie Nginx para implementar el cambio:
|
1 |
sudo systemctl restart nginx |
Necesitamos modificar las reglas de UFW nuevamente. Ya no necesitamos acceso al servidor de desarrollo, por lo que podemos eliminar la excepción para el puerto 8000. Además, queremos abrir el puerto 80 para el tráfico normal:
|
1 2 |
sudo ufw delete allow 8000 sudo ufw allow 'Nginx Full' |
Verifique estos cambios en las reglas del cortafuegos:
|
1 |
sudo ufw status |
El servidor ahora debería ser accesible desde un navegador web normal.
Procedimientos de resolución de problemas
Si todos los pasos se siguieron correctamente, la aplicación Django debería ser accesible a través de Internet. Si no es así, indica que la instalación no salió como se planeó. Necesitamos solucionar problemas para descubrir el origen del problema.
-
Nginx muestra la página predeterminada
Si Nginx muestra la página predeterminada en lugar del proxy de la aplicación, generalmente significa que el server_name se configuró incorrectamente en el bloque del servidor.
En este ejemplo, el bloque del servidor se almacena en la siguiente ubicación:
|
1 |
/etc/nginx/sites-available/viktor_project |
La server_name entrada determina qué bloque de servidor usará Nginx para responder a las solicitudes. Si muestra la página predeterminada, probablemente Nginx no pudo hacer coincidir la solicitud con un bloque de servidor explícito, por lo que está recurriendo al bloque predeterminado en su lugar:
|
1 |
/etc/nginx/sites-available/default |
Compruebe si el bloque de servidor de su proyecto Django tiene un server_name.
-
502 Bad Gateway
El error 502 indica que Nginx no pudo realizar el proxy de la solicitud con éxito. Existe una amplia gama de posibles problemas de configuración que pueden provocar el error 502, por lo que necesitamos pistas para solucionar el problema correctamente.
La fuente principal de pistas son los registros de errores de Nginx. Generalmente, sugerirá las condiciones que causaron los problemas durante el proxy. Verifique el registro de errores de Nginx usando el siguiente comando:
|
1 |
sudo tail -F /var/log/nginx/error.log |
Una vez que se abra el registro, intente acceder al servidor una vez más. Debería generar un nuevo mensaje de error en el registro. Esto puede ayudar a delimitar el problema. Aquí hay un par de mensajes posibles:
- connect() to unix:/run/gunicorn.sock failed (2: No such file or directory)
Indica que Nginx no pudo encontrar gunicorn.sock en la ubicación definida en la configuración. La ubicación está descrita por la directiva proxy_pass bajo el bloque del sitio. Compruebe si proxy_pass indica la ubicación correcta de gunicorn.sock generado por la unidad systemd gunicorn.socket:
|
1 |
/etc/nginx/sites-available/viktor_project |
Si gunicorn.sock no se encontró bajo el directorio /run, significa que systemd no pudo generarlo. Debe volver a verificar los pasos de configuración del archivo de socket de Gunicorn.
- connect() to unix:/run/gunicorn.sock failed (13: Permission denied)
Indica que Nginx no pudo conectarse al socket de Gunicorn debido a problemas de permisos. Puede ocurrir si el proceso se realizó como usuario root en lugar de un usuario sudo . Aunque systemd generó gunicorn.sock con éxito, Nginx no puede usarlo.
Un posible culpable puede ser los permisos limitados entre el directorio raíz (/) y el archivo gunicorn.sock . Verifique los permisos y la propiedad del archivo de socket y de cada uno de sus directorios principales:
|
1 |
namei -l /run/gunicorn.sock |
La primera columna describe los permisos del archivo. La segunda columna describe el usuario propietario y la tercera columna el grupo propietario. Si alguno de los directorios que conducen a gunicorn.sock no tiene los permisos de lectura y ejecución adecuados, Nginx no podrá acceder al socket.
-
Django muestra “could not connect to the server: Connection refused”
Indica que Django no pudo conectarse al servidor PostgreSQL. Asegúrese de que el servidor PostgreSQL esté funcionando:
|
1 |
sudo systemctl status postgresql |
Si no se está ejecutando, ejecute los siguientes comandos para iniciarlo y habilitarlo:
|
1 2 |
sudo systemctl start postgresql sudo systemctl enable postgresql |
Si sigue teniendo este error, asegúrese de que las credenciales de la base de datos estén correctamente definidas en settings.py:
|
1 |
~/viktor_project/viktor_project/settings.py |
Más resolución de problemas
Para una resolución de problemas adicional, existen varios registros configurados. Estos registros pueden ayudar a acotar el origen de los problemas.
Aquí tiene una lista de registros que pueden ayudar:
- Registros de Nginx
|
1 |
sudo journalctl -u nginx |
- Registros de acceso-Nginx
|
1 |
sudo less /var/log/nginx/access.log |
- Registros de errores-Nginx
|
1 |
sudo less /var/log/nginx/error.log |
- Registros de aplicación-Gunicorn
|
1 |
sudo journalctl -u gunicorn |
- Registros de socket-Gunicorn
|
1 |
sudo journalctl -u gunicorn.socket |
|
1 |
sudo systemctl restart gunicorn |
|
1 2 |
sudo systemctl daemon-reload sudo systemctl restart gunicorn.socket gunicorn.service |
|
1 2 |
sudo nginx -t sudo systemctl restart nginx |
Reflexiones finales
Esta guía demuestra con éxito cómo sentar las bases de Django. Django proporciona muchos de los componentes comunes de una aplicación web, lo que le permite concentrarse en los elementos únicos. El proyecto Django funcionará dentro del entorno virtual. Gunicorn gestiona la comunicación entre las solicitudes de los clientes y Django. Por último, Nginx actúa como un proxy inverso para manejar las conexiones de los clientes.
¡Feliz computación!










Comentarios
Aún no hay comentarios. Sea el primero.