201
Escuela Politécnica Superior de Linares UNIVERSIDAD DE JAÉN Escuela Politécnica Superior de Linares CREACIÓN DE TESTS INTERACTIVOS SCORM Alumno: Sergio Díaz Fuentes Tutor: Prof. D. Ildefonso Ruano Ruano Depto.: Ingeniería de Telecomunicación Septiembre, 2016

CREACIÓN DE TESTS INTERACTIVOS SCORMtauja.ujaen.es/bitstream/10953.1/3622/1/TFG_Díaz_Fuentes_Sergio.pdf · especificación de interoperabilidad de preguntas y tests, es decir, permite

  • Upload
    lytruc

  • View
    216

  • Download
    0

Embed Size (px)

Citation preview

Escu

ela

Polit

écni

ca S

uper

ior d

e Li

nare

s

UNIVERSIDAD DE JAÉN Escuela Politécnica Superior de Linares

Trabajo Fin de Grado

______

CREACIÓN DE TESTS INTERACTIVOS SCORM

Alumno: Sergio Díaz Fuentes Tutor: Prof. D. Ildefonso Ruano Ruano Depto.: Ingeniería de Telecomunicación

Septiembre, 2016

Junio, 2016

1

UNIVERSIDAD DE JAÉN

ESCUELA POLITÉCNICA SUPERIOR DE LINARES

Departamento de Ingeniería de Telecomunicación

CURSO ACADÉMICO

2015-2016

TRABAJO FIN DE GRADO

TÍTULO DEL TRABAJO

CREACIÓN DE TESTS INTERACTIVOS SCORM

Grado en Ingeniería Telemática

AUTOR: Sergio Díaz Fuentes

TUTOR: D. Ildefonso Ruano Ruano

2

1

1 ÍNDICE .......................................................................................................................2

2 RESUMEN ..................................................................................................................4 3 INTRODUCCIÓN ........................................................................................................6

3.1 JavaScript ............................................................................................................7 3.2 HTML ...................................................................................................................9

3.2.1 La estructura del documento HTML ............................................................10 3.3 CSS ...................................................................................................................11 3.4 DOM ..................................................................................................................12 3.5 XML ...................................................................................................................14 3.6 QTI ....................................................................................................................15 3.7 SCORM .............................................................................................................18

3.7.1 SCORM 1.1 ................................................................................................19 3.7.2 SCORM 1.2 ................................................................................................19 3.7.3 La versión actual: SCORM 2004 .................................................................20 3.7.4 Compatibilidad entre versiones ...................................................................20 3.7.5 ¿Qué significa SCORM?.............................................................................20 3.7.6 Contenido del paquete ................................................................................22 3.7.7 Run Time ....................................................................................................22 3.7.8 Secuenciación ............................................................................................22

4 OBJETIVOS .............................................................................................................24 5 MATERIALES Y MÉTODOS .....................................................................................26

5.1 JavaScript ..........................................................................................................26 5.2 Editores de texto ................................................................................................26 5.3 SCORM RTE .....................................................................................................27

5.3.1 Run-Time Environment (RTE) Management ...............................................28 5.3.2 Application Programming Interface (API) ....................................................29

5.4 SCO ...................................................................................................................33 5.5 Navegadores Web .............................................................................................35

5.5.1 Google Chrome ..........................................................................................35 5.5.2 Mozilla Firefox ............................................................................................35 5.5.3 Internet Explorer .........................................................................................36

5.6 Funcionamiento de las librerías JavaScript para creación de tests online ..........36

5.6.1 Análisis del banco de preguntas .................................................................38 5.6.2 Evaluación de las preguntas .......................................................................44 5.6.3 Interacción de las librerías con el LMS .......................................................46

3

5.7 Interacciones .....................................................................................................50 5.8 Módulos SCORM desarrollados .........................................................................53

5.8.1 Módulo SCORM 2004 .................................................................................53 5.8.2 Módulo SCORM 1.2 ....................................................................................57

5.9 Manual de usuario y mantenimiento ..................................................................58 6 RESULTADOS Y DISCUSIÓN .................................................................................63 7 CONCLUSIONES .....................................................................................................71 8 ANEXOS...................................................................................................................74

8.1 Índice de figuras ................................................................................................74 8.2 Siglas .................................................................................................................76 8.3 Librería JavaScript creaPreguntas2004.js..........................................................76 8.4 Librería JavaScript creaPreguntasSinCom.js ................................................... 114 8.5 Librería JavaScript creaPreguntas12.js ........................................................... 146 8.6 Librería JavaScript principal.js ......................................................................... 188 8.7 Hoja de estilo estiloIndex.css ........................................................................... 189 8.8 Hoja de estilo estilo.css ................................................................................... 193

9 REFERENCIAS BIBLIOGRÁFICAS ........................................................................ 199

4

El e-learning o docencia electrónica consiste en el uso de Tecnologías de la

Información y Comunicaciones (TIC), principalmente Internet, aplicadas a la enseñanza. Puede presentarse como apoyo a clases presenciales en su modalidad mixta o b-learning

(de blended learning) o totalmente a distancia (e-learning puro). En cualquiera de estos

casos el elemento TIC más utilizado en la docencia universitaria son los LMS (Learning

Management System, Sistema de Gestión de Aprendizaje), prácticamente todas las

universidades españolas cuentan con un LMS institucional que sirve de apoyo a la

docencia en cualquiera de sus modalidades (ESPAÑOLAS, 2015).

En este Trabajo Fin de Grado (TFG) se han desarrollado una serie de herramientas que facilitan la creación de tests online y unos documentos web en forma

de contenidos SCORM (Sharable Content Object Reference Model) que muestran su uso

(Ostyn, 2007). SCORM es el estándar de facto de contenido de e-learning ya que es

soportado por la mayoría de los LMS del mercado. Los tests creados con estas

herramientas pueden integrarse en un LMS para interactuar con el mismo o presentarse de forma independiente en páginas Web HTML (Hypertext Markup Language) (“HTML

W3C,” 02/08/2016.).

Las herramientas desarrolladas se han implementado como cuatro librerías JavaScript (Melorose, Perroy, & Careas, 2015) que permiten crear tests interactivos, una

de ellas se utiliza mediante la asignación de sus argumentos para gestionar el uso de las

otras tres, las cuales se han diseñado para ser empleadas en distintos entornos:

Interaccionando con un LMS estableciendo comunicaciones mediante la versión

1.2 del estándar SCORM.

Interaccionando con un LMS estableciendo comunicaciones mediante la versión

2004 edición 4 del estándar SCORM.

Sin interaccionar con un LMS.

Las librerías desarrolladas han sido diseñadas para poder utilizar ficheros XML

(eXtensible Markup Language) (W3C, 02/08/2016.) que cumplan el estándar QTI (Question & Test Interoperability) (Tobergte & Curtis, 2013). QTI constituye una

especificación de interoperabilidad de preguntas y tests, es decir, permite definir tests y

preguntas de test en e-learning. De hecho constituyen el estándar de facto en evaluación

online, es utilizado por múltiples aplicaciones y, por supuesto, por los LMS. De este modo se pueden usar los ficheros QTI de bancos de preguntas o test que se hayan creado y

exportado en LMS para crear tests online mediante las herramientas desarrolladas en el

ámbito de este TFG. Las cuatro librerías, aparecen en el apartado Anexos.

5

De este modo, estas herramientas poseen una serie de características que las

hacen muy adecuadas tanto para su utilización en entornos educativos como para

entornos de evaluación más personales, que no necesiten interactuar con ningún LMS.

Entre ellas, podemos destacar las siguientes:

Cómoda portabilidad entre distintos Sistemas de Gestión de Aprendizaje al

trabajar con estándares.

Permite su reusabilidad entre distintos LMSs por el mismo motivo anterior.

Posibilidad de ser incluidas en módulos de aprendizaje SCORM, así como de

interactuar con el LMS, de tal forma, que quede en la plataforma reflejada toda

la actuación del alumno.

Posibilidad de ser incluidas en plataformas que utilicen la versión 1.2 de

SCORM, la 2004 edición 4 o ambas.

Capacidad de personalizar los tests mediante distintos parámetros, como el

número de preguntas o la aleatoriedad de las mismas entre otras.

Las dos librerías que se comunican con el LMS permiten la personalización de los tests y de la experiencia a través del módulo, mediante la identidad de los

alumnos y las puntuaciones obtenidas a lo largo del módulo, interactuando con

el LMS.

Además de las librerías JavaScript, los dos módulos SCORM constituyen

ejemplos de uso de las librerías en LMS mostrando algunas de las distintas posibilidades

de uso que se les puede dar:

Test sin seguimiento en el LMS.

Test de evaluación con seguimiento en el LMS.

Test de autoevaluación flexible con posibilidad de elegir sus parámetros.

6

Vivimos en una época de información y conocimientos llena de oportunidades,

donde las TIC juegan un papel importante y que han evolucionado de tal manera que han

cambiado la forma en que se realizan muchas actividades cotidianas de la sociedad

moderna. Esto ha originado lo que se conoce como sociedad de la información.

En ella, el acceso a la información es cada vez más libre y amplio, elimina

barreras geográficas, de tiempo y espacio, y favorece así, una comunicación ubicua y

asíncrona. Y es de este modo, como surge otra sociedad, la sociedad del conocimiento.

Esta sociedad está ligada a las actividades económicas, sociales y culturales, y propone

cambios especialmente en instituciones educativas en la forma de utilizar las tecnologías

en la enseñanza y en el aprendizaje de sus alumnos, creando así un nuevo conocimiento. En palabras de Fco. Javier Quiroz Waldez, “el siglo XXI se presenta asomando el

rostro de un nuevo paradigma de sociedad, un modelo donde la información entendida

como conocimiento acumulado de forma comunicable aparece como el cimiento del

desarrollo económico, político y social. El proceso de transformación hacia este modelo –

se afirma– es irreversible. El avance tecnológico faculta al ser humano para hacer

provecho de datos, información y conocimiento en formas, modos o maneras sin

precedentes, propiciando un intercambio científico, cultural y técnico a escala mundial,

pasando sobre las barreras geográficas, las divisiones políticas y las de tiempo”. (Quiroz,

2003). Es en este escenario, en el que se enmarca este TFG. Un escenario de

conocimiento libre, de interoperabilidad entre sistemas, de presentación de la información

en múltiples dispositivos y formatos y en el que los métodos de enseñanza han

evolucionado. Los alumnos ya no solo aprenden en el aula o a través de los libros, ahora

además tienen a su disposición una gran herramienta como es Internet para afianzar sus

conocimientos y solventar las posibles dudas que les surjan, aumentando así su

motivación.

Y es aquí, donde hay que mencionar la función realizada por el aprendizaje

electrónico o e-learning, un término que hace referencia a la educación a distancia

totalmente virtual en la que los estudiantes pueden interactuar con el profesor, así como

con el objeto de estudio en cuestión, utilizando un computador y un software cualquiera

como soporte de los procesos de enseñanza-aprendizaje.

Entre las ventajas brindadas por la formación online nos encontramos con:

Eliminación de barreras espaciales y temporales.

Actualización constante de los contenidos.

Reducción de costos.

7

El instrumento usado para compartir contenidos de aprendizaje electrónico es

Internet o intranets. Con la ayuda de un laboratorio web, se pueden mostrar textos,

imágenes y gráficos en un navegador web.

Pero para que los usuarios puedan hacer uso de estos contenidos, el laboratorio

ha de estar en un entorno software y hardware adecuado. Este entorno, creado para

automatizar y gestionar el desarrollo de actividades didácticas se conoce como Sistema

de Gestión de Aprendizaje (SGA), en inglés LMS.

Existe un gran número de plataformas virtuales, tanto propietarias como de código

abierto. La plataforma de licencia libre Moodle sería un ejemplo de ellas, así como ILIAS,

utilizada por la Universidad de Jaén.

Una vez que se añade el laboratorio al LMS, se permite el acceso solo a los

usuarios autorizados. Esto facilita el seguimiento en el aprendizaje de los alumnos por

parte del profesorado, así como evaluar sus conocimientos y competencias.

La posibilidad de ofrecer a la Universidad una serie de herramientas para la

creación de test interactivos de forma dinámica y contribuir de esta manera con el

aprendizaje electrónico junto con el trabajar con JavaScript, con el lenguaje de marcas de

hipertexto (HTML) y con los estándares SCORM y QTI han sido las principales

motivaciones para la realización de este TFG.

Lo que se ha logrado con este trabajo ha sido la obtención de unas librerías

capaces de mostrar hasta cuatro tipos diferentes de preguntas, que serían las de opción

múltiple respuesta única, opción múltiple respuesta múltiple, de tipo numérica y de

rellenar huecos, a partir de un fichero XML en el que se encuentra el banco de preguntas

relativas al tema a evaluar. Además, este fichero XML estaría estructurado siguiendo el

estándar QTI, ampliamente utilizado por los SGAs, como ILIAS por ejemplo.

Además, para la creación de los tests, se le da la posibilidad al administrador o

profesor, de configurar ciertos parámetros de los mismos, como lo serían el banco de preguntas a utilizar, el número de preguntas del que va a constar el test, la aleatoriedad

de las mismas, la aleatoriedad de sus opciones en caso de tener, un tiempo para la realización del test y si se va a comunicar o no con el Sistema de Gestión de Aprendizaje.

3.1 JavaScript

JavaScript es el lenguaje de programación de la Web. La inmensa mayoría de

sitios web actuales utilizan JavaScript, y todos los navegadores web modernos -en

ordenadores de sobremesa, videoconsolas, tabletas y teléfonos inteligentes- incluyen

8

intérpretes de JavaScript haciéndolo el lenguaje de programación más omnipresente en

la historia.

JavaScript es parte del trío de tecnologías que todos los desarrolladores web deben aprender: HTML para especificar el contenido de las páginas web, Cascading

Style Sheets (CSS) para especificar la presentación de las páginas web y JavaScript para

especificar el comportamiento de las páginas web. (Melorose et al., 2015).

Surgió de la necesidad de crear un lenguaje de programación que se ejecutara en

el navegador del usuario ante la creciente inclusión de formularios en las primeras

aplicaciones web allá por principios de los años 90 y la lentitud de las conexiones

existentes por entonces.

Así, si se producían errores al rellenar el formulario, no había que esperar hasta

que el servidor respondiera y mostrara de nuevo el formulario indicándole los errores que

había cometido.

De este modo, y tras las implementaciones de varias empresas, para evitar una

guerra de tecnologías, se decidió que lo más adecuado era estandarizar el lenguaje

JavaScript. De esta forma, en 1997 se envió la especificación por parte de Netscape al organismo European Computer Manufacturers Association (ECMA).

El primer estándar se denominó ECMA-262, y allí se definió el lenguaje

ECMAScript. JavaScript de esta forma, no es más que la implementación que hizo

Netscape del estándar ECMAScript.

Se trata de un lenguaje de propósito general robusto y eficiente. Un lenguaje de

programación interpretado (no requiere compilación) y utilizado principalmente en su

forma del lado cliente para crear aplicaciones cliente. Su sintaxis es similar a Java y al

lenguaje C. Se puede insertar en páginas HTML o bien ser agregado como una

referencia a un archivo externo.

Mejora la apariencia con la inclusión de gráficos y permitiendo la interacción con el

usuario. Además, se pueden implementar cálculo de funciones y permite la validación de

formularios como se ha comentado anteriormente.

JavaScript se puede incluir en el contenido HTML o puede ser usado para modificar la presentación (CSS) o el comportamiento (event handling) de las páginas

webs. Un manejador de eventos (o event handler) es una función JavaScript que

registramos con el navegador y que el navegador invoca cuando ocurren algunos tipos

de eventos. El evento en cuestión puede ser un clic de ratón o la pulsación de una tecla.

Un manejador de eventos también puede ser invocado cuando el navegador termina de

cargar un documento, cuando el usuario cambia el tamaño de la ventana del navegador o

cuando el usuario introduce datos en un elemento de un formulario HTML. (Melorose et

al., 2015).

9

La manera más sencilla de definir manejadores de eventos es mediante los

atributos HTML que comienzan con “on”. El manejador “onclick” es particularmente útil

cuando estás desarrollando programas de prueba. Este haría referencia a hacer una

pulsación o clic al botón del ratón sobre un elemento de la página.

Por otro lado, las interfaces de programación de interfaces (APIs) usadas

normalmente pueden ser algo complicadas y están llenas de incompatibilidades con los

navegadores. Por estas razones, muchos o la mayoría de los programadores de

JavaScript del lado cliente eligen utilizar una librería del lado cliente o framework para

simplificar el trabajo de la programación.

La librería más popular en este sentido sería jQuery. Esta define un API inteligente

y fácil de usar para acceder al contenido HTML, modificar la presentación y el

comportamiento. Esta librería ha sido probada y trabaja en la mayoría de navegadores,

incluidos los antiguos como Internet Explorer 6.

En resumen, se pueden manejar distintos objetos como ventanas, documentos,

botones, tablas, etcétera que tienen asociados distintos parámetros o atributos cuyo valor

puede ser modificado a través de las sentencias.

3.2 HTML

El lenguaje de marcas de hipertexto comúnmente abreviado como HTML, se basa en el metalenguaje SGML (Standard Generalized Markup Language) y es el formato de

los documentos de la World Wide Web (WWW). El World Wide Web Consortium (W3C)

es la organización que desarrolla los estándares para normalizar el desarrollo y la

expansión de la web y la que publica las especificaciones relativas al lenguaje HTML.

HTML fue concebido como un lenguaje para el intercambio de documentos

científicos y técnicos adaptado para su uso por no especialistas en tratamiento de

documentos. HTML resolvió el problema de la complejidad de SGML sirviéndose de un

reducido conjunto de etiquetas estructurales y semánticas apropiadas para la realización

de documentos relativamente simples. Pero, además de simplificar la estructura de los

documentos, HTML soportaba el hipertexto. El lenguaje HTML nace en 1991 de manos de Tim Bernes-Lee del CERN (Conseil

Européen pour la Recherche Nucléaire) como un sistema hipertexto con el único objetivo

de servir como medio de transmisión de información entre los científicos que se

ocupaban de la Física de alta energía, como parte de la iniciativa WWW. Así pues, HTML

tuvo lugar a la par que el origen de la Web, ya que se trata del lenguaje que sirve para

crear páginas web.

10

HTML es el lenguaje de marcado estándar utilizado para crear páginas web. Junto

con CSS y JavaScript, HTML es una tecnología fundamental utilizada para crear páginas

web así como para crear interfaces de usuario para aplicaciones móviles y web.

Los navegadores web pueden leer los archivos HTML y traducirlos a páginas web

visibles o audibles. HTML describe la estructura de un sitio web semánticamente y antes

del advenimiento de CSS, incluía señales para la presentación o apariencia del

documento, por lo que es un lenguaje de marcas, en lugar de un lenguaje de

programación.

Los elementos HTML forman los pilares de las páginas HTML. HTML permite que

tanto las imágenes como otros objetos sean incrustados y puedan ser usados para crear

formularios interactivos. Proporciona un medio para crear documentos estructurados al

indicar la semántica estructural del texto con encabezados, párrafos, listas, enlaces, citas

y otros ítems.

Los elementos HTML son delineados por las etiquetas, escritas usando paréntesis

angulares (<>). Los navegadores no muestran las etiquetas HTML, pero las utilizan para

interpretar el contenido de la página.

HTML puede incrustar scripts escritos en lenguajes tales como JavaScript que

afecten al comportamiento de las páginas web HTML. Las marcas HTML pueden además

referirse al navegador como hojas de estilo en cascada (CSS) para definir el aspecto y el

diseño del texto y otros ítems. El W3C, encargado de mantener los estándares de HTML

y de CSS, ha fomentado el uso de CSS para la presentación de los documentos desde

1997.

3.2.1 La estructura del documento HTML

Un documento HTML comienza con la etiqueta <html> y termina con </html>.

Dentro del documento hay dos zonas principales: el encabezamiento, delimitado

por las marcas <head> y </head> que sirve para definir algunos valores válidos para todo

el documento, y el cuerpo, delimitado por las etiquetas <body>, donde reside la

información del documento. Existen muchos otros elementos que se engloban dentro del

encabezamiento pero para la estructura básica del lenguaje HTML en su nivel básico no

son necesarios.

El cuerpo de un documento HTML contiene el texto, imágenes, etc. que con la

presentación y los efectos que se decidan, se presentarán ante el usuario. Dentro del

cuerpo se puede aplicar una serie de efectos a través de diferentes etiquetas.(“Hipertexto

HTML,” 29/07/2016.).

Así pues, la figura 1 muestra la estructura de un documento HTML:

11

Figura 1: Estructura básica documento HTML. Fuente:http://www.hipertexto.info/documentos/html.htm

3.3 CSS

Las hojas de estilo en cascada son un mecanismo simple para añadir estilo (por

ejemplo, fuentes, colores, espacios) a los documentos web. (“CSS W3C,” 30/07/2016.).

Esta separación entre la estructura y la presentación es muy importante, ya que

permite que sólo modificando las hojas de estilo el aspecto de una página web cambie

completamente. Esto hace posible que los usuarios puedan utilizar sus propias hojas de

estilo personalizadas.

De nuevo, es el W3C el encargado de definir las especificaciones del estándar

CSS. Más tarde, son los navegadores los que intentan implementar todas esas

especificaciones, para que las páginas web se vean igual en todos ellos. En este sentido,

y dado que cada navegador puede implementar el estándar en un porcentaje distinto, se

recomienda el realizar las páginas web siguiendo el estándar CSS y no las

especificaciones de un navegador determinado. Además, se ha de intentar mantener

nuestro navegador lo más actualizado posible, ya que a medida que el navegador va

cumpliendo con el estándar, va añadiendo nuevas funcionalidades.

De este estándar, se han realizado varias especificaciones, generalmente

añadiendo funciones al anterior. Así, la primera especificación oficial de CSS fue CSS1 y

se publicó en 1995 y se abandonó en abril de 2008. Entre las funcionalidades que ofrecía

nos encontramos con las siguientes:

Propiedades de la fuente, como tipo, énfasis, tamaño…

Color del texto, bordes o fondos.

Atributos del texto, como letras, líneas, espaciado entre las palabras…

La especificación CSS2 fue publicada como recomendación en mayo de 1998

también por la W3C y se abandonó en abril de 2008. Amplió las funcionalidades de CSS1

ofreciendo entre otras las siguientes:

Las funcionalidades propias de las capas div como posicionamiento

relativo/absoluto/fijo, niveles, etcétera.

Soporte para las hojas de estilo auditivas.

12

Sombras, texto bidireccional, etcétera.

La primera revisión de CSS2, comúnmente conocida como CSS 2.1, corrige

algunos errores encontrados en CSS2, elimina algunas funcionalidades poco soportadas

en los navegadores y añade alguna nueva. Después de ser propuesta como candidata y

ser rechazada varias veces, finalmente fue publicada como recomendación oficial en

junio de 2011.

La última especificación hasta la fecha, sería CSS3 y está dividida en varios

documentos separados, llamados módulos. Cada módulo añade nuevas funcionalidades

a las definidas en CSS2, de forma que se conservan las anteriores para preservar la

compatibilidad. Debido a su modularización, diferentes módulos pueden encontrarse en

distintos estados de desarrollo. A fecha de noviembre de 2011, existen alrededor de

cincuenta módulos publicados, tres de los mismos se convirtieron en recomendaciones

oficiales de la W3C en 2011.

Una vez repasadas las distintas especificaciones de este estándar, pasamos a

comentar las ventajas que presenta:

Control centralizado de la presentación de un sitio web completo con lo

que se agiliza de forma considerable la actualización del mismo.

Optimización del ancho de banda de la conexión, pues un sólo archivo

CSS puede servir para una multitud de documentos.

Mejora en la accesibilidad del documento, debido a que con el uso de

hojas de estilo se evitan antiguas prácticas necesarias para el control del

diseño y que iban en perjuicio de ciertos usos de los documentos, por

parte de navegadores orientados a personas con algunas limitaciones

sensoriales. (“CSS Wiki,” 30/07/2016.).

3.4 DOM

El Modelo de Objeto del Documento (DOM) es un estándar del W3C. El DOM

define un estándar para el acceso a documentos.

“El DOM es una interfaz de plataforma y leguaje neutro que permite a los

programas y scripts acceder y actualizar el contenido, la estructura y el estilo de un

