Archivo por meses: noviembre 2009

Castillo de Onda

El Castillo de Onda se alza en lo alto de una pequeña colina en la villa del mismo nombre desde donde la domina y vigila.
DSC_0527

El origen del castillo es árabe y en el interior se puede encontrar la parte más antigua, en proceso de excavación, es la parte central que podemos ver en esta foto, una pequeña fortaleza dentro de otra. Creo que está cerrada al público debido a los trabajos de investigación, alk menos cuando fuimos nosotros así era.

DSC_0518

Onda fue conquistada en 1090 por El Cid, aunque volvería a ser árabe hasta la conquista definitiva en 1238 por, ¿lo adivináis?, Jaime I El Conquistador en su camino a Valencia cuyo último rey musulmán, Zayyan, era precisamente originario de Onda.

DSC_0540Traes caer en manos de la Orden del Temple, el castillo pasa a manos de la nueva Orden de Montesa creada en Valencia.DSC_0525Posteriormente el castillo pasó con cierta relevancia por todos los acontecimientos históricos de España, desde la ocupación francesa hasta las Guerras Carlistas. Finalmente, en 1938 es bombardeado durante la Guerra Civil quedando prácticamente destruido.

DSC_0506

La fortaleza, de origen puramente militar, tenía hasta cuatro líneas de defensa comenzando en la propia alcazaba original en el punto más alto y terminando en las murallas más exteriores que todavía pueden verse, torreones incluidos.

DSC_0523DSC_0522

El castillo se puede visitar completamente pero hay que estar atentos a los horarios. El paseo por el recinto es fantástico, es de esos donde llegas a sentir el peso de la historia en cada una de sus piedras y rincones. DSC_0521

La muralla de todo el perímetro del recinto es sencillamente espectacular y la muralla más externa, que se ve bien desde algunos lados del castillo, aún conserva algunas de las numerosas torres que llegó a tener, de hecho se le conoce también como el castillo de las 300 torres en palabras del cronista Muntaner: El castillo de la villa de Onda, que tiene tantas torres como días hay en el año.

DSC_0520DSC_0517

En el interior del castillo, en lo que antiguamente eran la iglesia y la escuela, se encuentra hoy en día un pequeño museo y el centro de recepción de visitantes.

DSC_0516DSC_0513DSC_0512

DSC_0510Después de visitar el castillo puedes dar una vuelta por el casco histórico de Onda, declarado en 1967 Conjunto Histórico-Artístico, descubrirás algunos rincones interesantes como la plaza mudéjar de L’Almodí, la iglesia de la Sangre, la Plaza de la Sinagoga o las Escaletes dels gats, rincón típico de Onda este último de camino hacia el Castillo.

De artículo a proyecto explicado paso a paso: acorta URLs con IraUrl.me

Hace un par de semanas me encontré con la necesidad de utilizar uno de esos sistemas que hay por ahí para acortar URL‘s. Necesitaba enviar una dirección por SMS y tenía que ocupar la menor cantidad de caracteres posible por aquello de optimizar el texto del mensaje. Mientras lo utilizaba pensaba en lo ingenioso de utilizar un sistema de numeración base36 para reducir exponencialmente el número de caracteres de la redirección. Esto iba a ser, pues, un artículo sobre las ventajas de los sistemas de numeración distintos al decimal para determinados proyectos, pero se acabó convirtiendo en un proyecto completo. Cuando estaba comenzando la explicación teórica pensé, ¿por qué no hacerlo? ¿por qué no demostrar lo rápido y fácil que se puede montar algo en Internet hoy en día?

Así, tras unas 15 horas de trabajo os presento IraUrl.me, un acortador de URL’s al estilo de TinyUrl o Bit.ly. Me ha costado más escribir el artículo que hacerlo realidad, curioso ¿eh?. En realidad a medida que iba preparando la aplicación se me iban ocurriendo más cosas que sería interesante montar, por lo que las 8 horas iniciales, más o menos, se convirtieron en 15.

iraurl

La teoría

Para el que no lo sepa, un acortador de URL se basa en encontrar un dominio lo más corto posible y crear redirecciones HTTP 301 a otras URL‘s. El truco está en optimizar los parámetros que añadiremos a la URL para que sean lo más cortos posible, no queremos que éstos nos penalicen lo corto del dominio.

¿Cómo funcionan entonces estos acortadores de URL‘s? Mucho más fácil de lo que parece y seguramente como a ti se te habría ocurrido. Simplemente tenemos una base de datos donde vamos añadiendo registros a una tabla a medida que se van creando nuevas URL’s cortas.  Esta tabla tiene un campo autonumérico, la clave de la tabla, que para cada nueva URL nos devuelve un identificador único, con lo que cada dirección podría ser accesible de la manera habitual:

http://dominio.com/1

http://dominio.com/1000000

Esa es exactamente la idea, lo único que hacemos es cambiar el identificador en cuestión de base10 (la de nuestro sistema métrico decimal) a base36 o base62 en mi caso. Otros sistemas de numeración conocidos son el hexadecimal (base16) y base64.

Vale, ya has hablado en chino. ¿De qué va esto? Veamos.

Sobre bases de numeración

El sistema decimal utiliza diez dígitos (de ahí lo de decimal :P) para formar todas las combinaciones de números posibles. Lo que ya conocemos, vamos. El binario utiliza dos dígitos (0 y 1), el hexadecimal 16 (0..9ABCDE), base36, como su nombre indica, treinta y seis (0..9a..z) y base62 utiliza los 62 dígitos que comprenden los números del 0 al 9 y las letras de la A a la Z en mayúsculas y minúsculas (0..9a..zA..Z). Veamos unos ejemplos:

Binario Decimal Hexadecimal Base36 Base62
0 0 0 0 0
1 1 1 1 1
10 2 2 2 2
1010 10 A a a
1100100 100 64 2s 1c
1000000 F4240 lfls 4c92
10000000 989680 5yc1s FXsk

Se puede observar de un vistazo cómo a medida que aumenta el número, cuanto mayor sea la base que manejamos menos dígitos tendrá . Los números, a fin de cuentas, son combinaciones continuas entre todos los dígitos posibles.Así, en función de la base y del número de dígitos, el mayor número representable representable sería:

Num. dígitos
Decimal Base62
1 10 62
2 100 3844
3 1000 238328
4 10000 14776336
5 100000 916132832
6 1000000 56800235584
7 10000000 3521614606208
8 100000000 218340105584896
9 1000000000 13537086546263552

O lo que es lo mismo, base(número de dígitos), 629 contra 109.Espero que se entienda la teoría. Como curiosidad:

Decimal: 10000000000000000000000

Base62: 36aHo5IWaicak

La pregunta ahora sería, ¿Por qué Base62 y no Base64, por ejemplo, mucho más conocida? Sencillo, porque además de los 62 caracteres de Base62, Base64 utiliza dos adicionales, generalmente + y / además del =, lo que convierten la cadena en no web safe, es decir, los caracteres especiales debieran traducirse para que su transporte no diese problemas, con lo que estaríamos perdiendo las ventajas de nuestro cifrado corto. Los 62 caracteres utilizados en Base62 son totalmente seguros, sólo letras (mayúsculas y minúsculas) y números.

Sabiendo ya cómo funciona el sistema, veremos cómo crear nuestra aplicación. Obviamente no contaré todo paso a paso ya que sino tardaría mucho más en escribir el artículo que en hacer la aplicación, me meteré sólo en las cosas que considere más importantes.

Para codificar/decodificar de base10 a base62 utilizaré estas librerías:

function dec2base($dec, $base, $digits = FALSE) {
      if($base < 2 or $base > 256) {
            die("Invalid Base: .$basen");
      }
      bcscale(0);
      $value = '';
      if(!$digits) {
            $digits = digits($base);
      }
      while($dec > $base - 1) {
            $rest = bcmod($dec,$base);
            $dec = bcdiv($dec,$base);
            $value = $digits[$rest].$value;
      }
      $value=$digits[intval($dec)].$value;
      return (string) $value;
}

function base2dec($value, $base, $digits = FALSE) {
      if($base < 2 or $base > 256) {
            die("Invalid Base: .$basen");
      }
      bcscale(0);
      if($base < 37) {
            $value = strtolower($value);
      }
      if(!$digits) {
            $digits = digits($base);
      }
      $size = strlen($value);
      $dec = '0';
      for($loop=0; $loop < $size; $loop++) {
            $element = strpos($digits, $value[$loop]);
            $power = bcpow($base, $size-$loop-1);
            $dec = bcadd($dec, bcmul($element, $power));
      }
      return (string)$dec;
}

function digits($base) {
      if($base < 64) {
            return substr('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_', 0, $base);
      } else {
            return substr("x0x1x2x3x4x5x6x7x8x9xaxbxcxdxexfx10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f !x22#x24%&'()*+,-./0123456789:;<=>x3f@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~x7fx80x81x82x83x84x85x86x87x88x89x8ax8bx8cx8dx8ex8fx90x91x92x93x94x95x96x97x98x99x9ax9bx9cx9dx9ex9fxa0xa1xa2xa3xa4xa5xa6xa7xa8xa9xaaxabxacxadxaexafxb0xb1xb2xb3xb4xb5xb6xb7xb8xb9xbaxbbxbcxbdxbexbfxc0xc1xc2xc3xc4xc5xc6xc7xc8xc9xcaxcbxccxcdxcexcfxd0xd1xd2xd3xd4xd5xd6xd7xd8xd9xdaxdbxdcxddxdexdfxe0xe1xe2xe3xe4xe5xe6xe7xe8xe9xeaxebxecxedxeexefxf0xf1xf2xf3xf4xf5xf6xf7xf8xf9xfaxfbxfcxfdxfexff", 0, $base);
      }
}

function base_encode($value) {
      return dec2base(base2dec($value, 256), 62);
}

function base_decode($value) {
      return dec2base(base2dec($value, 62), 256);
}

Las dos últimas funciones son las que utilizaremos para las conversiones.

