programas

Symfony: creando, listando, editando y borrando datos de la BD

2018-08-22 - Categorías: Symfony
Symfony Flex, Symfony 4, Doctrine

De nuevo continuando con el post anterior sobre cómo trabajar modificaciones en la base de datos, vengo hoy con otro HOWTO para modificar los datos de nuestra aplicación web que tenemos en la BD. Es decir, no para modificar la estructura de la BD como en el post anterior, sino para crear, listar, modificar o borrar los datos con Doctrine. Para los nuevos, Doctrine es la herramienta que viene con Symfony Flex, Symfony 4 para trabajar la persistencia de los datos.

Así que, continuando con el ejemplo del post anterior..

Continuar leyendo..

Symfony: tutorial 4: el día a día con las bases de datos

2018-08-14 - Categorías: PHP / Symfony
Symfony4 Doctrine Migrations

Otra vez más traigo de nuevo otro HOWTO de la serie de tutoriales de iniciación a Symfony, esta vez trabajando día a día con bases de datos en Symfony. En el tutorial anterior empecé con cómo empezar a crear una base de datos. Ahora lo extiendo ya que trabajar con la base de datos es un tema muy extenso.

En este minitutorial me centro más en cómo modificar la base de datos, cómo es el día a día. Cómo desplegar estos cambios en producción sin temor a dejar sin servicio todo el sistema por haber hecho un cambio inesperado. Es importante que tengas un plan B por si lo último modificado rompe producción, y así puedas hechar atrás los últimos cambios de la base de datos.

Continuar leyendo..

Prolog: enlazando un sistema experto con PHP

2018-07-08 - Categorías: Inteligencia Artificial / PHP / Prolog
Prolog enlazando PHP

Hoy traigo cómo enlazar proyectos web en PHP con partes hechas en Prolog. No he encontrado casi información en Internet, así que aquí estoy compartiendo algo sobre esto. Este es un simple HOWTO para tratar las respuestas, o muchos de los casos que puedes tener en ataques a Prolog desde PHP.

La idea general es que tenemos uno o varios programas en ficheros .pl. Estos ficheros a su vez se pueden autoincluir unos en otros con sentencias del estilo include o import como en otros lenguajes. Además, también estos ficheros .pl se pueden generar automáticamente desde otras fuentes de datos. Esta dinamicidad de la parte programada en Prolog es una de las cosas más interesantes. Es decir, los programas en Prolog pueden cambiarse a si mismos, tema muy interesante en Inteligencia Artificial. Esto es, los programas en Prolog pueden aprender sobre la marcha, además de que, la base de conocimiento puede nutrirse de muchos datos externos también sobre la marcha.

Continuar leyendo..

Prolog: hola mundo, IA y la programación lógica

2018-07-05 - Categorías: Inteligencia Artificial / Prolog
Prolog hola mundo

Un lenguaje muy de moda en Inteligencia Artificial es Prolog. Se trata de un lenguaje de programación lógica, de ahí su nombre, que viene de PROgramación LOGica. En programación lógica dejamos de pensar tanto en el cómo se hace, para pensar más en qué es lo que tenemos que hacer. Es decir, una vez que cambias el chip es muy intuitivo leerlo, y comprender lo que expresan sus sentencias.

Se utiliza mucho para crear sistemas expertos o sistemas basados en conocimiento. Se trata de un lenguaje puente, o más bien un lenguaje al que se suele recurrir desde otros lenguajes para resolver ciertos problemas. De esta forma, se programan partes de los programas en otros lenguajes más conocidos, mientras que se lanzan estos programas lógicos para resolver cierto tipo de tareas.

Continuar leyendo..

Del código fuente a la ejecución de los programas

2018-06-08 - Categorías: General
Programming programando

Ya tenemos el código fuente, ¿y ahora? ¿cuál es la magia que hace posible que un código fuente se ejecute?

Todo programa necesita un traductor, que compile o interprete el código fuente para que pueda ser ejecutado. Es una primera diferencia que puede marcarse entre los lenguajes de programación, ya que un lenguaje puede ser interpretado, compilado, o incluso ambas cosas. Además, la ejecución puede ser dependiente de la máquina, o ejecutarse en una máquina virtual, que te independiza de la máquina.