documento de forma dinámica”. (“https://www.w3.org/DOM/#what,” 29/07/16.).

El estándar W3C DOM está separado en tres partes diferentes:

Núcleo DOM – modelo estándar para todos los tipos de documentos.

XML DOM – modelo estándar para documentos XML.

HTML DOM – modelo estándar para los documentos HTML.

13

El HTML DOM es el modelo de objetos y la interfaz de programación estándar

para los documentos HTML. Este define:

Los elementos HTML como objetos.

Las propiedades de todos los elementos HTML.

Los métodos para acceder a todos los elementos HTML.

Los eventos para todos los elementos HTML.

Cuando una página web se carga, el navegador crea un modelo de objetos del

documento de la página. La figura 2 muestra el árbol de objetos del HTML DOM:

Figura 2: Árbol de objetos HTML DOM. Fuente: http://www.w3schools.com/js/js_htmldom.asp

Con el modelo de objetos, JavaScript consigue todo el poder que necesita para

crear HTML de forma dinámica:

JavaScript puede cambiar todos los elementos HTML de la página.

JavaScript puede cambiar todos los atributos HTML de la página.

JavaScript puede cambiar todos los estilos CSS de la página.

JavaScript puede eliminar elementos y atributos HTML ya existentes.

JavaScript puede añadir nuevos elementos y atributos HTML.

JavaScript puede reaccionar a todos los eventos HTML existentes en la

página.

JavaScript puede crear nuevos eventos HTML en la página.

En otras palabras, el HTML DOM es un estándar para obtener, cambiar, añadir, o

eliminar elementos HTML. (“http://www.w3schools.com/js/js_htmldom.aspitle,”

29/07/2016.).

14

3.5 XML

El lenguaje extensible de marcas XML, describe una clase de objetos de datos

llamados documentos XML y parcialmente describe el comportamiento de los programas

informáticos que los procesan. XML es un perfil de aplicación o una forma restringida de SGML, el estándar del lenguaje de marcado generalizado (Standard Generalized Markup

Language) [ISO 8879]. Por construcción, los documentos XML son conformes a los

documentos SGML. (“XML W3C,” 31/07/2016.).

En la figura 3 podemos ver cómo SGML engloba tanto a XML como a HTML.

Figura 3: Gráfico estándares. Fuente: http://www.cyta.com.ar/elearn/editor_digital/curso_archivos/m4_a2_lectura/xml_0.htm

Los documentos XML están compuestos de unidades de almacenamiento

llamadas entidades, que contienen datos, ya sean analizados o no. Los datos analizados

se componen de caracteres, algunos de los cuales forman datos de caracteres y algunos

otros componen las marcas.

Las marcas codifican la descripción del documento y la estructura lógica. XML

proporciona un mecanismo para imponer limitaciones al diseño del almacenamiento y a la

estructura lógica. Un ejemplo de fichero XML puede verse en la figura 4.

15

Figura 4: Ejemplo fichero XML. Fuente: http://social.technet.microsoft.com/wiki/contents/articles/27147.windows-phone-how-to-

create-a-data-class-from-an-xml-document.aspx

Los objetivos que se cubren con el diseño de XML son los siguientes:

XML será directamente utilizable a través de Internet.

XML debe soportar una amplia variedad de aplicaciones.

XML debe ser compatible con SGML.

Debe ser fácil escribir programas que procesen documentos XML.

El número de características opcionales en XML debe mantenerse al

mínimo absoluto, idealmente cero.

Los documentos XML deben ser legibles por humanos y ser

razonablemente claros.

El diseño de XML debe ser formal y conciso.

Los documentos XML deben ser fáciles de crear.

Así pues, tenemos un estándar con las siguientes características: (“Manual de

XML,” 31/07/16.).

Extensibilidad.

Estructura.

Validación.

Basado en texto.

Orientado a los contenidos no a la presentación.

Las etiquetas se definen para crear los documentos, no tienen un

significado preestablecido.

3.6 QTI

La Especificación de Interoperabilidad de Preguntas y Pruebas de IMS (IMS/QTI

por sus siglas en inglés) define un formato estándar para la representación de contenidos

16

y resultados de evaluaciones educativas, soportando el intercambio de este material

entre sistemas de creación y de visualización, repositorios y otros sistemas de gestión del

aprendizaje (LMS por sus siglas en inglés). Permite la creación y entrega de materiales

de pruebas en múltiples sistemas de forma intercambiable. Es diseñado para facilitar

la interoperabilidad entre sistemas. (Joint Information Systems Committee (JISC), 2007).

Las especificaciones de las preguntas y exámenes QTI describen la estructura

básica para la representación de las preguntas (item) y los exámenes (assessment). Esta

especificación capacita el intercambio de preguntas y exámenes entre sistemas para la

gestión del aprendizaje (Learning Management Systems), al igual que los estándares

para los contenidos desarrollados por este mismo consorcio. La especificación del QTI

está definida en lenguaje XML para que pueda ser adoptado lo más ampliamente posible.

La especificación se puede extender y personalizar para ser implantado en sistemas

propietarios. (Tobergte & Curtis, 2013).

La primera versión de la especificación V0.5 apareció en marzo de 1999, y la

primera versión no beta V 1.0 es de febrero del 2000. Actualmente se dispone de la

versión 1.2 de febrero del 2002.

Los objetivos principales del grupo de trabajo del IMS para las QTI son:

Capacidad de proporcionar exámenes o bancos de preguntas a los

usuarios de los entornos virtuales de enseñanza.

La capacidad de usar exámenes y bancos de preguntas procedentes de

distintas fuentes no vinculadas a un solo vendedor de sistemas virtuales

de enseñanza (VLE).

Soporte para las herramientas que permitan crear nuevos exámenes y

preguntas.

Capacidad de generar los informes de los resultados de las pruebas de

una forma consistente.

Se presenta a continuación en la figura 5 el núcleo de la estructura adoptada de

forma esquemática:

17

Figura 5: Esquema de la estructura básica adoptada por el QTI IMS. Fuente: (Tobergte &

Curtis, 2013).

Los ítems son las unidades mínimas que pueden ser intercambiadas usando QTI.

Los ítems no pueden estar compuestos por otros ítems.

Además, un archivo QTI-XML puede contener varias secciones. Las secciones

pueden contener más secciones o ítems y realizan la función de agrupar las preguntas. Un archivo QTI-XML también puede incluir más de un assessment o evaluación.

Cada evaluación debe contener al menos una sección. No puede anexarse ítems

directamente a la evaluación.

La figura 6 muestra de forma gráfica el diagrama de casos de uso, en el que

aparecen los posibles actores del sistema, junto con los procesos y sistemas.

Figura 6: Casos de uso del sistema de exámenes. Fuente: (Tobergte & Curtis, 2013).

Los principales componentes del sistema de exámenes son:

Authoring system. Es el proceso que soporta la creación y edición de

exámenes, secciones y preguntas (ASIs).

18

Assessment engine: Es el proceso que soporta la evaluación o

calificación de las respuestas de forma que genere calificaciones y

consejos o refuerzos de estudio.

Learning management system: Sistema o proceso responsable de la

gestión completa de la arquitectura de enseñanza.

Candidate and Repository. Se trata de una base de datos con las

especificaciones para el candidato (alumno).

ASI repository. Se trata de una base de datos local de exámenes

secciones y preguntas.

External ASI repository. Se trata de una base de datos externa que debe

ser importada haciendo uso de las especificaciones del QTI.

En cuanto a las preguntas y las respuestas, en primer lugar hay que aclarar que

cada ítem se interpreta como un bloque fundamental que contiene una o más preguntas y

respuestas.

Básicamente, se distinguen tres tipos de respuestas: simples, compuestas y

definidas por el fabricante. Además, dentro de las simples y compuestas, se pueden

diferenciar dos tipos en función del control que se haga del tiempo.

3.7 SCORM

Desde hace mucho tiempo, uno de los mayores problemas relacionados con el e-

learning ha sido la creación y el desarrollo de contenidos e-learning de calidad. Los

contenidos de e-learning son en realidad software, por lo que su desarrollo ha estado

ligado a los mismos problemas que otros proyectos software.

Cada sistema de gestión de aprendizaje tenía distintos entornos de envío y

seguimiento del contenido de aprendizaje, por lo que si una empresa quería renovar su

sistema o cambiar de distribuidor, esto a menudo significaba abandonar un contenido

caro y empezar de nuevo. Del mismo modo, si un distribuidor de contenido quería

distribuir ampliamente su contenido, esto era bastante caro, ya que había que

acomodarlo a una versión distinta para cada sistema de gestión de aprendizaje.

A medida que los requerimientos cambiaron y coincidieron en empresas y en

agencias de gobierno, se hizo evidente la importancia de realizar módulos de contenido

reutilizable.

Fue entonces cuando hubo una fuerte motivación por parte del mercado para

crear contenido que fuese duradero, portable y reutilizable entre distintos sistemas, en

otras palabras, que el contenido fuera interoperable.

19

Con contenido interoperable, los desarrolladores de contenido ganan porque el

mismo contenido puede funcionar en diferentes sistemas sin modificación. Los

vendedores de sistemas de gestión ganan porque pueden centrarse en los aspectos de

gestión del aprendizaje, sin tener que estar adaptando constantemente el entorno de

entrega a varias librerías de contenido.

Empresas y agencias que utilizan los sistemas de gestión de aprendizaje y el

contenido ganan porque en lugar de perder dinero y tiempo en la integración de las

diferentes bibliotecas del contenido, estas se pueden mezclar fácilmente con su propio

contenido personalizado usando el mismo entorno de entrega. (Ostyn, 2007).

De este modo fue como, hace ya varios años, la oficina de tecnología de la casa blanca, el departamento de defensa y el departamento de trabajo lanzaron la Advanced

Distributed Learning Initiative (ADL) o iniciativa de aprendizaje distribuido avanzada en

los Estados Unidos. Uno de los primeros proyectos de la ADL fue el de crear ciertas

especificaciones o normas para el contenido electrónico de aprendizaje. El resultado fue

un conjunto de libros, cada uno de los cuales, describía un aspecto diferente de la solución. Al resultado se le llamó Sharable Content Object Reference Model, o SCORM.

Una posible traducción al castellano sería “modelo de referencia para un objeto de

contenido compartible”.

El SCORM nació para tomar lo mejor de los primeros esfuerzos, las

especificaciones y normas, y alcanzar los objetivos de durabilidad, portabilidad,

reutilización, interoperabilidad y accesibilidad al contenido.

Muchas personas de la industria del e-learning estaban implicados en la génesis

de SCORM, junto con el equipo técnico financiado por la ADL. Cada versión de SCORM ha sido probada en eventos “Plugfest”.

3.7.1 SCORM 1.1

La primera versión de SCORM fue un globo de ensayo, destinado a descubrir

problemas no resueltos. Implementaciones de prueba revelaron que SCORM 1.1 era

menos funcional de lo esperado, y la interoperabilidad fue en su mayor parte fruto del

azar. Las lecciones de SCORM 1.1 fueron utilizadas para las versiones posteriores.

3.7.2 SCORM 1.2

La primera versión “real” de la era SCORM fue SCORM 1.2. Esta fue la primera

versión para la cual había disponible un conjunto de pruebas, y por lo tanto, fue la

primera versión para la que la conformidad puedo ser verificada.

SCORM 1.2 demostró que el contenido podía ser portátil e interoperable. Alguna

cuestión pendiente fue el resultado de que no se ajustara a SCORM, o el resultado de

20

una mala interpretación de una característica SCORM, hecho que aún no se había

contemplado.

3.7.3 La versión actual: SCORM 2004

SCORM 2004 mejora significativamente a SCORM 1.2, eliminando aún más

ambigüedades en la especificación, y haciendo SCORM operable con robustos

estándares del Instituto de Ingenieros eléctricos y electrónicos (IEEE). El API ahora es

compatible con la gran variedad de lenguajes apoyados por el ECMAScript.

Además de mejorar la versión 1.2, SCORM 2004 incluye las funciones opcionales

de secuenciación y navegación. La adición de la secuenciación es un hito importante de

funcionamiento. SCORM 1.2 trataba sobre la portabilidad del contenido, pero dejó en

manos del alumno elegir qué parte del contenido se ejecutara.

SCORM 2004 agrega la capacidad de presentar paquetes de contenido al alumno

mediante la secuenciación guiada o adaptativa.

3.7.4 Compatibilidad entre versiones

Muchos proveedores de sistemas de gestión de aprendizaje continuarán

apoyando el contenido SCORM 1.2 durante mucho tiempo, junto con SCORM 2004. Hay

herramientas disponibles o se pueden construir relativamente con facilidad para convertir

los paquetes de contenido SCORM 1.2 a SCORM 2004.

Es posible lanzar objetos de contenido SCORM 1.2 sin modificaciones en un

entorno SCORM 2004 mediante el uso de un “contenedor” (wrapper) proporcionado por

la ADL.

También es posible poner en marcha objetos de contenido SCORM 2004 en un

entorno SCORM 1.2 a través de otro contenedor. Sin embargo, en ese caso, y

dependiendo del contenido, algunos datos de seguimiento o de sesión se pueden perder

debido a que SCORM 1.2 no soporta el modelo de datos completo del IEEE utilizado por

SCORM 2004. Obviamente, los entornos SCORM 1.2 no son compatibles con la

secuenciación de SCORM 2004.

Otro enfoque sería el de crear objetos de contenido que puedan trabajar tanto en

SCORM 1.2 como en SCORM 2004, con una degradación importante si el entorno es

SCORM 1.2. Esta aproximación es por supuesto más cara. (Ostyn, 2007).

3.7.5 ¿Qué significa SCORM?

SCORM es un conjunto de normas técnicas para los productos software de e-

learning. SCORM explica a los programadores cómo escribir el código para que pueda

encajar bien con otro software de e-learning. Es el estándar de facto para la

21

interoperabilidad en e-learning. En concreto, SCORM gobierna cómo el contenido de

aprendizaje online y los LMSs se comunican entre sí.

Un contenido de aprendizaje que cumpla con las normas SCORM deberá cumplir

los siguientes requisitos:

Durabilidad: el contenido debe durar el tiempo suficiente para amortizar

su coste, y ser utilizado siempre y cuando sea relevante.

Portabilidad: Debe ser posible trasladar el contenido fácilmente desde un

entorno de entrega a otro. El mismo contenido debe trabajar sin

modificaciones en diferentes entornos de entrega, siempre que el entorno

incluya un navegador web.

Reusabilidad: Debe ser posible construir el contenido en pequeños

módulos reutilizables que puedan ser recombinados de diferentes

maneras. Los diferentes LMSs deben ser capaces de compartir contenido

reutilizable.

Interoperabilidad: El mismo contenido debe trabajar de la misma manera

cuando se hace uso de él en diferentes entornos.

Accesibilidad: Debe ser posible encontrar el contenido en un repositorio.

Esto requiere que algunos datos de catalogación estándar sean

asociados con el contenido.

El SCORM se compone de varios libros, cada uno de ellos especifica algunos

aspectos técnicos de contenido compartible. Se proporciona algún tipo de software junto

con los libros para verificar la conformidad y permitir las demostraciones de algunas de

las funcionalidades SCORM.

SCORM especifica que el contenido debería:

Ser empaquetado en un fichero ZIP.

Ser descrito en un fichero XML.

Comunicarse vía JavaScript.

Secuenciar usando reglas en XML.

SCORM se compone de tres sub-especificaciones: (“SCORM explained,”

31/07/16).

La sección del contenido del paquete especifica cómo debe ser

empaquetado y descrito el contenido. Se basa principalmente en XML.

La sección de tiempo de ejecución (Run Time) detalla cómo el contenido

debería lanzarse y cómo se comunica este con el LMS. Se basa

principalmente en ECMAScript (JavaScript).

22

La sección se secuenciación establece cómo el alumno puede navegar entre las diferentes partes de un curso o SCO (Sharable Content Object).

Se define mediante un conjunto de reglas y atributos escritos en XML.

3.7.6 Contenido del paquete

SCORM especifica que el contenido debe ser empaquetado en un directorio auto

contenido o en un fichero ZIP. Esta forma de importación se denomina fichero de

intercambio de paquete (PIF). El PIF debe contener siempre un fichero XML llamado

imsmanifest.xml en el directorio raíz. El archivo de manifiesto contiene toda la información

que el LMS necesita para importar el contenido.

El manifiesto divide el curso en una o más partes llamadas SCOs que traducido

serían objetos de contenido compartible. Estos se pueden combinar en una estructura de

árbol que representa el curso. El manifiesto contiene una representación en XML del

árbol de actividades, información sobre cómo poner en marcha cada SCO y

(opcionalmente) metadatos que describen el curso y sus partes.

3.7.7 Run Time

La determinación del tiempo de ejecución detalla que el LMS debería lanzar el

contenido en un navegador web, ya sea en una nueva ventana o en un conjunto de

marcos. El LMS sólo puede presentar un SCO a la vez.

Todo el contenido debe ser entregado y presentado en un navegador web. Una

vez que el contenido es presentado, utiliza un algoritmo bien definido para localizar una

API de ECMAScript (JavaScript) que es proporcionado por el LMS. Esta API tiene

funciones que permiten el intercambio de datos con el LMS. El modelo de datos CMI

proporciona una lista de elementos de datos (un vocabulario) que pueden ser escritos y

leídos desde el LMS. Algunos elementos del modelo de datos incluyen por ejemplo el

estado del SCO (completado, aprobado, suspenso, etcétera), la puntuación que el

alumno logra, la ubicación del alumno, o la cantidad total de tiempo que el alumno pasa

en el SCO.

3.7.8 Secuenciación

La definición de la secuenciación permite al autor del contenido administrar la

forma en la que el alumno navega entre los distintos SCOs y cómo los datos del progreso

se van acumulando.

Las reglas de la secuenciación se representan mediante XML dentro del

manifiesto del curso. La secuenciación actúa en un modelo de seguimiento que se

asemeja mucho a los datos del modelo CMI producidos por los SCOs durante el tiempo

23

de ejecución. Las reglas de secuenciación permiten al autor del contenido hacer cosas

como:

Determinar qué controles de navegación debería el LMS proporcionar al

usuario (botones de siguiente/anterior, una tabla de navegación por los

contenidos, etcétera).

Precisar que ciertas actividades se han de completar antes de comenzar

otras (requisitos previos).

Hacer que unas partes del curso cuenten más que otras para el estado

final del curso o para la puntuación final (creando secciones opcionales o

mediante pesos en las preguntas).

Seleccionar de forma aleatoria un subconjunto diferente de SCOs

disponibles para ser entregados en cada nuevo intento.

Llevar al usuario hacia la lección que no fue superada.

24

El objetivo principal de este TFG es la obtención de herramientas y ejemplos de

desarrollo de tests en formato SCORM. Para ello, existen una serie de objetivos

secundarios previos, que serían los siguientes:

Estudio de estándares SCORM.

Estudio de sus normas y la evolución de sus distintas versiones.

Comprender los modelos existentes de WebLab basados en SCORM que

se han facilitado.

Estudio del estándar QTI. Obtención de tests.

Desarrollo de librerías JavaScript que ayuden a la creación y ejecución de los tests.

Edición de páginas web mediante programación HTML y JavaScript.

Edición de páginas web haciendo uso de hojas de estilo.

Personalización de la interacción entre el LMS y el alumno a través del

uso de las comunicaciones SCORM-LMS y de las herramientas

desarrolladas.

Revisión y mejora de los modelos WebLab basados en SCORM con el fin

de obtener mejores desarrollos.

Adaptación de WebLab basado en SCORM 2004 a SCORM 1.2.

Creación de ejemplos de WebLab demostrativos del trabajo realizado.

Presentación de los WebLab en un LMS.

Además, para el desarrollo de las librerías JavaScript para crear y ejecutar los tests, se han debido cubrir estos otros objetivos:

Parsear mediante JavaScript los ficheros XML en formato QTI de los bancos de preguntas para los tests.

Proporcionar librerías para la realización de test sin comunicación

SCORM-LMS.

Elegir e importar la librería JavaScript a utilizar en el documento

dinámicamente dependiendo de si se va a comunicar con el LMS o no.

Dar soporte al mayor número de tipos de preguntas posible. Poder modificar el número de preguntas que aparecen en el test creado.

Dar la opción al administrador de barajar o no las preguntas contenidas

en el fichero XML.

25

Dar la opción al administrador de aleatorizar las opciones de las

preguntas (en caso de existir, dependerá del tipo de pregunta).

Proporcionar la posibilidad de ponerle tiempo de realización al test.

26

En este capítulo se va a abordar el lenguaje de programación con el que se han

desarrollado las librerías, así como las herramientas usadas para programarlas y

depurarlas. También se explican conceptos necesarios para la comprensión de la

interacción entre las librerías JavaScript y el LMS.

5.1 JavaScript

El lenguaje de programación utilizado para el desarrollo de las librerías ha sido

JavaScript. Este lenguaje se imparte en varias asignaturas del Grado en Ingeniería

Telemática y cuenta con un nutrido número de ventajas, hecho que hace atractivo su uso.

Entre ellas vamos a destacar las siguientes (“Ventajas JavaScript,” 01/08/2016.):

Es un lenguaje de alto nivel que se compila en tiempo de ejecución.

Es un lenguaje sencillo y ligero.

Casi todos los navegadores puede ejecutar JavaScript.

Tiene una baja barrera de entrada ya que todo lo que necesitas para

programar en JavaScript es un editor de texto y un navegador web.

Es un lenguaje dinámico, lo que significa que los elementos de un

programa pueden variar mientras está en ejecución.

Es flexible, lo que permite por ejemplo la reutilización del código, y la

adición de funciones o la modificación de ellas si fuera necesario.

El código JavaScript se ejecuta en el cliente, por lo que no es necesario

la realización excesiva de peticiones al servidor.

El lenguaje de scripting es fiable y seguro porque va en claro y hay que

interpretarlo, por lo que es posible realizarle un filtrado.

Utiliza poca memoria.

Útil para el desarrollo de páginas web dinámicas.

Fácil de integrar.

Puede agregar interactividad a elementos web.

5.2 Editores de texto

Para desarrollar las librerías, se ha elegido el editor de texto libre Notepad++,

cuyo logo se aprecia en la figura 7 y que en Windows funciona muy bien. Otros editores

interesantes también para la programación de JavaScript serían E Text Editor, UltraEdit,

Atom o Sublime Text.

27

Figura 7: Icono de Notepad++. Fuente: http://www.transitionblog.com/alternatives-to-notepad/

También se podría haber considerado el uso de un entorno de programación

integrado (IDE) como Eclipse, Coda o Netbeans. Por último, otra opción interesante sería

la de Brackets, que además de ser gratis y multiplataforma, está escrita en JavaScript.

Además de para el desarrollo de las librerías, comentar que este editor ha sido el

que se ha usado también para la visión y modificación de los ficheros XML, edición de las

páginas web y creación de hojas de estilo utilizadas en ellas.

5.3 SCORM RTE

El modelo del entorno de tiempo de ejecución (Run-Time Enviroment) detalla los

requisitos para el lanzamiento de objetos de contenido, para el establecimiento de la

comunicación entre los LMSs y los objetos de contenido compartible o SCO (Sharable

Content Object) y para la gestión de la información de seguimiento que puede ser

comunicada entre el SCO y el LMS. En el contexto de SCORM, los objetos de contenido

pueden ser: (Advanced Distributed Learning, 2009)

- SCOs, que se comunican en tiempo de ejecución, o

- Assets (recursos), que no se comunican en tiempo de ejecución.

SCORM se desarrolló para permitir el desarrollo de objetos de contenido que

fueran reutilizables e interoperables sobre múltiples sistemas LMS. Para hacer esto

posible, debe haber una forma común de gestionar objetos de contenido, un mecanismo

común para que los objetos de contenido se comuniquen con un LMS y un lenguaje

predefinido o vocabulario que formen la base de la comunicación.

El proceso de lanzamiento define una forma común para que los sistemas LMS

inicien los objetos de contenido basados en la web. El término “objeto de contenido” se

usa genéricamente para describir cualquier pieza de contenido que pueda ser ejecutada

por un alumno. En SCORM, hay dos tipos de objetos de contenido: los SCOs y los

Assets. El proceso de puesta en marcha establece los procedimientos y

responsabilidades para el establecimiento de la comunicación entre el objeto de

contenido lanzado y el LMS. El mecanismo para la comunicación ha sido estandarizado

con un API común.

28

El API es el mecanismo de comunicación para informar al LMS del estado de la

comunicación entre un objeto de contenido y el LMS (por ejemplo, inicializada, terminada

y/o en una condición de error) y se utiliza para recuperar y almacenar datos entre el LMS

y el SCO.

Un modelo de datos es un conjunto estándar de los elementos del modelo de

datos utilizado para definir la información que se registra para un SCO, tales como el estado de finalización del SCO o la puntuación de una evaluación como la de un test o un

examen. En su forma más simple, el modelo de datos define los elementos del modelo de

datos que tanto el LMS como el SCO esperan conocer. El LMS debe mantener el estado

de los elementos del modelo de datos del SCO a través de las sesiones del alumno, y el

SCO debe utilizar solamente esos elementos predefinidos del modelo de datos para

asegurar la reutilización a través de múltiples sistemas.

5.3.1 Run-Time Environment (RTE) Management

Mientras que el alumno interactúa con objetos de contenido (la experiencia de

aprendizaje), el LMS evalúa el desempeño del alumno y las solicitudes de navegación.

Cuando el LMS identifique una actividad que debe ser presentada al alumno, la

actividad tiene un objeto de contenido asociada a ella. El LMS pondrá en marcha el

objeto de contenido y se lo presentará al alumno. La figura 8 muestra cómo la estructura

del contenido (la sección de organización de un manifiesto) se puede interpretar como un

árbol. La representación en árbol es solo una manera distinta de presentar la estructura

de contenido que se encuentra en el manifiesto.

29

Figura 8: Estructura conceptual del contenido. Fuente: (Advanced Distributed Learning, 2009).

5.3.2 Application Programming Interface (API)

Todas las comunicaciones entre un SCO y el LMS utilizan un API de ECMAScript

(JavaScript) (“Run-Time SCORM,” 03/08/2016). Esta es la única forma posible para que

la comunicación se lleve a cabo. No existen otros canales de comunicación disponibles.

El contenido no puede comunicarse a través de servicios web, formularios de correo,

escritura en base de datos o cualquier otro mecanismo, sólo a través del API de

JavaScript proporcionada por el LMS.

El LMS es el responsable de proporcionar un objeto JavaScript con un nombre

específico en una ubicación específica dentro del DOM del navegador. Por tanto, el

contenido siempre localiza este API utilizando un algoritmo común.

En SCORM 1.1 y SCORM 1.2, el objeto del API siempre se denomina “API”. En

SCORM 2004 el objeto se denomina “API_1484_11”.

Una vez que un SCO encuentra el API, puede usarlo para comunicarse con el

LMS. Hay que tener en cuenta que únicamente los SCOs pueden iniciar la comunicación.

El LMS es una entidad pasiva que simplemente responde a las llamadas API realizadas

por el contenido. El LMS no puede iniciar cualquier comunicación, simplemente lanza el

contenido y responde a las solicitudes.

30

El API de SCORM contiene ocho métodos que difieren ligeramente en su nombre

en función de la versión del estándar. En la figura 9 vemos los de la versión 2004

mientras que en la 10 aparecen los de la versión 1.2 de SCORM:

Figura 9: Métodos del API SCORM 2004. Fuente:(“Run-Time SCORM,” 03/08/2016).

Figura 10: Métodos del API SCORM 1.1/SCORM 1.2. Fuente: (“Run-Time SCORM,” 03/08/2016).

Los nombres de los métodos varían ligeramente entre las versiones SCORM, pero

conceptualmente los métodos son idénticos. Initialize / LMSInitialize El método de inicialización indica al LMS que el contenido desea comenzar una

sesión de comunicación. Todos los SCOs deben llamar a Initizalize antes de realizar

cualquier tipo de comunicación. El LMS devuelve un valor booleano que indica el éxito o

fracaso de la inicialización. Por lo general, siempre devuelve verdadero.

Terminate / LMSFinish

El método Terminate le indica al LMS que la comunicación se ha realizado. Todos

los SCOs deben llamar a Terminate, por lo que no importa cómo el alumno salga del

SCO. Sin embargo, es aconsejable llamar a Terminate en el evento onunload del SCO. El

valor devuelto por el LMS normalmente indica si los datos fueron guardados en el

servidor de forma satisfactoria o no.

GetValue / LMSGetValue

El método GetValue permite al SCO recuperar datos del LMS. Los datos que se

obtienen son siempre elementos definidos en el modelo de datos SCORM. Cada uno de

31

estos elementos del modelo de datos contiene información diferente. Algunos de los

elementos del modelo de datos tienen valores inicializados por el LMS que hacen

referencia a las circunstancias bajo las que el SCO se ha lanzado. Otros valores son

inicializados por el SCO a través de llamadas al método SetValue.

SetValue / LMSSetValue

El método SetValue posibilita que el SCO pueda almacenar datos en el LMS. Los

datos siempre se almacenan en uno de los elementos del modelo de datos definidos por

SCORM. La llamada a este método, devuelve un valor booleano indicando el éxito o

fracaso de la misma.

Commit / LMSCommit

Este método señala al LMS que un chuck de datos considerable se ha guardado y

que debería asegurar que persiste correctamente. No hay requisitos para la puesta en

práctica de este método por parte del LMS.

GetLastError / LMSGetLastError

El método GetLastError comprueba si la última llamada al API SCORM lanzó un

error. Si es así, este método devuelve un código de error que corresponde con un

conjunto definido de posibles errores. La lista completa de códigos de error se encuentra

en la referencia RTE de SCORM.

GetErrorString / LMSGetErrorString

Dado un código numérico (por lo general el número del error devuelto por

GetLastError), el método GetErrorString devolverá una descripción textual de lo que

significa el código del error.

GetDiagnostic / LMSGetDiagnostic

Este método permite al LMS devolver información detallada sobre el error anterior

que puede ser útil en el diagnóstico del problema.

A continuación, la figura 11 muestra el API, una instancia de ella y su

implementación.

32

Figura 11: Ilustración del API, instancia del API y la implementación del API. Fuente: (Advanced Distributed Learning, 2009)

Los estados de la instancia del API especifican las transiciones de la instancia API

para eventos específicos. Cada uno de los estados de la instancia del API establece qué

funciones puede invocar un SCO. Los estados de la instancia del API se definen como

“no inicializado”, “en proceso” o “terminado”. El modelo de estado es un modelo

conceptual utilizado para ayudar a ilustrar el comportamiento previsto de las funciones del

API durante una sesión de comunicación típica. La figura 12 presenta las transiciones de

estados de la instancia del API.

Figura 12: Transiciones de estados de la instancia del API. Fuente: (Advanced Distributed Learning, 2009)

33

5.4 SCO

Un SCO (“SCO SCORM,” 02/08/2016.) u objeto de contenido compartible es un

objeto de aprendizaje que puede ser lanzado (recurso) que se comunica con el entorno

de tiempo de ejecución. Un SCO debe estar diseñado de manera que pueda ser

ejecutado en una ventana web independiente o en un marco dentro de una página web.

Un SCO es especial, ya que cuando se lanza para un alumno en el navegador de

este, el SCO comunicará información de vuelta al LMS que lo lanzó, normalmente un

servidor remoto. Esta comunicación permite al LMS rastrear la información relativa a la

experiencia del alumno.

Un SCO representa el nivel más bajo de granularidad de un recurso de

aprendizaje al que un LMS debe hacer un seguimiento. SCORM no impone ninguna

limitación particular sobre el tamaño de un SCO. Un SCO puede ser una sola página web

o una colección de ellas (siempre y cuando el conjunto de páginas puedan ser

consideradas una sola unidad auto-contenida).

Cada SCO debe ser reutilizable e independiente de su contexto de aprendizaje.

Para lograr dicha reutilización, un SCO debe ser autónomo y no hacer referencia a otros

SCOs.

Cualquier recurso pasivo se puede convertir en un SCO declarándolo como SCO

en el fichero del manifiesto y asegurándonos de que muestra los comportamientos

requeridos por un SCO. Los comportamientos requeridos por un SCO en tiempo de

ejecución serían:

Encontrar la instancia del API RTE proporcionada por el LMS.

Usar la instancia del API para inicializar la comunicación con el LMS.

Utilizar la instancia del API para finalizar la comunicación con el LMS.

Por su lado, los comportamientos que se recomiendan que tenga un SCO serían

los siguientes:

Un SCO debe ser reutilizable en diferentes contextos educativos.

Un SCO debería ser independiente de las limitaciones visuales, tales

como el tamaño de la ventana.

Un SCO debería transmitir de forma fiable los datos del alumno de forma

que no se pierdan en caso de cerrarse inesperadamente.

Un SCO debería comunicar su estado de finalización.

Un SCO no debería lanzar nuevas ventanas en el navegador si no las

cierra una vez se realicen.

34

Un SCO no debería tener vínculos a otros ficheros en el paquete de

contenido que no aparezcan listados como SCO en el manifiesto.

Como comportamientos restringidos de un SCO tenemos:

Un SCO no puede interactuar con el entorno de tiempo de ejecución de

otra forma que no sea la prevista por la API.

Un SCO no puede intentar cambiar el tamaño o la apariencia del RTE en

el que se ha ejecutado.

Un SCO no puede cerrar la ventana principal del navegador que fue

lanzada a menos que sea la única cosa en la ventana.

Por último vamos a comentar el ciclo de vida típico de un SCO:

1. El SCO es lanzado por un RTE SCORM (a menudo un LMS).

2. El SCO encuentra el API aportada por el RTE.

3. El SCO comienza la comunicación con la API RTE (a través de la

llamada al método Initialize()).

4. El alumno comienza a interactuar con el SCO.

5. El SCO envía y recibe datos a través de la API RTE (a través de la

llamada a los métodos Get/SetValue()).

6. El alumno termina de interaccionar con el SCO.

7. El SCO finaliza la comunicación con la API RTE (a través de la llamada al

método Terminate()).

El esquema de presentación de SCOs a los alumnos aparece en la figura 13.

Figura 13: Esquema presentación de SCOs a los alumnos. Fuente: http://eduworks.com/LOTT/Tutorial/scormconcepts.html

35

5.5 Navegadores Web

En el desarrollo y depuración de las librerías y páginas realizadas, ha sido de vital

importancia el uso de los navegadores web en general, y de su herramienta de

depuración en particular. Se ha trabajado principalmente con tres navegadores,

asegurando el correcto funcionamiento en cada uno de ellos. Estos navegadores han

sido Google Chrome, Mozilla Firefox e Internet Explorer.

5.5.1 Google Chrome

Ha sido el principal navegador con el que se ha trabajado, eso sí, asegurándonos

después de la operatividad en el resto. La versión utilizada ha sido la 51.0.2704.103 m.

La herramienta de depuración puede ser accedida a través de la tecla F12. A

continuación la figura 14 presenta una captura de la misma:

Figura 14: Herramienta para desarrolladores en Google Chrome. Fuente: Propia.

5.5.2 Mozilla Firefox

Este navegador también presenta buenas prestaciones a la hora de desarrollar

contenido HTML y usar JavaScript. La versión utilizada en este caso ha sido la 47 y en la

figura 15 podemos apreciar una imagen de su herramienta para desarrolladores:

Figura 15: Herramienta para desarrolladores en Mozilla Firefox. Fuente: Propia.

36

5.5.3 Internet Explorer

El navegador menos usado en el desarrollo y a la vez el que más trabas ha

presentado. La versión del navegador ha sido la 11.0.9600.18376 y el aspecto de su

herramienta para desarrolladores se puede ver en la figura 16.

Figura 16: Herramienta de desarrolladores en Internet Explorer. Fuente: Propia.

5.6 Funcionamiento de las librerías JavaScript para creación de tests online

Para darle a una página o SCO la posibilidad de crear tests online tenemos que

importar la librería denominada principal.js en la cabecera de la página web, es decir,

dentro de las etiquetas head del fichero HTML. Para importarla añadimos la línea que se

aprecia en la figura 17 dentro de la cabecera (anotando en el atributo src la ruta en la

que se encuentre la misma):

Figura 17: Línea para importar la librería principal.js.

En cada página web desarrollada, se han añadido seis variables JavaScript, para posibilitar modificaciones rápidas y de forma sencilla a la hora de la creación de los tests.

Las variables aparecen gráficamente en la figura 18. Estas seis variables son:

banco. Esta variable hace referencia al banco de preguntas que alberga

las cuestiones de las que se compondrá el test. En ella hay que

especificar la ruta (path) del mismo en forma de cadena de caracteres.

numeroPr. Sirve para establecer el número de preguntas de las que va a

constar el test. Habría que indicar un entero.

time. Si queremos que el test cuente con tiempo de realización debemos

darle un valor entero diferente de cero. Este entero indicaría los minutos de los que dispone el alumno para la realización del test.

37

Estableceríamos esta variable a cero si, por el contrario, no se deseara que el test disponga de un tiempo máximo para su realización.

prAleat. Variable utilizada para aleatorizar o no las cuestiones contenidas

en el banco de preguntas. Hay que establecerla bien al string si o bien al

string no.

opAleat. Variable utilizada para aleatorizar o no las opciones de las

preguntas en el caso de las preguntas de opción múltiple y respuesta

única y de las preguntas de opción múltiple y respuesta múltiple. Al igual que para la variable anterior, se debe establecer a si o no.

comunicaciones. Esta variable le permitiría al administrador elegir entre la

posibilidad de que el SCO se comunique con el LMS o por el contrario

que no lo hiciese. Del mismo modo que las variables previas, se fijaría a si o no.

Figura 18: Ejemplo de inicialización de variables JavaScript en páginas web. Fuente:

Propia.

Una vez definidas las variables anteriores, estamos en disposición de llamar a la

función definida en la librería principal.js y denominada loadJsLib. Debemos llamarla

dentro de etiquetas <script> en el documento HTML y pasarle como parámetros todas las

variables. La figura 19 muestra cómo sería esta llamada a la función.

Figura 19: Ejemplo de llamada a la función loadJsLib. Fuente: Propia.

Una vez hecho esto, la función loadJsLib se encarga, dependiendo del valor de la

variable comunicaciones, de importar una librería u otra. Además, una vez la importa,

llama a la función loadXMLDoc definida en la librería recién importada (está en ambas) y

analiza el banco de preguntas en caso de existir. La función implementada para la importación de las librerías sería loadScript,

definida en principal.js. A ella habría que pasarle el localizador de recursos uniforme

(URL, del inglés Uniform Resource Locator) y la función loadXMLDoc con sus

parámetros. En la figura 20 vemos cómo sería:

38

Figura 20: Uso de la función loadScript. Fuente: Propia.

A continuación se muestra un esquema resumiendo todo lo anterior:

SÍ NO

Se consigue de esta forma, una programación modular y escalable. Asimismo, se

produce una encapsulación haciendo posible la modificación de las otras librerías sin que

ello afecte al funcionamiento básico creado. Otra ventaja de hacerlo de este modo, sería

que sólo se le solicitaría al servidor una librería bastante liviana a la hora de cargar la

página web, y más tarde se produciría la importación de la otra más pesada. Además,

proporciona la facilidad de ofrecer la librería adecuada a partir de una variable, sin tener

que preocuparnos del directorio de la librería y su importación.

5.6.1 Análisis del banco de preguntas

Los bancos de preguntas como ya se ha dilucidado en capítulos anteriores que se

han utilizado en este TFG, están estructurados siguiendo el estándar QTI. Estos se

pueden crear en el LMS y después importarlos en XML. Algunos de los bancos aquí utilizados han sido importados de la asignatura Sistemas de Telefonía, impartida en el

Grado en Ingeniería Telemática. Otros en cambio, han sido de elaboración propia. En el

Establecer las variables

en el documento HTML

Llamar a la función

loadJsLib

¿Se va a comunicar

con el LMS?

Importar librería de

comunicaciones y llamar

a loadXMLDoc

Importar librería sin

comunicaciones y llamar

a loadXMLDoc

39

apartado donde se explican los módulos realizados, se detallará qué banco de preguntas

se ha empleado para cada SCO.

Un fichero XML conforme al estándar QTI tiene una estructura simplificada que se

puede apreciar en la figura 21:

Figura 21: Contenido minimizado de un banco de preguntas acorde con QTI. Fuente:

Propia.

Como podemos ver en la figura anterior, las etiquetas padre serían

<questestinterop> y dentro de ellas cada pregunta estaría representada por la etiqueta

<item>.

Además, cada etiqueta ítem presenta tres atributos, dos de ellos muy interesantes

a la hora de la creación de los tests online. Los dos primeros serían un identificador de la

pregunta y un título. El tercero haría referencia al número máximo de intentos, pero para la creación de los tests no se ha tenido en cuenta.

La función encargada de analizar los bancos de preguntas ha sido loadXMLDoc.

Dicha función realiza la petición del fichero XML, y si existe lo analiza. El análisis y creación de tests se realiza del siguiente modo:

1. Obtenemos el contenedor preguntas que debe existir en el fichero HTML.

Ahí será donde vayamos añadiendo las preguntas. La figura 22 muestra

este punto.

Figura 22: Obtención del div preguntas del documento HTML. Fuente: Propia.

2. En la figura 23 obtenemos las preguntas existentes en el banco.

40

Figura 23: Obtención de las preguntas del fichero XML. Fuente: Propia.

3. Después, mientras no se alcance el número de preguntas deseado se

ejecuta un bucle while que va incluyendo en el fichero las distintas

preguntas.

4. Para cada pregunta, se obtiene el título, la descripción, el tipo y el

identificador. Las figuras 24 y 25 muestran la obtención de cada uno.

Figura 24: Obtención del título y la descripción de la pregunta. Fuente: Propia.

Figura 25: Obtención del tipo y del identificador de la pregunta. Fuente: Propia.

5. A continuación se comprueba el tipo de pregunta que es, y dependiendo

de su tipo, se llama a unas funciones u otras. Los cuatro tipos de preguntas soportados en formato QTI se denominan SINGLE CHOICE

QUESTION, MULTIPLE CHOICE QUESTION, NUMERIC QUESTION y

CLOZE QUESTION.

6. Para el tipo de pregunta de opción múltiple y respuesta única (SINGLE

CHOICE QUESTION) primero se obtienen las opciones de la misma, la

puntuación de cada una de ellas y finalmente se llama a la función única,

pasándole como atributos las opciones, el identificador, los puntos y el número de pregunta. Esta vuelca su contenido la variable opciones que

un poco antes de finalizar el bucle while se añade al fichero HTML. En la

figura 26 se aprecia la obtención de las variables para este tipo de

pregunta.

41

Figura 26: Obtención de las opciones, los puntos y llamada a la función única. Fuente:

Propia.

7. La pregunta de opción múltiple y respuesta múltiple (MULTIPLE CHOICE

QUESTION) sigue el mismo método anterior para su creación. Difiere en

que para su creación en lugar de llamar a la función única llamamos a la

función múltiple. Lo comprobamos en la figura 27.

Figura 27: Obtención de las opciones, los puntos y la llamada a la función múltiple.

Fuente: Propia.

8. Para la creación de una pregunta numérica (NUMERIC QUESTION)

obtenemos los límites superior e inferior, el número máximo de

caracteres permitidos y los puntos en caso de acierto. Tras esto, llamamos a la función numerica que volcará su resultado en la variable

opciones del mismo modo que para las preguntas anteriores. Esto se

muestra en la figura 28.

Figura 28: Acciones a realizar en caso de pregunta de tipo numérica. Fuente: Propia.

9. Por último, si el tipo de pregunta es de rellenar huecos (CLOZE

QUESTION) hay que crear más variables y obtener más parámetros,

debido a la complejidad que presentan este tipo de preguntas, ya que sus

huecos pueden ser numéricos, de texto o listas desplegables. En este

42

sentido, tenemos que obtener las opciones, el número total de huecos,

las opciones en caso de listas desplegables y los puntos de cada opción.

Después de esto, se ejecuta un bucle do while para formar la pregunta.

Todo lo descrito, se presenta en la figura 29.

Figura 29: Obtención de las distintas variables para un pregunta de rellenar huecos.

Fuente: Propia.

10. Una vez se le ha dado valor a cada una de las variables dependiendo del

tipo de pregunta, se añaden al div preguntas obtenido en el paso 1. Para

añadirlas se usa el método innerHTML:

Figura 30: Método innerHTML para agregar contenido al documento HTML. Fuente:

Propia.

Un posible diagrama que representara lo explicado previamente podría ser este:

SÍ NO

Obtención de las preguntas del fichero XML

¿Se desea barajar las preguntas?

Obtención del div preguntas

Obtención de las preguntas del fichero XML

43

SÍ NO

Para la aleatorización de las preguntas, así como de las opciones, se ha utilizado el algoritmo de Fisher-Yates que podemos observar en la figura 31. A esta función, le

pasamos un array de valores, y nos devuelve el mismo array, pero con sus valores

desordenados.

¿Se ha alcanzado el número de preguntas

deseado?

Fin de la función loadXMLDoc

Obtener título, descripción, tipo e identificador de la

pregunta

Aleatorizar preguntas No aleatorizar preguntas

¿Tipo de la pregunta?

SINGLE CHOICE QUESTION

MULTIPLE CHOICE

QUESTION

CLOZE QUESTION

NUMERIC QUESTION

Obtención de variables y llamada a la función

única.

Obtención de variables y llamada a la función

multiple.

Obtención de variables y llamada a la función

numerica.

Obtención de variables y ejecución de bucle do

while.

Añadir al div preguntas

44

Figura 31: Función fisher_yates para aleatorizar un array. Fuente: Propia.

5.6.2 Evaluación de las preguntas

Una vez añadidas las preguntas, nos encontramos tres botones debajo de cada una de ellas. El primero de ellos sería el de validar y se usaría para puntuar la pregunta.

El segundo es el de borrar y lo pulsaríamos si queremos resetear los campos de la

pregunta o eliminar nuestra elección y el último sería el de posponer. Este botón tiene

sentido ya que se ha decidido mostrar las preguntas una a una, pensando en las

comunicaciones con el LMS y con el fin de evitar demoras a la hora de interaccionar con

el LMS, enviando la información de la pregunta cuando es mostrada al usuario, y no

antes. El aspecto de estos tres botones los podemos ver en la figura 32.

Figura 32: Botones bajo cada pregunta. Fuente: Propia.

Para evaluar una pregunta de opción múltiple y respuesta única, la función que se ha desarrollado se ha denominado puntuarUni a la cual debemos pasarle como

parámetros el identificador de la pregunta y el número de pregunta que es.

La función obtiene todos los elementos con ese identificador y comprueba el

elemento que se ha seleccionado. Adquiere la puntuación de la selección y la respuesta

dada para después deshabilitar las opciones así como los botones de la pregunta,

actualizar la puntuación y mostrar un mensaje al alumno y la siguiente pregunta. La actualización de la puntuación consiste en la modificación de un div pequeño

que se añade en la parte superior derecha de la página cuando se crea el test. Este div

muestra nuestra puntuación actual y el total de puntos que podemos obtener separados

por una barra. El aspecto de este div aparece en la figura 33.

45

Figura 33: Div con la puntuación del alumno. Fuente: Propia.

El mensaje que se muestra al alumno no deja de ser un alert para el que se ha

usado la librería smoke.js (“Smoke JS,” 01/07/2016). Este mensaje difiere dependiendo

de si se acierta o no la cuestión. La alerta que aparece en los tests sin comunicación se

muestra en la figura 34.

Figura 34: Mensaje que aparece al acertar una pregunta. Fuente: Propia.

Por lo que respecta a mostrar la siguiente pregunta, se ha creado una función denominada muestra a la que le pasamos el número de pregunta desde la que la

llamamos. En ella, se obtiene el div que contiene a la siguiente pregunta y lo modifica

para que sea visible.

La corrección del resto de preguntas se realizaría de manera análoga a la de las

preguntas de opción múltiple y respuesta única, con la única salvedad de la función usada para la corrección, que en cada caso difiere. Para las preguntas de opción múltiple

y respuesta múltiple se denomina puntuarMul, para las preguntas numéricas puntuarNum

y para las preguntas de rellenar huecos puntuaHuecos.

Validar pregunta de opción múltiple y

respuesta única

Llamada a la función

puntuarUni

Deshabilitar opciones y botones, obtener

puntuación, actualizarla,

mostrar mensaje y

mostrar siguiente pregunta.

46

La función puntuarNum necesita para su correcto funcionamiento además del

identificador y del número de pregunta, los valores inferior y superior obtenidos para esa

pregunta. La corrección de la pregunta dependerá de que el valor introducido por el

usuario se encuentre dentro de esos valores. La función putuaHuecos sería algo más compleja que el resto, debido a que tiene

que discernir si el hueco es de tipo decimal, string o es una lista desplegable. Un ejemplo

de pregunta de rellenar huecos se puede ver en la figura 35.

Figura 35: Ejemplo de pregunta de rellenar huecos. Fuente: Propia.

Además, si el hueco es de tipo string es necesario obtener la sensibilidad para

saber si a la hora de evaluar su corrección debemos tener en cuenta las letras mayúsculas o una cierta distancia de Levenshtein. Si la opción escrita por el usuario no

fue contemplada en la creación de la pregunta y por tanto no aparece en el banco de

preguntas, no sumará ni restará puntos. La corrección de un hueco de tipo decimal se llevaría a cabo del mismo modo que

el de una pregunta de tipo numérica y la de tipo lista desplegable sería similar a la de las

respuestas múltiples, obteniendo los puntos de la opción seleccionada.

5.6.3 Interacción de las librerías con el LMS

Todo lo comentado previamente, serviría tanto para las librerías que interaccionan

con el LMS como las que no. En este sentido, se han desarrollado dos librerías, denominadas creaPreguntas2004.js y creaPreguntas12.js que, aparte de las funciones

anteriores, realizan otras tareas relacionadas con la interacción entre alumno y LMS. A

continuación comentaremos estas otras funciones.

Pero antes de explicar esas funciones, hay que aclarar que para la interactividad añadida a los tests se ha utilizado la librería JavaScript APIWrapper.js de la ADL y la

librería RTE.js cuyo autor es Ildefonso Ruano Ruano, ambas de necesaria importación en

los documentos HTML. Un fragmento del inicio de la librería RTE.js se puede visualizar

en la figura 36.

47

Figura 36: Fragmento objeto RTE de la librería RTE.js. Fuente: RTE.js.

creaPreguntas2004.js

Después de haber obtenido las variables necesarias para poder modificar el div de

la página web con la pregunta adecuada, en caso de ser la primera pregunta que se genera, se llama a las funciones setInteractionsAddNewI y setInteractionsDescription de

la instancia del RTE que previamente ha debido ser inicializada a la versión 2004. Si no fuera la primera pregunta, estas funciones son llamadas en la función muestra.

A la función setInteractionsAddNewI se le pasan cinco parámetros para su

correcto funcionamiento. Estos parámetros serían el número de interacción que es, el identificador de la misma, el tipo de cuestión (en nuestro caso podrá ser choice, fill-in o

numeric), el peso de la pregunta (que por defecto se ha establecido a uno) y la respuesta

correcta de la pregunta.

Para la obtención de la respuesta correcta, se ha realizado una función denominada getCorrectaInv a la cual debemos pasarle el identificador de la pregunta y el

tipo de pregunta que es como parámetros. A partir del tipo y de los puntos de las

opciones, se realizan las acciones necesarias para obtener la respuesta correcta.

La respuesta correcta debemos obtenerla en el mismo formato aceptado por el

LMS para que no se produzca ningún tipo de error a la hora de almacenarla en él. Del

mismo modo, a la hora de obtener la respuesta dada por el alumno, también debemos

formatearla de la misma manera para que se pueda guardar en el LMS y se pueda

comparar con la respuesta correcta. La función setInteractionsDescription por su parte, le añade una descripción a la

interacción. En nuestro caso, como descripción le hemos pasado el título de la cuestión,

por lo que se recomienda que no haya preguntas con el mismo título en el banco de

preguntas. A esta función hay que pasarle el número de interacción y la descripción que

queremos que añada a esa interacción.

48

En la figura 37 tenemos una captura de ambas funciones en la librería creaPreguntas2004.js.

Figura 37: Uso de estas funciones en la librería creaPreguntas2004.js. Fuente: Propia.

Por lo que respecta a la corrección de las preguntas, en todas las funciones

realizadas para tal fin, se ha seguido el mismo procedimiento:

1. Primero se obtiene la respuesta correcta a través de la función getInteractionsCorrectRespPattern.

2. Después se compara la respuesta dada por el alumno con la respuesta

correcta obtenida en la consulta al LMS. A partir de esta comparación, se

determina la corrección de la interacción. Esta consulta y la posterior

comparación, aparecen plasmadas en la figura 38.

3. Una vez se determina la corrección de la interacción, se llama a la función setInteractionsResponseAll pasándole el número de interacción,

la respuesta dada por el alumno y la corrección de la interacción como

parámetros, quedando así almacenada en el LMS la información de la

interacción realizada por el alumno.

Figura 38: Uso de funciones para interacciones con el LMS en creaPreguntas2004.js.

Fuente: Propia.

4. Además, a la hora de presentarle el mensaje al alumno con su

puntuación para esa pregunta/interacción, este se modifica para que aparezca el nombre de pila del alumno, personalizando el tests aún más.

Esto se consigue gracias a la función getLearnerName y a la función split

para sólo quedarnos con la primera cadena del nombre. El uso de esta

función aparece en la figura 39.

Figura 39: Uso de la función getLearnerName en creaPreguntas2004.js. Fuente: Propia.

49

creaPreguntas12.js

Igualmente que con la librería creaPreguntas2004.js una vez que se han obtenido

las variables necesarias para poder modificar el div de la página web con la pregunta

adecuada, en caso de ser la primera pregunta que se genera, se llama a las funciones setInteractionsId, setInteractionsType, setInteractionsTs, setInteractionsWeight y

setInteractionsCorrectRespPattern de la instancia del RTE que previamente ha debido

ser inicializada a la versión 1.2. Si no fuera la primera pregunta, estas funciones son llamadas en la función muestra.

Estas funciones tienen el mismo cometido que la función setInteractionsAddNewI

utilizada en la versión 2004. Sin embargo, hay que utilizarlas por separado, dado que esta función no es soportada por la versión 1.2 de SCORM, como se aprecia en la figura

40. En este sentido, los parámetros necesarios para el uso de cada función son los

siguientes:

Para la función setInteractionsId es necesario indicarle el número de

interacción de la que se trata y el identificador de la misma.

La función setInteractionsType se ocuparía de señalar el tipo de

interacción de la que se trata. Así, tendríamos que pasarle el número de interacción y el tipo al que corresponde (choice, fill-in o numeric).

setInteractionsTs nos ayudaría a almacenar el timestamp de la

interacción. Tendríamos que pasarle como parámetros el número de

interacción y el tiempo de ella, para lo que usamos la función de la

librería RTE.js llamada timeStamp a la que basta con indicarle la versión

en la que queremos el timestamp puesto que de una versión a otra

varían.

La función setInteractionsWeight necesitaría el número de interacción y

el peso de la interacción, que por defecto hemos establecido a uno.

Por último, la función setInteractionsCorrectRespPattern nos facilitaría el

almacenamiento de la respuesta correcta en el LMS. Habría que pasarle

el número de interacción, el valor cero, y la respuesta correcta.

Figura 40: Uso de funciones para añadir una interacción al LMS en creaPreguntas12.js.

Fuente: Propia.

50

Para obtener la respuesta correcta, se ha obrado de la misma manera que en la versión de 2004, es decir, se ha utilizado la función denominada getCorrectaInv y de la

misma manera, estas respuestas deben ir bien formateadas para que el LMS no nos

rechace la operación de almacenarlas.

En cuanto a la corrección de las preguntas, la versión 1.2 no proporciona ninguna

función para recuperar del LMS la respuesta correcta de la interacción, por lo que hemos tenido que usar de nuevo la función getCorrectaInv para tal fin.

De este modo, una vez se comprueba la corrección de la pregunta, se utiliza la función setInteractionsResponseAll que tendría los mismos parámetros que su versión de

2004 para almacenar la experiencia del alumno con esa interacción. Esta función se

muestra en la figura 41.

Figura 41: Uso de la función setInteractionsResponseAll en la creaPreguntas12.js.

Fuente: Propia.

Finalmente, para mostrar el nombre de pila del alumno, se procede de una forma similar pero no igual, dado que la función getLearnerName de la versión 1.2 nos devuelve

el nombre del usuario de manera diferente, en concreto, esta nos devuelve primero los

apellidos del usuario y tras una coma, su nombre de pila. La obtención del nombre de pila

se muestra en la figura 42.

Figura 42: Uso de la función getLearnerName en la librería creaPreguntas12.js. Fuente:

Propia.

5.7 Interacciones

El modelo de datos del RTE de SCORM contiene, entre otros, una serie de datos

a los que llama interacciones. Estas interacciones definen un conjunto elementos que

sirven para representar preguntas que se presentan a un alumno en el entorno de un

SCO y las respuestas del alumno. Estos elementos pueden enviarse desde el SCO al

LMS para que guarde un registro de estas interacciones realizadas entre el alumno y el

SCO. Las interacciones pretenden ser respuestas a cuestiones o tareas individuales que

el desarrollador desea almacenar. No existe ningún comportamiento implícito que el LMS

deba tener cuando se le solicita introducir una interacción en el sistema a parte de la del

almacenamiento de los datos.

Las interacciones sirven para registrar en un LMS el trabajo que realiza un alumno

referente a las respuestas que introduce en un SCO ante cuestiones que se le han

51

planteado. La 43 muestra los datos de una interacción en la versión 2004 edición 4 de

SCORM, en la que puede haber 10 tipos distintos de interacciones: “true-false”, “choice”,

“fill-in”, “long-fill-in”, “matching”, “performance”, “sequencing”, “likert”, “numeric” y “other”.

Las interacciones pueden ser consideradas como una colección de información

(datos de la interacción) (Advanced Distributed Learning, 2009). Según se define en el estándar IEEE 1484.11.1, un LMS tiene la obligación de dar soporte (por ejemplo

almacenar) al menos a 250 conjuntos de datos de interacción. Un LMS puede elegir dar

soporte a más de 250, sin embargo, el requisito es el de dar soporte al menos a 250

conjuntos de datos de interacción.

Figura 43: Interacciones y datos de interacción. Fuente: (Advanced Distributed Learning, 2009).

Existen dos elementos de datos que deben aparecer en los datos de interacción, un identificador (cmi.interactions.n.id) y un tipo de interacción (cmi.interactions.n.type). El

elemento cmi.interactions.n.type del modelo de datos es necesario si la interacción

incluye datos relativos a la respuesta correcta o a la respuesta del alumno. Estos dos

datos son los que distinguen los datos de interacción de otros datos de interacción

encontrados en el conjunto de interacciones (considerados como los dependientes de los

datos de interacción). El identificador identifica de manera única una interacción dentro del alcance de un SCO. El tipo identifica de forma única el tipo de la interacción (true-

false, matching, etcétera).

El elemento del modelo de datos de las interacciones se puede usar por el SCO

en dos modalidades: de diario y de estado.

Un esquema de diario requiere que el SCO registre datos de interacción cada vez

que el alumno realiza alguna acción en el SCO, es decir, cada vez que datos nuevos de interacción se añaden al array de interacciones. Aplicando este esquema para el

52

almacenamiento de interacciones, la información puede reunirse para estudiar la experiencia del alumno a partir de las interacciones encontradas en el SCO.

Por ejemplo, en los informes generados se puede ver el número de veces que un

alumno ha respondido a una interacción, cuál fue la latencia de cada interacción, cuál fue

la respuesta del alumno o el resultado de su respuesta. Estos datos se pueden reunir y

utilizar para la actualización de la interacción para su uso futuro. Los LMSs pueden

proporcionar este tipo de información para la investigación y análisis independiente, sin embargo, la disponibilidad de estos datos está fuera del alcance de SCORM.

Un esquema de estados necesita que el SCO almacene datos de interacción y mantenga la interacción actualizada basándose en la experiencia del alumno con el SCO.

Por ejemplo, si el alumno responde a una interacción, la respuesta se añade. Si el

alumno después corrige su respuesta, los mismos datos de interacción deben ser modificados para reflejar el cambio (en contra de añadir una nueva entrada en el array de

interacciones). En este escenario, los datos de interacción contienen el último estado

almacenado de la interacción y por lo tanto, no existe la posibilidad de conocer cuántas

veces ha cambiado la respuesta el alumno en la interacción. Este modelo sería el

seguido por el LMS ILIAS usado por la Universidad de Jaén.

Tenemos pros y contras dependiendo de un esquema u otro. Los desarrolladores

de los SCO deben ser conscientes de ambos esquemas y usar el que deseen. Desde la perspectiva del LMS, el esquema de diario requiere más carga en cuanto a requisitos de

almacenamiento. Pero de nuevo recordamos que, el único requisito en cuanto a

almacenamiento de datos de interacción es el de almacenar al menos 250. Como con cualquier elemento del modelo de datos almacenado en arrays, la

posición del índice (n) no es lo que distingue la singularidad de los datos que se

almacenan. Los requisitos de implementación definidos en la norma, indican que los arrays deben ser implementados como una bolsa.

Esta estructura de datos permite que el mismo objeto (datos de interacción) se repita en el array (a diferencia de un conjunto que requiere que los elementos en el

conjunto sean únicos). Dependiendo del alumno y la sesión de aprendizaje, los mismos datos de interacción no se pueden almacenar en la misma posición en el array.

Todos los elementos del modelo de datos, así como sus requisitos de implementación, los requisitos en el comportamiento del LMS, los requisitos en el

comportamiento del SCO y los requisitos de implementación del API para la versión 2004

de SCORM que es más completa, se pueden consultar en (Advanced Distributed

Learning, 2009).

53

5.8 Módulos SCORM desarrollados

En este apartado, vamos a explicar los dos módulos SCORM desarrollados. En

primer lugar se desarrolló el módulo para la versión 2004 edición 4 y posteriormente,

eliminando las funcionalidades no soportadas por la versión 1.2 del estándar, se

desarrolló el segundo. Dada la naturaleza de estos módulos, que necesitan comprimirse

en formato ZIP para su uso y cuya entrega debía ser en este formato según la propuesta

de este TFG, se han entregado como anexos a esta memoria. Durante el desarrollo, se

han ido probando en la web de la Universidad de Jaén de espacios virtuales https://ev.ujaen.es/ en un espacio reservado para dicho fin. La versión de ILIAS utilizada

ha sido la v5.1.7. En la figura 44 se presenta el aspecto del acceso a la plataforma.

Figura 44: Acceso a espacios virtuales de la Universidad de Jaén. Fuente: ev.ujaen.es.

5.8.1 Módulo SCORM 2004

Este módulo consta de cinco páginas o SCOs y muestra los distintos usos que se

le pueden dar a las librerías realizadas.

En este sentido, al iniciar la primera página, se llevan a cabo varias acciones. Si

es la primera vez que accedemos al módulo, este nos da la bienvenida a través de una

alerta personalizada mientras que si no lo es, nos avisa del número de veces que hemos

accedido, nos muestra nuestra calificación para ese SCO tras consultarlo con el LMS y

nos pregunta si queremos continuar en él o por el contrario continuar hacia el siguiente SCO. La página en sí, nos explica brevemente cómo usar la librería

creaPreguntas2004.js y cómo sería su incorporación en el fichero HTML. Además, en su

parte final se da la posibilidad de realizar un pequeño test sobre el contenido de la

página. Este test tiene las siguientes características:

Presenta dos preguntas.

No existe tiempo de realización.

54

No se comunica con el LMS.

Preguntas aleatorizadas.

Opciones en caso de tener aleatorizadas también.

Al final de la página, también se muestra un botón para pasar a la página 2 del

módulo. Una vez se pulsa, se llevan a cabo las siguientes acciones:

Si la puntuación obtenida en el test es la mitad o superior a la máxima

posible se da como aprobado, y como tal, se envía al LMS la calificación de aprobado (passed). En caso contrario, se daría la evaluación como

suspensa (failed).

Se establecen los objetivos de la página, mediante un identificador, la

puntuación obtenida, el mínimo y el máximo posible, y su estado, bien

completo o bien incompleto. Además, se incluye una descripción de la

página.

Se envía al LMS la puntuación obtenida por el alumno, así como la

mínima y la máxima posible.

Por último, cierra la sesión y nos presenta la página dos.

En la segunda página del módulo, se ha incluido información relacionada con este TFG como por ejemplo los objetivos principales así como algunos secundarios, los cuales

se pueden ver en la figura 45. También se ha incluido una breve introducción a las

tecnologías utilizadas en él y de nuevo, se ha incluido la posibilidad de realizar antes de pasar a la página tres un pequeño test con las mismas características que el presentado

en la página uno. Una vez se pulsa el botón de ir a la página tres, dependiendo de la puntuación obtenida en el test se da por superado o no el SCO, se cierra la sesión y se

pasa a la página tres.

Figura 45: Objetivos secundarios que aparecen en la página dos del módulo. Fuente:

Propia.

En la tercera página, se le muestra al alumno un test de diez preguntas tomadas

de un banco que contiene un total de doce, muchas de ellas utilizadas en los tests de

evaluación de la asignatura Sistemas de Telefonía. En este test, como en los anteriores,

55

las preguntas aparecen de forma aleatoria al igual que sus opciones, tampoco dispone de

tiempo de ejecución pero sí que interacciona con el LMS.

Cada vez que aparece una nueva pregunta, esta queda registrada como una nueva interacción en el LMS. Las respuestas del alumno así como su evaluación,

también son enviadas al sistema de aprendizaje. En el momento en el que decidamos dar por terminado el test, es decir, pulsemos el botón de Terminar Test, aparecerá un cuadro

resumen bajo las preguntas, con toda la información de nuestra actuación en el SCO. El

aspecto de este cuadro resumen se muestra en la figura 46. Bajo este cuadro, aparecerá

un botón para avanzar hacia la página cuatro.

Figura 46: Cuadro resumen de la página 3. Fuente: Propia.

Es en la página cuatro en la que se ha incluido un vídeo explicativo de cómo

descargar un banco de preguntas previamente hecho en el LMS y de cómo realizando pequeños cambios en el fichero HTML, se da la posibilidad de la creación de un test

SCORM. El aspecto inicial del vídeo aparece en la figura 47. Tras el vídeo, tenemos un

botón para ir a la página cinco, a la que hemos titulado como de autoevaluación. Una vez

lo pulsamos, se da por completado este SCO, finaliza la sesión y nos presenta la página

cinco. Comentar también, que, al igual que en el resto de páginas, si no es la primera vez

que accedemos a la página, nos presenta un mensaje avisándonoslo y mostrándonos

nuestros valores obtenidos en la evaluación de esta página.

56

Figura 47: Vídeo incluido en la página 4 del módulo. Fuente: Propia.

Finalmente, la página cinco sería una página de autoevaluación. Y es que en ella, se ha hecho un formulario con el que podemos crear un test a nuestra medida. Dicho

formulario aparece en la figura 48. En él, se solicitan los seis parámetros necesarios para su creación. Una vez rellenados, pulsamos el botón de crear test para que bajo este

formulario aparezca nuestro test con las características deseadas. Tras su creación, bajo

el formulario aparece el test y a su conclusión un nuevo botón para ver nuestro

desempeño en el test. Tras todo esto, ya estaríamos en disposición de terminar el módulo

SCORM.

Al terminar se da por completado el SCO y dependiendo de la puntuación

obtenida, habremos aprobado o suspendido el mismo. Después de esto, saldríamos de la

sesión y finalizaríamos el módulo.

57

Figura 48: Formulario de la página 5 del módulo. Fuente: Propia.

5.8.2 Módulo SCORM 1.2

Al igual que el módulo para la versión 2004, este módulo consta de cinco páginas

o SCOs y muestra los distintos usos que se le pueden dar a las librerías realizadas.

A diferencia del módulo anterior, en esta versión no se pueden establecer caminos

de aprendizaje, por lo que la navegación es necesario realizarla a través del árbol de

navegación que proporciona el LMS para tal fin. La figura 49 muestra el aspecto del árbol

de navegación en el LMS para la versión 1.2 de SCORM.

Figura 49: Árbol de navegación en el LMS para la versión 1.2 de SCORM. Fuente: Ilias.

Del mismo modo, los elementos para la evaluación son más limitados en esta

versión, por lo que los SCOs de este módulo van a ser lo más similares posible a los

anteriores con la salvedad de la ausencia de los elementos y funciones de los que carece

esta versión. También el debugger, que se aprecia en la figura 50, es más simple.

Figura 50: Debugger del LMS para la versión 1.2 de SCORM. Fuente: Ilias.

58

5.9 Manual de usuario y mantenimiento

Seguidamente vamos a pasar a explicar cómo utilizar estas herramientas para

aprovechar todas sus posibilidades.

En primer lugar, es necesario obtener el banco de preguntas sobre el tema que

queremos evaluar/nos en un fichero XML que siga la norma QTI. Para tal fin, lo más

sencillo sería crear un banco de preguntas en el LMS y posteriormente exportarlo en formato QTI. La creación del fichero de exportación se puede ver en la figura 51.

Figura 51: Creación de archivo de exportación QTI. Fuente: Ilias.

Una vez descargado el fichero, lo descomprimimos y nos aparecerá una carpeta llamada objects normalmente vacía y dos ficheros XML, como bien muestra la figura 52.

La única diferencia en el nombre entre ambos es que en uno aparecen las siglas qpl y en

otro qti. El fichero en cuyo nombre aparecen las siglas qti contiene toda la información

referente a las preguntas del test, mientras que en el fichero con las siglas qpl aparece

información relacionada con el banco de preguntas y la plataforma virtual. El que

usaremos nosotros para pasárselo como parámetro a nuestra función es el que contiene las siglas qti en su nombre.

Figura 52: Ficheros que aparecen al descomprimir el archivo descargado. Fuente: Propia.

En cuanto a la integración con nuestro fichero HTML, lo primero que tendríamos

que hacer sería incluir en la cabecera (etiquetas head) la librería principal.js con la ayuda

de las etiquetas script. Además, si deseamos interaccionar con el LMS tampoco deben

faltar las librerías denominadas RTE.js y APIWrapper.js. En nuestro caso también hemos

añadido la librería smoke.js que se puede descargar de http://smoke-js.com/ para darle

estilo a las alertas, pero esa sería opcional, eso sí, si no se incluye habría que modificar

los ficheros para no utilizar las funciones de esta librería.

59

También habría que añadir las hojas de estilo que fueran necesarias, en nuestro caso han sido dos, una propia y la otra para ser usada por la librería smoke.js. La figura

53 muestra todas las librerías importadas así como hojas de estilo para la página 1 del módulo SCORM además de un favicon para la página.

Figura 53: Cabecera del documento HTML. Fuente: Propia.

Después tendríamos que darle valores a las variables que utiliza la función de creación de tests en el fichero HTML. En este sentido, tendríamos que indicar la ruta del

banco de preguntas, el número de preguntas del test, su tiempo de realización, si

deseamos aleatorizar las preguntas así como sus opciones en caso de tenerlas y si se va

a comunicar con el LMS o no. Esto se ve de forma gráfica en la figura 54.

Figura 54: Inicialización de variables para creación de tests. Fuente: Propia.

Antes de llamar a la función loadJsLib hay que comentar que esta busca una

tabla con el identificador tabla en el documento HTML en la que ir añadiendo las distintas

preguntas del test, por lo que para su correcto funcionamiento esta debe aparecer en el

fichero. Llegados a este punto, estamos en disposición de llamar a la función loadJsLib la

cual importará la librería adecuada en función del valor de la variable comunicaciones y

añadirá las preguntas a la tabla. La llamada a la función se muestra en la figura 55.

Figura 55: Llamada a la función LoadJsLib. Fuente: Propia.

Si se desea añadir algún botón de finalización del test para realizar alguna acción

o mostrar cierta información, este debe añadirse al fichero HTML puesto que la función

no añade ningún tipo de botón final por sí sola. Se da así la posibilidad de personalizar

60

aún más el test generado. En este caso, la figura 56 muestra el botón de terminación del

test de la página 3 del módulo SCORM.

Figura 56: Botón de terminación de test en página 3 del módulo SCORM. Fuente: Propia.

Cuando la función nos presente las preguntas, bajo cada una de ellas, podemos

observar tres botones cuyo aspecto se puede ver en la figura 57, uno para validar la

pregunta y mostrar la siguiente, otro para borrar nuestra elección y un último para pasar a

la siguiente pregunta si no deseamos responder o no conocemos la respuesta a la

pregunta actual.

Figura 57: Botones bajo cada pregunta. Fuente: Propia.

Además, durante la realización del mismo, podremos ver en la esquina superior

derecha nuestra puntuación total junto con la máxima posible. Esta puntuación sería la utilizada para dar como aprobado o suspenso un test y por extensión el SCO.

Por lo que respecta al estilo y presentación de los tests, esta puede verse y/o

modificarse en las dos hojas de estilo desarrolladas. Para modificar el estilo, no

tendríamos más que localizar el elemento que queremos modificar en el fichero CSS y

cambiar el atributo deseado. Por ejemplo, la figura 58 muestra el estilo aplicado a la etiqueta h3 en estilo.css. A las hojas de estilo creadas se las ha denominado

estiloIndex.css y estilo.css. Ambas se han añadido en el apartado Anexos.

Figura 58: Estilo para el elemento h3 en estilo.css. Fuente: Propia.

Cada función contenida en las librerías incluye en su parte superior una

descripción de la misma, por lo que si queremos modificarla o deseamos tener una

funcionalidad que no está incluida en ninguna de ellas no tenemos más que añadirla en la

librería.

61

Si vamos a utilizar la librería de creación de tests para autoevaluación es

necesario acceder a la página web a través de un servidor local dado que los

navegadores por temas de seguridad sólo abren páginas que sigan los esquemas de los estándares conocidos. En nuestro caso, para probar los test de autoevaluación hemos

usado el servidor Apache contenido en el paquete XAMPP, que aparece en la figura 59.

Figura 59: Panel de control de XAMPP. Fuente: XAMPP.

Para el mantenimiento de las mismas, se recomienda utilizar las herramientas

para desarrolladores que proporciona cada navegador. Ante cualquier error, lo mejor

sería utilizar estas herramientas y con la ayuda de puntos de ruptura seguir el código e ir

viendo el valor que van tomando las distintas variables y ver cómo responde y si realiza lo

que deseamos o no. La figura 60 muestra una captura de la herramienta para

desarrolladores de Google Chrome en la que se ha añadido un punto de ruptura para

analizar el código. La función aquí analizada es la de puntuaHuecos. Justo al lado de su definición,

aparecen los valores que toman los parámetros recibidos por la función. En la parte derecha de la captura se observan tres pestañas, una llamada Watch, otra Call Stack y la

tercera Scope. Es en la pestaña Call Stack donde se aprecia que el punto de ruptura

está en la línea 955 de la librería creaPreguntas.js.

Por último, en la pestaña Scope es en la que aparecen todas las variables de la

función y sus valores. Como está pausado al inicio de la función, todas las variables

excepto las pasadas como parámetros están indefinidas.

62

Figura 60: Ejemplo de punto de ruptura en la herramienta para desarrolladores de

Google Chrome. Fuente: Google Chrome.

63

El resultado del TFG actual ha sido, por un lado, la obtención de cuatro librerías

JavaScript, una para interaccionar con la versión 1.2 de SCORM, otra para interaccionar

con la versión 2004 edición 4 del estándar, una tercera para autoevaluación y una última

de apoyo que ayuda a importar la librería adecuada (bien la de comunicaciones o bien la

de sin comunicaciones con el LMS) que se ha realizado con vistas a ser escalable y

hacer posible la inclusión de más estándares haciendo las mínimas modificaciones

posibles. Las cuatro librerías, se pueden ver en el apartado Anexos.

Por otro lado, se han desarrollado dos módulos SCORM, uno para la versión 1.2

del estándar y otro para la versión 2004 edición 4, en los que se han incluido estas

librerías con la finalidad de demostrar su uso.

A continuación, se va a proceder a la realización del módulo SCORM 2004 y a la

visualización de la información que se puede obtener por parte del administrador o

profesor desde el LMS, una vez terminado. En este sentido, la figura 61 presenta una

captura parcial de la página 1 del módulo.

Figura 61: Captura parcial de la página 1 del módulo SCORM 2004. Fuente: Propia.

En esta página se da la posibilidad de realizar un test que evalúa la comprensión

de la explicación dada en ella sobre cómo usar la librería creaPreguntas2004.js. Además,

se añade como objetivo de la página en el LMS la correcta comprensión de la página

inicial, el cual puede verse en la figura 62 y que se evalúa dependiendo de la puntuación obtenida en la realización del test. También se evalúa a partir de esta puntuación el

elemento successStatus del modelo de datos, que por defecto se inicializa a suspenso.

64

Eso sí, una vez se pulsa el botón para pasar a la página 2, se establece a completado el elemento CompletionStatus del modelo de datos que por defecto se inicializa a

incompleto y se fija la puntuación de la página.

Figura 62: Informe de objetivos para la página 1 del módulo. Fuente: Ilias.

La página 2, que aparece en parte en la figura 63, sigue el mismo guion de la

página 1, en este caso explica brevemente los objetivos y las tecnologías involucradas en este TFG así como el test de la página 3 y por último presenta otro pequeño test con

cuestiones relativas a lo comentado anteriormente.

Figura 63: Captura parcial de la página 2 del módulo SCORM 2004. Fuente: Propia.

Los elementos SuccessStatus y CompletionStatus se evalúan del mismo modo

que en la página 1, es decir, dependiendo de la puntuación obtenida en la realización del test. Al pulsar el botón de ir a página 3, se establece la puntuación del SCO, que va

acorde a la obtenida en el test también.

Al iniciar la página 3, por defecto los elementos SuccessStatus y

CompletionStatus se inicializan a suspenso e incompleto respectivamente. Después

presenta un total de 10 preguntas, que aparecen de una en una. Conforme van

apareciendo, se va añadiendo la interacción en el LMS. En ella, se especifica el número

65

de interacción, su identificador, su tipo, el peso, la respuesta correcta y una descripción

de la misma.

Cuando el alumno responde, se añade la respuesta dada y si esta es correcta o no. Además, al finalizar el test, aparece una tabla al pulsar el botón de Terminar Test con

el nombre del alumno, el tiempo empleado en la realización del test y su desempeño en

el mismo. De la misma manera que en las páginas anteriores, el valor de SuccessStatus

dependerá de si la puntuación obtenida por el alumno es igual o mayor a la máxima

posible, así como la puntuación del SCO.

La página 4 muestra un vídeo sobre cómo exportar un banco de preguntas desde el LMS e incluirlo en un fichero HTML para la creación de test con la ayuda de las

variables JavaScript que en él aparecen. También se ve en el vídeo el test que se crea.

Un fragmento de esta página aparece en la figura 64. Al pulsar el botón de Ir a

autoevaluación y antes de pasar a la página 5, se establece el elemento

CompletionStatus a completado, ya que se entiende que el alumno ha visto el vídeo y

SuccessStatus a aprobado, sin necesidad de ninguna puntuación en este caso.

Figura 64: Captura parcial de la página 4 del módulo SCORM 2004. Fuente: Propia.

Por último, la página 5 es una página de autoevaluación. En ella aparece un

cuadro, el cual se muestra en la figura 65 y en el que se puede elegir todos los parámetros necesarios para la creación de un test. Este test según la opción elegida, se

comunicará o no con el LMS.

Si se elige la librería creaPreguntas2004.js el funcionamiento sería el mismo que

el comentado para la página 3, es decir, añadiendo cada interacción en el sistema con la

aparición de cada pregunta y más tarde incluyendo las respuestas dadas por el alumno.

66

Si por el contrario se elige la librería creaPreguntasSinCom.js las respuestas

dadas por el alumno así como las latencias de cada pregunta, serían administradas

localmente por la librería, ya que esta, no se comunica con el LMS.

Figura 65: Captura parcial de la página 5 del módulo SCORM 2004. Fuente: Propia.

La puntuación que se obtenga en este test será la puntuación del SCO y se

utilizará para dar por aprobado o suspenso el mismo. Cuando el alumno termine el test

puede ver su desempeño en un cuadro resumen que aparece bajo el test y tras ello

pulsar el botón de Finalizar módulo para establecer a completado CompletionStatus y

salir del módulo.

Al finalizar un alumno el módulo, es posible ver información sobre su desempeño en Ilias, en el apartado Datos de Seguimiento de la configuración del módulo. Así por

ejemplo, si aplicamos el filtro de Informe básico de éxito nos aparece la información

contenida en la figura 66:

Figura 66: Informe básico de éxito. Fuente: Ilias.

En este informe, podemos ver el título del módulo, el nombre del usuario que lo ha

realizado, el estado de superación, el número de intentos que ha hecho, el número de

SCOs completados y el número de SCOs superados. Además, de esta información, se

67

podría ver también el identificador del módulo, la versión del módulo, el tiempo total en

segundos o el último acceso realizado entre otros.

Si ahora aplicamos el filtro de informe básico de capítulos (SCO) nos sale el

cuadro que aparece en la figura 67:

Figura 67: Informe básico de capítulos (SCO). Fuente: Ilias.

Este informe nos muestra el título del capítulo, el nombre del usuario, la

puntuación obtenida en cada uno, el tiempo empleado en la última sesión en segundos,

el estado de éxito del SCO y la fecha del último acceso. Aparte de estas columnas, al

igual que en el informe anterior, se podrían mostrar algunas más sobre la actuación del

alumno en el módulo, pero que en este caso, no se han seleccionado. Otro filtro aplicable dentro del apartado de Datos de seguimiento sería el de

informe básico de interacciones, el cual muestra todas las interacciones que se han

almacenado en el LMS durante el módulo. En este caso, todas se produjeron durante la

realización de la página 3.

En la figura 68 podemos ver 10 interacciones, cada una con su título, un

identificador de usuario, su identificador, una descripción que se corresponde con el título

de la pregunta, el tipo de interacción de la que se trata, el resultado de la respuesta dada

por el alumno y la respuesta que dio el alumno. Otras columnas como el título del módulo

de aprendizaje, su identificador, el email del alumno o su departamento se han obviado

por considerarse poco interesantes para la captura.

68

Figura 68: Informe básico de interacciones. Fuente: Propia.

El último informe que se puede consultar sería el de Objetivos globales del

sistema (usado en la secuenciación). La figura 69 muestra este informe, en el que

podemos observar de nuevo el título del módulo de aprendizaje, el nombre del usuario, el

estado total del módulo y el estado de éxito.

Figura 69: Informe de objetivos globales del sistema. Fuente: Ilias.

Todos los informes anteriores, formarían parte de los informes por usuario, pero

también podemos obtener los informes por capítulo. En este sentido, para un mismo

capítulo, podemos obtener los siguientes informes:

Informe básico de capítulos (SCO).

Informe básico de interacciones.

Informe de objetivos.

Evaluación por interacción.

Evaluación por usuario.

Respuestas de los usuarios.

A continuación, la figura 70 muestra una captura del informe de evaluación por

usuario para la página 3 del módulo:

69

Figura 70: Informe de evaluación por usuario para la página 3 del módulo. Fuente: Ilias.

En esta captura, se aprecia el título del SCO, el nombre del usuario, las

interacciones contestadas correctamente así como el porcentaje que representan dentro

del SCO, las interacciones contestadas incorrectamente y su porcentaje y la puntuación

obtenida.

El informe de evaluación por interacción también es bastante interesante. La

figura 71 muestra un posible aspecto del mismo:

Figura 71: Informe de evaluación por interacción para la página 5. Fuente: Ilias.

Igualmente, en la pestaña Progreso de aprendizaje de la configuración del

módulo, también es posible ver un cuadro con información sobre el alumno, sus accesos

al módulo o su estado entre otros. La figura 72 presenta esta información.

Figura 72: Progreso de aprendizaje del módulo. Fuente: Ilias.

Echando un simple vistazo rápido al cuadro, podemos ver por ejemplo si el alumno ha accedido o aún no al módulo, como sería el caso de Fernando. También

70

puede verse si lo ha superado o no, el tiempo que ha empleado y las fechas del primer y

del último acceso. Estas y otras columnas son configurables por parte del administrador

del espacio, por lo que se pueden quitar o añadir al gusto.

71

Como se ha explicado en el comienzo del capítulo anterior, el resultado del

presente TFG ha sido la creación de cuatro librerías JavaScript, una para cada versión

del estándar SCORM, una para autoevaluación y una última de apoyo para la creación de tests. Asimismo, también se han desarrollado dos módulos basados en la estructura de

contenidos SCORM para hacer una demostración de sus posibles usos y se han

integrado de manera exitosa en el LMS de la Universidad de Jaén.

De este modo, se han obtenido una serie de herramientas útiles para su uso en el

ámbito de la enseñanza online y aptas para cualquier tema de evaluación de cualquier

asignatura. Además, se han desarrollado de acuerdo con estándares, como lo son

SCORM y QTI, lo que asegura su interoperabilidad y portabilidad a través de los distintos

LMSs del mercado.

Este trabajo, además, permite realizar una enseñanza personalizada en el alumno

y ofrecer la posibilidad de modificar hasta seis parámetros del mismo. Finalmente, se ha

dado soporte hasta a cuatro tipos de preguntas diferentes que han sido las siguientes:

Opción múltiple respuesta única.

Opción múltiple respuesta múltiple.

Numérica.

De rellenar huecos.

Ha sido así, como se han cumplido todos los objetivos marcados al inicio del TFG

y además, se han proporcionado algunas funcionalidades extra que no estaban

contempladas en la propuesta del TFG, como la posibilidad de utilizar librerías para

autoevaluación y la obtención de las preguntas a partir de un fichero XML que cumpla el

estándar QTI.

Asimismo, gracias al desarrollo realizado durante la elaboración de este TFG, el

autor del mismo ha desarrollado una serie de competencias y habilidades:

- Evolución de los conocimientos sobre la programación en JavaScript.

- Evolución de los conocimientos sobre la programación en HTML.

- Progreso en el uso de las hojas de estilo para dar estilo a las páginas

web.

- Aprendizaje y uso de módulos con contenidos basados en SCORM.

- Estudio y uso de ficheros XML acordes a la norma QTI.

- Aprendizaje y uso de las herramientas para desarrolladores en tres

navegadores distintos para resolver errores de programación.

- Aprendizaje de términos como LMS o SCO.

72

- Aprendizaje y uso de funciones para comunicarse con el LMS.

- Desarrollo de la capacidad de transformar indicaciones y sugerencias en

acciones.

- Mejora de la capacidad de solucionar problemas de distinto grado de

dificultad.

- Mejora de la capacidad de trabajo.

- Mejora de la capacidad de organización.

- Mejora de la capacidad de sacrificio.

- Realización personal.

En cuanto a las líneas de futuro se podrían añadir elementos gráficos a los tipos

de preguntas ya soportados o por otro lado, dar soporte a más tipos de preguntas (de

ordenación, de unir parejas, de mapa de imagen…).

Además, se podría usar la secuenciación de SCORM en conjunción con los

resultados que obtiene el alumno para personalizar su experiencia a lo largo del módulo

así como los SCOs que se le ofrecen.

Para finalizar, se debería tener en cuenta la posibilidad de ampliar este trabajo

haciéndolo compatible con otros estándares de e-learning. Al hacer este trabajo

escalable, sería cuestión de estudiar los nuevos estándares y realizar una librería para

cada norma. Una vez hecho esto, simplemente habría que modificar la librería de apoyo principal.js para añadirlas y darles soporte.

Entre los nuevos estándares de e-learning vamos a destacar los siguientes: LTI (Learning Tools Interoperability), Experience API o xAPI y cmi5 (“Versiones SCORM,”

12/08/2016.).

LTI se utiliza principalmente en sistemas LMSs de centros educativos. Se trata de una arquitectura plug-in que permite a los LMSs trabajar con herramientas de aprendizaje

remotas.

Con fecha de lanzamiento el 26 de abril de 2013, Experience API es la nueva

versión de SCORM y resuelve muchos de los problemas que eran inherentes a versiones

anteriores de SCORM. El aprendizaje móvil, aprendizaje en equipo, la eliminación de la

necesidad de un navegador web o simulaciones de juegos son solo algunos aspectos

que ahora son relativamente fáciles de conseguir.

cmi5 sería el último en aparecer, ya que lo hizo el 1 de junio de 2016. Es una

especificación complementaria a xAPI. Proporciona un conjunto de normas destinadas a

lograr la interoperabilidad en un entorno tradicional de LMS, y utiliza a xAPI como

protocolo de comunicación y formato de datos. cmi5 incluye el concepto de una sesión de

73

aprendizaje y tiene normas específicas para la captura de un conjunto básico de datos

para las experiencias de aprendizaje.

cmi5 está listo para su adopción, pero aún está muy limitado. Eso sí, si se tiene un

sistema de aprendizaje LMS y se desea flexibilidad y la conveniencia de datos a largo

plazo de xAPI, se debe considerar su adopción.

74

Es en este capítulo en el que se han incluido el índice de figuras y la relación de

siglas utilizadas a lo largo de este TFG. Además, también se han incorporado las cuatro

librerías JavaScript implementadas así como las dos hojas de estilo utilizadas.

8.1 Índice de figuras Figura 1: Estructura básica documento HTML.

Fuente:http://www.hipertexto.info/documentos/html.htm ........................................................ 11

Figura 2: Árbol de objetos HTML DOM. Fuente: http://www.w3schools.com/js/js_htmldom.asp . 13

Figura 3: Gráfico estándares. Fuente:

http://www.cyta.com.ar/elearn/editor_digital/curso_archivos/m4_a2_lectura/xml_0.htm ......... 14

Figura 4: Ejemplo fichero XML. Fuente:

http://social.technet.microsoft.com/wiki/contents/articles/27147.windows-phone-how-to-

create-a-data-class-from-an-xml-document.aspx ......................................................................... 15

Figura 5: Esquema de la estructura básica adoptada por el QTI IMS. Fuente: (Tobergte & Curtis,

2013). .......................................................................................................................................... 17

Figura 6: Casos de uso del sistema de exámenes. Fuente: (Tobergte & Curtis, 2013). ................... 17

Figura 7: Icono de Notepad++. Fuente: http://www.transitionblog.com/alternatives-to-notepad/

.................................................................................................................................................... 27

Figura 8: Estructura conceptual del contenido. Fuente: (Advanced Distributed Learning, 2009). .. 29

Figura 9: Métodos del API SCORM 2004. Fuente:(“Run-Time SCORM,” 03/08/2016). ................... 30

Figura 10: Métodos del API SCORM 1.1/SCORM 1.2. Fuente: (“Run-Time SCORM,” 03/08/2016). 30

Figura 11: Ilustración del API, instancia del API y la implementación del API. Fuente: (Advanced

Distributed Learning, 2009) .......................................................................................................... 32

Figura 12: Transiciones de estados de la instancia del API. Fuente: (Advanced Distributed Learning,

2009) ........................................................................................................................................... 32

Figura 13: Esquema presentación de SCOs a los alumnos. Fuente:

http://eduworks.com/LOTT/Tutorial/scormconcepts.html ........................................................... 34

Figura 14: Herramienta para desarrolladores en Google Chrome. Fuente: Propia. ........................ 35

Figura 15: Herramienta para desarrolladores en Mozilla Firefox. Fuente: Propia. ......................... 35

Figura 16: Herramienta de desarrolladores en Internet Explorer. Fuente: Propia. ......................... 36

Figura 17: Línea para importar la librería principal.js. ................................................................... 36

Figura 18: Ejemplo de inicialización de variables JavaScript en páginas web. Fuente: Propia......... 37

Figura 19: Ejemplo de llamada a la función loadJsLib. Fuente: Propia. .......................................... 37

Figura 20: Uso de la función loadScript. Fuente: Propia. ............................................................... 38

Figura 21: Contenido minimizado de un banco de preguntas acorde con QTI. Fuente: Propia....... 39

Figura 22: Obtención del div preguntas del documento HTML. Fuente: Propia. ............................ 39

Figura 23: Obtención de las preguntas del fichero XML. Fuente: Propia........................................ 40

Figura 24: Obtención del título y la descripción de la pregunta. Fuente: Propia. ........................... 40

Figura 25: Obtención del tipo y del identificador de la pregunta. Fuente: Propia. ......................... 40

Figura 26: Obtención de las opciones, los puntos y llamada a la función única. Fuente: Propia. .... 41

Figura 27: Obtención de las opciones, los puntos y la llamada a la función múltiple. Fuente: Propia.

.................................................................................................................................................... 41

Figura 28: Acciones a realizar en caso de pregunta de tipo numérica. Fuente: Propia. .................. 41

Figura 29: Obtención de las distintas variables para un pregunta de rellenar huecos. Fuente:

Propia. ......................................................................................................................................... 42

75

Figura 30: Método innerHTML para agregar contenido al documento HTML. Fuente: Propia. ...... 42

Figura 31: Función fisher_yates para aleatorizar un array. Fuente: Propia. ................................... 44

Figura 32: Botones bajo cada pregunta. Fuente: Propia. ............................................................... 44

Figura 33: Div con la puntuación del alumno. Fuente: Propia. ...................................................... 45

Figura 34: Mensaje que aparece al acertar una pregunta. Fuente: Propia. .................................... 45

Figura 35: Ejemplo de pregunta de rellenar huecos. Fuente: Propia. ............................................ 46

Figura 36: Fragmento objeto RTE de la librería RTE.js. Fuente: RTE.js. .......................................... 47

Figura 37: Uso de estas funciones en la librería creaPreguntas2004.js. Fuente: Propia. ................ 48

Figura 38: Uso de funciones para interacciones con el LMS en creaPreguntas2004.js. Fuente:

Propia. ......................................................................................................................................... 48

Figura 39: Uso de la función getLearnerName en creaPreguntas2004.js. Fuente: Propia. ............. 48

Figura 40: Uso de funciones para añadir una interacción al LMS en creaPreguntas12.js. Fuente:

Propia. ......................................................................................................................................... 49

Figura 41: Uso de la función setInteractionsResponseAll en la creaPreguntas12.js. Fuente: Propia.

.................................................................................................................................................... 50

Figura 42: Uso de la función getLearnerName en la librería creaPreguntas12.js. Fuente: Propia. .. 50

Figura 43: Interacciones y datos de interacción. Fuente: (Advanced Distributed Learning, 2009). . 51

Figura 44: Acceso a espacios virtuales de la Universidad de Jaén. Fuente: ev.ujaen.es.................. 53

Figura 45: Objetivos secundarios que aparecen en la página dos del módulo. Fuente: Propia. ...... 54

Figura 46: Cuadro resumen de la página 3. Fuente: Propia. .......................................................... 55

Figura 47: Vídeo incluido en la página 4 del módulo. Fuente: Propia. ........................................... 56

Figura 48: Formulario de la página 5 del módulo. Fuente: Propia. ................................................ 57

Figura 49: Árbol de navegación en el LMS para la versión 1.2 de SCORM. Fuente: Ilias. ................ 57

Figura 50: Debugger del LMS para la versión 1.2 de SCORM. Fuente: Ilias. ................................... 57

Figura 51: Creación de archivo de exportación QTI. Fuente: Ilias. ................................................. 58

Figura 52: Ficheros que aparecen al descomprimir el archivo descargado. Fuente: Propia. ........... 58

Figura 53: Cabecera del documento HTML. Fuente: Propia. ......................................................... 59

Figura 54: Inicialización de variables para creación de tests. Fuente: Propia. ................................ 59

Figura 55: Llamada a la función LoadJsLib. Fuente: Propia. ........................................................... 59

Figura 56: Botón de terminación de test en página 3 del módulo SCORM. Fuente: Propia. ........... 60

Figura 57: Botones bajo cada pregunta. Fuente: Propia. ............................................................... 60

Figura 58: Estilo para el elemento h3 en estilo.css. Fuente: Propia. .............................................. 60

Figura 59: Panel de control de XAMPP. Fuente: XAMPP. .............................................................. 61

Figura 60: Ejemplo de punto de ruptura en la herramienta para desarrolladores de Google

Chrome. Fuente: Google Chrome. ................................................................................................ 62

Figura 61: Captura parcial de la página 1 del módulo SCORM 2004. Fuente: Propia. ..................... 63

Figura 62: Informe de objetivos para la página 1 del módulo. Fuente: Ilias. .................................. 64

Figura 63: Captura parcial de la página 2 del módulo SCORM 2004. Fuente: Propia. ..................... 64

Figura 64: Captura parcial de la página 4 del módulo SCORM 2004. Fuente: Propia. ..................... 65

Figura 65: Captura parcial de la página 5 del módulo SCORM 2004. Fuente: Propia. ..................... 66

Figura 66: Informe básico de éxito. Fuente: Ilias. .......................................................................... 66

Figura 67: Informe básico de capítulos (SCO). Fuente: Ilias. .......................................................... 67

Figura 68: Informe básico de interacciones. Fuente: Propia. ......................................................... 68

Figura 69: Informe de objetivos globales del sistema. Fuente: Ilias. .............................................. 68

Figura 70: Informe de evaluación por usuario para la página 3 del módulo. Fuente: Ilias. ............. 69

Figura 71: Informe de evaluación por interacción para la página 5. Fuente: Ilias. .......................... 69

Figura 72: Progreso de aprendizaje del módulo. Fuente: Ilias. ...................................................... 69

76

8.2 Siglas

ADL Advanced Distributed Learning

API Application Programming Interface

CERN Conseil Européen pour la Recherche Nucléaire

CSS Cascading Style Sheets

DOM Document Object Model

ECMA European Computer Manufacturers Association

HTML Hypertext Markup Language

IDE Integrated Development Environment

IEEE Institute of Electrical and Electronics Engineers

LMS Learning Management System

LTI Learning Tools Interoperability

PIF Package Interchange Format

QTI Question & Test Interoperability

RTE Run-Time Environment

SCO Sharable Content Object

SCORM Sharable Content Object Reference Model

SGA Sistema de Gestión de Aprendizaje

SGML Standard Generalized Markup Language

TFG Trabajo Fin de Grado

TIC Tecnologías de la Información y Comunicaciones

URL Uniform Resource Locator

W3C World Wide Web Consortium

WWW World Wide Web

XML eXtensible Markup Language

8.3 Librería JavaScript creaPreguntas2004.js

/* creaPreguntas2004.js JavaScript library by Sergio Díaz

([email protected])

Librería desarrollada por Sergio Díaz Fuentes. Se permite cualquier

explotación de la obra, incluyendo una finalidad comercial, así

como la creación de obras derivadas, la distribución de las cuales

también está permitida sin ninguna restricción.

*/

var puntosPreguntas = new Array ();

var puntuacion = 0;

var minimo = 0;

var total = 0;

77

var longitudPreg = 0;

var auxiliarCont = 0;

var contador = 0;

var auxiliarResp = ["a","b","c","d","e","f","g","h","i","j"];

var miTiempo="";

rellenaPuntos();

/*********************************************************************

**********

**

** Function: loadXMLDoc()

** Inputs: The name of the xml file in which the questions are, the

number of questions of the test, time for the test, random questions,

random options and type of comunication.

** Return: The number of requested test questions presented to the

user with the chosen features.

**

** Description:

** Requests the xml file and presents the questions.

**

**********************************************************************

*********/

function loadXMLDoc (xmlName,numPreguntas,time,aleaPr, aleaOp) {

var xmlhttp;

if (window.XMLHttpRequest) {

xmlhttp = new XMLHttpRequest();

} else {

// code for older browsers

xmlhttp = new ActiveXObject("Microsoft.XMLDOM");

}

xmlhttp.onreadystatechange = function() {

if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

var xmlDoc = xmlhttp.responseXML;

if (xmlDoc != null){

ponDiv();

ponPuntuacion();

if (parseInt(time) > 0){

if (time.toString().length < 2)

var t = "0" + time + ":00";

else

var t = time + ":00";

ponTime(t);

}

if (time != 0)

miTiempo = setInterval( function(){cuentAtras()},

1000);

// En la variable div_preguntas obtenemos el contenedor

div con el id 'preguntas'

var div_preguntas =

document.getElementById('preguntas');

// Obtenemos la lista de preguntas

var preguntas_set =

xmlDoc.getElementsByTagName("questestinterop")[0].getElementsByTagName

("item");

longitudPreg = preguntas_set.length;

78

// Comprobamos que el número de preguntas sea igual o

inferior al número de preguntas disponibles

if (numPreguntas > preguntas_set.length){

numeroPr = preguntas_set.length;

}

//Mezclamos un array auxiliar que nos servirá para

obtener las preguntas de forma aleatoria

if (aleaPr.localeCompare("si") == 0)

var arrayaux = arrayAlea(preguntas_set);

else

var arrayaux = obtenArray(preguntas_set.length);

while (auxiliarCont < numeroPr){

// Obtenemos el título y la descripción de la pregunta

var titulo =

preguntas_set[arrayaux[auxiliarCont]].getAttribute("title");

var

descripcion=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName

("mattext")[0].childNodes[0].nodeValue;

if (descripcion.indexOf("&lt;p&gt;") == -1)

descripcion = "<p>"+descripcion+"</p>";

//Obtenemos el tipo de la pregunta

var

tipo=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("qtime

tadatafield")[1].getElementsByTagName("fieldentry")[0].childNodes[0].n

odeValue;

//Obtenemos el identificador de la pregunta

var

id=preguntas_set[arrayaux[auxiliarCont]].getAttribute("ident");

//Inicializamos un par de flags auxiliares a cero

var flag=0;

var flagE = 0;

var patt = "";

//Inicializamos las opciones de la pregunta a un string

vacío

var opciones="";

if(tipo=="SINGLE CHOICE QUESTION"){

// Obtenemos las opciones de la pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los puntos de cada opcion

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar");

opciones +=

unica(opciones_set,id,puntos,contador);

total += sumaPuntos(puntos, tipo, 0);

minimo += restaPuntos(puntos, tipo,

0);

flagE = 1;

} else

if(tipo=="NUMERIC QUESTION"){

79

//Obtenemos los límites

superior e inferior para la pregunta

var sup =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varlte")[0

].childNodes[0].nodeValue;

var inf =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("vargte")[0

].childNodes[0].nodeValue;

//Obtenemos el numero máximo de

caracteres permitidos

var maxcar =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("render_fib

")[0].getAttribute("maxchars");

//Obtenemos los puntos en caso

de acierto

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar")[0

].childNodes[0].nodeValue;

opciones +=

numerica(id,sup,inf,puntos,maxcar,contador);

total += parseFloat(puntos);

flagE = 1;

} else

if(tipo=="MULTIPLE CHOICE

QUESTION"){

// Obtenemos las

opciones de la pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los

puntos de cada opcion

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar");

opciones +=

multiple(opciones_set,id,puntos,contador);

//patt =

getCorrectaMul (opciones_set,puntos);

total +=

sumaPuntos(puntos, tipo, 0);

minimo +=

restaPuntos(puntos, tipo, 0);

flagE = 1;

} else

if(tipo=="CLOZE

QUESTION"){

var

opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

var

huecosString =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_s

tr");

var huecosNum

=

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_n

um");

var oposibles

=

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varequal")

; //Opciones posibles

80

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar");

//Puntos de las distintas opciones

var nHuecos =

huecosString.length + huecosNum.length; //Número total de huecos

var tagNot =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("not");

var arr =

[].slice.call(oposibles);

var arrMat =

[].slice.call(opciones_set);

if

(tagNot.length != 0){

var osobrantes

= tagNot[0].getElementsByTagName("varequal").length;

arr.length =

oposibles.length - osobrantes * 2;

arrMat.length

= opciones_set.length - osobrantes;

}

var gapsT = 0;

var gapsN = 0;

descripcion="";

total +=

sumaPuntos(puntos,tipo, oposibles);

minimo +=

restaPuntos(puntos, tipo, oposibles);

var w = 0;

do{

if

(arrMat[w].childNodes[0].nodeValue.indexOf("&lt;p&gt;") != -1)

descripcion += arrMat[w].childNodes[0].nodeValue; //La primera

nunca será un hueco

else{

if (w==0)

descripcion += "<p>" +

arrMat[w].childNodes[0].nodeValue.replace(/<br \/>/g,

'').replace(/<\/?span[^>]*>/g,"");

else if

(w == arrMat.length - nHuecos - 1)

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"") +"</p>";

else

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"");

}

if

(gapsT + gapsN < nHuecos){

if (huecosString.length != 0 && gapsT < huecosString.length){

81

if

(huecosString[gapsT].getAttribute("ident").indexOf(gapsT + gapsN) != -

1){

if

(huecosString[gapsT].getElementsByTagName("render_choice").length ==

1){

descripcion +=

creaDesplegable(id,arr,puntos,gapsT + gapsN);

w = w +

huecosString[gapsT].getElementsByTagName("render_choice")[0].getElemen

tsByTagName("mattext").length;

}

else{

descripcion+="<input type=\"text\"

name=\""+id+"\" class=\"huecoT\"

tipo=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("fibtype")+"\"

sensibilidad=\""+preguntas_set[arrayaux[auxiliarCont]].getElementsByTa

gName("fieldentry")[5].childNodes[0].nodeValue+"\" puntos=\"\"

sol=\"\"

size=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("columns")+"\"

maxlength=\""+parseInt(huecosString[gapsT].getElementsByTagName("rende

r_fib")[0].getAttribute("columns"))+"\"/>";

}

gapsT++;

w++;

} else if

(huecosNum[gapsN].getAttribute("ident").indexOf(gapsT + gapsN) != -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

if (min == null)

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\"

name=\""+id+"\" class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

82

gapsN++;

w++;

}

}

else if (huecosNum[gapsN].getAttribute("ident").indexOf(gapsT +

gapsN) != -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

if (min == null)

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\" name=\""+id+"\"

class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

gapsN++;

w++;

}

}

else

w++;

} while

(w < arrMat.length - nHuecos);

if (contador + 1 !=

numeroPr && auxiliarCont + 1 < preguntas_set.length)

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+contador+"\" type=\"button\"

value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+contador+")\"/></form>";

else

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/></form>";

83

// Modificamos el

contenido html del contenedor div

if (contador==0)

div_preguntas.innerHTML += "<div id=\"div"+contador+"\"

style=\"display:block;\"><form id=\"formulario"+contador+"\"

tipo=\""+tipo+"\"><h3>" + parseInt(contador+1) + ". " + titulo +

"</h3>" + descripcion +"</form></div>";

else

div_preguntas.innerHTML += "<div id=\"div"+contador+"\"

style=\"display:none;\"><form id=\"formulario"+contador+"\"

tipo=\""+tipo+"\"><h3>" + parseInt(contador+1) + ". " + titulo +

"</h3>" + descripcion + "</form></div>";

for (var q = 0; q <

nHuecos; q++){

for (var k =

0; k < oposibles.length; k++){

if

(oposibles[k].getAttribute("respident").indexOf(q) != -1 &&

document.getElementsByName(id)[q].getAttribute("tipo")){

if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"String") == 0){

var attAnterior =

document.getElementsByName(id)[q].getAttribute("sol");

var puntAnterior =

document.getElementsByName(id)[q].getAttribute("puntos");

document.getElementsByName(id)[q].setAttribute("sol",attAnterior+op

osibles[k].childNodes[0].nodeValue+",");

document.getElementsByName(id)[q].setAttribute("puntos",puntAnterio

r + puntos[k].childNodes[0].nodeValue+",");

}

else if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"Decimal") == 0)

document.getElementsByName(id)[q].setAttribute("puntos",

puntos[k].childNodes[0].nodeValue);

}

}

}

if (contador ==0){

patt =

getCorrectaInv(id,tipo);

miRTE.setInteractionsAddNewI(contador,id,"fill-

in",1,patt.toLowerCase());

miRTE.setInteractionsDescription(contador, titulo);

84

}

flag = 1;

//Este flag nos permite que no se repita la pregunta de nuevo en el

div_preguntas

flagE = 1;

}

// Modificamos el contenido html del contenedor div

if(flag == 0 && flagE == 1){

if (contador == 0){

div_preguntas.innerHTML += "<div

id=\"div"+contador+"\" style=\"display:block;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

opciones+"</form></div>";

patt = getCorrectaInv(id,tipo);

switch (tipo){

case "SINGLE CHOICE QUESTION":

miRTE.setInteractionsAddNewI(contador,id,"choice",1, patt);

miRTE.setInteractionsDescription(contador,titulo);

break;

case "MULTIPLE CHOICE QUESTION":

miRTE.setInteractionsAddNewI(contador,id,"choice",1, patt);

miRTE.setInteractionsDescription(contador,titulo);

break;

case "NUMERIC QUESTION":

miRTE.setInteractionsAddNewI(contador,id,"numeric",1, patt);

miRTE.setInteractionsDescription(contador,titulo);

break;

default:

console.log("No es un tipo

válido de pregunta");

}

} else

div_preguntas.innerHTML += "<div

id=\"div"+contador+"\" style=\"display:none;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

opciones+"</form></div>";

}

if(flagE == 1){

auxiliarCont++;

contador++;

} else{

auxiliarCont++;

}

if (auxiliarCont >= preguntas_set.length)

break;

}

85

actualiza(puntuacion,total);

}

}

};

