Index: controlies/trunk/applications/controlies/controllers/gestion.py
===================================================================
--- controlies/trunk/applications/controlies/controllers/gestion.py	(revision 180)
+++ controlies/trunk/applications/controlies/controllers/gestion.py	(revision 181)
@@ -1,5 +1,7 @@
 # coding: utf8
 from applications.controlies.modules.Hosts import Hosts
+from applications.controlies.modules.Rayuela2Ldap import Rayuela  
+import applications.controlies.modules.Utils.LdapUtils as LdapUtils
 
 @service.json   
 @auth.requires_login()
@@ -33,16 +35,112 @@
 
 
     return dict(message="hello from gestion.py")
+  
 
 
 @auth.requires_login()    
-def rayuela_alumnos():
-    return dict()
+def rayuela():
+    import StringIO
+    form=SQLFORM.factory(Field('principiocurso','boolean',default=False ),submit_button='Enviar')
+    if form.accepts(request.vars, session):
+        response.flash = 'Procesando datos, espere'
+        borrando = form.vars.principiocurso
+        if len(session.archivos)>0:
+            l=conecta()
+            usuarios=[]
 
-@auth.requires_login()    
-def rayuela_profesores():
-    return dict()
+            try:
+                os.mkdir( "/tmp/rayuela-ldap")
+            except:
+                pass #problema de permisos o directorio ya creado 
 
+            lista_completa=[]                           
+            for archivo in session.archivos:
+                rayuela=Rayuela(l,archivo,borrando)
+                todos=rayuela.gestiona_archivo()
+                lista_completa +=[(archivo,)]
+                lista_completa +=todos
+                lista_completa +=[("--",)]
+                
+            session.archivos=[]    
+            LdapUtils.sanea_grupos(l)                               
+            l.close()
+            
+            #generamos el archivo de salida 
+            s=StringIO.StringIO()
+
+            for entrada in lista_completa:
+                if len(entrada)<3:
+                    s.write(entrada[0]+ '\n' + '\n')
+                else:
+                    linea=entrada[0] + " - "
+                    if entrada[1]:
+                        linea +=entrada[2]
+                        linea +='\n'
+                    else:
+                        linea += "USUARIO ANTIGUO\n"
+                    s.write(linea)             
+                          
+            response.headers['Content-Type']='text/txt'
+            response.headers['Content-Disposition'] = 'attachment;filename=usuarios.txt'
+            return s.getvalue()
+
+
+        session.archivos=[]
+  
+    
+    return dict(form=form)
+
+
+
+@auth.requires_login()  
+def subida_rayuela():
+
+###################################################
+#
+# Used for file upload (with multiple file up) + Need rework (too much queries
+#
+###################################################
+    if "archivos" not in session.keys():archivos=[]
+    for r in request.vars:
+        if r=="qqfile":
+            try:
+                size = 0
+                #
+                # Differ between web2py server and apache
+                #
+                if request.env.has_key('http_content_length'):
+                    size = int(request.env['http_content_length'])
+                else:
+                    size = int(request.env['content_length'])
+                logger.debug(size)
+            except:
+                raise HTTP(405,'not enough space')            
+                
+           
+            parentpath="/tmp/"
+            file=request.vars.qqfile
+            logger.debug('New file uploading : ' + file)
+            #
+            # Re-arange file
+            #
+            
+            filename=file.replace(' ','_')
+            filepath=parentpath+filename
+            f = open(filepath, 'wb')
+            data=request.body.read()
+            f.write(data)
+            f.close()
+            session.archivos.append(filepath)
+            
+            print session.archivos
+            return response.json({'success':'true'})
+            # else:
+            #     return response.json({'success':'false'})
+    return response.json({'success':'false'})
+
+
+
 @service.json   
 @auth.requires_login()    
 def getLTSPServers():
