/** Script: ShCalendarioDate
  * Descripcion: Clase que almacena fecha y hora, permitiendo obtener la misma
  *             en distintos formatos
  * Version 1.0.17
  */

/** Mini-guia de utilización
Constructor:
  + ShCalendario ({ idSeccionDiv: <idSeccionDiv>,
                    idObjeto    : <idObjeto>,
                    idNombreEdit: <idNombreEdit>,
                    alineacion  : <alineacion>,
                    formato     : <formato>,
                    fechaInicial: <fechaInicial>,
                    plegable    : <plegable>,
                    top         : <top>,
                    left        : <left>,
                    tabulacion  : <tabulacion>,
                    idTabulacion: <idTabulacion>,
                    imgBaseDir  : <imgBaseDir>,
                    noNulo      : <true | false>,
                    manejadorOnClick: <manejadorOnClick>,
                    manejadorOnBlur : <manejadorOnBlur> })

   - idSeccionDiv  : Nombre de la seccion (span o div) donde se generara el calendario DHTML (requerido)
   - idObjeto  		 : Nombre interno del objeto shCalendar (requerido)
   - idNombreEdit  : Nombre del parametro de formulario asociado al edit generado.
   - alineacion  	 : Alineacion de la seccion de despliegue ('izq'/'der')
   - formato			 : Formato de lectura/escritura de fecha seleccionada:
                     d, dd    -> cifra del dia con una/dos cifra
                     m, mm    -> cifra del mes con una/dos cifra
                                 [El indicador de una/dos cifras es util para el formateo de salida,
                                  indiferente para la entrada de fecha]
                     mmm      -> tres primeras letras inciales del mes (ene, feb...)
                     yy, yyyy -> decada/cifra completa del año
                     'dd-mm-yyyy', 'd/m/yy', 'dd&mmmm&yy', 'd*m*yyyy' son formatos de fechas correctas
   - fechaInicial: Fecha inicial a mostrar
   - plegable: Indica si el calendario es desplegable o estático
   - top: Valor top del estilo del calendario con posición fija (aunque sea desplegable)
   - left: Valor left del estilo del calendario con posición fije (aunque sea desplegable)
   - tabulacion: Indica si el calendario va incluido en una shTabs (corrige un pequeño bug en el comportamiento)
   - idTabulacion: Identificador de la tabulacion que incluye el calendario (implica «tabulacion: true»)
   - imgBaseDir: Ruta relativa donde se alojan las imagenes que usa el calendario (p.d. «js/shCalendario/img»)
   - noNulo: Indica si se permite valor nulo como resultado en el input de entrada de fecha (En ambos casos se parsea y corrige una cadena no nula).
   - manejadorOnClick: Nombre de funcion javascript asociada al evento onClick de cada día
   - manejadorOnBlur: Nombre de funcion javascript asociada al evento onBlur del input de entrada
*/

/*
[TODO: Posibilidad de construir cadenas horarias con segundos y milisegundos (no necesario ahora)]
*/

/** Vbles. globales:
  * Vectores constantes con los nombres de los dias de la semana y los meses del año
  */
var dias        = new Array ('Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo');
var iniciales   = new Array ('L', 'M', 'X', 'J', 'V', 'S', 'D');
var meses       = new Array ('Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep',
                             'Oct', 'Nov', 'Dic');
var mesesLargos = new Array ('Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio',
                             'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre');