Paquetes y librerías utilizadas:

  • Free CSS Templates: Para tener una bonita plantilla xhtml para nuestro proyecto 🙂 .
  • Maxmind GeoLite Country: Para la geolocalización de un usuario a través de su IP.
  • Wurfl: Para identificar el navegador/terminal de un visitante por su User Agent. Yo lo complemento con Tera-Wurfl para mantener la información en una base de datos.
  • Fusion Charts Free: Para los gráficos de estadísticas.
  • Zero Clipboard: Para copiar al portapapeles la url corta generada sin que el usuario deba seleccionarla, solo con un click.
  • JqueryUI: Para el componente de navegación con pestañas.
  • Google Safebrowsing API: Para comprobar si una url es potencialmente peligrosa.
  • Adodb (opcional): Para abstraer el acceso a la base de datos. Yo suelo utilizarla en todos mis proyectos pero se pueden utilizar las funciones nativas de PHP.
  • PHPExcel: Para generar Excel y PDF.

Adicionalmente:

  • Un dominio y hosting donde alojarlo (6€).
  • PHP y MySQL (tampoco son obligatorios, puedes hacerlo con cualquier tecnología).
  • 15 horas de tu tiempo :P.

Estructura de la web

Cualquier proyecto web que se precie debe comenzarse describiendo qué queremos mostrar a nuestros visitantes, hay que recopilar todas las ideas, decidir las que interesan de verdad, estudiar cómo se van a disponer en el frontend y terminar con un mapa web que nos indique el flujo a seguir en el trabajo. Este será el nuestro:

mapaWebIraUrlCuando un usuario vaya a una de nuestras url’s cortas en realidad estaremos reenviando la petición http internamente a un script encargado de hacer todo el proceso, link.php en mi caso.

Para ver las estadísticas de una URL me ha gustado el sistema de bit.ly, así que se lo copiamos :P. Añadiendo un “+” al final de la URL corta, en vez de saltar a la dirección larga mostraremos las estadísticas. Esto lo haremos, como en el caso anterior, dirigiendo internamente a otro script, stats.php.

Si el identificador que pretendemos usar para saltar a la url larga o ver estadísticas no existe, reenviaremos a index.php para que muestre un mensaje de error tipo “La url no existe“.

El dominio

Obviamente tendremos que buscar un dominio lo más corto posible, la mayoría estarán ya ocupados, pero buscando y buscando en TLD‘s extraños puedes encontrar algo. Yo he escogido un .me porque tiene un carácter menos que un .com 🙂 y no cuesta lo que un .es :P.

La base de datos

Muy sencilla, dos tablas solamente, en una mantendremos las urls generadas y en otra las estadísticas de acceso a las mismas.

