Mapeate = function(entradas){

    const self = this, 
          configuracion_por_defecto = {
              nombre_objeto : "mapeate", 
              nulos : false, 
              valor_por_defecto : null, 
              autoiniciar : true, 
              archivos_de_configuracion_por_defecto : [
                  "/json/Mapeate.configuracion.json", 
                  "/json/Mapeate.configuracion.secretos.json"
              ], 
              texto_por_defecto : "", 
              configuracion_sobreescribir : false, 
              i18n_sobreescribir : false, 
              formato_terminal : "[{tipo}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{linea}]{archivo}({metodo}): {mensaje}", 
              terminal_tipos_por_defecto : [
                  ["unkn", "unknown", "desconocido"], 
                  [" ok ", "ok", "si", "correcto"], 
                  ["info", "informacion", "information"], 
                  ["note", "nota", "comment", "comentario"], 
                  ["warn", "warning", "aviso", "advertencia"], 
                  ["erro", "error", "incrrecto"], 
                  ["fail", "failure", "fallo"], 
                  ["exce", "excepcion"]
              ], 
              archivos_de_i18n_por_defecto : [
                  "/json/i18n/Mapeate.i18n.espanol.json"
              ], 
              ajax_timeout : 2000, 
              terminal_unkn_oscuro : "color : #AAA;", 
              terminal_unkn_claro : "color : #666;", 
              terminal_ok_oscuro : "color : #9F9;", 
              terminal_ok_claro : "color : #090;", 
              terminal_info_oscuro : "color : #99F;", 
              terminal_info_claro : "color : #009;", 
              terminal_note_oscuro : "color : #222;", 
              terminal_note_claro : "color : #EFEFEF;"
          }, 
          configuracion = {}, 
          textos = {
              espanol : {
                  mapeate : "Mapeate", 
                  mapeate_construyendose : "La aplicación 'Mapéate' se está construyendo...", 
                  mapeate_construido : "La aplicación 'Mapeate' se construyó completamente.", 
                  mapeate_iniciando : "La aplicación 'Mapeate' se está iniciando...", 
                  cargando_configuracion : "Cargando la configuración...", 
                  configuracion_cargada : "La configuración ha sido cargada completamente.", 
                  cargando_terminal_tipos : "Cargando los tipos de salidas de terminal...", 
                  terminal_tipos_cargados : "Los tipos de terminal han sido cargados completamente.", 
                  cargando_i18n : "Cargando los textos internacionalizados...", 
                  i18n_cargada : "Los textos internacionalizados han sido cargados completamente.", 
                  mapeate_vistas_construyendose : "El módulo de gestión de vistas de la aplicación 'Mapeatea' se está construyendo...", 
                  mapeate_vistas_construido : "El módulo de gestión de vistas de la aplicación 'Mapeatea' se construyó completamente.", 
                  mapeate_mapas_construyendose : "El módulo de gestión de mapas de la aplicación 'Mapeatea' se está construyendo...", 
                  mapeate_mapas_construido : "El módulo de gestión de mapas de la aplicación 'Mapeatea' se construyó completamente.", 
                  mapeate_mapas_selector_construyendose : "El módulo de gestión de selección de mapas de la aplicación 'Mapeatea' se está construyendo...", 
                  mapeate_mapas_selector_construido : "El módulo de gestión de selección de mapas de la aplicación 'Mapeatea' se construyó completamente.", 
                  mapeate_base_construyendose : "El módulo de gestión básica de DPIs de la aplicación 'Mapeatea' se está construyendo...", 
                  mapeate_base_construido : "El módulo de gestión básica de DPIs de la aplicación 'Mapeatea' se construyó completamente."
              }
          }, 
          terminal_tipos = [], 
          hashes = [], 
          hilos_de_proceso = [];
    let iniciado = false, 
        configuracion_sobreescribir = false, 
        i18n_sobreescribir = false, 
        idioma_por_defecto = null, 
        idioma = null, 
        modo_dispositivo = "por_defecto", 
        modo_gui = "por_defecto", 
        terminal_unkn_oscuro, terminal_unkn_claro, 
        terminal_ok_oscuro, terminal_ok_claro, 
        terminal_info_oscuro, terminal_info_claro, 
        terminal_note_oscuro, terminal_note_claro, 
        modo_gui_oscuro = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches, 
        formato_terminal = "[{tipo}] {yyyy}{mm}{dd} {hh}{ii}{ss} [{linea}]{archivo}({metodo}): {mensaje}", 
        hash_alfabeto, hash_longitud, 
        intervalo_de_procesos, precarga_timeout, 
        construido = false, 
        claves_padre_por_defecto = ["tagName", "nodeName"];

    let hash_self = this.hash_self;
    let si_mismo = this.si_mismo = document;
    let nombre_objeto = this.nombre_objeto;
    
    let vistas = this.vistas;
    let mapas = this.mapas;
    let base = this.base;

    const re_bloque_de_traza = new RegExp("^(" + [
        /\s*at\s+(([^\s]+)\s+\()?(([^\(\)\:]+\:)?[^\(\)\:]+)(\:([0-9]+)\:[0-9]+)?\)?/.source, // Webkit
        /([^\@]+)\@([^:]+\:[^\:]+)\:([0-9]+)\:[0-9]+/.source, // Gecko
    ].join("|") + ")$");

    const constructor = () => {

        nombre_objeto = self.nombre_objeto = self.configuracion("nombre_objeto");
        configuracion_por_defecto.terminal_tipos_por_defecto.forEach(entrada => terminal_tipos.push(entrada));
        establecer_variables_comunes();

        self.print("info", "mapeate_construyendose");

        vistas = this.vistas = new Mapeate.Vistas(self, entradas);
        mapas = this.mapas = new Mapeate.Mapas(self, entradas);
        base = this.base = new Mapeate.Base(self, entradas);

        self.print("ok", "mapeate_construido");

        self.configuracion("autoiniciar") && self.iniciar();

    };

    const establecer_variables_comunes = () => {

        configuracion_sobreescribir = self.configuracion(["configuracion_sobreescribir", "sobreescribir"]);
        formato_terminal = self.configuracion("formato_terminal");

        terminal_unkn_oscuro = self.configuracion("terminal_unkn_oscuro");
        terminal_unkn_claro = self.configuracion("terminal_ounkn_claro");
        terminal_ok_oscuro = self.configuracion("terminal_ok_oscuro");
        terminal_ok_claro = self.configuracion("terminal_ok_claro");
        terminal_info_oscuro = self.configuracion("terminal_info_oscuro");
        terminal_info_claro = self.configuracion("terminal_info_claro");
        terminal_note_oscuro = self.configuracion("terminal_note_oscuro");
        terminal_note_claro = self.configuracion("terminal_note_claro");

    };

    this.ejecutar = (callback, ...argumentos) => typeof callback == "function" && callback(...argumentos);

    this.iniciar = callback => {

        const terminar = estado => self.ejecutar(callback, estado);

        self.print("info", "mapeate_iniciando");

        if(iniciado){
            self.print("warn", "mapeate_ya_iniciado");
            terminar(false);
            return false;
        };
        iniciado = true;
        
        establecer_variables_comunes();

        self.print("info", "cargando_configuracion");

        self.ejecutar_array_asincrono([
            "archivos_de_configuracion_por_defecto", 
            "archivos_de_configuracion"
        ], (clave, callback) => self.configuracion_anadir(self.configuracion(clave), true, () => {
            establecer_variables_comunes();
            self.ejecutar(callback)
        }), () => {
            self.print("ok", "configuracion_cargada");
            self.print("info", "cargando_terminal_tipos");
            self.ejecutar_array_asincrono([
                "terminal_tipos_por_defecto", 
                "terminal_tipos"
            ], (clave, callback) => self.terminal_tipos_anadir(self.configuracion(clave), callback), () => {
                self.print("ok", "terminal_tipos_cargados");

                const nuevas_claves_padre = self.coger_claves(self.configuracion(("claves_padre_por_defecto", "claves_padre")));

                i18n_sobreescribir = self.configuracion(["i18n_sobreescribir", "sobreescribir"]);
                idioma_por_defecto = self.configuracion(["idioma_por_defecto", "idioma"]);
                idioma = self.configuracion(["idioma", "idioma_por_defecto"]);
                hash_alfabeto = self.configuracion("hash_alfabeto");
                hash_longitud = self.configuracion("hash_longitud");
                precarga_timeout = self.configuracion("precarga_timeout");

                nuevas_claves_padre.length && (claves_padre_por_defecto = nuevas_claves_padre);

                intervalo_de_procesos = setInterval(ejecutar_hilos_de_proceso, 1000 / self.configuracion("fotogramas_por_segundo"));

                self.print("info", "cargando_i18n");
                self.ejecutar_array_asincrono([
                    "archivos_de_i18n_por_defecto", 
                    "archivos_de_i18n"
                ], (clave, callback) => self.i18n_anadir(self.configuracion(clave), true, callback), () => {
                    self.print("ok", "i18n_cargada");
                    self.ejecutar_array_asincrono([vistas, mapas, base], (elemento, callback) => elemento.iniciar(callback), () => {
                        self.print("ok", "mapeate_iniciado");
                        terminar(true);
                    });
                });

            });
        });

        return true;
    };

    this.nulos = nulos => typeof nulos != "boolean" ? self.configuracion("nulos", null, false, false) : nulos;

    this.valor_por_defecto = (valor_por_defecto, nulos) => (
        valor_por_defecto !== undefined && (self.nulos(nulos) || valor_por_defecto !== null) ? valor_por_defecto : 
        self.configuracion("valor_por_defecto", null, null, true)
    );

    this.coger_array = valor => valor && typeof valor == "object" && valor instanceof Array ? valor : [valor];

    this.coger_array_de_diccionarios = valor => valor && typeof valor == "object" ? valor instanceof Array ? valor : [valor] : [];

    this.coger_array_de_strings = valor => (
        valor && typeof valor == "object" && valor instanceof Array ? valor : [valor]
    ).filter(elemento => typeof elemento == "string" && elemento.trim()).map(elemento => elemento.trim());

    this.coger_claves = valor => self.coger_array_de_strings(valor).filter(elemento => elemento);

    this.configuracion = (claves, entradas, valor_por_defecto, nulos) => {

        const m = (claves = self.coger_array_de_strings(claves)).length;

        if(m){

            const l = (entradas = [].concat(
                self.coger_array_de_diccionarios(entradas), 
                [entradas, configuracion, configuracion_por_defecto]
            )).length;
            
            nulos = self.nulos(nulos);

            for(let i = 0; i < l; i ++)
                if(entradas[i] && typeof entradas[i] == "object")
                    for(let j = 0; j < m; j ++)
                        if(entradas[i][claves[j]] !== undefined && (nulos || entradas[i][claves[j]] !== null))
                            return entradas[i][claves[j]];
        };
        return self.valor_por_defecto(valor_por_defecto, nulos);
    };

    this.leer_archivo = (url, entradas) => {

        let terminado = false;
        const ajax = new XMLHttpRequest(), 
              callback = self.configuracion("callback", 
                  typeof entradas == "function" ? (entradas = {callback : entradas}) : 
                  entradas && typeof entradas == "object" ? entradas : 
              {}), 
              timeout = self.configuracion(["ajax_timeout", "timeout"], entradas), 
              terminar = codigo => (
                  !terminado && 
                  (terminado = true) && 
                  self.ejecutar(callback, ajax.responseText, ajax.status, ajax.readyState, codigo)
              ), 
              fecha = Date.now();

        ajax.open("get", url, true);
        ajax.timeout = timeout;
        ajax.onreadystatechange = () => {
            if(terminado)
                return;
            if(ajax.readyState == 4)
                terminar(
                    (ajax.status >= 200 && ajax.status < 300) || [301, 302, 304].includes(ajax.status) ? "OK" : 
                "HTTP_ERROR");
            else if(Date.now() - fecha > timeout)
                terminar("TIMEOUT_FORZADO");
        };
        ajax.send(null);
    
        ajax.onabort = () => terminar("ABORTADO");
        ajax.onerror = () => terminar("ERROR");
        ajax.ontimeout = () => terminar("TIMEOUT");

        return ajax;
    };

    const ejecutar_array_asincrono = (elementos, callback_parcial, callback_final, i) => {

        if(!elementos || i >= elementos.length){
            self.ejecutar(callback_final);
            return;
        };

        const terminar = () => ejecutar_array_asincrono(elementos, callback_parcial, callback_final, i + 1);

        if(!elementos[i]){
            terminar();
            return;
        };

        if(typeof elementos[i] == "object" && elementos[i] instanceof Array)
            ejecutar_array_asincrono(elementos[i], terminar, 0);
        else 
            self.ejecutar(callback_parcial, elementos[i], terminar);

    };

    this.ejecutar_array_asincrono = (elementos, callback_parcial, callback_final) => ejecutar_array_asincrono(
        typeof elementos == "object" && elementos instanceof Array ? elementos : [elementos], 
        callback_parcial, 
        callback_final, 
        0
    );

    this.procesar_json = (string, si, no) => {

        let json;

        try{
            json = JSON.parse(string);
        }catch(excepcion){};

        if(json){

            typeof si == "function" && si(json);

            return true;
        };
        
        typeof no == "function" && no(null);

        return false;
    };

    this.cargar_diccionarios = (entradas, callback_parcial, callback_final, i) => {

        if(!entradas || (i || (i = 0)) >= entradas.length){
            self.ejecutar(callback_final);
            return;
        };

        const terminar = () => self.cargar_diccionarios(entradas, callback_parcial, callback_final, i + 1);

        if(!entradas[i])
            terminar();
        else if(typeof entradas[i] == "string")
            self.procesar_json(entradas[i], json => self.cargar_diccionarios(self.coger_array(json), callback_parcial, terminar, 0), () => {
                self.leer_archivo(entradas[i], respuesta => self.procesar_json(respuesta, json => self.cargar_diccionarios(self.coger_array(json), callback_parcial, terminar, 0), terminar));
            });
        else if(typeof entradas[i] == "object"){
            if(entradas[i] instanceof Array)
                self.cargar_diccionarios(entradas[i], callback_parcial, terminar, 0);
            else 
                self.ejecutar(callback_parcial, entradas[i], terminar);
        }else 
            terminar();

    };

    this.configuracion_anadir = (entradas, sobreescribir, callback) => {
        typeof sobreescribir == "boolean" || (sobreescribir = configuracion_sobreescribir);
        self.cargar_diccionarios(self.coger_array(entradas), (nueva_configuracion, callback) => {
            for(const clave in nueva_configuracion)
                (sobreescribir || configuracion[clave] === undefined) && (configuracion[clave] = nueva_configuracion[clave]);
            self.ejecutar(callback);
        }, callback, 0);
    };

    const formatear_parametro = (clave, string) => ' ' + ([
        "onclick", "onmouseover", "onmouseout", "onmousedown", "onmouseup", "onmousemove"
    ].includes(clave) ? '' : 'data-') + clave.replace(/[^a-z0-9]+/g, '-') + '="' + ("" + string).replace(/"/g, "\\\"") + '"';

    this.string_variables = (string, variables, por_defecto) => {

        const l = (variables = self.coger_array_de_diccionarios(variables)).length, 
              valores_por_defecto = {
                  nombre_objeto : nombre_objeto, 
                  hash_self : hash_self
              };

        return (string || "" + string).replace(/(nombre_objeto)/g, "{$1}").replace(/\{{2}([^\{\}]+)\}{2}|\{([^\{\}]+)\}| +data-([^=\s]+)="\[{2}\]{2}"/g, (crudo, i18n, clave, parametros) => {
            if(i18n){

                let texto = i18n;
                const argumentos = i18n.match(/^(((?!(_descripcion)).)+)(_descripcion)?$/), 
                      [clave, descripcion] = argumentos ? [argumentos[1], argumentos[4]] : [i18n, null];

                for(let i = 0; i < l; i ++)
                    if(typeof variables[i][clave] == "string" && /^[a-z0-9_]+$/i.test(variables[i][clave])){
                        texto = variables[i][clave];
                        break;
                    };

                return self.i18n(texto + (descripcion || ""), variables);
            }if(parametros){
                for(let i = 0; i < l; i ++)
                    switch(typeof variables[i][parametros]){
                        case "object":

                            let html = '';

                            for(const clave in variables[i][parametros])
                                html += formatear_parametro(clave, variables[i][parametros][clave]);

                            return html;
                        case "string":
                        default:
                            if(variables[i][parametros] !== undefined)
                                return formatear_parametro(parametros, variables[i][parametros])
                    };
                return "";
            };
            for(let i = 0; i < l; i ++)
                if(variables[i][clave] !== undefined)
                    return variables[i][clave];
            if(valores_por_defecto[clave] !== undefined)
                return valores_por_defecto[clave];
            return por_defecto === undefined ? crudo : por_defecto;
        });
    };

    const i18n = (claves, por_defecto) => {

        const m = (claves = self.coger_array_de_strings(claves)).length, 
              idiomas = [idioma_por_defecto, idioma].concat(Object.keys(textos)), 
              l = idiomas.length;

        for(let i = 0; i < l; i ++)
            if(typeof idiomas[i] == "string" && textos[idiomas[i]])
                for(let j = 0; j < m; j ++)
                    if(textos[idiomas[i]][claves[j]] !== undefined)
                        return textos[idiomas[i]][claves[j]];
        return (
            por_defecto !== undefined ? por_defecto : 
            m ? claves[0] : 
        self.configuracion("texto_por_defecto"));
    };

    this.i18n = (claves, variables, por_defecto) => self.string_variables(i18n(claves, por_defecto), variables);

    this.i18n_anadir = (entradas, sobreescribir, callback) => {
        typeof sobreescribir == "boolean" || (sobreescribir = i18n_sobreescribir);
        self.cargar_diccionarios(self.coger_array_de_strings(entradas), (nuevos_textos, callback) => {
            for(const idioma in nuevos_textos){
                textos[idioma] || (textos[idioma] = {});
                if(nuevos_textos[idioma] && typeof nuevos_textos[idioma] == "object")
                    for(const clave in nuevos_textos[idioma])
                        (sobreescribir || textos[idioma][clave] === undefined) && (textos[idioma][clave] = nuevos_textos[idioma][clave]);
            };
            self.ejecutar(callback)
        }, callback, 0);
    };

    const terminal_tipos_anadir = (entradas, callback, i, clave) => {
        if(i > 10)
            return;

        if(!entradas || i >= entradas.length){
            typeof callback == "function" && callback();
            return;
        };

        const terminar = () => terminal_tipos_anadir(entradas, callback, i + 1, clave);

        if(!entradas[i]){
            terminar();
            return;
        };

        if(typeof entradas[i] == "string"){
            if(/^ *[a-z0-9]+ *$/i.test(entradas[i])){
                if(clave === undefined && !terminal_tipos.some((tipos, j) => tipos.includes(entradas[i]) && !isNaN(clave = j))){
                    clave = terminal_tipos.length;
                    terminal_tipos.push([entradas[i]]);
                }else 
                    !terminal_tipos[clave].includes(entradas[i]) && terminal_tipos[clave].push(entradas[i]);
                terminar();
                return;
            };

            const procesar = string => self.procesar_json(
                string, 
                json => terminal_tipos_anadir(json instanceof Array ? json : [json], terminar, 0), 
                terminar
            );

            if(/^(\[(.|[\r\n])+\])$/.test(entradas[i]))
                procesar(entradas[i]);
            else 
                self.leer_archivo(entradas[i], procesar);
        }else if(typeof entradas[i] == "object" && entradas[i] instanceof Array)
            terminal_tipos_anadir(entradas[i], terminar, 0);
        else 
            terminar();

    };

    this.es_dispositivo_movil = () => modo_dispositivo == "movil" || (modo_dispositivo != "escritorio" && es_movil);
    this.modo_gui_oscuro = () => modo_gui == "oscuro" || (modo_gui != "claro" && modo_gui_oscuro);

    this.terminal_tipos_anadir = (entradas, callback) => terminal_tipos_anadir(entradas, callback, 0);

    this.coger_id_tipo_terminal = tipo => {

        const l = terminal_tipos.length;

        if(typeof tipo == "string" && (tipo = tipo.trim().toLowerCase()))
            for(let i = 0; i < l; i ++)
                if(terminal_tipos[i].includes(tipo))
                    return i;
        return 0;
    };

    this.coger_tipo_terminal = tipo => terminal_tipos[self.coger_id_tipo_terminal(tipo)][0].toUpperCase();

    this.coger_traza = i => (new Error()).stack.replace(/^Error\s*?[\r\n]+/, "").trim().split(/[\r\n]+/).slice(1 + (i || 0)).map(linea => {

        const matches = linea.match(re_bloque_de_traza);

        return matches ? {
            archivo : matches[4] || matches[9], 
            metodo : matches[3] || matches[8] || "", 
            linea : Number(matches[7] || matches[10])
        } : null;
    }).filter(linea => linea);

    this.print = (tipo, mensaje, variables, i) => {

        const fecha = new Date(), 
              conjunto = {
                  tipo_crudo : tipo, 
                  tipo : self.coger_tipo_terminal(tipo), 
                  i18n : mensaje, 
                  ...(typeof variables == "object" ? variables : {}), 
                  ...(self.coger_traza(isNaN(i) ? 1 : i)[0] || {
                      linea : -1, 
                      metodo : "UNKNOWN", 
                      archivo : "UNKNOWN"
                  })
              };
        let mensaje_procesado;

        conjunto.line = conjunto.linea;
        conjunto.method = conjunto.metodo;
        conjunto.file = conjunto.archivo;

        ["year", "month", "date", "hours", "minutes", "seconds"].forEach(clave => {

            let k = clave.substring(0, 1); 
            const valor = fecha["get" + (clave == "year" ? "FullYear" : k.toUpperCase() + clave.substring(1))]();

            conjunto[clave == "date" ? "day" : clave] = valor;
            conjunto[clave == "minutes" ? k = "i" : k] = valor;
            conjunto[k + k] = ("00" + valor).slice(-2);

        });
        conjunto["yyyy"] = conjunto["year"];

        conjunto.mensaje = self.i18n(mensaje, conjunto);

        mensaje_procesado = self.string_variables(formato_terminal, conjunto);

        switch(conjunto.tipo){
            case " OK ":
                console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_ok_oscuro : terminal_ok_claro);
            break;
            case "INFO":
                console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_info_oscuro : terminal_info_claro);
            break;
            case "UNKN":
                console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_note_oscuro : terminal_note_claro);
            break;
            case "ERRO":
            case "FAIL":
            case "EXCE":
                console.error(mensaje_procesado);
            break;
            case "WARN":
                console.warn(mensaje_procesado);
            break;
            case "UNKN":
            default:
                console.log("%c" + mensaje_procesado, self.modo_gui_oscuro() ? terminal_unkn_oscuro : terminal_unkn_claro);
            break;
        };

    };

    this.hash = () => {

        let hash;
        const l = hash_alfabeto.length;

        do{
            hash = "";
            while((hash += hash_alfabeto[Math.random() * l >> 0]).length < hash_longitud);
        }while(
            hashes.includes(hash) || 
            /^[0-9]/.test(hash) || 
            document.querySelector("#" + hash + ",." + hash + ",[name=" + hash + "]")
        );
        hashes.push(hash);

        return hash;
    };

    const ejecutar_hilos_de_proceso = () => hilos_de_proceso.forEach(hilo => hilo && hilo());

    this.anadir_hilo_de_proceso = metodo => {
        if(typeof metodo != "function")
            return null;

        let i = 0;
        const l = hilos_de_proceso.length;

        for(; i < l; i ++)
            if(!hilos_de_proceso[i])
                break;

        hilos_de_proceso[i] = metodo;

        return i;
    };

    this.eliminar_hilo_de_proceso = i => !isNaN(i) && hilos_de_proceso[i] && (hilos_de_proceso[i] = null);

    this.precargar = (selector, callback) => {

        const terminar = elemento => {
            if(typeof callback == "function")
                try{
                    callback(elemento);
                }catch(exception){
                    console.error(exception);
                };
        };

        if(!selector)
            terminar(null);
        else if(selector.nodeName || selector.tagName)
            terminar(selector);
        else if(typeof selector == "string" && (selector = selector.trim())){

            let elemento;

            try{
                if(elemento = si_mismo.querySelector(selector)){
                    terminar(elemento);
                    return;
                };
            }catch(excepcion){
                terminar(null);
                return;
            };

            const fecha = Date.now(), 
                  precarga = self.anadir_hilo_de_proceso(() => {
                      if(elemento = si_mismo.querySelector(selector)){
                          self.eliminar_hilo_de_proceso(precarga);
                          terminar(elemento);
                      }else if(Date.now() - fecha > precarga_timeout){
                          self.eliminar_hilo_de_proceso(precarga);
                          terminar(null);
                      };
                  });

        }else 
            terminar(null);

    };

    this.coger_hash = () => hash_self || (hash_self = self.hash_self = mapeate.hash());

    this.establecer_si_mismo = elemento => (!si_mismo.constructor || si_mismo.constructor.name == "HTMLDocument") && (si_mismo = self.si_mismo = elemento);

    this.porcentuar = (x1, y1, x2, y2, cubrir) => {

        const r1 = x1 / y1, 
              r2 = x2 / y2;

        if((cubrir && r1 > r2) || (!cubrir && r1 < r2)){

            const x = x1 * y2 / y1;

            return [(x2 - x) / 2, 0, x, y2];
        };

        const y = y1 * x2 / x1;

        return [0, (y2 - y) / 2, x2, y];
    };

    this.consola = (texto, variables) => {

        si_mismo && (si_mismo.querySelector(".juego .consola section").appendChild(document.createElement("div")).innerText = self.i18n(texto, variables));
        self.print("test", texto, variables);

    };

    this.coger_padre = (elemento, objetivo, atributos) => {

        (atributos = self.coger_claves(atributos)).length || 
        (atributos = claves_padre_por_defecto);
        objetivo = objetivo.toLowerCase();

        while(elemento && elemento.hasAttribute && !atributos.some(atributo => elemento[atributo].toLowerCase() == objetivo) && (elemento = elemento.parentNode));
        
        return (elemento && elemento.hasAttribute && elemento) || null;
    };

    this.alerta = (mensaje, callback, datos) => base.abrir_dialogo(mensaje, datos, [
        {i18n : "aceptar", accion : () => {
            base.ocultar_dialogo();
            self.ejecutar(callback);
        }}
    ]);

    this.confirmar = (mensaje, si, no, datos) => base.abrir_dialogo(mensaje, datos, [
        {i18n : "aceptar", accion : () => {
            base.ocultar_dialogo();
            self.ejecutar(si);
        }}, 
        {i18n : "cancelar", accion : () => {
            base.ocultar_dialogo();
            self.ejecutar(no);
        }}
    ]);

    constructor();

};