xmlhttp.open("GET", xmlName, true);

xmlhttp.send();

}

/*********************************************************************

**********

**

** Function: ponDiv()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a div tag to the html page.

**

**********************************************************************

*********/

function ponDiv(){

var midiv = document.createElement("div");

midiv.setAttribute("id","preguntas");

midiv.setAttribute("class","desplazado");

document.getElementById('tabla').appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: ponPuntuacion()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a puntuation div tag to the html page.

**

**********************************************************************

*********/

function ponPuntuacion(){

var midiv = document.createElement("div");

midiv.setAttribute("id","divPuntuacion");

midiv.setAttribute("class","cuadroFijo");

var mipar = document.createElement("p");

mipar.setAttribute("align","center");

var t = document.createTextNode("Puntuación");

mipar.appendChild(t);

var centr = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("value","");

cuadro.setAttribute("id","tupuntuacion");

cuadro.setAttribute("size","3");

cuadro.setAttribute("maxlength","3");

cuadro.setAttribute("readonly","true");

cuadro.setAttribute("class","altura puntos");

centr.appendChild(cuadro);

midiv.appendChild(mipar);

midiv.appendChild(centr);

document.body.appendChild(midiv);

86

}

/*********************************************************************

**********

**

** Function: ponTime()

** Inputs: The time in minutes of test performance.

** Return: None.

**

** Description:

** It adds a time div tag to the html page.

**

**********************************************************************

*********/

function ponTime(time){

var midiv = document.createElement("div");

midiv.setAttribute("id","divTiempo");

midiv.setAttribute("class","cuadroFijoT");

var para = document.createElement("p");

para.setAttribute("align","center");

var t = document.createTextNode("Tiempo");

para.appendChild(t);

var centrado = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("id","tutiempo");

cuadro.setAttribute("value",time); cuadro.setAttribute("size","3");

cuadro.setAttribute("class","altura puntos");

cuadro.setAttribute("readonly","true");

cuadro.setAttribute("maxlength","3");

centrado.appendChild(cuadro);

midiv.appendChild(para);

midiv.appendChild(centrado);

document.body.appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: cuentAtras()

** Inputs: None.

** Return: None.

**

** Description:

** It decrements the clock.

**

**********************************************************************

*********/

function cuentAtras(){

var cad = document.getElementById("tutiempo").value;

var minutos = parseInt(cad.substring(0,2));

var segundos = parseInt(cad.substring(3,cad.length));

segundos--;

if (segundos < 0){

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0){

clearInterval(miTiempo);

87

deshabilitar();

smoke.alert("Lo sentimos "+miRTE.getLearnerName().split("

")[0]+". Se ha acabado tu tiempo para responder");

} else{

if (minutos.toString().length < 2)

minutos = "0" + minutos;

if (segundos.toString().length < 2)

segundos = "0" + segundos;

document.getElementById("tutiempo").value = minutos

+":"+segundos;

}

}

/*********************************************************************

**********

**

** Function: arrayAlea()

** Inputs: An array of questions.

** Return: An array with unordered values depending of the length of

de questions array.

**

** Description:

** It mixes a vector of sorted values.

**

**********************************************************************

*********/

function arrayAlea(array){

var unArray=[];

for(var z=0;z<array.length;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

/*********************************************************************

**********

**

** Function: obtenArray()

** Inputs: An integer.

** Return: An array of ordered values with variable length depending

of the integer.

**

** Description:

** It creates an ordered array.

**

**********************************************************************

*********/

function obtenArray(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

return unArray;

}

/*********************************************************************

**********

**

** Function: arrayAleaEnt()

88

** Inputs: An integer.

** Return: An array with unordered values with variable length

depending of the integer.

**

** Description:

** It creates an unordered array.

**

**********************************************************************

*********/

function arrayAleaEnt(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

/*********************************************************************

**********

**

** Function: rellenaPuntos()

** Inputs: None.

** Return: None.

**

** Description:

** It initializes the points vector called puntosPreguntas.

**

**********************************************************************

*********/

function rellenaPuntos(){

for (var j=0; j < numeroPr; j++){

puntosPreguntas[j] = 0;

}

}

/*********************************************************************

**********

**

** Function: fisher_yates()

** Inputs: An array of sorted values.

** Return: The function returns the untidy vector.

**

** Description:

** It mixes a vector of sorted values according to Fisher-Yates

algorithm.

**

**********************************************************************

*********/

function fisher_yates(array){

var i=array.length;

while(i--){

var j=Math.floor( Math.random() * (i+1) );

var tmp=array[i];

array[i]=array[j];

array[j]=tmp;

}

}

/*********************************************************************

**********

89

**

** Function: sumaPuntos()

** Inputs: An array with the points of a question, the type of the

question and the number of gaps.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this question..

**

**********************************************************************

*********/

function sumaPuntos(pts,tipo,ngaps){

var suma= 0;

var cont = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

suma+=Math.max.apply(null, arr);

} else if (tipo.localeCompare("CLOZE QUESTION") == 0){

suma += getPuntos(pts,ngaps);

} else {

for(var z=0;z<pts.length;z++){

if (parseFloat(pts[z].childNodes[0].nodeValue) > 0)

suma+=parseFloat(pts[z].childNodes[0].nodeValue);

}

}

return suma;

}

/*********************************************************************

**********

**

** Function: restaPuntos()

** Inputs: An array with the points of a question, the type of the

question and the number of gaps.

** Return: The minimum points for this question.

**

** Description:

** It calculates the minimumm number of points for this question..

**

**********************************************************************

*********/

function restaPuntos(pts,tipo,ngaps){

var resta= 0;

var cont = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

resta+=Math.min.apply(null, arr);

} else if (tipo.localeCompare("CLOZE QUESTION") == 0){

resta += getPuntosResta(pts,ngaps);

} else {

for(var z=0;z<pts.length;z++){

if (parseFloat(pts[z].childNodes[0].nodeValue) < 0)

resta+=parseFloat(pts[z].childNodes[0].nodeValue);

}

}

90

return resta;

}

/*********************************************************************

**********

**

** Function: getPuntos()

** Inputs: An array with the points of a question and the number of

gaps.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this cloze question.

**

**********************************************************************

*********/

function getPuntos(pts,ngaps){

cont = 0;

cont2 = 0;

z=0;

suma = 0;

var arr = [];

do{

if (ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

else if (ngaps[z].getAttribute("respident").indexOf(cont) ==

-1){

suma+=Math.max.apply(null, arr);

arr = [];

cont++;

cont2 = 0;

if

(ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

}

z++;

if (z == pts.length)

suma+=Math.max.apply(null, arr);

} while (z < pts.length);

return suma;

}

/*********************************************************************

**********

**

** Function: getPuntosResta()

** Inputs: An array with the points of a question and the number of

gaps.

** Return: The minimum points for this question.

**

** Description:

** It calculates the minimum number of points for this cloze question.

**

91

**********************************************************************

*********/

function getPuntosResta(pts,ngaps){

cont = 0;

cont2 = 0;

z=0;

resta = 0;

var arr = [];

do{

if (ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) < 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

else if (ngaps[z].getAttribute("respident").indexOf(cont) ==

-1){

if (arr.length != 0)

resta+=Math.min.apply(null, arr);

arr = [];

cont++;

cont2 = 0;

if

(ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) < 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

}

z++;

if (z == pts.length){

if(arr.length != 0)

resta+=Math.min.apply(null, arr);

}

} while (z < pts.length);

return resta;

}

/*********************************************************************

**********

**

** Function: creaDesplegable()

** Inputs: The identifier, the options of the cloze question, the

points of the question and an integer that represents the order of the

question.

** Return: A drop-down list

**

** Description:

** It creates a drop-down list gap.

**

**********************************************************************

*********/

function creaDesplegable (ident, opcs, pts, ent){

if (opAleat.localeCompare("si") == 0)

var unArray = arrayAleaEnt(opcs.length);

else

var unArray = obtenArray (opcs.length);

var lista = "<select name=\""+ident+"\" class=\"desplegable\"

tipo=\"Lista\">";

for (j = 0; j < opcs.length; j++){

92

if (opcs[unArray[j]].getAttribute("respident").indexOf(ent)

!= -1)

lista += "<option

value=\""+opcs[unArray[j]].childNodes[0].nodeValue+"\"

puntos=\""+pts[unArray[j]].childNodes[0].nodeValue+"\"

>"+opcs[unArray[j]].childNodes[0].nodeValue +"</option>";

}

lista += "</select>";

return lista;

}

/*********************************************************************

**********

**

** Function: getCorrecta()

** Inputs: An array with the options of the question and another

array with the points of each option.

** Return: The correct answer of a multiple choice question and only

answer.

**

** Description:

** It obtains the correct answer of a multiple choice question and

only answer.

**

**********************************************************************

*********/

function getCorrecta(opciones_tag, pts){

var corr = "";

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

corr =

normalize(opciones_tag[arr.indexOf(Math.max.apply(null,

arr))+1].childNodes[0].nodeValue);

if (corr.replace(/\s/g,"_").length < 66)

corr = corr.replace(/\s/g,"_");

else

corr =

auxiliarResp[arr.indexOf(Math.max.apply(null, arr),0)];

return corr;

}

/*********************************************************************

**********

**

** Function: getCorrectaMul()

** Inputs: An array with the options of the question and another

array with the points of each option.

** Return: The correct answers of a multiple choice question and

multiple answer.

**

** Description:

** It obtains the correct answer of a multiple choice question and

multiple answer.

**

**********************************************************************

*********/

93

function getCorrectaMul(opciones_tag, pts){

var corr = "";

var corr2 = "";

var count = 0;

for(var z=0;z < pts.length; z = z + 2){

if (parseFloat(pts[z].childNodes[0].nodeValue) > 0){

if (count == 0){

corr =

normalize(opciones_tag[z/2+1].childNodes[0].nodeValue);

corr2 = auxiliarResp[z/2];

} else {

corr =

corr.concat("[,]",normalize(opciones_tag[z/2+1].childNodes[0].nodeValu

e));

corr2 = corr2.concat("[,]",auxiliarResp[z/2]);

}

count++;

}

}

if (corr.replace(/\s/g,"_").length < 66)

return corr.replace(/\s/g,"_");

else

return corr2;

}

/*********************************************************************

**********

**

** Function: unica()

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and only answer.

**

**********************************************************************

*********/

function unica(opciones_tag,ident,pts,ent){

if (opAleat.localeCompare("si") == 0)

var vector = arrayAleaEnt(pts.length);

else

var vector = obtenArray(pts.length);

var opciones="<form name=\"formulario\">";

for (var j = 0; j < pts.length; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"puntos=

\""+pts[vector[j]].childNodes[0].nodeValue+"\"/>"+opciones_tag[vector[

j]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

cadena = cadena.replace("</p>",'');

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

94

value=\""+cadena+"\"puntos=\""+pts[vector[j]].childNodes[0].nodeValue+

"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarUni("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+ent+"\" type=\"button\"

value=\"Posponer\" class=\"boton\" onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarUni("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: numerica()

** Inputs: An identifier, two floats, an array with the options

points and two integers.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a numeric question.

**

**********************************************************************

*********/

function numerica(iden,superior,inferior,pts,maxcar,ent){

var opciones="<form name=\"formulario\">";

opciones += "<p class=\"opciones\">Escribe el valor: <input

type=\"text\" name=\""+iden+"\" inf=\""+inferior+"\"

sup=\""+superior+"\" puntos=\""+pts+"\" size=\"3\"

maxlength=\""+parseInt(maxcar)+"\"/></p></br>";

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/><input name=\"posponer"+ent+"\"

type=\"button\" value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: multiple()

95

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and multiple answer.

**

**********************************************************************

*********/

function multiple(opciones_tag,ident,pts,ent){

//Vectores que contendran los puntos de las opciones

var respondidas=[];

var norespondidas=[];

var j=0;

for(var z = 0; z < pts.length; z=z+2){

respondidas[j]=pts[z].childNodes[0].nodeValue;

norespondidas[j]=pts[z+1].childNodes[0].nodeValue;

j++;

}

if (opAleat.localeCompare("si") == 0)

var vector=arrayAleaEnt(pts.length/2);

else

var vector=obtenArray(pts.length/2);

var opciones="<form name=\"formulario\">";

for (var j = 0; j < pts.length/2; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+opciones_tag[vector[j

]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

cadena = cadena.replace("</p>",'');

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\" value=\""+cadena+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+ent+"\" type=\"button\"

value=\"Posponer\" class=\"boton\" onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/>";

return opciones;

}

96

/*********************************************************************

**********

**

** Function: puntuarUni()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a multiple choice question and only answer.

**

**********************************************************************

*********/

function puntuarUni(iden, ent){

var contestada = "";

var contestadaAux = "";

var res = "";

var puntos = 0;

var corr= miRTE.getInteractionsCorrectRespPattern(ent,0);

var opt = document.getElementsByName(iden[0].name);

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion+=

parseFloat(opt[i].getAttribute("puntos"));

puntos =

parseFloat(opt[i].getAttribute("puntos"));

contestada = normalize(opt[i].value);

contestadaAux = auxiliarResp[i];

}

opt[i].disabled = true;

}

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

if (contestada.replace(/\s/g,"_").length <= 66){

if (corr.localeCompare(contestada.replace(/[^a-

zA-Z 0-9.]+/g,' ').replace(/\s/g,"_")) == 0)

res="correct";

else

res="incorrect";

miRTE.setInteractionsResponseAll(ent,contestada.replace(/[^a-zA-Z

0-9.]+/g,' ').replace(/\s/g,"_"),res);

} else{

if

(corr.localeCompare(contestadaAux.replace(/[^a-zA-Z 0-9.]+/g,'

').replace(/\s/g,"_")) == 0)

res="correct";

else

res="incorrect";

miRTE.setInteractionsResponseAll(ent,contestadaAux.replace(/[^a-zA-

Z 0-9.]+/g,' ').replace(/\s/g,"_"),res);

97

}

//Actualizamos la puntuación y mostramos la siguiente

pregunta

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

if (puntos > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(" ")[0]+"! Has conseguido "+puntos+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(" ")[0]+"! ¡A por las siguientes

preguntas!");

muestra(ent);

puntosPreguntas[ent] = puntos;

}

/*********************************************************************

**********

**

** Function: puntuarMul()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a multiple choice question and multiple

answer.

**

**********************************************************************

*********/

function puntuarMul(iden, ent){

var opt=document.getElementsByName(iden[0].name);

var corr= miRTE.getInteractionsCorrectRespPattern(ent,0);

var contestada = "";

var contestadaAux = "";

var res = "";

var puntos = 0;

var count = 0;

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion +=

parseFloat(opt[i].getAttribute("respondida"));

puntos +=

parseFloat(opt[i].getAttribute("respondida"));

if (count == 0){

contestada =

normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

contestadaAux = auxiliarResp[i];

} else {

contestada =

contestada.concat("[,]"+normalize(opt[i].value).replace(/[^a-zA-Z 0-

9.]+/g,' '));

contestadaAux =

contestadaAux.concat("[,]"+auxiliarResp[i]);

}

count++;

}

else {

98

puntuacion +=

parseFloat(opt[i].getAttribute("norespondida"));

puntos +=

parseFloat(opt[i].getAttribute("norespondida"));

}

opt[i].disabled = true;

}

if (contestada.replace(/\s/g,"_").length <= 66){

if

(corr.localeCompare(contestada.replace(/\s/g,"_")) == 0)

res="correct";

else

res="incorrect";

miRTE.setInteractionsResponseAll(ent,contestada.replace(/\s/g,"_"),

res);

} else{

if

(corr.localeCompare(contestadaAux.replace(/\s/g,"_")) == 0)

res="correct";

else

res="incorrect";

miRTE.setInteractionsResponseAll(ent,contestadaAux.replace(/\s/g,"_

"),res);

}

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

//Actualizamos la puntuación y mostramos la siguiente

pregunta

actualiza(puntuacion, total);

puntuacion = Math.round(puntuacion * 100) / 100;

if (puntos > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(" ")[0]+"! Has conseguido "+puntos+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(" ")[0]+"! ¡A por las siguientes

preguntas!");

muestra(ent);

puntosPreguntas[ent] = puntos;

}

/*********************************************************************

**********

**

** Function: puntuarNum()

** Inputs: An identifier, an integer and two floats.

** Return: None.

**

** Description:

** It gets the score of a numeric question.

99

**

**********************************************************************

*********/

function puntuarNum(iden, ent, inf, sup){

var valor = parseFloat(iden.value);

var res = "";

var puntos =

document.getElementsByName(iden.name)[0].getAttribute("puntos");

var points = 0;

if(valor<=sup && valor>=inf){

puntuacion+=parseFloat(puntos);

points = parseFloat(puntos);

res = "correct";

}

else

res = "incorrect";

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

document.getElementsByName(iden.name)[0].disabled = true;

miRTE.setInteractionsResponseAll(ent,valor,res);

//Actualizamos la puntuación y mostramos la siguiente

pregunta

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

if (points > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(" ")[0]+"! Has conseguido "+points+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(" ")[0]+"! ¡A por las siguientes

preguntas!");

muestra(ent);

puntosPreguntas[ent] = points;

}

/*********************************************************************

**********

**

** Function: puntuaHuecos()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a cloze question.

**

**********************************************************************

*********/

function puntuaHuecos(iden, ent){

//Obtenemos la respuestas correctas

var corr= miRTE.getInteractionsCorrectRespPattern(ent,0);

100

var count = 0;

var contestada = "";

var contestada2 = "";

var res = "";

var points = 0;

if (typeof iden.name !== "undefined"){

//Obtenemos los huecos

var opcs = document.getElementsByName(iden.name);

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo del hueco

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos =

opcs[i].getAttribute("puntos");

var sup =

parseFloat(opcs[i].getAttribute("sup"));

var inf =

parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count==0){

contestada += inf+"[:]"+sup;

contestada2 += auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",inf+"[:]"+sup);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

} else {

if (count==0){

contestada += valor;

contestada2 += auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",valor);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

}

} else

if (tipo.localeCompare("Lista") == 0){

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

101

contestada =

contestada.concat("[,]",opcs[i].options[opcs[i].options.selectedIndex]

.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[opcs[i].options.selectedIndex]);

}

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

} else

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del

hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

if (count==0){

contestada += iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k <

opciones.length - 1; k++){

if(opcs[i].value.toLowerCase() == opciones[k].toLowerCase()){

if (k==0){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

else if (opciones[k-

1].toLowerCase() != opciones[k].toLowerCase()){

puntuacion += parseFloat(puntos[k]);

points

+= parseFloat(puntos[k]);

}

}

}

} else

102

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada += iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

break;

}

}

}

//Si tiene distancia de Levenshtein

else {

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

if(min <= dist){

puntuacion +=

parseFloat(puntos[indice]);

points +=

parseFloat(puntos[indice]);

}

}

103

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

}

} else {

//Obtenemos los huecos

var opcs = document.getElementsByName(iden[0].name);

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo de la pregunta

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos = opcs[i].getAttribute("puntos");

var sup = parseFloat(opcs[i].getAttribute("sup"));

var inf = parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count==0){

contestada += inf+"[:]"+sup;

contestada2 += auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",inf+"[:]"+sup);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

} else {

if (count==0){

contestada += valor;

contestada2 += auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",valor);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

}

} else

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

104

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value.toLowerCase()

== opciones[k].toLowerCase()){

if (k==0){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

else if (opciones[k-

1].toLowerCase() != opciones[k].toLowerCase()){

puntuacion += parseFloat(puntos[k]);

points

+= parseFloat(puntos[k]);

}

}

}

} else

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

break;

}

}

}

105

//Si tiene distancia de Levenshtein

else {

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

if(min <= dist){

puntuacion +=

parseFloat(puntos[indice]);

points +=

parseFloat(puntos[indice]);

}

}

} else {

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

contestada =

contestada.concat("[,]",opcs[i].options[opcs[i].options.selectedIndex]

.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[opcs[i].options.selectedIndex]);

}

count++;

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

106

}

}

if (normalize(contestada.replace(/\s/g,"_")).length > 66)

contestada = contestada2;

contestada =

normalize(contestada.toLowerCase().replace(/\s/g,"_"));

if

(corr.localeCompare(normalize(contestada.replace(/\s/g,"_"))) == 0)

res="correct";

else

res="incorrect";

miRTE.setInteractionsResponseAll(ent,contestada,res);

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled =

true;

//Actualizamos la puntuación y mostramos la siguiente

pregunta

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

if (points > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(" ")[0]+"! Has conseguido "+points+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(" ")[0]+"! ¡A por las siguientes

preguntas!");

muestra(ent);

puntosPreguntas[ent] = points;

}

/*********************************************************************

**********

**

** Function: muestra()

** Inputs: The number of the question and the total number of

questions of the test.

** Return: None.

**

** Description:

** It shows the next question and introduces it in the LMS.

**

**********************************************************************

*********/

function muestra (num){

var next = num + 1;

var patt = "";

if (next < numeroPr){

var aux = "div"+next;

document.getElementById(aux).style.display = "block";

var id =

document.getElementById("formulario"+next).getElementsByTagName("input

")[0].getAttribute("name");

107

var tipo =

document.getElementById("formulario"+next).getAttribute("tipo");

var desc =

document.getElementById("formulario"+next).getElementsByTagName("h3")[

0].innerHTML;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsAddNewI(next,id,"choice",1, patt);

miRTE.setInteractionsDescription(next, desc);

} else

if (tipo.localeCompare("MULTIPLE CHOICE QUESTION") ==

0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsAddNewI(next,id,"choice",1,

patt);

miRTE.setInteractionsDescription(next,desc);

} else

if (tipo.localeCompare("CLOZE QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsAddNewI(next,id,"fill-in",1,

patt.toLowerCase());

miRTE.setInteractionsDescription(next, desc);

} else

if (tipo.localeCompare("NUMERIC QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsAddNewI(next,id,"numeric",1,

patt);

miRTE.setInteractionsDescription(next, desc);

}

}

}

/*********************************************************************

**********

**

** Function: actualiza()

** Inputs: The score of the learner and the total number of available

points of the test.

** Return: None.

**

** Description:

** It updates the score and puts it into the square situated in the

top right of the page.

**

**********************************************************************

*********/

function actualiza (pts, sum){

document.getElementById("tupuntuacion").value = pts + "/" + sum;

}

/*********************************************************************

**********

**

** Function: getCorrectaInv()

** Inputs: The identifier of the question and its type

** Return: The correct answer without special signs neither spaces.

**

** Description:

** It obtains the correct answer/s of any question.

**

108

**********************************************************************

*********/

function getCorrectaInv(iden, tipo){

var opt=document.getElementsByName(iden);

var correcta = "";

var correcta2 = "";

var count = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var i=0; i<opt.length;i++){

arr[i] =

parseFloat(opt[i].getAttribute("puntos"));

}

if (opt[arr.indexOf(Math.max.apply(null,

arr))].value.length < 66)

correcta =

normalize(opt[arr.indexOf(Math.max.apply(null,

arr))].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

else

correcta =

auxiliarResp[arr.indexOf(Math.max.apply(null, arr))].replace(/[^a-zA-Z

0-9.]+/g,' ');

} else

if (tipo.localeCompare("MULTIPLE CHOICE QUESTION") == 0){

for(var i=0; i<opt.length;i++){

if (parseFloat(opt[i].getAttribute("respondida"))

> 0){

if (count==0){

correcta =

normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

correcta2 = auxiliarResp[i];

} else {

correcta =

correcta.concat("[,]",normalize(opt[i].value).replace(/[^a-zA-Z 0-

9.]+/g,' '));

correcta2 = correcta2.concat("[,]",

auxiliarResp[i]);

} count++;

}

}

if (correcta.replace(/\s/g,"_").length >=

66)

correcta = correcta2;

} else

if (tipo.localeCompare("CLOZE QUESTION") == 0){

for(var i=0; i<opt.length;i++){

//Obtenemos el tipo de hueco

var hueco = opt[i].getAttribute("tipo");

if (hueco.localeCompare("Decimal") == 0){

if (count==0){

correcta =

opt[i].getAttribute("inf")+"[:]"+opt[i].getAttribute("sup");

correcta2 =

opt[i].getAttribute("inf")+"[:]"+opt[i].getAttribute("sup");

} else{

correcta =

correcta.concat("[,]",opt[i].getAttribute("inf")+"[:]"+opt[i].getAttri

bute("sup"));

109

correcta2 =

correcta2.concat("[,]",opt[i].getAttribute("inf")+"[:]"+opt[i].getAttr

ibute("sup"));

}

count++;

} else

if (hueco.localeCompare("Lista") ==

0){

if (count==0){

correcta =

getCorrectaH(opt[i])[0];

correcta2 =

auxiliarResp[getCorrectaH(opt[i])[1]];

} else {

correcta =

correcta.concat("[,]",getCorrectaH(opt[i])[0]);

correcta2 =

correcta2.concat("[,]",getCorrectaH(opt[i])[1]);

}

count++;

} else

if (hueco.localeCompare("String") ==

0){

var opcs =

opt[i].getAttribute("sol").split(",");

var pts =

opt[i].getAttribute("puntos").split(",");

pts.length = pts.length - 1;

var numbers = pts.map(Number);

var index =

numbers.indexOf(Math.max.apply(null, numbers));

if (count==0)

correcta =

opcs[index];

else

correcta =

correcta.concat("[,]",opcs[index]);

count++;

}

}

if (correcta.replace(/\s/g,"_").length >=

66)

correcta = correcta2;

} else

if (tipo.localeCompare("NUMERIC QUESTION")

== 0){

correcta =

opt[0].getAttribute("inf")+"[:]"+opt[0].getAttribute("sup");

}

return normalize(correcta.replace(/\s/g,"_"));

}

/*********************************************************************

**********

**

** Function: getCorrectaH()

** Inputs: The options of a drop-down list of a cloze question.

** Return: The correct answer.

**

** Description:

** It obtains the correct answer of the list.

110

**

**********************************************************************

*********/

function getCorrectaH (opciones) {

var arr = [];

cont = 0;

for(var j = 0; j < opciones.options.length; j++){

arr[cont] =

parseFloat(opciones.options[j].getAttribute("puntos"));

cont++;

}

var index = arr.indexOf(Math.max.apply(null, arr));

return [opciones.options[index].value,index];

}

/*********************************************************************

**********

**

** Function: normalize()

** Inputs: A string with special signs.

** Return: The same string without any special sign.

**

** Description:

** It normalizes a string.

**

**********************************************************************

*********/

var normalize = (function() {

var from = "ÃÀÁÄÂÈÉËÊÌÍÏÎÒÓÖÔÙÚÜÛãàáäâèéëêìíïîòóöôùúüûÑñÇç",

to = "AAAAAEEEEIIIIOOOOUUUUaaaaaeeeeiiiioooouuuunncc",

mapping = {};

for(var i = 0, j = from.length; i < j; i++ )

mapping[ from.charAt( i ) ] = to.charAt( i );

return function( str ) {

var ret = [];

for( var i = 0, j = str.length; i < j; i++ ) {

var c = str.charAt( i );

if( mapping.hasOwnProperty( str.charAt( i ) ) )

ret.push( mapping[ c ] );

else

ret.push( c );

}

return ret.join( '' );

}

})();

/*********************************************************************

**********

**

** Function: getEditDistance()

** Inputs: Two strings.

** Return: An integer that represents the Levenshtein distance

between the two strings.

**

** Description:

** It gets the Levenshtein distance between the two strings given.

**

111

**********************************************************************

*********/

function getEditDistance (a, b){

if(a.length == 0) return b.length;

if(b.length == 0) return a.length;

var matrix = [];

// increment along the first column of each row

var i;

for(i = 0; i <= b.length; i++){

matrix[i] = [i];

}

// increment each column in the first row

var j;

for(j = 0; j <= a.length; j++){

matrix[0][j] = j;

}

// Fill in the rest of the matrix

for(i = 1; i <= b.length; i++){

for(j = 1; j <= a.length; j++){

if(b.charAt(i-1) == a.charAt(j-1)){

matrix[i][j] = matrix[i-1][j-1];

} else {

matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution

Math.min(matrix[i][j-1] + 1, //

insertion

matrix[i-1][j] + 1)); //

deletion

}

}

}

return matrix[b.length][a.length];

};

/*********************************************************************

**********

**

** Function: obtieneTiempo()

** Inputs: Two strings of time.

** Return: The difference between both strings.

**

** Description:

** It calculates the difference between both strings.

**

**********************************************************************

*********/

function obtieneTiempo(cad1, cad2){

var annio = parseInt(cad2.substring(0,4)) -

parseInt(cad1.substring(0,4));

var mes = parseInt(cad2.substring(5,7)) -

parseInt(cad1.substring(5,7));

var dias = parseInt(cad2.substring(8,10)) -

parseInt(cad1.substring(8,10));

var horas = parseInt(cad2.substring(11,13)) -

parseInt(cad1.substring(11,13));

var minutos = parseInt(cad2.substring(14,16)) -

parseInt(cad1.substring(14,16));

112

var segundos = parseInt(cad2.substring(17,19)) -

parseInt(cad1.substring(17,19));

var tiempo = "";

if (segundos < 0) {

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0) {

horas--;

minutos = 60 + minutos;

}

if (horas < 0) {

dias--;

horas = 24 + horas;

}

if (dias < 0) {

mes--;

dias = 30 + dias;

}

if (mes < 0) {

annio--;

mes = 12 + mes;

}

if (segundos.toString().length < 2){

segundos = "0"+segundos;

}

if (minutos.toString().length < 2){

minutos = "0"+minutos;

}

if (horas.toString().length < 2){

horas = "0"+horas;

}

if (annio != 0){

if (annio != 1 && mes != 1)

tiempo = annio +" a&ntildeos, "+ mes + " meses, " +

dias +" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio != 1 && mes == 1)

tiempo = annio +" a&ntildeos, "+ mes + " mes, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio == 1 && mes != 1)

tiempo = annio +" a&ntildeo, "+ mes + " meses, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio == 1 && mes == 1)

tiempo = annio +" a&ntildeo, "+ mes + " mes, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

} else if (mes != 0){

if (mes != 1 && dias != 1)

tiempo = mes + " meses, " + dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else if (mes != 1 && dias == 1)

tiempo = mes + " meses, " + dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

else if (mes == 1 && dias != 1)

tiempo = mes + " mes, " + dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else if (mes == 1 && dias == 1)

113

tiempo = mes + " mes, " + dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

} else if (dias != 0){

if (dias != 1)

tiempo = dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else

tiempo = dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

}

else

tiempo = horas+":"+minutos+":"+segundos;

return tiempo;

}

/*********************************************************************

**********

**

** Function: ponRespuestas()

** Inputs: An integer depending on the rows of the table.

** Return: None.

**

** Description:

** It adds the answers given by the learner in a table interacting

with the LMS.

**

**********************************************************************

*********/

function ponRespuestas(entero){

for (var i = 0; i < numeroPr; i++){

var res = miRTE.getInteractionsResponse(i).split("[,]");

var lat = miRTE.getInteractionsLatency(i);

var type = miRTE.getInteractionsType(i);

if (res == "")

res = "No_contestada";

var aux = entero + i;

var preg = "PE"+ aux;

document.getElementById("PE"+aux).innerHTML = res;

if (lat.localeCompare("") == 0)

document.getElementById("PE"+aux).innerHTML += "

Sin_latencia";

else

document.getElementById("PE"+aux).innerHTML += " " +

miRTE.getInteractionsLatency(i);

if (puntosPreguntas[i] != "undefined")

document.getElementById("PE"+aux).innerHTML += " " +

puntosPreguntas[i] + " puntos";

else

document.getElementById("PE"+aux).innerHTML += " 0

puntos";

}

}

/*********************************************************************

**********

**

** Function: reiniciar()

** Inputs: None.

** Return: None.

**

114

** Description:

** It resets a couple of fields in the LMS.

**

**********************************************************************

*********/

function reiniciar (){

for (var i = 0; i < numeroPr; i++){

if (i == 0)

miRTE.setInteractionsResponse(i,"No_contestada");

else {

miRTE.setInteractionsCorrectRespPattern(i,0,"No_contestada");

miRTE.setInteractionsResponse(i,"No_contestada");

}

}

}

/*********************************************************************

**********

**

** Function: deshabilitar()

** Inputs: None.

** Return: None.

**

** Description:

** It disables the questions buttons.

**

**********************************************************************

*********/

function deshabilitar (){

for (var i = 0; i < numeroPr; i++){

document.getElementsByName("valida"+i)[0].disabled = true;

document.getElementsByName("borra"+i)[0].disabled = true;

if (i + 1 != numeroPr)

document.getElementsByName("posponer"+i)[0].disabled =

true;

}

}

8.4 Librería JavaScript creaPreguntasSinCom.js

/* creaPreguntasSinCom.js JavaScript library by Sergio Díaz

([email protected])

Librería desarrollada por Sergio Díaz Fuentes. Se permite cualquier

explotación de la obra, incluyendo una finalidad comercial, así como

la creación de obras derivadas, la distribución de las cuales también

está permitida sin ninguna restricción.

*/

var puntuacion = 0;

var total = 0;

var minimo = 0;

var longitudPreg = 0;

var auxiliarCont = 0;

var contador = 0;

var auxiliarResp = ["a","b","c","d","e","f","g","h","i","j"];

var respuestas = [];

var puntosPreguntas = new Array ();

var latencias = new Array ();

var f=new Date();

var hora = f.getHours();

115

var min = f.getMinutes();

var sec = f.getSeconds();

if (hora.length == 1)

hora = "0"+ hora;

if (min.length == 1)

min = "0"+ min;

if (sec.length == 1)

sec = "0"+ sec;

var horaInicio = hora+":"+min+":"+sec;

var miTiempo="";

rellenaPuntos();

/*********************************************************************

**********

**

** Function: loadXMLDoc()

** Inputs: The path of the XML file, the number of questions of the

test, time for the test, random questions and random options.

** Return: The number of requested test questions presented to the

user with the chosen features.

**

** Description:

** Requests the xml file and presents the questions.

**

**********************************************************************

*********/

function loadXMLDoc(xml,numPreg,time,aleaPr, aleaOp) {

var xmlhttp;

if (window.XMLHttpRequest) {

xmlhttp = new XMLHttpRequest();

} else {

// code for older browsers

xmlhttp = new ActiveXObject("Microsoft.XMLDOM");

}

xmlhttp.onreadystatechange = function() {

if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

var xmlDoc = xmlhttp.responseXML;

if (xmlDoc != null) {

ponDiv();

ponPuntuacion();

if (parseInt(time) > 0){

if (time.toString().length < 2)

var t = "0" + time + ":00";

else

var t = time + ":00";

ponTime(t);

}

if (time != 0)

miTiempo = setInterval( function(){cuentAtras()},

1000);

// En la variable div_preguntas obtenemos el contenedor div

con el id 'preguntas'

var div_preguntas = document.getElementById('preguntas');

// Obtenemos la lista de preguntas

116

var preguntas_set =

xmlDoc.getElementsByTagName("questestinterop")[0].getElementsByTagName

("item");

longitudPreg = preguntas_set.length;

// Comprobamos que el número de preguntas sea igual o

inferior al número de preguntas disponibles

if (numPreg > preguntas_set.length){

numeroPr = preguntas_set.length;

}

//Mezclamos un array auxiliar que nos servirá para obtener

las preguntas de forma aleatoria

if (aleaPr.localeCompare("si") == 0)

var arrayaux = arrayAlea(preguntas_set);

else

var arrayaux = obtenArray(preguntas_set.length);

while (auxiliarCont < numeroPr)

{

// Obtenemos el título de la pregunta

var titulo =

preguntas_set[arrayaux[auxiliarCont]].getAttribute("title");

var

descripcion=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName

("mattext")[0].childNodes[0].nodeValue;

if (descripcion.indexOf("&lt;p&gt;") == -1)

descripcion = "<p>"+descripcion+"</p>";

//Obtenemos el tipo de la pregunta

var

tipo=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("qtime

tadatafield")[1].getElementsByTagName("fieldentry")[0].childNodes[0].n

odeValue;

//Obtenemos el identificador de la pregunta

var

id=preguntas_set[arrayaux[auxiliarCont]].getAttribute("ident");

var flag=0;

var flagE = 0;

var opciones="";

if(tipo=="SINGLE CHOICE QUESTION"){

// Obtenemos las opciones de la pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los puntos de cada opcion

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var");

opciones+=unica(opciones_set,id,puntos,contador);

total+=sumaPuntos(puntos, tipo, 0);

minimo+=restaPuntos(puntos, tipo, 0);

flagE = 1;

} else

if(tipo=="NUMERIC QUESTION"){

117

//Obtenemos los límites superior e inferior

para la pregunta

var

sup=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varlte

")[0].childNodes[0].nodeValue;

var

inf=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("vargte

")[0].childNodes[0].nodeValue;

//Obtenemos el numero máximo de caracteres

permitidos

var

maxcar=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("ren

der_fib")[0].getAttribute("maxchars");

//Obtenemos los puntos en caso de acierto

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var")[0].childNodes[0].nodeValue;

opciones+=numerica(id,sup,inf,puntos,maxcar,contador);

total+=parseFloat(puntos);

flagE = 1;

}

else

if(tipo=="MULTIPLE CHOICE QUESTION"){

// Obtenemos las opciones de la

pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los puntos de cada opcion

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var");

opciones+=multiple(opciones_set,id,puntos,contador);

total+=sumaPuntos(puntos, tipo, 0);

minimo+=restaPuntos(puntos, tipo, 0);

flagE = 1;

}

else

//https://social.msdn.microsoft.com/Forums/es-ES/119aa457-11f6-

408b-9c70-d368deabd597/javascript-saber-si-una-letra-esta-en-una-

cadena-de-caracteres?forum=netfxwebes

if(tipo=="CLOZE QUESTION"){

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

var huecosString =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_s

tr");

var huecosNum =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_n

um");

var oposibles =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varequal")

; //Opciones posibles

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar");

//Puntos de las distintas opciones

var nHuecos = huecosString.length +

huecosNum.length; //Número total de huecos

118

var tagNot =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("not");

var arr = [].slice.call(oposibles);

var arrMat =

[].slice.call(opciones_set);

if (tagNot.length != 0){

var osobrantes =

tagNot[0].getElementsByTagName("varequal").length;

arr.length = oposibles.length -

osobrantes * 2;

arrMat.length = opciones_set.length -

osobrantes;

}

var gapsT = 0;

var gapsN = 0;

descripcion="";

total += sumaPuntos(puntos,tipo,

oposibles);

minimo += restaPuntos(puntos, tipo,

oposibles);

var w = 0;

do{

if

(arrMat[w].childNodes[0].nodeValue.indexOf("&lt;p&gt;") != -1)

descripcion +=

arrMat[w].childNodes[0].nodeValue; //La primera nunca será un hueco

else{

if (w==0)

descripcion += "<p>"

+arrMat[w].childNodes[0].nodeValue.replace(/<br \/>/g,

'').replace(/<\/?span[^>]*>/g,"");

else if (w ==

arrMat.length - nHuecos - 1)

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"") +"</p>";

else

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"");

}

if (gapsT + gapsN <

nHuecos){

if

(huecosString.length != 0 && gapsT < huecosString.length){

if

(huecosString[gapsT].getAttribute("ident").indexOf(gapsT + gapsN) != -

1){

if

(huecosString[gapsT].getElementsByTagName("render_choice").length ==

1){

descripcion += creaDesplegable(id,arr,puntos,gapsT + gapsN,

aleaOp);

w = w +

119

huecosString[gapsT].getElementsByTagName("render_choice")[0].getElemen

tsByTagName("mattext").length;

}

else{

descripcion+="<input type=\"text\" name=\""+id+"\"

class=\"huecoT\"

tipo=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("fibtype")+"\"

sensibilidad=\""+preguntas_set[arrayaux[auxiliarCont]].getElementsByTa

gName("fieldentry")[5].childNodes[0].nodeValue+"\" puntos=\"\"

sol=\"\"

size=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("columns")+"\"

maxlength=\""+parseInt(huecosString[gapsT].getElementsByTagName("rende

r_fib")[0].getAttribute("columns"))+"\"/>";

}

gapsT++;

w++;

}

else if (huecosNum[gapsN].getAttribute("ident").indexOf(gapsT + gapsN)

!= -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

if (min == null)

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\" name=\""+id+"\"

class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

gapsN++;

w++;

}

}

else if

(huecosNum[gapsN].getAttribute("ident").indexOf(gapsT + gapsN) != -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

120

if

(min == null)

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\" name=\""+id+"\"

class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

gapsN++;

w++;

}

}

else

w++;

} while (w < arrMat.length

- nHuecos);

if (contador + 1 != numeroPr &&

auxiliarCont + 1 < preguntas_set.length)

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+contador+"\" type=\"button\"

value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+contador+")\"/></form>";

else

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/></form>";

// Modificamos el contenido

html del contenedor div

if (contador==0)

div_preguntas.innerHTML +=

"<div id=\"div"+contador+"\" style=\"display:block;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion

+"</form></div>";

else

div_preguntas.innerHTML +=

"<div id=\"div"+contador+"\" style=\"display:none;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

"</form></div>";

for (var q = 0; q < nHuecos;

q++){

for (var k = 0; k <

oposibles.length; k++){

121

if

(oposibles[k].getAttribute("respident").indexOf(q) != -1 &&

document.getElementsByName(id)[q].getAttribute("tipo")){

if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"String") == 0){

var attAnterior =

document.getElementsByName(id)[q].getAttribute("sol");

var puntAnterior =

document.getElementsByName(id)[q].getAttribute("puntos");

document.getElementsByName(id)[q].setAttribute("sol",attAnterior+op

osibles[k].childNodes[0].nodeValue+",");

document.getElementsByName(id)[q].setAttribute("puntos",puntAnterio

r + puntos[k].childNodes[0].nodeValue+",");

}

else if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"Decimal") == 0)

document.getElementsByName(id)[q].setAttribute("puntos",

puntos[k].childNodes[0].nodeValue);

}

}

}

flag = 1; //Este flag nos

permite que no se repita la pregunta de nuevo en el div_preguntas

flagE = 1;

}

// Modificamos el contenido html del contenedor div

if(flag == 0 && flagE == 1){

if (contador == 0)

div_preguntas.innerHTML += "<div id=\"div"+contador+"\"

style=\"display:block;\"><form id=\"formulario"+contador+"\"

tipo=\""+tipo+"\"><h3>" + parseInt(contador+1) + ". " + titulo +

"</h3>" + descripcion + opciones+"</form></div>";

else

div_preguntas.innerHTML += "<div id=\"div"+contador+"\"

style=\"display:none;\"><form id=\"formulario"+contador+"\"

tipo=\""+tipo+"\"><h3>" + parseInt(contador+1) + ". " + titulo +

"</h3>" + descripcion + opciones+"</form></div>";

}

if(flagE == 1){

auxiliarCont++;

contador++;

} else{

auxiliarCont++;

}

if (auxiliarCont >= preguntas_set.length)

break;

}

actualiza(puntuacion,total);

}

}

122

};

xmlhttp.open("GET", xml, true);

xmlhttp.send();

}