Por ejemplo los lenguajes funcionales o lógicos suelen usarse primeramente interpretados, como Prolog o Haskell mientras se desarrolla, pero también tenemos disponible un compilador para mejorar su eficiencia. PHP es interpretado, aunque ha habido compiladores para mejorar su eficiencia de ejecución. Cabe destacar como curiosidad la Hip Hop Virtual Machine de Facebook que crearon para agilizar PHP, que precisamente compila los ficheros fuente a un código objeto intermedio más rápido de ejecutar. Javascript, Typescript, Ruby, también son interpretados. C, C++, VB, C# son compilados, es decir, se necesita compilar el código fuente a códigos objeto, estos códigos objeto a su vez se linkan, se retraducen, para ejecutarse finalmente en la máquina.

Continuar leyendo..

Symfony: tutorial 2: los comandos de consola

2018-05-31 - Categorías: PHP / Symfony

Symfony Flex comandos

Una respuesta a porqué Symfony es tan productivo son los comandos de consola. Con simples comandos podemos crear un proyecto nuevo, añadir nuevos componentes, lanzar un servidor de pruebas, chequear las URLs, crear entidades que vamos a guardar en base de datos, probar el envío de emails. También podemos modificar las tablas de datos, añadir nuevos elementos, editar los existentes, actualizar la base de datos, preparar nuevos elementos para almacenar, generar pantallas de listado de datos, ediciones de datos, borrados o creaciones. Generamos formularios automáticamente, con recepción de sus datos y guardado de dichos formularios en bases de datos. Podemos importar una base de datos de otro proyecto, generando todo el código fuente de una aplicación web que consulte los datos de dicha BD en apenas un rato. Podemos crear controladores, estructurando todas las zonas de la web, con sus plantillas, rutas, y URLs en poco tiempo. Y mucho más..

No tenemos que gastar tiempo en tediosas tareas repetitivas de construcción, y podremos centrarnos en lo especial de la aplicación web que estemos creando. Todas estas herramientas de generación de código las tenemos mediante comandos de consola. Cuantos más módulos añadas probablemente más comandos tendrás que te harán mucho más productivo. Te ahorrarán muchísimo tiempo, pero tienes que saber que estan ahí, saber cómo funcionan, y usarlos siempre que puedas. Hay que coger práctica con los comandos para aprovechar la productividad que te da Symfony. Continuar leyendo..


Características de los lenguajes de programación

2018-05-26 - Categorías: General
Código fuente

No es fácil elegir un lenguaje de programación para cada proyecto. Dependiendo de las características del proyecto, entorno, etc.. deberemos de elegir bien. Será más complejo aún, si además convivirán varios sistemas informáticos interconectados, comunicándose entre sí. De la misma forma, será más complejo si en el proyecto tenemos elementos programados a bajo y alto nivel. También se añade complejidad si los entornos de ejecución son varios: escritorio, web, móvil.. o varios sistemas operativos.

Es importante tener claras las características, y conocer, por lo menos en líneas generales, la mayor cantidad de lenguajes. Así elegiremos bien, y luego profundizaremos más y más a lo largo del proyecto, o subproyecto. Una mala decisión inicial del lenguaje, framework o plataforma, acarreará grandes problemas a la larga. Puede desembocar en grandes gastos y esfuerzos que se podrían haber evitado, o en una merma de la calidad final del proyecto.

Continuar leyendo..

PHP: usando CURL para visitar servidores web

2018-05-23 - Categorías: General / GNU/Linux / PHP
Websites

Continuando con el post anterior, traigo otro code-kata, o HOWTO, para hacer ping a una web, para empezar. Digo para empezar, porque esto no es más que el comienzo de una serie de acciones muy potentes sobre servidores web. Así podemos interactuar con un sitio web automática y remotamente. Este es uno de los mecanismos básicos de comunicación entre sistemas informáticos. Es decir, esto se puede reutilizar para que una web se comunique con otra web, con otro sistema informático, leyendo información o enviándola.

Para esto vamos a usar el gran CURL, que va muy bien para trabajar con el protocolo de las webs. Aunque como reza en su web, es compatible con muchos otros protocolos:

DICT, FILE, FTP, FTPS, Gopher, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, Telnet and TFTP. curl supports SSL certificates, HTTP POST, HTTP PUT, FTP uploading, HTTP form based upload, proxies, HTTP/2, cookies, user+password authentication (Basic, Plain, Digest, CRAM-MD5, NTLM, Negotiate and Kerberos), file transfer resume, proxy tunneling and more..

Esta forma de hacer un simple ping web se puede escalar todo lo que quieras. Es decir, de esta forma es como trabajan internamente los módulos de métodos de pago, envío/lectura de feeds, las APIs de los CMSs, por ejemplo. Las acciones que se ejecutan mediante la comunicación con APIs sobre HTTP/HTTPS. Y claro, cómo no, para las archiconocidas y tan queridas API REST sobre HTTP. Todas estas acciones tienen un origen (cliente) y un destino (servidor).

Así pues, vamos a complicar el ejemplo anterior..

Instalando CURL

Vamos a usar en este caso CURL, que es una herramienta del sistema operativo que podemos usar desde PHP. Si estás en GNU/Linux, puedes instalar tanto CURL como el módulo de PHP que lo usa así:

sudo apt-get install curl php7.2-curl

Si estás en Windows, Mac, o cualquier otro sistema operativo tendrás que ir a:

https://curl.haxx.se/download.html

..y seguir las instrucciones.

Primera petición web

Así un ping sencillo queda de la siguiente forma:

<?php

$curlHandler = curl_init('jnjsite.com');
curl_exec($curlHandler);

Si ejecutamos esto desde línea de comandos veremos algo tal que así:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://jnjsite.com/">here</a>.</p>
</body></html>

El contenido de la web se está pintando por pantalla: jnjsite.com te invita a redirigirte a https://jnjsite.com que es la versión segura de la web.

Viendo información de la respuesta

Para esto podemos modificar el script anterior y sacamos mucha información de la respuesta:

<?php

$curlHandler = curl_init('jnjsite.com');
$response = curl_exec($curlHandler);

var_dump(curl_getinfo($curlHandler));

curl_close($curlHandler);

Ejecutando esto desde línea de comandos veremos por pantalla mucha más información. En concreto el siguiente array:

array(26) {
‘url’ =>
string(19) «https://jnjsite.com/»
‘content_type’ =>
string(29) «text/html; charset=iso-8859-1»
‘http_code’ =>
int(301)
‘header_size’ =>
int(254)
‘request_size’ =>
int(50)
‘filetime’ =>
int(-1)
‘ssl_verify_result’ =>
int(0)
‘redirect_count’ =>
int(0)
‘total_time’ =>
double(0.131197)
‘namelookup_time’ =>
double(0.004185)
‘connect_time’ =>
double(0.067574)
‘pretransfer_time’ =>
double(0.067664)
‘size_upload’ =>
double(0)
‘size_download’ =>
double(228)
‘speed_download’ =>
double(1740)
‘speed_upload’ =>
double(0)
‘download_content_length’ =>
double(228)
‘upload_content_length’ =>
double(-1)
‘starttransfer_time’ =>
double(0.131118)
‘redirect_time’ =>
double(0)
‘redirect_url’ =>
string(20) «https://jnjsite.com/»
‘primary_ip’ =>
string(12) «52.31.12.158»
‘crtinfo’ =>
array(0) {}
‘primary_port’ =>
int(80)
‘local_ip’ =>
string(13) «192.168.0.221»
‘local_port’ =>
int(48976)
}

En este caso nos está pidiendo hacer una redirección 301 a la web con HTTPS. En teoría deberíamos de seguir esta redirección. Así que..

Siguiendo redirecciones

Modificando el script anterior, tenemos haciendo las redirecciones lo siguiente:

<?php

$curlHandler = curl_init('jnjsite.com');
$response = curl_exec($curlHandler);

while(curl_getinfo($curlHandler)['http_code'] >= 300
and curl_getinfo($curlHandler)['http_code'] < 400){
    // new URL to be redirected
    curl_setopt($curlHandler, CURLOPT_URL, curl_getinfo($curlHandler)['redirect_url']);
    $response = curl_exec($curlHandler);
}
var_dump(curl_getinfo($curlHandler));

curl_close($curlHandler);

Ejecutamos, y revisando por pantalla el resultado tendremos que finalmente se paran las redirecciones en https://jnjsite.com/ con un código de respuesta 200. He marcado en negrita lo nuevo con respecto al script anterior para redirigir hasta la página de destino.