Index: controlies/trunk/applications/controlies/languages/es-es.py
===================================================================
--- controlies/trunk/applications/controlies/languages/es-es.py	(revision 180)
+++ controlies/trunk/applications/controlies/languages/es-es.py	(revision 181)
@@ -1,7 +1,7 @@
 # coding: utf8
 {
 '"update" is an optional expression like "field1=\'newvalue\'". You cannot update or delete the results of a JOIN': '"actualice" es una expresión opcional como "campo1=\'nuevo_valor\'". No se puede actualizar o eliminar resultados de un JOIN',
-'%Y-%m-%d': '%Y-%m-%d',
+'%Y-%m-%d': '%d-%m-%Y',
 '%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
 '%s rows deleted': '%s filas eliminadas',
 '%s rows updated': '%s filas actualizadas',
@@ -68,9 +68,9 @@
 'Forms and Validators': 'Forms and Validators',
 'Free Applications': 'Free Applications',
 'Functions with no doctests will result in [passed] tests.': 'Funciones sin doctests equivalen a pruebas [aceptadas].',
-'Group %(group_id)s created': 'Group %(group_id)s created',
+'Group %(group_id)s created': 'Grupo %(group_id)s creado',
 'Group ID': 'ID de Grupo',
-'Group uniquely assigned to user %(id)s': 'Group uniquely assigned to user %(id)s',
+'Group uniquely assigned to user %(id)s': 'Grupo asignado únicamente al usuario %(id)s',
 'Groups': 'Grupos',
 'Hello World': 'Hola Mundo',
 'Home': 'Inicio',
@@ -127,13 +127,13 @@
 'Record ID': 'ID de Registro',
 'Record Updated': 'Registro actualizado',
 'Register': 'Registrese',
-'Registration identifier': 'Registration identifier',
+'Registration identifier': 'Identificación del registro',
 'Registration key': 'Contraseña de Registro',
-'Registration successful': 'Registration successful',
+'Registration successful': 'Registro correcto',
 'Remember me (for 30 days)': 'Recuérdame (durante 30 días)',
 'Reset Password key': 'Resetear contraseña',
 'Resolve Conflict file': 'archivo Resolución de Conflicto',
-'Resources': 'Resources',
+'Resources': 'Recursos',
 'Role': 'Rol',
 'Rows in table': 'Filas en la tabla',
 'Rows selected': 'Filas seleccionadas',
@@ -143,7 +143,7 @@
 'Static files': 'Archivos estáticos',
 'Stylesheet': 'Hoja de estilo',
 'Submit': 'Enviar',
-'Support': 'Support',
+'Support': 'Soporte',
 'Sure you want to delete this object?': '¿Está seguro que desea eliminar este objeto?',
 'Table name': 'Nombre de la tabla',
 'Testing application': 'Probando aplicación',
@@ -170,10 +170,10 @@
 'Upload existing application': 'Suba esta aplicación',
 'Use (...)&(...) for AND, (...)|(...) for OR, and ~(...)  for NOT to build more complex queries.': 'Use (...)&(...) para AND, (...)|(...) para OR, y ~(...) para NOT, para crear consultas más complejas.',
 'User %(id)s Logged-in': 'Usuario %(id)s Autenticado',
-'User %(id)s Logged-out': 'User %(id)s Logged-out',
-'User %(id)s Password changed': 'User %(id)s Password changed',
-'User %(id)s Profile updated': 'User %(id)s Profile updated',
-'User %(id)s Registered': 'User %(id)s Registered',
+'User %(id)s Logged-out': 'Usuario %(id)s cerró la sesión',
+'User %(id)s Password changed': 'Usuario %(id)s cambió su contraseña',
+'User %(id)s Profile updated': 'Usuario %(id)s actualizó su perfil',
+'User %(id)s Registered': 'Usuario %(id)s registrado',
 'User ID': 'ID de Usuario',
 'User Voice': 'User Voice',
 'Username': 'Usuario',
@@ -234,6 +234,7 @@
 'edit': 'editar',
 'edit controller': 'editar controlador',
 'edit profile': 'editar perfil',
+'enter an integer between %(min)g and %(max)g': 'Escriba un número entero entre %(min)g y %(max)g',
 'errors': 'errores',
 'export as csv file': 'exportar como archivo CSV',
 'exposes': 'expone',
Index: controlies/trunk/applications/controlies/static/wait2.gif
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: controlies/trunk/applications/controlies/static/wait2.gif
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: controlies/trunk/applications/controlies/static/wait3.gif
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: controlies/trunk/applications/controlies/static/wait3.gif
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: controlies/trunk/applications/controlies/static/js/loading.gif
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: controlies/trunk/applications/controlies/static/js/loading.gif
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: controlies/trunk/applications/controlies/static/js/fileuploader.css
===================================================================
--- controlies/trunk/applications/controlies/static/js/fileuploader.css	(nonexistent)
+++ controlies/trunk/applications/controlies/static/js/fileuploader.css	(revision 181)
@@ -0,0 +1,81 @@
+.qq-uploader {
+    min-height : 100px;
+
+    max-height : 200px;
+    overflow-y : auto;
+}
+
+
+.qq-upload-button {
+    /*    heigth: 10px;
+	  width: 80px;
+	  margin-top : 4px;
+	  text-align : center;
+	  background-color: white;
+	  */
+    border-style: none;
+}
+
+.qq-upload-button-hover {}
+.qq-upload-button-focus {}
+
+.qq-upload-drop-area {
+    position:absolute; top:0; left:0; width:100%; height:100%; min-height: 70px; z-index:2;
+    background:#FF9797; text-align:center; 
+}
+.qq-upload-drop-area span {
+    display:block; 
+    position:absolute; 
+    top: 50%; 
+    width:100%;
+    margin-top:-8px; 
+    font-size:16px;
+}
+.qq-upload-drop-area-active {
+    background:#FF7171;
+}
+
+.qq-upload-list { 
+    padding:0;
+    margin-top : 10px;
+    border-top : 1px solid #333;
+    overflow-x: hidden;
+    list-style-type:none;
+}
+
+.qq-upload-list li { 
+    overflow-x: hidden;
+    margin:0; padding:0; 
+    line-height:20px; 
+    font-size:12px;
+    display:inline;    
+}
+
+.qq-upload-file, .qq-upload-spinner, .qq-upload-size, .qq-upload-cancel, .qq-upload-failed-text {
+    margin-right: 10px;
+}
+
+.qq-upload-cancel {
+    color : red;
+    text-decoration:none;
+}
+
+.qq-upload-file {}
+.qq-upload-spinner {display:inline-block; background: url("../wait2.gif"); width:15px; height:15px; vertical-align:text-bottom;}
+
+.qq-upload-size{
+
+}
+
+.qq-upload-size,.qq-upload-cancel {font-size:11px;}
+
+.qq-upload-failed-text {display:none;}
+.qq-upload-fail .qq-upload-failed-text {display:inline;}
+
+
+.list-down{
+    width : 200px;
+    height : 200px;
+    background-color : black;
+    z-index : 9999;
+}
\ No newline at end of file
Index: controlies/trunk/applications/controlies/static/js/fileuploader.js
===================================================================
--- controlies/trunk/applications/controlies/static/js/fileuploader.js	(nonexistent)
+++ controlies/trunk/applications/controlies/static/js/fileuploader.js	(revision 181)
@@ -0,0 +1,1247 @@
+/**
+ * http://github.com/valums/file-uploader
+ * 
+ * Multiple file upload component with progress-bar, drag-and-drop. 
+ * © 2010 Andrew Valums ( andrew(at)valums.com ) 
+ * 
+ * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
+ */    
+
+//
+// Helper functions
+//
+
+var qq = qq || {};
+
+/**
+ * Adds all missing properties from second obj to first obj
+ */ 
+qq.extend = function(first, second){
+    for (var prop in second){
+        first[prop] = second[prop];
+    }
+};  
+
+/**
+ * Searches for a given element in the array, returns -1 if it is not present.
+ * @param {Number} [from] The index at which to begin the search
+ */
+qq.indexOf = function(arr, elt, from){
+    if (arr.indexOf) return arr.indexOf(elt, from);
+    
+    from = from || 0;
+    var len = arr.length;    
+    
+    if (from < 0) from += len;  
+
+    for (; from < len; from++){  
+        if (from in arr && arr[from] === elt){  
+            return from;
+        }
+    }  
+    return -1;  
+}; 
+    
+qq.getUniqueId = (function(){
+    var id = 0;
+    return function(){ return id++; };
+})();
+
+//
+// Events
+
+qq.attach = function(element, type, fn){
+    if (element.addEventListener){
+        element.addEventListener(type, fn, false);
+    } else if (element.attachEvent){
+        element.attachEvent('on' + type, fn);
+    }
+};
+qq.detach = function(element, type, fn){
+    if (element.removeEventListener){
+        element.removeEventListener(type, fn, false);
+    } else if (element.attachEvent){
+        element.detachEvent('on' + type, fn);
+    }
+};
+
+qq.preventDefault = function(e){
+    if (e.preventDefault){
+        e.preventDefault();
+    } else{
+        e.returnValue = false;
+    }
+};
+
+//
+// Node manipulations
+
+/**
+ * Insert node a before node b.
+ */
+qq.insertBefore = function(a, b){
+    b.parentNode.insertBefore(a, b);
+};
+qq.remove = function(element){
+    element.parentNode.removeChild(element);
+};
+
+qq.contains = function(parent, descendant){       
+    // compareposition returns false in this case
+    if (parent == descendant) return true;
+    
+    if (parent.contains){
+        return parent.contains(descendant);
+    } else {
+        return !!(descendant.compareDocumentPosition(parent) & 8);
+    }
+};
+
+/**
+ * Creates and returns element from html string
+ * Uses innerHTML to create an element
+ */
+qq.toElement = (function(){
+    var div = document.createElement('div');
+    return function(html){
+        div.innerHTML = html;
+        var element = div.firstChild;
+        div.removeChild(element);
+        return element;
+    };
+})();
+
+//
+// Node properties and attributes
+
+/**
+ * Sets styles for an element.
+ * Fixes opacity in IE6-8.
+ */
+qq.css = function(element, styles){
+    if (styles.opacity != null){
+        if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
+            styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
+        }
+    }
+    qq.extend(element.style, styles);
+};
+qq.hasClass = function(element, name){
+    var re = new RegExp('(^| )' + name + '( |$)');
+    return re.test(element.className);
+};
+qq.addClass = function(element, name){
+    if (!qq.hasClass(element, name)){
+        element.className += ' ' + name;
+    }
+};
+qq.removeClass = function(element, name){
+    var re = new RegExp('(^| )' + name + '( |$)');
+    element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
+};
+qq.setText = function(element, text){
+    element.innerText = text;
+    element.textContent = text;
+};
+
+//
+// Selecting elements
+
+qq.children = function(element){
+    var children = [],
+    child = element.firstChild;
+
+    while (child){
+        if (child.nodeType == 1){
+            children.push(child);
+        }
+        child = child.nextSibling;
+    }
+
+    return children;
+};
+
+qq.getByClass = function(element, className){
+    if (element.querySelectorAll){
+        return element.querySelectorAll('.' + className);
+    }
+
+    var result = [];
+    var candidates = element.getElementsByTagName("*");
+    var len = candidates.length;
+
+    for (var i = 0; i < len; i++){
+        if (qq.hasClass(candidates[i], className)){
+            result.push(candidates[i]);
+        }
+    }
+    return result;
+};
+
+/**
+ * obj2url() takes a json-object as argument and generates
+ * a querystring. pretty much like jQuery.param()
+ * 
+ * how to use:
+ *
+ *    `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
+ *
+ * will result in:
+ *
+ *    `http://any.url/upload?otherParam=value&a=b&c=d`
+ *
+ * @param  Object JSON-Object
+ * @param  String current querystring-part
+ * @return String encoded querystring
+ */
+qq.obj2url = function(obj, temp, prefixDone){
+    var uristrings = [],
+        prefix = '&',
+        add = function(nextObj, i){
+            var nextTemp = temp 
+                ? (/\[\]$/.test(temp)) // prevent double-encoding
+                   ? temp
+                   : temp+'['+i+']'
+                : i;
+            if ((nextTemp != 'undefined') && (i != 'undefined')) {  
+                uristrings.push(
+                    (typeof nextObj === 'object') 
+                        ? qq.obj2url(nextObj, nextTemp, true)
+                        : (Object.prototype.toString.call(nextObj) === '[object Function]')
+                            ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
+                            : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)                                                          
+                );
+            }
+        }; 
+
+    if (!prefixDone && temp) {
+      prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
+      uristrings.push(temp);
+      uristrings.push(qq.obj2url(obj));
+    } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
+        // we wont use a for-in-loop on an array (performance)
+        for (var i = 0, len = obj.length; i < len; ++i){
+            add(obj[i], i);
+        }
+    } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
+        // for anything else but a scalar, we will use for-in-loop
+        for (var i in obj){
+            add(obj[i], i);
+        }
+    } else {
+        uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
+    }
+
+    return uristrings.join(prefix)
+                     .replace(/^&/, '')
+                     .replace(/%20/g, '+'); 
+};
+
+//
+//
+// Uploader Classes
+//
+//
+
+var qq = qq || {};
+    
+/**
+ * Creates upload button, validates upload, but doesn't create file list or dd. 
+ */
+qq.FileUploaderBasic = function(o){
+    this._options = {
+        // set to true to see the server response
+        debug: false,
+        action: '/server/upload',
+        params: {},
+        button: null,
+        multiple: true,
+        maxConnections: 3,
+        // validation        
+        allowedExtensions: [],               
+        sizeLimit: 0,   
+        minSizeLimit: 0,                             
+        // events
+        // return false to cancel submit
+        onSubmit: function(id, fileName){},
+        onProgress: function(id, fileName, loaded, total){},
+        onComplete: function(id, fileName, responseJSON){},
+        onCancel: function(id, fileName){},
+        // messages                
+        messages: {
+            typeError: "{file} has invalid extension. Only {extensions} are allowed.",
+            sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
+            minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
+            emptyError: "{file} is empty, please select files again without it.",
+            onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."            
+        },
+        showMessage: function(message){
+            alert(message);
+        }               
+    };
+    qq.extend(this._options, o);
+        
+    // number of files being uploaded
+    this._filesInProgress = 0;
+    this._handler = this._createUploadHandler(); 
+    
+    if (this._options.button){ 
+        this._button = this._createUploadButton(this._options.button);
+    }
+                        
+    this._preventLeaveInProgress();         
+};
+   
+qq.FileUploaderBasic.prototype = {
+    setParams: function(params){
+        this._options.params = params;
+    },
+    getInProgress: function(){
+        return this._filesInProgress;         
+    },
+    _createUploadButton: function(element){
+        var self = this;
+        
+        return new qq.UploadButton({
+            element: element,
+            multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
+            onChange: function(input){
+                self._onInputChange(input);
+            }        
+        });           
+    },    
+    _createUploadHandler: function(){
+        var self = this,
+            handlerClass;        
+        
+        if(qq.UploadHandlerXhr.isSupported()){           
+            handlerClass = 'UploadHandlerXhr';                        
+        } else {
+            handlerClass = 'UploadHandlerForm';
+        }
+
+        var handler = new qq[handlerClass]({
+            debug: this._options.debug,
+            action: this._options.action,         
+            maxConnections: this._options.maxConnections,   
+            onProgress: function(id, fileName, loaded, total){                
+                self._onProgress(id, fileName, loaded, total);
+                self._options.onProgress(id, fileName, loaded, total);                    
+            },            
+            onComplete: function(id, fileName, result){
+                self._onComplete(id, fileName, result);
+                self._options.onComplete(id, fileName, result);
+            },
+            onCancel: function(id, fileName){
+                self._onCancel(id, fileName);
+                self._options.onCancel(id, fileName);
+            }
+        });
+
+        return handler;
+    },    
+    _preventLeaveInProgress: function(){
+        var self = this;
+        
+        qq.attach(window, 'beforeunload', function(e){
+            if (!self._filesInProgress){return;}
+            
+            var e = e || window.event;
+            // for ie, ff
+            e.returnValue = self._options.messages.onLeave;
+            // for webkit
+            return self._options.messages.onLeave;             
+        });        
+    },    
+    _onSubmit: function(id, fileName){
+        this._filesInProgress++;  
+    },
+    _onProgress: function(id, fileName, loaded, total){        
+    },
+    _onComplete: function(id, fileName, result){
+        this._filesInProgress--;                 
+        if (result.error){
+            this._options.showMessage(result.error);
+        }             
+    },
+    _onCancel: function(id, fileName){
+        this._filesInProgress--;        
+    },
+    _onInputChange: function(input){
+        if (this._handler instanceof qq.UploadHandlerXhr){                
+            this._uploadFileList(input.files);                   
+        } else {             
+            if (this._validateFile(input)){                
+                this._uploadFile(input);                                    
+            }                      
+        }               
+        this._button.reset();   
+    },  
+    _uploadFileList: function(files){
+        for (var i=0; i<files.length; i++){
+            if ( !this._validateFile(files[i])){
+                return;
+            }            
+        }
+        
+        for (var i=0; i<files.length; i++){
+            this._uploadFile(files[i]);        
+        }        
+    },       
+    _uploadFile: function(fileContainer){      
+        var id = this._handler.add(fileContainer);
+        var fileName = this._handler.getName(id);
+        
+        if (this._options.onSubmit(id, fileName) !== false){
+            this._onSubmit(id, fileName);
+            this._handler.upload(id, this._options.params);
+        }
+    },      
+    _validateFile: function(file){
+        var name, size;
+        
+        if (file.value){
+            // it is a file input            
+            // get input value and remove path to normalize
+            name = file.value.replace(/.*(\/|\\)/, "");
+        } else {
+            // fix missing properties in Safari
+            name = file.fileName != null ? file.fileName : file.name;
+            size = file.fileSize != null ? file.fileSize : file.size;
+        }
+                    
+        if (! this._isAllowedExtension(name)){            
+            this._error('typeError', name);
+            return false;
+            
+        } else if (size === 0){            
+            this._error('emptyError', name);
+            return false;
+                                                     
+        } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){            
+            this._error('sizeError', name);
+            return false;
+                        
+        } else if (size && size < this._options.minSizeLimit){
+            this._error('minSizeError', name);
+            return false;            
+        }
+        
+        return true;                
+    },
+    _error: function(code, fileName){
+        var message = this._options.messages[code];        
+        function r(name, replacement){ message = message.replace(name, replacement); }
+        
+        r('{file}', this._formatFileName(fileName));        
+        r('{extensions}', this._options.allowedExtensions.join(', '));
+        r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
+        r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
+        
+        this._options.showMessage(message);                
+    },
+    _formatFileName: function(name){
+        if (name.length > 33){
+            name = name.slice(0, 19) + '...' + name.slice(-13);    
+        }
+        return name;
+    },
+    _isAllowedExtension: function(fileName){
+        var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
+        var allowed = this._options.allowedExtensions;
+        
+        if (!allowed.length){return true;}        
+        
+        for (var i=0; i<allowed.length; i++){
+            if (allowed[i].toLowerCase() == ext){ return true;}    
+        }
+        
+        return false;
+    },    
+    _formatSize: function(bytes){
+        var i = -1;                                    
+        do {
+            bytes = bytes / 1024;
+            i++;  
+        } while (bytes > 99);
+        
+        return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];          
+    }
+};
+    
+       
+/**
+ * Class that creates upload widget with drag-and-drop and file list
+ * @inherits qq.FileUploaderBasic
+ */
+qq.FileUploader = function(o){
+    // call parent constructor
+    qq.FileUploaderBasic.apply(this, arguments);
+    
+    // additional options    
+    qq.extend(this._options, {
+        element: null,
+        // if set, will be used instead of qq-upload-list in template
+        listElement: null,
+                
+        template: '<div class="qq-uploader">' + 
+                '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
+                '<div class="qq-upload-button">Subir uno a más ficheros</div>' +
+                '<ul class="qq-upload-list"></ul>' + 
+             '</div>',
+
+        // template for one item in file list
+        fileTemplate: '<li>' +
+                '<span class="qq-upload-file"></span>' +
+                '<span class="qq-upload-spinner"></span>' +
+                '<span class="qq-upload-size"></span>' +
+                '<a class="qq-upload-cancel" href="#">Cancelar</a>' +
+                '<span class="qq-upload-failed-text">Falló</span>' +
+            '</li>',        
+        
+        classes: {
+            // used to get elements from templates
+            button: 'qq-upload-button',
+            drop: 'qq-upload-drop-area',
+            dropActive: 'qq-upload-drop-area-active',
+            list: 'qq-upload-list',
+                        
+            file: 'qq-upload-file',
+            spinner: 'qq-upload-spinner',
+            size: 'qq-upload-size',
+            cancel: 'qq-upload-cancel',
+
+            // added to list item when upload completes
+            // used in css to hide progress spinner
+            success: 'qq-upload-success',
+            fail: 'qq-upload-fail'
+        }
+    });
+    // overwrite options with user supplied    
+    qq.extend(this._options, o);       
+
+    this._element = this._options.element;
+    this._element.innerHTML = this._options.template;        
+    this._listElement = this._options.listElement || this._find(this._element, 'list');
+    
+    this._classes = this._options.classes;
+        
+    this._button = this._createUploadButton(this._find(this._element, 'button'));        
+    
+    this._bindCancelEvent();
+    this._setupDragDrop();
+};
+
+// inherit from Basic Uploader
+qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
+
+qq.extend(qq.FileUploader.prototype, {
+    /**
+     * Gets one of the elements listed in this._options.classes
+     **/
+    _find: function(parent, type){                                
+        var element = qq.getByClass(parent, this._options.classes[type])[0];        
+        if (!element){
+            throw new Error('element not found ' + type);
+        }
+        
+        return element;
+    },
+    _setupDragDrop: function(){
+        var self = this,
+            dropArea = this._find(this._element, 'drop');                        
+
+        var dz = new qq.UploadDropZone({
+            element: dropArea,
+            onEnter: function(e){
+                qq.addClass(dropArea, self._classes.dropActive);
+                e.stopPropagation();
+            },
+            onLeave: function(e){
+                e.stopPropagation();
+            },
+            onLeaveNotDescendants: function(e){
+                qq.removeClass(dropArea, self._classes.dropActive);  
+            },
+            onDrop: function(e){
+                dropArea.style.display = 'none';
+                qq.removeClass(dropArea, self._classes.dropActive);
+                self._uploadFileList(e.dataTransfer.files);    
+            }
+        });
+                
+        dropArea.style.display = 'none';
+
+        qq.attach(document, 'dragenter', function(e){     
+            if (!dz._isValidFileDrag(e)) return; 
+            
+            dropArea.style.display = 'block';            
+        });                 
+        qq.attach(document, 'dragleave', function(e){
+            if (!dz._isValidFileDrag(e)) return;            
+            
+            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
+            // only fire when leaving document out
+            if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){               
+                dropArea.style.display = 'none';                                            
+            }
+        });                
+    },
+    _onSubmit: function(id, fileName){
+        qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
+        this._addToList(id, fileName);  
+    },
+    _onProgress: function(id, fileName, loaded, total){
+        qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
+
+        var item = this._getItemByFileId(id);
+        var size = this._find(item, 'size');
+        size.style.display = 'inline';
+        
+        var text; 
+        if (loaded != total){
+            text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
+        } else {                                   
+            text = this._formatSize(total);
+        }          
+        
+        qq.setText(size, text);         
+    },
+    _onComplete: function(id, fileName, result){
+        qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
+
+        // mark completed
+        var item = this._getItemByFileId(id);                
+        qq.remove(this._find(item, 'cancel'));
+        qq.remove(this._find(item, 'spinner'));
+        
+        if (result.success){
+            qq.addClass(item, this._classes.success);    
+        } else {
+            qq.addClass(item, this._classes.fail);
+        }         
+    },
+    _addToList: function(id, fileName){
+        var item = qq.toElement(this._options.fileTemplate);                
+        item.qqFileId = id;
+
+        var fileElement = this._find(item, 'file');        
+        qq.setText(fileElement, this._formatFileName(fileName));
+        this._find(item, 'size').style.display = 'none';        
+
+        this._listElement.appendChild(item);
+    },
+    _getItemByFileId: function(id){
+        var item = this._listElement.firstChild;        
+        
+        // there can't be txt nodes in dynamically created list
+        // and we can  use nextSibling
+        while (item){            
+            if (item.qqFileId == id) return item;            
+            item = item.nextSibling;
+        }          
+    },
+    /**
+     * delegate click event for cancel link 
+     **/
+    _bindCancelEvent: function(){
+        var self = this,
+            list = this._listElement;            
+        
+        qq.attach(list, 'click', function(e){            
+            e = e || window.event;
+            var target = e.target || e.srcElement;
+            
+            if (qq.hasClass(target, self._classes.cancel)){                
+                qq.preventDefault(e);
+               
+                var item = target.parentNode;
+                self._handler.cancel(item.qqFileId);
+                qq.remove(item);
+            }
+        });
+    }    
+});
+    
+qq.UploadDropZone = function(o){
+    this._options = {
+        element: null,  
+        onEnter: function(e){},
+        onLeave: function(e){},  
+        // is not fired when leaving element by hovering descendants   
+        onLeaveNotDescendants: function(e){},   
+        onDrop: function(e){}                       
+    };
+    qq.extend(this._options, o); 
+    
+    this._element = this._options.element;
+    
+    this._disableDropOutside();
+    this._attachEvents();   
+};
+
+qq.UploadDropZone.prototype = {
+    _disableDropOutside: function(e){
+        // run only once for all instances
+        if (!qq.UploadDropZone.dropOutsideDisabled ){
+
+            qq.attach(document, 'dragover', function(e){
+                if (e.dataTransfer){
+                    e.dataTransfer.dropEffect = 'none';
+                    e.preventDefault(); 
+                }           
+            });
+            
+            qq.UploadDropZone.dropOutsideDisabled = true; 
+        }        
+    },
+    _attachEvents: function(){
+        var self = this;              
+                  
+        qq.attach(self._element, 'dragover', function(e){
+            if (!self._isValidFileDrag(e)) return;
+            
+            var effect = e.dataTransfer.effectAllowed;
+            if (effect == 'move' || effect == 'linkMove'){
+                e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)    
+            } else {                    
+                e.dataTransfer.dropEffect = 'copy'; // for Chrome
+            }
+                                                     
+            e.stopPropagation();
+            e.preventDefault();                                                                    
+        });
+        
+        qq.attach(self._element, 'dragenter', function(e){
+            if (!self._isValidFileDrag(e)) return;
+                        
+            self._options.onEnter(e);
+        });
+        
+        qq.attach(self._element, 'dragleave', function(e){
+            if (!self._isValidFileDrag(e)) return;
+            
+            self._options.onLeave(e);
+            
+            var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);                      
+            // do not fire when moving a mouse over a descendant
+            if (qq.contains(this, relatedTarget)) return;
+                        
+            self._options.onLeaveNotDescendants(e); 
+        });
+                
+        qq.attach(self._element, 'drop', function(e){
+            if (!self._isValidFileDrag(e)) return;
+            
+            e.preventDefault();
+            self._options.onDrop(e);
+        });          
+    },
+    _isValidFileDrag: function(e){
+        var dt = e.dataTransfer,
+            // do not check dt.types.contains in webkit, because it crashes safari 4            
+            isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;                        
+
+        // dt.effectAllowed is none in Safari 5
+        // dt.types.contains check is for firefox            
+        return dt && dt.effectAllowed != 'none' && 
+            (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
+        
+    }        
+}; 
+
+qq.UploadButton = function(o){
+    this._options = {
+        element: null,  
+        // if set to true adds multiple attribute to file input      
+        multiple: false,
+        // name attribute of file input
+        name: 'file',
+        onChange: function(input){},
+        hoverClass: 'qq-upload-button-hover',
+        focusClass: 'qq-upload-button-focus'                       
+    };
+    
+    qq.extend(this._options, o);
+        
+    this._element = this._options.element;
+    
+    // make button suitable container for input
+    qq.css(this._element, {
+        position: 'relative',
+        overflow: 'hidden',
+        // Make sure browse button is in the right side
+        // in Internet Explorer
+        direction: 'ltr'
+    });   
+    
+    this._input = this._createInput();
+};
+
+qq.UploadButton.prototype = {
+    /* returns file input element */    
+    getInput: function(){
+        return this._input;
+    },
+    /* cleans/recreates the file input */
+    reset: function(){
+        if (this._input.parentNode){
+            qq.remove(this._input);    
+        }                
+        
+        qq.removeClass(this._element, this._options.focusClass);
+        this._input = this._createInput();
+    },    
+    _createInput: function(){                
+        var input = document.createElement("input");
+        
+        if (this._options.multiple){
+            input.setAttribute("multiple", "multiple");
+        }
+                
+        input.setAttribute("type", "file");
+        input.setAttribute("name", this._options.name);
+        
+        qq.css(input, {
+            position: 'absolute',
+            // in Opera only 'browse' button
+            // is clickable and it is located at
+            // the right side of the input
+            right: 0,
+            top: 0,
+            fontFamily: 'Arial',
+            // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
+            fontSize: '118px',
+            margin: 0,
+            padding: 0,
+            cursor: 'pointer',
+            opacity: 0
+        });
+        
+        this._element.appendChild(input);
+
+        var self = this;
+        qq.attach(input, 'change', function(){
+            self._options.onChange(input);
+        });
+                
+        qq.attach(input, 'mouseover', function(){
+            qq.addClass(self._element, self._options.hoverClass);
+        });
+        qq.attach(input, 'mouseout', function(){
+            qq.removeClass(self._element, self._options.hoverClass);
+        });
+        qq.attach(input, 'focus', function(){
+            qq.addClass(self._element, self._options.focusClass);
+        });
+        qq.attach(input, 'blur', function(){
+            qq.removeClass(self._element, self._options.focusClass);
+        });
+
+        // IE and Opera, unfortunately have 2 tab stops on file input
+        // which is unacceptable in our case, disable keyboard access
+        if (window.attachEvent){
+            // it is IE or Opera
+            input.setAttribute('tabIndex', "-1");
+        }
+
+        return input;            
+    }        
+};
+
+/**
+ * Class for uploading files, uploading itself is handled by child classes
+ */
+qq.UploadHandlerAbstract = function(o){
+    this._options = {
+        debug: false,
+        action: '/upload.php',
+        // maximum number of concurrent uploads        
+        maxConnections: 999,
+        onProgress: function(id, fileName, loaded, total){},
+        onComplete: function(id, fileName, response){},
+        onCancel: function(id, fileName){}
+    };
+    qq.extend(this._options, o);    
+    
+    this._queue = [];
+    // params for files in queue
+    this._params = [];
+};
+qq.UploadHandlerAbstract.prototype = {
+    log: function(str){
+        if (this._options.debug && window.console) console.log('[uploader] ' + str);        
+    },
+    /**
+     * Adds file or file input to the queue
+     * @returns id
+     **/    
+    add: function(file){},
+    /**
+     * Sends the file identified by id and additional query params to the server
+     */
+    upload: function(id, params){
+        var len = this._queue.push(id);
+
+        var copy = {};        
+        qq.extend(copy, params);
+        this._params[id] = copy;        
+                
+        // if too many active uploads, wait...
+        if (len <= this._options.maxConnections){               
+            this._upload(id, this._params[id]);
+        }
+    },
+    /**
+     * Cancels file upload by id
+     */
+    cancel: function(id){
+        this._cancel(id);
+        this._dequeue(id);
+    },
+    /**
+     * Cancells all uploads
+     */
+    cancelAll: function(){
+        for (var i=0; i<this._queue.length; i++){
+            this._cancel(this._queue[i]);
+        }
+        this._queue = [];
+    },
+    /**
+     * Returns name of the file identified by id
+     */
+    getName: function(id){},
+    /**
+     * Returns size of the file identified by id
+     */          
+    getSize: function(id){},
+    /**
+     * Returns id of files being uploaded or
+     * waiting for their turn
+     */
+    getQueue: function(){
+        return this._queue;
+    },
+    /**
+     * Actual upload method
+     */
+    _upload: function(id){},
+    /**
+     * Actual cancel method
+     */
+    _cancel: function(id){},     
+    /**
+     * Removes element from queue, starts upload of next
+     */
+    _dequeue: function(id){
+        var i = qq.indexOf(this._queue, id);
+        this._queue.splice(i, 1);
+                
+        var max = this._options.maxConnections;
+        
+        if (this._queue.length >= max && i < max){
+            var nextId = this._queue[max-1];
+            this._upload(nextId, this._params[nextId]);
+        }
+    }        
+};
+
+/**
+ * Class for uploading files using form and iframe
+ * @inherits qq.UploadHandlerAbstract
+ */
+qq.UploadHandlerForm = function(o){
+    qq.UploadHandlerAbstract.apply(this, arguments);
+       
+    this._inputs = {};
+};
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
+
+qq.extend(qq.UploadHandlerForm.prototype, {
+    add: function(fileInput){
+        fileInput.setAttribute('name', 'qqfile');
+        var id = 'qq-upload-handler-iframe' + qq.getUniqueId();       
+        
+        this._inputs[id] = fileInput;
+        
+        // remove file input from DOM
+        if (fileInput.parentNode){
+            qq.remove(fileInput);
+        }
+                
+        return id;
+    },
+    getName: function(id){
+        // get input value and remove path to normalize
+        return this._inputs[id].value.replace(/.*(\/|\\)/, "");
+    },    
+    _cancel: function(id){
+        this._options.onCancel(id, this.getName(id));
+        
+        delete this._inputs[id];        
+
+        var iframe = document.getElementById(id);
+        if (iframe){
+            // to cancel request set src to something else
+            // we use src="javascript:false;" because it doesn't
+            // trigger ie6 prompt on https
+            iframe.setAttribute('src', 'javascript:false;');
+
+            qq.remove(iframe);
+        }
+    },     
+    _upload: function(id, params){                        
+        var input = this._inputs[id];
+        
+        if (!input){
+            throw new Error('file with passed id was not added, or already uploaded or cancelled');
+        }                
+
+        var fileName = this.getName(id);
+                
+        var iframe = this._createIframe(id);
+        var form = this._createForm(iframe, params);
+        form.appendChild(input);
+
+        var self = this;
+        this._attachLoadEvent(iframe, function(){                                 
+            self.log('iframe loaded');
+            
+            var response = self._getIframeContentJSON(iframe);
+
+            self._options.onComplete(id, fileName, response);
+            self._dequeue(id);
+            
+            delete self._inputs[id];
+            // timeout added to fix busy state in FF3.6
+            setTimeout(function(){
+                qq.remove(iframe);
+            }, 1);
+        });
+
+        form.submit();        
+        qq.remove(form);        
+        
+        return id;
+    }, 
+    _attachLoadEvent: function(iframe, callback){
+        qq.attach(iframe, 'load', function(){
+            // when we remove iframe from dom
+            // the request stops, but in IE load
+            // event fires
+            if (!iframe.parentNode){
+                return;
+            }
+
+            // fixing Opera 10.53
+            if (iframe.contentDocument &&
+                iframe.contentDocument.body &&
+                iframe.contentDocument.body.innerHTML == "false"){
+                // In Opera event is fired second time
+                // when body.innerHTML changed from false
+                // to server response approx. after 1 sec
+                // when we upload file with iframe
+                return;
+            }
+
+            callback();
+        });
+    },
+    /**
+     * Returns json object received by iframe from server.
+     */
+    _getIframeContentJSON: function(iframe){
+        // iframe.contentWindow.document - for IE<7
+        var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
+            response;
+        
+        this.log("converting iframe's innerHTML to JSON");
+        this.log("innerHTML = " + doc.body.innerHTML);
+                        
+        try {
+            response = eval("(" + doc.body.innerHTML + ")");
+        } catch(err){
+            response = {};
+        }        
+
+        return response;
+    },
+    /**
+     * Creates iframe with unique name
+     */
+    _createIframe: function(id){
+        // We can't use following code as the name attribute
+        // won't be properly registered in IE6, and new window
+        // on form submit will open
+        // var iframe = document.createElement('iframe');
+        // iframe.setAttribute('name', id);
+
+        var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
+        // src="javascript:false;" removes ie6 prompt on https
+
+        iframe.setAttribute('id', id);
+
+        iframe.style.display = 'none';
+        document.body.appendChild(iframe);
+
+        return iframe;
+    },
+    /**
+     * Creates form, that will be submitted to iframe
+     */
+    _createForm: function(iframe, params){
+        // We can't use the following code in IE6
+        // var form = document.createElement('form');
+        // form.setAttribute('method', 'post');
+        // form.setAttribute('enctype', 'multipart/form-data');
+        // Because in this case file won't be attached to request
+        var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
+
+        var queryString = qq.obj2url(params, this._options.action);
+
+        form.setAttribute('action', queryString);
+        form.setAttribute('target', iframe.name);
+        form.style.display = 'none';
+        document.body.appendChild(form);
+
+        return form;
+    }
+});
+
+/**
+ * Class for uploading files using xhr
+ * @inherits qq.UploadHandlerAbstract
+ */
+qq.UploadHandlerXhr = function(o){
+    qq.UploadHandlerAbstract.apply(this, arguments);
+
+    this._files = [];
+    this._xhrs = [];
+    
+    // current loaded size in bytes for each file 
+    this._loaded = [];
+};
+
+// static method
+qq.UploadHandlerXhr.isSupported = function(){
+    var input = document.createElement('input');
+    input.type = 'file';        
+    
+    return (
+        'multiple' in input &&
+        typeof File != "undefined" &&
+        typeof (new XMLHttpRequest()).upload != "undefined" );       
+};
+
+// @inherits qq.UploadHandlerAbstract
+qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
+
+qq.extend(qq.UploadHandlerXhr.prototype, {
+    /**
+     * Adds file to the queue
+     * Returns id to use with upload, cancel
+     **/    
+    add: function(file){
+        if (!(file instanceof File)){
+            throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
+        }
+                
+        return this._files.push(file) - 1;        
+    },
+    getName: function(id){        
+        var file = this._files[id];
+        // fix missing name in Safari 4
+        return file.fileName != null ? file.fileName : file.name;       
+    },
+    getSize: function(id){
+        var file = this._files[id];
+        return file.fileSize != null ? file.fileSize : file.size;
+    },    
+    /**
+     * Returns uploaded bytes for file identified by id 
+     */    
+    getLoaded: function(id){
+        return this._loaded[id] || 0; 
+    },
+    /**
+     * Sends the file identified by id and additional query params to the server
+     * @param {Object} params name-value string pairs
+     */    
+    _upload: function(id, params){
+        var file = this._files[id],
+            name = this.getName(id),
+            size = this.getSize(id);
+                
+        this._loaded[id] = 0;
+                                
+        var xhr = this._xhrs[id] = new XMLHttpRequest();
+        var self = this;
+                                        
+        xhr.upload.onprogress = function(e){
+            if (e.lengthComputable){
+                self._loaded[id] = e.loaded;
+                self._options.onProgress(id, name, e.loaded, e.total);
+            }
+        };
+
+        xhr.onreadystatechange = function(){            
+            if (xhr.readyState == 4){
+                self._onComplete(id, xhr);                    
+            }
+        };
+
+        // build query string
+        params = params || {};
+        params['qqfile'] = name;
+        var queryString = qq.obj2url(params, this._options.action);
+
+        xhr.open("POST", queryString, true);
+        xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+        xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
+        xhr.setRequestHeader("Content-Type", "application/octet-stream");
+        xhr.send(file);
+    },
+    _onComplete: function(id, xhr){
+        // the request was aborted/cancelled
+        if (!this._files[id]) return;
+        
+        var name = this.getName(id);
+        var size = this.getSize(id);
+        
+        this._options.onProgress(id, name, size, size);
+                
+        if (xhr.status == 200){
+            this.log("xhr - server response received");
+            this.log("responseText = " + xhr.responseText);
+                        
+            var response;
+                    
+            try {
+                response = eval("(" + xhr.responseText + ")");
+            } catch(err){
+                response = {};
+            }
+            
+            this._options.onComplete(id, name, response);
+                        
+        } else {                   
+            this._options.onComplete(id, name, {});
+        }
+                
+        this._files[id] = null;
+        this._xhrs[id] = null;    
+        this._dequeue(id);                    
+    },
+    _cancel: function(id){
+        this._options.onCancel(id, this.getName(id));
+        
+        this._files[id] = null;
+        
+        if (this._xhrs[id]){
+            this._xhrs[id].abort();
+            this._xhrs[id] = null;                                   
+        }
+    }
+});
Index: controlies/trunk/applications/controlies/static/wait.gif
===================================================================
Cannot display: file marked as a binary type.
svn:mime-type = application/octet-stream