/*********************************************************************

**********

** Function: ponDiv()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a div tag to the html page.

**

**********************************************************************

*********/

function ponDiv(){

var midiv = document.createElement("div");

midiv.setAttribute("id","preguntas");

midiv.setAttribute("class","desplazado");

//document.body.appendChild(midiv); // Lo pones en "body", si

quieres ponerlo dentro de algún id en concreto usas

document.getElementById('donde lo quiero poner').appendChild(midiv);

document.getElementById('tabla').appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: ponPuntuacion()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a puntuation div tag to the html page.

**

**********************************************************************

*********/

function ponPuntuacion(){

var midiv = document.createElement("div");

midiv.setAttribute("class","cuadroFijo");

midiv.setAttribute("id","divPuntuacion");

if

(banco.localeCompare("../../_ejs_library/html/PreguntasPage2.xml") ==

0 || banco.localeCompare("../../_ejs_library/html/PreguntasPage1.xml")

== 0)

midiv.setAttribute("style","display:none;");

var centro = document.createElement("center");

var mipar = document.createElement("p");

mipar.setAttribute("align","center");

centro.appendChild(mipar);

var t = document.createTextNode("Puntuación");

mipar.appendChild(t);

var centr = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("value","");

cuadro.setAttribute("id","tupuntuacion");

cuadro.setAttribute("size","3");

cuadro.setAttribute("maxlength","3");

cuadro.setAttribute("readonly","true");

123

cuadro.setAttribute("class","altura puntos");

centr.appendChild(cuadro);

midiv.appendChild(centro);

midiv.appendChild(centr);

document.body.appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: ponTime()

** Inputs: The time in minutes of test performance.

** Return: None.

**

** Description:

** It adds a time div tag to the html page.

**

**********************************************************************

*********/

function ponTime(time){

var midiv = document.createElement("div");

midiv.setAttribute("id","divTiempo");

midiv.setAttribute("class","cuadroFijoT");

var para = document.createElement("p");

para.setAttribute("align","center");

var t = document.createTextNode("Tiempo");

para.appendChild(t);

var centrado = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("id","tutiempo");

cuadro.setAttribute("value",time); cuadro.setAttribute("size","3");

cuadro.setAttribute("class","altura puntos");

cuadro.setAttribute("readonly","true");

cuadro.setAttribute("maxlength","3");

centrado.appendChild(cuadro);

midiv.appendChild(para);

midiv.appendChild(centrado);

document.body.appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: cuentAtras()

** Inputs: None.

** Return: None.

**

** Description:

** It decrements the clock.

**

**********************************************************************

*********/

function cuentAtras(){

var cad = document.getElementById("tutiempo").value;

var minutos = parseInt(cad.substring(0,2));

var segundos = parseInt(cad.substring(3,cad.length));

segundos--;

if (segundos < 0){

124

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0){

clearInterval(miTiempo);

deshabilitar();

smoke.alert("Lo sentimos. Se ha acabado tu tiempo para

responder");

} else{

if (minutos.toString().length < 2)

minutos = "0" + minutos;

if (segundos.toString().length < 2)

segundos = "0" + segundos;

document.getElementById("tutiempo").value = minutos

+":"+segundos;

}

}

/*********************************************************************

**********

**

** Function: arrayAlea()

** Inputs: An array of questions.

** Return: An array with unordered values depending of the length of

de questions array.

**

** Description:

** It mixes a vector of sorted values.

**

**********************************************************************

*********/

function arrayAlea(array){

var unArray=[];

for(var z=0;z<array.length;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

/*********************************************************************

**********

**

** Function: arrayAleaEnt()

** Inputs: An integer.

** Return: An array with unordered values with variable length

depending of the integer.

**

** Description:

** It creates an unordered array.

**

**********************************************************************

*********/

function arrayAleaEnt(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

125

/*********************************************************************

**********

**

** Function: obtenArray()

** Inputs: An integer.

** Return: An array of ordered values with variable length depending

of the integer.

**

** Description:

** It creates an ordered array.

**

**********************************************************************

*********/

function obtenArray(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

return unArray;

}

/*********************************************************************

**********

**

** Function: rellenaPuntos()

** Inputs: None.

** Return: None.

**

** Description:

** It initializes the points vector called puntosPreguntas.

**

**********************************************************************

*********/

function rellenaPuntos(){

for (var j=0; j < numeroPr; j++){

puntosPreguntas[j] = 0;

}

}

/*********************************************************************

**********

**

** Function: fisher_yates()

** Inputs: An array of sorted values.

** Return: The function returns the untidy vector.

**

** Description:

** It mixes a vector of sorted values according to Fisher-Yates

algorithm.

**

**********************************************************************

*********/

function fisher_yates(array){

var i=array.length;

while(i--){

var j=Math.floor( Math.random() * (i+1) );

var tmp=array[i];

array[i]=array[j];

array[j]=tmp;

}

126

}

/*********************************************************************

**********

**

** Function: creaDesplegable()

** Inputs: The identifier, the options of the cloze question, the

points of the question and an integer that represents the order of the

question.

** Return: A drop-down list

**

** Description:

** It creates a drop-down list gap.

**

**********************************************************************

*********/

function creaDesplegable (ident, opcs, pts, ent, alea){

if (alea.localeCompare("si") == 0)

var unArray = arrayAleaEnt(opcs.length);

else

var unArray = obtenArray (opcs.length);

var lista = "<select name=\""+ident+"\" class=\"desplegable\"

tipo=\"Lista\">";

for (j = 0; j < opcs.length; j++){

if (opcs[unArray[j]].getAttribute("respident").indexOf(ent)

!= -1)

lista += "<option

value=\""+opcs[unArray[j]].childNodes[0].nodeValue+"\"

puntos=\""+pts[unArray[j]].childNodes[0].nodeValue+"\"

>"+opcs[unArray[j]].childNodes[0].nodeValue +"</option>";

}

lista += "</select>";

return lista;

}

/*********************************************************************

**********

**

** Function: sumaPuntos()

** Inputs: An array with the points of a question, the type of the

question and the number of gaps.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this question..

**

**********************************************************************

*********/

function sumaPuntos(pts,tipo,ngaps){

var suma= 0;

var cont = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

suma+=Math.max.apply(null, arr);

} else if (tipo.localeCompare("CLOZE QUESTION") == 0){

suma += getPuntos(pts,ngaps);

} else {

for(var z=0;z<pts.length;z++){

127

if (parseFloat(pts[z].childNodes[0].nodeValue) > 0)

suma+=parseFloat(pts[z].childNodes[0].nodeValue);

}

}

return suma;

}

/*********************************************************************

**********

**

** Function: restaPuntos()

** Inputs: An array with the points of a question, the type of the

question and the number of gaps.

** Return: The minimum points for this question.

**

** Description:

** It calculates the minimum number of points for this question..

**

**********************************************************************

*********/

function restaPuntos(pts,tipo,ngaps){

var resta= 0;

var cont = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

resta+=Math.min.apply(null, arr);

} else if (tipo.localeCompare("CLOZE QUESTION") == 0){

resta += getPuntosResta(pts,ngaps);

} else {

for(var z=0;z<pts.length;z++){

if (parseFloat(pts[z].childNodes[0].nodeValue) < 0)

resta+=parseFloat(pts[z].childNodes[0].nodeValue);

}

}

return resta;

}

/*********************************************************************

**********

**

** Function: getPuntos()

** Inputs: An array with the points of a question and the number of

gaps.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this cloze question.

**

**********************************************************************

*********/

function getPuntos(pts,ngaps){

cont = 0;

cont2 = 0;

z=0;

suma = 0;

var arr = [];

do{

128

if (ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

else if (ngaps[z].getAttribute("respident").indexOf(cont) ==

-1){

suma+=Math.max.apply(null, arr);

arr = [];

cont++;

cont2 = 0;

if

(ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

}

z++;

if (z == pts.length)

suma+=Math.max.apply(null, arr);

} while (z < pts.length);

return suma;

}

/*********************************************************************

**********

**

** Function: getPuntosResta()

** Inputs: An array with the points of a question and the number of

gaps.

** Return: The minimum points for this question.

**

** Description:

** It calculates the minimum number of points for this cloze question.

**

**********************************************************************

*********/

function getPuntosResta(pts,ngaps){

cont = 0;

cont2 = 0;

z=0;

resta = 0;

var arr = [];

do{

if (ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) < 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

else if (ngaps[z].getAttribute("respident").indexOf(cont) ==

-1){

if (arr.length != 0)

resta+=Math.min.apply(null, arr);

arr = [];

cont++;

cont2 = 0;

129

if

(ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) < 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

}

z++;

if (z == pts.length){

if(arr.length != 0)

resta+=Math.min.apply(null, arr);

}

} while (z < pts.length);

return resta;

}

/*********************************************************************

**********

**

** Function: obtenLatencia()

** Inputs: Two strings.

** Return: The time between both strings.

**

** Description:

** It calculates the difference between two given strings.

**

**********************************************************************

*********/

function obtenLatencia(cad1, cad2){

var hora1 = cad1.split(":")[0];

var hora2 = cad2.split(":")[0];

var min1 = cad1.split(":")[1];

var min2 = cad2.split(":")[1];

var sec1 = cad1.split(":")[2];

var sec2 = cad2.split(":")[2];

var sec = parseInt(sec1) - parseInt(sec2);

var min = parseInt(min1) - parseInt(min2);

var hora = parseInt(hora1) - parseInt(hora2);

if (sec < 0){

min--;

sec = 60 + sec;

}

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

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

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

return hora+":"+min+":"+sec;

}

/*********************************************************************

**********

**

** Function: unica()

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and only answer.

**

130

**********************************************************************

*********/

function unica(opciones_tag,ident,pts,ent){

if (opAleat.localeCompare("si") == 0)

var vector = arrayAleaEnt(pts.length);

else

var vector = obtenArray(pts.length);

var opciones="";

for (var j = 0; j < pts.length; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"puntos=

\""+pts[vector[j]].childNodes[0].nodeValue+"\"/>"+opciones_tag[vector[

j]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

cadena = cadena.replace("</p>",'');

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

value=\""+cadena+"\"puntos=\""+pts[vector[j]].childNodes[0].nodeValue+

"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\" type=\"button\"

value=\"Validar\" onclick=\"puntuarUni("+ident+","+ent+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/><input name=\"posponer"+ent+"\"

type=\"button\" value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\" type=\"button\"

value=\"Validar\" onclick=\"puntuarUni("+ident+","+ent+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: numerica()

** Inputs: An identifier, two floats, an array with the options

points and two integers.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a numeric question.

**

**********************************************************************

*********/

function numerica(iden,superior,inferior,pts,maxcar,ent){

var opciones="";

opciones += "<p class=\"opciones\">Escribe el valor: <input

type=\"text\" name=\""+iden+"\" inf=\""+inferior+"\"

sup=\""+superior+"\" puntos=\""+pts+"\" size=\"3\"

maxlength=\""+parseInt(maxcar)+"\"/></p></br>";

131

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/><input name=\"posponer"+ent+"\"

type=\"button\" value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: multiple()

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and multiple answer.

**

**********************************************************************

*********/

function multiple(opciones_tag,ident,pts,ent){

//Vectores que contendran los puntos de las opciones

var respondidas=[];

var norespondidas=[];

var j=0;

for(var z = 0; z < pts.length; z=z+2){

respondidas[j]=pts[z].childNodes[0].nodeValue;

norespondidas[j]=pts[z+1].childNodes[0].nodeValue;

j++;

}

if (opAleat.localeCompare("si") == 0)

var vector=arrayAleaEnt(pts.length/2);

else

var vector=obtenArray(pts.length/2);

var opciones="";

for (var j = 0; j < pts.length/2; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+opciones_tag[vector[j

]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

132

cadena = cadena.replace("</p>",'');

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\" value=\""+cadena+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+ent+"\" type=\"button\"

value=\"Posponer\" class=\"boton\" onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: puntuarUni()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a multiple choice question and only answer.

**

**********************************************************************

*********/

function puntuarUni(iden, ent){

var opt = document.getElementsByName(iden[0].name);

var points = 0;

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion +=

parseFloat(opt[i].getAttribute("puntos"));

points +=

parseFloat(opt[i].getAttribute("puntos"));

respuestas[ent] = opt[i].value;

}

opt[i].disabled = true;

}

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled =

true;

puntuacion = Math.round(puntuacion * 100) / 100;

if (points > 0)

smoke.alert("¡Enhorabuena! Has conseguido "+points+ "

puntos en esta pregunta. Tu puntuación total es de "+puntuacion+ "

puntos.");

133

else

smoke.alert("¡No te desanimes! ¡A por las siguientes

preguntas!");

actualiza(puntuacion, total);

puntosPreguntas[ent] = Math.round(points * 100) / 100;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1.length == 1)

hora1 = "0"+ hora1;

if (min1.length == 1)

min1 = "0"+ min1;

if (sec1.length == 1)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

muestra(ent);

}

/*********************************************************************

**********

**

** Function: puntuarMul()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a multiple choice question and multiple

answer.

**

**********************************************************************

*********/

function puntuarMul(iden, ent){

var opt = document.getElementsByName(iden[0].name);

var contestada = "";

var points = 0;

var count = 0;

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion +=

parseFloat(opt[i].getAttribute("respondida"));

points +=

parseFloat(opt[i].getAttribute("respondida"));

if (count==0)

contestada = opt[i].value;

else

contestada =

contestada.concat("[,]"+opt[i].value);

count++;

}

else {

puntuacion +=

parseFloat(opt[i].getAttribute("norespondida"));

points +=

parseFloat(opt[i].getAttribute("norespondida"));

}

opt[i].disabled = true;

134

}

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled =

true;

puntuacion = Math.round(puntuacion * 100) / 100;

if (points > 0)

smoke.alert("¡Enhorabuena! Has conseguido "+points+ "

puntos en esta pregunta. Sigue así. Tu puntuación total es de

"+puntuacion+ " puntos.");

else

smoke.alert("¡No te desanimes! ¡A por las siguientes

preguntas!");

actualiza(puntuacion, total);

respuestas[ent] = contestada;

puntosPreguntas[ent] = Math.round(points * 100) / 100;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1.length == 1)

hora1 = "0"+ hora1;

if (min1.length == 1)

min1 = "0"+ min1;

if (sec1.length == 1)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

muestra(ent);

}

/*********************************************************************

**********

**

** Function: puntuarNum()

** Inputs: An identifier, an integer and two floats.

** Return: None.

**

** Description:

** It gets the score of a numeric question.

**

**********************************************************************

*********/

function puntuarNum(iden, ent, inf, sup){

var valor = parseFloat(iden.value);

var puntos =

document.getElementsByName(iden.name)[0].getAttribute("puntos");

var points = 0;

if(valor <= sup && valor >= inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

}

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled =

true;

135

document.getElementsByName(iden.name)[0].disabled = true;

puntuacion = Math.round(puntuacion * 100) / 100;

if (points > 0)

smoke.alert("¡Enhorabuena! Has conseguido "+points+ "

puntos en esta pregunta. Tu puntuación total es de "+puntuacion+ "

puntos.");

else

smoke.alert("¡No te desanimes! ¡A por las siguientes

preguntas!");

respuestas[ent] = valor;

puntosPreguntas[ent] = Math.round(points * 100) / 100;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1.length == 1)

hora1 = "0"+ hora1;

if (min1.length == 1)

min1 = "0"+ min1;

if (sec1.length == 1)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

actualiza(puntuacion, total);

muestra(ent);

}

/*********************************************************************

**********

**

** Function: puntuaHuecos()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a cloze question.

**

**********************************************************************

*********/

function puntuaHuecos(iden, ent){

var count = 0;

var points = 0;

var contestada = "";

var contestada2 = "";

if (typeof iden.name !== "undefined"){

//Obtenemos los huecos

var opcs = document.getElementsByName(iden.name);

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo del hueco

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos = opcs[i].getAttribute("puntos");

136

var sup =

parseFloat(opcs[i].getAttribute("sup"));

var inf =

parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count == 0){

contestada += inf +"[:]"+ sup;

contestada2 += inf +"[:]"+ sup;

} else {

contestada = contestada.concat("[,]",

inf +"[:]"+ sup);

contestada2 =

contestada.concat("[,]", inf +"[:]"+ sup);

}

count++;

} else {

if (count == 0){

contestada += valor +"[:]"+ valor;

contestada2 += valor +"[:]"+ valor;

} else {

contestada = contestada.concat("[,]",

valor +"[:]"+ valor);

contestada2 =

contestada.concat("[,]", valor +"[:]"+ valor);

}

count++;

}

} else

if (tipo.localeCompare("Lista") == 0){

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

contestada =

contestada.concat("[,]",opcs[i].options[opcs[i].options.selectedIndex]

.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[opcs[i].options.selectedIndex]);

}

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

} else

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del

hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

137

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

if (count==0){

contestada += iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value.toLowerCase()

== opciones[k].toLowerCase()){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

break;

}

}

} else

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada += iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

}

}

//Si tiene distancia de Levenshtein

else {

138

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

if(min == dist){

puntuacion +=

parseFloat(puntos[indice]);

points +=

parseFloat(puntos[indice]);

}

}

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

}

} else {

//Obtenemos los huecos

var opcs = document.getElementsByName(iden[0].name);

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo de la pregunta

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos = opcs[i].getAttribute("puntos");

var sup = parseFloat(opcs[i].getAttribute("sup"));

var inf = parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count == 0){

contestada += inf +"[:]"+ sup;

contestada2 += inf +"[:]"+ sup;

} else {

139

contestada = contestada.concat("[,]",

inf +"[:]"+ sup);

contestada2 =

contestada.concat("[,]", inf +"[:]"+ sup);

}

count++;

} else {

if (count == 0){

contestada += valor +"[:]"+ valor;

contestada2 += valor +"[:]"+ valor;

} else {

contestada = contestada.concat("[,]",

valor +"[:]"+ valor);

contestada2 =

contestada2.concat("[,]", valor +"[:]"+ valor);

}

count++;

}

} else

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value.toLowerCase()

== opciones[k].toLowerCase()){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

break;

}

}

} else