CREATE TABLE IF NOT EXISTS `urls` (
  `id` bigint(20) NOT NULL auto_increment,
  `url` varchar(500) NOT NULL,
  `titulo` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `stats` (
`id` bigint(20) NOT NULL auto_increment,
`idurl` bigint(20) NOT NULL,
`codpais` varchar(255) NOT NULL,
`referer` varchar(255) NOT NULL,
`hostreferer` varchar(255) NOT NULL,
`ua` varchar(255) NOT NULL,
`hora` datetime NOT NULL,
`pais` varchar(255) NOT NULL,
`marca` varchar(255) NOT NULL,
`modelo` varchar(255) NOT NULL,
PRIMARY KEY  (`id`),
KEY `idurl` (`idurl`,`hora`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

La configuración de Apache

Como hemos comentado queremos que cuando alguien vaya a cualquier url de nuestro site del tipo

http://tudominio.com/prueba3

Se redirija internamente al script link.php que será el encargado de procesar la petición. De igual modo si alguien visita

http://tudominio.com/prueba3+

le mostraremos las estadísticas de esa URL (si existen). Configuramos Apache para que tenga en cuenta todas estas particularidades, mod_rewrite será nuestro amigo para conseguirlo. En mi caso he hecho que si la llamada no es un script php, ni una imagen ni un archivo javascript ni un css ni tiene el signo “+“, se vaya a link.php. Si tiene el signo “+” se irá a stats.php.

RewriteEngine on
RewriteCond %{REQUEST_URI} !^/(.*).php$
RewriteCond %{REQUEST_URI} !^/css/(.*)$
RewriteCond %{REQUEST_URI} !^/js/(.*)$
RewriteCond %{REQUEST_URI} !^/(.*)+$
RewriteCond %{REQUEST_URI} !^/images/(.*)$
RewriteRule ^(.+) /link.php?id=$1
RewriteCond %{REQUEST_URI} ^/(.*)+$
RewriteRule ^(.+) /stats.php?id=$1

Imagen y diseño

Para el diseño, o te lo haces tú mismo o si eres un negado creativo como yo te descargas una plantilla superchula de freecsstamplates.org y la adaptas a tus necesidades, no voy a entrar en más detalles.

Crear urls cortas

El primer script de nuestra aplicación. Un sencillo formulario donde el usuario introduce la URL que quiere acortar y al hacer submit… la acortamos integrando el sistema con la comprobación de malware que explicaba hace unos días ;).

$urlbase="";
if(isset($_POST['url'])){
        $url=$_POST['url'];

        try{
	        if(substr($url, 0, 4)!="http")
	        	$url="http://".$url;
			$class = new GoogleSafeBrowsing('ABQIAAAAQYvf-54bCBMuGY20SeONVxQ236Mc_IjryQBl-W_Repaw3fCykA', true);
			$nomalware=$class->lookupsFor($url);

			if($nomalware){
        			$htmltitle="";
        			$html=file_get_contents($url);
        			if($html!=""){
        				preg_match('/(.*)</title>/is', $html, $matches);
					if(is_array($matches) && count($matches>0))
						$htmltitle=trim($matches[1]);
        			}
                		$query="select * from urls where url='$url'";
				$rs=$conn->Execute($query);
				if($rs->recordcount()>0){
					$id=$rs->fields['id'];
				}else{
					$query="insert into urls (url, titulo) VALUES ('$url', '$htmltitle')";
					$rs=$conn->Execute($query);
					$id=$conn->insert_id();
				}
				$base=base_encode($id);
				$urlbase="http://iraurl.me/".$base;
			}else{
				$err=4;
			}
        }catch(exception $e){
        	$err=3;
        }
}

Hemos añadido una pequeña comprobación. Si la URL que se quiere añadir ya existe, devolvemos la misma URL corta, yo he tomado esa decisión, tú puedes hacer lo que quieras. Además obtenemos el título de la URL final para tener una referencia hacia la misma, cuestión de sencillez visual :P.

Reenvío a urls largas

Ya tenemos nuestra URL corta, vamos ahora a reenviar las solicitudes a ella a la larga. Recordemos que nuestro Apache nos va a redirigir esa petición a link.php?id=XXXX. Nuestro script actualiza, además, las estadísticas de visitas de la url.

if(isset($_GET['id'])){
	$idb=$_GET['id'];
	$id=base_decode($idb)+0;
	try{
		$query="select * from urls where id=$id";
		$rs=$conn->Execute($query);
		if($rs->recordcount()>0){
			$url=$rs->fields['url'];
			$referer=@$_SERVER['HTTP_REFERER'];
			$ua=@$_SERVER['HTTP_USER_AGENT'];
			$ip=@$_SERVER['REMOTE_ADDR'];

			$hostreferer="";
			if(preg_match('@^(?:http://)?([^/]+)@i', $referer, $matches)>0)
				$hostreferer = $matches[1];

			$terminal=getMarcaModelo($_SERVER['HTTP_USER_AGENT']);
			$marca=$terminal['marca'];
			$modelo=$terminal['modelo'];

			$temp=getGeoCodeAndPais($ip);
			$codpais=$temp['code'];
			$pais=$temp['pais'];

			$query="insert into stats (idurl, codpais, referer, ua, hora, pais, marca, modelo, hostreferer) VALUES
					($id, '$codpais', '$referer', '$ua', now(), '$pais', '$marca', '$modelo', '$hostreferer')";
			$rs2=$conn->Execute($query);

			header("HTTP/1.x 301 Moved");
			header("Location: $url");
			exit;
		}else{
			header("Location: http://iraurl.me/index.php?err=1");
			exit;
		}
	}catch(exception $e){
		header("Location: http://iraurl.me/index.php?err=2");
		exit;
	}
}
header("Location: http://iraurl.me/index.php?err=1");

Como veis, si la URL no existe redirigimos al usuario a index.php con un mensaje de error. Necesitaremos dos funciones adicionales, las que nos devuelven información del país de origen de una IP y los datos del terminal del usuario (móvi o web). No entraré en detalles sobre la instalación de Maxmind GeoLite Country o de Wurfl/Tera-Wurfl.

function getGeoCodeAndPais($ip){
	require_once(dirname(__FILE__)."/geoip/geoip.inc");
	$gi = geoip_open("/usr/share/GeoIP/GeoIP.dat",GEOIP_STANDARD);
	$codpais=geoip_country_code_by_addr($gi, $ip);
	$pais=geoip_country_name_by_addr($gi, $ip);
	geoip_close($gi);
	return array("pais"=>$pais, "code"=>$codpais);
}

function getCapabilities($ua){
	require_once(dirname(__FILE__)."/Tera-WURFL/TeraWurfl.php");
	$wurflObj = new TeraWurfl();
	$matched = $wurflObj->GetDeviceCapabilitiesFromAgent($ua);
	$movil = $wurflObj->capabilities;

	return $movil;
}

Estadísticas

La teoría es la misma. Si existe la URL cargamos los datos, si no redirigimos a la home. En nuestra caso utilizamos el componente de pestañas de JqueryUI para organizar los distintos tipos de datos que permitiremos ver y añadiremos los botones para exportar a Excel y PDF.

$idb=substr($_GET['id'], 0, strlen($_GET['id'])-1);

$id=base_decode($idb)+0;
$query="select * from urls where id=$id";
$rs=$conn->Execute($query);
if($rs->recordcount()>0){
	$urlbase="http://iraurl.me/".$idb;
	$url=$rs->fields['url'];
	$id=$rs->fields['id'];
	$htmltitulo=$rs->fields['titulo'];
	if($htmltitulo=="")
		$htmltitulo=$url;
	$query="select count(*) as nregs from stats where idurl=$id";
	$rs=$conn->Execute($query);
	$clicks=$rs->fields['nregs'];
}else{
	header("Location: http://iraurl.me/index.php?err=1");
	exit;
}

Muy sencillo.

stats

Lo complicado en este caso es mostrar las gráficas con FusionCharts. Para cada una debemos añadir algo de código html:

<div id="chartClicks"></div>
<script type="text/javascript">
 var myChart = new FusionCharts("images/Charts/FCF_Column3D.swf", "idChartClicks", "430", "400", "0", "1");
 myChart.setDataURL(escape("xml.php?t=cli&id='.$idb.'"));
 myChart.setTransparent(true);
 myChart.render("chartClicks");
</script>

El script xml.php será el que devuelva los datos en el formato adecuado para FusionCharts. Por ejemplo:

$query="select DAY(hora) as dia, MONTH(hora) as mes, YEAR(hora) as ano, count(*) as nclicks
 from stats
 where idurl=$id
 group by ano, mes, dia
 order by hora";
$rs=$conn->Execute($query);

$xml='<graph caption="Clicks" rotateNames="1" xAxisName="Día" yAxisName="Clicks" showNames="1" decimalPrecision="0" formatNumberScale="0" chartLeftMargin="5" chartRightMargin="5" chartTopMargin="0">';
while($r=$rs->fetchrow()){
 $xml.='<set name="'.$r['dia']."/".$r['mes']."/".$r['ano'].'" value="'.$r['nclicks'].'" color="#A1A1A1" />';
}
$xml.='</graph>';

Os doy sólo un ejemplo, el resto lo montáis por vuestra cuenta :).

Descifrar urls cortas

Todos los sistemas de acortar URL’s funcionan tal y como cuento en este artículo, haciendo un HTTP/301 redirect hacia la url original.

A partir de la URL corta podemos saber cual es la URL original simplemente siguiendo las redirecciones que hace. Muy sencillo con PHP y que, además nos sirve para, integrándola en nuestra API de malware, prevenir posibles problemas con la URL final.

function get_web_page( $url )
{
    $options = array( 'http' => array(
        'user_agent'    => 'spider',
        'max_redirects' => 10,
        'timeout'       => 120,
    ) );
    $context = stream_context_create( $options );
    $page    = @file_get_contents( $url, false, $context );

    $result  = array( );
    if ( $page != false )
        $result['content'] = $page;
    else if ( !isset( $http_response_header ) )
        return null;    // Bad url, timeout

    // Save the header
    $result['header'] = $http_response_header;

    // Get the *last* HTTP status code
    $nLines = count( $http_response_header );
    for ( $i = $nLines-1; $i >= 0; $i-- )
    {
        $line = $http_response_header[$i];
        if ( strncasecmp( "HTTP", $line, 4 ) == 0 )
        {
            $response = explode( ' ', $line );
            $result['http_code'] = $response[1];
            break;
        }
    }

    return $result;
}

$url="";
if(isset($_POST['url'])){
	$url=$_POST['url'];
	$datos=get_web_page( $url );
	if($datos){
		$headers=$datos['header'];
		$urls=array($url);
		foreach($headers as $head){
			$temp=explode(" ", $head);
			if(strtolower($temp[0])=="location:"){
				$urls[]=$temp[1];
			}
		}
		$htmltitle="";
	    	preg_match('/(.*)</title>/is', $datos['content'], $matches);
		if(is_array($matches) && count($matches>0))
			$htmltitle=trim($matches[1]);
	}
}

Ya está, en $urls tendremos la lista de urls que van saltando hasta llegar a la final.

descifrar

Api

Hoy en día todo tiene que tener Api. Para las estadísticas es muy sencillo, el propio XML que generamos para consumir con FusionCharts nos permite que clientes externos se alimenten del mismo. Para crear URL‘s cortas remotamente, simplemente creamos un archivo api.php:

if(isset($_GET['url'])){
	$url=urldecode($_GET['url']);
	try{
        $htmltitle="";
        if(substr($url, 0, 4)!="http")
        	$url="http://".$url;
        $html=file_get_contents($url);
        if($html!=""){
        	preg_match('/(.*)</title>/is', $html, $matches);
			if(is_array($matches) && count($matches>0))
				$htmltitle=trim($matches[1]);
        }
        $query="select * from urls where url='$url'";
        $rs=$conn->Execute($query);
        if($rs->recordcount()>0){
                $id=$rs->fields['id'];
        }else{
                $query="insert into urls (url, titulo) VALUES ('$url', '$htmltitle')";
                $rs=$conn->Execute($query);
                $id=$conn->insert_id();
        }
        $base=base_encode($id);
        $urlbase="http://iraurl.me/".$base;
        echo $urlbase;
    }catch(exception $e){
   		echo "ERROR";
    }
}

Eso es todo. No olvides integrarlo también con el sistema de malware.

Conclusiones

Bueno, y todo este rollo ¿para qué?. Pues muy sencillo, para que veais que hoy en día la tecnología está al alcance de todos, es sencillo y rápido crear un proyecto en Internet, hay de todo por todas las esquinas, la tecnología no es lo importante, lo que verdaderamente cuenta es cómo mueves ese producto tecnológico para rentabilizarlo y obtener un beneficio de él.

Ya tengo mi proyecto superchulo funcionando, sólo me ha costado unas 15 horas de trabajo. Le he puesto un poco de Adsense por aquí y por allí. ¿Y ahora qué? ¿A esperar a que la gente entre y me haga millonario? 😛 Es mucho más complicado que eso como todos sabéis, primero tienes que tener una masa de usuarios elevada que le dé movimiento al proyecto y después tienes que conseguir que la mayoría de ellos sean gente normal, no gente técnica, usuarios avanzados que no pagamos por nada ni pinchamos en publicidad :P.

Hoy en día, en Internet, como en cualquier negocio, las técnicas de marketing y venta son mucho más importantes que la tecnología en sí misma, es duro reconocerlo, pero es así. De nada sirve que tengas el mejor producto del mundo mundial si no consigues que la gente lo utilice y se deje dinero, así de claro. Si tienes los conocimientos adecuados para mover el negocio, no te preocupes, la tecnología te la aporta cualquier partner por un módico precio, pero poner en manos de otro toda la estrategia de ventas de tu negocio no está tan claro ¿no?.

Espero que os sirva de algo el artículo. He querido mostrar, fundamentalmente, cómo utilizando algunas librerías que puedes obtener sin coste puedes hacer algo realmente útil y funcional con muy poco esfuerzo. Seguro que sacáis alguna idea.

Perdón por el rollo :P, al final me ha costado mucho más escribir el artículo que implementarlo.

HTPC (Home Theater PC)

Si hace unas semanas os hablaba del DVD grabador de mis padres, hoy voy a enseñaros uno de mis juguetes, la evolución natural de esos cacharros, el HTPC.

Los HTPC son ordenadores cuya principal función es servir de plataforma de ocio y entretenimiento: televisión, dvd, reproductor de vídeos, música, fotos… todo lo hace él solito y con una gran ventaja: en cualquier momento puedes actualizar las partes de hardware que se hayan quedado obsoletas y tener tu aparato actualizado permanentemente.

El mío tiene ahora dos años y medio y en líneas generales es esto:

El aparato se complementa con un receptor A/V Denon AVR-2802,  7 cajas acústicas (JBL e Infinity),  un subwoofer Yamaha,  un televisor LCD Samsung de 32” y un mando a distancia Logitech Harmony 555.

IMG_6257

La caja

Es el punto más crítico e importante de un aparato de este tipo ya que normalmente querremos colocarlo en el salón de casa y no nos gustaría que desentonase, que se viese la típica caja de ordenador. Desde el principio tenía claro que buscaba una caja que no fuese excesivamente grande, a ser posible del tamaño de un DVD o similar. La mayoría de modelos son, en cuanto a tamaño, como una minitorre de ordenador tradicional en horizontal, muy altos, cosa que no me convencía. Buscando encontré la que terminé comprando, Thermaltake Mozart SX, buen precio, mando a distancia, buena disipación, dos ventiladores incorporados… me pareció perfecta y que era lo que yo buscaba, sin embargo no es oro todo lo que reluce.

Seguramente el sitio más completo donde puedes buscar componentes de este tipo es Sistemas Ibertrónica.

IMG_6258Los componentes

Cuando comencé a montar el aparato me di cuenta de que la escasa altura de la caja penalizaba las opciones de ampliación del aparato con tarjetas PCI ya que no se pueden poner tal cual, son más grandes que la caja. Para solucionarlo viene de serie con un adaptador que expande la ranura PCIe y dos PCI de la placa en horizontal tal y como podéis ver en la imagen de abajo. En general debiera ser más que suficiente, pero no tiene por qué ser así. Otro detalle importante a tener en cuenta es que la placa hay que escogerla cuidadosamente ya que, para poder utilizar el adaptador que os he comentado, la ranura PCIe tiene que estar situada en la posición exacta donde se sitúa el adaptador, de otro modo no podremos pincharla.

En mi caso tengo pinchadas la tarjeta gráfica y la sintonizadora de TV como podéis ver. En la ranura de abajo de todo tengo el adaptador de la placa para sacar la salida de audio digital y no puedo poner nada más que eso, la ranura PCI en sí queda totalmente inutilizada ya que la tarjeta gráfica es más “gorda” de lo habitual y se come el espacio disponible. En mi caso no he tenido problemas, todo lo que necesitaba está ahí, pero si necesitas algo más no tendrás donde ponerlo. De hecho una de las razones de poner la sintonizadora dual fue precisamente la imposibilidad de poner dos sencillas.

La gráfica se conecta al televisor a través de un cable DVIHDMI con lo que la visualización es increíblemente perfecta. Hasta hace un par de meses tenía otra gráfica, pero se estropeó :P, en realidad la estropeé yo. Traía uno de esos pequeños ventiladores que traen las gráficas y al poco tiempo comenzó a hacer un ruido muy molesto, con lo que decidí desconectarlo y que aguantase con el disipador lo que pudiese. Al final fueron más de dos años, no está mal. Para la nueva tenía claro que la quería silenciosa, sin ventilador, pensáoslo bien antes de comprar una con ventilador, los de estas tarjetas suelen ser de muy baja calidad.

blog_IMG_6253Aquí os muestro una panorámica del equipo montado, queda todo muy limpio y accesible, la verdad. Una de las ventajas que tiene es que la fuente de alimentación es de tamaño normal, no tienes que preocuparte si se estropea. En aparatos tipo barebone tienes un grave problema si se estropea la fuente, y tarde o temprano se estropeará. El disco IDE que tengo es porque ya lo tenía :P, tengo ganas de quitar el cable IDE amarillo que se ve y que abulta muchísimo y  sustituirlo por los sencillos Serial ATA, pero mientras funcione tampoco lo vamos a jubilar :).  blog_IMG_6250He pensado más de una vez en ponerle un lector Blu Ray y aprovechar la calidad de imagen que ofrece, pero después pienso, ¿para qué si no alquilo una película desde 2001?.

