<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog OpenAlfa</title>
	<atom:link href="http://blog.openalfa.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.openalfa.com</link>
	<description>Diario del proyecto OpenAlfa</description>
	<lastBuildDate>Sat, 25 May 2013 16:46:53 +0000</lastBuildDate>
	<language>es-ES</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Cómo ejecutar funciones callback en PHP</title>
		<link>http://blog.openalfa.com/como-ejecutar-funciones-callback-en-php/</link>
		<comments>http://blog.openalfa.com/como-ejecutar-funciones-callback-en-php/#comments</comments>
		<pubDate>Sat, 25 May 2013 16:15:51 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=4033</guid>
		<description><![CDATA[PHP ofrece la posibilidad de ejecutar una función a partir de una variable cuyo contenido es el nombre de la función. Una función que es ejecutada de esta forma se denomina función &#8220;callback&#8221;. En este artículo presentamos los detalles de la implementación de esta funcionalidad. Ejemplo de llamada a una función &#8220;callback&#8221; En el siguiente <a href='http://blog.openalfa.com/como-ejecutar-funciones-callback-en-php/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>PHP ofrece la posibilidad de ejecutar una función a partir de una variable cuyo contenido es el nombre de la función. Una función que es ejecutada de esta forma se denomina función &#8220;callback&#8221;. En este artículo presentamos los detalles de la implementación de esta funcionalidad.</p>
<p><span id="more-4033"></span></p>
<h2>Ejemplo de llamada a una función &#8220;callback&#8221;</h2>
<p>En el siguiente ejemplo se define una función &#8220;hola()&#8221; que imprime el texto &#8220;Hola, mundo!&#8221;.</p>
<p>A continuación se define una variable &#8220;$nombre_funcion&#8221; cuyo valor es el nombre de la función.</p>
<p>Despues se utiliza la función intrínseca &#8220;call_user_func()&#8221; para ejecutar la función &#8220;hola()&#8221; pasandole como argumento la variable que contiene el nombre de la función a ejecutar:</p>
<pre class="brush: php; gutter: false">&lt;?php

function hola() {
    echo &quot;Hola, mundo!\n&quot;;
}

$nombre_funcion = &quot;hola&quot;;

call_user_func($nombre_funcion);</pre>
<h2>Paso de argumentos a una función &#8220;callback&#8221;</h2>
<p>Para pasar argumentos a la función callback, basta como añadirlos como argumentos adicionales en la llamada a &#8220;call_user_func()&#8221;.</p>
<p>Ejemplo:</p>
<pre class="brush: php; gutter: false">function imprime_argumentos($argumento1, $argumento2) {
    echo &quot;El primer argumento es $argumento1, el segundo es $argumento2\n&quot;;
}

call_user_func(&quot;imprime_argumentos&quot;,&quot;Londres&quot;, &quot;New York&quot;);</pre>
<p>Si los argumentos que se desea pasar a la función callback están contenidos en un array, podemos llamar en su lugar a la función intrínseca &#8220;call_user_func_array()&#8221;, pasándole como segundo argumentos el array de argumentos a pasar a la función callback:</p>
<pre class="brush: php; gutter: false">function imprime_argumentos($argumento1, $argumento2) {
    echo &quot;El primer argumento es $argumento1, el segundo es $argumento2\n&quot;;
}
// Creamos un array con los argumentos
$argumentos = array(&quot;Londres&quot;,&quot;New York&quot;);
// Llamamos a call_user_func_array con el array de argumentos como segundo argumento
call_user_func_array(&quot;imprime_argumentos&quot;, $argumentos);</pre>
<h2>Otro tipo de callbacks: métodos de clase y métodos de objeto</h2>
<p>Además de una función, es posible ejecutar como callback un método estático de una clase.</p>
<p>Para ello, en la llamada a call_user_func() podemos pasar como primer argumento:</p>
<ul>
<li>un array que contiene el nombre de la clase y el nombre del método.</li>
<li>o bien un string de la forma NOMBRE_CLASE::NOMBRE_METODO.</li>
</ul>
<p>Ejemplo:</p>
<pre class="brush: php; gutter: false">class miclase {
    static function hola()
    {
        echo &quot;Hola mundo!\n&quot;;
    }
}

call_user_func(array(&quot;miclase&quot;, &#039;hola&#039;));
call_user_func(&quot;miclase::hola&quot;);

</pre>
<p>También podemos llamar a un método de una instancia de la clase, pasando como argumento a call_user_func() un array con el objeto y el nombre del método:</p>
<pre class="brush: php; gutter: false">// Creamos una instancia de la clase &quot;miclase&quot;
$mi_objeto = new miclase();
// Llamamos al método &quot;hola&quot; de la instancia
call_user_func(array($mi_objeto,&quot;hola&quot;));</pre>
<h4>Referencias:</h4>
<ul>
<li><a title="Documentación de la función call_user_func() de PHP" href="http://www.php.net/manual/es/function.call-user-func.php" target="_blank">Documentación de referencia de call_user_func()</a></li>
<li><a href="http://www.php.net/manual/es/language.types.callable.php" target="_blank">Documentación de referencia de llamadas de retorno en PHP</a></li>
</ul>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-ejecutar-funciones-callback-en-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo cambiar con javascript el vídeo en un video player</title>
		<link>http://blog.openalfa.com/como-cambiar-de-video-en-un-video-player-por-javascript/</link>
		<comments>http://blog.openalfa.com/como-cambiar-de-video-en-un-video-player-por-javascript/#comments</comments>
		<pubDate>Sat, 18 May 2013 19:06:44 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Javascript]]></category>
		<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3958</guid>
		<description><![CDATA[En un navegador con soporte para html5, es posible insertar un reproductor de vídeo utilizando el tag &#8220;&#60;video&#62;&#8221;. Además, podemos utilizar javascript para controlar el estado del reproductor. Esto incluye la posibilidad de cambiar el vídeo que está cargado, de manera que en nuestra página podemos incluir botones o enlaces para seleccionar mediante javascript el <a href='http://blog.openalfa.com/como-cambiar-de-video-en-un-video-player-por-javascript/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>En un navegador con soporte para html5, es posible insertar un reproductor de vídeo utilizando el tag &#8220;&lt;video&gt;&#8221;. Además, podemos utilizar javascript para controlar el estado del reproductor. Esto incluye la posibilidad de cambiar el vídeo que está cargado, de manera que en nuestra página podemos incluir botones o enlaces para seleccionar mediante javascript el vídeo que queremos reproducir, sin necesidad de recargar la página.</p>
<p>En este artículo utilizamos y explicamos el procedimiento presentado originalemente en <a href="http://jsfiddle.net/swinginsam/NWbUG/" target="_blank">jsfiddle.net</a>, para incluir esta funcionalidad en nuestra instalación de wordpress.</p>
<p><span id="more-3958"></span></p>
<h2>Resultado</h2>
<p>El resultado final que obtenemos es el siguiente:</p>
<div style="width: 690px; padding: 10px auto; margin: 10px auto;">
<div style="width: 511px; margin: 10px auto;">
	<!-- Begin Video.js -->
	<video id="mi_video" class="video-js vjs-default-skin" width="511" height="382" poster="/test-video/testvid_01.jpg" controls preload="auto" data-setup="{}">
		
		
		
	</video>
	<!-- End Video.js -->
</div>
<div style="margin: 10px auto; width: 350px;"><!--
<video id="mi_video" class="video-js vjs-default-skin" controls preload="auto" width="511" height="382" poster="/test-video/testvid_01.jpg" data-setup="{}">  
    <source src="" type="video/mp4" />
    <source src="" type="video/webm" />
    <source src="" type="video/ogg" />
</video>
-->
<div class="wrap">
  <input rel="01" type="button" value="cargar video 1">
  <input rel="02" type="button" value="cargar video 2">
  <input rel="03" type="button" value="cargar video 3">
</div>

<script type="text/javascript">
jQuery("input[type=button]").click(function() {
    var $target         = "testvid_"+jQuery(this).attr("rel");
    var $content_path   = "/test-video/";
    var $vid_obj        = _V_("mi_video");
    
    // hide the current loaded poster
    jQuery("img.vjs-poster").hide();

    $vid_obj.ready(function() {
      // and stop it from playing
      $vid_obj.pause();

      // assign the targeted videos to the source nodes
      jQuery("video:nth-child(1)").attr("src",$content_path+$target+".mp4");
      jQuery("video:nth-child(1)").attr("src",$content_path+$target+".ogv");
      jQuery("video:nth-child(1)").attr("src",$content_path+$target+".webm");

      // replace the poster source
      jQuery("img.vjs-poster").attr("src",$content_path+$target+".jpg").show();
      // reset the UI states
      jQuery(".vjs-big-play-button").show();

      // load the new sources
      $vid_obj.load();

    });
});

jQuery("input[rel='01']").click();

</script></div>
</div>
<p>Como vemos, hemos incluido un reproductor de vídeo en el artículo, y bajo el mismo hemos incluido tres botones que permiten seleccionar el vídeo que queremos reproducir.</p>
<h2>La librería javascript video.js</h2>
<p>El ejemplo de jsfiddle.net utiliza la librería &#8220;video.js&#8221; de <a href="http://www.videojs.com" target="_blank">www.videojs.com</a>.</p>
<p>En nuestro blog de wordpress, instalamos el plugin &#8220;<strong>Video.js &#8211; HTML5 Video Player for WordPress&#8221;</strong>, que nos permite utilizar dicha librería.</p>
<h2>Insertar el tag &#8220;&lt;video&gt;&#8221;</h2>
<p>En el ejemplo de jsfiddle.net se indica que para incluir el reproductor de vídeo en la página utilicemos un tag &#8220;&lt;video&gt;&#8221; de html5 de la forma:</p>
<pre class="brush: html; gutter: false">&lt;video id=&quot;mi_video&quot; class=&quot;video-js vjs-default-skin&quot; controls preload=&quot;auto&quot; 
       width=&quot;511&quot; height=&quot;382&quot; poster=&quot;/test-video/testvid_01.jpg&quot; data-setup=&quot;{}&quot;&gt;  
    &lt;source src=&quot;&quot; type=&quot;video/mp4&quot; /&gt;
    &lt;source src=&quot;&quot; type=&quot;video/webm&quot; /&gt;
    &lt;source src=&quot;&quot; type=&quot;video/ogg&quot; /&gt;
&lt;/video&gt;</pre>
<p>En wordpress, en lugar de insertar directamente el tag &#8220;&lt;video&gt;&#8221;, se utiliza un &#8220;shortcode&#8221; <code><span>[</span>video]</code>:<br />
<code><span>[</span>video mp4="" webm="" ogg="" id="mi_video" controls preload="auto" width="511" height="382" poster="/test-video/testvid_01.jpg" data-setup="{}"]</code></p>
<h2>El código HTML de los botones</h2>
<p>El código HTML que se utiliza para los botones es:</p>
<pre class="brush: html; gutter: false">&lt;div class=&quot;wrap&quot;&gt;
  &lt;input rel=&quot;01&quot; type=&quot;button&quot; value=&quot;cargar video 1&quot;&gt;
  &lt;input rel=&quot;02&quot; type=&quot;button&quot; value=&quot;cargar video 2&quot;&gt;
  &lt;input rel=&quot;03&quot; type=&quot;button&quot; value=&quot;cargar video 3&quot;&gt;
&lt;/div&gt;</pre>
<p>Como se puede ver, se añade un atributo &#8220;rel&#8221; a cada botón. Este atributo se utiliza en le código javascript para identificar el vídeo que se quiere cargar en el video player.</p>
<h2>El código javascript</h2>
<p>El código javascript que aparece en jsfiddle.net utiliza la librería jQuery.</p>
<p>Para que el código funcione sin problemas en nuestra instalación de wordpress, sustituimos la llamadas a jQuery que utilizan la sintaxis abreviada &#8220;$()&#8221; por la sintaxis standard &#8220;jQuery()&#8221;:</p>
<pre class="brush: javascript; gutter: false">jQuery(&quot;input[type=button]&quot;).click(function() {
    var $target         = &quot;testvid_&quot;+jQuery(this).attr(&quot;rel&quot;);
    var $content_path   = &quot;/test-video/&quot;;
    var $vid_obj        = _V_(&quot;mi_video&quot;);

    // oculta el poster actualmente cargado
    jQuery(&quot;img.vjs-poster&quot;).hide();

    $vid_obj.ready(function() {
      // Parar el vídeo que se estaba reproduciendo
      $vid_obj.pause();

      // asigna los nuevos vídeos a los nodos correspondientes del reproductor
      jQuery(&quot;video:nth-child(1)&quot;).attr(&quot;src&quot;,$content_path+$target+&quot;.mp4&quot;);
      jQuery(&quot;video:nth-child(1)&quot;).attr(&quot;src&quot;,$content_path+$target+&quot;.ogv&quot;);
      jQuery(&quot;video:nth-child(1)&quot;).attr(&quot;src&quot;,$content_path+$target+&quot;.webm&quot;);

      // sustituye la fuente del poster
      jQuery(&quot;img.vjs-poster&quot;).attr(&quot;src&quot;,$content_path+$target+&quot;.jpg&quot;).show();
      // reset the UI states
      jQuery(&quot;.vjs-big-play-button&quot;).show();

      // carga las nuevas fuentes
      $vid_obj.load();

    });
});

jQuery(&quot;input[rel=&#039;01&#039;]&quot;).click();</pre>
<p>como vemos, el código javascript define una función click() que es asignada a todos los elementos de la página del tipo &#8220;button&#8221;</p>
<p>La función comienza por identificar el video a cargar, utilizando para ello el valor del atributo &#8220;rel&#8221; del botón que ha sido pulsado.</p>
<p>A continuación, obtiene un objeto de tipo vídeo haciendo una llamada a la función &#8220;_V_()&#8221; definida en la librería video.js</p>
<p>Por último, asigna al objeto una función &#8220;ready()&#8221; que sustituye las fuentes del vídeo en los distintos formatos mp4, ogv y web, por las correspondientes al nuevo vídeo a reproducir.</p>
<p>Y eso es todo !</p>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-cambiar-de-video-en-un-video-player-por-javascript/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo crear un filesystem en un fichero en Linux</title>
		<link>http://blog.openalfa.com/como-crear-un-filesystem-en-un-fichero-en-linux/</link>
		<comments>http://blog.openalfa.com/como-crear-un-filesystem-en-un-fichero-en-linux/#comments</comments>
		<pubDate>Tue, 14 May 2013 17:53:29 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Admon. Servidores]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3937</guid>
		<description><![CDATA[Hay distintas razones por las que nos puede interesar una configuración en la que todo un filesystem virtual esté contenido en un único fichero. En este artículo vemos cómo obtener esta configuración en un sistema Linux Debian Squeeze. El procedimiento explicado es aplicable también a otras versiones Debian y otros sistemas Linux, (por ejemplo, Ubuntu). <a href='http://blog.openalfa.com/como-crear-un-filesystem-en-un-fichero-en-linux/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>Hay distintas razones por las que nos puede interesar una configuración en la que todo un filesystem virtual esté contenido en un único fichero. En este artículo vemos cómo obtener esta configuración en un sistema Linux Debian Squeeze. El procedimiento explicado es aplicable también a otras versiones Debian y otros sistemas Linux, (por ejemplo, Ubuntu).</p>
<p><span id="more-3937"></span></p>
<h2>Introducción</h2>
<p>Normalmente, un servidor linux consta de uno o varios discos físicos. Cada uno de ellos contiene una serie de particiones, y cada partición contiene un filesystem de determinado tipo (excluyendo las particiones dedicadas a swap).</p>
<p>En un sistema en producción, puede ocurrir que no tengamos posibilidad de modificar la configuración de discos, particiones y filesystems, por limitaciones impuestas por nuestro proveedor de hosting, u otras razones. Pero creando discos virtuales sobre ficheros, tenemos completa libertad para crear en ellos filesystems de última generación como zfs o btrfs, con funcionalidades avanzadas como encriptación, compresión de datos, etc.</p>
<p>Además, la portabilidad de un filesystem de este tipo a otro sistema es máxima, porque basta con copiar al sistema de destino el fichero que contiene el filesystem</p>
<h2>Crear el fichero contenedor</h2>
<h4>1. Creamos un fichero llamado &#8220;linux.ex2&#8243; de 1 GB:</h4>
<pre class="brush: bash; gutter: false">dd if=/dev/zero of=linux.ex2 bs=1024 count=1048576</pre>
<h4>2. Creamos el filesystem en el fichero con el comando mke2fs:</h4>
<p>Utilizando distintas opciones de mke2fs, podemos crear un filesystem con journaling, cambiar el número de inodos, etc:</p>
<pre class="brush: bash; gutter: false"># mke2fs -i 8192 -j linux.ex2
mke2fs 1.41.12 (17-May-2010)
linux.ex2 is not a block special device.
Proceed anyway? (y,n) y
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=0 blocks, Stripe width=0 blocks
131072 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
16384 inodes per group
Superblock backups stored on blocks:
        32768, 98304, 163840, 229376

Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 35 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.
sh-4.1#</pre>
<p>En el ejemplo, se han creado 8 grupos de bloques de 16384 inodos, para obtener un total de 131072 inodos. Además, la opción &#8220;-j&#8221; hace que se cree un filesystem con journaling.</p>
<h2>Montar el filesystem</h2>
<p>Para ello, utilizamos el mismo comando mount que se utiliza para montar filesystems creados en particiones de un disco físico, pasándole la opción &#8220;-o loop=/dev/loop0&#8243;:</p>
<pre class="brush: bash; gutter: false"># mount linux.ex2 /mnt -o loop=/dev/loop0</pre>
<h2>El dispositivo &#8216;loop&#8217;</h2>
<p>El dispositivo loop es el que permite que el sistema acceda a un fichero como si se tratase de un disco físico. En un sistema Debian, el módulo loop está normalmente compilado como parte del kernel. Si no es así, deberemos compilarlo a partir del código fuente.</p>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-crear-un-filesystem-en-un-fichero-en-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo montar una tienda internet con PrestaShop</title>
		<link>http://blog.openalfa.com/como-montar-una-tienda-internet-con-prestashop/</link>
		<comments>http://blog.openalfa.com/como-montar-una-tienda-internet-con-prestashop/#comments</comments>
		<pubDate>Tue, 14 May 2013 11:53:57 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Comercio electrónico]]></category>
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3916</guid>
		<description><![CDATA[En nuestro anterior artículo hicimos una breve presentación de Las 10 mejores plataformas gratuitas de comercio electrónico. En éste vamos a ver paso a paso cómo montar una tienda internet utilizando PrestaShop, una de las plataformas más extendidas. Prerequisitos El sistema en donde vamos a realizar la instalación es un servidor Linux que ya dispone de <a href='http://blog.openalfa.com/como-montar-una-tienda-internet-con-prestashop/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>En nuestro anterior artículo hicimos una breve presentación de <a title="Las 10 mejores plataformas gratuitas de comercio electrónico" href="http://blog.openalfa.com/las-1-mejores-plataformas-gratuitasde-comercio-electronico/">Las 10 mejores plataformas gratuitas de comercio electrónico.<br />
</a></p>
<p>En éste vamos a ver paso a paso cómo montar una tienda internet utilizando PrestaShop, una de las plataformas más extendidas.</p>
<p><span id="more-3916"></span></p>
<h2>Prerequisitos</h2>
<p>El sistema en donde vamos a realizar la instalación es un servidor Linux que ya dispone de una instalación de Apache, MySQL y PHP (normalmente conocido como LAMP, por las siglas de estas aplicaciones.</p>
<h2>Instalación</h2>
<p>Comenzamos por descargar de <a title="Sitio web de PrestaShop" href="http://www.prestashop.com" target="_blank">www.prestashop.com</a> el paquete de instalación de la versión 1.5.4. Al pulsar en el botón &#8220;Download&#8221; aparece un formulario en el que podemos seleccionar los idiomas a incluir en el paquete. En nuestro caso, seleccionamos &#8220;English&#8221; (por defecto), y Español. El fichero descargado se llama prestashop_1.5.4.1.zip, y ocupa 17 MB.</p>
<h4>Configuración del virtual host</h4>
<p>En el fichero de configuración &#8220;/etc/apache2/sites-enabled&#8221; de nuestro servidor Apache, creamos un fichero &#8220;tienda-prestashop&#8221; con la definición del VirtualHost que va a contener la instalación de PrestaShop:</p>
<pre class="brush: bash; gutter: false">&lt;VirtualHost *:80&gt;
ServerName tienda.openalfa.com
DocumentRoot &quot;/web/tienda&quot;
&lt;/VirtualHost&gt;</pre>
<h4>Instalación del sofware</h4>
<p>A continuación, creamos el directorio /web/tienda, y descomprimimos en él el contenido del fichero zip descargado. Al descomprimirlo, se crea un directorio &#8220;prestashop&#8221; y un fichero &#8220;Install_Prestashop.html&#8221;:</p>
<pre class="brush: bash; gutter: false">/web/tienda$ ls -l
total 17236
-rw-r--r--  1 root     root          308 Mar 26 18:10 Install_PrestaShop.html
drwxr-xr-x 24 root     root         4096 May 14 14:10 prestashop</pre>
<h4>Registro y configuración del dominio</h4>
<p>Si no disponemos de un dominio, debemos proceder a registrarlo en alguno de los registradores que trabajan con el tipo de dominio que deseamos (por ejemplo, euroDNS, goDaddy o Nominalia). Casi todos los registradores permiten registrar un dominio &#8220;.com&#8221;, pero no todos trabajan con dominios de un determinado país, como por ejemplo dominios &#8220;.es&#8221;.</p>
<p>También debemos añadir un registro de tipo &#8220;A&#8221; o &#8220;CN&#8221; a la configuración DNS de nuestro dominio, para que el nombre del subdominio &#8220;tienda.openalfa.com&#8221; se resuelva en la dirección IP de nuestro servidor.</p>
<h4>Creación de la base de datos</h4>
<p>Por último, debemos crear una base de datos para contener la información de nuestra tienda. Para ello, se puede utilizar phpMyAdmin si lo tenemos instalado en nuestro servidor. En caso contrario, podemos hacerlo directamente desde la línea de comandos:</p>
<pre class="brush: sql; gutter: false">$ mysql -u root -pCLAVE
mysql&gt; create database tienda;
Query OK, 1 row affected (0.02 sec)

mysql&gt;</pre>
<h2>Configuración de prestashop</h2>
<p>Con esto, ya podemos apuntar nuestro navegador a la url &#8220;http://tienda.openalfa.com/prestashop/install&#8221;. La primera página que aparece nos permite seleccionar el idioma en el que queremos realizar la instalación. Seleccionamos &#8220;Español&#8221;, y pulsamos &#8220;Siguiente&#8221;:</p>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-1.jpg"><img class="aligncenter size-full wp-image-3922" alt="Instalación de PrestaShop. 1. Selección de idioma" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-1.jpg" width="690" height="501" /></a></p>
<p>La siguiente pantalla solicita que aceptemos la licencia de uso del software.</p>
<p>A continuación aparece una pantalla en la que se presenta el resultado de la comprobación de la compatibilidad del sistema. En nuestro caso, la pantalla indica que:</p>
<ul>
<li>Hay un problema con los permisos de usuario.</li>
<li>La extensión Mcrypt de PHP no está habilitada.</li>
</ul>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-2.jpg"><img class="aligncenter size-full wp-image-3925" alt="Instalación PrestaShop: 2. Comprobación de compatibilidad del sistema" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-2.jpg" width="690" height="703" /></a></p>
<p>Para solucionar el primer problema, ponemos el usuario con el que se ejecuta apache como propietario de todos los ficheros de la instalación:</p>
<pre class="brush: bash; gutter: false">$ sudo chown -R www-data:www-data prestashop</pre>
<p>El segundo problema se soluciona instalando el paquete que contiene la extensión Mcrypt de PHP. en un sistema Debian o Ubuntu, esto se hace fácilmente con la utilidad apt-get:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-get install php5-mcrypt
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following extra packages will be installed:
  libmcrypt4
...
$</pre>
<p>y a continuación reiniciamos el servidor web:</p>
<pre class="brush: bash; gutter: false">$ sudo /etc/init.d/apache2 restart</pre>
<p>Con esto, al hacer click sobre &#8220;Actualizar esta información&#8221; ya no aparece ningún error, y podemos hacer click en &#8220;Siguiente&#8221; para continuar.</p>
<p>La siguiente pantalla contiene el formulario en donde introducimos la información de conexión a la base de datos que hemos creado previamente. Normalmente, sólo tenemos que introducir el nombre de la base de datos (en nuestro caso, &#8220;tienda&#8221;)  y la contraseña de la misma, y dejar los demás campos con sus valores por defecto:</p>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-3.jpg"><img class="aligncenter size-full wp-image-3929" alt="Instalación Prestashop. 3: Configuración de la base de datos" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-3.jpg" width="690" height="592" /></a></p>
<p>El primer bloque del formulario que aparece en la siguiente pantalla nos permite darle un nombre a la tienda, establecer la actividad principal, asignarle un logo, etc. En el segundo bloque, establecemos el usuario, dirección de correo y contraseña de acceso como administrador de la tienda.</p>
<p>Al pulsar en &#8220;Siguiente&#8221;, el script de instalación procede a crear las tablas de la base de datos y cargarlas con datos de demo.</p>
<p>Cuando nos indica que ya ha finalizado, ya podemos acceder a la url de inicio de la tienda para ver el aspecto que tiene:</p>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-4.jpg"><img class="aligncenter size-full wp-image-3932" alt="Instalación PrestaShop: 4. Página de Inicio" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-4.jpg" width="690" height="493" /></a></p>
<p>Por último, sólo nos queda eliminar el directorio &#8220;install&#8221; y cambiar de nombre al directorio &#8220;admin&#8221; para poder acceder al interfaz de administración de la tienda:</p>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-5.jpg"><img class="aligncenter size-full wp-image-3934" alt="Prestashop. Cuadro de Mandos" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/05/instalacion-prestashop-5.jpg" width="690" height="339" /></a></p>
<p>Y con esto ya tenemos nuestra instalación de PrestaShop plenamente operativa!</p>
<p>En el siguiente artículo de esta serie examinaremos la tareas que todavía nos quedan pendientes antes de tener una tienda &#8220;de verdad&#8221;:</p>
<p>- Configuración y mantenimiento del catálogo de productos</p>
<p>- Configuración de medios de pago</p>
<p>- Gestión de inventario</p>
<p>- etc.</p>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-montar-una-tienda-internet-con-prestashop/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo insertar un vídeo/audio con autoplay en wordpress.com</title>
		<link>http://blog.openalfa.com/como-insertar-un-videoaudio-con-autoplay-en-wordpress-com/</link>
		<comments>http://blog.openalfa.com/como-insertar-un-videoaudio-con-autoplay-en-wordpress-com/#comments</comments>
		<pubDate>Tue, 14 May 2013 07:09:15 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[WordPress]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3910</guid>
		<description><![CDATA[En este artículo explicamos la manera de insertar en un blog gratuito de wordpress.com un vídeo o audio de youtube, de manera que comience a reproducirse al visitar la página, sin necesidad de que el usuario haga click en el botón de &#8220;play&#8221;. Shortcodes documentados para la inserción de audio y vídeo En la documentación <a href='http://blog.openalfa.com/como-insertar-un-videoaudio-con-autoplay-en-wordpress-com/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>En este artículo explicamos la manera de insertar en un blog gratuito de wordpress.com un vídeo o audio de youtube, de manera que comience a reproducirse al visitar la página, sin necesidad de que el usuario haga click en el botón de &#8220;play&#8221;.</p>
<p><span id="more-3910"></span></p>
<h2>Shortcodes documentados para la inserción de audio y vídeo</h2>
<p>En la <a title="Shortcodes disponibles en wordpress para insertar audio y vídeo" href="http://jetpack.me/support/shortcode-embeds/" target="_blank">documentación de jetpack</a> hay una relación de los &#8220;shortcodes&#8221; disponibles en un blog de wordpress.com para la inserción de referencias a vídeo o audio existente en distintos servicios (flickr, vimeo, youtube).</p>
<p>El problema es que el shortcode documentado para la inserción de vídeo de youtube no permite especificar la opción &#8220;autostart&#8221;, para que comience a reproducirse de manera automática.</p>
<h2>Shortcode gigya</h2>
<p>La solución para conseguir un vídeo que se reproduzca automáticamente es utilizar el shortcode &#8220;gigya&#8221;. Este shortcode también está disponible en un blog gratuito de wordpress.com, aunque no se hace referencia al mismo en la documentación oficial.</p>
<h2>Inserción de un vídeo de youtube con autoplay</h2>
<p>Supongamos que hemos localizado un vídeo en youtube que queremos insertar en nuestro blog. En la barra de direcciones del navegador, tomamos nota de la url que aparece, por ejemplo:</p>
<pre class="brush: bash; gutter: false">http://www.youtube.com/watch?v=1SuXOnBmDm8</pre>
<p>En esta url, el valor <code>1SuXOnBmDm8</code> es el identificador del vídeo.<br />
Ahora, insertamos en nuestro blog el shortcode gigya de la siguiente forma:</p>
<pre class="brush: bash; gutter: false">[gigya src=&quot;http://www.youtube.com/v/VIDEO-ID&quot; flashvars=&quot;autoplay=1&quot; 
      type=&quot;application/x-shockwave-flash&quot; allowscriptaccess=&quot;always&quot;
      allowfullscreen=&quot;true&quot;
      width=&quot;425&quot; height=&quot;350&quot;]</pre>
<p>En donde simplemente sustituimos VIDEO-ID por el identificador del vídeo que hemos anotado más arriba.</p>
<p>Como vemos, en el shortcode hemos incluido la variable &#8220;autoplay=1&#8243;, con la cual el vídeo comienza a ejecutarse automáticamente.</p>
<p>&nbsp;</p>
<h2>Inserción de música de fondo</h2>
<p>De la misma forma, podemos incluir un vídeo de youtube que sólo contenga música, para que se escuche como música de fondo cuando visitamos una página.</p>
<p>Para evitar que se vean en pantalla los controles del reproductor, podemos editar la página en el editor HTML, para hacer invisible el shortcode poniéndolo en el interior de un elemento &lt;div&gt; con el adecuado estilo CSS:</p>
<pre>&lt;div style=&quot;display:none&quot;&gt;
    [gigya src=&quot;http://www.youtube.com/v/VIDEO-ID&quot; flashvars=&quot;autoplay=1&quot; 
                                   type=&quot;application/x-shockwave-flash&quot; width=&quot;425&quot; height=&quot;350&quot;]
&lt;/div&gt;</pre>
<p>Para que la música se reproduzca en todas las páginas del blog, en lugar de poner el shortcode en cada página, podemos ponerlo en el interior de un widget de tipo &#8220;Texto o HTML arbitrario&#8221;, en la barra lateral.</p>
<p>Referencias:</p>
<ul>
<li><a href="http://jetpack.me/support/shortcode-embeds/">http://jetpack.me/support/shortcode-embeds/</a></li>
<li><a href="http://en.forums.wordpress.com/topic/enable-youtube-autoplay-for-wordpresscom-a-workaround" target="_blank">Enable Youtube autoplay for wordpress.com &#8211; a workaround</a></li>
<li><a href="http://wpbtips.wordpress.com/category/gigya/">http://wpbtips.wordpress.com/category/gigya/</a></li>
<li><a href="http://wpbtips.wordpress.com/2010/07/03/gigya-shortcode-1-videos/" target="_blank">The gigya shortcode 1 – inserting videos</a></li>
</ul>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-insertar-un-videoaudio-con-autoplay-en-wordpress-com/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Diferencias entre InnoDB y MyISAM en MySQL</title>
		<link>http://blog.openalfa.com/diferencias-entre-innodb-y-myisam-en-mysql/</link>
		<comments>http://blog.openalfa.com/diferencias-entre-innodb-y-myisam-en-mysql/#comments</comments>
		<pubDate>Sun, 12 May 2013 12:47:28 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3900</guid>
		<description><![CDATA[Una de las decisiones que tiene que tomar el administrador de bases de datos cuando diseña una base de datos para ser implementada en MySQL, es el tipo de motor de almacenamiento que utiliza para cada tabla. Las dos alternativas principales son InnoDB y MyISAM. En este artículo comentamos las ventajas e inconvenientes de cada uno <a href='http://blog.openalfa.com/diferencias-entre-innodb-y-myisam-en-mysql/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>Una de las decisiones que tiene que tomar el administrador de bases de datos cuando diseña una base de datos para ser implementada en MySQL, es el tipo de motor de almacenamiento que utiliza para cada tabla. Las dos alternativas principales son InnoDB y MyISAM. En este artículo comentamos las ventajas e inconvenientes de cada uno de ellos.</p>
<p><span id="more-3900"></span></p>
<h2>Diferencias entre InnoDB y MyISAM</h2>
<p>InnoDB es actualmente el motor de almacenamiento por defecto en las últimas versiones de MySQL. Las funcionalidades que ofrece son más adecuadas que las de MyISAM en general, pero en casos particulares puede ser preferible utilizar este último para aprovechar algunas de sus características.</p>
<h4>Funcionalidades específicas de InnoDB</h4>
<ul>
<li>transacciones ACID</li>
<li>bloqueo a nivel de registro (MyISAM sólo ofrece bloqueo a nivel de tabla)</li>
<li>restricciones de clave externa (foreign key constraints)</li>
<li>recuperación automática en caso de crash</li>
<li>compresión de tablas con posibilidad de lectura/escritura</li>
<li>tipos de datos espaciales(pero no índices espaciales)</li>
<li>Los datos son guardados en páginas en orden de clave primaria</li>
</ul>
<h4>Funcionalidades específicas de MyISAM</h4>
<ul>
<li>ejecución rápida de COUNT(*)s (cuando no se utilizan WHERE, GROUP BY, o JOIN)</li>
<li>indexación &#8220;full text&#8221;</li>
<li>ocupan menos espacio en disco que las tablas InnoDB</li>
<li>compresión elevada de tablas, pero las tablas comprimidas son de sólo lectura</li>
<li>tipos de datos e índices espaciales (R-tree)</li>
</ul>
<p>A continuación detallamos en qué consiste cada una de estas funcionalidades. Con ello, el DBA puede tomar una decisión adecuada sobre el tipo de tabla que conviene en cada caso.</p>
<h2>Transacciones ACID</h2>
<p>ACID son las siglas en inglés de las propiedades ( (Atomicity, Consistency, Isolation, Durability) que debe tener una transacción para ser fiable.</p>
<p>El cumplimiento de estas propiedades realizar un conjunto de operaciones sucesivas sobre los datos, y validarlas todas ellas como un bloque mediante una sentencia COMMIT, o bien deshacerlas con una sentencia ROLLBACK, volviendo a dejar el contenido de la tabla como estaba.</p>
<h2>Bloqueo a nivel de registro y bloqueo a nivel de tabla</h2>
<p>En InnoDB, si varios usuarios o aplicaciones intentan acceder simultáneamente a la información contenida en una tabla para modificarla, el bloqueo a nivel de registro hace que sólo deban quedar a la espera aquellos que intentan modificar los datos de un mismo registro. En MyISAM, cualquier proceso que intenta modificar la información de una tabla queda a la espera hasta que no hay ningún otro proceso accediendo a la misma.</p>
<h2>Restricciones de clave externa</h2>
<p>A menudo, los datos de una tabla están relacionados con los de otras. Las restricciones de clave externa aseguran la integridad de estas relaciones.</p>
<p>Esta integridad referencial está implementada en el motor de almacenamiento InnoDB, pero no en MyISAM</p>
<p>Ejemplo:</p>
<p>En una aplicación de comercio electrónico podemos tener una tabla &#8216;clientes&#8217;, otra tabla &#8216;productos&#8217; y una tercera tabla &#8216;pedidos&#8217;. Para cada cliente, hay un registro en la tabla &#8216;clientes&#8217;. En esta tabla hay un campo &#8216;id_cliente&#8217; que identifica al cliente de forma única, y que constituye la clave primaria de la misma.</p>
<p>De la misma forma, en la tabla &#8216;productos&#8217; hay un campo &#8216;id_producto&#8217; que identifica cada producto de forma única, y es la clave primaria de dicha tabla.</p>
<p>Por último, la tabla &#8216;pedidos&#8217; guarda una relación de los productos que han sido solicitados por cada cliente. Para ello incluye, entre otros, un campo &#8216;id_cliente&#8217; y un campo &#8216;id_producto&#8217; .</p>
<p>Para que los datos sean consistentes, no debe ser posible insertar en la tabla &#8216;pedidos&#8217; un id de cliente que no exista en la tabla de clientes, o un id de producto que no exista en la tabla de productos. Esto lo podemos conseguir definiendo dos claves externas en la tabla de pedidos:</p>
<pre class="brush: sql; gutter: false">CREATE TABLE pedidos (
    id_pedido int NOT NULL primary key,
    id_cliente integer NOT NULL,
    id_producto integer not null,
    FOREIGN KEY (id_cliente) REFERENCES clientes(id_cliente),
    FOREIGN KEY (id_producto) REFERENCES productos(id_producto)
) ENGINE=InnoDB;</pre>
<p><strong><em>Notas:</em></strong></p>
<ol>
<li><em><em>Para crear las claves externas, las tablas referenciadas también deben utilizar el motor de almacenamiento InnoDB.</em></em></li>
</ol>
<h2>Recuperación en caso de caída</h2>
<p>Si el servidor sufre una avería, o por alguna otra razón finaliza abruptamente la ejecución de mysql, es necesario recuperar las tablas MyISAM mediante una reparación completa de las tablas e índices. Si las tablas contienen un gran volumen de datos, el tiempo necesario para recuperar las tablas puede ser muy elevado.</p>
<p>Por el contrario, las tablas InnoDB  se recuperan automáticamente volviendo a ejecutar las transacciones grabadas en el fichero de log de transacciones, haciendo rollback de las que estén incompletas.</p>
<h2>Compresión de tablas</h2>
<p>InnoDB permite trabajar con tablas comprimidas. Aunque la compresión que se obtiene no es muy grande, sigue siendo posible modificar el contenido con sentencias INSERT, UPDATE y DELETE.</p>
<p>Permite realizar (con myisampack) una compresión mucho mayor, pero a cambio las tablas comprimidas pasan a ser de sólo lectura.</p>
<h2> Tipos de datos espaciales</h2>
<p>En MySQL, se pueden definir columna de una tabla que contengan tipos de datos geométricos.</p>
<p>Las columnas cuyo tipo de datos se define como POINT, LINESTRING y POLYGON pueden contener datos del tipo indicado. Por otra parte, también se puede utiliizar el tipo de datos genérico GEOMETRY para definir una columna que puede contener datos de cualquiera de los tipos anteriormente indicados.</p>
<p>También existen los correspondientes tipos de datos para contener colecciones de datos geométricos: MULTIPOINT, MULTILINESTRING, MULTIPOLYGON y GEOMETRYCOLLECTION.</p>
<p>Tanto InnoDB como MySQL soportan estos tipos de datos. Pero mientras que en InnoDB no es posible definir índices que utilizen columnas con este tipo de datos, en MyISAM sí es posible.</p>
<h2>Indexación &#8220;Full Text&#8221;</h2>
<p>A partir de la versión 5.6 de MySQL, es posible crear índices &#8220;Full Text&#8221; tanto en tablas MyISAM como InnoDB. Sin embargo, si la versión de MySQL con la que se está trabajando es 5.5 o anterior, este tipo de índices no está soportado en tablas InnoDB.</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h4>Referencias:</h4>
<ul>
<li>Innodb Storage Engine: <a title="http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html" href="http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html" rel="nofollow">http://dev.mysql.com/doc/refman/5.0/en/innodb-storage-engine.html</a></li>
<li>MyISAM Storage Engine: <a title="http://dev.mysql.com/doc/refman/5.0/en/myisam-storage-engine.html" href="http://dev.mysql.com/doc/refman/5.0/en/myisam-storage-engine.html" rel="nofollow">http://dev.mysql.com/doc/refman/5.0/en/myisam-storage-engine.html</a></li>
</ul>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/diferencias-entre-innodb-y-myisam-en-mysql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo incluir un área de navegación con pestañas en una página HTML</title>
		<link>http://blog.openalfa.com/como-incluir-un-area-de-navegacion-con-pestanas-en-una-pagina-html/</link>
		<comments>http://blog.openalfa.com/como-incluir-un-area-de-navegacion-con-pestanas-en-una-pagina-html/#comments</comments>
		<pubDate>Wed, 08 May 2013 08:35:30 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Miscelánea]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3885</guid>
		<description><![CDATA[En este artículo se presenta un procedimiento basado en jQuery para crear un área con una serie de pestañas en la parte superior, de modo que cuando se selecciona una pestaña, se presenta en el área el contenido correspondiente a la misma. El resultado final Con el procedimiento explicado en este artículo, obtenemos el siguiente <a href='http://blog.openalfa.com/como-incluir-un-area-de-navegacion-con-pestanas-en-una-pagina-html/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>En este artículo se presenta un procedimiento basado en jQuery para crear un área con una serie de pestañas en la parte superior, de modo que cuando se selecciona una pestaña, se presenta en el área el contenido correspondiente a la misma.</p>
<p><span id="more-3885"></span></p>
<h2>El resultado final</h2>
<p>Con el procedimiento explicado en este artículo, obtenemos el siguiente resultado:</p>
<style>

#ejemplo .list-wrap { background: #eee; padding: 10px; margin: 0 0 15px 0; }

#ejemplo ul { list-style: none; }
#ejemplo ul li a { display: block; border-bottom: 1px solid #666; padding: 4px; color: #666; }
#ejemplo ul li a:hover { background: #333; color: white; }
#ejemplo ul li:last-child a { border: none; }

#ejemplo .nav { overflow: hidden; margin: 0.5em 0 0 1.5em; }
#ejemplo .nav li { width: 97px; float: left; margin: 0 10px 0 0; }
#ejemplo .nav li.last { margin-right: 0; }
#ejemplo .nav li a { display: block; padding: 5px; background: #666; color: white; font-size: 14px; font-weight: bold; text-align: center; border: 0; }

#ejemplo li a.current,#ejemplo li a.current:hover { background-color: #eee !important; color: black; }
#ejemplo .nav li a:hover, #ejemplo .nav li a:focus { background: #999;}

.oculto { position: absolute; top: -9999px; left: -9999px; }
</style>

<div id="ejemplo">
<ul class="nav">
    <li class="nav-one"><a href="#contenido1" class="current">Pestaña 1</a></li>
    <li class="nav-two"><a href="#contenido2">Pestaña 2</a></li>
    <li class="nav-three"><a href="#contenido3">Pestaña 3</a></li>
</ul>
<div class="list-wrap">
    <div id="contenido1">
        Este es el contenido uno
    </div>
    <div id="contenido2" class="oculto">
        Este es el contenido dos<br/>
        Este bloque ocupa<br/>
       más de una línea<br/>
    </div>
    <div id="contenido3" class="oculto">
        Este es el contenido tres
    </div>
</div>
</div>


<script type="text/javascript">

(function($) {

    $.organicTabs = function(el, options) {
    
        var base = this;
        base.$el = $(el);
        base.$nav = base.$el.find(".nav");
                
        base.init = function() {
            base.options = $.extend({},$.organicTabs.defaultOptions, options);
            $(".oculto").css({
                "position": "relative",
                "top": 0,
                "left": 0,
                "display": "none"
            }); 
            
            base.$nav.delegate("li > a", "click", function() {
                var curList = base.$el.find("a.current").attr("href").substring(1),
                    $newList = $(this),
                    listID = $newList.attr("href").substring(1),
                    $allListWrap = base.$el.find(".list-wrap"),
                    curListHeight = $allListWrap.height();
                $allListWrap.height(curListHeight);
                                        
                if ((listID != curList) && ( base.$el.find(":animated").length == 0)) {
                    base.$el.find("#"+curList).fadeOut(base.options.speed, function() {
                        base.$el.find("#"+listID).fadeIn(base.options.speed);
                        var newHeight = base.$el.find("#"+listID).height();
                        $allListWrap.animate({
                            height: newHeight
                        });
                        
                        base.$el.find(".nav li a").removeClass("current");
                        $newList.addClass("current");
                    });
                }   
                return false;
            });
            
        };
        base.init();
    };
    
    $.organicTabs.defaultOptions = {
        "speed": 300
    };
    
    $.fn.organicTabs = function(options) {
        return this.each(function() {
            (new $.organicTabs(this, options));
        });
    };
    
})(jQuery);

jQuery("#ejemplo").organicTabs();
</script>
<h2>El código HTML</h2>
<p>En primer lugar, utilizamos los tags &#8220;&lt;ul&gt;&#8221; y &#8220;&lt;li&gt;&#8221; para crear una lista con los títulos de las pestañas.</p>
<p>Cada título es un enlace al correspondiente contenido.</p>
<pre class="brush: bash; gutter: false">        &lt;ul class=&quot;nav&quot;&gt;
            &lt;li class=&quot;nav-one&quot;&gt;&lt;a href=&quot;#contenido1&quot; class=&quot;current&quot;&gt;Pestaña 1&lt;/a&gt;&lt;/li&gt;
            &lt;li class=&quot;nav-two&quot;&gt;&lt;a href=&quot;#contenido2&quot;&gt;Pestaña 2&lt;/a&gt;&lt;/li&gt;
            &lt;li class=&quot;nav-three&quot;&gt;&lt;a href=&quot;#contenido3&quot;&gt;Pestaña 3&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;</pre>
<p>A continuación ponemos los contenidos, en forma de elementos &lt;div&gt; o de otro tipo.</p>
<p>Todos los contenidos se incluyen dentro de un elemento &lt;div&gt; con id &#8220;list-wrap&#8221;.</p>
<p>Cada contenido tiene un identificador único indicado por la etiqueta &#8220;id&#8221;. Además, asignamos la clase css &#8220;oculto&#8221; a todos los contenidos excepto el primero</p>
<pre class="brush: bash; gutter: false">    &lt;div class=&quot;ejemplo&quot;&gt;
        &lt;div class=&quot;list-wrap&quot;&gt;
            &lt;div id=&quot;contenido1&quot;&gt;
                Este es el contenido uno
            &lt;/div&gt;
            &lt;div id=&quot;contenido2&quot; class=&quot;oculto&quot;&gt;
                Este es el contenido dos
            &lt;/div&gt;
            &lt;div id=&quot;contenido3&quot; class=&quot;oculto&quot;&gt;
                Este es el contenido tres
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;</pre>
<h2>El código CSS</h2>
<p>En el código CSS añadimos la definición de la clase &#8220;oculto&#8221;, para que los contenidos no seleccionados estén fuera del área visible en pantalla:</p>
<pre class="brush: css; gutter: false">.oculto { position: absolute; top: -9999px; left: -9999px; }</pre>
<p>Por lo demás, podemos utilizar el código CSS que queramos, para adaptar el estilo del área de contenido al estilo general de la página.</p>
<h2>El código Javascript</h2>
<p>El plugin jQuery a incluir es:</p>
<pre class="brush: javascript; gutter: false">(function($) {
    $.organicTabs = function(el, options) {
        var base = this;
        base.$el = $(el);
        base.$nav = base.$el.find(&quot;.nav&quot;);

        base.init = function() {
            base.options = $.extend({},$.organicTabs.defaultOptions, options);
            // Accessible hiding fix
            $(&quot;.oculto&quot;).css({
                &quot;position&quot;: &quot;relative&quot;,
                &quot;top&quot;: 0,
                &quot;left&quot;: 0,
                &quot;display&quot;: &quot;none&quot;
            }); 
            base.$nav.delegate(&quot;li &amp;gt; a&quot;, &quot;click&quot;, function() {
                // Determinar el nombre del bloque que va a quedar oculto
                var curList = base.$el.find(&quot;a.current&quot;).attr(&quot;href&quot;).substring(1),
                    // Determinar el nombre del nuevo bloque 
                    $newList = $(this),
                    listID = $newList.attr(&quot;href&quot;).substring(1),
                    // Set outer wrapper height to (static) height of current inner list
                    $allListWrap = base.$el.find(&quot;.list-wrap&quot;),
                    curListHeight = $allListWrap.height();
                $allListWrap.height(curListHeight);

                if ((listID != curList) &amp;amp;&amp;amp; ( base.$el.find(&quot;:animated&quot;).length == 0)) {
                    // Ocultar progresivamente el bloque previo
                    base.$el.find(&quot;#&quot;+curList).fadeOut(base.options.speed, function() {
                        // En la funcion callback, presentar progresivamente (Fade in) el
                        // nuevo bloque
                        base.$el.find(&quot;#&quot;+listID).fadeIn(base.options.speed);
                        // Ajustar la altura del div envolvente a la altura del nuevo bloque
                        var newHeight = base.$el.find(&quot;#&quot;+listID).height();
                        $allListWrap.animate({
                            height: newHeight
                        });

                        // Marcar la nueva pestaña como pestaña activa
                        base.$el.find(&quot;.nav li a&quot;).removeClass(&quot;current&quot;);
                        $newList.addClass(&quot;current&quot;);
                    });
                }   

                // Evitar la propagación del evento click
                return false;
            });

        };
        base.init();
    };

    $.organicTabs.defaultOptions = {
        &quot;speed&quot;: 300
    };

    $.fn.organicTabs = function(options) {
        return this.each(function() {
            (new $.organicTabs(this, options));
        });
    };

})(jQuery);

jQuery(&quot;#ejemplo&quot;).organicTabs();</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>Referencias:</p>
<p><a href="http://css-tricks.com/organic-tabs/" target="_blank">http://css-tricks.com/organic-tabs/</a></p>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-incluir-un-area-de-navegacion-con-pestanas-en-una-pagina-html/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gestión de paquetes en Debian</title>
		<link>http://blog.openalfa.com/gestion-de-paquetes-en-debian/</link>
		<comments>http://blog.openalfa.com/gestion-de-paquetes-en-debian/#comments</comments>
		<pubDate>Sat, 04 May 2013 18:54:36 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Admon. Servidores]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3877</guid>
		<description><![CDATA[En este artículo se comentan los comandos disponibles para la administración de paquetes en el sistema operativo Debian Cómo listar los paquetes instalados El comando dpkg -l produce un listado de los paquetes instalados en el sistema, con indicación de su estado, nombre, versión, arquitectura y una breve descripción: $ dpkg -l&#124;more Desired=Unknown/Install/Remove/Purge/Hold &#124; Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend <a href='http://blog.openalfa.com/gestion-de-paquetes-en-debian/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>En este artículo se comentan los comandos disponibles para la administración de paquetes en el sistema operativo Debian <span id="more-3877"></span></p>
<h2>Cómo listar los paquetes instalados</h2>
<p>El comando dpkg -l produce un listado de los paquetes instalados en el sistema, con indicación de su estado, nombre, versión, arquitectura y una breve descripción:</p>
<pre class="brush: bash; gutter: false">$ dpkg -l|more
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name               Version    Arch.  Description
+++-==================-==========-======-=========================================================
ii  abiword            2.8.6-0.3  amd64  efficient, featureful word processor with collaboration
ii  accountsservice    0.6.21-8   amd64  query and manipulate user account information
ii  acl                2.2.51-8   amd64  Access control list utilities</pre>
<p>En las tres primeras columas se codifica el status del paquete, de la siguiente forma:</p>
<ul>
<li>Columna 1 &#8211; Acción deseada
<ul>
<li>u = Desconocido</li>
<li>i = Instalar</li>
<li>h = Mantener</li>
<li>r = Borrar</li>
<li>p = Eliminar</li>
</ul>
</li>
<li>Columna 2 &#8211; Status del paquete
<ul>
<li>n = No-instalado</li>
<li>c = Sólo están los ficheros de configuración del paquete</li>
<li>H = Semi-instalado</li>
<li>U = Desempaquetado</li>
<li>F = Semi-configurado</li>
<li>W = Esperando triggers</li>
<li>t = Pendiente de triggers</li>
<li>i = Instalado</li>
</ul>
</li>
<li>Columna 3 &#8211; Indicador de error
<ul>
<li>&lt;vacio&gt; = (ningun error)</li>
<li>R = Necesita ser reinstalado</li>
</ul>
</li>
</ul>
<p>Normalmente, Las tres primeras columnas contendrán los caracters &#8220;ii &#8220;, indicando que:</p>
<p style="padding-left: 30px;">- El estado deseado para el paquete es &#8220;instalado&#8221;</p>
<p style="padding-left: 30px;">- El paquete está  instalado</p>
<p style="padding-left: 30px;">- no hay ningún error, y el paquete no necesita ser reinstalado</p>
<p>Podemos filtrar la lista con un comando &#8220;grep&#8221;, para obtener los paquetes que no se encuentran en dicha situación:</p>
<pre class="brush: bash; gutter: false">$ dpkg -l | grep -v &quot;^ii &quot;                                                                                    
Desired=Unknown/Install/Remove/Purge/Hold                                                                     
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend                                
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)                                                    
||/ Name                 Version            Description                                                       
+++-====================-==================-================================================================  
rc  libapache2-mod-php5  5.3.3-7+squeeze15  server-side, HTML-embedded scripting language (Apache 2 module)</pre>
<p>En el ejemplo, el status &#8220;rc &#8221; que el paquete libapache2-mod-php5 debería estar borrado del sistema (&#8220;r&#8221;), pero todavía están presentes los ficheros de configuración (&#8220;c&#8221;). Por lo demás, no hay ningún error relacionado con el paquete (el tercer carácter es un espacio en blanco).</p>
<h2>Como instalar y desinstalar paquetes</h2>
<p>Normalmente, en un sistema debian se utiliza el comando &#8220;apt-get&#8221; para realizar estas tareas</p>
<ul>
<li>Instalar los paquetes paquete1 y paquete2:
<pre class="brush: bash; gutter: false">$ apt-get install paquete1 paquete2</pre>
</li>
<li>Instalar la versión 2.3 de paquete1:
<pre class="brush: bash; gutter: false">$ apt-get install paquete1=2.3</pre>
</li>
<li>Desinstalar paquete1 y paquete2:
<pre class="brush: bash; gutter: false">$ apt-get remove paquete1 paquete2</pre>
</li>
<li>Desinstalar y eliminar paquete1 y paquete2:
<pre class="brush: bash; gutter: false">$ apt-get purge paquete1 paquete2</pre>
<p>&#8220;purge&#8221; desinstala el paquete igual que &#8220;remove&#8221;, pero además borra también los ficheros de configuración</li>
</ul>
<h2>Cómo actualizar paquetes</h2>
<p>Antes de actualizar un paquete, se debe ejecutar el comando &#8220;apt-get update&#8221; para actualizar la lista de paquetes disponibles:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-get update
Get:1 http://security.debian.org squeeze/updates Release.gpg [836 B]
Ign http://security.debian.org/ squeeze/updates/contrib Translation-en         
Ign http://security.debian.org/ squeeze/updates/contrib Translation-en_GB
Ign http://security.debian.org/ squeeze/updates/main Translation-en</pre>
<p>A continuación, si queremos actualizar a la última versión un determinado paquete, utilizamos &#8220;apt-get install paquete&#8221;, como si se fuera a instalar por primera vez</p>
<p>Si queremos actualizar todos los paquetes instalados, utilizamos &#8220;apt-get upgrade&#8221;. Este comando no instala ni desinstala ningún paquete, simplemente actualiza los paquetes ya instalados. Si para actualizar un paquete a la última versión fuera necesario instalar o desinstalar otro paquete, el comando &#8220;apt-get upgrade&#8221; no realizaría ninguna acción con dicho paquete.</p>
<p>Ejemplo:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-get upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages have been kept back:
  abiword abiword-plugin-grammar abiword-plugin-mathview alsa-base alsa-utils apt
  apt-utils at-spi bind9-host binfmt-support bluez browser-plugin-gnash cpp cpp-4.4
  cpp-4.6 dasher dnsmasq-base dnsutils dvd+rw-tools eclipse-jdt eclipse-pde
   ...
The following packages will be upgraded:
  acpid comerr-dev cups-bsd cups-client cups-common cups-ppdc curl dmsetup doc-debian
  docbook-xml dpkg dpkg-dev e2fslibs e2fsprogs evolution evolution-common
  evolution-plugins foomatic-db foomatic-db-engine gdm3 gir1.2-cogl-1.0
  ... 
185 upgraded, 0 newly installed, 0 to remove and 184 not upgraded.
Need to get 201 MB of archives.
After this operation, 17.4 MB disk space will be freed.
Do you want to continue [Y/n]?</pre>
<p>Como podemos ver en las últimas líneas de la salida generada, en el ejemplo se descargarán 201 MB de archivos, pero el resultado final será que se liberan 17,4 MB, porque alguna de las actualizaciones ocupan menos espacio que las versiones antiguas a las que sustituyen.</p>
<h2>Cómo instalar una versión anterior de un paquete instalado (downgrade)</h2>
<p>En ocasiones, la versión más reciente de un determinado paquete puede causar problemas en algunas aplicaciones que esperan una versión anterior de dicho paquete. En estos casos es posible reinstalar la versión previa.</p>
<p>Para ello, desinstalamos la versión actual del paquete con &#8220;apt-get remove&#8221; o &#8220;apt-get purge&#8221;, y procedemos a instalar la otra versión:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-get remove paquete
$ sudo apt-get install paquete=version</pre>
<p>A continuación, utilizamos &#8220;dpkg &#8211;set-selections&#8221; para marcar el paquete, para impedir que la versión instalada se actualice si se ejecuta un &#8220;apt-get upgrade&#8221; u otro comando que intente actualizar paquetes:</p>
<pre class="brush: bash; gutter: false">echo “paquete hold” | dpkg --set-selections</pre>
<h2>Cómo averiguar de qué ficheros consta un paquete</h2>
<p>el comando &#8220;dpkg -L paquete&#8221; permite saber de qué ficheros consta un paquete instalado. Ejemplo:</p>
<pre class="brush: bash; gutter: false">$ sudo dpkg -L abiword
/.
/usr
/usr/share
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/abiword.1.gz
/usr/share/doc
/usr/share/doc/abiword
/usr/share/doc/abiword/changelog.Debian.gz
...</pre>
<p>Si el paquete no está instalado, se puede utilizar &#8220;apt-file list paquete&#8221;. Previamente, es necesario ejecutar &#8220;apt-file update&#8221; para cargar la cache utilizada por este comando:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-file update
$ apt-file list abiword
abiword: /usr/bin/abiword
abiword: /usr/lib/mime/packages/abiword
abiword: /usr/lib/x86_64-linux-gnu/abiword-2.9/plugins/aiksaurus.so
abiword: /usr/lib/x86_64-linux-gnu/abiword-2.9/plugins/applix.so
abiword: /usr/lib/x86_64-linux-gnu/abiword-2.9/plugins/babelfish.so
...</pre>
<p>Como vemos, &#8220;apt-file list&#8221; devuelve la información de la versión más reciente que existe en el repositorio (en el ejemplo, la versión 2.9 de abiword), mientras que el comando &#8220;dpkg -L&#8221; devuelve la información correspondiente a la versión instalada (abiword 2.8 en el ejemplo).</p>
<h2>Cómo averiguar a qué paquete pertenece un fichero</h2>
<p>Si el fichero está instalado en el sistema, podemos utilizar &#8220;dpkg -S&#8221;. Ejemplo</p>
<pre class="brush: bash; gutter: false">$ dpkg -S /bin/ls
coreutils: /bin/ls</pre>
<p>En el ejemplo, vemos que el comando &#8220;ls&#8221; se encuentra en el paquete &#8220;coreutils&#8221;</p>
<p>Si el fichero no está en el sistema, utilizamos &#8220;apt-file search&#8221; para buscarlo en el repositorio de paquetes. Ejemplo:</p>
<pre class="brush: bash; gutter: false">$ apt-file search /bin/ls
9base: /usr/lib/plan9/bin/ls
canna-utils: /usr/bin/lsdic
cgroup-bin: /usr/bin/lscgroup
cgroup-bin: /usr/bin/lssubsys
cmtk: /usr/lib/cmtk/bin/lsba
coreutils: /bin/ls
cwiid-dbg: /usr/lib/debug/usr/bin/lswm
distcc: /usr/bin/lsdistcc
dracut: /usr/bin/lsinitrd
...</pre>
<p>Como podemos ver, el comando busca cualquier línea del contenido de un paquete que incluya el string &#8220;/bin/ls&#8221; que le hemos pasado como argumento. Por esta razón devuelve muchos resultados, entre los cuales se encuentra el que nos interesa: &#8220;coreutils: /bin/ls&#8221;.</p>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/gestion-de-paquetes-en-debian/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo enviar un email codificado en UTF-8 en PHP</title>
		<link>http://blog.openalfa.com/como-enviar-un-email-codificado-en-utf-8-en-php/</link>
		<comments>http://blog.openalfa.com/como-enviar-un-email-codificado-en-utf-8-en-php/#comments</comments>
		<pubDate>Fri, 03 May 2013 15:45:04 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[PHP]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3873</guid>
		<description><![CDATA[Este artículo detalla la manera de enviar un correo electrónico desde un script PHP, cuando el cuerpo del mensaje o alguno de los headers (remitente, destinatario, asunto,&#8230;) están codificados en UTF-8. Para ver la manera de enviar un correo electrónico de texto simple o HTML, o un correo electrónico con ficheros adjuntos, consultar: Cómo enviar <a href='http://blog.openalfa.com/como-enviar-un-email-codificado-en-utf-8-en-php/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>Este artículo detalla la manera de enviar un correo electrónico desde un script PHP, cuando el cuerpo del mensaje o alguno de los headers (remitente, destinatario, asunto,&#8230;) están codificados en UTF-8.</p>
<p>Para ver la manera de enviar un correo electrónico de texto simple o HTML, o un correo electrónico con ficheros adjuntos, consultar: <a title="Cómo enviar emails desde un script PHP" href="http://blog.openalfa.com/como-enviar-emails-desde-un-script-php/" target="_blank">Cómo enviar emails desde un script PHP</a></p>
<p><span id="more-3873"></span></p>
<p>Si el cuerpo del mensaje está codificado en UTF-8, basta con añadir una línea al header indicando este hecho, y codificar adecuadamente el cuerpo del mensaje. Así, podemos definir una función &#8220;mail_utf8&#8243; que añade el header y envía el mensaje de la siguiente forma:</p>
<pre class="brush: php; gutter: false">&lt;?php

function mail_utf8($to, $subject = &#039;(No subject)&#039;, $message = &#039;&#039;, $header = &#039;&#039;) {
  $header_ = &#039;MIME-Version: 1.0&#039; . &quot;\r\n&quot; . &#039;Content-type: text/plain; charset=UTF-8&#039; . &quot;\r\n&quot;;
  mail($to, &#039;=?UTF-8?B?&#039;.base64_encode($subject).&#039;?=&#039;, $message, $header_ . $header);
}

?&gt;</pre>
<p>&nbsp;</p>
<p>Si también los campos From o Cc contienen caracteres utf-8, se pueden codificar de la misma manera. En el caso del campo From, los caracteres utf-8 pueden encontrarse en el nombre del remitente, entre &#8220;&lt;&#8221; y &#8220;&gt;&#8221;</p>
<pre class="brush: php; gutter: false">    if (ereg(&quot;(.*)&lt;(.*)&gt;&quot;, $from, $regs)) {
       // El campo From contiene el nombre del remitente
       $from = &#039;=?UTF-8?B?&#039;.base64_encode($regs[1]).&#039;?= &lt;&#039;.$regs[2].&#039;&gt;&#039;;
    } else {
       // No hay nombre del remitente, sólo su email. No hay necesidad de
       // codificar el campo
    }

    // Después, añadimos al header la línea &quot;From:&quot;
    $header = &#039;From: &#039; . $from . &quot;\n&quot;;</pre>
<p>&#8212;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-enviar-un-email-codificado-en-utf-8-en-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Cómo descargar un informe de adwords desde un script perl</title>
		<link>http://blog.openalfa.com/como-descargar-un-informe-de-adwords-desde-un-script-perl/</link>
		<comments>http://blog.openalfa.com/como-descargar-un-informe-de-adwords-desde-un-script-perl/#comments</comments>
		<pubDate>Tue, 30 Apr 2013 06:48:31 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[SEM - AdWords]]></category>

		<guid isPermaLink="false">http://blog.openalfa.com/?p=3851</guid>
		<description><![CDATA[Algunos informes disponibles en el frontend web de Google AdWords no pueden ser descargados utilizando el API de AdWords. En concreto, el informe que contiene el resumen de facturación de los clientes de una cuenta MCM, incluyendo el presupuesto total, el presupuesto consumido y el que queda disponible. Esta información permite, entre otras cosas, automatizar <a href='http://blog.openalfa.com/como-descargar-un-informe-de-adwords-desde-un-script-perl/' class='excerpt-more'>[...]</a>]]></description>
				<content:encoded><![CDATA[<p>Algunos informes disponibles en el frontend web de Google AdWords no pueden ser descargados utilizando el API de AdWords. En concreto, el informe que contiene el resumen de facturación de los clientes de una cuenta MCM, incluyendo el presupuesto total, el presupuesto consumido y el que queda disponible. Esta información permite, entre otras cosas, automatizar la generación de una alerta cuando el presupuesto disponible disminuye por debajo de una cierta cantidad.</p>
<p>En este artículo se explica cómo automatizar el proceso de login y la descarga de dicho informe, utilizando un script perl.</p>
<p><span id="more-3851"></span></p>
<h2>0. Prerequisitos</h2>
<p>El procedimiento que se explica en este artículo requiere que se hayan instalado previamente los módulos CPAN:</p>
<ul>
<li>WWW::Mechanize::GZip</li>
<li>LWP::Protocol::https</li>
</ul>
<p>Para ello, en un sistema debian o ubuntu debemos haber instalado también las librerías SSL requeridas:</p>
<pre class="brush: bash; gutter: false">$ sudo apt-get install libnet-ssleay-perl libcrypt-ssleay-perl</pre>
<h2>1. Proceso de login</h2>
<p>Cuando accedemos a la url http://adwords.google.com desde un navegador en el que no nos hemos validado previamente, somos redirigidos al formulario de login, que se encuentra en una url de la forma:</p>
<pre class="brush: bash; gutter: false">https://accounts.google.com/ServiceLogin
    ?service=adwords
    &amp;hl=en_GB
    &amp;ltmpl=jfk
    &amp;continue=https://adwords.google.com/um/gaiaauth?apt%3DNone%26ltmpl%3Djfk
    &amp;passive=86400
    &amp;sacu=1&amp;sarp=1</pre>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/04/adwords-login.jpg"><img class="aligncenter size-full wp-image-3855" alt="adwords-login" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/04/adwords-login.jpg" width="690" height="307" /></a></p>
<p>Una vez introducido el usuario y la contraseña, al hacer el submit el navegador accede a la url de proceso del formulario (https://accounts.google.com/ServiceLoginAuth). A continuación, pasa por varias urls mediante una serie de redirecciones 302, hasta que carga la página de inicio de adwords.</p>
<p>En este proceso, el navegador recibe los cookies que necesita para el acceso como usuario autentificado.</p>
<p>Todo este proceso se puede automatizar en perl utilizando el módulo WWW::Mechanize::GZip disponible en CPAN. El código es el siguiente:</p>
<pre class="brush: perl; gutter: false">#!/usr/bin/perl

use strict;
use warnings;
use WWW::Mechanize::GZip;
use HTTP::Cookies;
use Encode qw(from_to);

my $outfile = &quot;spend.txt&quot;;
my $url = &#039;https://accounts.google.com/ServiceLogin&#039; .
          &#039;?service=adwords&amp;hl=en_GB&amp;ltmpl=jfk&#039; .
          &#039;&amp;continue=https://adwords.google.com/um/gaiaauth?apt%3DNone%26ltmpl%3Djfk&#039; .
          &#039;&amp;passive=86400&amp;sacu=1&amp;sarp=1&#039;;

my $username = &#039;NOMBRE-DE-USUARIO&#039;;
my $password = &#039;CONTRASEÑA&#039;;
my $mech = WWW::Mechanize::GZip-&gt;new();
$mech-&gt;agent( &#039;Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-us) &#039; .
              &#039;AppleWebKit/533.17.8 (KHTML, like Gecko) Version/5.0.1 Safari/533.17.8&#039; );
$mech-&gt;cookie_jar(HTTP::Cookies-&gt;new());
$mech-&gt;get($url);
$mech-&gt;form_id(&#039;gaia_loginform&#039;);
$mech-&gt;field(&quot;Email&quot;, $username);
$mech-&gt;field(&quot;Passwd&quot;, $password);
$mech-&gt;submit;</pre>
<p>En la página de inicio de adwords, se incluye un token que necesitaremos más tarde para realizar la descarga del informe. También se encuentran en ella los valores de &#8216;effectiveUserId&#8217; y &#8216;customerId&#8217; (que son utilizados a menudo en forma de argumentos &#8216;__u&#8217; y &#8216;__c&#8217;). Utilizando expresiones regulares es sencillo extraer estos valores.</p>
<pre class="brush: perl; gutter: false">my $output_page = $mech-&gt;content();

my $token = &quot;&quot;;
my $customerId = &quot;&quot;;
my $effectiveUserId = &quot;&quot;;
if ($output_page =~ m/token:&#039;([^&#039;]+)&#039;/) {
    $token = $1; 
}   
if ($output_page =~ m/effectiveUserId:&#039;([^&#039;]+)&#039;/) {
    $effectiveUserId = $1; 
}   
if ($output_page =~ m/customerId:&#039;([^&#039;]+)&#039;/) {
    $customerId = $1; 
}</pre>
<h2>2. Descarga del informe resumen de presupuesto</h2>
<p>Desde la página de inicio, la pestaña &#8220;Budget&#8221; permite acceder al informe resumen del presupuesto de los clientes gestionados por la cuenta MCM.</p>
<p>También hay un botón para realizar la descarga del informe en formato csv.</p>
<p><a href="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/04/adwords-budget-summary.jpg"><img class="aligncenter size-full wp-image-3858" alt="adwords-budget-summary" src="http://blog.openalfa.com/wp-content/uploads/sites/2/2013/04/adwords-budget-summary.jpg" width="670" height="340" /></a></p>
<p>Analizando con Firebug los accesos que realiza el navegador cuando se pulsa el botón de descarga del informe, comprobamos que realiza una solicitud POST a la url: &#8220;https://adwords.google.com/mcm/file/ClientSummary&#8221;. En la solicitud se envía un parámetro &#8220;token&#8221;, cuyo valor es el que hemos obtenido previamente, y un parámetro &#8220;mcsSelector&#8221;, con un valor que representa el formato del informe a descargar.</p>
<p>De nuevo, utilizando Mechanize, es sencillo reproducir por programa este acceso:</p>
<pre class="brush: perl; gutter: false">    my $report_url = &quot;https://adwords.google.com/mcm/file/ClientSummary&quot;;
    my $report;
    $report = $mech-&gt;post($report_url,
            [  &#039;token&#039; =&gt; $token,
              &#039;mcsSelector&#039; =&gt; &#039;7|0|46|https://adwords.google.com/mcm/gwt/|D3843BD1D93572
6A9B903EB8AB9B8B64|com.google.ads.api.services.common.selector.Selector/3608467785|com.go
ogle.ads.api.services.common.date.DateRange/1118087507|com.google.ads.api.services.common
.date.Date/373224763|java.util.ArrayList/4159755760|java.lang.String/2004016611|ClientNam
e|ExternalCustomerId|PrimaryUserLogin|PrimaryCompanyName|IsManager|SalesChannel|Tier|Acco
untSettingTypes|Labels|CostWithCurrency|CostUsd|Clicks|Impressions|Ctr|Conversions|Conver
sionRate|BudgetAmount|BudgetStartDate|BudgetEndDate|BudgetPercentSpent|BudgetType|Remaini
ngBudget|ClientDateTimeZoneId|com.google.ads.api.services.common.selector.OrderBy/5243884
50|SearchableData|com.google.ads.api.services.common.sorting.SortOrder/2037387810|com.goo
gle.ads.api.services.common.pagination.Paging/363399854|com.google.ads.api.services.commo
n.selector.Predicate/451365360|SeedObfuscatedCustomerId|com.google.ads.api.services.commo
n.selector.Predicate$Operator/2293561107|java.util.Arrays$ArrayList/2507071751|[Ljava.lan
g.String;/2600011424|1222574392|ExcludeSeeds|true|ClientTraversal|DIRECT|com.google.ads.a
pi.services.common.selector.Summary/3224078220|included|1|2|3|4|5|2|10|2012|5|2|10|2012|6
|23|7|8|7|9|7|10|7|11|7|12|7|13|7|14|7|15|7|16|7|17|7|18|7|19|7|20|7|21|7|22|7|23|7|24|7|
25|7|26|7|27|7|28|7|29|7|30|6|0|0|0|6|2|31|32|33|0|31|9|-32|34|50|0|6|0|6|3|35|36|37|2|38
|39|1|40|35|41|37|0|38|39|1|42|35|43|-42|38|39|1|44|0|0|6|0|6|1|45|6|0|46|6|0|0|6|0|&#039;
            ]);

my $data = $report-&gt;content();
from_to($data, &#039;UTF-16le&#039;, &#039;UTF-8&#039;);</pre>
<p>Como podemos ver, en la última línea realizamos la conversión a &#8220;UTF-8&#8243; desde el formato &#8220;UTF-16 little endian&#8221; con el que el servidor entrega los datos.</p>
<h2>3. Proceso del informe</h2>
<p>Ya sólo queda procesar el informe. Dado que está en formato TSV (Valores separados por tabuladores), tenemos en perl muchas maneras de procesarlo. Por ejemplo:</p>
<pre class="brush: perl; gutter: false"># Campos en el informe:
# Client Name | Company Name | Login | Customer ID | Labels | Budget |
# Start Date | End Date | Time Zone | % Spent | Remaining Budget | Amount Spent |
# Impr. | Clicks | Search CTR | Content CTR | Conversions | Conv. Rate
#
my %spend;
my @lines = split(&quot;\n&quot;,$data);
my $nlines = scalar(@lines);
for (my $iline = 0; $iline &lt; $nlines; $iline++) {
    my @fields = split(&quot;\t&quot;,$lines[$iline]);
    my $nfields = scalar(@fields);
    if ($nfields == 18) {
       $spend{$fields[0]} = { &quot;spent&quot; =&gt; $fields[9] , &quot;remaining&quot; =&gt; $fields[10] };
    } else {
        print &quot;Ignorando la línea: &quot; . $lines[$iline] . &quot;\n&quot;;
    }
}

# Imprimir los datos obtenidos
foreach my $account (keys %spend) {
    print &quot;cuenta: &quot; . $account . &quot;, gastado: &quot; . $spend{$account}{&quot;spent&quot;} .
          &quot;, disponible: &quot; . $spend{$account}{&quot;remaining&quot;} . &quot;\n&quot;;
}</pre>
<p>Referencias:</p>
<p><a href="http://stackoverflow.com/questions/15071327/no-content-disposition-header-in-response-from-mechanize" target="_blank">http://stackoverflow.com/questions/15071327/no-content-disposition-header-in-response-from-mechanize</a></p>
<p>&#8212;</p>
<p>&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.openalfa.com/como-descargar-un-informe-de-adwords-desde-un-script-perl/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk: enhanced

 Served from: blog.openalfa.com @ 2013-05-25 21:40:17 by W3 Total Cache -->