Terminando

Para dejarlo limpio el script, sólo quedaría comprobar finalmente si la página ha dado un código OK de respuesta. Marco en negrita lo nuevo:

<?php

$curlHandler = curl_init('jnjsite.com');
curl_setopt($curlHandler, CURLOPT_RETURNTRANSFER, true);
curl_exec($curlHandler);

while (curl_getinfo($curlHandler)['http_code'] >= 300
and curl_getinfo($curlHandler)['http_code'] < 400) {
    // new URL to be redirected
    curl_setopt($curlHandler, CURLOPT_URL, curl_getinfo($curlHandler)['redirect_url']);
    curl_exec($curlHandler);
}

if (curl_getinfo($curlHandler)['http_code'] >= 200
and curl_getinfo($curlHandler)['http_code'] < 300) {
    echo 'OK'.PHP_EOL;
} else {
    echo 'KO'.PHP_EOL;
}

curl_close($curlHandler);

A partir de aquí sólo queda hacer las acciones que consideres si tienes un OK o un KO.


Symfony: tutorial 0: preparando el espacio de trabajo

2018-03-15 - Categorías: PHP / Symfony
Herramientas para desarrollar en Symfony

A partir de la versión 4, tenemos un Symfony orientado a microservicios, en la que te vas construyendo tu propio framework poco a poco, con los componentes que necesites. Esto quiere decir que no te instalas el framework completo cuando empiezas a trabajar, sino que poco a poco vas añadiendo los componentes.

Gracias a Composer vas acoplando sólo los componentes que necesitas, ya sean del propio Symfony, o sean componentes externos al framework. Esto hace a su vez que la curva de aprendizaje no sea tan pronunciada.

Hay aquí una serie de tutoriales de iniciación, para principiantes, y no tan principiantes, con referencias para que los más avanzados sigáis adelante..

Continuar leyendo..

GNU/Linux: bloqueando visitas por país

2018-02-21 - Categorías: GNU/Linux
Internet Firewall

Siguiendo con el post anterior de bloqueo de IPs usando el cortafuegos integrado en el núcleo de Linux, llegamos a la necesidad de por ejemplo bloquear a cierto país. Hay negocios, o páginas web, que han recibido quizás ataques automáticos desde IPs que residen en Rusia, Rumanía o China. Si cerramos dicha entrada al servidor nos evitaríamos muchos problemas.

Tengamos en cuenta que si nuestro servidor trabaja con servidores que residen en estos países, podremos encontrarnos con conexiones que no se hacen, no dan respuesta al conectar, denegación de conexión.. Es complicado si nos olvidamos de que hemos bloqueado, por ejemplo, a Rusia. Si luego resulta que contratamos un servicio ruso, cuyos servidores residen en Rusia, estaremos contratando un servicio que no nos va a poder conectar.

Obteniendo IPs registradas por país

El primera paso es tener un listado de IPs del país en concreto que queremos bloquear. Para esto tenemos varias páginas que nos los sirven sin cargo. Por ejemplo:

http://www.ip2location.com/free/visitor-blocker
https://www.countryipblocks.net/country_selection.php
https://www.nirsoft.net/countryip/
http://www.ipdeny.com/ipblocks/
..

Mejor coge los rangos de IPs del país que quieras bloquear usando el formato en CIDR. Es decir de la forma red/máscara, son así:

2.136.0.0/13
2.152.0.0/14

Tienes entonces que guardarte este listado de CIDRs de forma que tengas 1 CIDR por línea del fichero. Lo guardas en un fichero de texto, y se lo subes al servidor.

Metiendo el bloqueo en el firewall de Iptables

Para esto, desde un servidor GNU/Linux, bastará con ejecutar lo siguiente:

$ while read line; do sudo ufw deny from $line; done < IPsCountryCIDR.txt

Ahora hay que esperar mientras que se cargan todos los bloqueos en el cortafuegos. Tardará un buen rato porque UFW se encarga de transformar, línea a línea, al formato de Iptables.

Sólo nos quedará comprobar que está activo y con las reglas de bloqueo funcionando. Simulando una visita desde dicho país podremos comprobarlo.

Simulando visita desde cierto país