Property changes on: controlies/trunk/applications/controlies/static/wait.gif
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Index: controlies/trunk/applications/controlies/modules/Rayuela2Ldap.py
===================================================================
--- controlies/trunk/applications/controlies/modules/Rayuela2Ldap.py	(revision 180)
+++ controlies/trunk/applications/controlies/modules/Rayuela2Ldap.py	(revision 181)
@@ -32,389 +32,411 @@
 import ldap
 from Users import Users
 from Groups import Groups
-    
-borrando=True
-esAlumnos=False
-ldap_con=None
+import Utils.LdapUtils as LdapUtils
 
-usuarios=[]
 
+class Rayuela(object):
 
-def unzip_alumnos(archivo_zip):
-    from zipfile import ZipFile
-    try:
-        myzip= ZipFile(archivo_zip, 'r')    
-        myzip.extractall(path="/tmp/rayuela-ldap")
-    except Exception,e:
-        return e
-        
-    if not os.path.exists("/tmp/rayuela-ldap/Alumnos.xml"):
-        return "No es un archivo de importación de alumnos"
+    def __init__(self,conexion,archivo,borrar):
+        self.borrando=borrar
+        self.esAlumnos=False
+        self.ldap_con =conexion
+        self.usuarios=[]
+        self.archivo=archivo
+
+
+    def unzip_alumnos(self,archivo_zip):
+        """ Descomprime el archivo de alumnos en el directorio
+        /tmp/rayuela-ldap"""
+        from zipfile import ZipFile
+        try:
+            myzip= ZipFile(archivo_zip, 'r')    
+            myzip.extractall(path="/tmp/rayuela-ldap")
+        except Exception,e:
+            return e
+            
+        if not os.path.exists("/tmp/rayuela-ldap/Alumnos.xml"):
+            return "No es un archivo de importación de alumnos"
+            
+        return "" #todo ha ido bien
     