El software

Hasta hace un par de semanas estaba con Windows XP Media Center Edition 2005, pero lo actualicé a Windows7 viendo las buenas referencias y creedme, es un lujazo verlo funcionar ahora, el buen rendimiento que tiene se une a las prestaciones del nuevo Media Center dando como resultado una plataforma de ocio multimedia que roza la perfección, aunque siempre hay cosas que te gustaría cambiar :). En su día probé otros programas media center, el mejor y más completo es, sin duda, Media Portal, también probé soluciones bajo Linux, MythTV y Freevo, pero no acabaron de convencerme, quizás les de otra oportunidad un día de estos. La última versión de Windows Media Center soporta teletexto, algo que echaba en falta anteriormente :P.

Hace mucho tiempo que quiero integrar Imagenio en el Media Center y tenerlo todo centralizado, pero no había manera de hacerlo hasta que apareció DVBLink for IPTV, un pequeño pero gran software que crea sintonizadoras virtuales para importar en Media Center los canales de televisión IP de forma que quedan integrados como cualquier otro canal de TV, pudiendo programar las grabaciones o verlos con un sólo click en el mando. DVBLink for IPTV cuesta unos 20 euros, un precio más que asequible para las ventajas que aporta, al menos yo lo veo así.

La prueba de fuego que le hice es grabar tres programas simultáneamente (uno en cada sintonizador TDT y otro desde Imagenio) y, mientras tanto, reproducir una película. Todo perfecto, no se puede pedir más.

El mando a distancia

Posiblemente el elemento más importante y el que más desapercibido pasa de todo el sistema si no quieres tener varios mandos a la vez entre tus manos.

Hasta hace un par de meses tenía un One-for-All Kamaleon que compré allá por 2002, pero también se estropeó :P, así que me decidí por un Logitech Harmony 555, muy buena relación calidad-precio y, sobre todo, con soporte directamente para el mando de las cajas Thermaltake.

IMG_6260

La verdad es que la curva de aprendizaje del mando y el tiempo de puesta en marcha completa son muy elevados, una vez tienes claro como funciona llega el momento de ir ajustando cada botón para que haga lo que tu quieres. Aunque tiene el clásico modo de funcionamiento de todos los mandos basado en dispositivos, es decir, funcionar como cada mando por separado, el funcionamiento principal se basa en actividades, lo que despista mucho al principio. Por ejemplo:

  • Actividad Ver TV: Encender la TV, ponerla en modo TDT, mantener el resto de aparatos apagados.
  • Actividad Media Center: Encenter la TV, ponerla en modo HDMI, encender el receptor A/V, ponerlo en modo DVD, encender el Media Center.

De esta manera con un sólo botón preparas el ambiente que vas a utilizar. El modo de actividades permite, además, combinar en el teclado del mando funciones de distintos aparatos, por ejemplo, que el volumen maneje el receptor A/V, otro botón el cambio de fuente en la TV, los de play/stop el Media Center, etc. En el modo dispositivo, cada mando simula ser el mando original completo sin posibilidad de intercalar funciones de otros mandos.

Es importante que el sistema del mando original de tu caja esté puenteado con el botón de encendido de la placa, de esta manera podrás encender y apagar completamente el aparato sin levantarte y con sólo darle a un botón.

Conclusiones

Como casi todo en la vida, tiene sus pros y sus contras.

A favor, sin duda, la versatilidad, la sencillez de manejo y la centralización de todo tu ocio multimedia en un sólo aparato

En contra… quieras o no,  es un ordenador con todos los inconvenientes que conlleva también eso para la gente no técnica. Si estás cansado de que familiares y amigos te llamen cada vez que tienen un problema con su ordenador, ni se te ocurra recomendarles que se pongan un cacharro de estos. Son muy apetecibles para todo el mundo pero dado que son tan abiertos a poder hacer casi de todo, la cantidad de problemas que pueden surgir son muy elevados. Sólo aptos para usuarios muy avanzados.

En cuando al hardware, lo más importante en este tipo de aparatos es la caja, yo he contado mi experiencia. Quizás hoy me  hubiese comprado una más normal en cuanto a tamaño de manera que pudiese utilizar las tarjetas PCI en su forma habitual, no lo sé, pero aún así estoy muy contento con la que tengo, es amplia y está muy bien refrigerada. Con este tipo de cajas hay que tener mucho ojo con la placa que compras, no sirve cualquiera ya que hay que hacer coincidir la ranura PCIe con el hueco adecuado para el adaptador horizontal. De igual modo cuando veas cajas mira una que venga con el display y el mando incorporados, comprarlos después te saldrá más caro y posiblemente no se integren a la perfección.

Para los demás componentes no creo que haya que irse a componentes de última generación, ya veis mi equipo, normalito y se mueve a la perfección.

Finalmente gástate algo de dinero en un buen mando universal que te permita tener todo unificado en un solo aparato, a la larga lo agradecerás, no tengo más que decir, creo que no haya que pensar mucho para darse cuenta de las ventajas :P.

Liberar modem 3G Huawei E220 de Vodafone

Facilísimo. Y de paso, al actualizar el firmware, lo dejamos preparado para soportar 7,2Mbps. Este es un proceso delicado ya que si se corta a mitad la instalación del firmware se pierde el modem para siempre, hazlo bajo tu responsabilidad. Hay muchos sitios por ahí donde lo cuentan, yo os lo resumo :).

Desinstalamos completamente el software de Vodafone (o de cualquier otra operadora) y descargamos dos paquetes de actualización, el del firmware en sí mismo y el del software del del modem, ya que no queremos que siga cargando los de Vodafone.

  • El firmware te lo doy yo :P.
  • Para el software vas aquí y escoges el que prefieras:
    • El primero, HOSTB107D05SP00C03…, instala el Mobile Connect.
    • El último, UTPSB002D03SP16C03…, el Mobile Partner.

Yo me quedo con el Partner, me parece más sencillo e intuitivo.

Además necesitarás el Qmat (QC Mobile Analysis Tool) y esto otro.

El procedimiento es muy simple. Conectas el modem y cancelas la instalación de los drivers. A continuación ejecutas los dos archivos de actualización, firmware y software. Entre uno y otro (y al terminar el segundo también) conviene desconectarlo y volver a conectarlo (cancelando nuevamente la instalación de los drivers de Vodafone). Ya casi hemos terminado :P.

Instala ahora el Qmat y ejecútalo. En el menú superior ve a Hardware Forensics y Use Mobile Ports.

forensics

En la ventana que aparece seleccionas, en el primer combo, el puerto donde está tu modem, no tiene pérdida, pondrá algo parecido a COMxx – HUAWEI Mobile Connect – 3G PC UI Interface (COMxx). Pulsas en “Send” y deberían salir unos números en la caja de texto de abajo, eso quiere decir que se comunica correctamente con el modem.

En el combo que muestro desplegado en la imagen de abajo seleccionas “Display NVItem” y a la izquierda, en “Item” introduces 1156 y pinchas en “Let’s go“. Si todo va bien en la ventana de texto aparecerán unos números y, al final de todo, un código de 6 a 8 cifras, ése es tu código de desbloqueo. Cópialo. Si tienes algún problema prueba a desconectar el modem y volver a conectarlo o, incluso, a instalar los drivers nuevos cerrando el programa cuando arranque.

qcAhora vamos a la última utilidad, al CardLock_UnLock. Lo abres y te identificará el IMEI de tu teléfono. Pegas en la caja de texto el código que obtuviste en el paso anterior y voilà, el modem está liberado.

unlockLo he probado hoy mismo con SIM’s de Orange y Movistar, aunque la idea es utilizarlo con la de Simyo :). La configuración que hay que utilizar en este último caso es la siguiente:

partner

APN: gprs-service.com
Usuario: (vacío)
Clave: (vacío)
Número: *99***1#

Ya podemos utilizar el modem con la operadora que nos venga en gana.

Como última curiosidad veremos cómo dejar el software en castellano ya que la actualización lo deja en inglés y no podemos modificar los archivos de la memoria del modem. Editamos el archivo C:Program FilesMobile PartnerRunInfo.ini de manera que donde pone:

[language]
active=_en-us

ponga

[language]
active=_es-es

Listo!

Google Safe Browsing API con PHP, filtra las urls potencialmente peligrosas