var diasDelMes  = new Array (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

/** Constantes de meses de control (bisiesto, primer y ultimo mes) */
var enero     = 0;
var febrero   = 1;
var diciembre = 11;

/** Constante de año maximo formato UTC (objeto Date) */
var anyoMaxUTC = 275760;

/** Constante ruta base de imagenes utilizadas */
var rutaImagenes = 'js/shCalendario/img/';

/**
  * Rutinas de posicionamiento en la pagina y utilidades visuales
  */

/**
  * Funcion     : FindPosX
  * Descripcion : Obtiene la posicion absoluta de un elemento web
  * Parametros  : La instancia del objeto
  * Retorna     : Los pixeles de la coordenada X del objeto referencia
  */
function findPosX (obj) {

  var curleft = 0;

  if (obj.offsetParent) {
    while (obj.offsetParent) {
      curleft += obj.offsetLeft;
      obj      = obj.offsetParent;
    }
  }
  else
   // En Netscape y similares cada objeto
   // contiene su posicion almacenada en un atributo
   if (obj.x)
     curleft += obj.x;

  return curleft;
}

/**
  * Funcion     : FindPosY
  * Descripcion : Obtiene la posicion absoluta de un elemento web
  * Parametros  : La instancia del objeto
  * Retorna     : Los pixeles de la coordenada Y del objeto referencia
  */
function findPosY (obj) {

  var curtop = 0;

  if (obj.offsetParent) {
    while (obj.offsetParent) {
      curtop += obj.offsetTop;
      obj     = obj.offsetParent;
    }
  }
  else
    // En Netscape y similares cada objeto
    // contiene su posicion almacenada en un atributo
    if (obj.y)
      curtop += obj.y;

  return curtop;
}

/**
  * Clase coordXY: Clase con dos atributos (X, Y) que indican la posicion html absoluta
  */

/**
  * Funcion     : coordXY
  * Descripcion : Constructor de la clase. Obtiene las coordenadas absolutas del elemento
  *              parametrizado
  * Parametros  : La id del objeto referencia
  * Retorna     : Una instancia de clase inicializada a las coord. absolutas del elemento referencia
  */
function coordXY (idObj) {

  var obj = document.getElementById (idObj);

  this.x       = findPosX (obj);
  this.y       = findPosY (obj);
  this.offsetH = obj.clientWidth + 2; // Añadimos un pequeño offset debido al borde del icono
  this.offsetV = obj.clientHeight + 2;

  return this;
}

/**
  * Funcion     : posicionarDespliegue
  * Descripcion : Posiciona la seccion de despliegue
  * Parametros  : El identificador de la seccion de despliegue,
  *              el identificardor del objeto de referencia y la alineacion
  *              respecto al objeto de referencia para evitar colisiones con el
  *              margen de la pagina (izq/der). En el caso de calendarios dentro
  * 						 de tabulaciones, el parametro opcional cabecera proporciona
  *              el id de la tabulacion principal.
  */
function posicionarDespliegue (objDivCalendar, referencia, alineacion, cabecera) {

  // Obtenemos las coordenadas absolutas del objeto de referencia
  var posicion = new coordXY (referencia);

  // Control de calendario en una tabulacion
  if (cabecera != null) {
    var posCabecera = new coordXY (cabecera);
    var tabulado = true;
  }

  if (!alineacion)
    alineacion = new String ('izq');

  if (tabulado)
		// El cálculo posiciona cuatro pixeles más abajo, debido a que se le asignan desp. de 1px a
		// algunas etiquetas tbody y table, intentamos subir un poco el calendario disminuyendo el
		// desplazamiento en una proporcion de la mitad de la altura del icono boton
    objDivCalendar.style.top  = (posicion.y - posCabecera.y - (posicion.offsetV / 2)) + 'px';
  else
    objDivCalendar.style.top  = (posicion.y + posicion.offsetV) + 'px';

  // Si la alineacion es "der" situamos la esquina superior
  //derecha del tip alineada a la esquina inferior izquierda de
  //la referencia, eoc la alineacion es "izq", situamos la esquina superior
  //izquierda del tip alineada a la esquina inferior derecha de
  //la referencia
  if (alineacion == 'der')
    objDivCalendar.style.left = ((posicion.x + posicion.offsetH) - objDivCalendar.clientWidth) + 'px';
  else
    objDivCalendar.style.left = posicion.x + 'px';
}

/**
  * Utilidades sobre una fecha y construccion de formato de fecha
  */

/** Funcion    : justificarIzq
  * Descripcion: Introduce ceros por la izquierda en horas/minutos con una sola cifra
  * Parametros : La cifra a justificar
  * Retorna    : Una cadena con el valor AM si es horario de ma?ana, PM e.o.c
  */
function justificarIzq (cifra) {

  if (cifra < 10) cifra = "0" + cifra;

  return cifra;
}

/** Funcion    : setHoraAmPm
  * Descripcion: Calcula la hora en formato 12h
  * Parametros : El valor horario en formato 24h
  * Retorna    : Una cadena con el valor AM si es horario de ma?ana, PM e.o.c
  */
function setHoraAmPm (hora) {

  var horaAmPm = hora;

  if (horaAmPm == 0) horaAmPm = 12;

  if (horaAmPm > 12)
    horaAmPm -= 12;

  horaAmPm = justificarIzq (horaAmPm);

  return horaAmPm;
}

/** Funcion    : setAmPm
  * Descripcion: Calcula el uso horario Ante/Post Meridian
  * Parametros : El valor horario en formato 24h
  * Retorna    : Una cadena con el valor 'AM' si es horario de ma/ana, 'PM' e.o.c
  */
function setAmPm (hora) {

  var amPmStr = "am";

  if (hora == 0) hora = 12;
  if (hora > 11)
    amPmStr = "pm";

  return amPmStr;
}

/** Funcion    : fechaFormatoLargo
  * Descripcion: Retorna la cadena de una fecha en formato largo (DDDD, DD de MM de YYYY)
  * Parametros : Un objeto ShDate
  * Retorna    : Una cadena con el formato resultante
  */
function fechaFormatoLargo (objShDate) {

  var strResult = objShDate.nombreDia + ', ' + objShDate.dia + ' de ' +
                  mesesLargos [objShDate.mes-1] + ' de ' + objShDate.anyo;

  return strResult;
}

/** Funcion    : fechaFormatoCorto
  * Descripcion: Retorna la cadena de una fecha en formato corto (DD / MM / YYYY)
  * Parametros : Un objeto ShDate y un caracter delimitador
  * Retorna    : Una cadena con el formato resultante
  */
function fechaFormatoCorto (objShDate, delimitador) {
  var simbolo = (delimitador)? delimitador : "/";
  var strResult = "" + objShDate.dia + simbolo + objShDate.mes + simbolo + objShDate.anyo;

  return strResult;
}

/** Funcion    : fechaFormatoMedio
  * Descripcion: Retorna la cadena de una fecha en formato corto (DDDD, DD / MM / YYYY)
  * Parametros : Un objeto ShDate y un caracter delimitador
  * Retorna    : Una cadena con el formato resultante
  */
function fechaFormatoMedio (objShDate, delimitador) {
  var simbolo = (delimitador)? delimitador : "/";
  var strResult = objShDate.nombreDia + ", " + objShDate.dia + simbolo + objShDate.mes + simbolo + objShDate.anyo;

  return strResult;
}

/** Funcion    : horaFormatoLargo
  * Descripcion: Retorna la cadena de una hora en formato largo (HH:MM, 24h)
  * Parametros : Un objeto ShDate
  * Retorna    : Una cadena con el formato resultante
  */
function horaFormatoLargo (objShDate) {

  var strResult = objShDate.horas + ':' + objShDate.minutos;

  return strResult;
}

/** Funcion    : horaFormatoCorto
  * Descripcion: Retorna la cadena de una hora en formato corto (HH:MM AM / PM)
  * Parametros : Un objeto ShDate
  * Retorna    : Una cadena con el formato resultante
  */
function horaFormatoCorto (objShDate) {

  var strResult = objShDate.horaAmPm + ':' + objShDate.minutos + ' ' + objShDate.amPm;

  return strResult;
}

/**
  * Funcion    : getEnumDia
  * Descripcion: Calcula el orden de dia de la semana tomando como
  *             dia inicial el Lunes
  * Parametros : Orden del dia de la semana (0-Domingo, 6-Sabado)
  * Retorna    : El orden de dia de la semana
  */
function getOrdenDia (diaSemana) {

  // La clase Date enumera al Domingo como '0'
  // ShDate contempla el Lunes como dia inicial
  // valorEnumDia        = objShDate.date.getDay ();

  var valorEnumDia = (0 == diaSemana)? 6 : --diaSemana;

  return valorEnumDia;
}

/**
  * Funcion    : incluirFecha
  * Descripcion: Introduce en la sección indicada la fecha de hoy con formato
  * Parametros : Identificador de la seccion y el tipo de formato (largo/corto)
  * Retorna    : Nada
  */
function incluirFecha (id, tipo) {

  var obj       = document.getElementById (id);
  var objShDate = new ShDate ();

  (tipo == 'largo')?
      obj.innerHTML = fechaFormatoLargo (objShDate):
      obj.innerHTML = fechaFormatoCorto(objShDate, '-');
}

/**
  * Clase ShDate: Objeto con valores de fecha en formato natural
  */

/** Funcion    : ShDate
  * Descripcion: Instancia e inicializa un objeto ShDate
  * Parametros : Objeto de tipo Date
  * Propiedades:
  *               date: Instancia de la clase estandar Date (facilita las
  *                    operaciones de navegacion, control...)
  *               anyo, mes... : Valores de la fecha seleccionada/almacenada
  *               minutos, segundos... : Valores de la hora seleccionada
  *               diaSemanaInicial: Valor del orden de dia de la semana del
  *               dia inicial del mes (necesario para calcular el calendario)
  */
function ShDate (date) {

  var objShDate = this;

  if (!date)
    date = new Date();

  objShDate.date      = date;
  objShDate.anyo      = null;
  objShDate.mes       = null;
  objShDate.dia       = null;
  objShDate.nombreMes = null;
  objShDate.diasMes   = null;
  objShDate.ordenDia  = null;
  objShDate.nombreDia = null;
  objShDate.minutos   = null;
  objShDate.segundos  = null;
  objShDate.horaAmPm  = null;
  objShDate.amPm      = null;
  objShDate.diaSemanaInicial = null;

  actualizarFecha (objShDate);

  return (objShDate);
}

/** Funcion    : actualizarFecha
  * Descripcion: Inicializa/actualiza un objeto ShDate
  * Parametros : Objeto de tipo ShDate
  * Descripcion: Actualiza las propiedades del objeto ShDate
  *             a partir de la fecha de tipo Date almacenada
  * NOTA       : Forma rapida de obtener el n de dias del mes (testear si IE
  *             lo acepta, firefox sin problemas):
  *                  // Inicializa el objeto en año, mes, dia 1, 12:00pm horas
  *                  with (new Date(YY, MM, 1, 12)) {
  *                    // Establecer el dia como 0 equivale a evaluar el n
  *                    // de dias del mes (incluyendo calculo bisiestos)
  *                    setDate(0);
  *                    return getDate();
  *                  }
  */
function actualizarFecha (objShDate) {

  objShDate.anyo      = objShDate.date.getFullYear ();
  objShDate.mes       = justificarIzq (objShDate.date.getMonth ()+1);
  objShDate.dia       = justificarIzq (objShDate.date.getDate ());
  objShDate.nombreMes = meses [objShDate.date.getMonth ()];

  // Control de año bisiesto (ver NOTA en cabecera)
  if (objShDate.date.getMonth () == febrero)
    objShDate.diasMes = ((0 == objShDate.anyo % 4) && (0 != (objShDate.anyo % 100))) ||
                         (0 == objShDate.anyo % 400) ? 29 : 28;
  else
    objShDate.diasMes = diasDelMes [objShDate.date.getMonth ()];

  // Calculamos el orden del dia inicial del mes
  // Solicitamos el n de orden del dia 1 del mes seleccionado a las 12 AM
	with (new Date(objShDate.date.getFullYear(), objShDate.date.getMonth(), 1, 12)) {
    // Obtenemos el dia de la semana del primer dia de ese mes
  	objShDate.diaSemanaInicial = getOrdenDia (getDay());
  }

  // Obtenemos el dia de la semana seleccionado
  objShDate.ordenDia  = getOrdenDia (objShDate.date.getDay());
  objShDate.nombreDia = dias [objShDate.ordenDia];
  objShDate.horas     = justificarIzq (objShDate.date.getHours ());
  objShDate.minutos   = justificarIzq (objShDate.date.getMinutes ());
  objShDate.segundos  = justificarIzq (objShDate.date.getSeconds ());

  objShDate.horaAmPm  = setHoraAmPm (objShDate.date.getHours ());
  objShDate.amPm      = setAmPm     (objShDate.date.getHours ());
}

/**
  * Clase shCalendario: Construye un componente calendario asociado a una entrada de texto
  */

/**
  * Funcion    : shCalendario
  * Descripcion: Crea un objeto de la clase shCalendario
  * Parametros : El nombre de la seccion div/span, el nombre de la variale
  *              que alojara globalmente el objeto Calendario, el nombre del parámetro
  *              de formulario, la alineacion de despliegue, el formato de fecha
  *              y la fecha a mostrar inicialmente (opcional)
  *
  * TODO       : Aceptar fechas maximas y minimas de control (afecta a las
  *             funciones de navegacion)
  *              Habilitar/Deshabilitar Calendario
  *							 Generar la seccion DIV que aloja el calendario mediante funciones
  *             manipuladoras del arbol DOM
  */
function shCalendario(opciones) {

  // Creamos un objeto Date con la fecha actual p.d.
  var objFecha = new Date();
  // Cadena generadora del render
  var strResult = new String();
  // Estilo del tipo de calendario
  var estiloCSS = new String();
  // Informacion de version
  this.version = "1.0.16";

  // Inicializamos los valores de las opciones
  this.opciones = opciones;

  // Obtenemos un objeto ShDate del año y mes seleccionado
  this.objDate  = new ShDate(objFecha);

  // Opciones requeridas (nombre de vble y seccion que alojara el calendario)
  if (this.getParam("idObjeto", null) == null)
    throw "Nombre de instancia nulo o no encontrado"
  else
	  // El identificador de objeto sigue siendo necesario para completar las
	  // llamadas de despliegue y la simulacion de 'hover' en la imagen de boton
	  this.idObjeto = this.getParam("idObjeto");

  // Obtenemos id ppal de las tabs si es un calendario incluido en shTags
  if ((this.getParam("idTabulacion", null) == null) &&
			(this.getParam("tabulacion", false) != false))
	    throw "Identificador de shTags nulo o no encontrado";

  if (this.getParam("idSeccionDiv", null) == null)
    throw "Nombre de seccion (div/span) nulo o no encontrado";
  else
    // Nodo DOM de la seccion
    this.seccion = document.getElementById(this.getParam("idSeccionDiv"));

  // Establecemos las opciones por defecto (si no han sido inicializadas)
  this.setParam("alineacion", "izq");
  this.setParam("idNombreEdit", "EditCal");
  this.setParam("imgBaseDir", rutaImagenes);

  this.setParam("manejadorOnClick", null);
  this.setParam("manejadorOnBlur", null);
  this.setParam("seleccion", this.getParam("fechaInicial", null));

  // Creacion de atributos asociados
  // Nombre del edit generado (parám. formulario)
  this.prefijoTema   = this.getParam("prefijoTema", "cal");
  this.estiloInput   = this.getParam("estiloInput", this.prefijoTema + "-CajaTexto");
  this.formato       = this.getParam("formato", "dd-mm-yyyy");
  this.plegable      = this.getParam("plegable", "true");
  this.top           = this.getParam("top", null);
  this.left          = this.getParam("left", null);
  this.tabulacion    = this.getParam("tabulacion", "false");
  this.noNulo        = this.getParam("noNulo", "true");
  this.idInput       = this.getParam("idSeccionDiv") + "TextInput";
  this.nameInput     = this.getParam("idNombreEdit");
  this.idBtn         = this.getParam("idSeccionDiv") + 'Button';
  this.idDivCal      = this.getParam("idSeccionDiv") + 'Calendario';
  this.fechaInicial  = this.getParam("fechaInicial", '');
  this.seleccion     = this.getParam("fechaInicial", '');

	// Asignamos el manejador de evento OnClick y OnBlur por defecto
  this.manejadorOnClick = this.getParam("manejadorOnClick", null);
  this.manejadorOnBlur  = this.getParam("manejadorOnBlur", null);
  this.posicionable = (this.top == null);

  estiloCSS = (this.plegable)? "shCalendar" : "shCalendarEstatico";

  // Si el calendario sera incluido dentro de un tab, debe tener otro estilo
  if (this.tabulacion) {
    estiloCSS = "shCalendarTab";
  }

  if (this.plegable)
  {
	  // Valor de inicializacion del valor de la caja de texto
	  if (this.fechaInicial != '')
	    this.parsearFecha(this.fechaInicial);

	  // Declaracion de la caja de texto contenedora de la fecha y el boton de despliegue
	  strResult  = "<input class='"+ this.estiloInput + "' type='text' id='" +  this.idInput +
	               "' name='"+ this.nameInput + "' size='10' width='10' maxlength='20' " +
	               "value='" + this.seleccion +
                 "' onBlur='" + this.idObjeto + ".onBlurInput()' />";
	  strResult += "<img class='"+ this.prefijoTema +"-symbolBtn' src='" + this.getParam("imgBaseDir") +
	  						 "btn_date_up.png' id = '" + this.idBtn + "' onclick='" +
	  						 this.idObjeto + ".desplegarCalendario()' " +
	  						 "onmouseover=\"this.src='" + this.getParam("imgBaseDir") + "btn_date_down.png'\" " +
	  						 "onmouseout=\"this.src='" + this.getParam("imgBaseDir") + "btn_date_up.png'\" />";
  }

  // Declaracion de la seccion div donde se renderizara el calendario
  strResult += "<div id='" + this.idDivCal + "' class='" + estiloCSS + "'></div>";

  // Incluimos las etiquetas en la seccion
  this.seccion.innerHTML = strResult;

  this.renderizarCalendario();

  return this;
}

/**
  * Funcion    : getParam
  * Descripcion: Obtiene el valor de las opciones
  * Parametros : El nombre de la opcion, su valor por defecto
  */
shCalendario.prototype.getParam = function(nombre, valorPd) {

	var valor = (typeof(this.opciones[nombre]) == "undefined") ? valorPd : this.opciones[nombre];

	// Calculamos valores booleanos
	if (valor == "true" || valor == "false")
		return (valor == "true");
	return valor;
};

/**
  * Funcion    : setParam
  * Descripcion: Establece el valor de las opciones (si valor recibido es nulo,
  *             asigna el valor por defecto establecido)
  * Parametros : El nombre de la opcion, su nuevo valor
  */
shCalendario.prototype.setParam = function(nombre, nuevo_valor) {
	this.opciones[nombre] = this.getParam(nombre, nuevo_valor);
};

/**
  * Funcion     : parsearFecha
  * Descripcion : Parsea la fecha contenida en la caja de texto e inicializa el calendario
  *               en esa fecha.
  * Parametros  : La cadena fecha
  *
  * NOTA        : Existe un bug de Mozilla Firefox en la función parseInt.
  *              «parseInt("08")» y «parseInt("09")» retornan 0::Integer.
  *               Es comprensible que se considere una cantidad con ceros a la izquierda como una
  *              cadena octal, pero en ese caso, la conversión del valor "08" o "09" debería retornar
  *              el valor NaN, en lugar de 0, comportándose del mismo modo que «parseInt("0xG")».
  *               Es por eso que se eliminan ceros por la izquierda antes de convertir
  *              utilizando la función String.replace(/^[0]*([1-9]*)/,"$1")
  */
shCalendario.prototype.parsearFecha = function(fecha) {

  // Parseamos en tokens la fecha y el formato de la misma
  var strFecha    = fecha.split(/\W+/);
  var formato     = (this.formato.toLowerCase()).split (/\W+/);
  var separador   = this.formato.replace(/\S+(\W+)\S+/, "$1");
  var cadenaMeses = meses.toString();
  var dia, mes, anyo, nombreMes;
  var parseError;

  // Recorremos los distintos tokens
  // Nota: Controles de formato para imprimir la fecha de salida
  for (var i = 0; i < strFecha.length; i++) {
    switch (formato [i]) {
      case "d"    :
      case "dd"   : dia = parseInt(strFecha [i].replace(/^[0]*([1-9]*)/,"$1")); break;
      case "m"    :
      case "mm"   : mes = parseInt(strFecha [i].replace(/^[0]*([1-9]*)/,"$1")); break;
      case "mmm"  : nombreMes = strFecha [i]; break; // formato nombre de mes como cadena abreviada
      case "yy"   :
      case "yyyy" : anyo = parseInt(strFecha [i].replace(/^[0]*([1-9]*)/,"$1")); break;
    }
  }

  // Si el mes es una cadena construimos la fecha mediante parseo
  if (this.formato.search(/m{3,}/) > 0) {
    mes = meses.indexOf(nombreMes);
  }

  parseError = isNaN(dia) || isNaN(mes) || isNaN(anyo);

  // Si no han ocurrido errores de parseo, actualizamos la fecha
  //del objeto date eoc no utilizamos la fecha introducida en
  //el input de texto y mantenemos la fecha anterior
  if (!parseError)
  {
    this.objDate.date.setTime( Date.parse ( mes + "/" + dia + "/" + anyo ) );

    // Actualizamos los datos del objeto ShDate
    actualizarFecha(this.objDate);
  }
  // Actualizamos la fecha seleccionada
  this.seleccion = fechaFormatoCorto (this.objDate, separador);
}

/**
  * Funcion    : desplegarCalendario
  * Descripcion: Muestra/oculta un calendario desplegable.
  */
shCalendario.prototype.desplegarCalendario = function() {

  var objDivCalendario = document.getElementById(this.idDivCal);
  var inputText        = document.getElementById(this.idInput);

  if (objDivCalendario.style.visibility == 'visible') {
    objDivCalendario.style.visibility = 'hidden';
	  // Mostramos ese valor en el campo de texto
	  inputText.value = this.seleccion;
    inputText.focus();
  }
  else
  {
    // Si el campo de fecha no es vacio, parseamos la fecha
    if (inputText.value) this.parsearFecha(inputText.value);
    this.renderizarCalendario();
    objDivCalendario.style.visibility = 'visible';
  }
}

/**
  * Funcion    : renderizarCalendario
  * Descripcion: Renderiza la tabla y elementos del calendario mensual
  */
shCalendario.prototype.renderizarCalendario = function() {

  // Obtenemos seccion de despligue
  var objDivCalendar = document.getElementById(this.idDivCal);

  var nombreMesLargo = mesesLargos[this.objDate.date.getMonth ()];

  // Num de columnas generadas en la cabecera (una mas si es desplegable)
  var columnas = (this.plegable)? 8:7;

  // Posicionamos la seccion de despliegue calculando su posicion absoluta
  if (this.plegable && this.posicionable)
    posicionarDespliegue(objDivCalendar, this.idBtn,
                         this.getParam('alineacion'), this.getParam('idTabulacion'));
  else
  {
    objDivCalendar.style.top  = this.top;
    objDivCalendar.style.left = this.left;
  }


  var strResult = "<table class='"+ this.prefijoTema +"-tabla' cellspacing='0' cellpadding='0'><tbody>";

  // Cabecera con selectores de Mes y Año btnNavOverOut (idBoton, estiloCSS)
  strResult += "<tr><th class='"+ this.prefijoTema +"-btnNavegador'><img class='"+ this.prefijoTema +"-symbolBtn' src='" +
               this.getParam("imgBaseDir") + "prior.png' id='" +
               this.idObjeto + "MesAnt' onclick='"+ this.idObjeto + ".mesAnterior ()'" +
               "title='Mes anterior'/></th>";

  strResult += "<th class='"+ this.prefijoTema +"-nombreMes' title='" + nombreMesLargo +"'>" + this.objDate.nombreMes + "</th>";

  strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'><img class='"+ this.prefijoTema +"-symbolBtn' src='" +
               this.getParam("imgBaseDir") + "next.png' id='" +
               this.idObjeto + "MesSig' onclick='" + this.idObjeto + ".mesSiguiente()' " +
               "title='Mes siguiente'/></th>";

  // strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'><img class='"+ this.prefijoTema +"-symbolBtn' src='" +
  //             this.getParam("imgBaseDir") + "hoy.png' id='" +
  //             this.idObjeto + "Hoy' onclick='" + this.idObjeto + ".irAHoy()' " +
  //             " title='Seleccionar fecha actual'/></th>";

  strResult += "<th class='" + this.prefijoTema + "-btnNavegador'>&nbsp;</th>";

  strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'><img class='"+ this.prefijoTema +"-symbolBtn' src='" +
               this.getParam("imgBaseDir") + "prior.png' id='" +
               this.idObjeto + "AnyoAnt' onclick='" + this.idObjeto + ".anyoAnterior()' " +
               "title='A&ntilde;o anterior'/></th>";

  strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'>" +
               "<div class='"+ this.prefijoTema +"-inputAnyo'" +
               "id='" + this.idObjeto + "AnyoInput' " +
               "title='" + this.objDate.anyo + "'>" + this.objDate.anyo + "</div></th>";

  //strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'>" +
  //             "<input type='text' size='4' class='"+ this.prefijoTema +"-inputAnyo'" +
  //             "id='" + this.idObjeto + "AnyoInput' " +
  //             "value='" + this.objDate.anyo + "' " +
  //             "onchange=\"" + this.idObjeto + ".anyoOnChange('" + this.idObjeto + "AnyoInput')\" " +
  //             "title='" + this.objDate.anyo + "'/></th>";

  strResult += "<th class='"+ this.prefijoTema +"-btnNavegador'><img class='"+ this.prefijoTema +"-symbolBtn' src='" +
               this.getParam("imgBaseDir") + "next.png' id='" +
               this.idObjeto + "AnyoSig' onclick='" + this.idObjeto + ".anyoSiguiente()'" +
               "title='A&ntilde;o siguiente'/></th>";

  if (this.plegable)
	  strResult += "<th class='"+ this.prefijoTema +"-btnNavegador esquina'><div class='"+ this.prefijoTema +"-symbolBtn' " +
	  		       "id='" + this.idObjeto + "Plegar' onclick='" + this.idObjeto + ".desplegarCalendario()' " +
	               " title='Plegar calendario'>x</div></th>";

  strResult += "</tr><tr><td class='" + this.prefijoTema + "-dias' colspan='" + columnas + "'>";

  strResult += this.generarMes();

  strResult += "</td></tr></tbody></table>";

  objDivCalendar.innerHTML = strResult;
}

/**
  * Funcion    : generarMes
  * Descripcion: Genera la tabla de dias del mes y año correspondiente
  */
shCalendario.prototype.generarMes = function() {

  var strResult        = new String();
  var diaSemanaInicial = this.objDate.diaSemanaInicial;
  var cntDias, cntOrdenDia;

  strResult += "<table width='100%' cellspacing='1' cellpadding='2'><tbody><tr>";

  // Generamos las columnas de cabecera
  for (cntDias = 0; cntDias < 7; ++cntDias) {
    strResult += "<td class='"+ this.prefijoTema +"-cabecera " + iniciales [cntDias] + 
    		     "'>" + iniciales [cntDias] + "</td>";
  }
  strResult += "</tr>";

  // Inicializacion del generador de dias del mes
  cntDias = 1;

  // Generamos las 4 semanas y los n dias
  // Rellenamos los dias de la primera semana hasta el primer dia del mes
  strResult += "<tr>";

  for (cntOrdenDia = 0; cntOrdenDia < 7; ++cntOrdenDia) {
    // Es este dia de la 1 semana del mes seleccionado
    if (cntOrdenDia < diaSemanaInicial)
      strResult += "<td class='"+ this.prefijoTema +"-celdaInvalida'>&nbsp;</td>";
    else {
      // Generamos la seccion en linea del dia con eventos asociados
      strResult += this.generarColumnaDia(cntDias);
      ++cntDias;
    }
  }
  strResult += "</tr>";

  // Generamos el resto los dias del mes seleccionado
  while (cntDias <= this.objDate.diasMes) {
    strResult += "<tr>";
    for (cntOrdenDia = 0; cntOrdenDia < 7; ++cntOrdenDia) {
      if (cntDias <= this.objDate.diasMes) {
        strResult += this.generarColumnaDia(cntDias);
      }
      else
        strResult += "<td class='"+ this.prefijoTema +"-celdaInvalida'>&nbsp;</td>";
      ++cntDias;
    }
    strResult += "</tr>";
  }
  strResult += "</tbody></table>";

  return strResult;
}

/**
  * Funcion    : generarColumnaDia
  * Descripcion: Introduce la columna de la tabla con el numero de dia, asignando un estilo predeterminado
  *             para dias especiales (hoy, dia seleccionado...)
  * Parametros : Numero de dia a generar
  */
shCalendario.prototype.generarColumnaDia = function(cntDias) {
  var strResult = new String();

  strResult += "<td class='"+ this.prefijoTema +"-celda' ";
  strResult += "onclick='"+ this.idObjeto + ".clickDia(" + cntDias +")'";
  // Hack para simular hover sobre los td de los días
  if ( Prototype.Browser.IE )
  {
    strResult += " onmouseover='this.className+=\" " + this.prefijoTema + "-celdaHover\";" +
    		         "this.firstChild.className+=\" " + this.prefijoTema + "-enlaceDiaHover\"' " +
    		         "onmouseout ='this.className=this.className.replace(/ " + 
    		         this.prefijoTema + "-celdaHover\\b\/g, \"\");" +
 		         		 "this.firstChild.className=this.firstChild.className.replace(/ " + 
 		         		 this.prefijoTema + "-enlaceDiaHover\\b\/g, \"\")'";    		         
  }
  strResult += "><a class='"+ this.prefijoTema +"-enlaceDia' >" + cntDias + "</a></td>";
  return strResult;
}

/**
  * Funcion    : clickDia
  * Descripcion: Si el calendario es desplegable, introduce la fecha seleccionada
  *             en la caja de texto asociada, e.c.c no realiza ninguna accion
  * Parametros : Numero de dia seleccionado
  * NOTA       : Manejador de evento OnClick interno, esta funcion prototipo lanza
  *             la ejecucion del manejador definido por el usuario (indicando su
  *							substitucion en los parametros de construccion)
  */
shCalendario.prototype.clickDia = function(dia) {
  var manejadorUsuario = this.getParam("manejadorOnClick");
  var fecha;

  // Actualizamos el valor almacenado de fecha seleccionada
  // Construimos la cadena de fecha y parseamos conforme al formato declarado
  this.objDate.dia = dia;
  fecha = fechaFormatoCorto(this.objDate, this.formato.replace (/\S+(\W+)\S+/, "$1"));
  this.parsearFecha(fecha);

  if (this.plegable)
	  this.desplegarCalendario();

  if (manejadorUsuario != null)
	  // Ejecutamos el manejador definido por el usuario
	  eval (manejadorUsuario + "('" +  this.seleccion + "')");
}

/**
  * Funcion    : onBlurInput
  * Descripcion: Chequea la expresión del input de entrada.
  *              Actualiza el valor almacenado de fecha seleccionada.
  *              Construye la cadena de fecha y parsea conforme al formato declarado en construcción.
  *              Se permite entrada nula conforme al parámetro declarado en construcción.
  *              En ambos casos, si existe una cadena no nula, es parseada y corregida.
  * NOTA       : Manejador de evento OnBlur interno, esta funcion prototipo lanza
  *             la ejecucion del manejador definido por el usuario (indicando su
  *             substitucion en los parametros de construccion)
  */
shCalendario.prototype.onBlurInput = function() {
  var manejadorUsuario = this.getParam("manejadorOnBlur");
  var input  = document.getElementById(this.idInput);

  this.parsearFecha(input.value);
  if ( (this.noNulo) || (input.value.length > 0) )
    input.value = fechaFormatoCorto(this.objDate, this.formato.replace (/\S+(\W+)\S+/, "$1"));
  if (manejadorUsuario != null)
    // Ejecutamos el manejador definido por el usuario
    eval (manejadorUsuario + "('" +  this.seleccion + "')");
}

/**
  * Funcion    : introducirFecha
  * Descripcion: Parsea la fecha recibida como parametro (para formatear la salida)
  *             y la introduce como valor del campo de edicion
  * Parametros : La fecha a introducir en el objeto textInput
  */
shCalendario.prototype.introducirFecha = function(fecha) {

  var input = document.getElementById(this.idInput);

  // Actualizamos el valor de la fecha seleccionada
  this.parsearFecha(fecha);

  // Mostramos ese valor en el campo de texto
  input.value = fechaFormatoCorto(this.objDate, this.formato.replace (/\S+(\W+)\S+/, "$1"));
}

/**
  * Funcion    : obtenerFecha
  * Descripcion: Retorna la fecha seleccionada.
  */
shCalendario.prototype.obtenerFecha = function() {
  return this.seleccion;
}

/**
	* Funcion    : compararFecha
	* Descripcion: Compara la cadena de fecha recibida como parametro
	* Parametros : La cadena con la fecha a comparar.
	* NOTA       : El formato asumido en la cadena de comparacion es el mismo
	* 						declarado en el objeto shCalendario que realiza la comparacion
	*             (para no añadir un parametro adicional con la expresion del
	*             formato)
  * TODO: Completar función
	*/
shCalendario.prototype.compararFecha = function () {
	this.parsearFecha
}

/**
  * Funcion    : IrAHoy
  * Descripcion: Selecciona la fecha del día en curso
  */
shCalendario.prototype.irAHoy = function() {
  this.objDate = new ShDate();

	actualizarFecha(this.objDate);
	fecha = fechaFormatoCorto(this.objDate, this.formato.replace (/\S+(\W+)\S+/, "$1"));
  this.parsearFecha(fecha);
	this.desplegarCalendario();
}

/**
  * Funcion    : desplazarDias
  * Descripcion: Recalcula el calendario incrementando/decrementando una cantidad de días
  * Parametros : Desplazamiento a operar
  */
shCalendario.prototype.desplazarDias = function(desplazamiento) {
  var input = document.getElementById(this.idInput);

  if (!desplazamiento)
    desplazamiento = 0;

  this.objDate.date.setDate( this.objDate.date.getDate() + desplazamiento );
  actualizarFecha(this.objDate);

	this.renderizarCalendario();
  input.value = fechaFormatoCorto(this.objDate, this.formato.replace (/\S+(\W+)\S+/, "$1"));
}

/**
  * Funcion    : AnyoChange
  * Descripcion: Regenera el calendario al ser editado el campo de año
  * Parametros : Identificador del objeto input
  *
  * NOTA: No se almacena el identificador para no recargar atributos.
  *       Si es molesto para el render o requiere logística más complicada
  *      generar el nombre y almacenar como atributo.
  */
shCalendario.prototype.anyoOnChange = function(idInputAnyo) {
  var inputAnyo = document.getElementById(idInputAnyo);

  var anyo = parseInt(inputAnyo.value);

  // El formato de año maximo UTC es de 4 cifras
  if ((!isNaN(anyo)) && (anyo <= anyoMaxUTC)) {
    this.objDate.date.setYear(anyo);
    actualizarFecha(this.objDate);
    this.renderizarCalendario();
  }
  else
    inputAnyo.value = this.objDate.anyo;
}

/**
  * Funcion    : mesAnterior
  * Descripcion: Retrasa un mes el calendario mostrado
  */
shCalendario.prototype.mesAnterior = function() {
  var mes  = this.objDate.date.getMonth();
  var anyo = this.objDate.date.getFullYear();

  if (mes > enero)
    mes--;
  else {
   mes = diciembre; anyo--;
  }
  this.objDate.date.setYear (anyo);
  this.objDate.date.setMonth(mes);
  this.objDate.date.setDate (1);
  actualizarFecha (this.objDate);
  this.renderizarCalendario ();
}

/**
  * Funcion    : mesSiguiente
  * Descripcion: Aumenta un mes el calendario mostrado
  */
shCalendario.prototype.mesSiguiente = function() {
  var mes  = this.objDate.date.getMonth();
  var anyo = this.objDate.date.getFullYear();

  if (mes < diciembre)
    mes++;
  else {
   mes = enero; anyo++;
  }
  this.objDate.date.setYear (anyo);
  this.objDate.date.setMonth(mes);
  this.objDate.date.setDate (1);
  actualizarFecha(this.objDate);
  this.renderizarCalendario();
}

/**
  * Funcion    : anyoAnterior
  * Descripcion: Retrasa un año el calendario mostrado
  */
shCalendario.prototype.anyoAnterior = function(objCalendar) {
  var mes  = this.objDate.date.getMonth();
  var anyo = this.objDate.date.getFullYear();

  this.objDate.date.setYear (--anyo);
  this.objDate.date.setMonth(mes);
  this.objDate.date.setDate (1);
  actualizarFecha (this.objDate);
  this.renderizarCalendario ();
}

/**
  * Funcion    : anyoSiguiente
  * Descripcion: Aumenta un año el calendario mostrado
  */
shCalendario.prototype.anyoSiguiente = function (objCalendar) {
  var mes  = this.objDate.date.getMonth ();
  var anyo = this.objDate.date.getFullYear ();

  this.objDate.date.setYear (++anyo);
  this.objDate.date.setMonth(mes);
  this.objDate.date.setDate (1);
  actualizarFecha (this.objDate);
  this.renderizarCalendario ();
}
