function UnvoidFormValidator(options) {
	
	if (!UnvoidSimpleTextManager) {
		throw "UNVOID.FORM.VALIDATOR | Constructor | UnvoidSimpleTextManager is required ";
	}
	if (!UnvoidRegExp) {
		throw "UNVOID.REGEXP | Constructor | UnvoidRegExp is required ";
	}
	
// ini: propiedades privadas
    // json con la estructura del formulario			
    var _formStructure = options.formStructure || {};
    // json conla configuración ajax
    var _ajaxConfig = options.ajaxConfig || null;
    // function para cuando el envio es correcto. Se usa tanto en envios por ajax como en los envios normales
    var _success = options.success || null;
    // variable de almacenamiento de errores
    var _error = '';
    // atributo id del formulario
    var _formID = options.formID || '';
    // prefijo para los nombres de los campos
    var _prefixName = options.prefixName || '';
    // objeto jquery del formulario
    var _form = jQuery('form#' + options.formID) || null;
    // objeto jquery del elemento donde se mostrarán los errores
    var _infoLabel = jQuery(options.infoLabel);
	var _auxInfoLabel = null;
    // a true si queremos escapar los datos en el envio (ESTA OPCIÓN NO FUNCIONA BIEN)
    var _escape = options.escape || false;
    // referencia privada a this
    var _that = this;
	// if true, nested all validation errors
	var _nestedErrors = options.nestedErrors || false;
// end: private properties
    
// ini: public properties
    // options.langManager es el json de configuracion de UnvoidSimpleTextManager
	var _LM = new UnvoidSimpleTextManager(options.langManager.textos, options.langManager.idioma, options.langManager.form);
    //_LM.setupTextManager(options.langManager.textos, options.langManager.idioma, options.langManager.form);
    // clase css general para asignar a los elementos que no son validos
    this.errorClass = options.errorClass || '';
    // mensage de 'enviando datos..'
    this.sendingMsg = options.sendingMsg || '';
    // mensage de 'envio ok'
    this.okMsg = options.okMsg || '';
    // TODO: mensage de 'error en el envio'
// end: public properties
    
// ini: private methods
    // Setea el error si y asigna clase css de error general o especifica del elemento
    function _setError(field_name, el, error, errorClass) {
		el = el[0] ? el : _form.find('[name=' + field_name + ']');
		if (!_nestedErrors) {
			if (_error === '') {
				_error = _LM.getLabel(field_name, error);
				if (_formStructure[field_name].infoLabel) {
					_auxInfoLabel = jQuery(_formStructure[field_name].infoLabel);
				}
	        }	
		}
		else {
			if ( _formStructure[field_name].infoLabel ) {
				if (jQuery(_formStructure[field_name].infoLabel).text() === "") {
					_error = _LM.getLabel(field_name, error);
					jQuery(_formStructure[field_name].infoLabel).html(_LM.getLabel(field_name, error));
				}
			}
			else {
				_error += _LM.getLabel(field_name, error) + "<br />&nbsp<br />";
			}
		}
        
        if (el && el[0] && el[0].tagName.toLowerCase() == "option") {
            el = el.parents("select:first");
        }
		if (_formStructure[field_name].errorShow) {
			jQuery(_formStructure[field_name].errorShow).addClass(errorClass || _that.errorClass);
		}
		else {
			el.addClass(errorClass || _that.errorClass);	
		}
		
        
    }
    // Resetea el formulario asignando valores por defecto de inicio.
    function _resetForm() {
		jQuery.each(_formStructure, function(i, v) {
			var e =  _form.find('[name=' + i.toString() + ']');
			if (e && e[0] && e[0].type) {
				v.type = e[0].type.toLowerCase() || "text";	
			}
			var _default = e.get(0).value == "" ?  _LM.getLabel(i.toString(),'_default') : e.get(0).value ;
			if (v.type != 'password' && v.type != 'hidden' && v.type != 'radio') {
                e
					.removeClass(v.errorClass || _that.errorClass)
					.attr({
						value: e.get(0).value != "" && e.get(0).value != _LM.getLabel(i.toString(),'_default') ? e.get(0).value : _LM.getLabel(i.toString(),'_default'),
	                    title: _LM.getLabel(i.toString(),'_default')
	                });
            }
			if (v.type != 'password' || v.type != 'text') {
                e.attr( { maxlength: v.maxlength || "" } );
            }
			
        });
        _initSubmit();
    }
    // Configuración del submit del formulario
    function _initSubmit(){
        _form.submit(function(){
            _error = "";
			_infoLabel.html('');
			_auxInfoLabel = null;
            jQuery.each(_formStructure, function(rootIndex, rootValue) {
				if (rootValue.infoLabel) {
					jQuery(rootValue.infoLabel).html('');
				}
                jQuery.each(rootValue.validators, function(validatorIndex, validatorValue) {
                    var el = _form.find('[name=' + rootIndex.toString() + ']');
					
					if (_formStructure[rootIndex.toString()].errorShow) {
						jQuery(_formStructure[rootIndex.toString()].errorShow).removeClass(rootValue.errorClass || _that.errorClass);
					}
					else {
						el.removeClass(rootValue.errorClass || _that.errorClass);	
					}
					
                    
					if (rootValue.type == 'select') {
                        el = el.find(':selected');
                    }
                    else if (rootValue.type == 'checkbox' || rootValue.type == 'radio') {
                        el = el.filter(':checked');
                    }
                    _initValidate(rootIndex, el, rootValue, validatorValue);
                });
            });
            
            return _submit(this);
        });
    }
    // Valida un campo
    function _initValidate(field_name, el, rootValue, validatorValue) {
		if (el && el[0] && el[0].value) {
			try {
				el[0].value = jQuery.trim(el[0].value);
			}catch(e){}
		}
		if (!validatorValue.regEx) {
            validatorValue.regEx = /[^\s]/;
        }
		if (el[0] && _form.find('[name=' + field_name + ']').attr('type') == "checkbox") {
            return;
        }        
		else {
			if (validatorValue.regEx.constructor == String) {
				var originalRegExp = validatorValue.regEx;
				var valid = false;
				jQuery.each(validatorValue.regEx.split(","), function(i, v){ // Si tiene mas de una opcion de validacion...
					validatorValue.regEx = _that.getFilter(v);
					if (_validate(field_name, el, rootValue, validatorValue, true)) {
						valid = true;
						return;
					}
				});
				if (!valid) {
					_validate(field_name, el, rootValue, validatorValue);
				}
				validatorValue.regEx = originalRegExp;
			}
			else
			{
				_validate(field_name, el, rootValue, validatorValue);
			}
		}
		
    }
	function _validate(field_name, el, rootValue, validatorValue, pre) {
		var valid = true;
		var value = jQuery.trim(el.val()) || "";
		if (rootValue.free) {
            if ( ( value !== "" && value != el.attr('title') ) && !validatorValue.regEx.test(value) ) {
					valid = false;
            }
        }
        else {
            if (!validatorValue.regEx.test(value) || value == el.attr('title')) {
				valid = false;
            }
            else if ( validatorValue.equal && _form.find('[name=' + validatorValue.equal + ']').val() != value ) {
					valid = false;
            }
        }
		if (!valid) {
			if (pre) {
				return false;
			}
			else {
				_setError(field_name, el, validatorValue.errorMsg, rootValue.errorClass);
			}
		}
		return true;
    }

    // Envio del formulario si no hay errores
    function _submit(){
		if (_error !== '') {
			var infoLbl = _auxInfoLabel !== null ? _auxInfoLabel : _infoLabel;
			infoLbl.html(_error);
            return false;
        }
        else {
            _infoLabel.html(_that.sendingMsg);
            
            if (_ajaxConfig) {
                var datapost = _serialize(_form);
				var ajax = _ajaxConfig;
                jQuery.ajax({
                	beforeSend: ajax.beforeSend,
                    url : ajax.url,
					timeout:30000,
                    type: ajax.type,
                    dataType: ajax.dataType,
                    data: 'nocache=' + Math.random() + '&' + datapost,
                    success: _success,
					error:function(a,b,c)
					{
						alert(a);
					}
                });
				_infoLabel.html(_that.okMsg);
                return false;
            }
            else {
                if (_success) {
                    _success();
                }
                _infoLabel.html(_that.sendingMsg);
                return true;
            }
        }
    }
    // Genera string de envio
    function _serialize(){
        var datapost = "";
        jQuery(_form).find('[name!=""]').each(function(i){
            if (this.type && (this.type.toLowerCase() == "radio" || this.type.toLowerCase() == "checkbox") && !this.checked) {
                // Los radios button y los checkbox no se envian si no estan seleccionados
                return;
            }
            datapost = _escapeData(datapost, this);
        });
        return datapost;
    }
    // Metodo que escapa el valor de un elemento
    function _escapeData(datapost, el) {
        if ( el && el.name ) {
	    	if (_escape) {
	            datapost += _prefixName + el.name + '=' + escape(unescape(el.value)) + '&';
	        }
	        else {
	            datapost += _prefixName + el.name + '=' + el.value + '&';
	        }
        }
		return datapost;
    }
    // end: private methods
    
    // ini: public methods
    this.resetForm = function(){
        _reserForm();
    };
    
    this.getError = function(){
        return _error;
    };
    
    this.getInfoLabel = function(){
        return _infoLabel;
    };
    
    this.getAjaxConfig = function(){
        return _ajaxConfig;
    };
    
    this.getSuccess = function(){
        return _success;
    };
// end: public methods
    
    _resetForm();
    
}
UnvoidFormValidator.prototype = new UnvoidRegExp();