-    return "" #todo ha ido bien
     
-    
-def asegura_codigos(cadena):
-    resultado = cadena.replace(u"á", u"a")
-    resultado = resultado.replace(u"Á", u"A")
-    resultado = resultado.replace(u"à", u"a")
-    resultado = resultado.replace(u"À", u"A")
-    resultado = resultado.replace(u"é", u"e")
-    resultado = resultado.replace(u"É", u"E")
-    resultado = resultado.replace(u"è", u"e")
-    resultado = resultado.replace(u"È", u"E")
-    resultado = resultado.replace(u"í", u"i")
-    resultado = resultado.replace(u"Í", u"I")
-    resultado = resultado.replace(u"ì", u"i")
-    resultado = resultado.replace(u"Ì", u"I")
-    resultado = resultado.replace(u"ó", u"o")
-    resultado = resultado.replace(u"Ó", u"O")
-    resultado = resultado.replace(u"ò", u"o")
-    resultado = resultado.replace(u"Ò", u"O")
-    resultado = resultado.replace(u"ú", u"u")
-    resultado = resultado.replace(u"Ú", u"U")
-    resultado = resultado.replace(u"ü", u"u")
-    resultado = resultado.replace(u"Ü", u"U")
-    resultado = resultado.replace(u"ù", u"u")
-    resultado = resultado.replace(u"Ù", u"U")
-    resultado = resultado.replace(u"ª", u"a")
-    resultado = resultado.replace(u"º", u"o")
-    resultado = resultado.replace(u"ñ", u"n")
-    resultado = resultado.replace(u"Ñ", u"N")
-    resultado = resultado.replace(u"ç", u"c")
-    resultado = resultado.replace(u"(", "")
-    resultado = resultado.replace(u")", u"")    
-    resultado = resultado.replace(u".", u"") 
-    return str(resultado)
+    def asegura_codigos(self,cadena):
+        """Quita caracteres no válidos para los nombres de login
+        de los usuarios"""
+        resultado = cadena.replace(u"á", u"a")
+        resultado = resultado.replace(u"Á", u"A")
+        resultado = resultado.replace(u"à", u"a")
+        resultado = resultado.replace(u"À", u"A")
+        resultado = resultado.replace(u"é", u"e")
+        resultado = resultado.replace(u"É", u"E")
+        resultado = resultado.replace(u"è", u"e")
+        resultado = resultado.replace(u"È", u"E")
+        resultado = resultado.replace(u"í", u"i")
+        resultado = resultado.replace(u"Í", u"I")
+        resultado = resultado.replace(u"ì", u"i")
+        resultado = resultado.replace(u"Ì", u"I")
+        resultado = resultado.replace(u"ó", u"o")
+        resultado = resultado.replace(u"Ó", u"O")
+        resultado = resultado.replace(u"ò", u"o")
+        resultado = resultado.replace(u"Ò", u"O")
+        resultado = resultado.replace(u"ú", u"u")
+        resultado = resultado.replace(u"Ú", u"U")
+        resultado = resultado.replace(u"ü", u"u")
+        resultado = resultado.replace(u"Ü", u"U")
+        resultado = resultado.replace(u"ù", u"u")
+        resultado = resultado.replace(u"Ù", u"U")
+        resultado = resultado.replace(u"ª", u"a")
+        resultado = resultado.replace(u"º", u"o")
+        resultado = resultado.replace(u"ñ", u"n")
+        resultado = resultado.replace(u"Ñ", u"N")
+        resultado = resultado.replace(u"ç", u"c")
+        resultado = resultado.replace(u"(", "")
+        resultado = resultado.replace(u")", u"")    
+        resultado = resultado.replace(u".", u"") 
+        return str(resultado)
 
 
-def chk_username(login,keep=False):
-    result = ldap_con.search("ou=People","uid="+ login,["uid"])
-    if len(result)==0 :
-        if not keep:
-            login= login + "01"
-        return login
-    else:
-        i=2
-        while len(result)>0:
-            nuevo_login=login + "%02d" % (i)
-            result = ldap_con.search("ou=People","uid="+ nuevo_login,["uid"])
-            i += 1
-        return nuevo_login
+    def chk_username(self,login,keep=False):
+        """Comprueba si el login existe ya en la base de datos
+        de ldap, y si existe le va aumentando el número del final"""
+        result = self.ldap_con.search("ou=People","uid="+ login,["uid"])
+        if len(result)==0 :
+            if not keep:
+                login= login + "01"
+            return login
+        else:
+            i=2
+            while len(result)>0:
+                nuevo_login=login + "%02d" % (i)
+                result = self.ldap_con.search("ou=People","uid="+ nuevo_login,["uid"])
+                i += 1
+            return nuevo_login
 