Para esto puedes usar el TorBrowser, del enrutamiento cebolla. Puedes descargártelo aquí:

https://www.torproject.org/

El navegador de la red Tor es un proyecto, que usando Firefox, hace que podamos navegar anónimamente. Esta anonimización la hace creando un camino de conexiones de varios nodos, desde nuestro PC, a la página destino. De esta manera podemos decirle al TorBrowser que queremos que el último nodo sea en un país concreto. Para esto abrimos el fichero este:

Browser/TorBrowser/Data/Tor/torrc

..y ponemos lo siguiente:

ExitNodes {es} 
StrictNodes 1

El código entre llaves es el código del nodo final. Con esta configuración podemos visitar y ver esto en el navegador:

Tor navegando desde España

Terminando

Esto es todo, si has seguido todos los pasos, debes de haber podido bloquear el acceso a cierto país y comprobarlo con el TorBrowser.


GNU/Linux: bloqueando visitas por IPs

2018-02-19 - Categorías: GNU/Linux
Internet Firewall

Cuando tenemos un servidor visible desde Internet, desde cualquier lugar del mundo, recibimos visitas constantemente. Esperamos que estas visitas siempre sean las deseadas, pero quizá no lo son. No sólo hay personas y ordenadores con buenas intenciones conectados a Internet. También puede incluso haber sistemas informáticos mal configurados, que involuntariamente, ocasionen problemas a tus sistemas informáticos.

Por ejemplo, nos podemos encontrar con casos como estos:

  • Una araña de un buscador, que escanea los contenidos de tu web con muy buenas intenciones, pero se engancha y no deja de escanearte llegando a bloquearte.
  • Registro de usuarios ficticios, a modo de SPAM.
  • Comentarios de SPAM con contenidos que enlazan a otras webs.
  • Una web que se copia tus contenidos tratando de posicionarse en buscadores antes que tú.
  • Y un largo etcétera.

Debemos de tener siempre nuestros servidores lo más actualizados posibles. Y sólo permitir la escucha en los puertos totalmente necesarios. Además de que sólo debemos de abrir los puertos para las IPs, o países que sean extrictamente necesarios.

Los escaneos automáticos en busca de vulnerabilidades en nuestros servidores va a ser constante, dalo por hecho. Si no lo haces, te arrepentirás más pronto que tarde, cuando la catástrofe haya acontecido. Así que los simples mortales como yo, que no somos expertos en ciberseguridad, debemos dejar trabajar a los que saben aplicando todas las actualizaciones lo antes posible, y sólo abriendo los puertos de escucha de los servicios estrictamente necesarios.

Cómo bloquear a estos visitantes

Ahora es cuando llegamos a Iptables. Iptables es un cortafuegos integrado en el núcleo de Linux. Es bastante engorroso de configurar así que han desarrollado utilizades que automatizan su configuración. Una utilidad muy interesante en GNU/Linux para manejar este cortafuegos es el UFW, que son las siglas de Uncomplicated FireWall.

Es bien sencillo de usar, por ejemplo para bloquear todas las conexiones que vengan de una IP basta con hacer lo siguiente:

$ sudo ufw deny from IP

También tenemos incluso una interfaz gráfica del UFW, es el llamado GUFW, que se ve tal que así si tienes escritorio gráfico en el servidor:

GUFW

Ahora bien, veamos los primeros pasos con este UFW desde línea de comandos. Ya que suponemos que estamos configurando un Servidor Ubuntu o basado en la distribución Debian.

Instalación

En Ubuntu 16 viene instalado por defecto. Si por lo que sea no lo tienes puedes hacer lo siguiente:

$ sudo apt-get install ufw

Para ver el estado en el que está podemos hacer:

$ sudo ufw status
Estado: inactivo

Perfecto, ya lo tenemos instalado, listo para activarlo.

Las configuraciones por defecto

Para hacer las primeras configuraciones tenemos el fichero /etc/default/ufw en donde tenemos estas dos configuraciones principales:

DEFAULT_INPUT_POLICY="DROP"
DEFAULT_OUTPUT_POLICY="ACCEPT"