Preparando un pequeño nuevo proyecto del que os hablaré en mi siguiente artículo, tuve un problema que me llevó a escribir este otro. En un momento de la aplicación el usuario debe escribir una url (el resto es secreto aún :P). Pues bien, ya durante las pruebas aparecieron los típicos graciosos que utilizan url’s potencialmente peligros, de esas que no hacen cosas nada buenas. Buscando un poco dí con Google Safe Browsing API, el sistema que Google pone a nuestra disposición para comprobar contra sus sistemas si tienen una determinada url recogida dentro de su base de datos de urls peligrosas, la misma que utilizan ellos para lanzar a veces esos avisos de “¿seguro que quieres ver esto?”.

Esta API funciona de modo diferente a la mayoría de aplicaciones de Google que residen directamente en la nube y lanzamos consultas contra su API. En este caso nos provee de su base de datos de urls potencialmente peligrosas para que las guardemos localmente y hagamos las consultas directamente en nuestros sistemas, lo único que debemos hacer es actualizar periódicamente esos datos, eso sí, con algunas limitaciones que nos impone Google, como que no se pueden renovar en intervalos inferiores a 30 minutos. Actualmente esta base de datos tiene más de trescientos mil registros.

Vamos a explicar como utilizarla. Lo primero que debemos hacer es, como en todo lo que hace Google, crear una “API KEY” y una sencilla tabla en nuestro servidor MySQL donde alojaremos la información que nos envíe Google.

CREATE TABLE IF NOT EXISTS `malware` (
`malware_hash` varchar(32) NOT NULL,
PRIMARY KEY  (`malware_hash`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Creamos ahora una tarea que se ejecutará periódicamente y que leerá, siguiendo las especificaciones adecuadas, los datos que devuelve Google y los insertará/eliminará de nuestra base de datos. Yo, como ya he comentado en otras ocasiones, utilizo Adodb para el acceso a datos.

include(dirname(dirname(__FILE__))."/includes/funciones.inc.php");

$api_key = "TU KEY";
$google_url = "http://sb.google.com/safebrowsing/update";

//open the remote URL
$target = "$google_url?client=api&apikey=$api_key&version=goog-malware-hash:1:-1,goog-black-hash:1:-1";

$handle = fopen("$target", 'r')
	or die("Couldn't open file handle " . $target);
if ($handle) {
    while (!feof($handle)) {
        $line = fgets($handle);
        if (substr($line,0,1) != '[') {
        	$operation = (substr($line,0,1)); //get the '+' or '-'
        	$hash = trim(substr($line,1)); //get the md5 hash
        	$hash = qstr($hash); //escape
        	if ($operation == '+'){
        		$sql = "insert ignore into malware (malware_hash) VALUES ('".$hash."')";
        		$conn->Execute($sql);
        	}else{
        		$sql = "delete from malware where malware_hash = '".$hash."'";
        		$conn->Execute($sql);
        	}
        }
    }
    fclose($handle);
}

Lo que hacemos simplemente es eliminar los hash que nos indica que debemos quitar y añadir los nuevos. Vamos a probar el sistema. Utilicé varias clases y sistemas que había por ahí para utilizar con PHP pero ninguno me parecía lo suficientemente completo hasta que encontré este. Su funcionamiento es muy sencillo. De nuevo está hecho usando Adodb, cámbialo según tus requerimientos.

$class = new GoogleSafeBrowsing('tu_key', true);
$safe=$class->lookupsFor("http://blog.osusnet.com");

Nos devolverá un booleano que indica si la url es segura o no. Si probamos con alguna de éstas obtendremos un bonito false :). Espero que os sirva.

Monitorizando aplicaciones web con rrdtool

Vamos con uno de esos artículos que le gustan a Álvaro basados en experiencias del trabajo.

En uno de los proyectos en los que estamos trabajando actualmente y del que ya os he hablado, teníamos un problema de rendimiento en una aplicación web, pero no teníamos localizado el punto donde se nos iba el tiempo. Incluso había momentos que la aplicación iba bien pero en otros era lamentable. Se nos ocurrió, entonces, hacer un sencillo sistema de monitorización de los distintos puntos sensibles que manejábamos y crear con ellos una serie de gráficas que nos permitiese hacer un seguimiento visual de los potenciales problemas.

Nuestra aplicación se basa en llamadas a distintos webservices a partir de las cuales se genera un XML al que se le aplican transformaciones XSL para obtener el código html que se muestra al usuario. Algunas de esas llamadas se guardan en la sesión del usuario, con lo que en sucesivos accesos a la aplicación el rendimiento es infinitamente mejor, así que necesitábamos que el control se hiciese con sesiones limpias para que no se nos desvirtuasen las gráficas.

Nuestro sistema consiste en generar por un lado, en la aplicación web, una serie de datos estadísticos que nos ayuden a buscar los puntos conflictivos. Estos datos se insertarán periódicamente en una base de datos rrd (round robin database) que finalmente utilizaremos para generar las gráficas.

Preparando la aplicación web

El primer paso es, entonces, modificar el código de la aplicación para que vaya generando los tiempos de respuesta en los distintos puntos que queremos controlar. Para facilitar las cosas decidimos que cada vez que se pase un parámetro predefinido en la url, en vez de mostrar la página html correspondiente mostraremos las estadísticas que después controlaremos. En nuestro caso decidimos que la url de estadísticas fuese:

http://tudominio.com/index.php?stats

Es decir, añadiendo el parámetro stats a cualquiera de las urls del site obtendremos los tiempos de respuesta que necesitamos.

Tenemos una función que genera el tiempo exacto del servidor cada vez que se llama, así que la utilizaremos después de ejecutar cada uno de los webservices y guardaremos esos datos. En mi caso tengo que obtener ocho puntos de control además del tiempo de inicio y el tiempo final, algo así:

function getTime() {
 $mtime2 = explode(" ", microtime());
 $endtime = $mtime2[0] + $mtime2[1];
 return $endtime;
}
$isStats=isset($_GET['stats']);
if($isStats)
    $time_inicio=getTime();
....
if($isStats)
    $time_ws1=getTime();
.....
if($isStats)
    $time_ws2=getTime();
....
if($isStats)
    $time_ws3=getTime();
....
if($isStats)
    $time_ws4=getTime();
....
if($isStats)
    $time_ws5=getTime();
....
if($isStats)
    $time_ws6=getTime();
....
if($isStats)
    $time_ws7=getTime();
....
if($isStats)
    $time_ws8=getTime();
....
if($isStats)
    $time_final=getTime();
......

Finalmente devolvemos una línea con todos los valores intermedios, es decir, para cada indicador sería el valor del tiempo de ese indicador menos el del anterior, esto nos dará el valor real del tiempo que ha pasado entre el indicador anterior y el actual. Para facilitar la posterior integración de estos valores con rrdtool lo que haremos será generarlos ya en el formato adecuado, separando cada valor con “:”.

if(isset($_GET['stats'])){
	echo number_format($time_ws1-$time_inicio, 7).":".
			number_format($time_ws2-$time_ws1, 7).":".
			number_format($time_ws3-$time_ws2, 7).":".
			number_format($time_ws4-$time_ws3, 7).":".
			number_format($time_ws5-$time_ws4, 7).":".
			number_format($time_ws6-$time_ws5, 7).":".
			number_format($time_ws7-$time_ws6, 7).":".
			number_format($time_ws8-$time_ws7, 7).":".
			number_format($time_final-$time_inicio, 7);
}

Si ahora llamamos a nuestra url obtendremos los valores de nuestros indicadores. El último valor nos devolverá el tiempo completo de ejecución del script entre los valores $time_inicio y $time final.

0.0281749:0.5443010:0.3132501:2.9015441:0.0000241:0.6517198:5.5171580:0.0000379:10.0677590

Vemos cómo esta llamada ha tardado diez segundos, de los cuales 5,5 corresponden al indicador ws7.

Tenemos el primer paso preparado, vamos ahora a crear la base de datos donde almacenaremos toda la información estadística de la url. Tendremos una tarea que se ejecutará cada minuto y que recuperará los valores en ese momento de los identificadores que se han definido.

Creando la base de datos

Para guardar la información he escogido rrd, el mismo que utilizan aplicaciones como mrtg o Cacti y el utilizado en la mayoría de sistemas de monitorización. Según reza en su propia web, es el sistema estándar para gráficas y logs de series de datos temporales.

Para que todo vaya bien nuestra base de datos rrd debe tener la misma cantidad de fuentes de datos que el sistema de estadísticas, nueve en este caso, los ocho puntos de control más el total. Además tenemos que pensar qué graficas vamos a crear. Yo quiero cuatro: última hora, últimas seis horas, últimas 24 horas y última semana. No necesito hacer un sistema con histórico avanzado, lo que quiero en este caso es controlar el rendimiento, así que no necesitaré gráficas mensuales ni anuales, pero se podrían generar de igual modo.

Para crear nuestro archivo rrd ejecutamos el comando siguiente:

rrdtool create estadisticas.rrd --step 60
DS:ws1:GAUGE:120:0:U
DS:ws2:GAUGE:120:0:U
DS:ws3:GAUGE:120:0:U
DS:ws4:GAUGE:120:0:U
DS:ws5:GAUGE:120:0:U
DS:ws6:GAUGE:120:0:U
DS:ws7:GAUGE:120:0:U
DS:ws8:GAUGE:120:0:U
DS:total:GAUGE:120:0:U 

RRA:AVERAGE:0.5:1:60
RRA:AVERAGE:0.5:1:360
RRA:AVERAGE:0.5:5:144
RRA:AVERAGE:0.5:10:202
RRA:MAX:0.5:1:60
RRA:MAX:0.5:1:360
RRA:MAX:0.5:5:144
RRA:MAX:0.5:10:202
RRA:MIN:0.5:1:60
RRA:MIN:0.5:1:360
RRA:MIN:0.5:5:144
RRA:MIN:0.5:10:202

Vamos a explicarlo un poco.

  • –step=60: indica que los datos los actualizaremos cada 60 segundos. Nuestro sistema guardará estadísticas cada minuto.
  • DS:ws1:GAUGE:120:0:U: Fuentes de datos, hay una línea de este tipo para cada valor que vamos a mantener.
    1. wsX es el nombre que tendrá esa fuente.
    2. GAUGE es el tipo de fuente de datos, puedes consultar la documentación completa para tener más idea.
    3. 120 es el parámetro heartbeat, tiempo máximo entre dos actualizaciones antes de que se asuma que es desconocido. He puesto el doble de step, si pasado ese tiempo no hay una actualización es que se ha perdido por cualquier razón.
    4. Los últimos datos son los valores mínimo y máximo que habrá en la fuente de datos. En nuestro caso el mínimo será cero y el máximo no lo sabemos, le ponemos U de unknown.
  • RRA:AVERAGE:0.5:1:60: Round robin archives. El objetivo de una rrd es guardar datos en los rra que mantienen valores y estadísticas para cada una de las fuentes de datos definidas.
    1. AVERAGE/MAX/MIN/LAST: la función utilizada para consolidar los datos. Como su propio nombre indica son la media, máximo, mínimo y el último valor.
    2. 0.5; el xff (xfiles factor). De 0 a 1, indica el número de datos UNKNOWN que se permiten por intervalo de consolidación.
    3. 1: define el número de datos utilizados para crear un intervalo de consolidación.
    4. 60: Cuantos datos se mantienen en cada RRA. Esto depende de la gráfica que vamos a generar y se relaciona con el dato anterior.

La última parte es la más complicada de comprender. Intentaré explicarlo lo mejor que pueda para que se entienda. En nuestro caso tomamos un dato cada 60 segundos y queremos tener la gráfica de la última hora (60 segundos x 60 minutos, 3600 segundos). Necesitamos, por tanto, 60 puntos de control (3600/60 segundos de intervalo), utilizaremos cada uno de los datos que se toman. Esto es lo que indica la creación RRA:AVERAGE:0.5:1:60, queremos utilizar 60 puntos de control tomados cada 60 segundos. Este es bastante fácil, veamos el siguiente.

RRA:MIN:0.5:1:360

Ahora estamos guardando 360 puntos generados cada 60 segundos, es decir, cubrimos 60×360=21600 segundos=6 horas. Era sencillo.

RRA:AVERAGE:0.5:5:144

En la siguiente gráfica queremos obtener datos de las últimas doce horas (12 horas x 60 minutos x 60 segundos, 43200 segundos). Ahora ya no necesitamos saber el comportamiento exacto en cada momento sino que necesitamos una visión global de lo que ha ocurrido durante esas doce horas, tomamos, por tanto, un punto de control cada cinco datos de registro, es decir, estamos manteniendo un dato cada 5×60 segundos, 300 segundos. Necesitaremos por tanto 43200/300=144 valores para obtener nuestra gráfica de las últimas 12 horas

RRA:MIN:0.5:10:202

La última gráfica es semanal, 7x24x60x60=604800 segundos. Acepto que con tener un valor cada 10 minutos será suficiente, tendré entonces un valor cada 10×300=3000 segundos. 604800/3000=~202.

Es algo difícil de entender al principio, pero una vez lo piensas, todo tiene sentido.

Guardando las estadísticas

Tenemos ya nuestra base de datos preparada, creemos ahora la tarea que guardará en este archivo rrd los datos estadísticos. En mi caso es una tarea PHP que lo hace todo muy sencillo.

tarea.php

$url="http://dominio.com/?stats";
$cont=file_get_contents($url);
system("rrdtool update estadisticas.rrd N:".$cont);

Añadimos la tarea al cron para que se ejecute cada 60 seguntos.

*/1 * * * * /usr/bin/php /path/to/tarea.php > /dev/null 2>&1

¡Y ya está! Nuestra base de datos de estadísticas se está alimentando automáticamente.

Creando las gráficas

Lo más importante a fin de cuentas, lo que realmente vamos a utilizar. Rrdtool viene con un script de ejemplo que debes colocar en el directorio cgi-bin de tu servidor web y realizar los ajustes adecuados. Yo he puesto como ejemplo la gráfica de la última hora, las demás se harían igual variando el parámetro “–start” a los segundos anteriores que queremos mostrar.

/cgi-bin/stats.cgi

#!/usr/bin/rrdcgi

Tu Dominio - Home Stats

<RRD::GRAPH /usr1/www/www.genteirc.com/htdocs/imagenes/daily-visitas.png --imginfo '<img src="http://tudominio.com/%s" alt="" width="%lu" height="%lu" />' --start -3600 --end -0 -h 400 -w 600 --vertical-label="Tiempo" --title="Last Hour" -a PNG -l 0 DEF:ws1=/path/to/estadisticas.rrd:ws1:AVERAGE LINE1:ws1#00FF00:WebSer1t GPRINT:ws1:AVERAGE:"Media : %.4lft" GPRINT:ws1:MAX:"Max : %.4lft" GPRINT:ws1:MIN:"Min : %.4lfl" DEF:ws2=/path/to/estadisticas.rrd:ws2:AVERAGE LINE1:ws2#800000:WebSer2t GPRINT:ws2:AVERAGE:"Media : %.4lft" GPRINT:ws2:MAX:"Max : %.4lft" GPRINT:ws2:MIN:"Min : %.4lfl" DEF:ws3=/path/to/estadisticas.rrd:ws3:AVERAGE LINE1:ws3#FF8000:WebSer3t GPRINT:ws3:AVERAGE:"Media : %.4lft" GPRINT:ws3:MAX:"Max : %.4lft" GPRINT:ws3:MIN:"Min : %.4lfl" DEF:ws4=/path/to/estadisticas.rrd:ws4:AVERAGE LINE1:ws4#400080:WebSer4t GPRINT:ws4:AVERAGE:"Media : %.4lft" GPRINT:ws4:MAX:"Max : %.4lft" GPRINT:ws4:MIN:"Min : %.4lfl" DEF:ws5=/path/to/estadisticas.rrd:ws5:AVERAGE LINE1:ws5#0000FF:WebSer5t GPRINT:ws5:AVERAGE:"Media : %.4lft" GPRINT:ws5:MAX:"Max : %.4lft" GPRINT:ws5:MIN:"Min : %.4lfl" DEF:ws6=/path/to/estadisticas.rrd:ws6:AVERAGE LINE1:ws6a#00FFF0:WebSer6t GPRINT:ws6a:AVERAGE:"Media : %.4lft" GPRINT:ws6:MAX:"Max : %.4lft" GPRINT:ws6:MIN:"Min : %.4lfl" DEF:ws7=/path/to/estadisticas.rrd:ws7:AVERAGE LINE1:ws7#FF00FF:WebSer7t GPRINT:ws7:AVERAGE:"Media : %.4lft" GPRINT:ws7:MAX:"Max : %.4lft" GPRINT:ws7:MIN:"Min : %.4lfl" DEF:ws8=/path/to/estadisticas.rrd:ws8:AVERAGE LINE1:ws8#FFFF00:WebSer8t GPRINT:ws8:AVERAGE:"Media : %.4lft" GPRINT:ws8:MAX:"Max : %.4lft" GPRINT:ws8:MIN:"Min : %.4lfl" DEF:total=/path/to/estadisticas.rrd:total:AVERAGE LINE2:total#FF0000:'Total t' GPRINT:total:AVERAGE:"Media : %.4lft" GPRINT:total:MAX:"Max : %.4lft" GPRINT:total:MIN:"Min : %.4lfl" >

Para cada línea que queremos mostrar en la gráfica le indicamos de qué archivo rrd saldrán los datos y la fuente (DS) que vamos a utilizar. Añadimos además como leyenda los datos de media, máximo y mínimo de cada fuente, de manera que en una sola gráfica tenemos toda la información necesaria. Si ahora vas a la url donde has dejado el cgi tendrás:

http://tudominio.com/cgi-bin/stats.cgi

stats

Y tu te preguntarás, con lo pesado que te pones con Cacti, ¿por qué no meterla ahí también? Buena pregunta :P. Si tengo un ratito os enseño cómo integrarlo también con Cacti :).

