/**
 * Javascript Important Resources
 * 
 * @author Edina C.B. <http://www.edina.es>
 * @version 1.0
 * @package javascript
 *
 * Copyright (c) Edina C.B. 2011 - Some functions has its own copyright
 */

/************************/
/*		JAVASCRIPT		*/
/************************/

	/////////////
	//PROTOTYPE//
	/////////////

	//Buscar un indice en un array de una forma similar a string.indexOf
	//Ejemplo: var mi_array = new Array(val1, val2, val3);
	//mi_array.indexOf(val1): Return 0 (index = 0)
	//mi_array.indexOf(val3): Return 2 (index = 2)
	//mi_array.indexOf(valUnknown): Return -1 (not in array)
	if (!Array.prototype.indexOf)
	{
	  Array.prototype.indexOf = function(elt /*, from*/)
	  {
		var len = this.length;
	
		var from = Number(arguments[1]) || 0;
		from = (from < 0)
			 ? Math.ceil(from)
			 : Math.floor(from);
		if (from < 0)
		  from += len;
	
		for (; from < len; from++)
		{
		  if (from in this &&
			  this[from] === elt)
			return from;
		}
		return -1;
	  };
	}
	
	/*
	 * Metodo Object.keys: Permite obtener las claves de una object (No funcione en IE9 o anterior ni FF3.6 o anterior (esto lo soluciona)
	 */
	Object.keys = Object.keys || function(o)
	{
		var result = [];
		for(var name in o) {
			if (o.hasOwnProperty(name))
			  result.push(name);
		}
		return result;
	};
	
	//Funcion necesaria para utilizar textShadow (añadir sombra a un texto incluso si el navegador no soporta shadow de css3)
	if(typeof Array.prototype.map == 'undefined')
	{
		Array.prototype.map = function(fnc)
		{
			var a = new Array(this.length);
			for (var i = 0; i < this.length; i++)
			{
				a[i] = fnc(this[i]);
			}
			return a;
		}
	};
	
	/////////////
	//FUNCTIONS//
	/////////////
	
		// INICIO DE INTERNET EXPLORER //
		
		var cache_IEVersion = null;

		/**
		 * Devolver la version actual de internet explorer o -1 si no es IE.
		 * @return integer 
		 */
		function getInternetExplorerVersion()
		{
			if(cache_IEVersion != null)
				return cache_IEVersion;
				
			  var rv = -1; // Return value assumes failure.
			  if (navigator.appName == 'Microsoft Internet Explorer')
			  {
				var ua = navigator.userAgent;
				var re  = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
				if (re.exec(ua) != null)
				  rv = parseFloat( RegExp.$1 );
			  }
			  cache_IEVersion = rv;
			  return rv;
		}
		
		/**
		 * Verifica si es IE7 o anterior (comprueba incluso si esta en vista compatibilidad)
		 * @return boolean 
		 */
		function isIE7prev()
		{
			try
			{
				//Fix vista compatibilidad IE8 (modo ie7)
				if(document.documentMode == 7 || (getInternetExplorerVersion()>-1 && getInternetExplorerVersion()<=7))
				{
					return true;
				}
			}
			catch(e)
			{
			}
			return false;
		}
		
		/**
		 * Verifica si es IE8 o anterior (comprueba incluso si esta en vista compatibilidad)
		 * @return boolean 
		 */
		function isIE8prev()
		{
			try
			{
				//Fix vista compatibilidad IE8 (modo ie7)
				if(document.documentMode == 7 || (getInternetExplorerVersion()>-1 && getInternetExplorerVersion()<=8))
				{
					return true;
				}
			}
			catch(e)
			{
			}
			return false;
		}		

		/**
		 * Verifica si es IE7 o anterior o no para establecer el inline-block como inline-block o como simplemente inline
		 * @return string 
		 */
		function getInlineBlock()
		{
			if(isIE7prev())
			{
				return "inline";
			}
			else
			{
				return "inline-block";
			}
		}

		// FIN DE INTERNET EXPLORER //
		
		// INICIO DE UTILIDADES //
		
		/**
		 * Comprueba si una fecha es valida o no (Española)
		 * @param string value: Texto con la fecha
		 * @param boolean forceGetDate: OPCIONAL - Si no se especifica devuelve true/false. Si se especifica devuelve false o un objecto DATE con la fecha 
		 * @return boolean o Date 
		 *
		 */
		function isADateES(value, forceGetDate)
		{
			var date = null;
			var separators = new Array("\\", "-", "/");
			for(var index in separators)
			{
				if(value.indexOf(separators[index])!=-1)
				{
					date = value.split(separators[index]);
				}
			}
			if(date == null  || date.length!=3 || date[1]*1>12 || date[0]*1>31)
			{
				return false;
			}
			var validDate = /^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/.test(date[1]+"/"+date[0]+"/"+date[2]);
			
			if(forceGetDate==undefined)
			{
				return validDate;
			}
			else
			{
				if(!validDate)
				{
					return false;
				}
				else
				{
					return new Date(date[1]+"/"+date[0]+"/"+date[2]);
				}
			}
		}
		
		/**
		 * Prepara un AjaxUploader con sus textos tanto de drag (arrastrar), drop (soltar) o other (IE)
		 * @param string dragText: Texto que se establecera para accion de arrastrar un fichero encima para subir por AjaxUploader
		 * @param string dropText: Texto que se establecera para accion de soltar un fichero encima para subir por AjaxUploader
		 * @param string otherText: Texto que se establecera para IE porque no soporta drag and drop
		 * @param string uploaderDivId: OPCIONAL - Establece el id del AjaxUploader
		 * @param string forceImg: OPCIONAL - Fuerza a que solo se ponga una imagen como dragText, dropText y otherText
		 * @param string imgClass: OPCIONAL - Clase de la imagen que se usa en forceImg
		 * @return string 
		 */
		function uploaderText(dragText, dropText, otherText, uploaderDivId, forceImg, imgClass)
		{
			if(uploaderDivId == undefined)
			{
				uploaderDivId = "editar_album";
			}
			
			var IEVersion = getInternetExplorerVersion();
			
			if(forceImg != undefined && forceImg)
			{
					return '<div id="'+uploaderDivId+'"><div class="qq-uploader">' + 
							'<div class="qq-upload-drop-area"><img src="'+forceImg+'" class="'+imgClass+'"  /><\/div>' +
							'<div class="qq-upload-button"><img src="'+forceImg+'" class="'+imgClass+'"  /><\/div><\/div>' +
							'<ul class="qq-upload-list"><\/ul>' + 
					'<\/div><\/div>';
			}
			else
			{
				if(IEVersion != -1)
				{
					//El texto que hay aqui de dropText nunca se mostrara porque no es compatible
					return '<div id="'+uploaderDivId+'"><div class="qq-uploader">' + 
							'<div class="qq-upload-drop-area">'+dropText+'<\/div>' +
							'<div class="qq-upload-button">'+otherText+'<\/div><\/div>' +
							'<ul class="qq-upload-list"><\/ul>' + 
					'<\/div><\/div>';
				}
				else
				{
					return '<div id="'+uploaderDivId+'"><div class="qq-uploader">' + 
							'<div class="qq-upload-drop-area">'+dropText+'<\/div>' +
							'<div class="qq-upload-button">'+dragText+'<\/div>' +
							'<ul class="qq-upload-list"><\/ul>' + 
					'<\/div><\/div>';
				}
			}
			
		}


		/**
		 * Obtener el tamaño de un objeto JSON (Limitado a json unidimensionales)
		 * @param string json_object: El objeto JSON a identificar
		 * @return integer 
		 */
		function json_size(json_object)
		{
			var size = 0;
			for(var index in json_object)
			{
				size++;
			}
			return size;
		}

		/**
		 * Fuerza a que todos los elementos del html (sean div, span, etc...) tengan la misma altura
		 * @param string className: selector className a utilizar
		 * @return integer 
		 */
		function sameHeight(className)
		{
			var maxTituloSize = 0;
			$(className).each(function()
			{
				if($(this).height()>maxTituloSize)
				{
					maxTituloSize = $(this).height();
				}
			});
			if(maxTituloSize>0)
				$(className).height(maxTituloSize);
		}

		/**
		 * Precarga de imagenes en el navegador
		 * @param array images: array con las imagenes a precargar
		 * @return integer 
		 */
		function preloader(images) 
		{
		
			 // counter
			 var i = 0;
		
			 // create object
			 imageObj = new Image();
		
			 // start preloading
			 for(i=0; i<=images.length; i++) 
			 {
				  imageObj.src=images[i];
			 }
		}

		/**
		 * Corta strings largos
		 * Se puede utilizar para cortar palabras largas si el ultimo parametro se pone a TRUE
		 * @param string str: string con palabra/s largas a cortar
		 * @param integer int_width: tamaño maximo de la palabra
		 * @param string str_break: string de union entre los leementos cortados
		 * @param boolean cut: establece si se cortan palabras a mitad o se espera a que termine la palabra para utilizar el caracter de corte (str_break)
		 * @return string 
		 * 
		 * CopyRight: Jonas Raoni Soares Silva (http://www.jsfromhell.com) & Nick Callen & Kevin van Zonneveld (http://kevin.vanzonneveld.net) & Sakimori & Michael Grier
		 * 
		 */
		function wordwrap (str, int_width, str_break, cut)
		{
			// *     example 1: wordwrap('Kevin van Zonneveld', 6, '|', true);
			// *     returns 1: 'Kevin |van |Zonnev|eld'
			// *     example 2: wordwrap('The quick brown fox jumped over the lazy dog.', 20, '<br />\n');
			// *     returns 2: 'The quick brown fox <br />\njumped over the lazy<br />\n dog.'
			// *     example 3: wordwrap('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.');
			// *     returns 3: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod \ntempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim \nveniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea \ncommodo consequat.'
			// PHP Defaults
			var m = ((arguments.length >= 2) ? arguments[1] : 75);
			var b = ((arguments.length >= 3) ? arguments[2] : "\n");
			var c = ((arguments.length >= 4) ? arguments[3] : false);
		
			var i, j, l, s, r;
		
			str += '';
		
			if (m < 1) {
				return str;
			}
		
			for (i = -1, l = (r = str.split(/\r\n|\n|\r/)).length; ++i < l; r[i] += s) {
				for (s = r[i], r[i] = ""; s.length > m; r[i] += s.slice(0, j) + ((s = s.slice(j)).length ? b : "")) {
					j = c == 2 || (j = s.slice(0, m + 1).match(/\S*(\s)?$/))[1] ? m : j.input.length - j[0].length || c == 1 && m || j.input.length + (j = s.slice(m).match(/^\S*/)).input.length;
				}
			}
		
			return r.join("\n");
		}

		/**
		 * Compara dos arrays para ver si son iguales
		 * @param array a1: array a comparar
		 * @param array a2: array a comparar
		 * @return boolean 
		 *
		 */
		function arrayCompare(a1, a2)
		{
			if (a1.length != a2.length) return false;
			var length = a2.length;
			for (var i = 0; i < length; i++)
			{
				if (a1[i] !== a2[i]) return false;
			}
			return true;
		}
		
		/**
		 * Comprueba si existe un valor en un array
		 * @param string needle: string a buscar
		 * @param array haystack: array a revisar
		 * @return boolean 
		 *
		 */
		function inArray(needle, haystack)
		{
			if(needle == undefined || haystack == undefined)
			{
				return false;
			}
			var length = haystack.length;
			for(var i = 0; i < length; i++)
			{
				if(typeof haystack[i] == 'object')
				{
					if(arrayCompare(haystack[i], needle)) return true;
				}
				else
				{
					if(haystack[i] == needle) return true;
				}
			}
			return false;
		}

		/**
		 * Realizar un var_dump similar al de PHP
		 * @param string object: objeto para hacer el var_dump
		 * @param array br: tipo de salto de linea (\r\n o <br />)
		 * @param array multi: recursividad (si es objeto seguir la recursividad)
		 * @return string 
		 *
		 */
		function var_dump(object, br, multi)
		{
			var temp = "";
			if(br == undefined)
			{
				newLine = "\r\n";
			}
			else
			{
				newLine = "<br />";
			}
			for(var index in object)
			{
				if(typeof(object) == "object" && multi != undefined)
				{
					temp += index + ": "+newLine;
					temp += "\t"+var_dump(object[index], br)+newLine;
				}
				else
				{
					temp += index + ": "+ object[index]+newLine;
				}
			}
			return temp;
		}
		
		/**
		 * Comprobar si un numero de cuenta esta bien o no
		 * @param String banco: numero del banco (4 digitos)
		 * @param String sucursal: numero de sucursal (4 digitos)
		 * @param String digitoControl: numero de control (2 digitos)
		 * @param String numCuenta: numero de cuenta (10 digitos)
		 * @return boolean 
		 *
		 */
		function validaCuentaBancaria(banco, sucursal, digitoControl, numCuenta)
		{
			if(banco.length !=4 || sucursal.length !=4 || digitoControl.length !=2 || numCuenta.length !=10)
			{
				return false;
			}
			return (digitoControl*1 == obtenerDigitoControl(banco, sucursal, numCuenta)*1);
		}
		 
		 
		/* PRIVADA NO USAR utilizar en su caso validaCuentaBancaria(banco, sucursal, digitoControl, numCuenta) */ 
		function obtenerDigitoControl(banco, sucursal, numCuenta)
		{
			return calculaDigito("00"+banco+sucursal) + "" + calculaDigito(numCuenta);
		}
		
		/* PRIVADA NO USAR utilizar en su caso validaCuentaBancaria(banco, sucursal, digitoControl, numCuenta) */ 
		function calculaDigito(valor)
		{
			  valores = new Array(1, 2, 4, 8, 5, 10, 9, 7, 3, 6);
			  control = 0;
			  for (i=0; i<=9; i++)
				control += parseInt(valor.charAt(i)) * valores[i];
			  control = 11 - (control % 11);
			  if (control == 11) control = 0;
			  else if (control == 10) control = 1;
			  return control;
		}
		
		/** Gestion de Cookies */
		function get_cookie(Name)
		{
			var search = Name + "="
			var returnvalue = "";
			if (document.cookie.length > 0)
			{
				offset = document.cookie.indexOf(search)
				if (offset != -1)
				{
					offset += search.length
					end = document.cookie.indexOf(";", offset);
					if (end == -1) end = document.cookie.length;
					returnvalue=unescape(document.cookie.substring(offset, end))
				}
			}
			return returnvalue;
		}


		function set_cookie(name, value, expires)
		{
			document.cookie = name + "=" + escape(value) + "; path=/" + ((expires == null) ? "" : "; expires=" + expires.toGMTString());
		} 


		function delCookie (name,path,domain)
		{
			if (get_cookie(name))
			{
				document.cookie = name + "=" + ((path == null) ? "" : "; path=" + path) + ((domain == null) ? "" : "; domain=" + domain) + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
			}
		}

		function prepareExpireDate(time_in_minutes)
		{
			var expirebar = new Date();
			expirebar.setTime(expirebar.getTime() + (1000 * 60 * time_in_minutes));
			return expirebar;
		}
		/** Fin de Gestion de Cookies */
		
		/**
		 * Muestra Banner si la cookie lo permite
		 * @param boolean/integer mostrarBanner: true/false, si se tiene que comprobar la cookie o no
		 * @param String nombreCookieBanner: nombre de la cookie a comprobar
		 * @param function accion: accion a ejecutar
		 *
		 */
		function muestraBannerSegunCookie(mostrarBanner, nombreCookieBanner, accion)
		{
			if(mostrarBanner)
			{
				//Verificamos si se ha mostrado el banner o no (si existe la cookie)
				if (get_cookie(nombreCookieBanner)=="")
				{
					//Si no existe la cookie la creamos y mostramos el banner. (tercer parametro controla cuando caduca la cookie: nada = al salir, o algo=dia que caducara).
					set_cookie(nombreCookieBanner, "true");
					accion();
				}
			}
		}

		
		// FIN DE UTILIDADES //
		

		// INICIO DE ANIMACIONES: No usar estas funciones //
		
		
		/**
		 * Hace un cambio de una imagen por otra con un efecto de desvanecimiento (necesita un div con una imagen dentro ambos con su id)
		 * Nota: Tanto el div y la imagen han de ser del mismo tamaño si no hara un efecto estraño de redimensionamiento.
		 * @param string newImgUrl: url de la nueva imagen para cambiar
		 * @param string divContainerId: id del div que se pondra la imagen
		 * @param string oldImageId: id de la imagen
		 * @return void 
		 *
		 */
		function crossFadeImg(newImgUrl, divContainerId, oldImageId)
		{
			$("#"+divContainerId).css("background-image", "url("+newImgUrl+")");
			$("#"+oldImageId).animate({opacity: 0},
				{
					queue:false, 
					duration:500,
					complete:function()
					{
						$("#"+oldImageId).attr("src", newImgUrl).css({opacity:1});
					}
				}
			);
		}

		/**
		 * Usar a traves de jquery para iniciar una transicion de imagenes
		 *
		 * USO:
		 * $(selector).initSwitchImages(delay, dur); 
		 * Hay que poner en un div varias img y ellas solas se iran mostrando una a una
		 *
		 */
		function initSwitch(id, duration)
		{
			slideSwitch(id);
			setInterval(function() { slideSwitch(id); }, duration);
		}

		/**
		 * Usar a traves de jquery para iniciar una transicion de imagenes
		 *
		 * USO:
		 * $(selector).initSwitchImages(delay, dur); 
		 * Hay que poner en un div varias img y ellas solas se iran mostrando una a una
		 *
		 */
		function slideSwitch(id)
		{    
			var active = $('#'+id+' IMG.active');
			if ( active.length == 0 ) active = $('#'+id+' IMG:last');    // use this to pull the images in the order they appear in the markup
			
			var next =  active.next().length ? active.next() : $('#'+id+' IMG:first');    // uncomment the 3 lines below to pull the images in random order
			// var $sibs  = $active.siblings();
			// var rndNum = Math.floor(Math.random() * $sibs.length );
			// var $next  = $( $sibs[ rndNum ] );
			
			active.addClass('last-active').animate({opacity: 0.0}, 1500);
			next.css({opacity: 0.0}).addClass('active').animate({opacity: 1.0}, 1000, function() {active.removeClass('active last-active');});
		}

		// FIN DE ANIMACIONES //
		
		// INICIO DE DESUSO - DEPRECATED //
		
		/*
			Remplazada por: $.html_entity_decode(text);
		
		function html_entity_decode (string, quote_style)
		{
			// http://kevin.vanzonneveld.net
			// +   original by: john (http://www.jd-tech.net)
			// +      input by: ger
			// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
			// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
			// +   bugfixed by: Onno Marsman
			// +   improved by: marc andreu
			// +    revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
			// +      input by: Ratheous
			// +   bugfixed by: Brett Zamir (http://brett-zamir.me)
			// +      input by: Nick Kolosov (http://sammy.ru)
			// +   bugfixed by: Fox
			// -    depends on: get_html_translation_table
			// *     example 1: html_entity_decode('Kevin &amp; van Zonneveld');
			// *     returns 1: 'Kevin & van Zonneveld'
			// *     example 2: html_entity_decode('&amp;lt;');
			// *     returns 2: '&lt;'
		
			var hash_map = {}, symbol = '', tmp_str = '', entity = '';
			tmp_str = string.toString();
			
			if (false === (hash_map = this.get_html_translation_table('HTML_ENTITIES', quote_style))) {
				return false;
			}
		
			// fix &amp; problem
			// http://phpjs.org/functions/get_html_translation_table:416#comment_97660
			delete(hash_map['&']);
			hash_map['&'] = '&amp;';
		
			for (symbol in hash_map) {
				entity = hash_map[symbol];
				tmp_str = tmp_str.split(entity).join(symbol);
			}
			tmp_str = tmp_str.split('&#039;').join("'");
			
			return tmp_str;
		}
		*/
		
		/*
			No se usa en ningun sitio, esta definido internamente en las dataTables
			
		function getDataFromDataTableById(table, id)
		{
			if(!$("#"+id).closest("tr").get(0))
			{
				return null;
			}
			var position = table.fnGetPosition($("#"+id).closest("tr").get(0));
			return tabla_presupuesto_platos.fnGetData(position);
		}
		*/

		function prepareAjaxError(successFunction, XMLHttpRequest, textStatus, errorThrown)
		{
			var data = new Object();
			data["error_thrown"] = errorThrown;
			data["error_number"] = -2;
			data["error_message"] = "Error, el servicio no esta disponible temporalmente";
			successFunction(data, textStatus, XMLHttpRequest);
		}

		function handleAjaxError(data, loginURL)
		{
			if(data["error_number"]<-1) //-2: Error estandar pero como tiene que mostrar un alert 
			{
				alert(data["error_message"]);
			}
			else if(data["error_number"]==-1)
			{
				alert(data["error_message"]);
				window.location = loginURL;
			}
		}
		
		// FIN DE DESUSO - DEPRECATED //