-def crea_logins():
-    global ldap_con
-    global usuarios
-    global esAlumnos
-    for usuario in usuarios:
-        usuario["nuevo"]=True
-        if "dni" in usuario.keys():
-            #contraseña del usuario:
-            if esAlumnos:
-                usuario["passwd"]=usuario["fecha-nacimiento"].replace("/","")
-                if usuario["passwd"]=="": usuario["passwd"]=usuario["dni"]
-            else:
-                usuario["passwd"]=usuario["dni"]            
-            
-            #login del usuario, primero vemos si ya está en ldap:
-            result = ldap_con.search("ou=People","employeeNumber="+ usuario["dni"],["uid"])
-            if len(result) > 0:
-                usuario["login"]=result[0][0][1]['uid'][0]
-                usuario["nuevo"]=False
+    def crea_logins(self):
+        """Revisa la lista de usuarios y le asigna nuevo login al que
+        no está ya en ldap o no lo trae de Rayuela"""
+
+        for usuario in self.usuarios:
+            usuario["nuevo"]=True
+            if "dni" in usuario.keys():
+                #contraseña del usuario:
+                if self.esAlumnos:
+                    usuario["passwd"]=usuario["fecha-nacimiento"].replace("/","")
+                    if usuario["passwd"]=="": usuario["passwd"]=usuario["dni"]
+                else:
+                    usuario["passwd"]=usuario["dni"]            
                 