140

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion+=parseFloat(puntos[k]);

points+=parseFloat(puntos[k]);

}

}

}

//Si tiene distancia de Levenshtein

else {

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

if(min <= dist){

puntuacion+=parseFloat(puntos[indice]);

points+=parseFloat(puntos[indice]);

}

}

141

} else {

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

contestada =

contestada.concat("[,]",opcs[i].options[opcs[i].options.selectedIndex]

.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[opcs[i].options.selectedIndex]);

}

count++;

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

}

}

if (normalize(contestada.replace(/\s/g,"_")).length > 66)

contestada = contestada2;

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled =

true;

puntuacion = Math.round(puntuacion * 100) / 100;

if (points > 0)

smoke.alert("¡Enhorabuena! Has conseguido "+points+ "

puntos en esta pregunta. Tu puntuación total es de "+puntuacion+ "

puntos.");

else

smoke.alert("¡No te desanimes! ¡A por las siguientes

preguntas!");

//Actualizamos la puntuación y mostramos la siguiente

pregunta

actualiza(puntuacion, total);

respuestas[ent] = contestada;

puntosPreguntas[ent] = Math.round(points * 100) / 100;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1.length == 1)

hora1 = "0"+ hora1;

if (min1.length == 1)

min1 = "0"+ min1;

if (sec1.length == 1)

142

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

muestra(ent);

}