/************************/
/*		JQUERY   		*/
/************************/

	/////////////////////////
	// EVENTS & OVERRIDES  //
	/////////////////////////

	/**
	 * Establecer/Recoger el valor de un input
	 * Añade soporte para checkbox
	 * @param string newImgUrl: url de la nueva imagen para cambiar
	 * @param string divContainerId: id del div que se pondra la imagen
	 * @param string oldImageId: id de la imagen
	 * @return void 
	 *
	 * Uso:
	 *	GET: $(selector).val()
	 *  SET: $(selector).val(value): Para establecer un valor de un checkbox pasar true o false
	 */
	var org_fn_val = jQuery.fn.val;
	$.fn.val = function()
	{
		if($(this).attr("type") == 'checkbox')
		{
			if(arguments != undefined && arguments[0] != undefined )
			{
				$(this).attr('checked', Boolean(arguments[0]));
			}
			else
			{
				if($(this).is(':checked'))
				{
					if($(this).attr("value")!=undefined)
					{
						return $(this).attr("value");
					}
					else
					{
						return true;
					}
				}
				else
				{
					return false;
				}
				//return $(this).is(':checked');
			}
		}
		/*
		else if($(this).attr("type") == 'select-one')
		{
			if(arguments != undefined && arguments[0] != undefined )
			{
				alert($(this).html());
				$(this).find("option[value='"+arguments[0]+"']").attr("selected", "selected");
	//			alert($(this).find("option[value='"+arguments[0]+"']").html());
			}
			else
			{
				return org_fn_val.apply( this, arguments );
			}
		}
		*/
		else
		{
			return org_fn_val.apply( this, arguments );
		}
	};

	/**
	 * Añade soporte de evento de rueda del raton
	 *
	 * Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net) Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
	 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
	 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
	 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
	 * 
	 * Version: 3.0.2
	 * 
	 * Requires: jquery 1.2.2+
	 *
	 * Uso:
	 *
	 * $(selector).mousewheel(fn):									Establece un evento de tipo mousewheel y aplica accion (fn)
	 * $(selector).bind('mousewheel', fn);							Establece un evento de tipo mousewheel y aplica accion (fn)
     * $(selector).bind('mousewheel', function(event, delta) {});   Establece un evento de tipo mousewheel y aplica accion definida internamente con parametros event y delta
	 *
	 */
	(function($)
	{
		var types = ['DOMMouseScroll', 'mousewheel'];
	
		$.event.special.mousewheel =
		{
			setup: function()
			{
				if ( this.addEventListener )
					for ( var i=types.length; i; )
						this.addEventListener( types[--i], handler, false );
				else
					this.onmousewheel = handler;
			},
			
			teardown: function()
			{
				if ( this.removeEventListener )
					for ( var i=types.length; i; )
						this.removeEventListener( types[--i], handler, false );
				else
					this.onmousewheel = null;
			}
		};
	
		$.fn.extend({
			mousewheel: function(fn)
			{
				return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
			},
			
			unmousewheel: function(fn)
			{
				return this.unbind("mousewheel", fn);
			}
		});
	
	
		function handler(event)
		{
			var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true;
			
			event = $.event.fix(event || window.event);
			event.type = "mousewheel";
			
			if ( event.wheelDelta ) delta = event.wheelDelta/120;
			if ( event.detail     ) delta = -event.detail/3;
			
			// Add events and delta to the front of the arguments
			args.unshift(event, delta);
		
			return $.event.handle.apply(this, args);
		}
	
	})(jQuery);
	

	/**
	 * Añade soporte de evento de rueda del raton
	 * 
	 * Requires: jquery 1.2.2+ and mousewheel event (zoom puede ser por rueda de raton o por teclas)
	 *
	 * Uso:
	 *
	 * $(selector).zoom(fn)											Establece un evento de tipo zoom y aplica accion (fn)
	 *
	 */
	jQuery.fn.zoom = function(fn)
	{
		jQuery(document).keydown(function(e)
		{
			switch (true)
			{
				case jQuery.browser.mozilla || jQuery.browser.msie :
					if (e.ctrlKey && (e.which == 187 || e.which == 189 || e.which == 107 || e.which == 109 || e.which == 96  || e.which == 48))
						fn();
					break;
				case jQuery.browser.opera :
					if (e.which == 43 || e.which == 45 || e.which == 42 ||(e.ctrlKey && e.which == 48))
						fn();
					break;
				case jQuery.browser.safari :
					if (e.metaKey && (e.charCode == 43 || e.charCode == 45))
						fn();
					break;
			}
			return;
		});
	
		jQuery(document).bind('mousewheel', function(e)
		{
			if (e.ctrlKey)
				fn();
		});
	
		jQuery(document).bind('DOMMouseScroll', function(e)
		{
			if (e.ctrlKey)
				fn();
		});
	};

	/**
	 * Fix de Mozilla firefox para ocultar un objeto flash y que no se reinicie constantemente al ocultar/mostrar
	 * 
	 * Requires: jquery 1.2.2+
	 *
	 * Uso:
	 *
	 * $(selector).flashHide()							Oculta un objeto de flash (Mozilla = visibility hidden, Resto = position absolute left -10000000)
	 * $(selector).flashShow()							Muestra un objeto de flash que se ha ocultado con flashHide (Mozilla = visibility hidden, Resto = position absolute left -10000000)
	 *
	 */
	(function($)
	{ 
	  var hideClassName = 'flashHide'; 
	  $.fn.extend({ 
		flashHide: function() {return this.each(function(){$(this).addClass(hideClassName);});}, 
		flashShow: function() {return this.each(function(){$(this).removeClass(hideClassName);}); 
		} 
	  }); 
	})(jQuery);
	
	$(document).ready(function()
	{
		if($.browser.mozilla)
		{
			$("<style type='text/css'> .flashHide {  visibility: hidden; width: 0px; height: 0px; } </style>").appendTo("head");
		}
		else
		{
			$("<style type='text/css'> .flashHide {    position: absolute;    left: -10000px;} </style>").appendTo("head");
		}
	});

	/**
	 * PRIVADA: No utilizar nunca para validar fechas, es algo interno del jquery.validation
	 *
	 * Uso: NO LO USES
	 *
	 */
	function isDateES(value, element, validator, forceGetDate)
	{ 
		var date = null;
		var separators = new Array("\\", "-", "/");
		for(var index in separators)
		{
			if(value.indexOf(separators[index])!=-1)
			{
				date = value.split(separators[index]);
			}
		}
		if(date == null  || date.length!=3 || date[1]*1>12 || date[0]*1>31)
		{
			return (validator.optional(element) || false);
		}
		var validDate = /^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[1,3-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$/.test(date[1]+"/"+date[0]+"/"+date[2]);
		
		if(forceGetDate==undefined)
		{
			return validator.optional(element) || (validDate && !/Invalid|NaN/.test(new Date(date[1]+"/"+date[0]+"/"+date[2])));
		}
		else
		{
			if((validDate && !/Invalid|NaN/.test(new Date(date[1]+"/"+date[0]+"/"+date[2]))))
			{
				return new Date(date[1]+"/"+date[0]+"/"+date[2]);
			}
			else
			{
				return false;
			}
		}
	}

	/**
	 * Añade soporte de validaciones especiales al validation
	 * 
	 * Requires: jquery 1.2.2+ & jquery.validation 1.0+
	 *
	 * Reglas añadidas:
	 * - youtube (verifica que una url es de tipo youtube)
	 * - requiredNotDefault (valor requerido pero no admite el valor por defecto del campo)
	 * - phone (formato +3491000000 o 910000000 o 003491000000)
	 * - hora (rango 00:00 a 23:59)
	 * - dateES (formato DD/MM/AAAA)
	 * - rangeDateES (rango de fechas en formato DD/MM/AAAA)
	 *
	 */
	if(jQuery.validator != undefined)
	{
		jQuery.validator.addMethod("youtube", function (value, element) {  return $.query.load(value).get("v")!=""; },"<br />Este no es un enlace de youtube valido.");
		jQuery.validator.addMethod("requiredNotDefault", function (value, element) { return  value!=$(element).attr("defaultValue") },"<br />Este campo es requerido");
		jQuery.validator.addMethod("phone", function (value, element) {  return this.optional(element) || /^\+*(?:[0-9]?){6,14}[0-9]$/.test(value); },"<br />Escriba un telefono valido ( +3491000000 o 910000000 o 003491000000)");
		jQuery.validator.addMethod("hora", function (value, element) {  return this.optional(element) || /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value); },"<br />Escriba una hora valida (00:00 a 23:59)");
		jQuery.validator.addMethod("dateES", function(value, element) {return isDateES(value, element, this)}, "<br />Escriba una fecha del formato DD/MM/AAAA (dia, mes y a&ntilde;o)");
		jQuery.validator.addMethod("rangeDateES", function(value, element, param)
		{ 
			var dates = (param+"").replace(/ /g, "").split(",");
			if(dates.length != 2)
			{
				return false;
			}
			
			var minDate = isDateES(dates[0], element, this, true);
			var maxDate = isDateES(dates[1], element, this, true);
			var userDate = isDateES(value, element, this, true);
			if(!minDate || !maxDate || !userDate)
			{
				return false;
			}
			if(userDate<minDate)
			{
				return false;
			}
			else if(userDate>maxDate)
			{
				return false;
			}
			else
			{
				return true;
			}
			
		}, "<br />Escriba una fecha del formato DD/MM/AAAA (dia, mes y a&ntilde;o)");
	}
	else
	{
		throw new Error ("Error, esta version de important.js tiene que ir justo debajo de jquery.validate.js en el head, no puede ir antes");
	}


	/////////////////////////////////////
	// FUNCTIONS:FORMULARIOS E INPUTS  //
	/////////////////////////////////////
	
	
	/**
	 * Borrar un form (poner todo vacio)
	 * @return void 
	 *
	 * Vease tambien: $(selector@form).reset(); Esta funcion reinicia un formulario a los valores iniciales (que pueden ser 0 o vacio o no)
	 *
	 * Uso:
	 *	$(selector).clearForm()
	 */
	$.fn.clearForm = function()
	{
		$(this).find(':input').each(function()
		{
			switch(this.type) {
				case 'password':
				case 'select-multiple':
				case 'select-one':
				case 'text':
				case 'textarea':
					$(this).val('');
					break;
				case 'checkbox':
				case 'radio':
					this.checked = false;
			}
		});
	};
	
	/**
	 * Reinicia un form (poner todo por defecto puede no ser vacio y que tenga datos por defecto)
	 * @return void 
	 *
	 * Vease tambien: $(selector).clearForm(); Esta funcion borra un form (poner todo vacio)
	 *
	 * Uso:
	 *	$(selector).reset()
	 */
	$.fn.extend({  
		reset: function() {  
		return this.each(function() {  
				$(this).is('form') && this.reset();  
		});  
		}  
	});
	
	/**
	 * Convierte un formulario en un objeto json
	 * @return json
	 *
	 * Vease tambien la funcion estandar: $(selector).serialize();
	 *
	 * Uso:
	 *	$(selector).parse_form()
	 */
	$.fn.parse_form = function()
	{
		var json = {};
		$(this).find(':input').each(function()
		{
			switch(this.type) {
				case 'password':
				case 'select-multiple':
				case 'select-one':
				case 'text':
				case 'textarea':
				case 'hidden':
					json[$(this).attr("id")] = $(this).val();
					break;
				case 'checkbox':
				case 'radio':
					json[$(this).attr("id")] = this.checked;
			}
		});
		return json;
	};

	/**
	 * Añade el evento onchange a todos los elementos de un formulario
	 * @param function action: funcion que se ejecuta si se produce algun cambio en un formulario
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).onChangeForm(action);
	 */
	$.fn.onChangeForm = function(action)
	{
		$(this).find(':input').each(function()
		{
			switch(this.type) {
				case 'password':
				case 'select-multiple':
				case 'select-one':
				case 'text':
				case 'textarea':
				case 'checkbox':
				case 'radio':
					$(this).change(action);
			}
		});
	};


	/**
	 * Establece un valor de un input con formato html (transforma las entidades html tipo &aacute; en á)
	 * permite convertir br en saltos de linea o omitir los saltos de linea previos
	 * @param string text: texto a convertir en html
	 * @param boolean br2nl: activar la conversion de br a nl
	 * @param boolean ingnorePreviousnl: activar la ignoracion de los saltos de linea anteriores
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).valHTMLEntities(text, br2nl, ingnorePreviousNL);
	 */
	$.fn.valHTMLEntities = function(text, br2nl, ingnorePreviousNL)
	{
		if(br2nl == undefined || br2nl == false)
		{
			$(this).val($("<div/>").html(text).text());
		}
		else
		{
			if(ingnorePreviousNL == undefined || ingnorePreviousNL == false)
			{
				$(this).val($("<div/>").html(text).text().replace(/<br \/>/g, "\n").replace(/<br\/>/g, "\n").replace(/<br>/g, "\n"));
			}
			else
			{
				$(this).val($("<div/>").html(text).text().replace(/\n/g, "").replace(/<br \/>/g, "\n").replace(/<br\/>/g, "\n").replace(/<br>/g, "\n"));
			}
		}
	};

	/**
	 * Recupera/Establece un valor de un input con formato numerico
	 * @param string value: valor que se pondra en el input
	 * @param json params: opciones para parsear el numero (numero de decimales:2, separador decimal: ".", separador de miles: ",", moneda: "" (ninguna)
	 * @return string 
	 *
	 * Uso:
	 *	$(selector).val_formated(value, params);
	 *
	 * Vease tambien $(selector).html_formated(): Dar formato a una etiqueta de html (div, span...)
	 */
	$.fn.val_formated = function(value, params)
	{
		//parametros default
		var sDefaults = 
		{			
			numberOfDecimals: 2,
			decimalSeparator: '.',
			thousandSeparator: ',',
			symbol: ''
		}
	 
		//Fusion de jsons
		var options = jQuery.extend(sDefaults, params);
		if(value != undefined)
		{
			if(isNaN(value))
			{
				throw new Error ("Error, el campo con id: "+$(this).attr("id")+" ha recibido un valor NaN");
				value = 0;
			}
			
			$(this).val($.number_format(value, sDefaults));
		}
		else
		{
			return $.number_format($(this).val().replace(/,/, "").replace(/ /, "").replace(/€/, "").replace(/&nbsp;/, "").replace(/&euro;/, ""), sDefaults);
		}
	};

	/**
	 * Recupera un valor de un input con formato numerico como numero estandar en javascript para seguir operando
	 * @return float 
	 *
	 * Uso:
	 *	$(selector).cleanNumberVal();
	 */
	$.fn.cleanNumberVal = function()
	{
		return $(this).val().replace(/,/, "").replace(/€/, "").replace(/&nbsp;/, "").replace(/&euro;/, "")*1;
	};

	/**
	 * Desactiva un boton (img o input), desactiva la funcion click, si es tipo boton modifica el texto del boton y ese mismo texto aparecera en un alert
	 * @param string text: texto del boton y mensaje de alert al pulsar el boton
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).disableButton(text);
	 */
	$.fn.disableButton = function(text)
	{
		if($(this).attr("src")!=undefined)
		{
			$(this).attr("src", $(this).attr("src").replace(/reposo/, "desactivado"));
			$(this).attr("src", $(this).attr("src").replace(/sobre/, "desactivado"));
		}
		else if($(this).attr("value")!=undefined)
		{
			$(this).val(text);
			$(this).attr("disabled", "disabled");
		}
		$(this).unbind("click");
		$(this).click(function(){alert(text)});
		if($(this).attr("id") != undefined && $("#"+$(this).attr("id")+"_msg") != undefined)
		{
			$("#"+$(this).attr("id")+"_msg").html(text);
		}
	};
	
	/**
	 * Re-activa un boton (img o input), añade de nuevo la funcion click (hay que pasarsela otra vez), si es tipo boton modifica el texto del boton
	 * @param function text: accion del boton al hacer click
	 * @param string text: texto del boton
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).enableButton(action, text);
	 */
	$.fn.enableButton = function(action, text)
	{
		if($(this).attr("src")!=undefined)
		{
			$(this).attr("src", $(this).attr("src").replace(/desactivado/, "reposo"));
		}
		else if($(this).attr("value")!=undefined)
		{
			$(this).val(text);
			$(this).attr("disabled", "");
		}
		$(this).unbind("click");
		$(this).click(action);
		if($(this).attr("id") != undefined && $("#"+$(this).attr("id")+"_msg") != undefined)
		{
			$("#"+$(this).attr("id")+"_msg").html(text);
		}
	};

	/**
	 * Elimina reglas de la validacion de jquery.validation (sirve por si se oculta unos campos de un formulario y no tienen que ser validados).
	 * @param array ruleObject: array con las reglas a desactivar
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).removeRuleObject(ruleObject);
	 */
	jQuery.removeRuleObject = function(ruleObject)
	{
		try
		{
			for(var index in ruleObject)
			{
				$("#"+index).rules("remove");
			}
		}
		catch(e){}
	};

	/**
	 * Añade reglas de la validacion de jquery.validation (sirve por si vuelven a mostrar campos ocultos de un formulario y tienen que volver a ser validados).
	 * @param array ruleObject: array con las reglas a activar
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).addRuleObject(ruleObject);
	 */
	jQuery.addRuleObject = function(ruleObject)
	{
		try
		{
			for(var index in ruleObject)
			{
				$("#"+index).rules("add", ruleObject[index]);
			}
		}
		catch(e){}
	};
	
	/**
	 * Añade el evento de presionar la tecla enter a un selector
	 * @param function action: accion que se ejecutara al pulsar enter sobre ese selector (es posible añadirlo al formulario y activar todos los inputs a la vez)
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).pressEnter(action);
	 */
	$.fn.pressEnter = function(action)
	{
		$(this).bind("keydown", function(e)
		{
			if (e.keyCode == 13)
			{
				action();
				return false; //prevent default behaviour
			}
		});
	};


	/**
	 * Devuelve la extension de un input (para usar con type=file)
	 * @param boolean allowEmpty: permitir valores vacios de formulario o no
	 * @return void 
	 *
	 * Uso:
	 *	$(selector).getFileExt(action);
	 */
	$.fn.getFileExt = function(allowEmpty)
	{
		if($(this).attr("tagName")!=null && $(this).attr("tagName").toLowerCase() == "input")
		{
			var temp = $(this).val().split(".");
			var ext = temp[temp.length-1].toLowerCase();
			return ext;
		}
		else
		{
			return Boolean(allowEmpty);
		}
	};


	////////////////////////////
	// FUNCTIONS: UTILIDADES  //
	////////////////////////////
	
	/**
	 * Modificacion del Query String de una url
	 *
	 * @author Blair Mitchelmore
	 * @version 2.1.7
	 *
	 * Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
	 * Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
	 * Date: 2009/8/13
	 *
	 * Uso:
	 *	SET: 	$.query.set(key, value); 	Establece un valor a una clave (si no existe la crea)
	 *	GET: 	$.query.get(key); 			Recupera un valor de una clave
	 *	REMOVE: $.query.remove(key); 		Borra un valor de una clave
	 *	EMPTY: 	$.query.empty(); 			Borra todos los valores del QS
	 *	LOAD: 	$.query.load(url); 			carga una url para trabajar con ella 
	 *	LOAD: 	$.query.load(); 			Rollback de los valores (valores de inicio de la querystring alias de $.query.load(window.location))
	 *  GO:  	$.query.go();				Hacer un window.location con los nuevos valores que se hayan hecho set o remove $.query.set(key1, value1).set(key2, value2).go();
	 *	COPY: 	$.query.copy(); 			Copia los valores del QS para utilizarlos con otro objeto
	 *
	 */
	new function(settings)
	{ 
	  // Various Settings
	  var $separator = settings.separator || '&';
	  var $spaces = settings.spaces === false ? false : true;
	  var $suffix = settings.suffix === false ? '' : '[]';
	  var $prefix = settings.prefix === false ? false : true;
	  var $hash = $prefix ? settings.hash === true ? "#" : "?" : "";
	  var $numbers = settings.numbers === false ? false : true;
	  
	  jQuery.query = new function() {
		var is = function(o, t) {
		  return o != undefined && o !== null && (!!t ? o.constructor == t : true);
		};
		var parse = function(path) {
		  var m, rx = /\[([^[]*)\]/g, match = /^([^[]+)(\[.*\])?$/.exec(path), base = match[1], tokens = [];
		  while (m = rx.exec(match[2])) tokens.push(m[1]);
		  return [base, tokens];
		};
		var set = function(target, tokens, value) {
		  var o, token = tokens.shift();
		  if (typeof target != 'object') target = null;
		  if (token === "") {
			if (!target) target = [];
			if (is(target, Array)) {
			  target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
			} else if (is(target, Object)) {
			  var i = 0;
			  while (target[i++] != null);
			  target[--i] = tokens.length == 0 ? value : set(target[i], tokens.slice(0), value);
			} else {
			  target = [];
			  target.push(tokens.length == 0 ? value : set(null, tokens.slice(0), value));
			}
		  } else if (token && token.match(/^\s*[0-9]+\s*$/)) {
			var index = parseInt(token, 10);
			if (!target) target = [];
			target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
		  } else if (token) {
			var index = token.replace(/^\s*|\s*$/g, "");
			if (!target) target = {};
			if (is(target, Array)) {
			  var temp = {};
			  for (var i = 0; i < target.length; ++i) {
				temp[i] = target[i];
			  }
			  target = temp;
			}
			target[index] = tokens.length == 0 ? value : set(target[index], tokens.slice(0), value);
		  } else {
			return value;
		  }
		  return target;
		};
		
		var queryObject = function(a) {
		  var self = this;
		  self.keys = {};
		  
		  if (a.queryObject) {
			jQuery.each(a.get(), function(key, val) {
			  self.SET(key, val);
			});
		  } else {
			jQuery.each(arguments, function() {
			  var q = "" + this;
			  q = q.replace(/^[?#]/,''); // remove any leading ? || #
			  q = q.replace(/[;&]$/,''); // remove any trailing & || ;
			  if ($spaces) q = q.replace(/[+]/g,' '); // replace +'s with spaces
			  
			  jQuery.each(q.split(/[&;]/), function(){
				var key = decodeURIComponent(this.split('=')[0] || "");
				var val = "";
				try
				{
					val = decodeURIComponent(this.split('=')[1] || "");
				}
				catch(e)
				{
				}
				
				if (!key) return;
				
				if ($numbers) {
				  if (/^[+-]?[0-9]+\.[0-9]*$/.test(val)) // simple float regex
					val = parseFloat(val);
				  else if (/^[+-]?[0-9]+$/.test(val)) // simple int regex
					val = parseInt(val, 10);
				}
				
				val = (!val && val !== 0) ? true : val;
				
				if (val !== false && val !== true && typeof val != 'number')
				  val = val;
				
				self.SET(key, val);
			  });
			});
		  }
		  return self;
		};
		
		queryObject.prototype = {
		  queryObject: true,
		  has: function(key, type) {
			var value = this.get(key);
			return is(value, type);
		  },
		  GET: function(key) {
			if (!is(key)) return this.keys;
			var parsed = parse(key), base = parsed[0], tokens = parsed[1];
			var target = this.keys[base];
			while (target != null && tokens.length != 0) {
			  target = target[tokens.shift()];
			}
			return typeof target == 'number' ? target : target || "";
		  },
		  get: function(key) {
			var target = this.GET(key);
			if (is(target, Object))
			  return jQuery.extend(true, {}, target);
			else if (is(target, Array))
			  return target.slice(0);
			return target;
		  },
		  SET: function(key, val) {
			var value = !is(val) ? null : val;
			var parsed = parse(key), base = parsed[0], tokens = parsed[1];
			var target = this.keys[base];
			this.keys[base] = set(target, tokens.slice(0), value);
			return this;
		  },
		  set: function(key, val) {
			return this.copy().SET(key, val);
		  },
		  GO: function() {
			this.go();
		  },
		  go: function() {
			  window.location = this.toString();
		  },
		  REMOVE: function(key) {
			return this.SET(key, null).COMPACT();
		  },
		  remove: function(key) {
			return this.copy().REMOVE(key);
		  },
		  EMPTY: function() {
			var self = this;
			jQuery.each(self.keys, function(key, value) {
			  delete self.keys[key];
			});
			return self;
		  },
		  load: function(url) {
			var hash = url.replace(/^.*?[#](.+?)(?:\?.+)?$/, "$1");
			var search = url.replace(/^.*?[?](.+?)(?:#.+)?$/, "$1");
			return new queryObject(url.length == search.length ? '' : search, url.length == hash.length ? '' : hash);
		  },
		  empty: function() {
			return this.copy().EMPTY();
		  },
		  copy: function() {
			return new queryObject(this);
		  },
		  COMPACT: function() {
			function build(orig) {
			  var obj = typeof orig == "object" ? is(orig, Array) ? [] : {} : orig;
			  if (typeof orig == 'object') {
				function add(o, key, value) {
				  if (is(o, Array))
					o.push(value);
				  else
					o[key] = value;
				}
				jQuery.each(orig, function(key, value) {
				  if (!is(value)) return true;
				  add(obj, key, build(value));
				});
			  }
			  return obj;
			}
			this.keys = build(this.keys);
			return this;
		  },
		  compact: function() {
			return this.copy().COMPACT();
		  },
		  toString: function() {
			var i = 0, queryString = [], chunks = [], self = this;
			var encode = function(str) {
			  str = str + "";
			  if ($spaces) str = str.replace(/ /g, "+");
			  return encodeURIComponent(str);
			};
			var addFields = function(arr, key, value) {
			  if (!is(value) || value === false) return;
			  var o = [encode(key)];
			  if (value !== true) {
				o.push("=");
				o.push(encode(value));
			  }
			  arr.push(o.join(""));
			};
			var build = function(obj, base) {
			  var newKey = function(key) {
				return !base || base == "" ? [key].join("") : [base, "[", key, "]"].join("");
			  };
			  jQuery.each(obj, function(key, value) {
				if (typeof value == 'object') 
				  build(value, newKey(key));
				else
				  addFields(chunks, newKey(key), value);
			  });
			};
			
			build(this.keys);
			
			if (chunks.length > 0) queryString.push($hash);
			queryString.push(chunks.join($separator));
			
			return queryString.join("");
		  }
		};
		
		return new queryObject(location.search, location.hash);
	  };
	}(jQuery.query || {}); // Pass in jQuery.query as settings object

	/**
	 * Decodifica las entidades html en sus correspondencias (similar al PHP html_entity_decode)
	 * @param string text: texto a decodificar
	 * @return string 
	 *
	 * Uso:
	 *	$.html_entity_decode(text);
	 */
	jQuery.html_entity_decode = function(text)
	{
		return $("<div/>").html(text).text();
	};

	/**
	 * Da formato a un texto con formato decimal (numero de decimales, separador de miles y decimales, simbolo, etc..)
	 * @param string numero: numero a formatear
	 * @param json params: parametros (por defecto vale decimales: 2, separador decimal: ".", separador de miles: ",", simbolo: "" (ninguno), poner simbolo detras: true
	 * @return string 
	 *
	 * Uso:
	 *	$.number_format(numero, parametros);
	 */
	jQuery.number_format = function(numero, params)
	{
			if(numero==undefined)
			{
				return "Error!";
			}
		
			//parametros default
			var sDefaults = 
			{			
				numberOfDecimals: 2,
				decimalSeparator: '.',
				thousandSeparator: ',',
				symbol: '',
				behind: true
			}
	 
			//função do jquery que substitui os parametros que não foram informados pelos defaults
			var options = jQuery.extend(sDefaults, params);
	
			//CORPO DO PLUGIN
			var number = numero; 
			var decimals = options.numberOfDecimals;
			var dec_point = options.decimalSeparator;
			var thousands_sep = options.thousandSeparator;
			var currencySymbol = options.symbol;
			var behind = options.behind;
	
			var exponent = "";
			var numberstr = number.toString ();
			var eindex = numberstr.indexOf ("e");
			if (eindex > -1)
			{
			exponent = numberstr.substring (eindex);
			number = parseFloat (numberstr.substring (0, eindex));
			}
			
			if (decimals != null)
			{
			var temp = Math.pow (10, decimals);
			number = Math.round (number * temp) / temp;
			}
			var sign = number < 0 ? "-" : "";
			var integer = (number > 0 ? 
			  Math.floor (number) : Math.abs (Math.ceil (number))).toString ();
			
			var fractional = number.toString ().substring (integer.length + sign.length);
			dec_point = dec_point != null ? dec_point : ".";
			fractional = decimals != null && decimals > 0 || fractional.length > 1 ? 
					   (dec_point + fractional.substring (1)) : "";
			if (decimals != null && decimals > 0)
			{
			for (i = fractional.length - 1, z = decimals; i < z; ++i)
			  fractional += "0";
			}
			
			thousands_sep = (thousands_sep != dec_point || fractional.length == 0) ? 
						  thousands_sep : null;
			if (thousands_sep != null && thousands_sep != "")
			{
			for (i = integer.length - 3; i > 0; i -= 3)
			  integer = integer.substring (0 , i) + thousands_sep + integer.substring (i);
			}
			
			if (options.symbol == '')
			{
			return sign + integer + fractional + exponent;
			}
			else
			{
				if(!behind)
					return currencySymbol + ' ' + sign + integer + fractional + exponent;
				else
					return sign + integer + fractional + exponent + currencySymbol;
			}
			//FIM DO CORPO DO PLUGIN	
	};

	/**
	 * Convierte un texto plano en un texto CUFON
	 * @param string selector: selector al que se le va a aplicar el cufon
	 * @param string text: texto que se transformara en un cufon
	 * @return string 
	 *
	 * Uso:
	 *	$.cufon(selector, text);
	 *  $(selector).cufon(text);
	 */
	jQuery.cufon = function(selector, text)
	{
		if(text!=undefined)
		{
			$(selector).html(text);
		}
		try
		{
			if(Cufon == undefined)
			{
				throw new Error ("Error, hay que agregar cufon-yui.js o cufon.js y cufon-font.js para que esto funcione");
				return;
			}
			Cufon.replace(selector);
		}
		catch(e)
		{
			throw new Error ("Error, hay que agregar cufon-yui.js o cufon.js y cufon-font.js para que esto funcione");
			return;
		}
	};
	$.fn.cufon = function(text)
	{
		$.cufon($(this), text);
	};

	/**
	 * Copyright (C) 2009 Jonathan Azoff <jon@azoffdesign.com> with GPLv2 License
	 *
	 * jQuery.log v1.0.0 - A jQuery plugin that unifies native console logging across browsers
	 * 
	 * @usage		call jQuery.log([args...]) to write to attempt to write to the console of any browser. 
	 *				**See http: *azoffdesign.com/plugins/js/log for an example.
	 * @param 		args...	one or more javascript objects to be written to the console
	 * @returns 	true if a console was detected and successfully used, false if the plug-in had to resort to alert boxes
	 * @note 		if a plug-in cannot be located then an alert is called with the arguments you wish to log. Multiple 
	 *              arguments are separated with a space.
	 * @depends 	just make sure you have jQuery and some code you want to debug.
	 */
	(function($){
		$.extend({"log":function(){ 
			if(arguments.length > 0) {
				
				// join for graceful degregation
				var args = (arguments.length > 1) ? Array.prototype.join.call(arguments, " ") : arguments[0];
				
				// this is the standard; firebug and newer webkit browsers support this
				try { 
					console.log(args);
					return true;
				} catch(e) {
					// newer opera browsers support posting erros to their consoles
					try { 
						opera.postError(args); 
						return true;
					} catch(e) { }
				}
				
				// catch all; a good old alert box
				alert(args);
				return false;
			}
		}});
	})(jQuery);

	/**
	 * Copyright (C) 2009 Jonathan Azoff <jon@azoffdesign.com> with GPLv2 License
	 *
	 * jQuery.log v1.0.0 - A jQuery plugin that unifies native console logging across browsers
	 * 
	 * @usage		call jQuery.log([args...]) to write to attempt to write to the console of any browser. 
	 *				**See http: *azoffdesign.com/plugins/js/log for an example.
	 * @param 		args...	one or more javascript objects to be written to the console
	 * @returns 	true if a console was detected and successfully used, false if the plug-in had to resort to alert boxes
	 * @note 		if a plug-in cannot be located then an alert is called with the arguments you wish to log. Multiple 
	 *              arguments are separated with a space.
	 * @depends 	just make sure you have jQuery and some code you want to debug.
	 */
	(function($){
		$.extend({"console":function(){ 
			if(arguments.length > 0) {
				
				// join for graceful degregation
				var args = (arguments.length > 1) ? Array.prototype.join.call(arguments, " ") : arguments[0];
				
				// this is the standard; firebug and newer webkit browsers support this
				try { 
					console.log(args);
					return true;
				} catch(e) {
					// newer opera browsers support posting erros to their consoles
					try { 
						opera.postError(args); 
						return true;
					} catch(e) { }
				}
				
				// catch all; a good old alert box
				//alert(args);
				return false;
			}
		}});
	})(jQuery);


	/**
	 * Establece un retraso para luego ejecutar una funcion de animacion sobre un selector
	 * @param int time: tiempo en milisegundos de retraso para ejecutar una funcion
	 * @param function callback: funcion que se ejecutara
	 * @return jquery.object 
	 *
	 * Uso:
	 * $(selector).delay(time, function() { $(this).animate({height:180},250);}); 
	 * $(selector).delay(1500).animate({opacity: 1}, 1500);
	 * $(selector).delay(100).show(1000);
	 */
	$.fn.delay = function(time, callback)
	{
		// Empty function:
		jQuery.fx.step.delay = function(){};
		// Return meaningless animation, (will be added to queue)
		return this.animate({delay:1}, time, callback);
	};

	/**
	 * Escribe un mensaje temporal en un selector y al cabo de un tiempo lo borra (o vuelve al estado anterior)
	 * @param string message: mensaje que se mostrara
	 * @param int delayTime: tiempo que mostrara el mensaje (en milisegundos) minimo 1000 ms (1 seg). por defecto 5seg
	 * @param boolean keepPrevious: si no se especifica que se mantenga el anterior se pondra un espacio (&nbsp)
	 * @return void 
	 *
	 * Uso:
	 * $(selector).delay(time, function() { $(this).animate({height:180},250);}); 
	 * $(selector).delay(1500).animate({opacity: 1}, 1500);
	 * $(selector).delay(100).show(1000);
	 */
	$.fn.tempHTMLMessage = function(message, delayTime, keepPrevious)
	{
		var previousHTML = "&nbsp;";
		if(keepPrevious != undefined && keepPrevious)
		{
			previousHTML = $(this).html();
		}
		if(delayTime == undefined || delayTime* 1 < 1000)
		{
			delayTime = 5000;
		}
		$(this).html(message);
		$(this).delay(delayTime, function(){$(this).html(previousHTML);});
	};

	/**
	 * Da formato numerico a un numero y lo escribe en una etiqueta html (span, div, ...)
	 * @param string value: valor que se pondra en la etiqueta
	 * @param json params: opciones para parsear el numero (numero de decimales:2, separador decimal: ".", separador de miles: ",", moneda: "" (ninguna)
	 * @return void 
	 *
	 * Uso:
	 * $(selector).html_formated(value, params); 
	 * 
	 * Vease tambien $(selector).val_formated(): Dar formato a un input
	 */
	$.fn.html_formated = function(value, params)
	{
		//parametros default
		var sDefaults = 
		{			
			numberOfDecimals: 2,
			decimalSeparator: '.',
			thousandSeparator: ',',
			symbol: ''
		}
	
		var cleanValue = value+"";
		cleanValue = cleanValue.replace(/,/, "").replace(/ /, "").replace(/€/, "").replace(/&nbsp;/, "").replace(/&euro;/, "");
		//função do jquery que substitui os parametros que não foram informados pelos defaults
		var options = jQuery.extend(sDefaults, params);
		$(this).html($.number_format(value, sDefaults));
	};
	
	/**
	 * Recupera un valor de un tag html con formato numerico como numero estandar en javascript para seguir operando
	 * @return float 
	 *
	 * Uso:
	 *	$(selector).cleanNumberHTML();
	 */
	$.fn.cleanNumberHTML = function()
	{
		return $(this).text().replace(/[^0-9.]+/g,'');
	};
	
	/**
	 * Añade un mapa de google a una web
	 * @param string lat: latitud de la posicion del mapa de google
	 * @param string long: longitud de la posicion del mapa de google
	 * @param string titulo: titulo que se le añadira (nombre de la empresa) al bocadillo
	 * @param string dir: direccion que aparecere en el bocadillo
	 * @param string tel: telefono que aparecere en el bocadillo
	 * @param string logo: logo que aparecere en el bocadillo
	 * @return void 
	 *
	 * Uso:
	 * $(selector).googleMaps(lat, long, titulo, dir, tel, logo); 
	 *
	 */
	$.fn.googleMaps = function(lat, long, titulo, dir, tel, logo)
	{
		//Uso:
		//lat, long, address
		//lat, long, titulo, dir, tel
		//lat, long, titulo, dir, tel, logo
		var address = "";
		if(!dir)
		{
			address = '<span style="font-size:11px; width: 220px;">'+titulo+'<\/span>';
		}
		else
		{
			if(!logo)
			{
				logo = "img/logo.jpg";
			}
			address = '<div style="font-size:11px; width: 220px;"><img src="'+logo+'" alt="'+titulo+'" title="'+titulo+'" width="150" height="40" border="0" /><br /><br /><b>Direcci&oacute;n: <\/b> '+dir+' <br /><b>Tel&eacute;fono<\/b>: '+tel+'<\/div>';
		}
	   
		
		var infowindow = new google.maps.InfoWindow({content: address});
	
		var latlng = new google.maps.LatLng(lat, long); 
	
		var myOptions =
		{
		  zoom: 17,
		  center: latlng,
		  mapTypeId: google.maps.MapTypeId.ROADMAP
		};
		
		var map = new google.maps.Map(document.getElementById($(this).attr("id")), myOptions);
	
		var marker = new google.maps.Marker(
		{
			position: latlng,
			map: map,
			title: titulo
		});
	
		infowindow.open(map,marker);
	};
	
	/**
	 * Inicia un cambio de imagenes
	 * @param int delay: delay inicial antes de empezar la animacion
	 * @param int dur: tiempo que permanece cada foto
	 * @return void 
	 *
	 * Uso:
	 * $(selector).initSwitchImages(delay, dur); 
	 * Hay que poner en un div varias img y ellas solas se iran mostrando una a una
	 *
	 */
	$.fn.initSwitchImages = function(delay, dur)
	{   
		var initialDelay = 0;
		if(delay != undefined){initialDelay = delay*1000;}
		
		var duration = 4000;
		if(dur != undefined){duration = dur*1000;}
		var id = $(this).attr("id");

		$("#"+id).css("position", "relative");
		$("#"+id).css("height", $("#"+id).find("IMG").attr("height")+"px");
		$("#"+id).css("width", $("#"+id).find("IMG").attr("width")+"px");
		
		var css = 
		{
			"position": "absolute",
			"top": "0",
			"left": "0",
			"right": "0",
			//"margin-left": "auto",
			//"margin-right": "auto",
			"z-index": "8",
			"opacity": "0.0"
		}
		
		$("#"+id).find("IMG").css(css);
		$("#"+id).find("IMG.active").css("z-index", "10");
		$("#"+id).find("IMG.active").css("opacity", "1.0");
		$("#"+id).find("IMG.last-active").css("z-index", "9");
		//initSwitch(id, duration);
		setTimeout(function() { initSwitch(id, duration) }, initialDelay);
	};

	/**
	 * jQuery.fn.sortElements
	 *
	 * Establece a un selector una funcion de orden
	 *
	 * USO: 
	 * $("#pais option").sortElements(jQuery.sortByName);
	 *
	 * --------------
	 * @param Function comparator: Exactly the same behaviour as [1,2,3].sort(comparator)
	 *   
	 * @param Function getSortable
	 *   A function that should return the element that is
	 *   to be sorted. The comparator will run on the
	 *   current collection, but you may want the actual
	 *   resulting sort to occur on a parent or another
	 *   associated element.
	 *   
	 *   E.g. $('td').sortElements(comparator, function(){
	 *      return this.parentNode; 
	 *   })
	 *   
	 *   The <td>'s parent (<tr>) will be sorted instead
	 *   of the <td> itself.
	 */
	jQuery.fn.sortElements = (function(){
	 
		var sort = [].sort;
	 
		return function(comparator, getSortable) {
	 
			getSortable = getSortable || function(){return this;};
	 
			var placements = this.map(function(){
	 
				var sortElement = getSortable.call(this),
					parentNode = sortElement.parentNode,
	 
					// Since the element itself will change position, we have
					// to have some way of storing its original position in
					// the DOM. The easiest way is to have a 'flag' node:
					nextSibling = parentNode.insertBefore(
						document.createTextNode(''),
						sortElement.nextSibling
					);
	 
				return function() {
	 
					if (parentNode === this) {
						throw new Error(
							"You can't sort elements if any one is a descendant of another."
						);
					}
	 
					// Insert before flag:
					parentNode.insertBefore(this, nextSibling);
					// Remove flag:
					parentNode.removeChild(nextSibling);
	 
				};
	 
			});
	 
			return sort.call(this, comparator).each(function(i){
				placements[i].call(getSortable.call(this));
			});
	 
		};
	 
	})();
	
	/**
	 * Funcion coparadora para usar junto con sortElements
	 * @param string a: texto A para ordenar
	 * @param string b: texto B para ordenar
	 * @return int: devuelve -1 o 1 en funcion de cual de los dos es mayor alfabeticamente 
	 *
	 * Uso:
	 * $("#pais option").sortElements(jQuery.sortByName);
	 *
	 */
	jQuery.sortByName = function(a,b)
	{
		return $(a).text().toLowerCase() > $(b).text().toLowerCase() ? 1 : -1;
	};
	
	/**
	 * Activa la animacion de sobre/reposo a una imagen (es necesario que la imagen tenga una equivalencia en el directorio sobre y otra en reoposo)
	 * @param string currentSection: especifica la seccion actual para saltarse la parte de reposo/sobre y marcarlos siempre como seleccionado
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeButtons(currentSection);
	 *
	 */
	$.fn.makeButtons = function(currentSection)
	{
		if($(this).size() == 0)
		{
			//Error, no existe el elemento al que se le quiere aplicar la funcion
			return;
		}
	
		if($(this).attr("id") != currentSection)
		{
			if($(this).attr("src").indexOf("reposo")!=-1)
			{
				$(this).hover(function(){$(this).attr("src", $(this).attr("src").replace(/reposo/, "sobre"))},
							  function(){$(this).attr("src", $(this).attr("src").replace(/sobre/, "reposo"))});
			}
			else
			{
				$(this).hover(function(){$(this).stop().animate({opacity: '0.75'}, 'fast');},
							  function(){$(this).stop().animate({opacity: '1'}, 'fast');});
			}
		}
		else
		{
			$(this).attr("src", $(this).attr("src").replace(/reposo/, "sobre"));
		}
		
		$(this).css("cursor", "pointer");
	};

	/**
	 * Convierte un elemento en un boton con sus efectos de over y out y se le asigna una accion en forma de funcion
	 * @param function clickFunctionName: accion que se ejecutara al hacer click
	 * @param function mouseOverFunctionName: accion que se ejecutara al pasar por encima
	 * @param function mouseOutFunctionName: accion que se ejecutara al sacar el raton de la zona activa
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeButtons(currentSection);
	 *
	 */
	$.fn.makeLinks = function(clickFunctionName, mouseOverFunctionName, mouseOutFunctionName)
	{
		$(this).click(clickFunctionName);
		$(this).hover(mouseOverFunctionName, mouseOutFunctionName);
		$(this).css("cursor", "pointer");
	};

	/**
	 * Esclusiva de besoos.com, deberia de estar en otro sitio
	 */
	$.fn.makePlayerFromIMG = function(action)
	{
		if($(this).attr("src") == undefined)
			return;
		
		$(this).unbind("click").click(function()
		{
			var url = $(this).attr("src").replace(".jpg", ".flv");
			var ids = $(this).attr("id").replace("id_video_", "").split("_");
			var idVideo = ids[1]*1;
			var idPropietario = ids[0]*1;
			action(url, idVideo, idPropietario);
		});
	};


	/**
	 * Establece una accion (funcion o url) a una imagen y le aplica el $(selector).makeButtons([currentSection]); (Animacion de imagen con over y out).
	 * @param function action: accion que se ejecutara al hacer click
	 * @param string currentSection: seccion actual para poner el boton como seleccionado siempre en lugar de reposo o sobre
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeImgLink(action, currentSection);
	 *
	 * Vease tambien: makeAlink, makeTagLink, makeCufonLink
	 *
	 */
	$.fn.makeImgLink = function(action, currentSection)
	{
		if($(this).size() == 0)
		{
			//Error, no existe el elemento al que se le quiere aplicar la funcion
			return;
		}
		
		if($(this) == undefined || $(this).attr("src") == undefined)
		{
			$.log("Error al hacer un makeImgLink en la id: "+$(this).attr("id")+" classes: "+$(this).attr('class') +" esto no sera una imagen usa el makeTagLink, accion a asignar: "+action);
			//Error, no existe el elemento al que se le quiere aplicar la funcion
			return;		
		}
		
		//Tipo null (se le ha hecho un <a> antes <a><img></a>
		if(action == null)
		{
			//No hacer nada
		}
		//Tipo location
		else if(typeof(action) == "string")
		{
			$(this).click(function(){window.location = action});
		}
		//Tipo function
		else
		{
			$(this).click(action);
		}
		
		$(this).makeButtons(currentSection);
	};
	
	/**
	 *
	 * Alias de makeImgLink
	 *
	 * Establece una accion (funcion o url) a una imagen y le aplica el $(selector).makeButtons([currentSection]); (Animacion de imagen con over y out).
	 * @param function action: accion que se ejecutara al hacer click
	 * @param string currentSection: seccion actual para poner el boton como seleccionado siempre en lugar de reposo o sobre
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeImgLinks(action, currentSection);
	 *
	 */
	$.fn.makeImgLinks = function(action, currentSection)
	{
		$(this).makeImgLink(action, currentSection);
	};

	/**
	 * Establece una accion (funcion o url) a una etiqueta cualquiera de html (div o span generalmente)
	 * le aplica los eventos de (className_reposo y className_sobre) y className_seleccionado si estamos en la seccion actual
	 * @param function action: accion que se ejecutara al hacer click
	 * @param string className: raiz de la clase que se le pondra (si pasa menu se tendran que activar en css menu_reposo, menu_sobre y men_seleccionado)
	 * @param string currentSection: seccion actual para poner el boton como seleccionado siempre en lugar de reposo o sobre
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeTagLink(action, className, currentSection);
	 *
	 * Vease tambien: makeAlink, makeImgLink, makeCufonLink
	 *
	 */
	$.fn.makeTagLink = function(action, className, currentSection)
	{
		//Tipo location
		if(typeof(action) == "string")
		{
			$(this).click(function(){window.location = action});
		}
		//Tipo function
		else
		{
			$(this).click(action);
		}
	
		if(className!=undefined)
		{
			if($(this).attr("id") != currentSection)
			{
				$(this).hover(function(){$(this).removeClass(className+"_reposo").addClass(className+"_sobre")},
							  function(){$(this).removeClass(className+"_sobre").addClass(className+"_reposo")});
				$(this).addClass(className+"_reposo");
			}
			else
			{
				$(this).addClass(className+"_seleccionado");
			}
		}
		
		$(this).css("cursor", "pointer");
	};

	/**
	 * Convierte una etiqueta a en un makeTagLink con eventos de reposo, sobre y seleccionado leyendo el href o pasandoselo como parametro
	 * le aplica los eventos de (className_reposo y className_sobre) y className_seleccionado si estamos en la seccion actual
	 * @param function action: accion que se ejecutara al hacer click
	 * @param string className: raiz de la clase que se le pondra (si pasa menu se tendran que activar en css menu_reposo, menu_sobre y men_seleccionado)
	 * @param string currentSection: seccion actual para poner el boton como seleccionado siempre en lugar de reposo o sobre
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeALink(action, className, currentSection);
	 * $(selector).makeALink("location.php"); 						Ir a la url indicada (sobreescribe el href)
	 * $(selector).makeALink();										Ir a la url del href tambien se puede pasar (null o undefined)
	 * $(selector).makeALink(action);								Ejecutar una accion determinada
	 * $(selector).makeALink(null, className, currentSection);		Ir a la url indicada en href y aplicar classname y currentSection
	 *
	 * Vease tambien: makeTagLink, makeImgLink, makeCufonLink
	 *
	 */
	$.fn.makeALink = function(action, className, currentSection)
	{
		//Tipo location
		if(typeof(action) == "string")
		{
			$(this).click(function(){window.location = action});
		}
		//Tipo href
		else if ((action == undefined || action == null) && ($(this).attr("tagName") == "a" || $(this).attr("tagName") == "A") && $(this).attr("href") != undefined)
		{
			$(this).click(function(){window.location = $(this).attr("href")});
		}
		//Tipo function
		else
		{
			$(this).click(action);
		}
	
		if(className!=undefined)
		{
			if($(this).attr("id") != currentSection)
			{
				$(this).hover(function(){$(this).removeClass(className+"_reposo").addClass(className+"_sobre")},
							  function(){$(this).removeClass(className+"_sobre").addClass(className+"_reposo")});
				$(this).addClass(className+"_reposo");
			}
			else
			{
				$(this).addClass(className+"_seleccionado");
			}
		}
		
		$(this).css("cursor", "pointer");
	};
	
	/**
	 * Convierte varios enlaces <a> en una especie de makeTagLink pero de forma simultanea.
	 * Especificamos una class comun para todos lo enlaces (cada uno con su id)
	 * La clase hover se tiene que aplicar sobre la id del elemento (#btn_elemento:hover) en el css
	 * La clase selected se tiene que aplicar tambien sobre la id del elemento pero convirtiendola en class y añadiendole _sel (.btn_elemento_sel)
	 * @param function action: accion que se ejecutara al hacer click (null para que sea el href)
	 * @param string currentSection: seccion actual para poner el boton como seleccionado
	 * @return void
	 *
	 * Uso:
	 * $(selector_tipo_class).makeALinkWithHoverByClass(action, currentSection);
	 * $(selector_tipo_class).makeALinkWithHoverByClass("location.php"); 						Ir a la url indicada (sobreescribe el href)
	 * $(selector_tipo_class).makeALinkWithHoverByClass();										Ir a la url del href tambien se puede pasar (null o undefined)
	 * $(selector_tipo_class).makeALinkWithHoverByClass(action);								Ejecutar una accion determinada
	 * $(selector_tipo_class).makeALinkWithHoverByClass(null,  currentSection);					Ir a la url indicada en href y currentSection
	 *
	 * Vease tambien: makeTagLink, makeImgLink, makeCufonLink, makeALink
	 *
	 */
	$.fn.makeALinkWithHoverByClass = function(action, currentSection)
	{
		$(this).each(function()
		{
			//Tipo location
			if(typeof(action) == "string")
			{
				$(this).click(function(){window.location = action});
			}
			//Tipo href
			else if ((action == undefined || action == null) && ($(this).attr("tagName") == "a" || $(this).attr("tagName") == "A") && $(this).attr("href") != undefined)
			{
				$(this).click(function(){window.location = $(this).attr("href")});
			}
			//Tipo function
			else
			{
				$(this).click(action);
			}
			
			$(this).css("cursor", "pointer");
			
			if($(this).attr("id") == currentSection)
			{
				$(this).addClass(currentSection+"_sel");
			}
		});
	};


	/**
	 * Aplica un cufon y establece una accion (funcion o url) a una etiqueta cualquiera de html (div o span generalmente),
	 * le aplica los eventos de (className_reposo y className_sobre) y className_seleccionado si estamos en la seccion actual
	 * @param function action: accion que se ejecutara al hacer click
	 * @param string className: raiz de la clase que se le pondra (si pasa menu se tendran que activar en css menu_reposo, menu_sobre y men_seleccionado)
	 * @param string currentSection: seccion actual para poner el boton como seleccionado siempre en lugar de reposo o sobre
	 * @return void
	 *
	 * Uso:
	 * $(selector).makeCufonLink(action, className, currentSection);
	 *
	 * Vease tambien: makeTagLink, makeImgLink, makeALink
	 *
	 */
	$.fn.makeCufonLink = function(action, className, currentSection)
	{
		if($(this).size() == 0 || $(this) == undefined || $(this).attr("id") == undefined || $(this).attr("id") == null || $(this).attr("id") == ""  || !$(this).attr("id"))
		{
			//Error, no existe el elemento al que se le quiere aplicar la funcion
			return;
		}
	
		//Tipo location
		if(typeof(action) == "string")
		{
			$(this).click(function(){window.location = action});
		}
		//Tipo function
		else
		{
			$(this).click(action);
		}
		
		if(className!=undefined)
		{
			if($(this).attr("id") != currentSection)
			{
				$(this).hover(function(){$(this).removeClass(className+"_reposo").addClass(className+"_sobre");$.cufon($(this))},
							  function(){$(this).removeClass(className+"_sobre").addClass(className+"_reposo");$.cufon($(this))});
				$(this).addClass(className+"_reposo");
			}
			else
			{
				$(this).addClass(className+"_seleccionado");
			}
		}
		
		$.cufon($(this));	
		
		$(this).css("cursor", "pointer");
	};

	/*
	$.fn.makeImgUploader = function(allowedExtensions, url, maxSize)
	{
	var	allowedExtensions = ['jpg', 'png'];
	var url = 'ajax/uploadImageProfilePhotos.php';
	var maxSize =  2*1024*1024;   //2 Megas maximo
	
	
		var uploader;
		var img = $(this).find("img");
		var IEVersion = getInternetExplorerVersion();
		var textoSubida = uploaderText(dragText, dropText, otherText, $(this).attr("id")+"_uploader", img.attr("src"), "a");
		console.log(textoSubida);
		
		uploader = new qq.FileUploader(
		{
			height: img.attr("height"),
			width: img.attr("width"),
			// pass the dom node (ex. $(selector)[0] for jQuery users)
			element: $(this)[0],
			// path to server-side upload script
			action: url,
			// ex. ['jpg', 'jpeg', 'png', 'gif'] or []
			allowedExtensions: allowedExtensions,    
			//Parameters to be send
			//params: { "idCertificacion": ""},
			// size limit in bytes, 0 - no limit
			// this option isn't supported in all browsers
			sizeLimit: maxSize,
			// template for one item in file list
			fileTemplate: '<li class="qq-upload-element">' +
					'<span class="qq-upload-file"><\/span>' +
					'<span class="qq-upload-spinner"><\/span>' +
					'<span class="qq-upload-size"><\/span>' +
					'<a class="qq-upload-cancel" href="#">Cancel<\/a>' +
					'<span class="qq-upload-failed-text">Failed<\/span>' +
				'<\/li>',
			template: textoSubida,
			onComplete: function(){alert("a")},
			onSubmit: function(){alert("b")}
		});
		
		$(this).find(".qq-upload-list").hide();
		$(this).find(".qq-upload-button").css("padding", "0px");
		$(this).find(".qq-upload-button").css("min-height", "0px");
		$(this).css("cursor", "pointer").find(".qq-upload-button input").css("cursor", "pointer").width(img.attr("width")).height(img.attr("height"));
		
		$(this).hover(function(){$(this).find(".qq-upload-button img").attr("src", img.attr("src").replace(/reposo/, "sobre"))},
					  function(){$(this).find(".qq-upload-button img").attr("src", img.attr("src").replace(/sobre/, "reposo"))});
	}
	*/
	
	//////////////////////////////////////////
	// FUNCTIONS: FIX DE INTERNET EXPLORER  //
	//////////////////////////////////////////
	
	/**
	 * Arregla los problemas del IE7 o anterior o vista compatibilidad con el css inline-block (al que el llama solo inline)
	 * @param string param1: selector al que se le aplicara el css
	 * @param string param2: selector al que se le aplicara el css
	 * .... todos los que se necesiten
	 * @return void
	 *
	 * Uso:
	 * $.fixInline(param1, param2, ...);
	 *
	 */
	jQuery.fixInline = function()
	{
		try
		{
			//Fix vista compatibilidad IE8 (modo ie7)
			if(isIE7prev())
			{
				for (var i = 0; i < arguments.length; ++i)
				{
					$(arguments[i]).css("display","inline");
				}
			}
		}
		catch(e)
		{
		}
	};

	/**
	 * Arregla los height de los selects que se ven mal en IE7 o anterior
	 * @param string param1: selector al que se le aplicara el css
	 * @param string param2: selector al que se le aplicara el css
	 * .... todos los que se necesiten
	 * @return void
	 *
	 * Uso:
	 * $.fixMultipleSelects(param1, param2, ...);
	 *
	 */
	jQuery.fixMultipleSelects = function()
	{
		try
		{
			//Fix vista compatibilidad IE8 (modo ie7)
			if(isIE7prev())
			{
				for (var i = 0; i < arguments.length; ++i)
				{
					try
					{
						$(arguments[i]).css("height","auto");
					}
					catch(e2)
					{
					}
				}
			}
		}
		catch(e)
		{
		}
	};

	/**
	 * Añade ficheros CSS exclusivamente a IE7 o anterior
	 * @param string param1: nombre y ruta completa o relativa de la localizacion del fichero css
	 * @param string param2: nombre y ruta completa o relativa de la localizacion del fichero css
	 * .... todos los que se necesiten
	 * @return void
	 *
	 * Uso:
	 * $.addCSS_IE7prev(param1, param2, ...);
	 *
	 */
	jQuery.addCSS_IE7prev = function()
	{
		try
		{
			//Fix vista compatibilidad IE8 (modo ie7)
			if(isIE7prev())
			{
				
				for (var i = 0; i < arguments.length; ++i)
				{
					$.include(arguments[i]);
				}
			}
		}
		catch(e)
		{
		}
	};
	
	/**
	 * Añade ficheros CSS exclusivamente a IE8 o anterior
	 * @param string param1: nombre y ruta completa o relativa de la localizacion del fichero css
	 * @param string param2: nombre y ruta completa o relativa de la localizacion del fichero css
	 * .... todos los que se necesiten
	 * @return void
	 *
	 * Uso:
	 * $.addCSS_IE8prev(param1, param2, ...);
	 *
	 */
	jQuery.addCSS_IE8prev = function()
	{
		try
		{
			//Fix vista compatibilidad IE8 (modo ie7)
			if(isIE7prev())
			{
				
				for (var i = 0; i < arguments.length; ++i)
				{
					$.include(arguments[i]);
				}
			}
		}
		catch(e)
		{
		}
	};
	
	/**
	 * Añade ficheros CSS exclusivamente a todos los ie
	 * @param string param1: nombre y ruta completa o relativa de la localizacion del fichero css
	 * @param string param2: nombre y ruta completa o relativa de la localizacion del fichero css
	 * .... todos los que se necesiten
	 * @return void
	 *
	 * Uso:
	 * $.addCSS_IE(param1, param2, ...);
	 *
	 */
	jQuery.addCSS_IE = function()
	{
		try
		{
			//Fix vista compatibilidad IE8 (modo ie7)
			if(getInternetExplorerVersion()>-1)
			{
				
				for (var i = 0; i < arguments.length; ++i)
				{
					$.include(arguments[i]);
				}
			}
		}
		catch(e)
		{
		}
	};

	/**
	 * Aplica la propiedad css de maxWidth a todos los exploradores (incluido ie7)
	 * @param string maxWidth: width maximo
	 * @return void
	 *
	 * Uso:
	 * $(selector).setMaxWidth(maxWidth);
	 *
	 * Vease tambien: setMinWidth, setMaxHeight, setMinHeight
	 *
	 */
	$.fn.setMaxWidth = function(maxWidth)
	{
	   var width=$(this).parent().width();
	   $(this).width("auto");
	
	   if (width == null|| width == 0)
	   {
		 //element not found or not visible
		 return;
	   }
	
	   //if content-width>max-width then resize to the max-width
	   if (width>maxWidth)
	   {
			$(this).width(maxWidth);
	   }
	   else
	   {
		   $(this).width($(this).parent().width());
	   }
	};
	
	/**
	 * Aplica la propiedad css de minWidth a todos los exploradores (incluido ie7)
	 * @param string minWidth: width minimo
	 * @return void
	 *
	 * Uso:
	 * $(selector).setMinWidth(minWidth);
	 *
	 * Vease tambien: setMaxWidth, setMaxHeight, setMinHeight
	 *
	 */
	$.fn.setMinWidth = function(minWidth)
	{
	   var width=$(this).parent().width();
	   $(this).width("auto");
	   
	   if (width == null|| width == 0)
	   {
		 //element not found or not visible
		 return;
	   }
	
	   //if content-width>max-width then resize to the max-width
	   if (width<=minWidth)
	   {
			$(this).width(minWidth);
	   }
	   else
	   {
			$(this).width($(this).parent().width());
	   }
	};

	/**
	 * Aplica la propiedad css de maxHeight a todos los exploradores (incluido ie7)
	 * @param string maxHeight: height maximo
	 * @return void
	 *
	 * Uso:
	 * $(selector).setMaxHeight(maxHeight);
	 *
	 * Vease tambien: setMaxWidth, setminWidth, setMinHeight
	 *
	 */
	$.fn.setMaxHeight = function(maxHeight)
	{
	   var height=$(this).parent().height();
	   $(this).height("auto");
	
	   if (height == null|| height == 0)
	   {
		 //element not found or not visible
		 return;
	   }
	
	   //if content-width>max-width then resize to the max-width
	   if (height>maxHeight)
	   {
		 $(this).height(maxHeight);
	   }
	   else
	   {
		   $(this).height($(this).parent().height());
	   }
	};

	/**
	 * Aplica la propiedad css de minHeight a todos los exploradores (incluido ie7)
	 * @param string minHeight: height minimo
	 * @return void
	 *
	 * Uso:
	 * $(selector).setMinHeight(minHeight);
	 *
	 * Vease tambien: setMaxWidth, setminWidth, setMaxHeight
	 *
	 */
	$.fn.setMinHeight = function(minHeight)
	{
	   var height=$(this).parent().height();
	   $(this).height("auto");
	
	   if (height == null|| height == 0)
	   {
		 //element not found or not visible
		 return;
	   }
	
	   //if content-width>max-width then resize to the max-width
	   if (height<minHeight)
	   {
		 $(this).height(minHeight);
	   }
	   else
	   {
		   $(this).height($(this).parent().height());
	   }
	};

	
	/* 
	 * Añade una sombra a un texto incluso si el navegador no tiene soporte de sobra de css3
	 * @param json options: opciones de css
	 * @return void
	 *
	 * Uso:
	 * $(selector).textShadow(options);
	 *
	 * Requires: jQuery 1.2+
	 *
	 * Created by Martin Hintzmann 2008 martin [a] hintzmann.dk
	 * MIT (http://www.opensource.org/licenses/mit-license.php) licensed.
	 *
	 * Version: 0.2
	 * Requires: jQuery 1.2+
	 * http://plugins.jquery.com/project/textshadow
	 */
	$.fn.textShadow = function(option)
	{
		if (!$.browser.msie) return;
		var IE6 = $.browser.version < 7;
		return this.each(function() {

			var el = $(this);
			var shadow = el.textShadowParse(this.currentStyle["text-shadow"]);
			shadow = $.extend(shadow, option);
			
			if(!$(this).is("div") && ($(this).css("background-image")!=undefined || $(this).css("background-color")!=undefined || $(this).css("background")!=undefined))
			{
				var newdiv = $("<div />");
				newdiv.html($(this).html());
				newdiv.attr("class", $(this).attr("class"));
				newdiv.attr("style", $(this).attr("style"));
				if($(this).attr("href")!=undefined)
				{
					var action = $(this).attr("href").replace("javascript:", "");
					newdiv.css("cursor", "pointer");
					newdiv.click(function(){eval(action)});
				}
				newdiv.width($(this).width());
				newdiv.height($(this).height());
//				newdiv.attr("style", $(this).attr("style"));
				var events = $(this).data("events");
				for(var event_type in events)
				{
					for(var index in events[event_type])
					{	
						newdiv.bind(event_type, events[event_type][index]);
					}
				}

				$(this).parent().append(newdiv);
				$(this).remove();
				el = newdiv;
				//				alert("Detectado un elemento no compatible porque no es un div y tiene fondo!");
			}

			el.textShadowRemove();

			if (shadow.x == 0 && shadow.y == 0 && shadow.radius == 0) return;

			if (el.css("position")=="static") {
				el.css({position:"relative"});
			}
			el.css({zIndex:"0"});
			if (IE6) {
				el.css({zoom:"1"});
			}

			var span=document.createElement("span");
			$(span).addClass("jQueryTextShadow");
			$(span).html(el.html());
			$(span).css({
				padding:		this.currentStyle["padding"],	
				width:		el.width(),
				position:	"absolute",
				zIndex:		"-1",
				color:		shadow.color!=null?shadow.color:el.css("color"),
				left:			(-parseInt(shadow.radius)+parseInt(shadow.x))+"px",
				top:			(-parseInt(shadow.radius)+parseInt(shadow.y))+"px"
			});

			if (shadow.radius != 0) {
				if (shadow.opacity != null) {
					$(span).css("filter", "progid:DXImageTransform.Microsoft.Blur(pixelradius="+parseInt(shadow.radius)+", enabled='true', makeShadow='true', ShadowOpacity="+shadow.opacity+")");
				} else {
					$(span).css("filter", "progid:DXImageTransform.Microsoft.Blur(pixelradius="+parseInt(shadow.radius)+", enabled='true')");
				}
			}	
			el.append(span);
		
	  });
	};
	
	/*
	 * Necesaria para hacer la sobra incluso si no soporta el navegador sombre de CSS3
	 * 
	 * Vease: textShadow
	 *
	 */
	$.fn.textShadowParse = function(value) 
	{
		value = String(value)
			.replace(/^\s+|\s+$/gi, '')
			.replace(/\s*!\s*important/i, '')
			.replace(/\(\s*([^,\)]+)\s*,\s*([^,\)]+)\s*,\s*([^,\)]+)\s*,\s*([^\)]+)\s*\)/g, '($1/$2/$3/$4)')
			.replace(/\(\s*([^,\)]+)\s*,\s*([^,\)]+)\s*,\s*([^\)]+)\s*\)/g, '($1/$2/$3)')
	
		var shadow =
		{
			x      : 0,
			y      : 0,
			radius : 0,
			color  : null
		};

		if (value.length > 1 || value[0].toLowerCase() != 'none')
		{
			value = value.replace(/\//g, ',');
			var color;
			if ( value.match(/(\#[0-9a-f]{6}|\#[0-9a-f]{3}|(rgb|hsb)a?\([^\)]*\)|\b[a-z]+\b)/i) && (color = RegExp.$1) )
			{
				shadow.color = color.replace(/^\s+/, '');
				value = value.replace(shadow.color, '');
			}

			value = value.replace(/^\s+|\s+$/g, '').split(/\s+/).map(function(item) {return (item || '').replace(/^0[a-z]*$/, '') ? item : 0 ;});

			switch (value.length)
			{
				case 1:
					shadow.x = shadow.y = value[0];
					break;
				case 2:
					shadow.x = value[0];
					shadow.y = value[1];
					break;
				case 3:
					shadow.x = value[0];
					shadow.y = value[1];
					shadow.radius = value[2];
					break;
			}
			if ((!shadow.x && !shadow.y && !shadow.radius) || shadow.color == 'transparent')
			{
				shadow.x = shadow.y = shadow.radius = 0;
				shadow.color = null;
			}
		}

		return shadow;
	};

	/*
	 * Necesaria para hacer la sobra incluso si no soporta el navegador sombre de CSS3
	 * 
	 * Vease: textShadow
	 *
	 */
	$.fn.textShadowRemove = function()
	{
		if (!$.browser.msie) return;
		return this.each(function() {
			$(this).children("span.jQueryTextShadow").remove();
		});
	};
	
	
	/**
	* 
	*/
	/**
	 * Calcula y aplica el tamaño maximo de pantalla que se puede utilizar para aplicarselo a un div principal
	 * @param boolean onChange: hacer seguimiento de cuando cambie el tamaño de la ventana (volver a calcular)
	 * @param boolean sourceDocumentWindow: si es true o undefined se utilizara document si es false se utilizara window (la diferencia es que document nunca decrece de tamaño y window si)
	 * @return void
	 *
	 * Uso:
	 * $(selector).setMaxDocumentHeight(true);
	 *
	 */
	$.fn.setMaxDocumentHeight = function(onChange, sourceDocumentWindow, maxSize)
	{
		var element = $(this);
		var tipo;
		if(sourceDocumentWindow == undefined || sourceDocumentWindow == true)
		{
			tipo = document;
		}
		else
		{
			tipo =  window;
		}	
		if(maxSize!=undefined && $(tipo).height()>maxSize)
		{
			return;
		}
		
		var paddingTop = $(this).css("padding-top").replace("px", "")*1;
		var paddingBottom = $(this).css("padding-bottom").replace("px", "")*1;
		var marginTop = $(this).css("margin-top").replace("px", "")*1;
		var marginBottom = $(this).css("margin-bottom").replace("px", "")*1;
		var borderTop = $(this).css("border-top-width").replace("px", "")*1;
		var borderBottom = $(this).css("border-bottom-width").replace("px", "")*1;
		
		var sobrante = (isNaN(paddingTop) ? 0 : paddingTop) + (isNaN(paddingBottom) ? 0 : paddingBottom) + (isNaN(marginTop) ? 0 : marginTop) + (isNaN(marginBottom) ? 0 : marginBottom) + (isNaN(borderTop) ? 0 : borderTop) + (isNaN(borderBottom) ? 0 : borderBottom);

		$(this).height($(tipo).height()-sobrante+"px");
		//console.log( paddingTop+", "+paddingBottom+", "+marginTop +", "+ marginBottom +", "+ borderTop +", "+ borderBottom+", "+($(tipo).height()-sobrante));

		
		if(onChange!=undefined && onChange != false)
		{
			$(window).resize(function()
			{
				element.setMaxDocumentHeight(false, sourceDocumentWindow, maxSize);
			});
		}
	};
	
	/**
	 * Ocultar elementos para mostrarlos poco a poco con un boton (Tipo Ver Mas)
	 * @param String elementClass: el nombre de la "clase" de los elementos que se "paginaran" (cada elemento tiene que tener esa clase
	 * Ejemplo: <div class="a">1</div>   <div class="a">2</div>  <div class="a">3</div> => En este caso el elementClass seria "a"
	 * @param int maxElementsPerPage: numero de elementos que se iran agregando (primero seran x luego x por 2 luego x por 3....)
 	 * @param String viewMoreText: Texto que aparecera
	 * @return void 
	 *
	 * Uso:
	 *	$.viewMore()
	 */
	jQuery.viewMore = function(elementClass, maxElementsPerPage, viewMoreText)
	{
		$('.'+elementClass+':gt('+(maxElementsPerPage-1)+')').hide().last().after(
			$(viewMoreText).attr('href','#').click(function(){
				var a = this;
				$('.'+elementClass+':not(:visible):lt('+maxElementsPerPage+')').fadeIn(function(){
				 if ($('.'+elementClass+':not(:visible)').length == 0) $(a).remove();   
				}); return false;
			})
		);
	};