-            else: #si el usuario no está en ldap y es nuevo, hay que crearle el login
-                if  usuario["datos-usuario-rayuela"] != "false": #esta en rayuela  su login
-                    usuario["login"]=chk_username(usuario["datos-usuario-rayuela"],True)
+                #login del usuario, primero vemos si ya está en ldap:
+                result = self.ldap_con.search("ou=People","employeeNumber="+ usuario["dni"],["uid"])
+                if len(result) > 0:
+                    usuario["login"]=result[0][0][1]['uid'][0]
+                    usuario["nuevo"]=False
                     
-                else: #iniciales del nombre + primer apellido + inicial segundo apellido
-                    login=''              
-                    for i in zip(*usuario["nombre"].lower().split(" "))[0]:
-                        login += i
-                    for i in usuario["primer-apellido"].lower().replace("-"," ").split(" "):
-                        login += i
-                    if "segundo-apellido" in usuario.keys():
-                        login += usuario["segundo-apellido"][0].lower()
-                    usuario["login"]=chk_username(login)
-        
-        else: #sin nie ni dni, no podemos gestionarlo
-            usuarios.remove(usuario)
+                else: #si el usuario no está en ldap y es nuevo, hay que crearle el login
+                    if  usuario["datos-usuario-rayuela"] != "false": #esta en rayuela  su login
+                        usuario["login"]=self.chk_username(usuario["datos-usuario-rayuela"],True)
+                        
+                    else: #iniciales del nombre + primer apellido + inicial segundo apellido
+                        login=''              
+                        for i in zip(*usuario["nombre"].lower().split(" "))[0]:
+                            login += i
+                        for i in usuario["primer-apellido"].lower().replace("-"," ").split(" "):
+                            login += i
+                        if "segundo-apellido" in usuario.keys():
+                            login += usuario["segundo-apellido"][0].lower()
+                        usuario["login"]=self.chk_username(login)
+            
+            else: #sin nie ni dni, no podemos gestionarlo
+                self.usuarios.remove(usuario)
 
             
-def encode(grafico):
-    archivo=os.path.join("/tmp/rayuela-ldap/",grafico)
-    im = Image.open(archivo)
-    im.thumbnail((60,80), Image.ANTIALIAS)
-    if im.mode != "RGB":
-        im = im.convert("RGB")
-    buf= StringIO.StringIO()
-    try:
-        im.save(buf, format= 'JPEG')
-        resultado=buf.getvalue()
-    except Exception,e:
-        return None
-        
-    return  resultado
+    def encode(self,grafico):
+        """Convierte la foto del alumno a un tamaño pequeño y 
+        en un formato gráfico aceptable en ldap"""
+        archivo=os.path.join("/tmp/rayuela-ldap/",grafico)
+        im = Image.open(archivo)
+        im.thumbnail((60,80), Image.ANTIALIAS)
+        if im.mode != "RGB":
+            im = im.convert("RGB")
+        buf= StringIO.StringIO()
+        try:
+            im.save(buf, format= 'JPEG')
+            resultado=buf.getvalue()
+        except Exception,e:
+            return None
+            
+        return  resultado
     
 
-def parse_nodo(nodo):
-    """ para cada nodo en el xml, obtiene sus datos y prepara sus grupos"""
-    global usuarios
-    usuario={}
+    def parse_nodo(self,nodo):
+        """ para cada nodo en el xml, obtiene sus datos y prepara sus grupos"""
+        usuario={}
 
-    for info in nodo.childNodes:        
-        if info.nodeType!=info.TEXT_NODE:
-            if info.nodeName in ("datos-usuario-rayuela","foto","grupos"):
-                dato=info.childNodes[1].firstChild.nodeValue
-                if info.nodeName=="foto" and dato=="true":
-                    dato=info.getElementsByTagName("nombre-fichero")[0].firstChild.nodeValue
-                if info.nodeName=="datos-usuario-rayuela" and dato=="true":
-                    dato=info.getElementsByTagName("login")[0].firstChild.nodeValue                
+        for info in nodo.childNodes:        
+            if info.nodeType!=info.TEXT_NODE:
+                if info.nodeName in ("datos-usuario-rayuela","foto","grupos"):
+                    dato=info.childNodes[1].firstChild.nodeValue
+                    if info.nodeName=="foto" and dato=="true":
+                        dato=info.getElementsByTagName("nombre-fichero")[0].firstChild.nodeValue
+                    if info.nodeName=="datos-usuario-rayuela" and dato=="true":
+                        dato=info.getElementsByTagName("login")[0].firstChild.nodeValue                
+                    
+                else:
+                    try:
+                        dato=info.firstChild.nodeValue                    
+                    except : # no hay dato en este nodo, p. ej. segundo-apellido
+                        dato=' '            
+                if info.nodeName == 'nie':
+                    usuario["dni"]=self.asegura_codigos(dato)
+                elif info.nodeName == 'foto': #no paso asegura_codigos para no quitar el "."
+                    usuario['foto']=str(dato)
+                else:  
+                    usuario[info.nodeName]=self.asegura_codigos(dato)
                 
-            else:
-                try:
-                    dato=info.firstChild.nodeValue                    
-                except : # no hay dato en este nodo, p. ej. segundo-apellido
-                    dato=' '            
-            if info.nodeName == 'nie':
-                usuario["dni"]=asegura_codigos(dato)
-            elif info.nodeName == 'foto': #no paso asegura_codigos para no quitar el "."
-                usuario['foto']=str(dato)
-            else:  
-                usuario[info.nodeName]=asegura_codigos(dato)
-            
-    usuarios.append(usuario)
+        self.usuarios.append(usuario)
 
     
-def parsea_archivo(archivo_xml,tipo):
-    
-    xml_usuarios=xml.dom.minidom.parse(archivo_xml)
-    lista= xml_usuarios.getElementsByTagName(tipo)
-    
-    for nodo in lista:
-        parse_nodo(nodo)
+    def parsea_archivo(self,archivo_xml,tipo):
+        """Recorre el archivo xml y va generando la lista de usuarios"""
+        
+        xml_usuarios=xml.dom.minidom.parse(archivo_xml)
+        lista= xml_usuarios.getElementsByTagName(tipo)
+        
+        for nodo in lista:
+            self.parse_nodo(nodo)
 
-    crea_logins()
+        self.crea_logins()
     
 
-def lista_grupos(lista,clave,sin_grupo="SIN_AULA"):
-    grupos={}
-    
-    for i in lista:
-        if clave not in i.keys():
-            grupo=sin_grupo
-        else:
-            grupo=i[clave]
+    def lista_grupos(self,lista,clave,sin_grupo="SIN_AULA"):
+        """Devuelve una enumeración de los grupos que pertenecen a 
+        clave, siendo normalmente clave igual a aulas o dptos"""
+        grupos={}
+        
+        for i in lista:
+            if clave not in i.keys():
+                grupo=sin_grupo
+            else:
+                grupo=i[clave]
+                
+            if grupo not in grupos.keys():
+                grupos[grupo]=[i["login"]]
+            else:
+                grupos[grupo].append(i["login"])
+                
+        return grupos
             
-        if grupo not in grupos.keys():
-            grupos[grupo]=[i["login"]]
-        else:
-            grupos[grupo].append(i["login"])
             
-    return grupos
-            
-            
-def crea_grupos(ldap_con,listado):
-    for grupo in listado:
-        nuevo=Groups(ldap_con,"school_class",str(grupo),"")       
-        nuevo.add()
+    def crea_grupos(self,listado):
+        """Da de alta en ldap los grupos de alumnos"""
+        for grupo in listado:
+            nuevo=Groups(self.ldap_con ,"school_class",str(grupo),"")       
+            nuevo.add()
     
-def existsUsername(ldap_con,login):
-    result = ldap_con.search("ou=People","uid="+ login,["uid"])
-    if len(result) > 0:
-        return True
-    else:
-        return False
+    def existsUsername(self,login):
+        """comprueba si login existe ya en la bb.dd de ldap"""
+        result = self.ldap_con.search("ou=People","uid="+ login,["uid"])
+        if len(result) > 0:
+            return True
+        else:
+            return False
 
 
-def crea_usuarios(ldap_con,listado):    
-    global esAlumnos
-    lista=[]
-    if esAlumnos:
-        tipo="student"
-    else:
-        tipo="teacher"
+    def crea_usuarios(self):
+        """Da de alta en ldap los usuarios que están en el listado""" 
+        lista=[]
+        if self.esAlumnos:
+            tipo="student"
+        else:
+            tipo="teacher"
 
-    for usuario in listado:
+        for usuario in self.usuarios:
 
-        if not existsUsername(ldap_con,usuario['login']):
-            if usuario['foto'] == 'false':
-                foto=None
+            if not self.existsUsername(usuario['login']):
+                if self.esAlumnos:
+                    if usuario['foto'] == 'false':
+                        foto=None
+                    else:
+                        foto=self.encode(usuario['foto'])
+                else:
+                    foto=None
+                    
+                nuevo=Users(self.ldap_con ,tipo,usuario['nombre'],usuario['primer-apellido'] + ' ' + usuario['segundo-apellido'],
+                    usuario['dni'],usuario['login'],usuario['passwd'],usuario['passwd'],'','',foto)
+                nuevo.add()
+                lista.append((usuario['login'],True,usuario['passwd']))
             else:
-                foto=encode(usuario['foto'])
+                lista.append((usuario['login'],False,''))
                 