Cuidado con esto, porque de estar configurando remotamente el cortafuegos, y no darte acceso a ti mismo, te puedes autodenegar el acceso y ya la hemos liado parda. El DEFAULT_INPUT_POLICY es la configuración de denegar por defecto las conexiones entrantes. Así que, podemos aceptar por defecto las configuraciones entrantes antes de activarlo si son las primeras pruebas que estamos haciendo.

Así que ponemos DEFAULT_INPUT_POLICY=»ACCEPT», o añadimos el permitir la entrada al puerto 22 para poder seguir trabajando remotamente. Si estas ya conectado a un servidor remoto, y quieres configurar esto, mejor dale una leída rápida a todo el post, para luego ir al grano de lo que necesites.

Algunas configuraciones

Así que vamos a arrancarlo:

$ sudo ufw enable
El cortafuegos está activo y habilitado en el arranque del sistema

Ahora vemos el estado de nuevo así:

$ sudo ufw status
Estado: activo

Ya lo tenemos en marcha. Vamos ahora con las configuraciones. Vamos a denegar el acceso a una IP:

$ sudo ufw deny from 1.2.3.4

Ahora vamos a aceptar todas las conexiones entrantes en los puertos 22, 80 y 443:

$ sudo ufw allow 22
$ sudo ufw allow 80
$ sudo ufw allow 443

Ok, ¿sencillo verdad? la verdad es que es mucho más sencillo que usar las tradicionales Iptables. Así que ahora listamos todo lo configurado:

$ sudo ufw status
Estado: activo

Hasta Acción Desde
----- ------ -----
Anywhere DENY 1.2.3.4
22 ALLOW Anywhere 
80 ALLOW Anywhere 
443 ALLOW Anywhere 
22 (v6) ALLOW Anywhere (v6) 
80 (v6) ALLOW Anywhere (v6) 
443 (v6) ALLOW Anywhere (v6)

Imagina que ahora queremos borrar una regla. Lo más sencillo es listar numeradas las reglas y borramos:

$ sudo ufw status numbered
Estado: activo

Hasta Acción Desde
 ----- ------ -----
[ 1] Anywhere DENY 1.2.3.4
[ 2] 22 ALLOW IN Anywhere 
[ 3] 80 ALLOW IN Anywhere 
[ 4] 443 ALLOW IN Anywhere 
[ 5] 22 (v6) ALLOW IN Anywhere (v6) 
[ 6] 80 (v6) ALLOW IN Anywhere (v6) 
[ 7] 443 (v6) ALLOW IN Anywhere (v6)

Ahora borramos la regla que permite conexiones entrantes al puerto 80 en el IPv4 así:

$ sudo ufw delete 3

Volvemos a listar para asegurarnos:

$ sudo ufw status numbered
Estado: activo

Hasta Acción Desde
 ----- ------ -----
[ 1] Anywhere DENY IN 1.2.3.4 
[ 2] 22 ALLOW IN Anywhere 
[ 3] 443 ALLOW IN Anywhere 
[ 4] 22 (v6) ALLOW IN Anywhere (v6) 
[ 5] 80 (v6) ALLOW IN Anywhere (v6) 
[ 6] 443 (v6) ALLOW IN Anywhere (v6)

Borramos ahora las conexiones al puerto 80 en IPv6:

$ sudo ufw delete 5

Cómo hubiera sido con Iptables

Sólo como curiosidad, si quieres ver la configuración tradicional mediante Iptables, ejecuta lo siguiente para ver la tonelada de configuraciones que te has ahorrado mantener:

$ sudo iptables --list

Terminando

Espero que sirva como introducción, ya que el siguiente paso es bloquear las visitas por país. Si la has liado con alguna de las configuraciones, o simplemente quieres resetear todo, ejecutas esto y todo limpio:

$ sudo ufw reset

PrEDA: esquemas de programación genéricos avanzados

2018-02-03 - Categorías: PHP / PrEDA
PrEDA algoritmos genericos

Otro HOWTO, code-kata, a traer vengo hoy..

XDD vengo con este post cargado de esquemas algorítmicos para programar. Estos esquemas algorítmicos son los que nos ayudan a programar las mejores soluciones. Así conseguimos llegar a las soluciones que a veces tenemos que encontrar, pero quizá no vemos en un principio cómo. Quizá el problema lo tenemos en que tarda mucho, o necesitamos demasiado espacio de almacenamiento.

Continuar leyendo..

© 2024 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.