Me he pasado con este artículo, no se lo va a leer nadie :P.

Google compra Admob y se hace con el mercado de la publicidad en el móvil

Acabo de enterarme, Admob, la empresa líder en publicidad en el móvil, ha sido adquirida hoy mismo por Google, cómo no, por nada más y nada menos que 750 millones de dólares.

admob

Utilizo Admob desde hace 3 años y es, sin lugar a dudas, el servicio de publicidad para terminales móviles más utilizado tanto por anunciantes como por publicadores. Según mi experiencia es, incluso, el más efectivo. Hasta crearon su propio sistema de estadísticas al estilo Google Analytics pues vieron rápidamente que había un hueco en lo que a terminales móviles se refería.

Google hizo hace un par de años un intento por lanzarse a la publicidad en el móvil pero no consiguió ni llegar a los talones de Admob, así que, para qué pensárselo, mejor compramos algo que ya funciona.

Ahora se abre una nueva puerta, en su afán por acaparar el marcado publicitario Google no ha dudado en comprar a su mayor competidor en algo que ya está explotando, y es que cada día más y más gente se sube al carro del Internet Móvil, con lo que las expectativas a corto plazo son impresionantes. Cierto es que llevamos ya algunos años escuchando que el futuro de La Red pasa por el móvil, pero creo que ahora, más que nunca, es cierto. Las nuevas hornadas de terminales aprovechan al máximo las posibilidades de navegación, lo que unido a las tarifas planas de datos, que han tardado en llegar pero al fin están aquí, están provocando una avalancha de nuevos usuarios de sitios para móviles.

¡Qué miedo da Google! ¿Qué será lo próximo?

Monitorizando Qmail con Cacti

Hace ya mucho tiempo que no hablamos de Cacti, mi sistema de monitorización preferido. Hoy veremos como controlar con él la actividad de nuestro Qmail.