/*********************************************************************

**********

**

** Function: actualiza()

** Inputs: The score of the learner and the total number of available

points of the test.

** Return: None.

**

** Description:

** It updates the score and puts it into the square situated in the

top right of the page.

**

**********************************************************************

*********/

function actualiza (pts, sum){

document.getElementById("tupuntuacion").value = pts+"/"+sum;

}

/*********************************************************************

**********

**

** Function: normalize()

** Inputs: A string with special signs.

** Return: The same string without any special sign.

**

** Description:

** It normalizes a string.

**

**********************************************************************

*********/

var normalize = (function() {

var from = "ÃÀÁÄÂÈÉËÊÌÍÏÎÒÓÖÔÙÚÜÛãàáäâèéëêìíïîòóöôùúüûÑñÇç",

to = "AAAAAEEEEIIIIOOOOUUUUaaaaaeeeeiiiioooouuuunncc",

mapping = {};

for(var i = 0, j = from.length; i < j; i++ )

mapping[ from.charAt( i ) ] = to.charAt( i );

return function( str ) {

var ret = [];

for( var i = 0, j = str.length; i < j; i++ ) {

var c = str.charAt( i );

if( mapping.hasOwnProperty( str.charAt( i ) ) )

ret.push( mapping[ c ] );

else

ret.push( c );

}

return ret.join( '' );

}

})();

/*********************************************************************

**********

**

143

** Function: muestra()

** Inputs: The number of the question and the total number of

questions of the test.

** Return: None.

**

** Description:

** It shows the next question and introduces it in the LMS.

**

**********************************************************************

*********/

function muestra (num){

var next = num + 1;

var patt = "";

if (next < numeroPr){

var aux = "div"+next;

document.getElementById(aux).style.display = "block";

}

var f2=new Date();

var hora2 = f2.getHours();

var min2 = f2.getMinutes();

var sec2 = f2.getSeconds();

if (hora2.length == 1)

hora2 = "0"+ hora2;

if (min2.length == 1)

min2 = "0"+ min2;

if (sec2.length == 1)

sec2 = "0"+ sec2;

horaInicio = hora2+":"+min2+":"+sec2;

}

/*********************************************************************

**********

**

** Function: getEditDistance()

** Inputs: Two strings.

** Return: An integer that represents the Levenshtein distance

between the two strings.

**

** Description:

** It gets the Levenshtein distance between the two strings given.

**

**********************************************************************

*********/

function getEditDistance (a, b){

if(a.length == 0) return b.length;

if(b.length == 0) return a.length;

var matrix = [];

// increment along the first column of each row

var i;

for(i = 0; i <= b.length; i++){

matrix[i] = [i];

}

// increment each column in the first row

var j;

for(j = 0; j <= a.length; j++){

matrix[0][j] = j;

}

144

// Fill in the rest of the matrix

for(i = 1; i <= b.length; i++){

for(j = 1; j <= a.length; j++){

if(b.charAt(i-1) == a.charAt(j-1)){

matrix[i][j] = matrix[i-1][j-1];

} else {

matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution

Math.min(matrix[i][j-1] + 1, //

insertion

matrix[i-1][j] + 1)); //

deletion

}

}

}

return matrix[b.length][a.length];

};

/*********************************************************************

**********

**

** Function: obtieneTiempo()

** Inputs: Two strings of time.

** Return: The difference between both strings.

**

** Description:

** It calculates the difference between both strings.

**

**********************************************************************

*********/

function obtieneTiempo(cad1, cad2){

var annio = parseInt(cad2.substring(0,4)) -

parseInt(cad1.substring(0,4));

var mes = parseInt(cad2.substring(5,7)) -

parseInt(cad1.substring(5,7));

var dias = parseInt(cad2.substring(8,10)) -

parseInt(cad1.substring(8,10));

var horas = parseInt(cad2.substring(11,13)) -

parseInt(cad1.substring(11,13));

var minutos = parseInt(cad2.substring(14,16)) -

parseInt(cad1.substring(14,16));

var segundos = parseInt(cad2.substring(17,19)) -

parseInt(cad1.substring(17,19));

var tiempo = "";

if (segundos < 0) {

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0) {

horas--;

minutos = 60 + minutos;

}

if (horas < 0) {

dias--;

horas = 24 + horas;

}

if (dias < 0) {

mes--;

dias = 30 + dias;

}

145

if (mes < 0) {

annio--;

mes = 12 + mes;

}

if (segundos.toString().length < 2){

segundos = "0"+segundos;

}

if (minutos.toString().length < 2){

minutos = "0"+minutos;

}

if (horas.toString().length < 2){

horas = "0"+horas;

}

if (annio != 0){

if (annio != 1 && mes != 1)

tiempo = annio +" a&ntildeos, "+ mes + " meses, " +

dias +" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio != 1 && mes == 1)

tiempo = annio +" a&ntildeos, "+ mes + " mes, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio == 1 && mes != 1)

tiempo = annio +" a&ntildeo, "+ mes + " meses, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

else if (annio == 1 && mes == 1)

tiempo = annio +" a&ntildeo, "+ mes + " mes, " + dias

+" d&iacuteas y " + horas+":"+minutos+":"+segundos;

} else if (mes != 0){

if (mes != 1 && dias != 1)

tiempo = mes + " meses, " + dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else if (mes != 1 && dias == 1)

tiempo = mes + " meses, " + dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

else if (mes == 1 && dias != 1)

tiempo = mes + " mes, " + dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else if (mes == 1 && dias == 1)

tiempo = mes + " mes, " + dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

} else if (dias != 0){

if (dias != 1)

tiempo = dias +" d&iacuteas y " +

horas+":"+minutos+":"+segundos;

else

tiempo = dias +" d&iacutea y " +

horas+":"+minutos+":"+segundos;

} else

tiempo = horas+":"+minutos+":"+segundos;

return tiempo;

}

/*********************************************************************

**********

**

** Function: deshabilitar()

** Inputs: None.

146

** Return: None.

**

** Description:

** It disables the questions buttons.

**

**********************************************************************

*********/

function deshabilitar (){

for (var i = 0; i < contador; i++){

document.getElementsByName("valida"+i)[0].disabled =

true;

document.getElementsByName("borra"+i)[0].disabled =

true;

if (i + 1 != contador)

document.getElementsByName("posponer"+i)[0].disabled = true;

}

}

/*********************************************************************

**********

**

** Function: ponRespuestas()

** Inputs: An integer depending on the rows of the table.

** Return: None.

**

** Description:

** It adds the answers given by the learner in a summary table.

**

**********************************************************************

*********/

function ponRespuestas(entero){

for (var i = 0; i < contador; i++){

var res = respuestas[i];

var lat = latencias[i];

if (lat == "undefined")

lat = "Sin latencia";

var puntos = puntosPreguntas[i];

var aux = entero + i;

var preg = "PE"+ aux;

document.getElementById("PE"+aux).innerHTML = res;

if (puntosPreguntas[i] != "undefined")

document.getElementById("PE"+aux).innerHTML += " " +

puntosPreguntas[i] + " puntos";

else

document.getElementById("PE"+aux).innerHTML += " 0

puntos";

document.getElementById("PE"+aux).innerHTML += " "+lat;

}

}

8.5 Librería JavaScript creaPreguntas12.js

/* creaPreguntas.js JavaScript library by Sergio Díaz

([email protected])

Librería desarrollada por Sergio Díaz Fuentes. Se permite cualquier

explotación de la obra, incluyendo una finalidad comercial, así como

la creación de obras derivadas, la distribución de las cuales también

está permitida sin ninguna restricción.

*/

147

var puntosPreguntas = new Array ();

var puntuacion = 0;

var total = 0;

var longitudPreg = 0;

var auxiliarCont = 0;

var contador = 0;

var auxiliarResp = ["a","b","c","d","e","f","g","h","i","j"];

var miTiempo="";

var latencias = new Array ();

var f=new Date();

var hora = f.getHours();

var min = f.getMinutes();

var sec = f.getSeconds();

if (hora < 10)

hora = "0"+ hora;

if (min < 10)

min = "0"+ min;

if (sec < 10)

sec = "0"+ sec;

var horaInicio = hora+":"+min+":"+sec;

var respuestas = new Array ();

rellenaPuntos();

/*********************************************************************

**********

**

** Function: loadXMLDoc()

** Inputs: The name of the xml file in which the questions are, the

number of questions of the test, time for the test, random questions

and random options.

** Return: The number of requested test questions presented randomly.

**

** Description:

** Requests the xml file and presents the questions.

**

**********************************************************************

*********/