-            nuevo=Users(ldap_con,tipo,usuario['nombre'],usuario['primer-apellido'] + ' ' + usuario['segundo-apellido'],
-                usuario['dni'],usuario['login'],usuario['passwd'],usuario['passwd'],'','',foto)
-            nuevo.add()
-            lista.append((usuario['login'],True,usuario['passwd']))
-        else:
-            lista.append((usuario['login'],False,''))
-            
-    return lista
+        return lista
         
 
-def rellena_students(ldap_con,listado):
-    if len(ldap_con.search("ou=Group","cn=students",["cn"]))==0:
-        attr = [
-        ('objectclass', ['top','posixGroup','lisGroup','lisAclGroup']),
-        ('grouptype', ["authority_group"] ),		
-        ('gidnumber', ["1100"] ),		
-        ('cn', ['students'] ),
-        ('description', ['Lista de estudiantes del centro']),
-        ('memberuid', ['']),
-        ('member', [''])
-        ]
-        ldap_con.add("cn=students,ou=Group", attr)  
+    def rellena_students(self):
+        """Da de alta en el grupo students los que están en listado"""
+        if len(self.ldap_con.search("ou=Group","cn=students",["cn"]))==0:
+            attr = [
+            ('objectclass', ['top','posixGroup','lisGroup','lisAclGroup']),
+            ('grouptype', ["authority_group"] ),		
+            ('gidnumber', ["1100"] ),		
+            ('cn', ['students'] ),
+            ('description', ['Lista de estudiantes del centro']),
+            ('memberuid', ['']),
+            ('member', [''])
+            ]
+            self.ldap_con.add("cn=students,ou=Group", attr)  
 
                   
-    for usuario in listado:
-        attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario['login'] + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
-        (ldap.MOD_ADD, 'memberUid', [usuario['login']] )]
-        try:
-            ldap_con.modify('cn=students,ou=Group', attr)
-        except Exception,e:
-            print e
+        for usuario in self.usuarios:
+            attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario['login'] + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
+            (ldap.MOD_ADD, 'memberUid', [usuario['login']] )]
+            try:
+                self.ldap_con.modify('cn=students,ou=Group', attr)
+            except Exception,e:
+                print e
         
 
-def rellena_teachers(ldap_con,listado):
-    if len(ldap_con.search("ou=Group","cn=teachers",["cn"]))==0:
-        attr = [
-        ('objectclass', ['top','posixGroup','lisGroup','lisAclGroup']),
-        ('grouptype', ["authority_group"] ),		
-        ('gidnumber', ["3000"] ),		
-        ('cn', ['teachers'] ),
-        ('description', ['Lista de profesores del centro']),
-        ('memberuid', ['']),
-        ('member', [''])
-        ]
-        ldap_con.add("cn=teachers,ou=Group", attr)  
+    def rellena_teachers(self):
+        """Da de alta en el grupo teachers a todos los que están en el listado"""
+        if len(self.ldap_con.search("ou=Group","cn=teachers",["cn"]))==0:
+            attr = [
+            ('objectclass', ['top','posixGroup','lisGroup','lisAclGroup']),
+            ('grouptype', ["authority_group"] ),		
+            ('gidnumber', ["3000"] ),		
+            ('cn', ['teachers'] ),
+            ('description', ['Lista de profesores del centro']),
+            ('memberuid', ['']),
+            ('member', [''])
+            ]
+            self.ldap_con.add("cn=teachers,ou=Group", attr)  
 
-                 
-    for usuario in listado:
-        attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario['login'] + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
-            (ldap.MOD_ADD, 'memberUid', [usuario['login']] )]
-        try:
-            ldap_con.modify('cn=students,ou=Group', attr)
-        except Exception,e:
-            print e            
-
-
-def usuarios_grupos(ldap_con,lista_grupos):
-
-    for grupo in lista_grupos:
-        for usuario in lista_grupos[grupo]:
-            attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
-                (ldap.MOD_ADD, 'memberUid', [usuario] ) ]
+                     
+        for usuario in self.usuarios:
+            attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario['login'] + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
+                (ldap.MOD_ADD, 'memberUid', [usuario['login']] )]
             try:
-                ldap_con.modify('cn=' + grupo + ',ou=Group', attr)
+                self.ldap_con.modify('cn=students,ou=Group', attr)
             except Exception,e:
-                print e 
+                print e            
 
 
-def lista_antiguos(ldap_con,grupo):
-    lista={}
-    try:
-        resultado=ldap_con.search("ou=Group","cn=" + grupo ,["memberuid"])
-     
-        miembros=resultado[0][0][1]['memberUid']
-        if len(miembros) > 0:
-            for usuario in miembros:
-                dato=ldap_con.search("ou=People","uid=" + usuario ,["employeeNumber","uid"])
-                if len(dato) > 0:
-                    dni=dato[0][0][1]['employeeNumber'][0]
-                    uid=dato[0][0][1]['uid'][0]                 
-                    lista[dni]=uid
-                    
-    
-    except:
-        pass
-        
-    return lista
+    def usuarios_grupos(self,lista_grupos):
+        """Da de alta en cada grupo a los usuarios correspondientes"""
 
+        for grupo in lista_grupos:
+            for usuario in lista_grupos[grupo]:
+                attr=[(ldap.MOD_ADD, 'member', ['uid=' + usuario + ',ou=People,dc=instituto,dc=extremadura,dc=es'] ),
+                    (ldap.MOD_ADD, 'memberUid', [usuario] ) ]
+                try:
+                    self.ldap_con.modify('cn=' + grupo + ',ou=Group', attr)
+                except Exception,e:
+                    print e 
 
-def borra_antiguos(ldap_con,viejos,nuevos):
 
-    for usuario in nuevos:
-        if usuario['dni'] in viejos:
-            viejos.pop(usuario['dni'])
-                
-    for viejo in viejos:
+    def lista_antiguos(self,grupo):
+        """Devuelve el listado de los dni de los usuarios que ya estaban en ldap antes"""
+        lista={}
         try:
-            ldap_con.delete('uid='+ viejos[viejo] +',ou=People')
-            ldap_con.delete("cn="+ viejos[viejo] +",ou=Group")
+            resultado=self.ldap_con.search("ou=Group","cn=" + grupo ,["memberuid"])
+         
+            miembros=resultado[0][0][1]['memberUid']
+            if len(miembros) > 0:
+                for usuario in miembros:
+                    dato=self.ldap_con.search("ou=People","uid=" + usuario ,["employeeNumber","uid"])
+                    if len(dato) > 0:
+                        dni=dato[0][0][1]['employeeNumber'][0]
+                        uid=dato[0][0][1]['uid'][0]                 
+                        lista[dni]=uid
+
         except:
-            pass #algún dato ya estaba borrado
+            pass
+            
+        return lista
+
+
+    def borra_antiguos(self,viejos):
+        """Borra de la base de datos de ldap a los usuarios que estaban 
+        en la lista de viejos y no están en la de nuevos"""
+
+        for usuario in self.usuarios:
+            if usuario['dni'] in viejos:
+                viejos.pop(usuario['dni'])
+                    
+        for viejo in viejos:
+            try:
+                self.ldap_con.delete('uid='+ viejos[viejo] +',ou=People')
+                self.ldap_con.delete("cn="+ viejos[viejo] +",ou=Group")
+            except:
+                pass #algún dato ya estaba borrado
     
     
-
+    def gestiona_archivo(self):
+        """Función principal que a partir del archivo hace todo en ldap"""
+        
+        aulas={}
+        dptos={}
+        
+        self.esAlumnos=(self.archivo[-4:].lower()==".zip")
+        
+        if self.esAlumnos:
+            intento=self.unzip_alumnos(self.archivo)
+            if intento!="": 
+                print "PROBLEMAS",intento
+            else:
+                self.parsea_archivo("/tmp/rayuela-ldap/Alumnos.xml","alumno")
+                aulas=self.lista_grupos(self.usuarios,"grupo")
+                
+        else:
+            self.parsea_archivo(self.archivo,"profesor")
+            dptos=self.lista_grupos(self.usuarios,"departamento","SIN_DPTO")
+            
+        self.crea_grupos(aulas)  
+        self.crea_grupos(dptos)
+        total=self.crea_usuarios()
+        
+        if self.esAlumnos:
+            usuarios_antiguos=self.lista_antiguos("students")
+         
+            LdapUtils.clean_students(self.ldap_con )   
+            self.rellena_students()    
+            self.usuarios_grupos(aulas)
+        else:
+            usuarios_antiguos=self.lista_antiguos("teachers")
+            LdapUtils.clean_teachers(self.ldap_con )
+            self.rellena_teachers()
+            self.usuarios_grupos(dptos)
+        
+        if self.borrando:
+            self.borra_antiguos(usuarios_antiguos) 
+                  
+        return total
     
+    
+    
+    
 if __name__ == '__main__':    
     #El código siguiente es sólo para depuración y desarrollo
-    #No tiene sentido fuera de ese contexto
-    import LdapConnection
-    import Utils.LdapUtils as LdapUtils
-    aulas={}
-    dptos={}
-
+    #No tiene sentido fuera de ese contexto  
+    import LdapConnection    
     session=Storage()
     session.server="ldap"
     session.username="admin"
     session.password="linex2008"
     
-    ldap_con=LdapConnection.LdapConnection(session)
-    ldap_con.process()
-
-    
+    ldap_con = LdapConnection.LdapConnection(session)
+    ldap_con.process()    
     try:
         os.mkdir( "/tmp/rayuela-ldap")
-        os.remove( "/tmp/rayuela.log")
     except:
         pass #problema de permisos o directorio ya creado
         
     archivo="/opt/instituto/santaeulalia/ExportacionDatosAlumnado.zip"
+    archivo="/tmp/ExportacionDatosProfesorado.xml"
     
-    esAlumnos=(archivo[-4:].lower()==".zip")
+    rayuela=Rayuela(ldap_con,archivo,True)
+    todos=rayuela.gestiona_archivo()
     
-    if esAlumnos:
-        intento=unzip_alumnos(archivo)
-        if intento!="": 
-            print "PROBLEMAS",intento
-        else:
-            parsea_archivo("/tmp/rayuela-ldap/Alumnos.xml","alumno")
-            aulas=lista_grupos(usuarios,"grupo")
-            
-    else:
-        parsea_archivo(archivo,"profesor")
-        dptos=lista_grupos(usuarios,"departamento","SIN_DPTO")
-        
-    crea_grupos(ldap_con,aulas)  
-    crea_grupos(ldap_con,dptos)
-    total=crea_usuarios(ldap_con,usuarios)
-    print total
-
-    if esAlumnos:
-        usuarios_antiguos=lista_antiguos(ldap_con,"students")
-     
-        LdapUtils.clean_students(ldap_con)   
-        rellena_students(ldap_con,usuarios)    
-        usuarios_grupos(ldap_con,aulas)
-    else:
-        usuarios_antiguos=lista_antiguos(ldap_con,"teachers")
-        LdapUtils.clean_teachers(ldap_con)
-        rellena_teachers(ldap_con,usuarios)
-        usuarios_grupos(ldap_con,dptos)
-    
-    if borrando:
-        borra_antiguos(ldap_con,usuarios_antiguos,usuarios) 
-              
     LdapUtils.sanea_grupos(ldap_con)