Habitualmente la instalación de Qmail se realiza utilizando las daemonTools, pero nunca me han gustado mucho así que lanzo el servicio qmail-send con un script propio, mientras que los servidores smtpd y pop3d los integro con xinetd. Ya sé que a muchos no les gustará esto último, pero para mi es muy cómodo :P. El caso es que con este sistema, estos últimos demonios escapan del control de multilog, el sistema de logging de las daemontools, mala suerte. Veremos en este artículo cómo monitorizar el servicio de envío y recepción de Qmail pero no veremos nada relativo a smtp o pop3 debido a que no tengo esos logs. Si los tuvieses sería muy sencillo añadir estas gráficas de actividad, incluso las de spamassasin o clamav.

Supondremos que nuestro multilog deja sus archivos en /var/log/qmail, sustituye esto por tu ruta real, esa es la mía :P.

Lo primero que tendremos que hacer será instalar el paquete qmailmrtg7. Este software permite crear gráficas mrtg a partir de los logs de multilog. Será la herramienta que utilicemos para obtener los datos. NO es necesario que configuremos todo el qmailmrtg7, sólo necesitamos el ejecutable. Podemos comprobar que funciona bien haciendo:

[osus@servidor ~]#/usr/local/bin/qmailmrtg7 q /var/qmail/queue
636
0

Como se puede ver, nos devuelve dos líneas, en la primera los mensajes en cola y en la segunda los mensajes sin procesar. Esto marcha bien.

Configuremos ahora el servidor snmp para que podamos obtener estos valores a través de Cacti, para ellos simplemente añadimos al archivo de configuración las siguientes líneas:

/etc/snmpd/snmpd.conf

extend .1.3.6.1.4.1.18689.1.3 qmail-message-status /usr/local/bin/qmailmrtg7 s /var/log/qmail
extend .1.3.6.1.4.1.18689.1.3 qmail-bytes-transfer /usr/local/bin/qmailmrtg7 b /var/log/qmail
extend .1.3.6.1.4.1.18689.1.3 qmail-smtp-concurrency /usr/local/bin/qmailmrtg7 t /var/qmail/supervise/qmail-smtpd/log/main
extend .1.3.6.1.4.1.18689.1.3 qmail-smtp-sessions /usr/local/bin/qmailmrtg7 a /var/qmail/supervise/qmail-smtpd/log/main
extend .1.3.6.1.4.1.18689.1.3 qmail-queue /usr/local/bin/qmailmrtg7 q /var/qmail/queue
extend .1.3.6.1.4.1.18689.1.3 qmail-messages /usr/local/bin/qmailmrtg7 m /var/log/qmail
extend .1.3.6.1.4.1.18689.1.3 qmail-concurrency /usr/local/bin/qmailmrtg7 c /var/log/qmail

Ahora podemos probar que funciona buscando, por ejemplo, el tamaño de la cola que veíamos antes:

[osus@servidor ~]# snmpwalk -Os -c COMUNIDAD -v 2c SERVIDOR  .1.3.6.1.4.1.18689.1.3.4.1.2.11.113.109.97.105.108.45.113.117.101.117.101.1
enterprises.18689.1.3.4.1.2.11.113.109.97.105.108.45.113.117.101.117.101.1 = STRING: "636"

Debes sustituir COMUNIDAD y SERVIDOR por los datos de tu servidor snmpd. Ya tenemos todo preparado, vamos a por las gráficas.

Cuando comencé a buscar plantillas de Cacti para Qmail encontré bastantes referencias, pero no conseguí que funcionasen, unas simplemente me daban errores al importarlas y otras tenían los OID del snmp incorrectas y no recopilaban nada, así que lo que hice fue una mezcla de todas ellas modificadas por mi. Aquí podéis descargar mis cacti_qmail_templates, espero que os sirvan a todos. Lo importante es que los “data templates” se importen bien, a partir de ellos puedes crear tus propias gráficas. Yo os adjunto una plantilla que crea una gráfica común de todos los indicadores, cacti_graph_template_qmail_-_statistics.xml, cuyo resultado es lo que véis:

qmail_cactiPero también podéis crear gráficas independientes para cada indicador, mucho más claras, sin duda, que todas juntas. Debéis importar, en este caso, todas las demás plantillas que aparecen en el archivo.

qmail_cacti2Eso es todo, ahora podemos ver el tráfico de nuestro MTA visualmente y buscar potenciales problemas.

Descarga los templates de cacti para Qmail.

Cómo comprobar SMTP autentificado

Siempre me pasa lo mismo, cada vez que hago algún cambio en el servidor de correo (Qmail) me vuelvo tonto para probar que funciona bien ya que con el SMTP autentificado no es tan sencillo como introducir tu usuario y tu contraseña.

Tenemos dos métodos para probarlo, el primero requiere de una sola cadena en base64 con el usuario y la clave del usuario. Para sacarlo utilizamos una pequeña expresión en PERL:

perl -MMIME::Base64 -e 'print encode_base64("00USUARIO00CLAVE")'
AFVTVUFSSU8AQ0xBVkU=

Debes sustituir USUARIO y CLAVE por los datos adecuados. Presta atención a los 00 al inicio de cada parámetro, deben seguir ahí. Si tu usuario lleva el dominio completo debes añadir una barra inversa a la @, por ejemplo [email protected].

Con este dato ya podemos probar el servidor:

[osus@host ~]# telnet dominio.com 25
Trying 10.10.10.10...
Connected to dominio.com (10.10.10.10).
Escape character is '^]'.
220 dominio.com ESMTP
EHLO dominio.com
250-dominio.com
250-AUTH=LOGIN PLAIN
250-PIPELINING
250 8BITMIME
AUTH PLAIN AFVTVUFSSU8AQ0xBVkU=
235 ok, go ahead (#2.0.0)

Como véis, después de saludar al servidor (ehlo dominio.com) le decimos en una sóla línea que queremos autentificarnos y cuales son nuestros datos (AUTH PLAIN tucadenaBase64). Eso es todo. Si la autentificación no fuese correcta recibiríamos la respuesta adecuada.

AUTH PLAIN AFVTVUFSSU8AQ0xBVkU=
535 authorization failed (#5.7.0)

En el segundo método enviamos el usuario y la claves por separado, también en base64.

perl -MMIME::Base64 -e 'print encode_base64("usuario")'
dXN1YXJpbw==
perl -MMIME::Base64 -e 'print encode_base64("clave")'
Y2xhdmU=

Para probarlo debemos enviar por un lado el nombre de usuario y por otro su contraseña:

[osus@servidor ~]# telnet dominio.com 25
Trying 10.10.10.10...
Connected to dominio.com (10.10.10.10).
Escape character is '^]'.
220 dominio.com ESMTP
ehlo dominio.com
250-dominio.com
250-AUTH=LOGIN PLAIN
250-PIPELINING
250 8BITMIME
AUTH LOGIN
334 VXNlcm5hbWU6
dXN1YXJpbw==
334 UGFzc3dvcmQ6
Y2xhdmU=
235 ok, go ahead (#2.0.0)

Como véis es parecido al primer sistema pero aquí enviamos el usuario y la contraseña. Después de saludar (ehlo dominio.com) le indicamos que queremos autentificarnos (AUTH LOGIN). Ahora nos pide el usuario (VXNlcm5hbWU6 es Username: en base64) y la clave (UGFzc3dvcmQ6 es Password:). Hemos terminado.

El resto del protocolo SMTP funciona como si estuviese sin autentificar.

Problemas con checkpassword

Otro de los puntos que suele darme algún tipo de problema es la configuración del sistema que utilizan tanto qmail-smtpd como qmail-pop3d para autentificarse: checkpassword. Por defecto no se puede probar directamente ya que lo importante es el código de error que devuelve. Para comprobar su funcionamiento debemos hacer:

perl -e 'printf"%s%sY123456","USUARIO","CLAVE"' | /bin/checkpassword id 3<&0
uid=511(USUARIO) gid=100(users) groups=100(users)

Si la autentificación es correcta nos devolvera algo similar a lo que se indica, si no, no obtendremos respuesta.

Es importante tener en cuenta que el usuario que ejecuta los demonios de Qmail (normalmente qmaild) debe poder ejecutar checkpasswd y que éste debe tener permiso setuid de manera que se ejecute siempre como root independientemente del usuario que lo haga en realidad. Esto debe ser así ya que tiene que acceder a /etc/passwd.

chmod 4755 /bin/checkpassword

Espero que a alguien le sirvan estos apuntes :P, a mi seguro que me ahorrarán tiempo dentro de algunos meses cuando lo necesite de nuevo.

SCRUM de dos equipos en distintas ciudades

El pasado mes de mayo comenzamos el desarrollo de un nuevo proyecto que ha terminado recientemente, al menos la primera fase del mismo. Partimos casi de cero, los requerimientos eran muy básicos y poco documentados, pero parte del equipo teníamos en la cabeza exactamente lo que teníamos que hacer, de hecho era plasmar en una única aplicación todo nuestro trabajo de los últimos cuatro años.

Desde el principio nadie tuvo dudas, SCRUM era la mejor metodología posible para cumplir los plazos que nos habían impuesto. Planteamos sprints de dos semanas y dailys diarios (obviamente) a las 10 de la mañana de alrededor de 10 minutos. Como scrum master se quedó uno de nuestros project managers y como product owner otro del departamento de operaciones. La mayoría nos habíamos leído ya el Scrum desde las trincheras, pero una cosa es la teoría y otra muy diferente la práctica, y ahí casi nadie teníamos experiencia.

No trataré en este artículo de enseñaros SCRUM, no soy un experto, como mucho un poco “evangelizador” :P, simplemente trataré de explicar mis sensaciones tras cinco meses de SCRUM intensivo.

El Equipo

En el proyecto participaron dos equipos de desarrollo, uno en Valencia de 6 personas y otro en Madrid en el que llegaron a trabajar más de 30. Ninguna de estas casi 40 personas tenía disponibilidad completa para este proyecto sino que hubo que redistribuir toda la carga de trabajo para, con el mismo equipo, asumir un nuevo proyecto de cinco meses de duración. Salió bastante bien. Los dailys se hacían vía conferencia entre una sala de reuniones en Madrid y otra en Valencia. Se hicieron, además, infinidad de reuniones a dos bandas entre los dos equipos para decidir funcionalidades, discutir los puntos donde no había acuerdo y resolver dudas.  No sólo participaban desarrolladores sino también jefes de equipo, project managers, diseñadores, “expertos” en usabilidad, gente de testing y calidad, documentalistas, gente de sistemas, de datawarehouse

En la parte tecnológica tuvimos otro grave problema. Desde Madrid desarrollaban en .NET y desde Valencia en PHP. ¿Cual es el problema? :P. Se diseñó la arquitectura de manera que la aplicación trabajase indistintamente en uno u otro lenguaje. Dentro de un mismo entorno web, unos módulos se cargan de un lado y los otros del otro de forma completamente transparente al usuario. Se diseñó un sistema de single sign on que pudiesen compartir y consultar ambas tecnologías y, asociado a éste, todo un mecanismo de seguridad de acceso a distintos menús y opciones de la aplicación. El resultado fue formidable, todo funciona perfectamente de una manera integrada, robusta, eficiente y segura.

La Pila de Producto (product backlog)

Con la poca documentación que se tenía al principio se elaboró una pequeña pila de producto que fue creciendo a medida que las tareas de análisis evolucionaban. A esto debemos sumar requerimientos que iban llegando por parte de los departamentos comerciales y de operaciones. Al final el backlog era considerable. En agosto hubo que decidir quitar algunas historias de usuario ya que era imposible acometer todo lo que se quería, de hecho los requerimientos a estas alturas eran infinitamente superiores a los que se habían supuesto en mayo. Aún así, eliminando cosas, se hizo un producto mucho más ambicioso de lo esperado. Las historias de usuario eliminadas no se olvidaron, simplemente se trasladaron a una segunda versión.

Para el seguimiento del SCRUM, en vez de la clásica pizarra y dada la dispersión de los equipos, decidimos utilizar una herramienta de software. Comenzamos con un sencillo Excel y a mitad del proyecto incorporamos ScrumWorks, no creo que sea el programa ideal pero cumple su función.

Los Sprints

Los tres primeros sprints fueron prácticamente de análisis. Se hicieron documentos funcionales y orgánicos de todos los módulos requeridos y se perdió bastante tiempo definiendo la arquitectura de la aplicación, en realidad de las dos aplicaciones (.NET y PHP). Todo esto nos llevó a plantarnos a mediados de junio, tras 3 sprints, con muy poco que mostrar en la demo, y nos condujo a la consabida reprimenda de los que mandan, nos estábamos desviando (teóricamente) de la planificación estipulada. Sin embargo el tiempo nos daría la razón, el tiempo perdido en el diseño de la arquitectura se recuperó con creces en sprints siguientes y llegamos a la fecha con el proyecto terminado. Bueno, en realidad dejamos para el final un pequeño detalle, el rendimiento, que se solucionó en un sprint adicional.

A todo esto hemos de añadir, como ya he dicho, que el equipo no estaba dedicado al proyecto, cada día variaba la gente que entraba al daily ya que no todos estaban trabajando en el proyecto en ese momento. A eso hay que sumarle lo que llamo “contingencias del trabajo diario“, es decir, nuevos proyectos que van surgiendo durante todo ese tiempo y que implican dedicarle tiempo que tienes que quitarle a lo verdaderamente importante. Por si eso no fuera poco, a finales de junio se nos informa que una parte de la aplicación, un módulo de reporting, tiene que estar operativa a finales de julio puesto que se necesita para dar servicio en otros proyectos. Esto, que de palabra suena sencillo, nos obligó a modificar la planificación, el product backlog y la pila de sprint ya que el funcionamiento de este módulo implicaba a otros (login, seguridad, diseño…). Y aún así cumplimos todos los plazos, increíble. Eso sí, otros proyectos tuvo que decidirse entre descartarlos o aprobar una desviación de una o dos semanas en el proyecto del que hablamos, con lo que de igual manera fueron descartados :P.

Las Estimaciones

Uno de los mayores problemas es, sin duda, estimar las horas de las tareas. Comenzamos quedándonos cortísimos y terminamos quedándonos cortos también, creo que rara vez se hizo una estimación cercana a la realidad. Es muy complicado, sin duda. Las tareas de análisis se sobrestimaron en su mayoría y las de desarrollo (la mayoría) se quedaron cortas. Para planificar los primeros sprints utilizamos Poker-Scrum, pero acabamos haciéndolo directamente de viva voz ya que decidimos que no aportaba nada. En los primeros sprints hubo que arrastrar bastantes tareas de uno a otro porque se habían estimado muy a la baja, sin embargo hacia el final se recuperó el tiempo perdido y las estimaciones no eran tan malas.

Las Demos

Las demos al cliente (interno en nuestro caso) son el peor momento del sprint, no pocos días nos tocó quedarnos hasta altas horas de la noche para llegar a la demo del día siguiente con el trabajo terminado o, al menos, visible. Si esto lo hacíamos cada dos semanas, imaginaos que hubiese pasado si utilizásemos metodologías tradicionales, hubiésemos llegado al final del tiempo de desarrollo sin hacer ninguna demo, sin haber probado las cosas de una manera real. Habría sido imposible, de hecho es lo que ocurre habitualmente :P.

En nuestro caso nos sirvió no sólo para ver la reacción del cliente ante el avance del producto sino también para ver las fortalezas y debilidades del trabajo que estábamos desarrollando y reorientar los siguientes sprints.

Las demos están para que el cliente vea el avance del producto que se le está desarrollando. El cliente en nuestro caso era una persona en representación de uno o varios departamentos internos de la organización. ¿Qué utilidad tienen las demos si sólo asiste el product owner? Digo esto porque de vez en cuando aparecía por allí algún despistado que se dedicaba a criticar el producto, en concreto partes del mismo que llevaban dos o tres meses terminadas y validadas por el product owner. ¿Por qué esa actitud revienta-demos? O mejor aún, ¿por qué no le prestas el debido interés a un producto en el que se basará todo tu trabajo los próximos años? Ah, ya, es más fácil dejar la responsabilidad al product owner y después quejarse. Sin la implicación de toda la organización da igual qué metodología se utilice, ninguna conseguirá triunfar. Estamos hablando de media hora cada quince días, sólo eso.  Otro punto importante a tener en cuenta es la opinión. La gente que asiste a la demo debería estar obligada a decir algo y no quedarse callados como si no fuera con ellos porque al final ocurre lo mismo, cuando tienen que opinar no opinan y después, cuando ya no se puede hacer nada, es cuando hablan.

Retrospectiva y planificación

Las reuniones de retrospectiva fueron bastante interesantes. Por un lado comentábamos la indiferencia de los asistentes a las demos y por otro discutíamos sobre los problemas que se habían presentado durante el sprint, unas veces con más energía y otras con menos. En realidad casi podríamos decir que servían como válvula de escape al pasotismo de los asistentes a la demo.  Son necesarias las retrospectivas, sirven precisamente para evaluar no sólo lo que ha ocurrido sino también los ánimos del equipo.

Tras la retrospectiva llega la reunión de planificación de sprint. Tal como indicaba más arriba, las estimaciones se hicieron muy complicadas, muchísimas tareas por historia de usuario, muchísimas dependencias entre ellas y no siempre tenemos todos en la cabeza las implicaciones que pueden tener unas con otras, lo que termina en estimaciones muy poco aproximadas por no decir aleatorias. Aún así poco a poco se fueron afinando bastante. En las reuniones de planificación se ponían sobre la mesa también nuevos requerimientos que habían ido surgiendo y modificaciones sobre los ya terminados que obligaban a modificar las prioridades de la pila de producto ajustando el calendario a las nuevas necesidades y objetivos hasta llegar a la pila del sprint siguiente.

Conclusiones

Sin duda la experiencia ha sido muy positiva. En otras ocasiones habíamos hecho intentos de aplicar SCRUM a determinados proyectos  que resultaron en utilizar solamente algunos conceptos de SCRUM, pero la falta de implicación del cliente (casi siempre interno) restaba utilidad a la metodología. En esta ocasión se hizo una aplicación completa, real y efectiva, implicando a todos los elementos de la organización que tuviesen algo que decir dentro del proyecto y, aunque siempre se pueden hacer críticas a la implicación de algunos, podemos afirmar que, en general, todos cumplieron con su parte.

Una de las cosas de SCRUM que todos alabamos cinco meses después son, sin duda, los dailys. Nos sirvieron a todos los partícipes del proyecto para conocer de buena mano qué estaban haciendo los demás, qué problemas había,  cuándo se iban a resolver, etc. Creo que cualquier equipo de desarrollo, independientemente de la metodología que utilice, debería realizar reuniones diarias para compartir ideas y problemas.

La idea de tener demos del producto cada dos semanas obliga a todo el equipo a pensar más en hacer cosas que funcionen que en ir avanzando en tareas, es más importante terminar dos historias de usuario para presentar al final del sprint que comenzar 20 tareas que no se terminen.

Uno de los puntos que nos desviaron del objetivo en cada sprint fue el proceso de integración y preparación para la demo. Nunca tuvimos en cuenta estas horas que al final implicaban bastante tiempo de todo el equipo ya que al realizar la integración siempre había cosas que no funcionaban bien. El último día estaba dedicado casi íntegramente a estas labores que nunca estaban planificadas en la pila del sprint.

Los que me conocen saben que llevo ya unos años defendiendo SCRUM como metodología adecuada para el desarrollo de software. Esta experiencia no ha hecho más que confirmar esta opinión, la adaptación del producto al cliente que se consigue no se podría lograr con metodologías clásicas en las que la más mínima modificación de las tareas a realizar puede provocar una desviación enorme.

Supongo que pagamos algo cara la falta de experiencia, sobre todo al principio, pero aún así logramos el objetivo, seguramente con un poco más de experiencia el resultado habría sido aún mejor.

Por cierto, no hemos dejado SCRUM, el proyecto sigue y ya trabajamos en la siguiente release, así que seguimos liados con dailys, sprints, estimaciones…