function loadXMLDoc (xmlName,numPreguntas,time,aleaPr, aleaOp) {

var xmlhttp;

if (window.XMLHttpRequest) {

xmlhttp = new XMLHttpRequest();

} else {

// code for older browsers

xmlhttp = new ActiveXObject("Microsoft.XMLDOM");

}

xmlhttp.onreadystatechange = function() {

if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {

var xmlDoc = xmlhttp.responseXML;

if (xmlDoc != null){

ponDiv();

ponPuntuacion();

if (parseInt(time) > 0){

if (time.toString().length < 2)

var t = "0" + time + ":00";

else

var t = time + ":00";

ponTime(t);

148

}

if (time != 0)

miTiempo = setInterval(

function(){cuentAtras()}, 1000);

// En la variable div_preguntas obtenemos el

contenedor div con el id 'preguntas'

var div_preguntas =

document.getElementById('preguntas');

// Obtenemos la lista de preguntas

var preguntas_set =

xmlDoc.getElementsByTagName("questestinterop")[0].getElementsByTagName

("item");

longitudPreg = preguntas_set.length;

// Comprobamos que el número de preguntas sea

igual o inferior al número de preguntas disponibles

if (numPreguntas > preguntas_set.length){

numeroPr = preguntas_set.length;

}

//Mezclamos un array auxiliar que nos servirá

para obtener las preguntas de forma aleatoria

if (aleaPr.localeCompare("si") == 0)

var arrayaux = arrayAlea(preguntas_set);

else

var arrayaux =

obtenArray(preguntas_set.length);

while (auxiliarCont < numeroPr){

// Obtenemos el título de la pregunta

var titulo =

preguntas_set[arrayaux[auxiliarCont]].getAttribute("title");

var

descripcion=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName

("mattext")[0].childNodes[0].nodeValue;

if (descripcion.indexOf("&lt;p&gt;") == -1)

descripcion = "<p>"+descripcion+"</p>";

//Obtenemos el tipo de la pregunta

var

tipo=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("qtime

tadatafield")[1].getElementsByTagName("fieldentry")[0].childNodes[0].n

odeValue;

//Obtenemos el identificador de la pregunta

var

id=preguntas_set[arrayaux[auxiliarCont]].getAttribute("ident");

//Inicializamos un par de flags auxiliares a

cero

var flag=0;

var flagE = 0;

var patt = "";

149

//Inicializamos las opciones de la pregunta a

un string vacío

var opciones="";

if(tipo=="SINGLE CHOICE QUESTION"){

// Obtenemos las opciones de la pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los puntos de cada opcion

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var");

opciones+=unica(opciones_set,id,puntos,contador);

//patt = obtieneCorrecta

(opciones_set,puntos);

total+=sumaPuntos(puntos, tipo, 0);

flagE = 1;

} else

if(tipo=="NUMERIC QUESTION"){

//Obtenemos los límites superior e

inferior para la pregunta

var

sup=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varlte

")[0].childNodes[0].nodeValue;

var

inf=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("vargte

")[0].childNodes[0].nodeValue;

//Obtenemos el numero máximo de

caracteres permitidos

var

maxcar=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("ren

der_fib")[0].getAttribute("maxchars");

//Obtenemos los puntos en caso de

acierto

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var")[0].childNodes[0].nodeValue;

opciones+=numerica(id,sup,inf,puntos,maxcar,contador);

total+=parseFloat(puntos);

flagE = 1;

} else

if(tipo=="MULTIPLE CHOICE

QUESTION"){

// Obtenemos las opciones

de la pregunta

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

//Obtenemos los puntos de

cada opcion

var

puntos=preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("set

var");

opciones+=multiple(opciones_set,id,puntos,contador);

total+=sumaPuntos(puntos,

tipo, 0);

flagE = 1;

} else

150

//https://social.msdn.microsoft.com/Forums/es-ES/119aa457-11f6-

408b-9c70-d368deabd597/javascript-saber-si-una-letra-esta-en-una-

cadena-de-caracteres?forum=netfxwebes

if(tipo=="CLOZE

QUESTION"){

var opciones_set =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("mattext");

var huecosString =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_s

tr");

var huecosNum =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("response_n

um");

var oposibles =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("varequal")

; //Opciones posibles

var puntos =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("setvar");

//Puntos de las distintas opciones

var nHuecos =

huecosString.length + huecosNum.length; //Número total de huecos

var tagNot =

preguntas_set[arrayaux[auxiliarCont]].getElementsByTagName("not");

var arr =

[].slice.call(oposibles);

var arrMat =

[].slice.call(opciones_set);

if (tagNot.length !=

0){

var osobrantes =

tagNot[0].getElementsByTagName("varequal").length;

arr.length =

oposibles.length - osobrantes * 2;

arrMat.length =

opciones_set.length - osobrantes;

}

var gapsT = 0;

var gapsN = 0;

descripcion="";

total+=sumaPuntos(puntos,tipo, oposibles);

var w = 0;

do{

if

(arrMat[w].childNodes[0].nodeValue.indexOf("&lt;p&gt;") != -1)

descripcion += arrMat[w].childNodes[0].nodeValue; //La primera

nunca será un hueco

else{

if

(w==0)

descripcion += "<p>"

+arrMat[w].childNodes[0].nodeValue.replace(/<br \/>/g,

'').replace(/<\/?span[^>]*>/g,"");

else if (w == arrMat.length - nHuecos - 1)

151

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"") +"</p>";

else

descripcion += arrMat[w].childNodes[0].nodeValue.replace(/<br

\/>/g, '').replace(/<\/?span[^>]*>/g,"");

}

if

(gapsT + gapsN < nHuecos){

if

(huecosString.length != 0 && gapsT < huecosString.length){

if (huecosString[gapsT].getAttribute("ident").indexOf(gapsT +

gapsN) != -1){

if

(huecosString[gapsT].getElementsByTagName("render_choice").length ==

1){

descripcion +=

creaDesplegable(id,arr,puntos,gapsT + gapsN);

w = w +

huecosString[gapsT].getElementsByTagName("render_choice")[0].getElemen

tsByTagName("mattext").length;

}

else{

descripcion+="<input type=\"text\"

name=\""+id+"\" class=\"huecoT\"

tipo=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("fibtype")+"\"

sensibilidad=\""+preguntas_set[arrayaux[auxiliarCont]].getElementsByTa

gName("fieldentry")[5].childNodes[0].nodeValue+"\" puntos=\"\"

sol=\"\"

size=\""+huecosString[gapsT].getElementsByTagName("render_fib")[0].get

Attribute("columns")+"\"

maxlength=\""+parseInt(huecosString[gapsT].getElementsByTagName("rende

r_fib")[0].getAttribute("columns"))+"\"/>";

}

gapsT++;

w++;

} else if

(huecosNum[gapsN].getAttribute("ident").indexOf(gapsT + gapsN) != -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

if (min == null)

152

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\"

name=\""+id+"\" class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

gapsN++;

w++;

}

}

else if (huecosNum[gapsN].getAttribute("ident").indexOf(gapsT +

gapsN) != -1){

var min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

innumber");

if (min == null)

min =

huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttribute("m

axnumber");

descripcion += "<input type=\"text\" name=\""+id+"\"

class=\"huecoT\"

tipo=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAtt

ribute("fibtype")+"\" inf=\""+min+"\"

sup=\""+huecosNum[gapsN].getElementsByTagName("render_fib")[0].getAttr

ibute("maxnumber")+"\" puntos=\"\" size=\"3\"

maxlength=\""+parseInt(huecosNum[gapsN].getElementsByTagName("render_f

ib")[0].getAttribute("columns"))+"\"/>";

gapsN++;

w++;

}

}

else

w++;

} while (w <

arrMat.length - nHuecos);

if (contador + 1 != numeroPr &&

auxiliarCont + 1 < preguntas_set.length)

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

153

class=\"boton\"/><input name=\"posponer"+contador+"\" type=\"button\"

value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+contador+")\"/></form>";

else

descripcion+="<input

name=\"valida"+contador+"\" type=\"button\" value=\"Validar\"

onclick=\"puntuaHuecos("+id+","+contador+")\" class=\"boton\"/><input

name=\"borra"+contador+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/></form>";

// Modificamos el contenido

html del contenedor div

if (contador==0)

div_preguntas.innerHTML +=

"<div id=\"div"+contador+"\" style=\"display:block;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion

+"</form></div>";

else

div_preguntas.innerHTML +=

"<div id=\"div"+contador+"\" style=\"display:none;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

"</form></div>";

for (var q = 0; q < nHuecos;

q++){

for (var k = 0; k <

oposibles.length; k++){

if

(oposibles[k].getAttribute("respident").indexOf(q) != -1 &&

document.getElementsByName(id)[q].getAttribute("tipo")){

if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"String") == 0){

var attAnterior =

document.getElementsByName(id)[q].getAttribute("sol");

var puntAnterior =

document.getElementsByName(id)[q].getAttribute("puntos");

document.getElementsByName(id)[q].setAttribute("sol",attAnterior+op

osibles[k].childNodes[0].nodeValue+",");

document.getElementsByName(id)[q].setAttribute("puntos",puntAnterio

r + puntos[k].childNodes[0].nodeValue+",");

}

else if

(document.getElementsByName(id)[q].getAttribute("tipo").localeCompare(

"Decimal") == 0)

document.getElementsByName(id)[q].setAttribute("puntos",

puntos[k].childNodes[0].nodeValue);

}

}

}

if (contador ==0){

patt =

getCorrectaInv(id,tipo);

154

miRTE.setInteractionsId(contador,id);

miRTE.setInteractionsType(contador,"fill-in");

miRTE.setInteractionsTs(contador, timeStamp("1.2"));

miRTE.setInteractionsWeight(contador, 1);

miRTE.setInteractionsCorrectRespPattern(contador, 0,

patt.toLowerCase());

}

flag = 1; //Este

flag nos permite que no se repita la pregunta de nuevo en el

div_preguntas

flagE = 1;

}

// Modificamos el contenido html del contenedor div

if(flag == 0 && flagE == 1){

if (contador == 0){

div_preguntas.innerHTML += "<div

id=\"div"+contador+"\" style=\"display:block;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

opciones+"</form></div>";

patt = getCorrectaInv(id, tipo);

switch (tipo){

case "SINGLE CHOICE QUESTION":

miRTE.setInteractionsId(contador,id);

miRTE.setInteractionsType(contador,"choice");

miRTE.setInteractionsTs(contador,

timeStamp("1.2"));

miRTE.setInteractionsWeight(contador,

1);

miRTE.setInteractionsCorrectRespPattern(contador, 0, patt);

break;

case "MULTIPLE CHOICE QUESTION":

miRTE.setInteractionsId(contador,id);

miRTE.setInteractionsType(contador,"choice");

miRTE.setInteractionsTs(contador,

timeStamp("1.2"));

miRTE.setInteractionsWeight(contador,

1);

miRTE.setInteractionsCorrectRespPattern(contador, 0, patt);

break;

case "NUMERIC QUESTION":

miRTE.setInteractionsId(contador,id);

miRTE.setInteractionsType(contador,"numeric");

miRTE.setInteractionsTs(contador,

timeStamp("1.2"));

miRTE.setInteractionsWeight(contador,

1);

155

miRTE.setInteractionsCorrectRespPattern(contador, 0, patt);

break;

default:

console.log("No es un tipo

válido de pregunta");

}

} else

div_preguntas.innerHTML += "<div

id=\"div"+contador+"\" style=\"display:none;\"><form

id=\"formulario"+contador+"\" tipo=\""+tipo+"\"><h3>" +

parseInt(contador+1) + ". " + titulo + "</h3>" + descripcion +

opciones+"</form></div>";

}

if(flagE == 1){

auxiliarCont++;

contador++;

} else{

auxiliarCont++;

}

if (auxiliarCont >= preguntas_set.length)

break;

}

//ponBoton();

actualiza(puntuacion,total);

}

}

};

xmlhttp.open("GET", xmlName, true);

xmlhttp.send();

}

/*********************************************************************

**********

**

** Function: ponDiv()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a div tag to the html page.

**

**********************************************************************

*********/

function ponDiv(){

var midiv = document.createElement("div");

midiv.setAttribute("id","preguntas");

midiv.setAttribute("class","desplazado");

//document.body.appendChild(midiv); // Lo pones en "body", si

quieres ponerlo dentro de algún id en concreto usas

document.getElementById('donde lo quiero poner').appendChild(midiv);

document.getElementById('tabla').appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: ponPuntuacion()

156

** Inputs: None.

** Return: None.

**

** Description:

** It adds a puntuation div tag to the html page.

**

**********************************************************************

*********/

function ponPuntuacion(){

var midiv = document.createElement("div");

midiv.setAttribute("class","cuadroFijo");

midiv.setAttribute("id","divPuntuacion");

var centr = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("value","");

cuadro.setAttribute("id","tupuntuacion");

cuadro.setAttribute("size","3");

cuadro.setAttribute("maxlength","3");

cuadro.setAttribute("readonly","true");

cuadro.setAttribute("class","altura puntos");

centr.appendChild(cuadro);

midiv.appendChild(centr);

document.body.appendChild(midiv);

}

/*********************************************************************

**********

**

** Function: ponTime()

** Inputs: The time in minutes of test performance.

** Return: None.

**

** Description:

** It adds a time div tag to the html page.

**

**********************************************************************

*********/

function ponTime(time){

var midiv = document.createElement("div");

midiv.setAttribute("id","divTiempo");

midiv.setAttribute("class","cuadroFijoT");

var para = document.createElement("p");

para.setAttribute("align","center");

var t = document.createTextNode("Tiempo");

para.appendChild(t);

var centrado = document.createElement("center");

var cuadro = document.createElement("input");

cuadro.setAttribute("type","text");

cuadro.setAttribute("id","tutiempo");

cuadro.setAttribute("value",time); cuadro.setAttribute("size","3");

cuadro.setAttribute("class","altura puntos");

cuadro.setAttribute("readonly","true");

cuadro.setAttribute("maxlength","3");

centrado.appendChild(cuadro);

midiv.appendChild(para);

midiv.appendChild(centrado);

document.body.appendChild(midiv);

}

157

/*********************************************************************

**********

**

** Function: cuentAtras()

** Inputs: None.

** Return: None.

**

** Description:

** It decrements the clock.

**

**********************************************************************

*********/

function cuentAtras(){

var cad = document.getElementById("tutiempo").value;

var minutos = parseInt(cad.substring(0,2));

var segundos = parseInt(cad.substring(3,cad.length));

segundos--;

if (segundos < 0){

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0){

clearInterval(miTiempo);

deshabilitar();

smoke.alert("Lo sentimos. Se ha acabado tu tiempo para

responder");

} else{

if (minutos.toString().length < 2)

minutos = "0" + minutos;

if (segundos.toString().length < 2)

segundos = "0" + segundos;

document.getElementById("tutiempo").value = minutos

+":"+segundos;

}

}

/*********************************************************************

**********

**

** Function: ponBoton()

** Inputs: None.

** Return: None.

**

** Description:

** It adds a button to the html page to correct questions.

**

**********************************************************************

*********/

function ponBoton(){

// En la variable div_preguntas obtenemos el contenedor div

con el id 'preguntas'

var div_preguntas = document.getElementById('preguntas');

div_preguntas.innerHTML += "<input type=\"button\" name=\"test\"

value=\"Corregir todas\" onclick=\"corrige()\" class=\"corrige\"/>";

}

/*********************************************************************

**********

**

158

** Function: arrayAlea()

** Inputs: An array of questions.

** Return: An array with unordered values depending of the length of

de questions array.

**

** Description:

** It mixes a vector of sorted values.

**

**********************************************************************

*********/

function arrayAlea(array){

var unArray=[];

for(var z=0;z<array.length;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

/*********************************************************************

**********

**

** Function: obtenArray()

** Inputs: An integer.

** Return: An array of ordered values with variable length depending

of the integer.

**

** Description:

** It creates an ordered array.

**

**********************************************************************

*********/

function obtenArray(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

return unArray;

}

/*********************************************************************

**********

**

** Function: arrayAleaEnt()

** Inputs: An integer.

** Return: An array with unordered values depending of the integer.

**

** Description:

** It mixes a vector of sorted values.

**

**********************************************************************

*********/

function arrayAleaEnt(entero){

var unArray=[];

for(var z=0;z<entero;z++){

unArray[z]=z;

}

fisher_yates(unArray);

return unArray;

}

159

/*********************************************************************

**********

**

** Function: rellenaPuntos()

** Inputs: None.

** Return: None.

**

** Description:

** It initializes the points vector called puntosPreguntas.

**

**********************************************************************

*********/

function rellenaPuntos(){

for (var j=0; j < numeroPr; j++){

puntosPreguntas[j] = 0;

}

}

/*********************************************************************

**********

**

** Function: fisher_yates()

** Inputs: An array of sorted values.

** Return: The function returns the untidy vector.

**

** Description:

** It mixes a vector of sorted values according to Fisher-Yates

algorithm.

**

**********************************************************************

*********/

function fisher_yates(array){

var i=array.length;

while(i--){

var j=Math.floor( Math.random() * (i+1) );

var tmp=array[i];

array[i]=array[j];

array[j]=tmp;

}

}

/*********************************************************************

**********

**

** Function: sumaPuntos()

** Inputs: An array with the points of a question and the type of the

question.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this question..

**

**********************************************************************

*********/

function sumaPuntos(pts,tipo,ngaps){

var suma= 0;

var cont = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

160

suma+=Math.max.apply(null, arr);

} else if (tipo.localeCompare("CLOZE QUESTION") == 0){

suma += getPuntos(pts,ngaps);

} else {

for(var z=0;z<pts.length;z++){

if (parseFloat(pts[z].childNodes[0].nodeValue) > 0)

suma+=parseFloat(pts[z].childNodes[0].nodeValue);

}

}

return suma;

}

/*********************************************************************

**********

**

** Function: getPuntos()

** Inputs: An array with the points of a question and the number of

gaps.

** Return: The maximum points for this question.

**

** Description:

** It calculates the maximum number of points for this cloze question.

**

**********************************************************************

*********/

function getPuntos(pts,ngaps){

cont = 0;

cont2 = 0;

z=0;

suma = 0;

var arr = [];

do{

if (ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

else if (ngaps[z].getAttribute("respident").indexOf(cont) ==

-1){

suma+=Math.max.apply(null, arr);

arr = [];

cont++;

cont2 = 0;

if

(ngaps[z].getAttribute("respident").indexOf(cont) != -1 &&

parseFloat(pts[z].childNodes[0].nodeValue) > 0){

arr[cont2] =

parseFloat(pts[z].childNodes[0].nodeValue);

cont2++;

}

}

z++;

if (z == pts.length)

suma+=Math.max.apply(null, arr);

} while (z < pts.length);

return suma;

}

/*********************************************************************

**********

161

**

** Function: creaDesplegable()

** Inputs: The identifier, the options of the cloze question, the

points of the question and an integer that represents the order of the

question.

** Return: A drop-down list.

**

** Description:

** It creates a drop-down list gap.

**

**********************************************************************

*********/

function creaDesplegable (ident, opcs, pts, ent){

if (opAleat.localeCompare("si") == 0)

var unArray = arrayAleaEnt(opcs.length);

else

var unArray = obtenArray (opcs.length);

var lista = "<select name=\""+ident+"\" class=\"desplegable\"

tipo=\"Lista\">";

for (j = 0; j < opcs.length; j++){

if (opcs[unArray[j]].getAttribute("respident").indexOf(ent)

!= -1)

lista += "<option

value=\""+opcs[unArray[j]].childNodes[0].nodeValue+"\"

puntos=\""+pts[unArray[j]].childNodes[0].nodeValue+"\"

>"+opcs[unArray[j]].childNodes[0].nodeValue +"</option>";

}

lista += "</select>";

return lista;

}

/*********************************************************************

**********

**

** Function: obtieneCorrecta()

** Inputs: An array with the options of the question and another

array with the points of each option.

** Return: The correct answer of a multiple choice question and only

answer.

**

** Description:

** It obtains the correct answer of a multiple choice question and

only answer.

**

**********************************************************************

*********/

function obtieneCorrecta(opciones_tag, pts){

var corr = "";

var arr = [];

for(var z=0;z<pts.length;z++){

arr[z] = parseFloat(pts[z].childNodes[0].nodeValue);

}

corr =

normalize(opciones_tag[arr.indexOf(Math.max.apply(null,

arr))+1].childNodes[0].nodeValue);

if (corr.replace(/\s/g,"_").length < 66)

corr = corr.replace(/\s/g,"_");

else

162

corr =

auxiliarResp[arr.indexOf(Math.max.apply(null, arr),0)];

return corr;

}

/*********************************************************************

**********

**

** Function: obtieneCorrectaMul()

** Inputs: An array with the options of the question and another

array with the points of each option.

** Return: The correct answers of a multiple choice question and

multiple answer.

**

** Description:

** It obtains the correct answer of a multiple choice question and

multiple answer.

**

**********************************************************************

*********/

function obtieneCorrectaMul(opciones_tag, pts){

var corr = "";

var corr2 = "";

var count = 0;

for(var z=0;z < pts.length; z = z + 2){

if (parseFloat(pts[z].childNodes[0].nodeValue) > 0){

if (count == 0){

corr =

normalize(opciones_tag[z/2+1].childNodes[0].nodeValue);

corr2 = auxiliarResp[z/2];

} else {

corr =

corr.concat("[,]",normalize(opciones_tag[z/2+1].childNodes[0].nodeValu

e));

corr2 = corr2.concat("[,]",auxiliarResp[z/2]);

}

count++;

}

}

if (corr.replace(/\s/g,"_").length < 66)

return corr.replace(/\s/g,"_");

else

return corr2;

}

/*********************************************************************

**********

**

** Function: unica()

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and only answer.

**

**********************************************************************

*********/

163

function unica(opciones_tag,ident,pts,ent){

if (opAleat.localeCompare("si") == 0)

var vector = arrayAleaEnt(pts.length);

else

var vector = obtenArray(pts.length);

var opciones="<form name=\"formulario\">";

for (var j = 0; j < pts.length; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"puntos=

\""+pts[vector[j]].childNodes[0].nodeValue+"\"/>"+opciones_tag[vector[

j]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

cadena = cadena.replace("</p>",'');

opciones += "<p class=\opciones\><input type=\"radio\"

name=\""+ident+"\"

value=\""+cadena+"\"puntos=\""+pts[vector[j]].childNodes[0].nodeValue+

"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarUni("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+ent+"\" type=\"button\"

value=\"Posponer\" class=\"boton\" onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarUni("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: numerica()

** Inputs: An identifier, two floats, an array with the options

points and two integers.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a numeric question.

**

**********************************************************************

*********/

function numerica(iden,superior,inferior,pts,maxcar,ent){

var opciones="<form name=\"formulario\">";

opciones += "<p class=\"opciones\">Escribe el valor: <input

type=\"text\" name=\""+iden+"\" inf=\""+inferior+"\"

sup=\""+superior+"\" puntos=\""+pts+"\" size=\"3\"

maxlength=\""+parseInt(maxcar)+"\"/></p></br>";

164

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/><input name=\"posponer"+ent+"\"

type=\"button\" value=\"Posponer\" class=\"boton\"

onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarNum("+iden+","+ent+","+inferior+","+superior+")\"

class=\"boton\"/><input name=\"borra"+ent+"\" type=\"reset\"

value=\"Borrar\" class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: multiple()

** Inputs: An array of options, an identifier, an array wiht the

options points and an integer.

** Return: The options of a question in a html format.

**

** Description:

** It gets the options of a question to create a multiple choice

question and multiple answer.

**

**********************************************************************

*********/

function multiple(opciones_tag,ident,pts,ent){

//Vectores que contendran los puntos de las opciones

var respondidas=[];

var norespondidas=[];

var j=0;

for(var z = 0; z < pts.length; z=z+2){

respondidas[j]=pts[z].childNodes[0].nodeValue;

norespondidas[j]=pts[z+1].childNodes[0].nodeValue;

j++;

}

if (opAleat.localeCompare("si") == 0)

var vector=arrayAleaEnt(pts.length/2);

else

var vector=obtenArray(pts.length/2);

var opciones="<form name=\"formulario\">";

for (var j = 0; j < pts.length/2; j++){

if

(opciones_tag[vector[j]+1].childNodes[0].nodeValue.indexOf("&lt;p&gt;"

) != -1)

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\"

value=\""+opciones_tag[vector[j]+1].childNodes[0].nodeValue+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+opciones_tag[vector[j

]+1].childNodes[0].nodeValue+"</p>";

else{

var cadena =

opciones_tag[vector[j]+1].childNodes[0].nodeValue.replace("<p>",'');

cadena = cadena.replace("</p>",'');

165

opciones += "<p class=\"opciones\"><input

type=\"checkbox\" name=\""+ident+"\" value=\""+cadena+"\"

respondida=\""+respondidas[vector[j]]+"\"

norespondida=\""+norespondidas[vector[j]]+"\"/>"+cadena+"</p>";

}

}

if (ent + 1 != numeroPr && auxiliarCont < longitudPreg)

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/><input name=\"posponer"+ent+"\" type=\"button\"

value=\"Posponer\" class=\"boton\" onclick=\"muestra("+ent+")\"/>";

else

opciones += "<input name=\"valida"+ent+"\"

type=\"button\" value=\"Validar\"

onclick=\"puntuarMul("+ident+","+ent+")\" class=\"boton\"/><input

name=\"borra"+ent+"\" type=\"reset\" value=\"Borrar\"

class=\"boton\"/>";

return opciones;

}

/*********************************************************************

**********

**

** Function: puntuarUni()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a multiple choice question and only answer.

**

**********************************************************************

*********/

function puntuarUni(iden, ent){

var contestada = "";

var contestadaAux = "";

var res = "";

var puntos = 0;

var opt=document.getElementsByName(iden[0].name);

var corr = '';

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion+=

parseFloat(opt[i].getAttribute("puntos"));

puntos =

parseFloat(opt[i].getAttribute("puntos"));

contestada = normalize(opt[i].value);

contestadaAux = auxiliarResp[i];

}

opt[i].disabled = true;

}

corr = getCorrectaInv(iden[0].name, "SINGLE CHOICE

QUESTION");

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

166

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

//Actualizamos la puntuación y mostramos la siguiente

pregunta

contestada = contestada.toLowerCase();

corr = corr.toLowerCase()

if (contestada.replace(/\s/g,"_").length <= 66){

if (corr.localeCompare(contestada.replace(/[^a-

zA-Z 0-9.]+/g,' ').replace(/\s/g,"_")) == 0)

res="correct";

else

res="wrong";

miRTE.setInteractionsResponseAll(ent,contestada.replace(/[^a-zA-Z

0-9.]+/g,' ').replace(/\s/g,"_"),res);

} else{

if

(corr.localeCompare(contestadaAux.replace(/\s/g,"_")) == 0)

res="correct";

else

res="wrong";

miRTE.setInteractionsResponseAll(ent,contestadaAux.replace(/\s/g,"_

"),res);

}

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

respuestas[ent] = contestada;

if (puntos > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(",")[1]+"! Has conseguido "+puntos+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(",")[1]+"! ¡A por las siguientes

preguntas!");

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1 < 10)

hora1 = "0"+ hora1;

if (min1 < 10)

min1 = "0"+ min1;

if (sec1 < 10)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

muestra(ent);

puntosPreguntas[ent] = Math.round(puntos * 100) / 100;

}

/*********************************************************************

**********

**

** Function: puntuarMul()

** Inputs: An identifier and an integer.

** Return: None.

167

**

** Description:

** It gets the score of a multiple choice question and multiple

answer.

**

**********************************************************************

*********/

function puntuarMul(iden, ent){

var opt = document.getElementsByName(iden[0].name);

var corr = '';

var corr2 = '';

var contestada = "";

var contestadaAux = "";

var res = "";

var puntos = 0;

var count = 0;

var count2 = 0;

for(var i=0; i<opt.length;i++){

if(opt[i].checked){

puntuacion+=parseFloat(opt[i].getAttribute("respondida"));

puntos +=

parseFloat(opt[i].getAttribute("respondida"));

if (count == 0){

contestada =

normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

contestadaAux = auxiliarResp[i];

} else {

contestada =

contestada.concat("[,]"+normalize(opt[i].value).replace(/[^a-zA-Z 0-

9.]+/g,' '));

contestadaAux =

contestadaAux.concat("[,]"+auxiliarResp[i]);

}

count++;

}

else {

puntuacion +=

parseFloat(opt[i].getAttribute("norespondida"));

puntos +=

parseFloat(opt[i].getAttribute("norespondida"));

}

if (parseFloat(opt[i].getAttribute("respondida"))

> 0){

if (count2 == 0){

corr =

normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

corr2 = auxiliarResp[i];

} else {

corr =

corr.concat("[,]"+normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,'

'));

corr2 =

corr2.concat("[,]"+auxiliarResp[i]);

}

count2++;

}

opt[i].disabled = true;

}

168

if (contestada.replace(/\s/g,"_").length <= 66){

if

(corr.replace(/\s/g,"_").localeCompare(contestada.replace(/\s/g,"_"))

== 0)

res="correct";

else

res="wrong";

miRTE.setInteractionsResponseAll(ent,contestada.replace(/\s/g,"_"),

res);

} else{

if

(corr2.localeCompare(contestadaAux.replace(/\s/g,"_")) == 0)

res="correct";

else

res="wrong";

miRTE.setInteractionsResponseAll(ent,contestadaAux.replace(/\s/g,"_

"),res);

}

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

//Actualizamos la puntuación y mostramos la siguiente

pregunta

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

respuestas[ent] = contestada;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1 < 10)

hora1 = "0"+ hora1;

if (min1 < 10)

min1 = "0"+ min1;

if (sec1 < 10)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

if (puntos > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(",")[1]+"! Has conseguido "+puntos+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(",")[1]+"! ¡A por las siguientes

preguntas!");

muestra(ent);

puntosPreguntas[ent] = Math.round(puntos * 100) / 100;

}

/*********************************************************************

**********

169

**

** Function: puntuarNum()

** Inputs: An identifier, an integer and two floats.

** Return: None.

**

** Description:

** It gets the score of a numeric question.

**

**********************************************************************

*********/

function puntuarNum(iden, ent, inf, sup){

var valor = parseFloat(iden.value);

var res = "";

var puntos =

document.getElementsByName(iden.name)[0].getAttribute("puntos");

var points = 0;

if(valor <= sup && valor >= inf){

puntuacion += parseFloat(puntos);

points = parseFloat(puntos);

res = "correct";

}

else

res = "wrong";

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

document.getElementsByName(iden.name)[0].disabled = true;

puntuacion = Math.round(puntuacion * 100) / 100;

miRTE.setInteractionsResponseAll(ent,valor,res);

//Actualizamos la puntuación y mostramos la siguiente

pregunta

actualiza(puntuacion, total);

respuestas[ent] = valor;

if (points > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(",")[1]+"! Has conseguido "+points+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(",")[1]+"! ¡A por las siguientes

preguntas!");

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1 < 10)

hora1 = "0"+ hora1;

if (min1 < 10)

min1 = "0"+ min1;

if (sec1 < 10)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

170

muestra(ent);

puntosPreguntas[ent] = Math.round(points * 100) / 100;

}

/*********************************************************************

**********

**

** Function: puntuaHuecos()

** Inputs: An identifier and an integer.

** Return: None.

**

** Description:

** It gets the score of a cloze question.

**

**********************************************************************

*********/

function puntuaHuecos(iden, ent){

var corr= "";

var corr2= "";

var count = 0;

var countCor = 0;

var contestada = "";

var contestada2 = "";

var res = "";

var points = 0;

if (typeof iden.name !== "undefined"){

//Obtenemos los huecos

var opcs = document.getElementsByName(iden.name);

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo del hueco

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos =

opcs[i].getAttribute("puntos");

var sup =

parseFloat(opcs[i].getAttribute("sup"));

var inf =

parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count == 0){

contestada += sup +"[:]"+ inf;

contestada2 += sup +"[:]"+ inf;

} else {

contestada =

contestada.concat("[,]", sup +"[:]"+ inf);

contestada2 =

contestada.concat("[,]", sup +"[:]"+ inf);

}

if (countCor == 0){

171

corr += sup +"[:]"+ inf;

corr2 += sup +"[:]"+ inf;

} else {

corr = corr.concat("[,]", sup

+"[:]"+ inf);

corr2 = corr2.concat("[,]", sup

+"[:]"+ inf);

}

countCor++;

count++;

} else {

if (count == 0){

contestada += valor +"[:]"+ valor;

contestada2 += valor +"[:]"+ valor;

} else {

contestada = contestada.concat("[,]",

valor +"[:]"+ valor);

contestada2 =

contestada.concat("[,]", valor +"[:]"+ valor);

}

if (countCor == 0){

corr += sup +"[:]"+ inf;

corr2 += sup +"[:]"+ inf;

} else {

corr = corr.concat("[,]", sup +"[:]"+

inf);

corr2 = corr2.concat("[,]", sup

+"[:]"+ inf);

}

countCor++;

count++;

}

} else

if (tipo.localeCompare("Lista") == 0){

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

contestada =

contestada.concat("[,]",

opcs[i].options[opcs[i].options.selectedIndex].value);

contestada2 =

contestada2.concat("[,]",

auxiliarResp[opcs[i].options.selectedIndex]);

}

var arr = [];

for (var k = 0; k <

opcs[i].options.length; k++){

arr[k] =

parseFloat(opcs[i].options[k].getAttribute("puntos"));

}

if (countCor == 0){

172

corr +=

opcs[i].options[arr.indexOf(Math.max.apply(null, arr))].value;

corr2 +=

auxiliarResp[k];

} else{

corr =

corr.concat("[,]",opcs[i].options[arr.indexOf(Math.max.apply(null,

arr))].value);

corr2 =

corr2.concat("[,]", auxiliarResp[k]);

}

count++;

countCor++;

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

} else

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del

hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

if (count==0){

contestada += iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

var arrPunt = [];

for(var k = 0; k <

opciones.length - 1; k++){

if(opcs[i].value.toLowerCase() == opciones[k].toLowerCase()){

if (k==0){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

else if (opciones[k-

1].toLowerCase() != opciones[k].toLowerCase()){

173

puntuacion += parseFloat(puntos[k]);

points

+= parseFloat(puntos[k]);

}

}

arrPunt[k] =

parseFloat(puntos[k]);

}

if (countCor == 0){

corr +=

opciones[arrPunt.indexOf(Math.max.apply(null, arrPunt))];

corr2 +=

auxiliarResp[k];

} else{

corr =

corr.concat("[,]", opciones[arrPunt.indexOf(Math.max.apply(null,

arrPunt))]);

corr2 =

corr2.concat("[,]", auxiliarResp[k]);

}

countCor++;

} else

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada +=

iden.value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

var arrPunt = [];

for(var k = 0; k <

opciones.length - 1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

arrPunt[k] =

parseFloat(puntos[k]);

}

if (countCor

== 0){

corr += opciones[arrPunt.indexOf(Math.max.apply(null, arrPunt))];

corr2 += auxiliarResp[k];

} else{

corr = corr.concat("[,]",

opciones[arrPunt.indexOf(Math.max.apply(null, arrPunt))]);

174

corr2 = corr2.concat("[,]", auxiliarResp[k]);

}

countCor++;

}

//Si tiene distancia de Levenshtein

else {

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

if (countCor==0){

corr += opciones[0];

corr += auxiliarResp[0];

} else{

corr =

corr.concat("[,]",opciones[0]);

ccorr2 +=

corr2.concat("[,]", auxiliarResp[0]);

}

count++;

countCor++;

if(min <= dist){

puntuacion +=

parseFloat(puntos[indice]);

points +=

parseFloat(puntos[indice]);

}

}

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

}

} else {

//Obtenemos los huecos

var opcs = document.getElementsByName(iden[0].name);

175

for(var i = 0; i < opcs.length; i++){

//Obtenemos el tipo de la pregunta

var tipo = opcs[i].getAttribute("tipo");

if (tipo.localeCompare("Decimal") == 0){

var valor = parseFloat(opcs[i].value);

var puntos = opcs[i].getAttribute("puntos");

var sup = parseFloat(opcs[i].getAttribute("sup"));

var inf = parseFloat(opcs[i].getAttribute("inf"));

if(valor<=sup && valor>=inf){

puntuacion += parseFloat(puntos);

points += parseFloat(puntos);

if (count == 0){

contestada += sup +"[:]"+ inf;

contestada2 += sup +"[:]"+ inf;

} else {

contestada = contestada.concat("[,]",

sup +"[:]"+ inf);

contestada2 =

contestada2.concat("[,]", sup +"[:]"+ inf);

}

if (countCor == 0){

corr += sup +"[:]"+ inf;

corr2 += sup +"[:]"+ inf;

} else {

corr = corr.concat("[,]", sup +"[:]"+

inf);

corr2 = corr2.concat("[,]", sup

+"[:]"+ inf);

}

countCor++;

count++;

} else {

if (count == 0){

contestada += valor +"[:]"+ valor;

contestada2 += valor +"[:]"+ valor;

} else {

contestada = contestada.concat("[,]",

valor +"[:]"+ valor);

contestada2 =

contestada.concat("[,]", valor +"[:]"+ valor);

}

if (countCor == 0){

corr += sup +"[:]"+ inf;

corr2 += sup +"[:]"+ inf;

} else {

corr = corr.concat("[,]", sup +"[:]"+

inf);

corr2 = corr2.concat("[,]", sup

+"[:]"+ inf);

}

countCor++;

count++;

}

} else

176

if (tipo.localeCompare("String") == 0){

//Obtenemos la sensibilidad del hueco

var sensibilidad =

opcs[i].getAttribute("sensibilidad");

//Obtenemos los puntos de las

opciones

var puntos =

opcs[i].getAttribute("puntos").split(",");

//Obtenemos los opciones de la

pregunta

var opciones =

opcs[i].getAttribute("sol").split(",");

//Si no es sensible a mayúsculas

if(sensibilidad == "ci"){

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

var arrPunt = [];

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value.toLowerCase()

== opciones[k].toLowerCase()){

if (k==0){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

else if (opciones[k-

1].toLowerCase() != opciones[k].toLowerCase()){

puntuacion += parseFloat(puntos[k]);

points

+= parseFloat(puntos[k]);

}

}

arrPunt[k] =

parseFloat(puntos[k]);

}

if (countCor == 0){

corr +=

opciones[arrPunt.indexOf(Math.max.apply(null, arrPunt))];

corr2 += auxiliarResp[k];

} else{

corr =

corr.concat("[,]", opciones[arrPunt.indexOf(Math.max.apply(null,

arrPunt))]);

corr2 =

corr2.concat("[,]", auxiliarResp[k]);

}

countCor++;

177

} else

//Si es sensible a mayúsculas

if(sensibilidad == "cs"){

if (count==0){

contestada +=

iden[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",iden[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

count++;

var arrPunt = [];

for(var k = 0; k < opciones.length -

1; k++){

if(opcs[i].value ==

opciones[k]){

puntuacion +=

parseFloat(puntos[k]);

points +=

parseFloat(puntos[k]);

}

arrPunt[k] =

parseFloat(puntos[k]);

}

if (countCor == 0){

corr +=

opciones[arrPunt.indexOf(Math.max.apply(null, arrPunt))];

corr2 +=

auxiliarResp[k];

} else{

corr =

corr.concat("[,]", opciones[arrPunt.indexOf(Math.max.apply(null,

arrPunt))]);

corr2 =

corr2.concat("[,]", auxiliarResp[k]);

}

countCor++;

}

//Si tiene distancia de Levenshtein

else {

var dist =

parseInt(sensibilidad.substr(1,1)); //Valor de distancia de

Levenshtein para esta pregunta

var levens = [];

for(var k = 0; k < opciones.length -

1; k++){

levens [k] =

getEditDistance(opcs[i].value, opciones[k]); //Distancia Levenshtein

entre las dos palabras

}

var min = Math.min.apply(null,

levens); //Mínimo del vector

var indice = levens.indexOf(min,0);

//Índice del valor mínimo encontrado

178

if (count==0){

contestada +=

opcs[i].value;

contestada2 +=

auxiliarResp[i];

} else{

contestada =

contestada.concat("[,]",opcs[i].value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[i]);

}

if (countCor==0){

corr += opciones[0];

corr2 += auxiliarResp[0];

} else{

corr = corr.concat("[,]",

opciones[0]);

corr2 =

corr2.concat("[,]", auxiliarResp[0]);

}

count++;

countCor++;

if(min <= dist){

puntuacion +=

parseFloat(puntos[indice]);

points +=

parseFloat(puntos[indice]);

}

}

} else {

if (count==0){

contestada +=

opcs[i].options[opcs[i].options.selectedIndex].value;

contestada2 +=

auxiliarResp[opcs[i].options.selectedIndex];

} else{

contestada =

contestada.concat("[,]",opcs[i].options[opcs[i].options.selectedIndex]

.value);

contestada2 =

contestada2.concat("[,]",auxiliarResp[opcs[i].options.selectedIndex]);

}

count++;

var arr = [];

for (var k = 0; k <

opcs[i].options.length; k++){

arr[k] =

parseFloat(opcs[i].options[k].getAttribute("puntos"));

}

if (countCor == 0){

corr +=

opcs[i].options[arr.indexOf(Math.max.apply(null, arr))].value;

corr2 +=

auxiliarResp[k];

} else{

corr =

corr.concat("[,]",opcs[i].options[arr.indexOf(Math.max.apply(null,

arr))].value);

179

corr2 =

corr2.concat("[,]", auxiliarResp[k]);

}

countCor++;

puntuacion +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

points +=

parseFloat(opcs[i].options[opcs[i].options.selectedIndex].getAttribute

("puntos"));

}

//Deshabilitamos el hueco

opcs[i].disabled = true;

}

}

if (normalize(contestada.replace(/\s/g,"_")).length > 66)

contestada = contestada2;

contestada = contestada.toLowerCase();

corr = corr.toLowerCase();

if

(normalize(corr.replace(/\s/g,"_")).localeCompare(normalize(contestada

.replace(/\s/g,"_"))) == 0)

res="correct";

else

res="wrong";

miRTE.setInteractionsResponseAll(ent,contestada,res);

//Deshabilitamos los botones

document.getElementsByName("valida"+ent)[0].disabled = true;

document.getElementsByName("borra"+ent)[0].disabled = true;

if (ent + 1 != contador)

document.getElementsByName("posponer"+ent)[0].disabled

= true;

//Actualizamos la puntuación y mostramos la siguiente

pregunta

puntuacion = Math.round(puntuacion * 100) / 100;

actualiza(puntuacion, total);

if (points > 0)

smoke.alert("¡Enhorabuena

"+miRTE.getLearnerName().split(",")[1]+"! Has conseguido "+points+ "

puntos en esta pregunta. Sigue así.");

else

smoke.alert("¡No te desanimes

"+miRTE.getLearnerName().split(",")[1]+"! ¡A por las siguientes

preguntas!");

respuestas[ent] = contestada;

var d = new Date();

var hora1 = d.getHours();

var min1 = d.getMinutes();

var sec1 = d.getSeconds();

if (hora1 < 10)

hora1 = "0"+ hora1;

if (min1 < 10)

min1 = "0"+ min1;

180

if (sec1 < 10)

sec1 = "0"+ sec1;

latencias[ent] =

obtenLatencia(hora1+":"+min1+":"+sec1,horaInicio);

muestra(ent);

puntosPreguntas[ent] = Math.round(points * 100) / 100;

}

/*********************************************************************

**********

**

** Function: muestra()

** Inputs: The number of the question and the total number of

questions of the test.

** Return: None.

**

** Description:

** It shows the next question and introduces it in the LMS.

**

**********************************************************************

*********/

function muestra (num){

var next = num + 1;

var patt = "";

if (next < numeroPr){

var aux = "div"+next;

document.getElementById(aux).style.display = "block";

var id =

document.getElementById("formulario"+next).getElementsByTagName("input

")[0].getAttribute("name");

var tipo =

document.getElementById("formulario"+next).getAttribute("tipo");

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsId(next,id);

miRTE.setInteractionsType(next,"choice");

miRTE.setInteractionsTs(next, timeStamp("1.2"));

miRTE.setInteractionsWeight(next, 1);

miRTE.setInteractionsCorrectRespPattern(next, 0, patt);

} else

if (tipo.localeCompare("MULTIPLE CHOICE QUESTION") ==

0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsId(next,id);

miRTE.setInteractionsType(next,"choice");

miRTE.setInteractionsTs(next, timeStamp("1.2"));

miRTE.setInteractionsWeight(next, 1);

miRTE.setInteractionsCorrectRespPattern(next, 0,

patt);

} else

if (tipo.localeCompare("CLOZE QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsId(next,id);

miRTE.setInteractionsType(next,"fill-in");

miRTE.setInteractionsTs(next, timeStamp("1.2"));

miRTE.setInteractionsWeight(next, 1);

miRTE.setInteractionsCorrectRespPattern(next, 0,

patt.toLowerCase());

181

} else

if (tipo.localeCompare("NUMERIC QUESTION") == 0){

patt = getCorrectaInv(id,tipo);

miRTE.setInteractionsId(next,id);

miRTE.setInteractionsType(next,"numeric");

miRTE.setInteractionsTs(next, timeStamp("1.2"));

miRTE.setInteractionsWeight(next, 1);

miRTE.setInteractionsCorrectRespPattern(next, 0,

patt);

}

}

var f2=new Date();

var hora2 = f2.getHours();

var min2 = f2.getMinutes();

var sec2 = f2.getSeconds();

if (hora2 < 10)

hora2 = "0"+ hora2;

if (min2 < 10)

min2 = "0"+ min2;

if (sec2 < 10)

sec2 = "0"+ sec2;

horaInicio = hora2+":"+min2+":"+sec2;

}

/*********************************************************************

**********

**

** Function: actualiza()

** Inputs: The score of the learner and the total number of available

points of the test.

** Return: None.

**

** Description:

** It updates the score and puts it into the square situated in the

top right of the page.

**

**********************************************************************

*********/

function actualiza (pts, sum){

document.getElementById("tupuntuacion").value = pts + "/" + sum;

}

/*********************************************************************

**********

**

** Function: getCorrectaInv()

** Inputs: The identifier of the question and its type

** Return: The correct answer without special signs neither spaces.

**

** Description:

** It obtains the correct answer/s of any question.

**

**********************************************************************

*********/

function getCorrectaInv(iden, tipo){

var opt=document.getElementsByName(iden);

var correcta = "";

var correcta2 = "";

var count = 0;

if (tipo.localeCompare("SINGLE CHOICE QUESTION") == 0){

182

var arr = [];

for(var i=0; i<opt.length;i++){

arr[i] =

parseFloat(opt[i].getAttribute("puntos"));

}

if (opt[arr.indexOf(Math.max.apply(null,

arr))].value.length < 66)

correcta =

normalize(opt[arr.indexOf(Math.max.apply(null,

arr))].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

else

correcta =

auxiliarResp[arr.indexOf(Math.max.apply(null, arr))];

} else

if (tipo.localeCompare("MULTIPLE CHOICE QUESTION") == 0){

for(var i=0; i<opt.length;i++){

if (parseFloat(opt[i].getAttribute("respondida"))

> 0){

if (count==0){

correcta =

normalize(opt[i].value).replace(/[^a-zA-Z 0-9.]+/g,' ');

correcta2 = auxiliarResp[i];

} else {

correcta =

correcta.concat("[,]",normalize(opt[i].value).replace(/[^a-zA-Z 0-

9.]+/g,' '));

correcta2 = correcta2.concat("[,]",

auxiliarResp[i]);

} count++;

}

}

if (correcta.replace(/\s/g,"_").length >= 66)

correcta = correcta2;

} else

if (tipo.localeCompare("CLOZE QUESTION") == 0){

for(var i=0; i<opt.length;i++){

//Obtenemos el tipo de hueco

var hueco = opt[i].getAttribute("tipo");

if (hueco.localeCompare("Decimal") == 0){

if (count==0){

correcta =

opt[i].getAttribute("inf")+"[:]"+opt[i].getAttribute("sup");

correcta2 =

opt[i].getAttribute("inf")+"[:]"+opt[i].getAttribute("sup");

} else{

correcta =

correcta.concat("[,]",opt[i].getAttribute("inf")+"[:]"+opt[i].getAttri

bute("sup"));

correcta2 =

correcta2.concat("[,]",opt[i].getAttribute("inf")+"[:]"+opt[i].getAttr

ibute("sup"));

}

count++;

} else

if (hueco.localeCompare("Lista") ==

0){

if (count==0){

correcta =

getCorrecta(opt[i])[0];

183

correcta2 =

auxiliarResp[getCorrecta(opt[i])[1]];

} else {

correcta =

correcta.concat("[,]",getCorrecta(opt[i])[0]);

correcta2 =

correcta2.concat("[,]",getCorrecta(opt[i])[1]);

}

count++;

} else

if (hueco.localeCompare("String") ==

0){

var opcs =

opt[i].getAttribute("sol").split(",");

var pts =

opt[i].getAttribute("puntos").split(",");

pts.length = pts.length - 1;

var numbers = pts.map(Number);

var index =

numbers.indexOf(Math.max.apply(null, numbers));

if (count==0)

correcta =

opcs[index];

else

correcta =

correcta.concat("[,]",opcs[index]);

count++;

}

}

if (correcta.replace(/\s/g,"_").length >=

66)

correcta = correcta2;

} else

if (tipo.localeCompare("NUMERIC QUESTION")

== 0){

correcta =

opt[0].getAttribute("inf")+"[:]"+opt[0].getAttribute("sup");

}

return normalize(correcta.replace(/\s/g,"_"));

}

/*********************************************************************

**********

**

** Function: getCorrecta()

** Inputs: The options of a drop-down list of a cloze question.

** Return: The correct answer.

**

** Description:

** It obtains the correct answer of the list.

**

**********************************************************************

*********/

function getCorrecta (opciones) {

var arr = [];

cont = 0;

for(var j = 0; j < opciones.options.length; j++){

arr[cont] =

parseFloat(opciones.options[j].getAttribute("puntos"));

184

cont++;

}

var index = arr.indexOf(Math.max.apply(null, arr));

return [opciones.options[index].value,index];

}

/*********************************************************************

**********

**

** Function: normalize()

** Inputs: A string with special signs.

** Return: The same string without any special sign.

**

** Description:

** It normalizes a string.

**

**********************************************************************

*********/

var normalize = (function() {

var from = "ÃÀÁÄÂÈÉËÊÌÍÏÎÒÓÖÔÙÚÜÛãàáäâèéëêìíïîòóöôùúüûÑñÇç",

to = "AAAAAEEEEIIIIOOOOUUUUaaaaaeeeeiiiioooouuuunncc",

mapping = {};

for(var i = 0, j = from.length; i < j; i++ )

mapping[ from.charAt( i ) ] = to.charAt( i );

return function( str ) {

var ret = [];

for( var i = 0, j = str.length; i < j; i++ ) {

var c = str.charAt( i );

if( mapping.hasOwnProperty( str.charAt( i ) ) )

ret.push( mapping[ c ] );

else

ret.push( c );

}

return ret.join( '' );

}

})();

/*********************************************************************

**********

**

** Function: getEditDistance()

** Inputs: Two strings.

** Return: An integer that represents the Levenshtein distance

between the two strings.

**

** Description:

** It gets the Levenshtein distance between the two strings given.

**

**********************************************************************

*********/

function getEditDistance (a, b){

if(a.length == 0) return b.length;

if(b.length == 0) return a.length;

var matrix = [];

// increment along the first column of each row

var i;

185

for(i = 0; i <= b.length; i++){

matrix[i] = [i];

}

// increment each column in the first row

var j;

for(j = 0; j <= a.length; j++){

matrix[0][j] = j;

}

// Fill in the rest of the matrix

for(i = 1; i <= b.length; i++){

for(j = 1; j <= a.length; j++){

if(b.charAt(i-1) == a.charAt(j-1)){

matrix[i][j] = matrix[i-1][j-1];

} else {

matrix[i][j] = Math.min(matrix[i-1][j-1] + 1, // substitution

Math.min(matrix[i][j-1] + 1, //

insertion

matrix[i-1][j] + 1)); //

deletion

}

}

}

return matrix[b.length][a.length];

};

/*********************************************************************

**********

**

** Function: obtieneTiempo()

** Inputs: Two strings of time.

** Return: The difference between both strings.

**

** Description:

** It calculates the difference between both strings.

**

**********************************************************************

*********/

function obtieneTiempo(cad1, cad2){

var horas = parseInt(cad2.substring(0,2)) -

parseInt(cad1.substring(0,2));

var minutos = parseInt(cad2.substring(3,5)) -

parseInt(cad1.substring(3,5));

var segundos = parseInt(cad2.substring(6,8)) -

parseInt(cad1.substring(6,8));

var tiempo = "";

if (segundos < 0) {

minutos--;

segundos = 60 + segundos;

}

if (minutos < 0) {

horas--;

minutos = 60 + minutos;

}

if (horas < 0) {

dias--;

horas = 24 + horas;

}

186

if (segundos.toString().length < 2){

segundos = "0"+segundos;

}

if (minutos.toString().length < 2){

minutos = "0"+minutos;

}

if (horas.toString().length < 2){

horas = "0"+horas;

}

tiempo = horas+":"+minutos+":"+segundos;

return tiempo;

}

/*********************************************************************

**********

**

** Function: obtenLatencia()

** Inputs: Two strings.

** Return: The time between both strings.

**

** Description:

** It calculates the difference between two given strings.

**

**********************************************************************

*********/

function obtenLatencia(cad1, cad2){

var hora1 = cad1.split(":")[0];

var hora2 = cad2.split(":")[0];

var min1 = cad1.split(":")[1];

var min2 = cad2.split(":")[1];

var sec1 = cad1.split(":")[2];

var sec2 = cad2.split(":")[2];

var sec = parseInt(sec1) - parseInt(sec2);

var min = parseInt(min1) - parseInt(min2);

var hora = parseInt(hora1) - parseInt(hora2);

if (sec < 0){

min--;

sec = 60 + sec;

}

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

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

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

return hora+":"+min+":"+sec;

}

/*********************************************************************

**********

**

** Function: ponRespuestas()

** Inputs: An integer depending on the rows of the table.

** Return: None.

**

** Description:

** It adds the answers given by the learner in the table interacting

with the LMS.

**

187

**********************************************************************

*********/

function ponRespuestas(entero){

for (var i = 0; i < contador; i++){

var res = "";

if (respuestas[i])

res = respuestas[i].toString();

else

res = "No contestada";

res = res.split("[,]");

var lat = latencias[i];

var aux = entero + i;

document.getElementById("PE"+aux).innerHTML = res;

if (typeof lat == "undefined")

lat = "Sin latencia";

document.getElementById("PE"+aux).innerHTML += " " + lat;

if (typeof puntosPreguntas[i] !== "undefined" )

document.getElementById("PE"+aux).innerHTML += " " +

puntosPreguntas[i] + " puntos";

else

document.getElementById("PE"+aux).innerHTML += " 0

puntos";

}

}

/*********************************************************************

**********

**

** Function: reiniciar()

** Inputs: None.

** Return: None.

**

** Description:

** It resets a couple of fields in the LMS.

**

**********************************************************************

*********/

function reiniciar (){

for (var i = 0; i < contador; i++){

if (i == 0)

miRTE.setInteractionsResponse(i,"No_contestada");

else {

miRTE.setInteractionsCorrectRespPattern(i,0,"No_contestada");

miRTE.setInteractionsResponse(i,"No_contestada");

}

}

}

/*********************************************************************

**********

**

** Function: deshabilitar()

** Inputs: None.

** Return: None.

**

** Description:

** It disables the questions buttons.

**

188

**********************************************************************

*********/

function deshabilitar (){

for (var i = 0; i < contador; i++){

document.getElementsByName("valida"+i)[0].disabled = true;

document.getElementsByName("borra"+i)[0].disabled = true;

if (i + 1 != contador)

document.getElementsByName("posponer"+i)[0].disabled =

true;

}

}

8.6 Librería JavaScript principal.js

// JavaScript Document

// Nombre del archivo: principal.js

//Autor: Sergio Díaz Fuentes

/*********************************************************************

**********

** Function: loadJsLib()

** Inputs: The path of the XML file, the number of questions of the

test, time for the test, random questions, random options and type of

comunication.

** Return: None.

**

** Description:

** It selects the correct JavaScript library.

**

**********************************************************************

*********/

function loadJsLib(xmlName,numPreguntas,time, aleaPr, aleaOp, comunic)

{

if(comunic.localeCompare("no") == 0){

loadScript("_ejs_library/scripts/creaPreguntasSinCom.js",

function(){

loadXMLDoc(xmlName,numPreguntas,time, aleaPr, aleaOp);

});

} else if (comunic.localeCompare("si") == 0){

loadScript("_ejs_library/scripts/creaPreguntas12.js", function(){

loadXMLDoc(xmlName,numPreguntas,time,

aleaPr, aleaOp);

});

}

}

/*********************************************************************

**********

** Function: loadScript()

** Inputs: The url of the JavaScript library and the call to the

function loadXMLDoc.

** Return: None.

**

** Description:

** It adds the correct library to the html page.

**

189

**********************************************************************

*********/

function loadScript(url, callback) {

var script = document.createElement('script');

if (script.readyState) { // IE

script.onreadystatechange = function () {

if (script.readyState === 'loaded' || script.readyState ===

'complete') {

script.onreadystatechange = null;

callback();

}

};

} else { // Others

script.onload = function() {

callback();

};

}

script.src = url;

document.getElementsByTagName('head')[0].appendChild(script);

}

8.7 Hoja de estilo estiloIndex.css

@font-face{

font-family: Dinlight;

src: url('../html/DIN-Light.woff') format('woff');

src: url('../html/dinlight.ttf') format('truetype');

src: url('../html/dinbeklight-webfont.woff?#iefix') format('woff');

}

body { margin-left: 3%;

margin-right: 3%;

margin-top: 2%;

margin-bottom: 2%;

text-align: justify;

overflow-x: hidden;

}

p{ font-family: Dinlight, cursive;

line-height: 1.5em;

margin: 0;

margin-bottom:1.5em;

text-align: justify;

orphans: 2;

widows: 2;

}

b{ font-family: Dinlight, cursive;

color: #00a65d;

}

ul{ list-style-type: disc;

font-family: Dinlight, cursive;

font-size: 11pt;

}

190

li{ font-family: Dinlight, cursive;

font-size: 11pt;

}

table{ width: 90%;

max-width: 1000px;

border-collapse: collapse;

background-color: #FAE38E;

}

h2{ text-align: center;

font-family: Dinlight, cursive;

color: #00a65d;

}

h3{ font-family: Dinlight, cursive;

color: #00a65d;

}

p span{ font-weight: bold;

}

video{ max-width: 87%;

height: 94%;

margin-right: 7%;

}

[type="checkbox"] { cursor: pointer;

}

[type="checkbox"]:disabled{

cursor: not-allowed;

}

[type="radio"] { cursor: pointer;

}

[type="radio"]:disabled{

cursor: not-allowed;

}

[type="text"] { cursor: text;

}

[type="text"]:disabled{

cursor: not-allowed;

}

.tablaIma{

background-color: #ffffff;

border-collapse: collapse;

}

.trIma{

border: 3px solid #ddb10a;

}

191

.thIma{

background-color: #00a65d;

}

.pEnca{

font-family: Dinlight, cursive;

color: white;

text-align: center;

height: 2px;

font-size: 14pt;

}

.boton{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:160px;

height:22px;

cursor: pointer;

margin-left: 2%;

}

.boton:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.boton:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.boton2{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:10%;

height:3%;

cursor: pointer;

margin-left: 2%;

}

.boton2:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.boton2:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.desplazado {

192

margin-top: 2em;

margin-left: 2em;

margin-right: 2em;

}

.desplaza2 { margin-right: 2em;

margin-left: 2em;

margin-top: 2em;

margin-bottom: 2em;

}

.desplegable{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: lighter;

}

.desplegable:disabled{

cursor: not-allowed;

}

.altura{

height:35%;

width:70%;

text-align: center;

bottom: 5%;

}

.cuadroFijo{

position: fixed;

opacity: 0.9;

border: 3px solid #00a65d;

background: #FAE38E;

width: 8%;

height: 14%;

top: 1%;

right: 0.5%;

}

.parrafoPuntos{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: bold;

}

.puntos{

font-family: Dinlight, cursive;

font-size: 12pt;

font-weight: bold;

cursor: not-allowed;

}

.huecoT{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: lighter;

cursor: text;

}

#integracion {

193

background:-webkit-linear-gradient(top, #FAE38E, #F7C91F);

width: 100%;

height: 100%;

position: relative;

text-align: center;

margin: 0 auto;

border: 3px solid #00a65d;

}

8.8 Hoja de estilo estilo.css

@font-face{

font-family: Dinlight;

src: url('../html/DIN-Light.woff') format('woff');

src: url('../html/dinlight.ttf') format('truetype');

src: url('../html/dinbeklight-webfont.woff?#iefix') format('woff');

}

b{ font-family: Dinlight, cursive;

color: #00a65d;

}

body { margin-left: 3%;

margin-right: 3%;

margin-top: 2%;

margin-bottom: 2%;

text-align: justify;

overflow-x: hidden;

}

p{ font-family: Dinlight, cursive;

font-size:14pt;

}

p span{ font-size:12pt;

font-weight: bold;

}

h2{ text-align: center;

font-family: Dinlight, cursive;

color: #00a65d;

}

h3{ font-family: Dinlight, cursive;

font-size: 20pt;

line-height: 150%;

text-align: center;

margin-left: 2em;

margin-right: 2em;

}

ul{ font-family: Dinlight, cursive;

font-size:12pt;

194

}

table{ width: 100%;

border-collapse: collapse;

}

th{ width: 100%;

}

[type="checkbox"] { cursor: pointer;

}

[type="checkbox"]:disabled{

cursor: not-allowed;

}

[type="radio"] { cursor: pointer;

}

[type="radio"]:disabled{

cursor: not-allowed;

}

[type="text"] { cursor: text;

}

[type="text"]:disabled{

cursor: not-allowed;

}

.epigrafe{

font-family: Dinlight, cursive;

font-weight: bold;

font-size: 16pt;

color: #00a65d;

}

.desplaza2 { width: 90%;

margin-right: 2em;

margin-left: 2em;

margin-top: 2em;

margin-bottom: 2em;

}

.desplazado { margin-top: 2em;

margin-left: 2em;

margin-right: 2em;

}

.desplegable{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: lighter;

cursor: pointer;

}

.desplegable:disabled{

cursor: not-allowed;

}

195

.huecoT{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: lighter;

cursor: text;

}

.opciones{

font-family: Dinlight, cursive;

font-size:14pt;

font-weight: lighter;

}

.boton{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:13%;

height:3%;

cursor: pointer;

margin-left: 2%;

margin-bottom: 2%;

}

.boton:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.boton:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.boton2{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:30%;

cursor: pointer;

height:100%;

}

.boton2:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.boton2:disabled{

opacity: 0.6;

cursor: not-allowed;

}

196

.corrige{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:12%;

height:4%;

margin-left: 26em;

}

.botonLMS{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:21%;

height:5%;

cursor: pointer;

}

.botonLMS:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.botonLMS:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.botonMostrar{

font-size:16px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

background:#00a65d;

border:0px;

width:20%;

height:100%;

margin-left: 40%;

cursor: pointer;

}

.botonMostrar:hover{

background: #00884D;

box-shadow: 0 6px 12px 0 rgba(0,0,0,0.24), 0 17px 50px 0

rgba(0,0,0,0.19);

}

.botonMostrar:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.boton3 {

197

background: #00a65d;

font-size:20px;

font-family:Dinlight, cursive;

font-weight:bold;

color:white;

outline: 0;

height: 200px; /* Alto del botón */

width: 200px; /* Ancho del botón */

display: table;

border-radius: 100%;

cursor: pointer;

box-shadow:

/* Sombras internas */

inset 0 10px 15px rgba(255,255,255,.35), inset 0 -10px 15px

rgba(0,0,0,.05), inset 10px 0 15px rgba(0,0,0,.05), inset -10px 0 15px

rgba(0,0,0,.05),

/* Sombra externa */

0 5px 20px rgba(0,0,0,0.4);

}

/* Al presionar */

.boton3:active { box-shadow: inset 0 5px 30px rgba(0,0,0,.2); /* Sombra interior */

background-size: 55%; /* Cambiamos el tamaño de la imagen */

}

.boton3:hover{

background: #00884D;

}

.boton3:disabled{

opacity: 0.6;

cursor: not-allowed;

}

.estiloTitulo {font-family: Dinlight, cursive; font-weight: bold;

font-size: 26px;

color: #FFFFFF;

}

.fila{

text-align: right;

}

.altura{

height:35%;

width:80%;

text-align: center;

margin-top: 7%;

}

.cuadroFijo{

position: fixed;

opacity: 0.9;

border: 3px solid #00a65d;

background: #FAE38E;

width: 8%;

height: 7%;

top: 1%;

right: 0.5%;

198

}

.cuadroFijoT{

position: fixed;

opacity: 0.9;

border: 3px solid #00a65d;

background: #FAE38E;

width: 10%;

height: 18%;

top: 1%;

left: 0.5%;

}

.parrafoPuntos{

font-family: Dinlight, cursive;

font-size:12pt;

font-weight: bold;

}

.puntos{

font-family: Dinlight, cursive;

font-size: 12pt;

font-weight: bold;

cursor: not-allowed;

}

#parametros{

background:linear-gradient(top, #FAE38E, #F7C91F);

background:-webkit-linear-gradient(top, #FAE38E, #F7C91F);

background:-moz-linear-gradient(top, #FAE38E, #F7C91F);

background:-o-linear-gradient(top, #FAE38E, #F7C91F);

background:-ms-linear-gradient(top, #FAE38E, #F7C91F);

width: 95%;

}

#questions{

background:linear-gradient(top, #FAE38E, #FAD54C);

background:-webkit-linear-gradient(top, #FAE38E, #FAD54C);

background:-moz-linear-gradient(top, #FAE38E, #FAD54C);

background:-o-linear-gradient(top, #FAE38E, #FAD54C);

background:-ms-linear-gradient(top, #FAE38E, #FAD54C);

width: 95%;

}

#resultados{

background:linear-gradient(top, #FAE38E, #F7C91F);

background:-webkit-linear-gradient(top, #FAE38E, #F7C91F);

background:-moz-linear-gradient(top, #FAE38E, #F7C91F);

background:-o-linear-gradient(top, #FAE38E, #F7C91F);

background:-ms-linear-gradient(top, #FAE38E, #F7C91F);

width: 95%;

}

199

Advanced Distributed Learning. (2009). SCORM 2004 Run-Time Environment [RTE].

CSS W3C. (n.d.). Retrieved July 30, 2016, from https://www.w3.org/Style/CSS/

CSS Wiki. (n.d.). Retrieved July 30, 2016, from https://es.wikipedia.org/wiki/Hoja_de_estilos_en_cascada#cite_note-CSS1-3

DOM W3C. (n.d.). Retrieved July 29, 2016, from https://www.w3.org/DOM/#what

DOM W3Schools. (n.d.). Retrieved July 29, 2016, from http://www.w3schools.com/js/js_htmldom.aspitle

ESPAÑOLAS, C. U. (2015). Analisis de las TIC en las Universidades Españolas. Universitic 2015. http://doi.org/10.1017/CBO9781107415324.004

Hipertexto HTML. (n.d.). Retrieved July 29, 2016, from http://www.hipertexto.info/documentos/html.htm

HTML W3C. (n.d.). Retrieved August 2, 2016, from https://www.w3.org/html/

Joint Information Systems Committee (JISC). (2007). Effective Practice with e-Assessment. Information Systems Journal. Retrieved from http://www.jisc.ac.uk/publications

Manual de XML. (n.d.). Retrieved July 31, 2016, from http://www.mundolinux.info/que-es-xml.htm

Melorose, J., Perroy, R., & Careas, S. (2015). JavaScript. The definitive Guide, 6th ed. Statewide Agricultural Land Use Baseline 2015 (Vol. 1). http://doi.org/10.1017/CBO9781107415324.004

Ostyn, C. (2007). In the Eye of the SCORM - An introduction to SCORM 2004 for Content Developers. Ostyn Consulting. Retrieved from https://www.google.ba/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CDgQFjAB&url=http://xa.yimg.com/kq/groups/18757856/1506523990/name/SCORM.pdf&ei=9AOKUtGYEsTCswaz7oCICw&usg=AFQjCNFVGGvo6uIPz_-EOaA4MfUxtMHFIQ&sig2=yrZEM_uSs0YmmT0u0YXrGw&bvm=bv.56643336,d

Quiroz, J. (2003). Sociedad de la información y del conocimiento. In Boletín de sistemas Nacionales Estadístico y de Información Geográfica (Vol. 1, pp. 81–92).

Run-Time SCORM. (n.d.). Retrieved August 2, 2016, from http://scorm.com/scorm-explained/technical-scorm/run-time/

SCO SCORM. (n.d.). Retrieved August 2, 2016, from http://www.scormsoft.com/scorm/cam/scos

SCORM explained. (n.d.). Retrieved July 31, 2016, from http://scorm.com/scorm-explained/technical-scorm/

Smoke JS. (n.d.). Retrieved August 4, 2016, from http://smoke-js.com/

Tobergte, D. R., & Curtis, S. (2013). Estándar QTI. Descripción. Journal of Chemical Information and Modeling. http://doi.org/10.1017/CBO9781107415324.004

Ventajas JavaScript. (n.d.). Retrieved August 1, 2016, from https://www.clubensayos.com/Temas-Variados/JAVA-SCRIPT-VENTAJAS-Y-DESVENTAJAS/222066.html

Versiones SCORM. (n.d.). Retrieved August 6, 2016, from http://scorm.com/scorm-

200

explained/business-of-scorm/scorm-versions/

W3C. (n.d.). XML W3C. Retrieved August 2, 2016, from https://www.w3.org/XML/

XML W3C. (n.d.). Retrieved July 31, 2016, from https://www.w3.org/TR/REC-xml/#sec-origin-goals