-        
-        
-
     
-        
+    print todos
     
-    
-    
-    
-        
-    
Index: controlies/trunk/applications/controlies/modules/Utils/LdapUtils.py
===================================================================
--- controlies/trunk/applications/controlies/modules/Utils/LdapUtils.py	(revision 180)
+++ controlies/trunk/applications/controlies/modules/Utils/LdapUtils.py	(revision 181)
@@ -80,7 +80,7 @@
 	else:
 		return "/home/alumnos/"
 
-def clean_teachers(ldap):
+def clean_teachers(ldap_con):
     """vaciado los grupos de profesores"""
     cadena="(cn=teachers)"
     clean_group(cadena,ldap_con)    
Index: controlies/trunk/applications/controlies/views/gestion/rayuela.html
===================================================================
--- controlies/trunk/applications/controlies/views/gestion/rayuela.html	(nonexistent)
+++ controlies/trunk/applications/controlies/views/gestion/rayuela.html	(revision 181)
@@ -0,0 +1,133 @@
+{{response.files.append(URL('static','js/fileuploader.css'))}}
+{{response.files.append(URL('static','js/fileuploader.js'))}}
+{{extend 'layout.html'}}
+   
+<p><h1>Importación de datos de alumnos y profesores desde Rayuela</h1><p>
+<p><h3>Pasos a realizar:</h3><p>
+
+
+<ol>
+  <li>Ir a <a href="http://rayuela.educarex.es">Rayuela
+(http://rayuela.educarex.es</a>), entrar con el login particular
+de administrador de un centro y desde Utilidades-&gt;Intercambios
+de informaci&oacute;n generar y descargar el listado que se desee
+de alumnos
+y/o profesores.</li>
+  <li>Subir, usando el botón <strong>Subir uno o más ficheros</strong>, el
+archivo
+descargado (No hay problema en seleccionar y subir juntos el archivo de profesores y de alumnos)</li>
+
+  <li>Una vez que los archivos están subidos (aparece su tamaño a la derecha del nombre), usar el botón <strong>Enviar</strong> para iniciar el proceso (antes se debe marcar el recuadro de <strong>Principio de curso...</strong> si se desea borrar los usuarios antiguos. 
+  <li>Se dar&aacute;n de alta en ldap los grupos a los que
+pertenecen
+los usuarios del listado, los usuarios y se
+a&ntilde;adir&aacute;n los usuarios a los grupos.&nbsp;</li>
+  <li>La aplicaci&oacute;n proporcionar&aacute; un
+archivo de salida con los
+usuarios y sus datos (login y contrase&ntilde;as)
+para comprobar si hay alg&uacute;n problema o error. Por motivos de
+seguridad obvios ese archivo debe destruirse o guardarse en lugar
+seguro una vez comprobado. Los usuarios incluidos en el archivo de Rayuela que estuvieran dados de alta previamente en el ldap aparecerán, pero sin su contraseña</li>
+</ol>
+
+<br>
+<hr />
+<br>
+
+
+<center>
+<br/>
+
+
+<div id="file-upload">
+  <noscript>
+    <p>Please enable JavaScript to use file uploader.</p>
+  </noscript>
+  
+</div>
+
+
+<h3>Pulse Enviar después de que el archivo o archivos hayan terminado de subir al servidor</h3>
+
+{{=form.custom.begin}}
+{{=form.custom.widget.principiocurso}}
+Para principio de curso, borrar los usuarios que no estén en este 
+archivo. Lo hace tanto con profesores (si es un archivo de profesores), como con alumnos (si hay archivo de alumnos).
+<span style="font-weight: bold;">Esta opción borrará usuarios.</span>
+<br><br>
+{{=form.custom.submit}}
+
+{{=form.custom.end}}
+<br>
+</center>
+<hr />
+<br>
+<h3>Información adicional de utilidad para los administradores:</h3>
+
+Durante el curso en cualquier momento se pueden dar de alta nuevos
+usuarios directamente usando el menú de administración, o importandolos desde Rayuela. Importar de nuevo un archivo descargado de Rayuela no afecta a los usuarios ya dados de alta (a no ser que se active la opción de principio de curso y haya profesores que hayan sido dados de baja en el centro en Rayuela) <br>
+<br>
+Los profesores ser&aacute;n dados de alta con el mismo login que
+tengan en
+Rayuela, la contrase&ntilde;a ser&aacute; su NIF.&nbsp;S&oacute;lo si ya hab&iacute;a 
+un usuario con el mismo login en
+el centro, le cambiar&aacute; la numeraci&oacute;n final del
+login.
+<br>
+<br>
+Los alumnos tendr&aacute;n como contrase&ntilde;a su fecha de nacimiento
+ en ocho dígitos (p. ej: 2-Enero-1989 tiene de contraseña 02011989) . Su
+login ser&aacute;:<br>
+<ul>
+  <li>Si el centro ha dado de alta a los alumnos como usuarios en
+Rayuela, el login que tengan ah&iacute;</li>
+  <li>Si no les ha dado de alta, ser&aacute;: primeras letras
+del
+nombre, primer apellido completo, primera letra del segundo apellido y
+01. Los acentos, puntos y caracteres no disponibles en el alfabeto inglés son eliminados.
+Por ejemplo: Mar&iacute;a Pilar Rodriguez-Mu&ntilde;ino
+Sanchez ser&aacute; el usuario:
+mprodriguezmuninos01. Si al dar al alumno de alta existiera
+ya otro
+alumno con ese login, el sistema cambiar&aacute; el 01 por 02 y
+as&iacute; sucesivamente.</li>
+</ul>
+<br>
+<br>
+<b>Importante</b>:
+Las carpetas de los usuarios y de los grupos se generan
+autom&aacute;ticamente
+en el servidor de NFS. Cada 5 minutos se comprueba si hay
+alg&uacute;n usuario
+nuevo y se genera su carpeta ah&iacute;. Si no se desea esperar y
+se quieren
+generar inmediatamente las carpetas, basta con ejecutar el script <i>crearCarpetas</i>
+en el servidor de NFS.
+
+
+
+<script type="text/javascript">
+  function createUploader(){            
+     var uploader = new qq.FileUploader({
+  element: document.getElementById('file-upload'),
+  action: '{{=URL("subida_rayuela")}}', 
+  sizeLimit: 200000000,      
+  minSizeLimit: 0,
+  debug: true,
+        onSubmit: function(id, fileName){},
+        onProgress: function(id, fileName, loaded, total){},
+        onComplete: function(id, fileName, responseJSON){},
+        onCancel: function(id, fileName){},
+        allowedExtensions: ['zip','xml'],
+        messages: {
+            typeError: "{file} {{='Tiene una extensión incorrecta.'}} {{='Sólo se permiten estas extensiones: '}} {extensions}",
+            sizeError: "{file} {{'es demasiado grande, el tamaño máximo permitido es'}} {sizeLimit}.",
+            minSizeError: "{file} {{='es demasiado pequeño, el tamaño mínimo de archivo es'}} {minSizeLimit}.",
+            emptyError: "{file} {{='está vacío, selecciona archivos de nuevo'}}",
+            onLeave: "{{='Se están subiendo los archivos. Si sales ahora se cancelará la subida.'}}"                         
+        },
+        showMessage: function(message){ alert(message); }        
+     });           
+  }
+  window.onload = createUploader;     
+</script>
Index: controlies/trunk/applications/controlies/models/menu.py
===================================================================
--- controlies/trunk/applications/controlies/models/menu.py	(revision 180)
+++ controlies/trunk/applications/controlies/models/menu.py	(revision 181)
@@ -45,12 +45,8 @@
      [            
      ('Servidores de Aula', False,URL( 'gestion', 'servidores_aula')),   
      
-    ('Importación de Rayuela', False, None,
-        [
-        ('Profesores', False, URL( 'gestion', 'rayuela_profesores')),
-        ('Alumnos', False, URL( 'gestion', 'rayuela_alumnos')),
+    ('Importación de Rayuela', False, URL( 'gestion', 'rayuela')),
 
-        ]),
         
     ('Mantenimiento de LDAP', False, None,
         [
Index: controlies/trunk/applications/controlies/models/db.py
===================================================================
--- controlies/trunk/applications/controlies/models/db.py	(revision 180)
+++ controlies/trunk/applications/controlies/models/db.py	(revision 181)
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 # this file is released under public domain and you can use without limitations
+import logging, logging.handlers
+import os
 
 #########################################################################
 ## This scaffolding model makes your app work on Google App Engine too
@@ -90,6 +92,55 @@
 
 auth.settings.login_methods=[ldap_auth( server='localhost', base_dn='ou=People,dc=instituto,dc=extremadura,dc=es',secure=LdapConnection.ldap_secure,cert_path=LdapConnection.ldap_cert,mode='cn')]
 
+
+#########################################################
+#
+# Global functions
+#
+#########################################################
+
+prod = cache.ram('prod', lambda : bool(os.environ.get('PRODUCTION')), time_expire=5000)
+
+if prod == False:
+    #
+    # Development environment
+    #
+    log_level = logging.DEBUG
+
+else:
+    #
+    # Production environment
+    #
+    log_level = logging.ERROR
+
+    
+
+#
+# Logger
+#
+
+def get_configured_logger(name):
+    logger = logging.getLogger(name)
+    if (len(logger.handlers) == 0):
+        # Create RotatingFileHandler
+        import os
+        formatter="%(asctime)s %(levelname)s %(process)s %(thread)s %(funcName)s():%(lineno)d %(message)s"
+        handler = logging.handlers.RotatingFileHandler(os.path.join(request.folder,'private/app.log'),maxBytes=1024,backupCount=2)
+        handler.setFormatter(logging.Formatter(formatter))
+        # setting level
+        handler.setLevel(log_level)
+        logger.addHandler(handler)
+        logger.setLevel(log_level)
+        logger.debug(name + ' logger created')
+        if str(prod) == True:
+            logger.debug('Server launched in production mode')
+        else:
+            logger.debug('Server launched in developpment mode')
+    return logger
+
+logger = cache.ram('once',lambda:get_configured_logger(request.application),time_expire=99999999)
+
+
 def conecta():
     l=LdapConnection.LdapConnection(session)
     l.process()
