core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de...

141
PROYECTO FIN DE GRADO GRADO EN INGENIERÍA DEL SOFTWARE Sistema web para la gestión de documentos de pre-producción audiovisual Director: Santiago Alonso Villaverde Autor: David Mª Martín García CURSO 2016-2017 Madrid, junio 2017

Transcript of core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de...

Page 1: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

PROYECTO FIN DE GRADO

GRADO EN INGENIERÍA DEL SOFTWARE

Sistema web para la gestión de documentos

de pre-producción audiovisual

Director: Santiago Alonso Villaverde Autor: David Mª Martín García

CURSO 2016-2017

Madrid, junio 2017

Page 2: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,
Page 3: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

Agradecimientos

Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios

con un trabajo orientado a la misma área de conocimiento, por fin estoy llegando al final de una

etapa y me he demostrado que soy capaz de crear mi propio proyecto de inicio a fin.

Para llegar a este proyecto ha habido muchas cosas que aprender y perfilar, puesto que para mí

este grado ha servido para sentar las bases, además de ampliar, lo que aprendí en mi etapa

educativa anterior: el extinto Ciclo Formativo de Grado Superior de Desarrollo de Aplicaciones

Informáticas. Guardo muy buenos recuerdos y amigos de aquella etapa aún hoy en día, pero

dentro de mí había más inquietudes que tenía que empezar a resolver estudiando esta carrera.

Durante estos años me he cruzado con profesores realmente volcados en su tarea, además de

una institución detrás que, la verdad, me ha facilitado mi labor siempre hasta este momento.

Gracias a todos vosotros por vuestro trabajo, nunca dejéis de actualizaros y actualizarnos, pan

nuestro de cada día en esta rama del conocimiento. En especial el agradecimiento a Santiago,

mi director, por ayudarme no solo en la asignatura en la que nos hemos cruzado sino también

en este proyecto tan importante para mí.

Gran parte de la motivación que me ha ayudado a cumplir mis objetivos año tras año ha sido la

familia que tengo detrás, exigente y comprensiva con mis retos. Hace unos cuantos años que mi

abuela ya no está y, no sé si por aquello de que estudiar te asegura el pan o porque me veía

capacidad, me he acordado de ella por cada año superado, fuera en la etapa educativa que

fuera, y, además, me animaba a seguir adelante. Gracias, de verdad, se te echa de menos.

Prometo que no voy a parar.

Gracias a amigos que estuvieron, que están y los que me llevo de la escuela. Como iguales

siempre hemos crecido juntos y hemos compartido nuestros avances. Con mayor o menor

discrepancia, para mí siempre han resultado la gasolina diaria o, en otros casos, del fin de

semana. Mención especial a la persona más especial que he tenido ocasión de cruzarme en el

camino, sigamos construyendo y caminando juntos.

No puedo dejar de mencionar a los que siempre están y no han faltado a una sola cita, mis

padres, porque sin vosotros nada de esto hubiera sido posible. Un modelo de que en la vida se

pelea por lo que uno quiere, de que de los fallos se aprende y de que fallando mucho eres sabio

si de ello sacas lecciones. Gracias por todo, espero que esto devuelva una mínima fracción de lo

que me habéis aportado del día 0 hasta hoy.

Gracias a todos, los que están y los que no, el autor de esta memoria no soy yo, es un pedacito

de todos vosotros.

“It does not matter how slowly you go as long as you do not stop.” Confucio

Page 4: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,
Page 5: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

Tabla de contenidos

1 Resumen ................................................................................................................................ 3

2 Abstract ................................................................................................................................. 3

3 Introducción .......................................................................................................................... 4

4 Bases teóricas ........................................................................................................................ 5

4.1 Pila de desarrollo MEAN ................................................................................................ 5

4.2 Grunt ............................................................................................................................. 6

4.3 Bower ............................................................................................................................ 6

4.4 Bootstrap ....................................................................................................................... 6

4.5 Mocha y Chai ................................................................................................................. 6

4.6 SCRUM ........................................................................................................................... 7

4.6.1 Manifiesto ágil (principios y valores) .................................................................... 7

4.6.2 Inception Deck ....................................................................................................... 7

4.6.3 Iteraciones ............................................................................................................. 8

5 Concepción del sistema ......................................................................................................... 9

5.1 ¿Por qué estamos aquí? (Why are we here?) ............................................................... 9

5.2 Breve descripción del proyecto (Elevator pitch) ........................................................... 9

5.3 Marketing del producto (Product box) ....................................................................... 10

5.4 Alcance del desarrollo (Not list) .................................................................................. 11

5.5 Stakeholders de apoyo (Meet your neighbours) ........................................................ 12

5.6 Arquitectura a alto nivel de la solución (Show your solution) .................................... 13

5.7 ¿Qué nos mantiene despiertos por la noche? (What keep us up at night?) .............. 14

5.8 Estimación y priorización de características (Size it up) ............................................. 15

5.9 ¿Qué vamos a ofrecer? (Requisitos no funcionales / What’s going to give?) ............. 20

5.10 ¿Qué vamos a necesitar? (What’s going to take?) ...................................................... 21

6 Iteraciones ........................................................................................................................... 22

6.1 Primer sprint................................................................................................................ 22

6.1.1 Sprint backlog ...................................................................................................... 22

6.1.2 Desarrollo del sprint ............................................................................................ 29

6.1.3 Documentación técnica del sprint ...................................................................... 32

6.1.4 Revisión y retrospectiva del sprint ...................................................................... 44

6.1.5 Resultados visuales en el cliente durante el sprint ............................................. 45

6.1.6 Análisis de valor entregado y esfuerzo desempeñado ....................................... 47

6.2 Segundo sprint ............................................................................................................ 50

Page 6: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

6.2.1 Sprint backlog ...................................................................................................... 50

6.2.2 Desarrollo del sprint ............................................................................................ 57

6.2.3 Documentación técnica del sprint ...................................................................... 59

6.2.4 Revisión y retrospectiva del sprint ...................................................................... 66

6.2.5 Resultados visuales en el cliente durante el sprint ............................................. 67

6.2.6 Análisis de valor entregado y esfuerzo desempeñado ....................................... 69

6.3 Tercer sprint ................................................................................................................ 72

6.3.1 Sprint backlog ...................................................................................................... 72

6.3.2 Desarrollo del sprint ............................................................................................ 79

6.3.3 Documentación técnica del sprint ...................................................................... 81

6.3.4 Revisión y retrospectiva del sprint ...................................................................... 99

6.3.5 Resultados visuales en el cliente durante el sprint ........................................... 100

6.3.6 Análisis de valor entregado y esfuerzo desempeñado ..................................... 102

6.4 Cuarto sprint ............................................................................................................. 103

6.4.1 Sprint backlog .................................................................................................... 104

6.4.2 Desarrollo del sprint .......................................................................................... 108

6.4.3 Documentación técnica del sprint .................................................................... 110

6.4.4 Revisión y retrospectiva del sprint .................................................................... 130

6.4.5 Resultados visuales en el cliente durante el sprint ........................................... 131

6.4.6 Análisis de valor entregado y esfuerzo desempeñado ..................................... 133

7 Conclusiones...................................................................................................................... 135

8 Bibliografía ........................................................................................................................ 136

Page 7: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

3

1 Resumen

El sistema de gestión de documentos de pre-producción audiovisual, sujeto y producto de este

libro, es el resultado de aplicar una metodología ágil al proceso de desarrollo de una aplicación

orientada a esta fase de los proyectos audiovisuales. En ella se definen los guiones literarios,

técnicos, además de los elementos de la biblia literaria que formarán parte de los guiones.

Haciendo uso del Inception Deck, de Scrum y de la pila de desarrollo MEAN, se aborda la creación

de una aplicación capaz de cubrir las necesidades de guionistas, técnicos, directores y los

equipos destinados a la labor a realizar en esta etapa de análisis. Como fin de esta etapa y

haciendo uso de la aplicación, los creativos podrán colaborar en sus proyectos con el objetivo

de exportar sus guiones literarios y técnicos y que estos sean visibles desde cualquier dispositivo.

Esta memoria se estructura para mostrar, en primer lugar, el resultado de la fase de concepción

del producto fruto del Inception Deck. Partiendo de esta fase en que se obtiene la hoja de ruta

y una estimación de riesgos y costes, se comienza a hacer seguimiento a cada uno de las entregas

de producto incrementales, desde el análisis hasta la retrospectiva, terminando en un análisis

cuantitativo del rendimiento en cada entrega.

El resultado de aplicar Scrum en el desarrollo de la aplicación aporta flexibilidad ante los cambios

y un producto totalmente configurado a los deseos del cliente, un creativo audiovisual que busca

agilizar la gestión de documentos de esta etapa. Adicionalmente, el uso de Scrum aporta una

mirada crítica a como se desarrollan los diferentes incrementos del producto, mejorando la

metodología del equipo de desarrollo en el camino.

2 Abstract

The product of this book is a pre-production audio-visual document management system as a

result of applying an agile methodology to the development process of an application intended

to be use at this stage. At the pre-production stage, literary and technical scripts are defined,

together with the elements that compose the literary bible which belong to the scripts.

By using the Inception Deck, Scrum and the development MEAN stack, the creation of an

application capable of filling the needs of scriptwriters, technicians, directors and the teams

intended to be part of the work at this stage is addressed. As a consequence of this stage, by

using the application, creative staff will be able to collaborate in their projects with the purpose

of exporting their literary and technical scripts, and to make them available through any device.

This book is structured to show, in the first place, the result of the conception stage after the

Inception Deck. After this stage when a roadmap is obtained together with risks and costs

estimation, there is an incremental product delivery tracking, from the analysis to the

retrospective, to finish with a quantitative analysis of the performance delivered in each sprint.

The result of applying Scrum in the development process contributes to be flexible against

requirement changes and a product fully configured to customer needs, an audio-visual creative

that searches how to speed the document management at the pre-production stage. What’s

more, by using Scrum we have a critical glance about how the different increments of the

product are developed, therefore it can be used to improve the development team’s

methodology.

Page 8: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

4

3 Introducción

Una de las muchas metas del ingeniero de software es hacer realidad las ideas y necesidades

informáticas de cualquier individuo o entidad. Es por ello que, para este proyecto, he decidido

embarcarme en la aventura de abordar la creación de una aplicación desde la concepción de las

primeras ideas hasta el momento de verlas en funcionamiento.

Como el camino entre las ideas y la aplicación es largo, los ingenieros tienen que hacer uso de

metodologías que les ayuden a que los proyectos sean un éxito. Sean pesadas o ágiles, recogen

el conocimiento y la experiencia de proyectos anteriores que hayan ido bien o mal, ayudan a no

cometer los mismos errores y aportan buenas ideas de cómo los profesionales del sector actúan

en su día a día.

Este libro habla de la concepción y el desarrollo de una aplicación de gestión de documentación

de la etapa de preproducción audiovisual. En esta etapa, los creativos establecen las pautas a

seguir durante la producción, es decir, el qué y cómo se va a desarrollar la obra en ciernes.

Lo que se va a tener que gestionar en la aplicación son los tres documentos más importantes de

esta etapa: el guion literario, el guion técnico y la biblia literaria. Estos documentos se aúnan

bajo un proyecto en el que hay involucrado más de un creativo, por lo que es imprescindible que

la aplicación tenga un carácter colaborativo.

Los guiones literario y técnico contienen las escenas que componen la obra. Por un lado, el

literario se encarga de reflejar la narrativa de la obra a través de la descripción de la escena, los

diálogos de los personajes y las acotaciones. El guion técnico, en otro lugar, recoge cómo se

debe abordar la escena para enfatizar la narrativa del literario: planos, iluminación, encuadre y

demás elementos técnicos envueltos en la narrativa técnica.

Personajes, escenarios, el público objetivo, el género o el estilo de la obra, son datos que vienen

recogidos en el documento llamado biblia literaria. Hace las veces de banco creativo de la obra

y es fuente de inspiración para las escenas, puesto que contiene los elementos y directrices en

que se basa la creación audiovisual.

Todas las etapas que han ayudado a que la gestión de estos documentos de una forma

colaborativa sea una realidad, vienen expuestas en este documento haciendo uso de una

metodología ágil llamada Scrum. Apoyadas por el Inception Deck, las diversas iteraciones del

producto aportarán valor de negocio a la solución con el enfoque de abordar lo más crítico y

valioso primero.

El resultado de estas líneas y otras tantas más de código fuente, además de la colaboración

cercana entre cliente y desarrollador, tiene que ser una aplicación web lista para recibir las ideas

de los creativos de obras audiovisuales que están en su etapa de concepción.

Page 9: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

5

4 Bases teóricas

4.1 Pila de desarrollo MEAN

La pila de desarrollo MEAN obedece a las siglas formadas por MongoDB, ExpressJS, Angular y

Node.js como plataforma de ejecución orientada a eventos y basada en el motor de JavaScript

V8 de Chrome. Lo que tienen en común todas estas tecnologías es el mismo lenguaje para

desarrollar sobre ellas: JavaScript. Esto supone un punto fuerte en la implementación y la

evolución de cualquier proyecto de software.

Hasta la aparición de Node.js en 2009, JavaScript era un lenguaje destinado únicamente

destinado al cliente. El hecho de que este lenguaje de scripting se pueda utilizar en el cliente es

gracias a los intérpretes que incorporaban los navegadores web. Google Chrome y su nuevo

motor de JavaScript llamado V8, vinieron a revolucionar el mercado de los navegadores web,

pero colateralmente, también lo hicieron sobre el mundo del desarrollo. Node.js a día de hoy

hace uso de este motor para permitir la creación de servidores web implementados con

JavaScript.

Un año después de Node.js apareció el gestor de paquetes JavaScript npm quien incluso,

después, se convertiría en el gestor de paquetes por defecto para la plataforma, instalándose

junto a ella. Esto hizo que Node.js se popularizara aún más al reunir a la comunidad de

desarrolladores de JavaScript para implementar módulos tan interesantes como ExpressJS que

salió a finales del mismo año en que se publicó npm.

ExpressJS es un conjunto de librerías específico para la creación de aplicaciones web. Desde su

nacimiento se ha convertido en un estándar de facto en Node.js para desarrollar aplicaciones

web y APIs. Una de las características más destacadas de este framework es que es muy

extensible y, por ello, se ha ganado a la comunidad de desarrolladores por su versatilidad por

medio de otros módulos de npm.

MongoDB salió a la luz en el año 2009 como un nuevo gestor de bases de datos NoSQL. Es un

motor orientado a documentos que son almacenados en archivos BSON (Binary JSON), es decir,

todos los tipos de datos de JavaScript son perfectamente soportados. Esto, en el inicio, lo hizo

bastante apetecible para proyectos involucrados con Node.js. Frente a soluciones más maduras

como las que ofrecen los gestores SQL, MongoDB, a costa de prescindir de restricciones de

integridad, transacciones o joins, ofrece una mayor escalabilidad y la posibilidad de estructurar

la información de una forma diferente, sin esquemas restrictivos. Esto lo convierte en una

solución joven e ideal en contextos con datos heterogéneos en estructura y, además, masivos

en cantidad.

Por último, abandonando la parte del servidor y el modelo de persistencia, en el lado del cliente

AngularJS fue publicado a finales del año 2010 para afianzar el modelo de desarrollo de las

aplicaciones web de una sola página (SPA). Basado en el patrón de diseño MVC, revolucionó la

implementación en la parte del cliente por desacoplar la parte visual de la lógica de negocio a

través de las vistas y los controladores. También desacopló la manipulación del árbol DOM de la

lógica de la aplicación con el llamado “two-way binding” o enlace a dos bandas entre modelos y

vistas. Por lo tanto, con esta filosofía, AngularJS se convirtió en un conjunto de librerías ideal

para desarrolladores que buscaban reutilizar código, probarlo con mayor facilidad, ganar en

Page 10: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

6

rendimiento por su modelo asíncrono de peticiones y respuestas y, en definitiva, mejorar la

experiencia de usuario.

En 2014, el equipo de desarrollo de AngularJS decide reescribir completamente la librería y opta

por hacer uso de TypeScript en Angular2 o Angular a secas. Aunque Angular permite su

implementación haciendo uso de ES6, desde el equipo desarrollador se recomienda hacer uso

de TypeScript debido a la facilidad que ofrece para trabajar con un paradigma de programación

orientado a objetos (basado en clases y no prototipos), la presencia de tipado estático y

polimorfismo.

Angular se desarrolla en paralelo a AngularJS y es diferente en casi todo. Especialmente destaca

por su modularidad con los componentes, la mejora en el manejo de las rutas, inyección de

dependencias mejorada, una sintaxis más sencilla centrándose en los corchetes para el enlace

con propiedades o los paréntesis para enlazar con eventos desde las plantillas, carga dinámica

de módulos, permitir el uso de los observables de RxJS (programación reactiva), etc. Es

importante añadir que la migración de una aplicación de AngularJS a Angular no sería posible de

forma directa y requeriría de la completa reingeniería del cliente por parte del equipo

desarrollador del proyecto.

4.2 Grunt

Durante el desarrollo de aplicaciones hay determinadas tareas que pueden llegar a ser

repetitivas. Grunt es un gestor de tareas en JavaScript que es capaz de automatizar tareas de

todo tipo: minimizar archivos de recursos, compilar fuentes, pruebas del software, instalar

dependencias, etc. Publicado en 2012, se puede adquirir desde el gestor de paquetes npm y es

personalizable por medio de extensiones desarrolladas por el propio equipo de Grunt o terceros,

todo con el objetivo de automatizar tareas rutinarias con las ventajas que esto conlleva.

4.3 Bower

Bower nace con la idea de facilitar la gestión de dependencias en las aplicaciones web. Con un

repositorio centralizado de librerías tan famosas como JQuery, Bootstrap, Semantic UI, CKEditor

o Summernote, permite a los desarrolladores mantener en un fichero de configuración qué

librerías quieren utilizar y la versión específica en que se quieren mantener. Junto a Grunt y uno

de sus módulos oficiales, se pueden preparar tareas para descargar y mover las dependencias

de cliente a un directorio específico para satisfacer unas necesidades específicas.

4.4 Bootstrap

Librería desarrollada en el seno de Twitter para dar una mayor consistencia a las herramientas

internas de las que hacían uso en el año 2011, se ha expandido rápidamente entre los frontales

de las aplicaciones web actuales por la facilidad que ofrece a la hora de implementar interfaces

gráficas aptas para todo tipo de dispositivos y por la cantidad de componentes reutilizables que

ofrece como modales, barras de progreso, pestañas o carruseles. Actualmente Bootstrap

permite desplegar aplicaciones web con una buena experiencia de usuario en muy poco tiempo

y, además, es bastante personalizable en lo que se refiere a añadir componentes de terceros o

modificar los esquemas de color preestablecidos.

4.5 Mocha y Chai

Cualquier ciclo de vida de desarrollo de software debe contemplar que las pruebas sean una

actividad más dentro de él. Por ello se implementaron estas dos librerías de JavaScript dedicadas

Page 11: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

7

a las pruebas de librerías y aplicaciones escritas con el mismo lenguaje de programación, ambas

instalables por medio del gestor de paquetes npm. Mocha aporta las funciones y el entorno

necesario para llevar a cabo la ejecución de las pruebas y las operaciones necesarias antes o

después de la ejecución de las mismas. Dentro de los casos de prueba, es recomendable hacer

uso de librerías de aserción para comprobar que las respuestas o valores obtenidos en las

pruebas coinciden con lo esperado. Es a esa tarea a la que se encomienda Chai, una librería

específica de aserciones escrita y dedicada a JavaScript.

4.6 SCRUM

Es una metodología ágil de desarrollo iterativa e incremental que incluye una serie de estrategias

en las que el equipo de desarrollo trabaja como una unidad auto-organizada y el líder como un

mero facilitador para que el equipo cumpla sus objetivos para con el dueño del producto o

cliente. Como toda metodología ágil, se basa en los principios y valores descritos por el

manifiesto ágil escrito en febrero de 2001.

4.6.1 Manifiesto ágil (principios y valores)

Con el objetivo de buscar nuevos métodos para minimizar las probabilidades de fracaso de los

proyectos relacionados con el software, se acordaron una serie de principios y valores entre

desarrolladores experimentados.

Entre los valores se encuentran:

Individuos e interacciones sobre procesos y herramientas

Software funcionando sobre extensa documentación.

Colaboración con el cliente sobre negociaciones contractuales.

Ser flexibles y responder ante los cambios sobre el seguir planes preestablecidos.

Para la consecución de los valores mencionados, se han de seguir doce principios entre los que

destacan:

Satisfacer al cliente con la entrega continua de valor.

Flexibilidad ante los cambios en cualquier momento del ciclo de vida del desarrollo.

Cliente y desarrolladores han de trabajar juntos en el éxito del proyecto.

Equipos motivados, multidisciplinares y auto-organizados, donde el líder es un

facilitador.

Agilidad a través de la excelencia técnica de los miembros, lo que se traduce en un ritmo

constante de entregas.

En intervalos de tiempo regulares, el equipo debe hacer introspección y determinar en

qué pueden mejorar, que han hecho mal o en qué han acertado.

La mejor medida de progreso es tener software funcionando y al cliente satisfecho.

4.6.2 Inception Deck

Una buena forma de arrancar los proyectos ágiles es a través del Inception Deck donde desde

un día a una semana de plazo, cliente y equipo de desarrollo deben alcanzar consensos, se

conceptualiza el producto, se obtiene la arquitectura a alto nivel del proyecto, los riesgos ante

los que se expone y una hoja de ruta a seguir que acabará derivando en las historias de usuario

y tareas a desarrollar por el equipo.

Page 12: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

8

De esta hoja de ruta también se pueden estimar unos costes, pero como las metodologías ágiles

exigen flexibilidad, es una vista panorámica inicial que puede variar con el paso de las semanas.

Para obtener todos estos productos, el Inception Deck se compone de diez pasos que han de

seguirse en orden y requieren de la participación activa de los presentes.

4.6.3 Iteraciones

Cada iteración en el proyecto se corresponde con la entrega de valor al cliente con respecto a lo

acordado en el inicio. Antes de comenzar a trabajar en la implementación de las tareas e

historias de usuario, el equipo debe debatir con el cliente que características y funcionalidades

son las que se deben realizar y con qué prioridad. Tras lo cual, el equipo estima el esfuerzo que

llevaría implementar lo solicitado por el cliente.

Durante el plazo que acuerdan cliente y equipo deben apoyarse para llevar a buen término las

tareas porque, una vez concluido dicho plazo para la entrega de valor, el cliente debe revisar

que todo satisface sus criterios de aceptación. Fruto de esta revisión pueden surgir nuevas

características o necesidades que se tendrán en cuenta para el inicio de la siguiente iteración.

Además, el equipo deberá hacer una retrospectiva de la iteración para revisar qué puede

mejorar en su forma de trabajar, qué es lo que se hace bien o, por el contrario, qué es lo que se

hace mal.

En un proyecto ágil se sucederán tantas iteraciones como sean necesarias hasta que el cliente

dé por finalizado el proyecto. Es importante aclarar al cliente que, normalmente, durante las

iteraciones la funcionalidad que se ha decidido implementar se congela para no paralizar el

proceso, por lo que si hubiera algún cambio, éste se encolaría hasta la siguiente iteración.

Page 13: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

9

5 Concepción del sistema

A lo largo de esta sección se van a identificar, en una fase inicial, las características clave del

sistema, la hoja de ruta que se va a seguir para llevar a cabo su desarrollo, la arquitectura a alto

nivel, los riesgos ante los que se encuentra el proyecto y los recursos que son necesarios para

llevarlo a cabo. Todo ello saldrá como resultado de aplicar el proceso conocido como Inception

Deck explicado en la anterior sección.

5.1 ¿Por qué estamos aquí? (Why are we here?)

Documentar la fase de pre-producción de un proyecto audiovisual cimienta el desarrollo de la

obra de cara a las siguientes actividades de producción y postproducción. Durante dicha etapa

se generarán los documentos que harán las veces de directrices para la grabación y/o animación

de la obra en desarrollo.

Los documentos más importantes creados durante la actividad de pre-producción son: la biblia

literaria, el guion literario y el técnico. Estos documentos contienen la información general

relativa a la obra audiovisual, descripción de la sinopsis, personajes, escenarios, escenas y tomas

a tener en cuenta por los técnicos que forman parte del desarrollo. La mayoría de ellos no siguen

un formato específico, pero los guiones sí que se adecúan a una estructura común que han de

respetar y que puede llegar a ser tediosa de mantener manualmente.

La razón por la que se construye este sistema es para, precisamente, facilitar la gestión

documental de la fase que se ha descrito previamente. Dicha gestión involucra a varios roles

creativos interesados en que esta tarea sea lo más simple y colaborativa posible, es por eso que

una aplicación web se ajusta a sus necesidades por motivos de ubicación y accesibilidad.

El cliente y destinatario final del sistema es una animadora digital que se encarga de realizar

todas las fases de producción en pequeños y medianos proyectos audiovisuales. Sin embargo,

su uso se puede propagar entre todos los participantes involucrados con el proceso creativo de

un proyecto audiovisual tal y como hemos dicho antes.

En su día a día, el cliente se ha percatado de la necesidad de automatizar la gestión de unos

documentos que tienden a la entropía cuando empiezan a crecer en número y contenido, pero

no sólo eso, lo más importante para él es ser capaz de añadir cierta lógica común entre todos

los que pertenecen al mismo proyecto para así poder facilitar su edición en la labor creativa, es

decir, ser capaz de referenciar en los guiones y de forma cruzada: escenarios, escenas,

personajes y descripciones sin duplicar información y sin apenas esfuerzo.

5.2 Breve descripción del proyecto (Elevator pitch)

El sistema web para la gestión de documentos de pre-producción audiovisual es un producto

concebido para creativos audiovisuales que tienen la necesidad de automatizar el tratamiento

de los documentos, realizar referencias cruzadas y trabajar de forma colaborativa. Para ello este

nuevo sistema, aunque ya existen algunas soluciones en la actualidad, busca aunar lo positivo

existente en el mercado con una solución web, que por definición es multiplataforma, capaz de

permitir a varios creativos trabajar en los mismos proyectos.

Page 14: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

10

5.3 Marketing del producto (Product box)

En un esfuerzo para entender qué es lo que consigue el cliente con este sistema web en

concepción y qué aporta éste a unos potenciales clientes, en esta fase se enfatizan las

características positivas para no perder de vista la visión del producto. Sin visión es difícil hacer

algo alineado con las necesidades del cliente, es por esto que se diseña una portada con los

elementos más característicos de la solución y se le asigna un eslogan.

Ilustración 1. Product box fruto de la fase de marketing del Inception Deck.

Page 15: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

11

Tras las diversas reuniones mantenidas con el cliente, nuestro desarrollo cuenta con una serie

de características beneficiosas para el estado de las cosas en este tipo de sistemas de gestión de

documentos de pre-producción audiovisual. Entre las más destacadas se enumeran las

siguientes:

Una gestión de proyectos colaborativa por medio de una aplicación web.

Automatizaciones y plantillas para llevar a cabo el desarrollo de los guiones de una

forma más sencilla, completa e intuitiva.

Gestión completa de elementos pertenecientes a biblias literarias que pueden

referenciarse de forma cruzada en la escritura de guiones.

Tener claras las principales características del desarrollo y mantenerlas presentes en el día a día,

facilitará que el proyecto sea del gusto del cliente al estar bien priorizado. El resto de

características pueden mutar, pero la visión base del proyecto queda perfectamente fijada y

debe cuidarse con esmero.

5.4 Alcance del desarrollo (Not list)

Tras definir las características clave del producto, es conveniente listar qué está dentro de

nuestro alcance en el desarrollo y qué no desde un primer momento. Cualquier modificación en

el futuro será bienvenida siguiendo la filosofía del Manifiesto Ágil, pero se debe dejar constancia

de que en un primer momento se desechó o ni se pensó en dicha posibilidad.

Es por esto que en una primera fase se ha decidido tener en cuenta el siguiente listado de

características dentro del alcance del desarrollo:

Gestión de proyectos (englobando biblia literaria y guiones)

Automatización de referencias cruzadas

Diccionario (acepciones y sinónimos)

Buscador (palabras, personajes, escenas y escenarios)

Gestión de roles y usuarios (modo colaborativo)

Gestión de plantillas de guion

Funciones sociales (ranking de proyectos públicos)

Se descarta en esta primera fase que el sistema sea:

Un procesador de textos tan completo como Microsoft Word

Una red social de creativos audiovisuales con mensajería interna o perfiles

Un soporte para escritores de novelas y otros tipos de narrativa, solo va destinado a

guiones y biblias que sustentan su contenido

No se exportarán animatics, es decir, aunque el guion técnico pueda contener imágenes

en las escenas, no se generará un vídeo secuencial con dichas imágenes

Pese a ser colaborativo, no habrá edición simultánea, el segundo escritor lo verá en

modo lectura.

No tendrá ningún tipo de mensajería instantánea o de buzón entre usuarios por el

momento.

Page 16: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

12

5.5 Stakeholders de apoyo (Meet your neighbours)

El sistema web para la gestión de documentos de pre-producción no es un proyecto estanco y

contará con la ayuda de otros stakeholders. Pese a que no afecte en nada a estos stakeholders

nuestro proceso de desarrollo, de alguna forma ellos sí pueden ser cruciales para el mismo. Es

por esto que se hace importante la labor de, aparte de desarrollar, mantener la confianza en

que vamos a poder seguir contando con ellos. Por lo tanto, asumiendo una colaboración activa

entre las dos partes activas del proyecto, cliente y desarrollador, entran más stakeholders al

juego.

Por un lado hay que tener en cuenta la vertiente tecnológica, es decir, las tecnologías en que

vamos a decidir realizar las actividades correspondientes al proceso de desarrollo. Librerías,

gestores de bases de datos, servidores de aplicaciones, sistemas operativos… todos ellos, pese

a que nuestra labor no les afecte, son cruciales a la hora de hacer realidad el sistema web. En

consecuencia, es importante elegir lo que más se adecúe a nuestras necesidades, pero también

asegurarse de que su mantenimiento y evolución sean activos ya que estas herramientas son el

soporte de nuestras ideas.

Seguidamente hay que tener en cuenta que, por norma general, un sistema web requerirá de

máquinas conectadas a Internet y disponibles 24/7. Que la futura solución esté disponible para

el uso dependerá de una infraestructura física que, hoy día, se ofrece a través de proveedores

de alojamiento de aplicaciones. Resultará crucial contratar uno en función de nuestras

necesidades tecnológicas y condiciones de tiempo en línea, condiciones que se pueden fijar en

un SLA (Service Level Agreement o Acuerdo de Nivel de Servicio).

Desde un punto de vista legal, puede ser también interesante contar con el consejo por parte

de algún bufete de servicios legales. Hoy día la legislación en torno a los derechos de autor,

servicios de la sociedad de la información y la protección de datos personales están a la orden

del día. Es por esto que, en vistas a cumplir con la legalidad es interesante un apoyo legal de

consultoría.

Una vez iniciado el desarrollo del proyecto será importante tener en cuenta a todos stakeholders

de apoyo. Con mayor o menor importancia sobre este proceso, todos ellos son necesarios para

que la solución tenga éxito.

Page 17: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

13

5.6 Arquitectura a alto nivel de la solución (Show your solution)

Ilustración 2. Representación de los casos de uso más importantes para el sistema web en concepción

Como se puede observar en la ilustración, los usuarios de la aplicación podrán acceder a la

misma por medio de su navegador web preferido ya instalado en sus tabletas, ordenadores

personales o móviles. La aplicación estará alojada en un servidor que hará las veces de servidor

de aplicaciones web Node.js con el marco de trabajo ExpressJS, además de contar con el gestor

de bases de datos MongoDB para mantener persistencia sobre la información tratada en la

aplicación.

Al acceder al frontal los usuarios tendrán la posibilidad de realizar dos tareas: la de gestionar

proyectos previa autenticación o la de registrarse en el caso de que no lo hayan hecho. Si han

decidido gestionar proyectos, tras identificarse tendrán la posibilidad de crearlos de cero,

modificar los existentes o cancelar los descartados.

Creando un nuevo proyecto o modificándolo los creativos serán capaces, en todo momento, de

actualizar los detalles del proyecto, los usuarios que forman parte de él y, por supuesto, realizar

modificaciones sobre la biblia literaria y guiones literario o técnico asociados al proyecto actual

sobre el que se está trabajando. Nótese que se están omitiendo características más específicas

como el buscador dentro de la edición de guiones o el generador de plantillas, todo esto con el

objetivo de dar una visión a alto nivel de la interacción entre usuarios potenciales y el sistema

web de gestión documental.

Page 18: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

14

5.7 ¿Qué nos mantiene despiertos por la noche? (What keep us up at night?)

Llegados a este punto recordamos la definición purista de riesgo como “contingencia o

proximidad de un daño”, es decir, es un evento que puede darse o no durante el desarrollo del

proyecto. Todo proyecto se enfrenta a una serie de riesgos que pueden llevarlo a su cancelación,

interrupción o retraso. Existen muchas formas de identificar estos riesgos, pero en este punto

nos hemos querido centrar en los requisitos clave para evaluar lo más prioritario e importante

del proyecto. Esto se hace así con el fin de salvaguardar que todo en lo que se empiece a trabajar,

incluida esta etapa de concepción del producto, esté alineado con las necesidades del cliente

final y que tenemos todo lo necesario para hacer que sea posible contando con los recursos, el

conocimiento necesario y la retroalimentación continua de quien va a usar la aplicación.

En primera instancia se proceden a identificar los riesgos que pueden surgir en la relación de

cliente con desarrollador:

Falta de entendimiento cliente – desarrollador: es un riesgo muy común dentro del

desarrollo de proyectos. Su impacto varía en torno a la gravedad del asunto o

característica en que se requiere la coordinación de las dos partes. Es importante una

comunicación fluida y en la que se detalle exactamente qué es lo que espera una parte

y cómo la otra la va a desarrollar para evitar esto.

Falta de disponibilidad de cliente o desarrollador: ligado al anterior punto es

imprescindible que las dos partes estén involucradas activamente. Si esto no se cumple

es probable que el proyecto se interrumpa.

El cliente incumple los tiempos de congelación o trabajo: puede que, debido a que el

cliente no tenía demasiado claro lo que quería, durante el tiempo en que se está

desarrollando una característica solicitada por él, decida interrumpir su desarrollo para

añadir o eliminar funcionalidades. Es importante tener, para ello, reuniones de calidad

y blindar los tiempos de congelación para que el proyecto llegue a buen término.

Otros riesgos vienen asociados al propio producto que se está desarrollando, generalmente

porque la perspectiva desde la que se está construyendo es incorrecta:

Interfaz poco amigable o accesible: dado que una de las ventajas de este desarrollo

radica en el concepto de una interfaz amigable e intuitiva, el hecho de que se descuide

es un riesgo para una meta importante del proyecto.

Lentitud a la hora de gestionar la escritura de guiones: el foco de este sistema se centra

en la escritura de guiones sencilla y cómoda, que el servidor se demore en exceso en

dar respuestas a las peticiones desde cliente no debe ser asumible.

Inconsistencia o pérdida de los datos dentro del proyecto: si los usuarios depositan su

confianza en el sistema para almacenar sus documentos de pre-producción, debe

asegurarse que esta información se guarda de forma precisa y consistente, pero

también que se pueda recuperar en cualquier momento.

Fallos de privacidad: que el sistema de autenticación sea vulnerable o que se puedan

acceder a proyectos creados como privados dentro del sistema sería fatal para el

proyecto. Las ideas privadas de un grupo de proyecto quedarían expuestas y también la

credibilidad del sistema.

Page 19: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

15

Hay otro tipo de limitaciones externas que poco tienen que ver en la forma en que trabajamos

tanto cliente como desarrollador:

Fallos de conexión demasiado continuos o imposibilidad de acceder al servicio: la

escritura de un guion, como cualquier texto sea del género que sea, conlleva tiempo y

esfuerzo, por lo que es importante asegurarse de que la plataforma sobre la que se

trabaja es estable y escalable en función de la demanda.

Violación de los derechos de autor: usuarios malintencionados podrían subir a la

plataforma guiones o biblias literarias con derechos de autor por los que no se ha

pagado. Desde el punto de vista legal habría que cubrirse de este punto.

Limitaciones tecnológicas durante el desarrollo: supone un riesgo más. El hecho de que

la tecnología que se ha decidido utilizar para el desarrollo no sea suficiente o no permita

realizarlo, repercute directamente sobre los tiempos y la existencia del proyecto en

casos graves.

Pese a la buena acogida del cliente, no es extrapolable a clientes similares: no es un

riesgo grave ni directo para el proyecto, pero sí para unas posibles pretensiones de

reutilizar todo lo realizado en otros clientes con proyectos similares.

5.8 Estimación y priorización de características (Size it up)

Para conocer la cantidad de valor que se va a aportar al cliente con el cumplimiento de

determinadas metas y objetivos, además de saber el esfuerzo que va a llevar su cumplimiento,

es necesario valorarlas y estimar el esfuerzo que conllevaría alcanzarlas en su posterior

desarrollo.

Una vez conocidos el tiempo y el esfuerzo del proyecto previo a su desarrollo podremos conocer

de antemano si la fecha de vencimiento que se nos ha impuesto es posible, si tenemos los

recursos suficientes para hacer frente al desarrollo y, a su vez, seremos conscientes de los

diferentes incrementos que, a lo largo del proyecto, van a ir dándole forma.

Durante las reuniones que se han mantenido con el cliente se han acordado desarrollar los

siguientes objetivos a lo largo de las entregas de valor:

Proyectos de pre-producción: que incluye todo lo relacionado con la gestión de

proyectos y sus documentos.

Usuarios y roles: la creación de usuarios, su administración, identificación y asociación

a roles van ligados a este objetivo.

Características sociales: aquellos que no quieran que su guion se quede en lo privado,

podrán publicarlo y someterse a la votación de usuarios del sistema.

Automatización en la escritura: las tareas repetitivas que pueden ser automatizadas en

la escritura del guion pueden ser automatizadas para ahorrar tiempo y mantener

referencias cruzadas y actualizadas contra los datos de la biblia literaria.

Personalización: hay formatos de guion estandarizado, pero se desea permitir que los

usuarios sean dueños del aspecto de lo que escriben.

Ayuda: tanto escribiendo, ya sea por medio de buscadores como de diccionarios, como

en el uso común de la aplicación, es interesante ofrecer ayuda a los usuarios para

ahorrarles tiempo y favorecer la usabilidad del producto.

Page 20: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

16

Cada objetivo se corresponde con un área de negocio sobre la que, poniendo la lupa,

encontramos áreas funcionales o servicios del sistema a desarrollar:

Proyectos de pre-producción

o F1: Gestión de proyectos

o F2: Gestión de guiones

o F3: Gestión de biblia literaria

Usuarios y roles

o F4: Gestión de usuarios

o F5: Gestión de roles

Características sociales

o F6: Ranking

o F7: Buscador

Automatización en la escritura

o F8: Gestión de encabezados

Personalización

o F9: Gestión de plantillas

Ayuda

o F10: En escritura

o F11: Fuera de escritura

Listadas las características asociadas a cada objetivo es el momento de estimar el valor que cada

una de ellas representa para el proyecto y, a su vez, el esfuerzo que a priori debería llevar su

construcción. Para ello se han establecido un total de 2000 unidades de valor (UV) a repartir

entre todas las características además de establecer la unidad de referencia de esfuerzo (puntos

de esfuerzo: PE). Dicha unidad de referencia se ha establecido como medida comparativa al

esfuerzo que llevaría crear una entidad en base de datos y un formulario a través del cual validar,

insertar, eliminar y modificar los registros que se manejen de dicha entidad (6 horas).

Las estimaciones son acercamientos a la realidad, por lo que, aunque a continuación se

desglosen por características, durante el desarrollo es probable que haya variaciones. Tras las

reuniones mantenidas con el cliente se ha repartido el valor de negocio y el esfuerzo de la

siguiente manera:

Característica Unidades de valor (UV) Puntos de esfuerzo (PE)

Gestión de guiones 400 13

Gestión de proyectos 350 10

Gestión de biblia literaria 300 8

Gestión de encabezados 250 6

Ayuda en escritura 225 4

Gestión de usuarios 175 3

Gestión de roles 125 2

Gestión de plantillas 100 3

Ayuda fuera de escritura 35 1

Buscador de proyectos (social)

25 2

Ranking (social) 15 2

Page 21: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

17

Al aportar unidades de valor en las características se puede ver cómo ellas mismas han quedado

priorizadas en base a las necesidades y requisitos del cliente. Es por eso que en las primeras

iteraciones hay que centrarse sobre las características que aportan un mayor valor de negocio

para dar sensación de progreso a las dos partes, lo crítico primero. En este caso se ha dado

prioridad sobre la gestión de proyectos y guiones, así como la ayuda en escritura, quedando las

características sociales en un segundo plano y, probablemente, postergadas a las últimas

iteraciones.

Poniendo el foco en el esfuerzo nos podemos hacer una idea del tiempo que nos puede llevar la

construcción de cada una de las características. En cada iteración, generalmente de 15 días,

tenemos una cantidad fija de puntos de esfuerzo a repartir entre las diferentes características,

sin embargo, primero es necesario aclarar en detalle cómo se va a llevar a cabo la construcción

a través de las historias de usuario.

Estimada la prioridad y el esfuerzo, es importante ahora agrupar las características de tal forma

que quede un mapa de hitos o lanzamientos a través de los cuales se darán como cumplidos

objetivos del proyecto. La forma en que se van a agrupar las características en hitos es por medio

de la arquitectura de alto nivel que se ha establecido previamente, es decir, se va a tener en

cuenta la manera en que hemos determinado que los usuarios van a interactuar con el sistema.

Nos apoyaremos en un tablero 2D para esta tarea, representando en el eje Y la prioridad y en el

eje X el tiempo.

Ilustración 3. Distribución de las características sobre el tablero en función del momento en que se utilizan y su prioridad.

Al desplegar todas las características sobre el tablero queda claro también qué es lo más

prioritario a la hora de desarrollar el sistema. Tiene sentido que el desarrollo comience de

izquierda a derecha y bajando en prioridad, por lo que, agrupando en función del esfuerzo que

se desea realizar en cada hito, tenemos un mapa de lanzamientos asequible a lo largo de las

iteraciones necesarias durante el desarrollo.

Page 22: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

18

Ilustración 4. Mapa de lanzamientos separados por colores, azul para el primero, rojo para el segundo y verde para el último de todos.

Entrega Unidades de valor (UV) Puntos de esfuerzo (PE)

1 1050 31

2 650 13

3 300 10

El primer lanzamiento agrupa la gestión de proyectos, guiones y la biblia literaria. Es el primer

hito y tiene sentido que represente el que más valor y esfuerzo generan por la filosofía por la

que hemos optado: abordar lo crítico primero. En total se juntan unas 1050 unidades de valor

de 2000 posibles y unos 31 puntos de esfuerzo que aproximadamente llevará algo más de 1 mes

de trabajo.

Dicha estimación se calcula a través del producto de los puntos de esfuerzo por la medida

comparativa en horas que hemos descrito previamente, en resumen, 31 (PE) x 6 (h/PE) = 186

horas. Una vez calculada la cantidad de horas, tomando como referencia que un desarrollador

a tiempo completo invierte unas 160 horas de trabajo al mes, usando dicha cantidad como

divisor sobre la obtenida previamente tenemos que: 186 (h) / 160 (h/mes) = 1.125 meses.

Por otro lado, el siguiente hito viene a completar la gestión de lo anterior con la ayuda en forma

de automatizaciones, gestión de encabezados para escenas además de la autenticación por

medio del registro y administración de usuarios. En esta entrega se acumulan unos 13 puntos

de esfuerzo, bastante menos que en la primera y por eso finalizará antes si la velocidad de

desarrollo es la misma. Referente al valor entregado se acumulan unas 650 unidades, 400 menos

que en el primer lanzamiento.

Page 23: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

19

Al final, la tercera entrega viene con características sociales y de personalización menos

prioritarias que las expuestas en los dos primeros hitos, es por ello que es la entrega que menos

valor acumula con 310 unidades y 10 puntos de esfuerzo. Con este último lanzamiento se

completan todas las características que en un principio se han acordado desarrollar con el

cliente a lo largo de las semanas que dure la construcción planificada sobre estas líneas. La

entrega de las últimas características viene a incrementar el valor de negocio que, desde un

primer momento, la solución ha aportado al cliente enfocándose en lo más prioritario para él.

Teniendo en cuenta el esfuerzo invertido en estas dos últimas se acumulan 23 puntos, o lo que

es lo mismo, 138 horas. Si tomamos de nuevo las 160 horas al mes como divisor de las horas

estimadas en esfuerzo, obtenemos que en estas dos entregas invertiremos 0.8625 meses. Por

lo tanto, en una estimación inicial, desarrollar este sistema llevaría aproximadamente 2 meses

para un desarrollador a tiempo completo dedicado a ello.

Ilustración 5. Gráfico de la planificación temporal del desarrollo tras una estimación inicial.

Ésta sería la estimación teórica para un desarrollador a tiempo completo que invirtiera 8 horas

al día de forma productiva. Lo normal es que esta estimación teórica se dilate en el tiempo al

pasarlo a la práctica y más teniendo en cuenta que este proyecto está siendo desarrollado por

un alumno de último curso de grado. Dependiendo de la implicación en horas por cada iteración,

es muy posible que la estimación se incremente en número de meses retrasándose así los

tiempos de entrega. Sin embargo, al final, con mayor o menor velocidad en las entregas de valor

al cliente, el esfuerzo total en horas seguirá siendo el mismo.

Page 24: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

20

5.9 ¿Qué vamos a ofrecer? (Requisitos no funcionales / What’s going to give?)

Hay un conjunto de demandas por parte del cliente que no se puede incluir en ninguna

funcionalidad descrita hasta ahora, pero sí trasladarse a la manera en que se enfoca la creación

o la integración de las mismas. En la Ingeniería del Software este tipo de requisitos se conocen

como los requisitos no funcionales.

Para darle mayor o menor importancia a determinados requisitos no funcionales, hemos

elaborado la siguiente tabla repartiendo 100 puntos. Idóneamente se desearía el 100 en todos

los aspectos, pero es importante enfatizar aquellos que son más relevantes para este proyecto.

Requisitos no funcionales Puntos

Tiempo (time to market) 5

Portabilidad 12

Rendimiento 15

Usabilidad 20

Accesibilidad 35

Seguridad 13

Podemos ver en la tabla que se le ha dado gran importancia a la accesibilidad del sistema,

atributo de calidad que se puede juntar con el de portabilidad y el de usabilidad dándonos una

de las primeras decisiones con respecto a cómo se va a plantear el desarrollo: podría ser un

sistema web con diseño adaptativo. Sin embargo, tampoco nos podemos relajar con esta

decisión ya que es necesario tener en cuenta aspectos como el de rendimiento y de seguridad

en la parte dinámica del sistema que se está planteando. Lo positivo es que parece que no va a

ser un desarrollo en el cual el tiempo sea una prioridad pese a que hay puntos asignados a dicho

requisito, premiando así calidad sobre tiempo, pero sin dar carta blanca a la relajación.

Page 25: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

21

5.10 ¿Qué vamos a necesitar? (What’s going to take?)

Llevar a cabo la tarea de desarrollar el sistema web para la gestión de documentos de pre-

producción audiovisual requiere de la activa colaboración entre el desarrollador y cliente. En

este proyecto coincidirá, dentro de SCRUM, la figura de Scrum Master con la de Scrum Team

debido a que no es de gran envergadura y sólo requiere de una persona. Además, la persona

encargada de mantenerse en contacto con el desarrollador será el Product Owner, o dueño de

producto, que vendrá representado por el cliente que ha propuesto el desarrollo.

La construcción se estima que aproximadamente durará unos 2 meses desde la fecha de la

concepción del producto hasta su puesta en producción. Este cálculo, realizado previamente, se

extrae de los puntos de esfuerzo que se han estimado en total aglutinando todas las

características: unos 54 puntos. Dependiendo de la velocidad del desarrollador, medida en

puntos de esfuerzo por semana, el desarrollo se alargará o acortará en el tiempo.

Durante la construcción del sistema se harán reuniones periódicamente para entregar valor al

cliente y asegurarse de que se cumplen las expectativas de las historias de usuario que están por

elaborarse. También se valorará si las prioridades siguen estando claras, además de si se están

cumpliendo tanto los plazos como atributos de calidad.

Teniendo esto en cuenta, una idea aproximada de la inversión inicial para arrancar este proyecto

es la expresada en la siguiente tabla.

Concepto Precio

Desarrollador FT / mes 2.000 €

Entorno de desarrollo 1.000 €

Creativo Interfaz Usuario / mes 2.000 €

Gastos corrientes / mes 150 €

Total de la inversión inicial 5.150 €

Es decir, que si trasladamos los gastos mensuales al gasto total que estimamos nos llevaría el

proyecto en su conjunto, tras 2 meses de desarrollo la panorámica del presupuesto sería la

siguiente:

Concepto Precio

Desarrollador FT / mes 2.000 € x 2

Entorno de desarrollo 1.000 €

Creativo Interfaz Usuario / mes 2.000 € x 2

Gastos corrientes / mes 150 €

Total tras los 2 meses de estimación 9.150 €

Page 26: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

22

6 Iteraciones

La hoja de ruta para llevar a cabo el desarrollo del gestor de documentos de pre-producción

audiovisual se estima en 54 PE. Asumiendo que, dependiendo de la implicación y la velocidad

del desarrollador, en cada iteración se puede hacer frente a una cantidad de esfuerzo que ronda

los 13 puntos, el desarrollo se podría hacer realidad en 4 iteraciones.

Hasta ahora, durante la concepción del producto se ha aportado un punto de vista de alto nivel

vinculado a los objetivos, características, hoja de ruta y arquitectura de la solución, pero a lo

largo de esta sección cambia el enfoque a particularidades de más bajo nivel. Para ello se

desglosarán las funcionalidades por medio de las historias de usuario, se priorizarán y se

valorarán de acuerdo a lo estimado previamente para mantener la coherencia. Así mismo, cada

historia de usuario vendrá acompañada del criterio de aceptación que permita validar, una vez

realizada, que se ha creado de acuerdo a las exigencias del cliente.

Desarrollador y cliente se han de reunir para la confección de las historias de usuario antes de

cada iteración. Tanto las funcionalidades, los criterios de aceptación, el valor y la prioridad

deben surgir por consenso. Desde el punto de vista técnico, los Story Points (SP) o el esfuerzo

que se invierte en las historias de usuario, será estimado por el desarrollador a partir de una

unidad de referencia y experiencias pasadas. En este caso dicha unidad de referencia se

establecerá como PE/2, es decir, en cada SP se consumirán 3 horas.

En cuanto a la estructura de cada una de las iteraciones será siempre la misma durante esta

sección. Primeramente, se detallarán las historias de usuario que se han acordado construir y,

además, se mostrarán en el orden de prioridad negociado entre el desarrollador y el cliente. A

continuación, se hará un desglose del día a día de la iteración en relación al progreso de las

historias de usuario. Dicho progreso se reflejará en el análisis del valor entregado y el esfuerzo

para llevarlo a cabo, pero también será evaluado por el cliente en una revisión, de la cual habrá

que hacer retrospectiva para mejorar de cara a las siguientes iteraciones.

6.1 Primer sprint

Para la primera iteración se ha escogido realizar el trabajo relativo a la gestión de guiones puesto

que representa la característica que más valor y esfuerzo lleva de todas. Teóricamente los

puntos de esfuerzo de esta característica se ajustan a los puntos objetivo por iteración

acordados con el cliente (13 PE / 26 SP). Por este motivo, si todo va bien durante la construcción,

la característica podría quedar completa a la par que la iteración se dé por concluida.

6.1.1 Sprint backlog

En esta sección se desglosan las historias de usuario sobre las que se van a trabajar durante esta

primera iteración. Es importante que, durante su desarrollo, no haya cambios sobre las mismas

salvo que sea para añadir algún tipo de aclaración. Pese a que los cambios son bienvenidos en

las metodologías ágiles, es importante respetar los tiempos y definir bien las historias de usuario

desde el principio con el fin de que se puedan llevar a cabo sin mayores problemas durante la

iteración. Cualquier modificación sustancial se puede negociar tras finalizar la iteración que está

en curso.

Page 27: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

23

Historia de usuario: Crear un guion

Funcionalidad Como usuario registrado Quiero poder crear un guion y asociarlo a un proyecto activo De forma que, tras la creación, se puedan crear escenas y asociarlas al nuevo guion

Criterio de aceptación Dada la pantalla inicial del proyecto Cuando se pulse el botón de crear un nuevo guion Entonces se creará un guion nuevo y se redirigirá a la pantalla de edición de dicho guion

Conversación Al acceder a cualquiera de los proyectos propios o compartidos, en la pantalla inicial de detalle del proyecto el usuario debe poder crear nuevos guiones. Por tanto, hay que mostrar un botón para generar los guiones asociados a los proyectos. Dicho botón deberá desaparecer una vez creado el guion. Se ha de tener en cuenta que la creación del guion técnico no puede preceder en ningún caso al literario.

Esfuerzo: 3 SP Valor: 55 UV Prioridad: Alta

Historia de usuario: Insertar escena a guion literario

Funcionalidad Como usuario registrado Quiero insertar una escena al guion literario activo De forma que se pueda trabajar en una nueva escena del guion literario activo

Criterio de aceptación Dada la pantalla de edición del guion literario Cuando se pulse el botón de insertar escena al guion Entonces se agregará la escena al guion literario y redirigirá a la vista de detalle de la misma

Conversación Un guion se compone de escenas. Es necesario que, para que cobre vida, se pueda agregar una escena en la pantalla de gestión de escenas del guion. Debe ser a través de un botón bien visible en la parte de arriba de la pantalla.

Esfuerzo: 3 SP Valor: 60 UV Prioridad: Alta

Page 28: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

24

Historia de usuario: Mostrar escenas de guion

Funcionalidad Como usuario registrado Quiero poder visualizar las escenas que componen un guion De forma que se pueda observar qué escenas pertenecen al guion en edición

Criterio de aceptación Dada la pantalla de edición del guion literario Cuando se acceda a dicha pantalla Entonces se mostrará el listado de escenas que forma parte del guion literario en edición

Conversación Con el objetivo de ver todas las escenas que componen un guion y poder administrarlas, nada más acceder a la pantalla de edición del guion se mostrará un listado de las mismas. Se podrá acceder a la vista de detalle de las escenas a través del enlace en cualquiera de ellas.

Esfuerzo: 1 SP Valor: 30 UV Prioridad: Media

Historia de usuario: Cambiar orden de escena

Funcionalidad Como usuario registrado Quiero alterar el orden de una escena asociada a un guion activo De forma que se pueda gestionar el orden de las escenas de forma dinámica

Criterio de aceptación Dada la pantalla de edición del guion literario Cuando se pulse un botón de subir o bajar, o bien se arrastre la escena Entonces se alterará el orden de las escenas cuando se confirme el nuevo orden

Conversación Es posible que se cree una escena a posteriori pero que en orden cronológico deba preceder a las anteriores. Ya sea arrastrando la escena en la pantalla de gestión de escenas o con unos botones de arriba o abajo, el orden de la escena debe ser editable. Deberemos, una vez alterado el orden, confirmar la nueva secuencia con un botón de confirmación que se muestre de forma dinámica.

Esfuerzo: 4 SP Valor: 55 UV Prioridad: Baja

Page 29: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

25

Historia de usuario: Eliminar un guion

Funcionalidad Como usuario registrado Quiero poder eliminar un guion y desasociarlo de un proyecto activo De forma que, tras eliminarlo, se pueda dar por cancelado

Criterio de aceptación Dada la pantalla inicial del proyecto con guiones activos Cuando se pulse el botón de eliminar un guion y se confirme la acción Entonces se eliminará el guion y se reemplazará el botón de eliminar por uno de agregar

Conversación Es interesante dejar la posibilidad de reescribir un proyecto permitiendo a los usuarios tener pleno control de lo que escriben. Por ello, en la pantalla de detalles de proyecto hay que dejar la posibilidad de eliminar un guion respetando la restricción de que si se elimina el literario debe eliminarse el técnico. Será importante pedir una confirmación para realizar esta acción.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Baja

Historia de usuario: Eliminar escena de guion

Funcionalidad Como usuario registrado Quiero eliminar una escena de guion De forma que se puedan borrar escenas que ya no son necesarias o se han descartado

Criterio de aceptación Dada la pantalla de edición del guion literario Cuando se pulse el botón de eliminación de una escena y se confirme la acción Entonces se borrarán todos los registros asociados a dicha escena y desaparecerá de la vista

Conversación Una vez descartada una escena, desde la pantalla de gestión de escenas del guion debe poderse eliminar dicha escena. La mejor forma es poner un botón de eliminación a la misma altura de la escena. Será imprescindible que se confirme la acción con el usuario.

Esfuerzo: 3 SP Valor: 20 UV Prioridad: Baja

Page 30: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

26

Historia de usuario: Insertar información detallada en escena de guion técnico

Funcionalidad Como usuario registrado Quiero insertar información detallada en una escena de guion técnico De forma que se pueda aclarar los requisitos técnicos de la misma

Criterio de aceptación Dada la pantalla de edición de la escena de guion técnico Cuando se accione el botón de insertar información detallada Entonces se incluirá el campo de información detallada listo para cumplimentar

Conversación Al igual que la imagen, si queremos agregar información técnica detallada a la escena del guion técnico, se debe facilitar al usuario la tarea de insertar un campo de información detallada. La inserción será a través de un botón en la pantalla de edición de escena de guion técnico. Solo habrá un campo de este tipo por escena.

Esfuerzo: 3 SP Valor: 30 UV Prioridad: Baja

Historia de usuario: Insertar imagen en escena de guion técnico

Funcionalidad Como usuario registrado Quiero insertar una imagen en una escena de guion técnico De forma que se ilustre cómo debe ser interpretada la propia escena de forma completa

Criterio de aceptación Dada la pantalla de edición de la escena de guion técnico Cuando se accione el botón de añadir una imagen a la escena Entonces se insertará dicha imagen en la escena siendo inmediatamente visible

Conversación El guion técnico puede tener imágenes asociadas a las escenas. Por ello se debe incluir un botón para añadir imágenes a las escenas. Como máximo una por escena. Se permitirá cargar imágenes a través de un diálogo emergente.

Esfuerzo: 3 SP Valor: 45 UV Prioridad: Baja

Page 31: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

27

Historia de usuario: Eliminar imagen en escena de guion técnico

Funcionalidad Como usuario registrado Quiero eliminar una imagen en una escena de guion técnico De forma que se pueda rectificar o cancelar la perspectiva técnica que se le da a una escena

Criterio de aceptación Dada la pantalla de edición de la escena de guion técnico Cuando se accione el botón de eliminar la imagen de la escena y se confirme la acción Entonces se eliminará la imagen de todos los registros

Conversación Al igual que insertarlas, tiene que haber un mecanismo para deshacer la subida y eliminar la imagen tanto de la escena como de los registros de la aplicación. Se requerirá confirmación para llevar a cabo la acción.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Baja

Historia de usuario: Eliminar información detallada en escena de guion técnico

Funcionalidad Como usuario registrado Quiero eliminar información detallada en una escena de guion técnico De forma que se puedan replantear los requisitos técnicos de una escena

Criterio de aceptación Dada la pantalla de edición de la escena de guion técnico Cuando se accione el botón de eliminar información detallada y se confirme la acción Entonces se eliminará el campo de información detallada de la escena

Conversación Si se decide replantear la perspectiva técnica de la escena, es conveniente eliminar el campo de información detallada. Para ello, lo ideal es tener un botón de eliminación a la altura del campo. Se pedirá confirmación al usuario para poder llevar a cabo la acción.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Baja

Page 32: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

28

Historia de usuario: Destacar escena

Funcionalidad Como usuario registrado Quiero poder seleccionar y resaltar las escenas que considere más relevantes De forma que pueda encontrarlas para su relectura y/o edición lo más rápidamente posible

Criterio de aceptación Dada la pantalla de gestión de escenas Cuando se accione el botón de destacar Entonces la escena se destacará de entre el resto de escenas

Conversación No todas las escenas son igual de importantes, por ello, para facilitar la lectura y distinguir entre las escenas críticas de las que no lo son, es deseable que se pueda destacar una escena por medio de la pulsación de un botón. Este botón deberá estar situado al mismo nivel de la escena que se quiera destacar.

Esfuerzo: 1 SP Valor: 25 UV Prioridad: Baja

Historia de usuario: Exportar guion

Funcionalidad Como usuario registrado Quiero poder exportar el trabajo realizado sobre la plataforma De forma que se pueda disponer del guion que se ha escrito fuera de línea

Criterio de aceptación Dada la pantalla de gestión de escenas Cuando se accione el botón de exportar Entonces se descargará una copia con las escenas del guion activo

Conversación Es importante para los creativos disponer de una copia a través de la cual se pueda visualizar todo el trabajo hecho hasta el momento. Para ello se dispondrá de un botón de exportación en el apartado de gestión de escenas. Al accionarlo se iniciará la descarga / previsualización del guion activo.

Esfuerzo: 2 SP Valor: 20 UV Prioridad: Baja

Page 33: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

29

6.1.2 Desarrollo del sprint

Esta primera entrega de funcionalidad es objetivamente la que normalmente más carga de

trabajo lleva entre la preparación del entorno de implementación y el análisis técnico de las

historias de usuario para plasmarlo sobre un modelo que luego pueda plasmarse sobre una base

de datos o colección en la jerga de MongoDB. Una vez preparado lo anterior, durante el proceso

de implementación es posible que haya que instalar librerías en pos de perseguir la reusabilidad

y productividad. El gestor de paquetes npm es un aliado enormemente útil en esta fase, en la

cual se establecen las bases de lo que será la aplicación en sucesivas iteraciones.

Ilustración 6. Instantánea del modelo UML de análisis sobre los conceptos que se manejan en la primera iteración

Abordando la filosofía de lo crítico primero, durante esta primera iteración se acordó con el

dueño del producto en trabajar sobre el concepto de los guiones sobre los que gira la aplicación.

Es por ello que durante esta iteración todo lo relacionado con escenas, es decir, tanto su

creación, modificación y eliminación expresadas en las historias de usuario previas son críticos.

Conceptualmente los guiones serán entidades virtuales, es decir, de puertas para adentro en la

aplicación el guion sólo será una agrupación de escenas de las que luego podremos extraer

detalles técnicos o puramente literarios.

Técnicamente, con el objetivo de desacoplar la capa de almacenamiento de la lógica de negocio

y de la presentación, se opta por realizar primeramente un servicio REST haciendo uso de

ExpressJS. De esta manera, cualquier aplicación cliente podría montar su lógica de negocio y de

presentación comunicándose con ExpressJS a través del API REST construido para la persistencia

de la información manejada en cliente.

Page 34: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

30

Al elegir la pila de desarrollo MEAN, se toma la decisión de hacer uso de Angular2 con TypeScript

en cliente por el atractivo que tiene en su facilidad a la hora de manejar los modelos por medio

de aplicaciones orientadas a componentes. Que esté orientado a componentes le da un plus de

productividad debido a que, bien pensados, los componentes pueden ser reutilizados en

diferentes vistas del cliente. Además, no solo representa ventajas para los desarrolladores, en

el núcleo de Angular2 está JavaScript, lo que facilita que la aplicación sea dinámica y asíncrona,

lo que se traduce en una mejor respuesta de cara al usuario. Para todo lo relacionado con la

gestión de componentes y creación del proyecto con Angular2, resulta especialmente útil hacer

uso del Angular-CLI1 que ponen a disposición de los usuarios.

La aplicación Angular-CLI viene a aumentar la productividad y reducir el riesgo de cometer fallos

durante el desarrollo del cliente con los diversos componentes, rutas y servicios que pudiera

contener. Es más, aporta agilidad en el desarrollo gracias a la inclusión de un servicio de

“desarrollo en vivo” que se mantiene a la espera de cambios en el código fuente para

trasladarlos automáticamente al navegador.

En lo referido a la tarea de desarrollar el proyecto, con un solo “new”, Angular-CLI genera por

nosotros un esqueleto de proyecto con todos los directorios básicos de los que se compone una

aplicación en Angular2 según su documentación, además del módulo principal, el primer

componente a modo de “Hola mundo” e, incluso, polyfills (o recursos para asegurar la

compatibilidad de la aplicación en diversos navegadores). Misma filosofía para los componentes,

rutas y servicios, ya que el fruto de ejecutar un “generate” de Angular-CLI es un nuevo script con

dependencias básicas resueltas y listo para ser implementado.

Ilustración 7. Esquema de la arquitectura de la aplicación haciendo uso de las tecnologías mencionadas previamente.

Como se puede observar en la ilustración, hay 3 bloques diferenciados en la arquitectura de la

aplicación: el gestor de base de datos MongoDB, el API REST en ExpressJS que trabaja

1 Angular2-CLI: https://cli.angular.io/

Page 35: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

31

directamente con MongoDB a través de los modelos Mongoose2 y, finalmente, el cliente web de

GuionMaker que usa la tecnología Angular2.

Entrando en el detalle de lo que sería un caso de uso genérico, un usuario del cliente web haría

una petición sobre una URL en específico. Al realizar dicha petición, el cliente web de Angular2

carga el paquete de módulos que contienen todos los elementos que le dan vida: componentes,

rutas y servicios. Una vez cargados los recursos del cliente, el usuario interaccionará con los

componentes y, fruto de esta interacción, se trasladarán al API REST las operaciones CRUD a

realizar junto a los datos manipulados.

La comunicación API-cliente se realiza por medio del servicio HTTP que Angular2 provee y, por

el lado del API, se hace uso del módulo de rutas de la aplicación de servidor en ExpressJS. La

definición de dichas rutas en el API determina la URL y los métodos HTTP a los que se va a

responder. Por lo tanto, cuando cualquiera de estas rutas es utilizada hay un método que, por

detrás, realizará las tareas pertinentes sobre los modelos Mongoose que, a la postre, se

reflejarán en las colecciones almacenadas en MongoDB. Cuando finaliza este procesamiento, se

recorre el camino inverso con una respuesta HTTP que finalmente se mostrará en el

componente Angular2 sobre el que trabaja el usuario en su navegador.

Teniendo en mente la arquitectura del aplicativo, como resultado de esta primera entrega se

acuerda seguir la siguiente hoja de ruta:

API REST Express JS

o Creación de los modelos y esquemas Mongoose asociados al UML como

middleware entre servicio web y gestor de bases de datos MongoDB.

Escenas

Proyectos

Detalles

Usuarios

Plantillas

Géneros

Clasificaciones

o Implementación de los métodos encargados de gestionar las operaciones CRUD

sobre las colecciones de MongoDB.

o Configuración de la aplicación.

Rutas a los métodos CRUD

Módulos

Servidor web

Angular2

o Inicialización del proyecto e instalación de dependencias por medio del

comando new del angular-cli.

o Creación de los módulos asociados a los conceptos de la primera iteración.

Componentes

Rutas

Plantillas

o Implementación del servicio haciendo uso de http del núcleo de Angular2 para

comunicarse con el servicio REST en ExpressJS en pos de trabajar con los

modelos en cliente a través de formularios.

2 Módulo orientado a simplificar el trabajo con las colecciones de MongoDB

Page 36: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

32

o Inclusión de las librerías adicionales de front-end como Summernote (editor de

textos), Bootstrap y JQuery como dependencia de este último.

6.1.3 Documentación técnica del sprint

Siguiendo estrictamente la hoja de ruta establecida en la anterior sección, lo primero que se

decide implementar es el API REST con ExpressJS y Mongoose. Así pues, antes de entrar en el

desarrollo de la aplicación ExpressJS, aprovechando los modelos explicados en la ilustración 5

se crean los esquemas que posteriormente se asignarán a los modelos de Mongoose. Con esos

modelos ya podremos manipular las colecciones de MongoDB.

Ilustración 8. Explorador de la solución en Visual Studio 2015

La estructura de la solución del proyecto GuionMaker es la mostrada en la ilustración 7. En

primera instancia lo que nos importa son los directorios relacionados con ExpressJS y Mongoose:

models (esquemas Mongoose), routes y el fichero app.ts (aplicación ExpressJS). La carpeta

public es la carpeta en la que se aloja la aplicación Angular2 y el resto de ficheros son necesarios

para los diferentes módulos instalados a través del npm. Entre ellos destacar package.json

(contiene las dependencias de npm), tsconfig.json + tslint.json (determina la configuración para

el compilador de TypeScript), GruntFile.js (fichero de configuración para el gestor de tareas

Grunt), bower.json (especifica las dependencias de front-end a instalar a través de Bower) y

angular-cli.json (configuración relativa a la aplicación Angular-CLI).

Cada clase representada en el UML del apartado anterior representa un esquema de Mongoose

y, por tanto, un documento susceptible de ir a una colección de documentos de ese esquema

en la base de datos MongoDB. Todos los modelos Mongoose creados en la carpeta models

tienen la misma estructura, excepto el contenido, debido a que los atributos varían según la

entidad a representar.

Como denominador común en todos los modelos, hay un primer bloque de importación de otros

modelos o módulos de npm de los que se va a hacer uso. En la ilustración 8 se realiza la

Page 37: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

33

importación del módulo Mongoose además de los modelos a los que luego se referencia desde

el constructor donde se genera el esquema con los atributos de la entidad Escena. Este esquema

será accesible de manera pública desde otros modelos y será el que se encargue de guardar,

leer, modificar y eliminar documentos de la colección en MongoDB a criterio del usuario.

Nótese que en este esquema se implementa una función anónima que se suscribe al evento de

eliminar un documento para deshacerse, en cascada, de los elementos a los que se hace

referencia y es coherente que no permanezcan en la base de datos.

import * as mongoose from "mongoose"; import { DetalleTecnico } from "./DetallesTecnicos"; import { DetalleLiterario } from "./DetallesLiterarios"; import { Proyecto } from "./Proyectos"; export class Escena { schema: mongoose.Schema; static current: Escena = new Escena(); private constructor() { this.schema = new mongoose.Schema({ titulo: String, orden: Number, destacado: Boolean, noche: Boolean, exterior: Boolean, fechaCreacion: { type: Date, default: Date.now() }, detalleTecnico: { type: mongoose.Schema.Types.ObjectId, ref: mongoose.model(DetalleTecnico.name).schema }, detalleLiterario: { type: mongoose.Schema.Types.ObjectId, ref: mongoose.model(DetalleLiterario.name).schema }, proyecto: { type: mongoose.Schema.Types.ObjectId, ref: mongoose.model(Proyecto.name).schema } }); this.schema.pre('remove', function (next) { console.log(this); mongoose.model(DetalleTecnico.name).remove({ _id: this.detalleTecnico }).exec(); mongoose.model(DetalleLiterario.name).remove({ _id: this.detalleLiterario }).exec(); next(); }); mongoose.model(Escena.name, this.schema); } }

Fragmento del modelo creado para el esquema Escena en Mongoose

enum ResponseStatus { OK = 0, KO } class RespuestaJson { estado: ResponseStatus; error: String; consulta: mongoose.Document[]; insertado: mongoose.Document; }

Page 38: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

34

class PeticionJson { find: any; sort: any; } export class APIHelper { private static buildJsonGeneric(estado: ResponseStatus) {[…]} private static buildJsonError(mensaje: String) {[…]} private static buildJsonConsulta(resultado: mongoose.Document[]) {[…]} private static buildJsonInsercion(resultado: mongoose.Document) {[…]} public static getAll(model: mongoose.Model<mongoose.Document>, res: express.Response): void {[…]} public static getById(model: mongoose.Model<mongoose.Document>, id: mongoose.Schema.Types.ObjectId, res: express.Response) : void {[…]} public static add(model: mongoose.Model<mongoose.Document>, req: express.Request, res: express.Response) : void {[…]} public static update(model: mongoose.Model<mongoose.Document>, req: express.Request, res: express.Response): void {[…]} public static delete(model: mongoose.Model<mongoose.Document>, id: mongoose.Schema.Types.ObjectId, res: express.Response) : void {[…]} public static getByFilterAndSort(model: mongoose.Model<mongoose.Document>, reqBody: string, res: express.Response): void {[…]} }

Fichero fuente del APIHelper para gestionar la interacción cliente-servidor-base de datos.

Puesto que sobre las entidades de la base de datos se van a realizar recurrentemente las mismas

operaciones, para facilitar el mantenimiento de la aplicación y tener centralizadas estas

operaciones, en el documento APIHelper se disponen los tipos de datos y las operaciones

necesarias para el trabajo con los esquemas de Mongoose (ilustración 9).

Entre dichas operaciones están las de generar respuestas o tratamiento de resultados que

comienzan con el prefijo “build”. Por otro lado, están las operaciones que se comunican con la

base de datos ya sea para añadir, modificar, eliminar o buscar registros con unos criterios u

otros. Generalmente la dinámica de estos métodos viene determinada por la carga de un

modelo Mongoose concreto que se recibe por parámetro, el cuerpo de la petición HTTP recibida

y un objeto respuesta para devolver la respuesta oportuna al cliente.

Como apoyo de estas operaciones y en lo referente a tipos, cabe destacar la clase

RespuestaJson, que contiene las propiedades del objeto respuesta que se generará por cada

operación, además de PeticionJson, que se utiliza para obtener registros en base a filtros por

propiedades u orden.

Page 39: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

35

Ilustración 9. Listado de clases-ruta generadas para llamar a las operaciones de APIHelper.

La clase APIHelper se utiliza desde las diferentes rutas de la aplicación, rutas que se distribuyen

en clases aparte, de forma análoga a los modelos (ilustración 10). De esta manera, se puede

personalizar la respuesta según la entidad sobre la que se está operando después de la llamada

al método genérico que se encarga de procesar la operación. Igualmente, y si fuera necesario,

podríamos llevar un procesamiento previo a la llamada del método genérico para operar sobre

base de datos.

El aspecto de una clase ruta suele ser siempre el mismo para todas y cada una de las clases-ruta

de la aplicación. Por lo general, y salvando las importaciones de modelos que luego se utilizan

en el código y módulos npm involucrados en la funcionalidad, las clases-ruta disponen de un

método estático get model() con el objetivo de cargar una vez el esquema del modelo con que

se trabaja en esa clase-ruta y, así, no tener que cargarlo desde Mongoose en cada petición. Al

final, este modelo se pasará como argumento a los métodos genéricos del APIHelper junto a los

objetos de petición y respuesta que sean necesarios en cada caso para procesar la operación

lanzada desde cliente.

module Route { export class GeneroRoute { static _model: mongoose.Model<mongoose.Document>; static get model(): mongoose.Model<mongoose.Document> { if (GeneroRoute._model == undefined) { GeneroRoute._model = mongoose.model(Genero.name); } return GeneroRoute._model; } public getGeneros(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getAll(GeneroRoute.model, res); } public addGenero(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.add(GeneroRoute.model, req, res); } public getGeneroById(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getById(GeneroRoute.model, req.params.id, res); } public deleteGenero(req: express.Request, res: express.Response, next: express.NextFunction) {

Page 40: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

36

APIHelper.delete(GeneroRoute.model, req.params.id, res); } } } export = Route;

Aspecto que toma la implementación de las clases-ruta. En este caso es el turno del género de los proyectos.

Teniendo claros los modelos de datos, el APIHelper y las clases-ruta que operan con los modelos

de Mongoose, el siguiente paso es establecer el vínculo entre servidor web y clases-ruta.

ExpressJS viene provisto de una clase Router mediante la que se pueden registrar rutas ante las

que el servidor web reaccionará según el método HTTP que elijamos y el método de las clases-

ruta que asociemos a estos con un bind() de JavaScript3.

private setRoutes(): void { let router: express.Router = express.Router(); let _indexRoute: IndexRoute = new IndexRoute(); let _proyectosRoute: ProyectoRoute = new ProyectoRoute(); […] router.get('/api/proyectos', _proyectosRoute.getProyectos.bind(_proyectosRoute.getProyectos)); router.post('/api/proyectosPorFiltro', _proyectosRoute.getProyectosByFilterAndSort.bind(_proyectosRoute.getProyectosByFilterAndSort)); router.get('/api/proyecto/:id', _proyectosRoute.getProyectoById.bind(_proyectosRoute.getProyectoById)); router.delete('/api/proyecto/:id', _proyectosRoute.deleteProyecto.bind(_proyectosRoute.deleteProyecto)); router.post('/api/proyecto/', _proyectosRoute.addProyecto.bind(_proyectosRoute.addProyecto));[…]

Fragmento del método setRoutes de la aplicación ExpressJS

El código presente en la ilustración 12 es bastante intuitivo en sus intenciones. Tras instanciar el

router de ExpressJS se va haciendo lo propio con las clases-ruta de las que vamos a hacer uso

según lleguen peticiones al servidor web. La ruta de dichas peticiones y los métodos que se

asocian a esas rutas se establecen a posteriori haciendo uso de los métodos HTTP del router. De

esta manera, una vez quedan asociados dirección, método HTTP y método de la clase-ruta,

cuando desde cliente se haga una petición, recibiremos una respuesta con el estado de la misma

y los resultados oportunos.

En la tabla que se facilita a continuación se muestran las diferentes rutas, métodos y parámetros

con que se pueden operar con el API REST, así como sus respuestas a final de la primera

iteración. Para simplificar la tabla, tanto en los parámetros como en las respuestas se hace

referencia al nombre de las clases que dan estructura a los JSON, no a los atributos de dichas

clases:

Método Ruta Parámetros Respuesta

GET /api/escenas - JSON (RespuestaJson)

3 Function.prototype.bind(): https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Function/bind

Page 41: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

37

POST /api/escenasPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/escena/ JSON (EscenaModel) JSON (RespuestaJson)

GET /api/escena/:id String(Id) JSON (RespuestaJson)

DELETE /api/escena/:id String(Id) JSON (RespuestaJson)

GET /api/detallesTecnicos - JSON (RespuestaJson)

POST /api/detalleTecnico/ JSON (Det.Tec.Model) JSON (RespuestaJson)

GET /api/detalleTecnico/:id String(Id) JSON (RespuestaJson)

DELETE /api/detalleTecnico/:id String(Id) JSON (RespuestaJson)

GET /api/detallesLiterarios - JSON (RespuestaJson)

POST /api/detalleLiterario/ JSON (Det.Lit.Model) JSON (RespuestaJson)

GET /api/detalleLiterario/:id String(Id) JSON (RespuestaJson)

DELETE /api/detalleLiterario/:id String(Id) JSON (RespuestaJson)

GET /api/plantillas - JSON (RespuestaJson)

POST /api/plantillasPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/plantilla/ JSON (PlantillaModel) JSON (RespuestaJson)

GET /api/plantilla/:id String(Id) JSON (RespuestaJson)

DELETE /api/plantilla/:id String(Id) JSON (RespuestaJson)

Terminados los esfuerzos en back-end para esta iteración, lo siguiente es preparar el front-end

en Angular2. Para ello, como hemos comentado en la sección anterior de este documento, nos

vamos a apoyar en Angular-CLI para generar un proyecto base que irá evolucionando con nuevos

módulos y declaraciones de componentes, rutas y servicios en función de nuestras necesidades

como desarrolladores.

Anteriormente se ha mencionado que el directorio public sería el

destinado para alojar el código fuente del cliente web, así que el

resultado del comando new de Angular-CLI se ejecuta sobre él. Como

resultado se genera una jerarquía de directorios entre los que destacan

por defecto:

DIST: aloja la compilación de los módulos de TypeScript y los

recursos adicionales que necesite el front-end. Representa

el directorio de producción sobre el que se mueven todos

los ficheros “ejecutables” de la aplicación web tras la

compilación.

E2E: en el caso de implementar pruebas unitarias sobre

cualquier entidad implementada en la solución, es el

directorio por omisión para alojarlas.

Ilustración 10. Estructura del directorio public del cliente web.

SRC: es el directorio sobre el que vamos a pasar más tiempo trabajando puesto que se

encarga de almacenar todos los ficheros fuente de recursos, módulos, componentes,

rutas y servicios de la solución.

o APP: dentro de SRC, da cabida a todos los componentes y módulos de la

aplicación. Como metodología es conveniente separar los módulos y

Page 42: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

38

componentes por entidad, de esta forma quedan agrupados y organizados por

concepto, lo que facilita el desarrollo del cliente.

o ASSETS: representa el directorio destino de las instalaciones llevadas a cabo por

medio de Bower y Grunt. Esto implica que aquí se encuentran las librerías de

front-end necesarias para nuestro cliente, entre ellas Bootstrap, Jquery y

Summernote.

o ENVIRONMENTS: relacionado con Angular-CLI, sirve para determinar si el

comando “build” hace uso del entorno de producción o el de desarrollo para

generar los paquetes de módulos que luego se incluyen en el índice del cliente

web.

o Resto de ficheros: index.html y main.js representan el punto de partida del

cliente web puesto que se ocupan de cargar todos los recursos necesarios de

Angular además de los ficheros fuente que se han ido desarrollando. Por otro

lado, polyfill.js se encargará de que se ejecuten los recursos necesarios en los

navegadores que no soportan determinadas características de Angular2.

Finalmente, test.js contiene la lógica de ejecución relacionada con los tests que

se encuentran en E2E.

La mayor parte del tiempo se invierte en el directorio APP, lo cual no quiere decir que no haya

que realizar modificaciones sobre ficheros de otros directorios. A modo de muestra de lo

descrito en el directorio APP, al final de la primera iteración la jerarquía del mismo quedaría

como sigue:

Ilustración 11. Jerarquía de directorios dentro del directorio APP del cliente web.

Como se puede apreciar, el código fuente del cliente queda organizado según conceptos, lo cual

es una metodología bastante cómoda de cara a una aplicación que vaya creciendo en número

de módulos y componentes. Por cada concepto habrá un directorio que contendrá una carpeta

models que alojará todos los ficheros fuente de las entidades con que se trabaje. Las vistas de

los componentes serán ficheros html que se guardarán en la carpeta templates, dejando al

módulo del concepto, enrutado y componentes en el directorio raíz.

Sin entrar en muchos detalles de implementación de entidades de Angular2, hay que destacar

el directorio utils por los componentes y ficheros de utilidad que contiene. Con el objetivo de

reutilizar el código lo máximo posible, durante el desarrollo de los componentes de la entidad

escena se vio adecuado generar componentes que pudieran ser reutilizados en otros directorios,

tarea para la cual, en Angular2, lo más limpio es crear un módulo.

import { NgModule } from '@angular/core';

Page 43: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

39

import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { ConfirmacionGuardadoComponent } from './confirmacion-guardado.component'; import { ListaGenericaComponent } from './lista-generica.component'; import { BotonesGuardadoComponent } from './botones-guardado.component'; import { Ng2Summernote } from 'ng2-summernote/ng2-summernote'; @NgModule({ declarations: [ConfirmacionGuardadoComponent, BotonesGuardadoComponent, Ng2Summernote, ListaGenericaComponent], imports: [CommonModule, FormsModule], exports: [ConfirmacionGuardadoComponent, BotonesGuardadoComponent, Ng2Summernote, ListaGenericaComponent] }) export class UtilsModule { }

Implementación del módulo utils en el fichero utils.module.ts

De un vistazo rápido, dentro del módulo se puede apreciar una primera sección de

importaciones, tanto de componentes propios como del núcleo de Angular. Le sigue el

decorador NgModule que, como propiedades, ha de declarar los componentes que tiene,

además de cuáles va a importar y exportar. Lógicamente, como vamos a querer hacer uso de

todos los componentes del módulo utils en otros módulos, tenemos que exportar todos los

componentes declarados.

A modo de resumen, el directorio de útiles contiene tres componentes: uno para mostrar

confirmaciones de guardado al usuario, otro para mostrar el panel de botones de acción con

registros, ya sea para guardarlos, eliminarlos o cancelar cualquier modificación y el tercero es

un módulo adicional que hace uso de la librería Summernote para incluir cajas de edición de

texto enriquecido en el cliente.

Pero no todo son componentes dentro del directorio de utilidad, también existe una clase

especialmente relevante para comunicarnos con el API REST definido en el apartado anterior. El

nombre de esta clase es AngularAPIHelper y hará uso del servicio HTTP de Angular2 para

canalizar todas las peticiones que queramos realizar contra el API desde todos los componentes

del cliente. Estas llamadas estarán enmascaradas con métodos que respetan la nomenclatura

guardada en el API REST.

Como queremos que la clase APIHelper del directorio de utilidad se pueda inyectar como

dependencia en los constructores de los componentes, se hace uso del decorador Injectable4

que nos provee Angular2. Por lo tanto, definida como inyectable, esta clase hace uso de HTTP

para, por medio de sus métodos, transmitir peticiones contra el API implementado

anteriormente. Para conseguirlo se ha de especificar la URL (como atributo estático de la clase)

que se completará con la entidad a la que afecta la operación por medio del parámetro entity

de los métodos que enmascaran las peticiones HTTP (ver ilustración 16).

El resultado de utilizar los métodos del servicio HTTP es la entidad Observable<RespuestaJson>

que luego se tendrá que procesar en los componentes teniendo en cuenta lo asíncrono de

4 Inyección de dependencias en Angular2: https://angular.io/docs/ts/latest/guide/dependency-injection.html

Page 44: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

40

trabajar con entidades Observable5 del módulo RxJS6. La razón por la que se hace uso de

observables en lugar de las promesas de JavaScript es una decisión de diseño de los

desarrolladores de Angular2. A simple vista no hay muchas diferencias entre observables y

promesas, pero entre las más destacadas se encuentra que hay mayor control sobre el inicio y

final de la vida de nuestra petición asíncrona, que se pueden cancelar las peticiones y que

también hay lógica de reintentos en el propio API de los observables.

import { Http, Response } from '@angular/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; export enum ResponseStatus {[…]} export class RespuestaJson {[…]} export class PeticionJson {[…]} @Injectable() export class AngularAPIHelper { static URL: string = ""; static maximoSizeByFichero: number = 0; static mimeTypesPermitidos: string[] = []; constructor(private http: Http) {[…]} private handleError(error: Response) {[…]} cargarConfiguracion() {[…]} parse(response: string): RespuestaJson { return JSON.parse(response) as RespuestaJson; } buildPeticion(find: any, sort: any): PeticionJson {[…]} getAll(entity: string) {[…]} getById(entity: string, id: string) { return this.http.get(AngularAPIHelper.URL + entity + "/" + id).map(response => this.parse(response.text())).catch(this.handleError); } deleteById(entity: string, id: string) {[…]} postEntryOrFilter(entity: string, entryOrFilter: string) {[…]} mimeTypePermitido(mime: string): boolean {[…]} sizeOfFicheroAdecuado(size: number): boolean {[…]} }

Fragmento de la implementación del APIHelper de Angular2.

La forma de utilizar los observables en los componentes se puede ver a través de un ejemplo

activo en uno de los componentes del módulo de escenas. Al igual que en los módulos, en los

componentes hay un primer bloque para importar módulos, clases y/o componentes que

precisemos utilizar en la propia clase del componente. Dicha clase contendrá los atributos y

métodos necesarios para que la interacción usuario-cliente dé como resultado interacciones con

el API REST y, a la postre, también con la colección o colecciones que se vean afectadas en

MongoDB.

5 Documentación sobre la clase Observable (RxJS docs): https://xgrommx.github.io/rx-book/content/observable/index.html 6 Why RxJS?: https://xgrommx.github.io/rx-book/why_rx.html

Page 45: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

41

El decorador del que se hace uso en los componentes es bastante intuitivo: Component. Esta

entidad tendrá como atributos más relevantes el nombre del selector del componente que se

tendrá que escribir en el HTML del template que lo incluya, la URL donde se puede localizar el

template y, en caso de necesidad, las dependencias que requieran ser inyectadas en la clase del

componente en cuestión.

@Component({ templateUrl: './templates/escena-detalle.component.html', providers: [AngularAPIHelper], selector: 'detalle-escena' }) export class DetalleEscenaComponent { confirmacionGuardado: ConfirmacionGuardado; escena: EscenaModel; detalleLiterario: DetalleLiterarioModel; detalleTecnico: DetalleTecnicoModel; botonesGuardado: BotonesGuardado; base64Imagen: SafeUrl; constructor(private angularAPIHelper: AngularAPIHelper, private el: ElementRef, private sanitizer: DomSanitizer, private router: Router, private route: ActivatedRoute) {[...]} private cargarModelo(response) {[…]} private guardarEscena(response) {[…]} private guardarDetalles(response) {[…]} private guardarCambios() {[…]} onSubidaImagen() {[…]} borrarImagen() {[…]} imagenSubida(): boolean {[…]} onAccionGuardado(event) {[…]} }

Fragmento de la clase del componente detalle de escena.

private guardarCambios() { this.angularAPIHelper.postEntryOrFilter('detalleLiterario', JSON.stringify(this.detalleLiterario)) .subscribe(response => this.guardarDetalles(response), error => this.confirmacionGuardado.setEstadoGuardado(false)); }

Ejemplo de uso de la dependencia AngularAPIHelper para guardar los cambios realizados sobre los modelos en el cliente web.

Atributos y métodos de la clase DetalleEscenaComponent son accesibles desde el template

HTML que se ha enlazado en el templateUrl del decorador. De esta forma, junto a las directivas

incluidas en Angular2, le aportamos dinamismo al cliente web y podemos interaccionar con el

API REST que manipula la información almacenada en las colecciones de documentos de

GuionMaker.

<confirmacion-guardado [oConfirm]="confirmacionGuardado"></confirmacion-guardado> <div class="well" *ngIf="escena != undefined"> <div class="panel panel-default"> <div class="panel-body"> <div class="row"> <div class="col-md-2 text-right">

Page 46: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

42

<h4> <label for="escena.titulo">Título:</label> </h4> </div> <div class="col-md-8"> <input type="text" [(ngModel)]="escena.titulo" class="form-control mg-btm" /> </div> <div class="col-md-2"></div> </div> <div class="row"> <div class="col-md-2"></div> <div class="col-md-8"> <input type="checkbox" [(ngModel)]="escena.noche" id="noche" />&nbsp;<label for="noche">¿Sucede de noche?</label> <input type="checkbox" [(ngModel)]="escena.exterior" id="exterior" />&nbsp;<label for="exterior">¿Sucede en exterior?</label> </div> <div class="col-md-2"></div> </div> </div> </div> <div *ngIf="detalleLiterario != undefined" class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title"> <a role="button" data-toggle="collapse" href="#detalleLiterario" aria-expanded="false" aria-controls="detalleLiterario"> Edición literaria de la escena '{{escena.titulo}}' (click para ocultar/mostrar)

Fragmento del template correspondiente al detalle de escena haciendo uso de directivas de Angular2.

Desarrollados los módulos e incluidos en ellos los diversos componentes que surgen de la

implementación de las historias de usuario del proyecto, sólo falta el pegamento que une a unos

con otros y permite la navegación desde cliente: las rutas o routing7 de Angular2.

A todos los efectos, las rutas en Angular2 son módulos que importan instancias de rutas

configuradas por el desarrollador, quien tiene pleno control sobre la navegación de la aplicación,

la cual es transparente para el usuario. Generalmente las rutas se instancian con un atributo que

almacena la dirección a través de la cual se accede y otro que actúa como selector del

componente del que hacer uso cuando desde cliente se llega a la ruta en cuestión. Nótese que

la dirección asignada a las rutas puede tener parámetros del estilo “ruta/:parámetro”.

import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PageNotFoundComponent } from './pageNotFound.component'; import { AppComponent } from './app.component'; import { IndexComponent } from './index.component'; const appRoutes: Routes = [

7 Navegación y enrutado en Angular2: https://angular.io/docs/ts/latest/guide/router.html

Page 47: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

43

{ path: '', component: IndexComponent }, { path: '**', component: PageNotFoundComponent } ] @NgModule({ imports: [RouterModule.forRoot(appRoutes)], exports: [RouterModule] }) export class AppRoutingModule { }

Módulo de enrutado principal de la aplicación

Tras haber generado todas las rutas, el módulo de rutas en desarrollo debe haber importado el

RouterModule. La razón por la que se debe importar RouterModule es porque, después de todo,

es el núcleo de Angular2 quien, por medio de este módulo, construye el árbol de navegación.

Por lo tanto, tendremos que hacer saber al módulo de rutas central de Angular cuáles son

nuestras nuevas rutas haciendo uso de los métodos estáticos implementados para la ocasión

(en el ejemplo de la ilustración 20 RouterModule.forRoot()).

Para que dicha navegación sea posible hay que realizar algunos ajustes extra sobre los

componentes, entre ellos incluir la directiva router-outlet8 en nuestra página principal y

gestionar los parámetros con que se haya llegado a un componente a través de cliente, por

ejemplo, en la carga de una escena en concreto desde un listado de escenas (ilustración 21).

constructor(private angularAPIHelper: AngularAPIHelper, private el: ElementRef, private sanitizer: DomSanitizer, private router: Router, private route: ActivatedRoute) { this.botonesGuardado = new BotonesGuardado(); this.botonesGuardado.mostrarCompleto(); this.confirmacionGuardado = new ConfirmacionGuardado(); this.route.params.switchMap((params: Params) => this.angularAPIHelper.getById('escena', params['id'])). subscribe(response => this.cargarModelo(response), error => console.log('Error:' + error)); }

Imagen del constructor completo del detalle de escena, incluyendo la gestión de parámetros para extraer del API REST la escena concreta.

<nav class="navbar navbar-default navbar-fixed-top">[…]</nav> <router-outlet></router-outlet>

Template de la aplicación principal que incluye el router-outlet para la carga de componentes dinámica según la ruta visitada.

<a routerLink="/proyectos">Proyectos</a>

Directiva routerLink como artífice de que se dispare el evento de navegación.

Sin entrar en mucho más detalle, todos los elementos básicos de la arquitectura de la aplicación

quedan descritos por los modelos previamente expuestos. El resto del trabajo, una vez

8 Router-outlet es la directiva sobre la que se llevará a cabo la carga dinámica de las vistas de componentes en función del estado de la navegación del usuario con el cliente web.

Page 48: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

44

establecida la arquitectura, es seguir implementando nuevos elementos arquitectónicos

siguiendo la metodología establecida y probar que cumplen con los criterios de aceptación de

las historias de usuario. Todo este esfuerzo será, a posteriori, evaluado en la fase de revisión del

sprint por el cliente de la aplicación.

6.1.4 Revisión y retrospectiva del sprint

En las metodologías ágiles, al final de cada iteración es vital mostrar los resultados al cliente para

validar que se han cumplido los criterios de aceptación de las historias de usuario. Esta reunión

es importante para alinear las necesidades del cliente con los trabajos llevados a cabo en la

implementación por lo que, fruto de esta revisión, se espera el éxito final del proyecto una vez

se completen todas las historias presentes en el backlog del producto.

Durante la reunión se han realizado las siguientes observaciones sobre la entrega de

funcionalidad desplegada:

Algunos defectos técnicos como el enrutado de la aplicación provocan situaciones

inesperadas a la hora de acceder directamente a URLs.

Identificadas mejoras técnicas como externalizar a un fichero la configuración

relacionada con las cadenas de conexión y no dejarlo dentro del propio código fuente

de la aplicación.

Respecto a la propia funcionalidad de la aplicación, la retroalimentación es positiva,

pero se identifican puntos que podrían ser complementados en iteraciones posteriores.

Uno de ellos es añadir la posibilidad de marcar si la escena ocurre de día o de noche y si

tiene lugar en el interior de alguna sala o por el contrario es una escena exterior.

También se solicita que, a la hora de exportar los guiones, las escenas tengan el título

en mayúsculas e incluyan la información de temporalidad y situación comentada

previamente.

Adicionalmente a estos comentarios, se detecta una posible mejora a la manera de

gestionar el detalle técnico que, sin embargo, decide aplazarse puesto que no es algo

crítico. Esta mejora es la de poder crear varias tomas asociadas a un detalle técnico,

siendo las tomas una entidad representada por un texto y por una imagen. De esta

manera, queda cubierto un posible caso de uso en que se quieran describir varias tomas

con imágenes.

Es clave considerar todas estas observaciones en la próxima iteración con la mayor urgencia

posible. Que se vuelvan a repetir los mismos errores o no conformidades en una próxima

demostración podría ser un motivo para que empiece a surgir desconfianza entre las dos partes.

Todo esto aporta la visión que nuestro cliente tiene de nuestro trabajo, que al final es lo más

importante y lo que tenemos que tender a mejorar. Sin embargo, también es relevante hacer

autocrítica de lo que ha sido el proceso seguido durante estas semanas con el objetivo de

mejorar y perfilar de cara a los próximos sprints. Esta actividad de autocrítica es llamada

retrospectiva y se evalúan qué cosas bajo nuestro punto de vista se han hecho bien, qué cosas

no se han hecho tan bien y qué otros aspectos se podrían mejorar pese a que no se han hecho

mal.

¿Qué es lo que se ha hecho bien?

o La fase de concepción del sistema es lo suficientemente completa y la hoja de

ruta a seguir es clara.

Page 49: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

45

o La elección de la arquitectura a priori ha llevado un esfuerzo extra, pero aporta

flexibilidad de cara al futuro y a cumplir con las exigencias del cliente.

o La especificación de los criterios de aceptación ha sido exacta, sin dejar lugar a

ambigüedades, lo que ha conducido a la aprobación rápida por parte del cliente.

o La estimación de esfuerzo de las historias de usuario es lo suficientemente

holgada, por lo que ha sido posible cumplir con los plazos indicados en ellas.

o La comunicación con el cliente es fluida y productiva.

¿Qué se podría mejorar?

o Los conocimientos de las tecnologías que se están utilizando para la

implementación de la funcionalidad. Con la experiencia obtenida en esta

iteración y en la siguiente este punto es probable que ya no sea un aspecto a

mejorar.

¿Qué es lo que se ha hecho mal?

o Se han producido algunos retrasos en la iteración por culpa de actualizar algunas

librerías sin documentarse previamente sobre la compatibilidad entre versiones

de otras librerías dependientes de ellas. Si no es estrictamente necesario es

recomendable no actualizar y centrarse en la implementación de

funcionalidades.

o No congelar del todo la funcionalidad que se ha acordado desarrollar,

realizando algún pequeño desarrollo extra o cambio de especificación sobre la

marcha. Esto es tremendamente importante para cumplir con los tiempos que

se han pactado ya que toda nueva funcionalidad o cambio de especificaciones

sobre lo que se está implementado debe esperar a la siguiente iteración.

6.1.5 Resultados visuales en el cliente durante el sprint

La funcionalidad implementada en el cliente durante esta iteración se centra en el listado de

escenas y el detalle de las mismas. Es por eso que se generan las primeras vistas útiles, desde

las cuales se pueden gestionar escenas según lo documentado en el sprint backlog.

Ilustración 12. Vista del listado de escenas, desde donde se puede gestionar el orden de las escenas, añadir nuevas escenas, eliminarlas, destacarlas o exportar el guion con las escenas actuales.

Page 50: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

46

Como se puede ver en la ilustración superior, de una manera intuitiva se puede cambiar el orden

de las escenas arrastrándolas y soltándolas, marcarlas como favoritas o eliminarlas. Cambios

como el de marcar como favoritas las escenas o el orden de las mismas, requiere de accionar el

botón de guardar cambios. Desde la misma pantalla se puede acceder al detalle de cada escena

haciendo click en el botón de edición y, en el caso de que queramos crear una nueva escena a

la lista, tenemos el botón de añadir escena presente.

Cuando terminemos de trabajar con las escenas y queramos ver el resultado, bastará con darle

al botón de exportar y elegir si exportar el guion con los detalles técnicos o literarios. Esta acción

abrirá una nueva ventana con el contenido del detalle literario o técnico de las escenas que

hemos gestionado previamente.

Ilustración 13. Vista de detalle de escena, donde el usuario trabaja con el detalle literario y técnico.

Al acceder al detalle de alguna de las escenas que aparece listada en la vista anterior, podemos

modificar su título, indicar si sucede de día o de noche y, por supuesto, editar el texto que debe

aparecer en el detalle literario y técnico de la escena. La edición del detalle técnico incluye la

posibilidad de subir una imagen al servidor para ilustrar mejor la escena.

Finalizada la edición de la escena se deben guardar los cambios. Esta acción mostrará una

confirmación de que todo ha ido bien en la parte superior de la pantalla, idea a exportar al resto

de vistas de detalle del proyecto según se vayan construyendo.

Page 51: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

47

Ilustración 14. Confirmación de que todo ha ido bien en el salvado de la información.

6.1.6 Análisis de valor entregado y esfuerzo desempeñado

Realizado el análisis cualitativo y mostrado los resultados de los primeros esfuerzos durante el

desarrollo de la aplicación, es hora de ir a los números con el análisis cuantitativo de cómo ha

ido el proceso. Para ello nos vamos a apoyar en 3 gráficos bastante útiles: burndown chart (o

gráfica de desempeño de esfuerzo, value up chart (gráfica de entrega de valor acumulativo) y la

gráfica de estados de historias de usuario.

Gráfica de desempeño de esfuerzo: en este gráfico se representa cómo los puntos de

esfuerzo van decreciendo según pasan los días y se van resolviendo historias de usuario

apiladas en el tablero de las activas.

Ilustración 15. Gráfico de desempeño de la iteración 1 con diferentes fechas como puntos de control.

0

5

10

15

20

25

30

SP (

pu

nto

s d

e h

isto

ria)

Fechas

Burndown chart de la iteración 1

Page 52: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

48

Gráfica de entrega de valor acumulativo: muy similar es este gráfico de entrega de valor

donde en lugar de mirar en orden decreciente el esfuerzo, se va observando cómo crece

el valor de negocio según se van resolviendo historias de usuario.

Ilustración 16. El gráfico de entrega de valor muestra cuánto se está progresando en aportar valor al cliente.

Gráfica de estados de historias de usuario: en este otro gráfico se puede observar como

los diferentes elementos de trabajo, en este caso historias de usuario, van cambiando

de estado según estén activos, resueltos o cerrados. La diferencia de resuelto y cerrado

es tenue pero importante. Cuando una historia de usuario se da por cerrada es porque

0

50

100

150

200

250

300

350

400

450

UV

(u

nid

ades

de

valo

r d

e n

ego

cio

)

Fechas

Value up chart de la iteración 1

Page 53: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

49

cumple con los criterios de aceptación, mientras tanto solo quedará resuelta una vez se

haya despachado.

Ilustración 17. Flujo acumulativo de historias de usuario en la primera iteración.

En lo referido al avance del proyecto, es indudable que en las gráficas se

muestra estancamiento durante la fase central e inicial. Es normal que

en los primeros días de desarrollo se entregue menos valor debido al

análisis de los elementos que conforman la arquitectura software de la

aplicación, añadiendo el hecho de que hay que preparar todas las

herramientas necesarias para hacer dichos elementos realidad. Lo que

tampoco se sale de la normalidad es ese valle que parte del día

19/12/2016 hasta el 9/1/2017, fechas que suelen coincidir con festivos

y celebraciones en la cultura española. Pero, una vez superado ese valle,

el desarrollo toma un buen ritmo y se empiezan a cerrar historias de

usuario desde el 16/1/2017 al 23/1/2017, fecha en la que se concluye

con la implementación de la primera entrega.

Como extra para el análisis de lo realizado hasta el momento, en la

ilustración 10 se muestra la pila de historias de usuario cerradas en este

primer sprint. Como en toda pila, la cima de la misma es la última historia

de usuario cerrada, mientras que la base es la primera. Nótese que, por

necesidad, pese a que se ha seguido la prioridad marcada, ha habido que

realizar primero tareas menos urgentes para luego facilitar el trabajo

con las más críticas.

Ilustración 18. Pila de historias de usuario cerradas

Page 54: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

50

La captura ha sido tomada en la aplicación web Visual Studio Team Services9 donde, aparte de

sincronizar repositorios de código, se pueden gestionar proyectos de metodologías ágiles con

todos sus elementos y conceptos. Esta característica resulta especialmente interesante cuando

se trabaja colaborativamente, pero también individualmente, ya que ayuda a tener un

seguimiento sobre el cumplimiento de objetivos y progresos en el desarrollo de las iteraciones.

6.2 Segundo sprint

En esta segunda iteración se empiezan a cerrar ya las características asociadas al primer

lanzamiento de la fase de concepción del sistema. Es por ello que este lanzamiento puede ser

considerado como el primer hito cumplido en el proyecto y la cuesta abajo en esfuerzo para el

desarrollador al haber atacado lo más crítico lo primero.

Para ello, durante la reunión para determinar cuáles iban a ser las historias de usuario y qué

contenido iban a tener, se acuerda implementar la gestión de proyectos completa y la de la

biblia literaria a falta de las vistas de detalle. Así pues, al final de este periodo de desarrollo ya

se podrá trabajar con diversos proyectos, asociar escenas a ellos y también elementos de la

biblia literaria como son los personajes y los escenarios.

6.2.1 Sprint backlog

Como en este segundo sprint se van a implementar funcionalidades relativas a varias

características de la aplicación, se han dividido las historias de usuario a mostrar en diversas

secciones conectadas con sus características.

A destacar que, aunque se dio por concluida la gestión de guiones, se ha añadido una nueva

historia de usuario para esta característica al hilo de una de las peticiones que el cliente realizó

durante la revisión del primer sprint.

6.2.1.1 Gestión de guiones

Historia de usuario: Marcar temporalidad y localización de escena

Funcionalidad Como usuario registrado Quiero marcar la temporalidad (día/noche) y la localización (interior/exterior) de la escena De forma que cuando se listen y exporten las escenas se muestre como parte del título

Criterio de aceptación Dada la pantalla de detalle de escena Cuando se seleccionen temporalidad y localización Entonces se guardarán los cambios y dicha información se mostrará junto a la escena en exportaciones y listados.

Conversación La pantalla de detalle de la escena tiene que obligar al usuario a establecer la temporalidad y la localización de la escena según lo indicado en el apartado de funcionalidad. Es un

9 Visual Studio Team Services: https://www.visualstudio.com/team-services/

Page 55: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

51

denominador común de todos los guiones literarios, por lo que sería interesante incluir dos cuadros de confirmación para establecer dichos valores.

Esfuerzo: 1 SP Valor: 40 UV Prioridad: Alta

6.2.1.2 Gestión de proyectos

Historia de usuario: Crear proyecto

Funcionalidad Como usuario registrado Quiero crear un proyecto desde la pantalla inicial de la aplicación De forma que se pueda empezar a trabajar en él recién creado

Criterio de aceptación Dada la pantalla de inicial de la aplicación al iniciar sesión Cuando se accione el botón de crear proyecto Entonces se creará un nuevo proyecto a partir de la información solicitada en el formulario de alta

Conversación Tras registrar al usuario, lo que verá al iniciar sesión será la pantalla de gestión de proyectos. En ella podrá ver los proyectos activos propios o compartidos y también podrá dar de alta uno nuevo accionando el botón creado para tal efecto. En el formulario inicial se debe establecer, al menos, un nombre, si es un proyecto público o no y una breve descripción o sinopsis.

Esfuerzo: 2 SP Valor: 65 UV Prioridad: Alta

Historia de usuario: Cancelar proyecto

Funcionalidad Como usuario registrado Quiero cancelar un proyecto De forma que no aparezca entre el listado de los proyectos activos de la cuenta

Criterio de aceptación Dada la pantalla de inicial de la aplicación al iniciar sesión Cuando se accione el botón de cancelar un proyecto y se confirme la acción Entonces desaparecerá de la lista de proyectos activos

Page 56: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

52

Conversación Al igual que se pueden crear proyectos desde la pantalla inicial de la aplicación, es imprescindible dejar al usuario la posibilidad de cancelar un proyecto si quiere abandonar su desarrollo. Será una acción que se pueda deshacer, pero de todas formas habrá que pedir confirmación.

Esfuerzo: 2 SP Valor: 30 UV Prioridad: Baja

Historia de usuario: Listar proyectos

Funcionalidad Como usuario registrado Quiero poder visualizar los proyectos en que estoy trabajando o bien se han cancelado De forma que se pueda acceder al detalle de los mismos o bien empezar a trabajar en sus documentos

Criterio de aceptación Dada la pantalla de inicial de la aplicación al iniciar sesión Cuando se acceda o bien se introduzca un nombre en el buscador de proyectos Entonces se mostrará la lista de proyectos que coincidan con la búsqueda o bien el listado completo de proyectos activos o inactivos, según se marque en el filtro

Conversación Normalmente cuando ya hay proyectos en nuestro listado, ya sean propios o compartidos, querremos acceder al detalle de los mismos o trabajar sobre guiones o biblia literaria. Para facilitar el trabajo, es útil incluir un buscador para buscar el proyecto por nombre. Además, habrá que mostrar un filtro de proyectos activos o cancelados por si en algún momento quiere recuperar algún proyecto.

Esfuerzo: 3 SP Valor: 60 UV Prioridad: Media

Historia de usuario: Recuperar proyecto

Funcionalidad Como usuario registrado Quiero recuperar un proyecto De forma que se pueda seguir trabajando en él tal y como estaba en su cancelación

Criterio de aceptación

Page 57: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

53

Dada la pantalla de inicial de la aplicación al iniciar sesión Cuando se seleccione un proyecto de la lista de cancelados, se accione el botón de recuperar y se confirme la acción Entonces reaparecerá el proyecto recuperado en la lista de proyectos activos

Conversación A veces los proyectos cancelados pueden ser retomados por los usuarios que los habían descartado. Para facilitar la tarea a todos aquellos creativos que en su momento cancelaron el proyecto, desde la lista de proyectos cancelados aparecerá un botón de recuperación. Una vez accionado el botón se pedirá confirmación para recuperarlo, hecho lo cual volverá a la lista de proyectos activos tal y como estaba.

Esfuerzo: 2 SP Valor: 20 UV Prioridad: Baja

Historia de usuario: Modificar detalles de proyecto

Funcionalidad Como usuario registrado Quiero poder modificar los detalles asociados a un proyecto De forma que se puedan modificar datos esenciales del proyecto en cualquier momento, entre los que se encuentran: su nombre, descripción o visibilidad.

Criterio de aceptación Dada la pantalla de edición de detalles de proyecto Cuando se cumplimenten los datos esenciales del formulario de proyecto Entonces se accionará el botón de guardado confirmando la modificación de los datos esenciales del proyecto

Conversación No queremos que los datos con los que creamos los proyectos sean inmutables, razón por la cual deben poderse editar desde una vista creada para tal efecto. Al modificar los detalles se podrán guardar al accionar el botón de guardado.

Esfuerzo: 3 SP Valor: 45 UV Prioridad: Media

Historia de usuario: Agregar un usuario al proyecto

Page 58: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

54

Funcionalidad Como usuario registrado Quiero poder agregar a un usuario al proyecto De forma que se pueda colaborar en el desarrollo

Criterio de aceptación Dada la vista de gestión de usuarios del proyecto Cuando se seleccione un usuario de la lista de usuarios, se decida si puede modificar el proyecto o no y se confirme la agregación Entonces se agregará al usuario a la lista de usuarios del proyecto

Conversación El desarrollo de un proyecto audiovisual suele tener a varios expertos en colaboración. Es imprescindible gestionar una lista de usuarios que puedan acceder al proyecto para leerlo o modificarlo. Será imprescindible que haya una vista dedicada a esta tarea incluyendo un selector de usuarios y de permisos. Configurado un usuario, sólo quedará agregarlo accionando el botón creado para tal efecto.

Esfuerzo: 4 SP Valor: 55 UV Prioridad: Media

Historia de usuario: Dar de baja un usuario del proyecto

Funcionalidad Como usuario registrado Quiero poder dar de baja un usuario del proyecto De forma que abandone el desarrollo colaborativo para ese proyecto

Criterio de aceptación Dada la vista de gestión de usuarios del proyecto Cuando se seleccione un elemento de la lista de usuarios, se accione el botón de baja y se confirme Entonces se eliminará de la lista de usuarios y no podrá acceder más al proyecto

Conversación Al igual que se puede agregar usuarios al proyecto, por diversas circunstancias es probable que uno de los miembros abandone su desarrollo. Para proteger la privacidad y la seguridad de ese proyecto activo, los usuarios tienen que dar de baja al que abandona al proyecto desde la vista de gestión de usuarios. En pos de permitir dicha acción se mostrará un botón de baja al lado del nombre del usuario seleccionado. Imprescindible la confirmación de la acción de la baja del usuario.

Page 59: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

55

Esfuerzo: 2 SP Valor: 15 UV Prioridad: Media

Historia de usuario: Seleccionar proyecto

Funcionalidad Como usuario registrado Quiero poder seleccionar uno de los proyectos De forma que biblia literaria y guiones estén asociados a dicho proyecto

Criterio de aceptación Dada la lista de proyectos Cuando se seleccione un elemento Entonces se podrá acceder a toda la información contenida en el proyecto

Conversación Debido a que un usuario puede crear tantos proyectos como desee, por mantener un orden y una coherencia, no se podrá acceder a los elementos asociados a dicho proyecto hasta que seleccione en qué proyecto quiere trabajar. Dicha pantalla de selección se mostrará siempre y cuando el usuario no haya seleccionado con qué proyecto trabajar.

Esfuerzo: 2 SP Valor: 60 UV Prioridad: Media

6.2.1.3 Gestión de biblias literarias

Historia de usuario: Listar personajes

Funcionalidad Como usuario registrado Quiero poder ver la lista de personajes De forma que cuando se elija hacer una acción sobre cada uno de ellos, se abra la vista pertinente

Criterio de aceptación Dada la vista de biblia literaria Cuando se acceda a la edición de personajes Entonces se visualizará un listado con todos los personajes que forman parte de esa biblia

Conversación

Page 60: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

56

Antes de empezar a trabajar con personajes, al igual que sucede en las escenas, es interesante verlos a todos y cada uno de ellos. Dicha lista será visualizable a través de la sección de biblia literaria.

Esfuerzo: 2 SP Valor: 45 UV Prioridad: Media

Historia de usuario: Crear personaje

Funcionalidad Como usuario registrado Quiero poder crear un personaje De forma que se agregue a la biblia literaria del proyecto en curso

Criterio de aceptación Dada la lista de personajes Cuando se accione el botón de nuevo personaje Entonces se creará un nuevo personaje que aparecerá en el listado listo para utilizar

Conversación Dinámicamente y, nuevamente como en las escenas, en la lista de personajes debe existir un botón a través del cual se pueda agregar un nuevo registro en la colección de personajes.

Esfuerzo: 2 SP Valor: 50 UV Prioridad: Media

Historia de usuario: Listar escenarios

Funcionalidad Como usuario registrado Quiero poder ver la lista de escenarios De forma que cuando se elija hacer una acción sobre cada uno de ellos, se abra la vista pertinente

Criterio de aceptación Dada la vista de biblia literaria Cuando se acceda a la edición de escenarios Entonces se visualizará un listado con todos los escenarios que forman parte de esa biblia

Conversación

Page 61: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

57

Antes de empezar a trabajar con escenarios, al igual que sucede en las escenas, es interesante verlos a todos y cada uno de ellos. Dicha lista será visualizable a través de la sección de biblia literaria.

Esfuerzo: 2 SP Valor: 45 UV Prioridad: Media

Historia de usuario: Crear escenario

Funcionalidad Como usuario registrado Quiero poder crear un escenario De forma que se agregue a la biblia literaria del proyecto en curso

Criterio de aceptación Dada la lista de escenarios Cuando se accione el botón de nuevo escenario Entonces se creará un nuevo escenario que aparecerá en el listado listo para utilizar

Conversación Dinámicamente y, nuevamente como en las escenas, en la lista de escenarios debe existir un botón a través del cual se pueda agregar un nuevo registro en la colección de escenarios.

Esfuerzo: 2 SP Valor: 50 UV Prioridad: Media

6.2.2 Desarrollo del sprint

Cerrar las funcionalidades relacionadas con las escenas, proyectos y comenzar con la biblia

literaria es el foco de esta iteración. Con motivo de la aparición de la gestión de la biblias

literarias y proyectos nuestro modelo conceptual se debe ampliar para dar cabida a toda la

funcionalidad que se describe en las historias de usuario de la sección anterior.

Por otro lado, la entidad de relación que nace de la asociación de proyectos y usuarios también

es necesaria para poder controlar desde el código cómo es dicha relación en cuanto a permisos,

así como la auditoría en términos de fecha y hora de dicha asociación.

Obviamente el concepto de biblia literaria, al igual que en los guiones, es un término virtual que

sirve para agrupar conceptos que sí se ven reflejados a posteriori en nuestro análisis. Por lo

tanto, y siendo coherentes con esta lógica, nacen las entidades de personajes y escenarios, se

vinculan con los proyectos y además se acuerda con el cliente que no son entidades que se

puedan compartir entre diferentes proyectos.

Page 62: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

58

Ilustración 19. Diagrama de clases UML para la iteración 2 de GuionMaker.

Durante esta fase se ha decidido que cuando los usuarios accedan al portal sólo puedan navegar

por el inicio de la aplicación o sus proyectos. Es por esto que hasta que no se seleccione el

proyecto en que se desea trabajar, el usuario no podrá actuar sobre la lista de escenas o la biblia

literaria relacionadas con él. Una vez se seleccione el proyecto, debe ser bien visible para el

usuario que está trabajando sobre él y que todos los registros que saldrán, tanto en escenas

como en la biblia literaria, pertenecerán a ese proyecto.

Con motivo de lo anterior, en el cliente surge la necesidad de tener persistencia del usuario que

está trabajando con la aplicación y el proyecto en que lo está haciendo. Como aún no es

momento de la gestión de usuarios, se decide crear a mano algunos usuarios en la colección de

usuarios de la base de datos y asociarlos a proyectos de prueba. Sin embargo, durante esta

iteración sí que se decide implementar la funcionalidad de selección de proyecto, momento que

se aprovechará para mantener persistencia de ese dato durante la sesión del usuario.

Tras abordar los conceptos con que vamos a trabajar en esta iteración y funcionalmente descrito

en las historias de usuario qué es lo que espera el cliente, queda especificar las evoluciones

realizadas sobre nuestra arquitectura.

API REST Express JS

o Creación de los modelos Mongoose asociados a los nuevos conceptos que

aparecen en el diagrama UML.

o Implementación de nuevas rutas haciendo uso del APIHelper para que las

nuevas entidades sean accesibles desde cliente.

o Modificación de la configuración de la aplicación

Page 63: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

59

Uso de un fichero externo JSON para URLs relacionadas con el API o la

base de datos para evitar introducir datos de configuración en el propio

código.

Inclusión de las nuevas rutas a los métodos de las nuevas entidades.

Angular2

o Creación de los módulos de proyectos y biblia literaria

Rutas

Componentes (listados y detalles)

Plantillas

o Ampliación del módulo de utilidades.

Creación de un servicio que accede al LocalStorage de HTML5 para

mantener en el navegador propiedades relacionadas con el proyecto

con que se está trabajando y el usuario actual.

Creación de un componente listado genérico para no repetir el mismo

código de listado para aquellos listados genéricos que no tienen

funcionalidad extra como los de personajes o escenarios.

6.2.3 Documentación técnica del sprint

Antes de ponernos a trabajar en el cliente, lo normal es que se creen los modelos Mongoose

que van a ser usados en la aplicación ExpressJS que tenemos sirviendo como API REST. Es por

eso que siguiendo el mismo esquema de la primera iteración se agregan los modelos

correspondientes a personajes, escenarios y colaboraciones. A destacar queda que esta última

entidad relaciona a proyectos y usuarios, pero además mantiene persistencia de cómo es esa

relación de colaboración en referencia a sus permisos.

Ampliando el concepto de colaboración, a priori se han definido dos tipos de colaboraciones

posibles: la de solo lectura o la de edición. La primera tiene sentido cuando el proyecto al que

se asocia el colaborador es privado, mientras que el segundo sí tiene sentido aplicarlo a

cualquier tipo de proyecto para permitir que otro usuario pueda realizar cambios sobre los

registros del proyecto, que no el proyecto en sí.

Por lo tanto y teniendo en cuenta que estos tipos de colaboración podrían mutar en un futuro,

lo más adecuado es plantearlo como una enumeración y establecer que el campo de

permisos sea numérico. Además, habrá que asegurarse de crear un índice único en

MongoDB para los identificadores de usuario y de proyecto. De esta forma se libera al

cliente de esta comprobación al utilizar al API y aseguramos la integridad de la

información persistente.

export enum PermisosColaboracion { SoloLectura, Edicion } export class Colaboracion { schema: mongoose.Schema; static current: Colaboracion = new Colaboracion(); private constructor() { this.schema = new mongoose.Schema({

Page 64: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

60

usuario: { type: mongoose.Schema.Types.ObjectId, ref: mongoose.model(Usuario.name).schema }, proyecto: { type: mongoose.Schema.Types.ObjectId, ref: mongoose.model(Proyecto.name).schema }, fecha: Date, permisos: Number }); this.schema.index({ usuario: 1, proyecto: 1 }, { unique: true }); mongoose.model(Colaboracion.name, this.schema); } }

Entidad colaboración que lleva a la realidad el concepto de usuarios colaboradores.

Una vez definidos los modelos y las clases-ruta que implementan los métodos CRUD a los que

se vincularán las rutas de la aplicación ExpressJS, sólo queda hacer esto último. Ahora bien,

adicionalmente en este punto queremos evitar una oportunidad de mejora detectada durante

el comienzo de la segunda iteración.

Esta oportunidad de mejora consiste en que no es posible acceder de forma directa a las rutas

que generamos en el cliente debido a que no se delegan desde la aplicación de ExpressJS las

peticiones que no contienen el prefijo “/api/”. Es decir, que, navegando por el cliente, si

intentamos recargar la página, ExpressJS nos devolverá un 404. Por ello, e inmediatamente

después de declarar las rutas del API, decidimos añadir el siguiente fragmento de código:

this.api.use(router); // aquí, con el objeto router, se formaliza la declaración de las rutas del api “/api/*” this.api.all('/*', function (req, res) { res.sendFile(path.join(__dirname, '/public/dist/index.html')); });

Fragmento de código del fichero app.ts en que se delega al cliente y su enrutador las peticiones HTTP sirviendo un fichero estático sito en la carpeta pública del proyecto.

Según la documentación de ExpressJS10, todas las sentencias de configuración se evalúan en el

mismo orden en que se implementan. En consecuencia, lo que estamos consiguiendo con este

fragmento de código es decirle a la aplicación que todas las rutas que no sean las que hayamos

contemplado sirvan un html que, en este caso, es nuestra aplicación Angular2 y cuenta con un

módulo de enrutado activo que, incluyendo el manejador del error 404, nos llevará al

componente adecuado en todo momento.

Método Ruta Parámetros Respuesta

GET /api/escenas - JSON (RespuestaJson)

POST /api/escenasPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/escena/ JSON (Escena) JSON (RespuestaJson)

GET /api/escena/:id String(Id) JSON (RespuestaJson)

DELETE /api/escena/:id String(Id) JSON (RespuestaJson)

GET /api/detallesTecnicos - JSON (RespuestaJson)

POST /api/detalleTecnico/ JSON (Det.Tec.) JSON (RespuestaJson)

GET /api/detalleTecnico/:id String(Id) JSON (RespuestaJson)

DELETE /api/detalleTecnico/:id String(Id) JSON (RespuestaJson)

10 ExpressJS docs: https://expressjs.com/en/guide/writing-middleware.html

Page 65: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

61

GET /api/detallesLiterarios - JSON (RespuestaJson)

POST /api/detalleLiterario/ JSON (Det.Lit.) JSON (RespuestaJson)

GET /api/detalleLiterario/:id String(Id) JSON (RespuestaJson)

DELETE /api/detalleLiterario/:id String(Id) JSON (RespuestaJson)

GET /api/plantillas - JSON (RespuestaJson)

POST /api/plantillasPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/plantilla/ JSON (Plantilla) JSON (RespuestaJson)

GET /api/plantilla/:id String(Id) JSON (RespuestaJson)

DELETE /api/plantilla/:id String(Id) JSON (RespuestaJson)

GET /api/proyectos - JSON (RespuestaJson)

POST /api/proyectosPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/proyecto/ JSON (Proyecto) JSON (RespuestaJson)

GET /api/proyecto/:id String(Id) JSON (RespuestaJson)

DELETE /api/proyecto/:id String(Id) JSON (RespuestaJson)

GET /api/colaboraciones - JSON (RespuestaJson)

POST /api/colaboracionesPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/colaboracion/ JSON (Colaboracion) JSON (RespuestaJson)

GET /api/colaboracion/:id String(Id) JSON (RespuestaJson)

DELETE /api/colaboracion/:id String(Id) JSON (RespuestaJson)

GET /api/escenarios - JSON (RespuestaJson)

POST /api/escenariosPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/escenario/ JSON (Escenario) JSON (RespuestaJson)

GET /api/escenario/:id String(Id) JSON (RespuestaJson)

DELETE /api/escenario/:id String(Id) JSON (RespuestaJson)

GET /api/personajes - JSON (RespuestaJson)

POST /api/personajesPorFiltro JSON (PeticionJson) JSON (RespuestaJson)

POST /api/personaje/ JSON (Personaje) JSON (RespuestaJson)

GET /api/personaje/:id String(Id) JSON (RespuestaJson)

DELETE /api/personaje/:id String(Id) JSON (RespuestaJson)

GET /api/clasificaciones - JSON (RespuestaJson)

POST /api/clasificacion/ JSON (Clasificacion) JSON (RespuestaJson)

GET /api/clasificacion/:id String(Id) JSON (RespuestaJson)

DELETE /api/clasificacion/:id String(Id) JSON (RespuestaJson)

GET /api/generos - JSON (RespuestaJson)

POST /api/genero/ JSON (Clasificacion) JSON (RespuestaJson)

GET /api/genero/:id String(Id) JSON (RespuestaJson)

DELETE /api/genero/:id String(Id) JSON (RespuestaJson)

Como se puede observar, el API de GuionMaker va creciendo en entidades y también en

prestaciones. Por lo general todas las entidades suelen tener asociadas las mismas acciones

salvo aquellas orientadas a ser más estáticas como sucede con la clasificación de edades o los

géneros de proyectos. Estas dos últimas entidades vienen a completar los detalles del proyecto

y sus registros serán compartidas por todos los proyectos, puesto que se trata de información

estática y común en toda la aplicación.

Por último, y no menos importante, se ha decidido externalizar la configuración relacionada con

la URL y puerto que utiliza el API para escuchar peticiones a un fichero JSON, así como la

información necesaria para comunicarse con las colecciones de MongoDB. Como este fichero

JSON también será necesario en el cliente, resulta interesante incluir qué mimetypes se van a

Page 66: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

62

poder enviar y el tamaño máximo de fichero en bytes. Todo esto facilitará la tarea de desplegar

en otros entornos ya que no requerirá modificar los ficheros fuente en caso de cambiar de

criterio. Bastará con cargar el fichero JSON y acceder a las propiedades para hacer uso de los

valores que contiene.

{ "apiURL": "localhost", "publicApiPort": "1337", "privateApiPort": "1337", "dbURL": "192.168.1.135", "dbCollectionName": "guionMaker", "maxFileSizeBytes": 2202009.6, "mimeTypesPermitidos": [ "image/jpeg", "image/jpg", "image/png" ] }

Fichero de configuración relativo al API y al cliente en formato JSON.

Una vez se ha implementado y probado el API, el siguiente paso es crear en el cliente Angular2

los módulos que incluyan las nuevas funcionalidades. Esta iteración incluye los nuevos módulos

de biblia literaria y proyectos, lo cual se traduce en nuevos directorios, componentes, rutas y

plantillas en nuestra solución inicializados por el Angular-CLI.

Ilustración 20. Nuevos elementos añadidos a la solución de cara a incluir las nuevas funcionalidades.

Entre todos los listados y detalles de esta iteración, cabe destacar que las listas de la biblia

literaria en realidad hacen uso de un componente de listados genéricos para facilitar el

mantenimiento de un componente que tiende a repetirse a lo largo de la aplicación en las

diversas gestiones que exigen las funcionalidades.

export class ListaGenerica { titulo: string; entidad: string; entidadPorFiltro: string; peticion: PeticionJson; nuevoElemento: any; constructor(titulo: string, entidad: string, entidadPorFiltro: string, peticion: PeticionJson, nuevoElemento: any) {[…]}

Page 67: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

63

} @Component({ selector: 'lista-generica', templateUrl: './templates/lista-generica.component.html', providers: [AngularAPIHelper] }) export class ListaGenericaComponent implements OnInit { botonesGuardado: BotonesGuardado; elementos: any[]; elementoAEliminar: any; @Input() listaGenerica: ListaGenerica; ngOnInit() { this.cargarElementos(); } constructor(private angularAPIHelper: AngularAPIHelper, private _location : Location) {[…]} private cargarElementos() {[…]} onNuevoElemento() {[…]} setElementoAEliminar(elemento: any) {[…]} onAccionGuardado(event) {[…]} }

Fragmento de código del componente de listados genéricos.

Como se puede observar, el componente tiene una propiedad <listaGenerica> en que se definen

una serie de atributos para poder trabajar sobre el API en diferentes instancias según esos

atributos se inicialicen. Consecuentemente, en los listados de personajes y escenarios lo que

habrá que hacer es importar este componente e instanciar la propiedad que funcionará como

Input del listado genérico. Dicha propiedad debe cumplimentar el título que tendrá el listado,

sobre qué entidad se trabaja, cuál es el nombre del método para obtener registros a través de

un filtro, el objeto PeticionJSON que servirá para filtrar los registros de la entidad deseada y una

nueva instancia de la que clonar e inicializar nuevos elementos desde el listado genérico.

export class EscenariosListaComponent { listaGenerica: ListaGenerica; constructor(private angularAPIHelper: AngularAPIHelper, private localStorageService: LocalStorageService) { let nuevoEscenario = new EscenarioModel(); nuevoEscenario.proyecto = this.localStorageService.getPropiedad('proyectoActual'); this.listaGenerica = new ListaGenerica ("Listado de escenarios de la biblia literaria", "escenario", "escenariosPorFiltro", this.angularAPIHelper.buildPeticion({ proyecto: this.localStorageService.getPropiedad('proyectoActual') }, {}), nuevoEscenario); } } Plantilla en HTML, sólo una línea que carga el componente del listado genérico con nuestro objeto listaGenerica. Esto desencadena a su vez la carga de la plantilla del listado genérico.

Page 68: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

64

<lista-generica [listaGenerica]="listaGenerica"></lista-generica>

Uso del componente de listado genérico en la entidad escenarios.

De una forma similar, haciendo uso de Input, se trabaja en la gestión de colaboradores en un

proyecto. Se ha acordado introducir esta gestión dentro del propio detalle del proyecto. Por lo

tanto, una vez se cargue la vista de detalle del proyecto también se cargará un subcomponente

dedicado a la gestión de colaboradores de dicho proyecto.

Funcionalmente se podrán agregar nuevos usuarios introduciendo un email que debe

corresponder a algún usuario de la aplicación y, en caso contrario, se alertará al usuario de que

el email no está registrado. Adicionalmente, si el email existe, tendremos un desplegable que

nos permitirá decidir entre permisos de edición o lectura para el colaborador recién creado.

export class GestorColaboraciones { colaboraciones: ColaboracionModel[]; proyectoID: string; colaboracionAEliminar: ColaboracionModel; angularAPIHelper: AngularAPIHelper; constructor(angularAPIHelper: AngularAPIHelper, proyectoID: string) {[…]} public getFromProyectoID(proyectoID: string) {[…]} public prepararAEliminar(colaboracion: ColaboracionModel) {[…]} public cancelarEliminacion() {[…]} public eliminarColaboracion() {[…]} public actualizar(colaboracion: any) {[…]} public nuevoColaborador(usuario: UsuarioModel) {[…]} } @Component({ selector: "gestor-colaboraciones", templateUrl: "./templates/gestor-colaboraciones.component.html", providers: [AngularAPIHelper] }) export class GestorColaboracionesComponent { @Input() gestorColaboraciones: GestorColaboraciones; email: string; usuarioNoEncontrado: boolean; emailInvalido: boolean; tiposPermiso: any[]; constructor() {[…]} onCrearColaboracion() {[…]} mostrarNombrePermiso(permiso: any): string {[…]}

Definición del componente de gestión de colaboradores

[…]<div class="row mgn-top-1"> <div class="col-md-12"> <gestor-colaboraciones [gestorColaboraciones]="gestorColaboraciones"></gestor-colaboraciones> </div> </div> <div class="row mgn-top-1">

Page 69: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

65

<div class="col-md-12"> <label for="sinopsis">Sinopsis</label> <ng2-summernote lang="es-ES" [(ngModel)]="proyecto.sinopsis" id="sinopsis"></ng2-summernote> </div> </div>[…]

Uso del componente de gestión de colaboradores en el detalle de proyecto

En otro lugar, los proyectos tienen una particularidad con respecto al resto de esquemas de la

aplicación. Al contrario que ellos, los proyectos en lugar de ser eliminados serán cancelados.

Esta acción se podrá deshacer en cualquier momento desde el listado de proyectos, donde para

tal efecto se habilita un botón que filtra entre los proyectos activos y cancelados para el usuario

que ha iniciado sesión (ver ilustración 17).

Ilustración 21. Captura de aplicación en el listado de proyectos que han sido cancelados.

Transcurrido un periodo de tiempo configurable, los proyectos cancelados se eliminarán

automáticamente por medio de un proceso en segundo plano programado para tal fin. Por

tanto, con la eliminación definitiva del proyecto, esta acción se trasladará a todos los elementos

asociados a él: biblia literaria, colaboraciones y detalles de escenas. Estos procesos programados

se implementarán en la última iteración puesto que es un añadido acordado durante el

desarrollo de este sprint y no es considerado urgente por el cliente.

Finalmente, otro de los puntos destacados en el sprint es la creación del LocalStorageService.

Gracias a este servicio, seremos capaces de trabajar con el diccionario localStorage de HTML511

en GuionMaker. Si bien se podría haber prescindido de este servicio porque es un objeto

predeterminado, lo cierto es que el proyecto seleccionado en la página principal y el usuario que

ha hecho login son datos que queremos mantener en la barra de navegación.

11 Referencia al webStorage de HTML5 en W3Schools: https://www.w3schools.com/html/html5_webstorage.asp

Page 70: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

66

En consecuencia, lo idóneo es asociar a esta barra una propiedad del servicio, que en este caso

será el diccionario localStorage. De esta forma y ante cualquier cambio, la barra siempre estará

mostrando información actualizada por medio de inyección de dependencias.

@Injectable() export class LocalStorageService { propiedades: { [id: number]: string } = {}; constructor() { for (let idx = 0; idx < localStorage.length; idx++) { let clave = localStorage.key(idx); this.propiedades[clave] = localStorage.getItem(clave); } } setPropiedad(clave: string, valor: string) { localStorage.setItem(clave, valor); this.propiedades[clave] = valor; } getPropiedad(clave: string) { return localStorage.getItem(clave); } deletePropiedad(clave: string) { delete this.propiedades[clave]; return localStorage.removeItem(clave); } }

Implementación del servicio LocalStorageService con el decorador @Injectable.

6.2.4 Revisión y retrospectiva del sprint

Implementada la funcionalidad correspondiente a la segunda iteración y probado que se cumple

con los criterios de aceptación, es hora de que el cliente sea el que tome la palabra con respecto

al desarrollo en curso. Tras una demostración de funcionalidad completa en la que el cliente ha

tenido contacto con la aplicación, se han realizado las siguientes observaciones:

Dentro del listado de proyectos y para resoluciones altas, el botón de mostrar/ocultar

proyectos cancelados aparece demasiado separado del resto de las acciones. Sería

conveniente llevarlo junto al de añadir proyectos, al estilo del listado de escenas.

El botón de volver dentro de los detalles debe avisar que se pueden haber realizado

cambios que no se han guardado.

Con vistas a la generación de escenarios y personajes, se acuerda que el detalle incluya

la opción de subir imágenes.

No permitir que los colaboradores puedan acceder al detalle del proyecto, pero sí

mostrar los proyectos en que colabora en su listado de proyectos con una marca.

Con vistas a la funcionalidad de la primera iteración, y a lo que está por venir en la subida

de imágenes, se sugiere limitar las imágenes subidas en resolución y aspecto.

Desde un punto de vista interno, esta iteración ha sido bastante más ágil que la anterior y se

nota que las bases de la arquitectura están bien asentadas. La experiencia obtenida sobre las

Page 71: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

67

tecnologías que toman parte del proyecto ha ayudado también enormemente a agilizar el

proceso, pero también el haber abordado lo más crítico primero. Pese a todo siempre hay

facetas a mejorar en el proceso:

¿Qué es lo que se ha hecho bien?

o La estimación de las historias de usuario ha sido realista y fácil de cumplir.

o Implementar componentes reutilizables en clientes con vistas a que serán

utilizados en diversas áreas de la aplicación. Esto es fruto de conocer mejor la

tecnología que se utiliza y aplicar buenas prácticas de implementación.

o La comunicación con el cliente casi no es necesaria porque las historias de

usuario están alineadas con sus necesidades. Aun así, se ha necesitado matizar

algunos puntos como la gestión de colaboradores.

¿Qué se podría mejorar?

o Aunque se sigue la metodología de comparar contra los criterios de aceptación

lo desarrollado en cada historia de usuario, convendría automatizar estas

pruebas. Es más, sería conveniente añadir otras pruebas relacionadas con el API,

validaciones en peticiones y sus respuestas.

¿Qué es lo que se ha hecho mal?

o Las metodologías ágiles persiguen mantener un ritmo constante de trabajo

durante las iteraciones. En la primera y segunda iteración parece que la última

semana resulta ser en la que se rompe ese promedio y ritmo constante de

desempeño. Sería mejor si ese esfuerzo estuviera proporcionalmente repartido.

6.2.5 Resultados visuales en el cliente durante el sprint

En esta iteración se han hecho los esfuerzos necesarios para que los usuarios puedan hacer una

gestión integral de los proyectos en que luego se incluirán los elementos de proyecto tales como,

escenas, personajes o escenarios.

Ilustración 22. Vista con el listado de los proyectos activos y su última fecha de modificación.

Siguiendo la misma idea que la vista del listado de escenas, aquí se muestra un listado de todos

los proyectos del usuario. Dicho usuario puede elegir cancelarlos por medio del botón con el

cubo de basura o bien acceder al detalle del mismo con el botón de modificar. En el caso en que

Page 72: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

68

cancele alguno de los proyectos, éste desaparecerá de la lista de proyectos activos y lo podrá

recuperar de la lista de proyectos cancelados que aparece cuando se hace click en mostrar

cancelados.

Ilustración 23. Vista de detalle del proyecto con la gestión de colaboradores en el proyecto incluida.

La vista de detalle no representa una gran novedad con respecto a la de escenas, pero sí que se

ven campos de selección, como el género y la clasificación, que vienen rellenos desde el API y,

además, es posible gestionar colaboradores. Para añadirlos al listado basta con introducir el

email que tengan en GuionMaker. Una vez en el listado podremos seleccionar sus permisos y/o

darlos de baja del proyecto.

Ilustración 24. Página principal del cliente en donde se puede seleccionar el proyecto en que se quiere trabajar y asociar escenas, personajes o escenarios. En este caso el proyecto “Esto es otro proyecto” está fijado.

Page 73: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

69

Antes de empezar a trabajar en escenas, escenarios o personajes, es preciso que el usuario

determine a qué proyecto quiere asociar todos estos elementos. Es por eso que, mientras no se

seleccione desde la pantalla principal en qué proyecto se desea trabajar, el usuario no podrá

acceder al listado de escenas, escenarios o personajes, estos dos últimos de la biblia literaria.

6.2.6 Análisis de valor entregado y esfuerzo desempeñado

Nuevamente es la hora de analizar cuantitativamente cómo ha ido la segunda iteración en lo

que se refiere en tiempos, esfuerzo y entrega de valor. Es por eso que volvemos a hacer uso de

los mismos diagramas de la primera iteración aplicados a las fechas de la segunda.

Gráfica de desempeño de esfuerzo

Ilustración 25. Diagrama de esfuerzo durante la segunda iteración del desarrollo de GuionMaker. Aparentemente la entrega de esfuerzo ha sido más lineal

0

5

10

15

20

25

30

35

SP (

pu

nto

s d

e h

isto

ria)

Fechas

Burndown chart de la iteración 2

Page 74: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

70

Gráfica de entrega de valor acumulativo

Ilustración 26. Diagrama de entrega de valor durante la segunda iteración. Extrapolando el esfuerzo a la entrega de valor se aprecia una evolución más lineal en el periodo de la segunda iteración.

Gráfica de estados de historias de usuario

Ilustración 27. Gráfico acumulado de estados durante la segunda iteración. En él se ve que la mayor carga de trabajo la supusieron las historias de usuario referentes a la selección de proyecto y creación de la biblia literaria.

Podemos decir que esta segunda iteración es la constatación de que la arquitectura de la

aplicación está bien asentada, la metodología está funcionando y que el desempeño de esfuerzo

es constante salvo alguna semana de valle y su contrapartida en la última semana de

sobreesfuerzo.

0

100

200

300

400

500

600

700

UV

(u

nid

ades

de

valo

r d

e n

ego

cio

)

Fechas

Value up chart de la iteración 2

Page 75: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

71

Según la gráfica de estados parece que la última semana se ha establecido en el momento en

que hay que realizar el mayor esfuerzo por cerrar historias de usuario activas. Por lo tanto,

podemos decir que este punto es un elemento claro de mejora y hay que trabajar en él de cara

a la siguiente iteración. Como resultado de dicha mejora, estaremos cumpliendo otro más de

los valores del manifiesto ágil que reza así: “los procesos ágiles promueven el desarrollo

sostenido. Los patrocinadores, desarrolladores y usuarios deben mantener un ritmo constante

de forma indefinida”.

Page 76: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

72

6.3 Tercer sprint

La tercera iteración debe representar la consolidación de la característica de gestión de biblia

literaria, permitiendo al usuario realizar una gestión completa sobre sus escenarios o

personajes. Además, durante esta fase se va a empezar a añadir valor competitivo a nuestra

solución con la inclusión de la gestión de encabezados, la ayuda en escritura y la gestión de

usuarios.

Uno de los puntos clave en esta iteración será la gestión de usuarios ya que dentro de ésta se

incluye que las llamadas al API REST deberán ir identificadas. Para ello, el usuario tiene que

poder darse de alta e iniciar sesión en el frontal de GuionMaker. Una vez se haya dado de alta

podrá realizar consultas sobre sus registros en el API, pero solo los suyos.

Por otro lado, habrá que revisar el componente encargado de recoger el texto de detalles

literarios y técnicos para incluir las nuevas funcionalidades reclamadas en la gestión de

encabezados y ayuda en escritura. Al final, el usuario debe percibir una mayor usabilidad y

seguridad a la hora de usar el producto, por lo que son los dos requisitos no funcionales clave

que habrá que contrastar en la posterior revisión.

6.3.1 Sprint backlog

Con motivo de la reunión llevada a cabo para definir la funcionalidad a implementar, se han

separado por características las historias de usuario asociadas a las mismas.

6.3.1.1 Gestión de biblia literaria

Historia de usuario: Modificar personaje

Funcionalidad Como usuario registrado Quiero poder acceder al detalle de un personaje De forma que se puedan modificar todas las propiedades asociadas con dicha entidad

Criterio de aceptación Dada la lista de personajes Cuando se accione el botón de vista Entonces se cargará la vista de detalle de personaje donde aparecerán todas las características asociadas al mismo

Conversación La nueva entidad tiene sus características vacías, por lo que será necesaria una vista de detalle para poder realizar cambios sobre ella. Lo mismo para las que están cumplimentadas, es interesante permitir su modificación.

Esfuerzo: 3 SP Valor: 35 UV Prioridad: Media

Page 77: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

73

Historia de usuario: Eliminar personaje

Funcionalidad Como usuario registrado Quiero poder eliminar un personaje De forma que su registro desaparezca de la lista de personajes de la biblia literaria en curso

Criterio de aceptación Dada la lista de personajes Cuando se accione el botón de eliminar sobre alguno de ellos Entonces el personaje desaparecerá de dicha lista y de la biblia literaria

Conversación Al igual que darlos de alta, el usuario tiene que poder eliminar personajes de su listado porque se hayan mostrado irrelevantes o inútiles para con la biblia literaria. Para ello la vista que se encarga de listar dichos elementos debe disponer de un botón para poder desencadenar la acción por cada personaje.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Media

Historia de usuario: Modificar escenario

Funcionalidad Como usuario registrado Quiero poder acceder al detalle de un escenario De forma que se puedan modificar todas las propiedades asociadas con dicha entidad

Criterio de aceptación Dada la lista de escenarios Cuando se accione el botón de vista Entonces se cargará la vista de detalle de escenario donde aparecerán todas las características asociadas al mismo

Conversación La nueva entidad tiene sus características vacías, por lo que será necesaria una vista de detalle para poder realizar cambios sobre ella. Lo mismo para las que están cumplimentadas, es interesante permitir su modificación.

Esfuerzo: 3 SP Valor: 35 UV Prioridad: Media

Page 78: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

74

Historia de usuario: Eliminar escenario

Funcionalidad Como usuario registrado Quiero poder eliminar un escenario De forma que su registro desaparezca de la lista de escenarios de la biblia literaria en curso

Criterio de aceptación Dada la lista de escenarios Cuando se accione el botón de eliminar sobre alguno de ellos Entonces el escenario desaparecerá de dicha lista y de la biblia literaria

Conversación Al igual que darlos de alta, el usuario tiene que poder eliminar escenarios de su listado porque se hayan mostrado irrelevantes o inútiles para con la biblia literaria. Para ello la vista que se encarga de listar dichos elementos debe disponer de un botón para poder desencadenar la acción por cada escenario.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Media

6.3.1.2 Gestión de encabezados

Historia de usuario: Mostrar sugerencias en el editor de texto

Funcionalidad Como usuario registrado Quiero que aparezcan sugerencias en el área de texto dedicada a las escenas De forma que la palabra actual se pueda autocompletar de entre una lista de escenarios o personajes de la biblia literaria asociada al proyecto

Criterio de aceptación Dado el editor de texto de la pantalla de edición de escenas Cuando se esté escribiendo una palabra Entonces se mostrarán los nombres de personajes o escenarios que coincidan con lo que hay escrito de palabra y se podrá autocompletar la misma.

Conversación Mientras que se está editando el texto de la escena será útil que nos muestra sugerencias con respecto a la palabra actual y que coincidan con los nombres de personajes o escenarios de la biblia literaria asociada al proyecto actual. A ser posible, estas sugerencias no deben ser demasiado intrusivas.

Page 79: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

75

Esfuerzo: 2 SP Valor: 175 UV Prioridad: Alta

Historia de usuario: Desactivar sugerencias en el editor de texto

Funcionalidad Como usuario registrado Quiero poder desactivar las sugerencias en el área de texto De forma que esta funcionalidad no esté disponible bajo mi criterio

Criterio de aceptación Dado el editor de texto de la pantalla de edición de escenas Cuando se accione el botón de silenciar sugerencias Entonces dejarán de mostrarse las mismas hasta que se vuelvan a activar

Conversación El área de texto debe dejar seleccionar al usuario si quiere mostrar o no sugerencias relacionadas con los personajes o escenarios de su biblia literaria con el objetivo de ser personalizable. En el caso en que no quiera, esta decisión se debe almacenar hasta que decida, de nuevo, quiera mostrar estas sugerencias.

Esfuerzo: 2 SP Valor: 75 UV Prioridad: Media

6.3.1.3 Ayuda en escritura

Historia de usuario: Insertar estilos de guion en el editor de texto

Funcionalidad Como usuario registrado Quiero seleccionar un estilo adecuado para el párrafo en que estoy trabajando en el editor de texto De forma que me sea sencillo distinguir entre las diferentes entidades existentes en la línea de diálogo: personaje, acotación y diálogo

Criterio de aceptación Dado el editor de texto de la pantalla de edición de escenas Cuando se seleccione uno de los estilos del desplegable o por atajo de teclado Entonces se aplicará uno de los estilos predefinidos sobre el párrafo actual

Conversación

Page 80: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

76

Mientras que se está editando el texto de la escena será útil disponer de un control a través del cual se pueda modificar el estilo del párrafo actual entre los diferentes estilos asociados por convención dentro de la escena. Si es posible y para agilizar la edición, sería interesante tener un atajo de teclado para dicha tarea.

Esfuerzo: 2 SP Valor: 100 UV Prioridad: Alta

Historia de usuario: Buscar acepciones de palabras en el editor de texto

Funcionalidad Como usuario registrado Quiero poder buscar las acepciones de una palabra concreta De forma que pueda saber el significado de una palabra mientras que estoy escribiendo en el detalle de la escena

Criterio de aceptación Dado el editor de texto de la pantalla de edición de escenas Cuando se seleccione una palabra y se ejecute la acción de búsqueda de acepciones Entonces se mostrará el significado de esa palabra en un diálogo emergente

Conversación Mientras que se está editando el texto de la escena puede ser que existan dudas con respecto a las acepciones de una palabra en concreto. Será posible el buscar su significado por medio de la acción de búsqueda de acepciones.

Esfuerzo: 3 SP Valor: 50 UV Prioridad: Baja

Historia de usuario: Buscar sinónimos de palabras en el editor de texto

Funcionalidad Como usuario registrado Quiero poder buscar los sinónimos de una palabra concreta De forma que pueda escribir una palabra sinónima a aquella que se seleccione

Criterio de aceptación Dado el editor de texto de la pantalla de edición de escenas Cuando se seleccione una palabra y se ejecute la acción de búsqueda de sinónimos Entonces se mostrarán los candidatos a sinónimos de esa palabra en el cliente

Page 81: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

77

Conversación Mientras que se está editando el texto de la escena puede ser que existan dudas con respecto a los sinónimos de una palabra en concreto. Será posible el buscar sus sinónimos por medio de la acción de búsqueda de sinónimos de tal manera que se pueda reemplazar por cualquiera de ellos.

Esfuerzo: 3 SP Valor: 75 UV Prioridad: Media

6.3.1.4 Gestión de usuarios

Historia de usuario: Iniciar sesión

Funcionalidad Como usuario anónimo Quiero poder iniciar sesión en la aplicación De forma que pueda hacer uso de las funcionalidades que me ofrece el portal

Criterio de aceptación Dada la pantalla de login de la aplicación Cuando se cumplimente el formulario de inicio de sesión y se accione el botón de login Entonces se redireccionará al usuario al frontal de la aplicación y podrá empezar a trabajar con sus proyectos

Conversación Cuando se acceda a la aplicación sin una sesión iniciada, el usuario deberá cumplimentar el formulario de inicio de sesión para poder trabajar en sus registros. Una vez se cumplimente, deberá validar la información suministrada contra el servidor accionando el botón de login. En caso afirmativo se le redireccionará al frontal y en caso contrario se le notificará que ha habido un error.

Esfuerzo: 2 SP Valor: 70 UV Prioridad: Alta

Historia de usuario: Cerrar sesión

Funcionalidad Como usuario registrado Quiero poder cerrar sesión en la aplicación De forma que pueda dar por concluido el trabajo realizado sobre la aplicación en un periodo de tiempo

Page 82: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

78

Criterio de aceptación Dada la barra de navegación de la aplicación Cuando se accione el botón de cerrar la sesión Entonces se eliminarán todos los datos asociados a la sesión y se devolverá la navegación a la pantalla de inicio de sesión

Conversación Una vez se dé por concluido el trabajo realizado sobre la aplicación, el usuario podrá cerrar su sesión con vistas a que, la próxima vez que quiera trabajar sobre sus registros, tendrá que iniciar sesión de nuevo y no se quedará la sesión abierta en el navegador en que ha hecho uso de ella.

Esfuerzo: 2 SP Valor: 25 UV Prioridad: Baja

Historia de usuario: Registrar usuario

Funcionalidad Como usuario anónimo Quiero poder crear una cuenta en la aplicación De forma que pueda iniciar sesión en la aplicación y empezar a trabajar con ella

Criterio de aceptación Dada la pantalla de registro Cuando se cumplimente el formulario de registro con los datos de la entidad usuario Entonces se validará el formulario, los datos introducidos y se procederá al registro de usuario

Conversación Cuando el usuario anónimo acceda al formulario de registro desde la pantalla de inicio de sesión, su tarea será rellenar cada uno de los campos que contenga como obligatorios. Cuando se rellenen los campos y se accione el botón de validar, deberá lanzarse un proceso de validación que compruebe que, entre otros, el email que se haya introducido sea único. Validado el formulario, el registro se llevará a cabo y el usuario podrá acceder a la aplicación con su nueva cuenta.

Esfuerzo: 2 SP Valor: 80 UV Prioridad: Alta

Page 83: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

79

6.3.2 Desarrollo del sprint

A nivel de entidades la nueva iteración no va a requerir de modificaciones en el diagrama de

clases, sin embargo, la implementación de esta iteración sí que va a llevar un importante

componente funcional con la autenticación de llamadas al API y la personalización del editor de

textos para añadirle valor competitivo a la solución.

Además de estos dos objetivos tan importantes durante esta iteración, será importante finalizar

también la gestión de escenarios y personajes. Una vez finalizada dicha gestión, se podrán hacer

pruebas sobre la carga de nombres y escenarios para las sugerencias en el editor de textos. Para

ello será necesario complementar el componente de lista genérica con un detalle genérico para

los elementos de la biblia literaria, teniendo en cuenta que sus propiedades pueden variar, pero

comparten la misma funcionalidad.

El siguiente paso por prioridades es la personalización del editor de textos para incluir estilos

personalizados y mostrar sugerencias relacionadas con los elementos de la biblia literaria

mientras se escribe. Implementar esto podría tener que llevar a modificar componentes del

núcleo del componente de edición de textos Summernote, pero lo ideal sería extenderlo con

plugins en el peor caso y, en el mejor de todos, hacer uso de su propia API.

Durante la edición de textos, es recurrente que los escritores tengan que hacer una breve

consulta a diccionarios en línea bien sea para sinónimos o para buscar la forma correcta de

escribir alguna palabra o bien para determinar si el significado de una palabra permite que ésta

se ubique en el contexto en que está. Para facilitar esta tarea, durante la iteración se va a insertar

un diálogo emergente accionable desde un botón que ocupará una posición fija en la pantalla.

En dicho diálogo emergente los usuarios podrán, bajo su criterio, buscar el significado de algunas

palabras nutriéndose de la RAE o, buscar el sinónimo de otras consultando sobre el sitio

WordReference.

Finalmente, y tras tres iteraciones, durante el desarrollo de ésta se tiene que implementar la

autenticación y registro de usuarios con JSON Web Token12. Con la inclusión de esta

funcionalidad ya nos tenemos que asegurar de autenticar las llamadas contra el API y no sólo

eso, sino también implementar la confidencialidad en los registros de cada usuario. A final de

esta iteración cada usuario sólo podrá acceder a sus registros y las peticiones al API que no

incluyan en la cabecera el token de autenticación serán rechazadas. Esto será así en todas las

llamadas salvo aquellas relacionadas con el registro y autenticación de usuarios.

12 JSON Web Token: https://jwt.io/introduction/

Page 84: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

80

Ilustración 28. Comunicación con la librería JWT para la generación de tokens de sesión desde el cliente.

Así pues, en términos de los elementos de nuestra arquitectura, tenemos que llevar a cabo las

siguientes implementaciones durante la tercera iteración:

API REST ExpressJS

o Inclusión de la librería JSON Web Token.

o Exigencia de cabecera de autenticación en los métodos del API modificando

para ello el fichero principal de la aplicación.

o Retocar la clase APIHelper para asegurar la confidencialidad de los registros de

usuario a partir del token de sesión.

o Importación de la librería jssha13 para pasar la función de resumen sobre las

contraseñas antes de almacenarlas en base de datos.

Cliente Angular2

o Creación de los componentes relacionados con la gestión de la biblia literaria,

detalles de escenarios, personajes y componente abstracto de detalle.

o Modificaciones sobre el componente Summernote para la inclusión de las

características que dan ventaja competitiva al proyecto: sugerencias y estilos

predefinidos.

o Inclusión de componente ayuda en la escritura para buscar el significado de

palabras o sinónimos.

o Implementación de los componentes relacionados con el registro y

autenticación de usuarios, así como su persistencia en el LocalStorage.

13 Jssha: https://github.com/Caligatio/jsSHA

Page 85: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

81

o Importar la librería jssha para comunicar al API las contraseñas pasadas por la

función de resumen SHA3-256.

6.3.3 Documentación técnica del sprint

Como se ha comentado en el apartado anterior, lo primero que se va a terminar de implementar

es la característica de gestión de biblia literaria. Tanto el detalle de personajes como el de

escenarios van a tener funcionalidades comunes, tales como guardar los cambios, el

comportamiento al intentar eliminar el registro, así como la interacción con el API. Por ello se

decide implementar una clase abstracta en la cual ese comportamiento común se refleje a nivel

de modelo, dejando a los diferentes elementos de la biblia literaria la responsabilidad de

heredar de dicho comportamiento y, en caso de que sea necesario, redefinir algunos

comportamientos.

export abstract class DetalleElementoBiblia implements DoCheck { elemento: any; entidadElemento: string; confirmacionGuardado: ConfirmacionGuardado; botonesGuardado: BotonesGuardado; fichero: Fichero; differ: any; ng2config: any; constructor(protected angularAPIHelper: AngularAPIHelper, protected localStorageService: LocalStorageService, protected route: ActivatedRoute, protected router: Router, entidadElemento: string, protected differs: KeyValueDiffers) { this.confirmacionGuardado = new ConfirmacionGuardado(); this.botonesGuardado = new BotonesGuardado(); this.botonesGuardado.mostrarCompleto(); this.fichero = new Fichero(); this.entidadElemento = entidadElemento; this.differ = this.differs.find({}).create(null); this.ng2config = { minHeight: 200, lang: 'es-ES', placeholder: 'Escribe tu texto...', toolbar: [ ['style', ['fontname', 'clear']], ['fontstyle', ['bold', 'italic', 'paragraph']], ['fontstyleextra', ['strikethrough', 'underline', 'hr', 'color', 'superscript', 'subscript']], ['extra', ['table', 'height']], ['misc', ['undo', 'redo', 'codeview']] ], fontNames: ['Courier New', 'Arial', 'Arial Black', 'Sans-serif', 'Serif'] }; this.route.params.switchMap((params: Params) => this.angularAPIHelper.getById(this.entidadElemento, params['id'])). subscribe(response => { this.cargarModelo(response as RespuestaJson); }); }

Page 86: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

82

abstract cargarModelo(respuesta: RespuestaJson); protected guardarCambios() {…} protected eliminarElemento() {…} ngDoCheck() { let changes = this.differ.diff(this.elemento); if (changes != undefined && changes._changesTail != undefined) { this.botonesGuardado.mostrarCompleto(false); } } }

Implementación del detalle genérico de elementos de la biblia literaria.

Como se puede apreciar, en dicha clase genérica se incluyen propiedades relevantes como el

nombre de la entidad que representa al elemento (escenarios o personajes), el elemento que se

va a vincular a la plantilla HTML sobre el que se podrán hacer cambios desde el cliente y una

propiedad fichero que se corresponde con el componente de utilidad de subida de imágenes.

Tanto personajes como escenarios permitirán la subida de una imagen para describir

gráficamente el elemento que se está creando.

Adicionalmente, y como novedad hasta ahora, se inyecta la dependencia KeyValueDiffers. Esta

clase forma parte de la librería del núcleo Angular2 y sirve para comprobar si existen cambios a

nivel de objeto desde el momento en que se carga la página. Cuando se realiza algún cambio,

dentro del ciclo de eventos de Angular214 se lanza el método ngDoCheck() de la interfaz DoCheck

que en este caso se encarga de cambiar el modal de guardado para que, en caso de que se dé al

botón de volver y se haya hecho algún cambio sobre el objeto, se avise al usuario de que hay

cambios en el elemento que se está editando. Esta inyección y este método se propaga hasta

todos los componentes de vista de detalle del proyecto para mejorar la usabilidad.

Las operaciones de lectura y escritura son comunes a todos los elementos de la biblia literaria,

sin embargo, la carga de modelos implica operaciones propias de cada elemento, por lo que se

marca como método abstracto y se delega la implementación a personajes y escenarios.

export class DetalleEscenarioComponent extends DetalleElementoBiblia { constructor(angularAPIHelper: AngularAPIHelper, localStorageService: LocalStorageService, route: ActivatedRoute, router: Router, differs: KeyValueDiffers) { super(angularAPIHelper, localStorageService, route, router, "escenario", differs); } cargarModelo(respuesta: RespuestaJson) { if (respuesta.estado == ResponseStatus.OK) { this.elemento = (respuesta as RespuestaJson).consulta[0] as EscenarioModel; this.elemento.ubicacion = this.elemento.ubicacion == undefined || this.elemento.ubicacion == "" ? new String('') : this.elemento.ubicacion;

14 Ciclo de vida en los componentes de Angular2: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html

Page 87: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

83

this.elemento.descripcion = this.elemento.descripcion == undefined || this.elemento.descripcion == "" ? new String('') : this.elemento.descripcion; this.fichero.base64 = this.elemento.imagen == undefined ? "" : this.elemento.imagen; this.fichero.mimeType = this.elemento.mimeType == undefined ? "" : this.elemento.mimeType; } } }

Implementación del detalle de escenarios, heredando del detalle genérico de elemento de biblia literaria.

Al heredar del detalle genérico tenemos que tener en cuenta que el constructor de clase debe

inyectar, al menos, las mismas dependencias del padre para luego poder hacer uso de ellas. Es

por eso que existe la llamada al constructor del padre con dichas dependencias. De esta forma,

dejamos trazabilidad de qué dependencias son las que nutren al detalle de elementos de la biblia

literaria.

Implementadas las clases de detalle y las plantillas tanto de personajes como escenarios

asociados a dichos componentes, lo siguiente es replicar la funcionalidad de eliminar al listado,

además de agregar los enlaces a la vista de detalle. Esta es una tarea sencilla teniendo en cuenta

que el listado, al igual que el detalle, también se nutre de un componente genérico que guarda

la referencia a la entidad de la que se están visualizando registros.

export class ListaGenericaComponent implements OnInit { […] setElementoAEliminar(elemento: any) { this.elementoAEliminar = elemento; } eliminarElemento() { this.angularAPIHelper.deleteById(this.listaGenerica.entidad, this.elementoAEliminar._id).subscribe(null, null, () => this.cargarElementos()); } onAccionGuardado(event) { if (event == TipoOperacionGuardado.Volver) { this.router.navigate(['/biblia']); } else if (event == TipoOperacionGuardado.Eliminar) { this.eliminarElemento(); } } }

Se añaden dos nuevos métodos en el componente de lista genérica y se modifica el método onAccionGuardado() haciendo referencia a eliminarElemento() que se encarga de hacer la llamada al API para la entidad con que se está trabajando.

Page 88: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

84

El siguiente paso es la edición del componente de edición de textos para incluir la funcionalidad

de mostrar sugerencias de elementos de la biblia literaria mientras se escribe en el detalle de

escenas.

Este es uno de los cambios sencillos puesto que Summernote es un editor bastante potente y

con una buena API que permite la inclusión de hints o sugerencias. Con tan solo modificar la

configuración con que se carga el componente incluyendo la referencia a la función de

sugerencias implementada por nosotros valdría.

this.ng2sconfig = { addclass: { debug: false }, hint: { elementosBiblia: [], activarSugerencias: this.activarSugerencias, match: /\b(\w{1,})$/, search: function (keyword, callback) { callback(jQuery.grep(this.elementosBiblia, (item) => { if (!this.activarSugerencias) { return false; } return item.toLowerCase().indexOf(keyword.toLowerCase()) === 0; })); } }, minHeight: 200, lang: 'es-ES', placeholder: 'Escribe tu texto...', toolbar: [ ['style', ['addclass', 'fontname', 'clear']], ['fontstyle', ['bold', 'italic', 'paragraph']], ['fontstyleextra', ['strikethrough', 'underline', 'hr', 'color', 'superscript', 'subscript']], ['extra', ['table', 'height']], ['misc', ['undo', 'redo', 'codeview']] ], fontNames: ['Courier New', 'Arial', 'Arial Black', 'Sans-serif', 'Serif'] };

Fragmento del constructor del componente detalle de escenas. La creación de este objeto de configuración define cómo se va a comportar el componente en la plantilla del detalle de escenas.

Omitiendo la propiedad addClass y yéndonos a la que nos interesa que es hint, en ella tenemos

en cuenta tres propiedades antes de la función que se encarga de mostrar las sugerencias en sí

(search). Estas propiedades son las de elementos de la biblia literaria, otra propiedad booleana

que determina si las sugerencias están activas o no y, finalmente, la expresión regular para las

palabras del texto introducido en Summernote. En este caso se va a ir cogiendo palabra por

palabra y se va a ejecutar la función search().

Page 89: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

85

La función search() es la encargada de, en caso de que estén activas las sugerencias, revisar si

algún elemento de array de elementos de la biblia literaria cumple con el principio de la palabra

que se está escribiendo en ese momento. En caso afirmativo, se mostrarán todos los elementos

que cumplen con la condición explicada.

Los elementos de la biblia literaria son inicializados como un array vacío que, posteriormente en

el constructor y durante la carga del modelo, se cargará en base al usuario y al proyecto en que

se está trabajando.

let peticion = this.angularAPIHelper.buildPeticion({ proyecto: this.escena.proyecto }, {}, "nombre proyecto"); this.angularAPIHelper.postEntryOrFilter('personajesPorFiltro', JSON.stringify(peticion)).subscribe(response => { let nombrePersonajes = (response as RespuestaJson).consulta; if (nombrePersonajes != undefined) { this.rellenarAutocompletar(nombrePersonajes); } }); this.angularAPIHelper.postEntryOrFilter('escenariosPorFiltro', JSON.stringify(peticion)).subscribe(response => { let nombreEscenarios = (response as RespuestaJson).consulta; if (nombreEscenarios != undefined) { this.rellenarAutocompletar(nombreEscenarios); } }); […] private rellenarAutocompletar(elementos: any[]) { for (let elemento of elementos) { this.ng2sconfig.hint.elementosBiblia.push(elemento.nombre); } }

Carga de los elementos de la biblia literaria para rellenar el array de sugerencias de elementos de la biblia literaria.

Una buena forma de mejorar el rendimiento en las consultas contra el API es construyendo las

consultas con una selección específica de las columnas que queremos de la entidad sobre la que

lanzamos la consulta. En este caso seleccionamos que queremos únicamente el nombre y el

proyecto al que pertenece la biblia literaria para asegurarnos que sólo salgan los personajes y

escenarios de ese proyecto. Además, evitamos que se cargue el objeto completo en caso de que

tenga alguna imagen con la demora en la respuesta que esto conlleva.

Todas las cargas conducen a un método llamado rellenarAutocompletar que escribe y apila

elementos en el array de elementos de la biblia literaria que hay en la configuración del

componente Summernote.

Por otro lado, en lo que se refiere a la personalización de estilos, Summernote no ofrece ninguna

facilidad para que el usuario o el desarrollador se encargue de realizar sus propios estilos para

la edición de textos. Tras una investigación profunda, se determina que en el API del

Page 90: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

86

componente no hay ninguna forma de realizar esto, por lo que se recurre al plugin que un

usuario de Github ha compartido con la comunidad (summernote-addclass15).

Para adaptarlo a nuestras necesidades, se realizan algunos cambios en el código del plugin a la

hora de aplicar los estilos. Dichos cambios son necesarios de acuerdo a nuestras restricciones

de estilo para seguir el formato de un guion audiovisual.

click: function (event, namespace, value) { event.preventDefault(); value = value || $(event.target).closest('[data-value]').data('value'); var $node = $(context.invoke("restoreTarget")) if ($node.length==0){ $node = $(document.getSelection().focusNode.parentElement, ".note-editable"); } if (typeof context.options.addclass !== 'undefined' && typeof context.options.addclass.debug !== 'undefined' && context.options.addclass.debug) { console.debug(context.invoke("restoreTarget"), $node, "toggling class: " + value, window.getSelection()); } if ($node.parent().prop("tagName") == "P") { $node.parent().attr("class", value); } if ("PFONT".indexOf($node.prop("tagName")) >= 0) { $node.attr("class", value); } }

Modificación aplicada sobre la función click de los elementos de la lista de estilos que se añade con el plugin addclass de Summernote.

El texto resaltado en negrita es la modificación realizada que limita la aplicación de estilos en el

plugin. Limita la asignación de la clase que se está eligiendo de entre el listado de opciones a

padres del elemento que son del tipo párrafo y a hijos que, o bien son párrafos o llevan la

etiqueta HTML Font. Anteriormente a este cambio se aplicaba el estilo en el primer párrafo no

sólo al primer párrafo si no a la caja del propio editor de textos.

context.options.addclass.classTags = [{ title: "Personaje", value: "txtTituloPersonaje" }, { title: "Acotación", value: "txtAcotacion" }, { title: "Diálogo", value: "txtDialogo" }, { title: "Normal", value: "txtNormal" }];

Sentencia de configuración del plugin addClass

15 Repositorio del módulo desarrollado por el usuario creativeprogramming: https://github.com/creativeprogramming/summernote-addclass

Page 91: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

87

En un guion existen 4 estilos predefinidos que es el del texto normal, el del diálogo, el de las

acotaciones y el nombre de los personajes. Es por eso que en el componente incluimos estos 4

estilos entre los que el usuario puede elegir para aplicárselo al texto. Dichos formatos varían en

la sangría del texto y, en el caso del título de personajes, si van en mayúsculas o no.

Como muestra de estilos y los márgenes que se deben guardar en los guiones, tenemos la

ilustración número 22. Dichas medidas, en pantalla ha habido que trasladarlas a porcentajes

para tratar de mantener la disposición de los elementos lo más fiel posible a la realidad.

Traducidas a CSS, éstas son las clases que les corresponden a cada uno de los estilos:

.txtTituloPersonaje { margin-left: 42.86%; text-transform: uppercase; } .txtAcotacion { margin-left: 35.71%; } .txtDialogo { margin-left: 28.57%; max-width: 40%; word-wrap: break-word; } .txtNormal { margin-left: 16.7%; }

Estilos para las diferentes clases de concepto de los guiones.

Page 92: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

88

Ilustración 29. Medidas y estilos del guion durante la fase de escritura.

El resultado de incluir este plugin en la configuración de Summernote se puede visualizar en la

edición de textos de detalles literarios y técnicos de las escenas del proyecto en que estemos

trabajando:

Page 93: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

89

Ilustración 30. Muestra del nuevo control de inserción de estilos y estos aplicados a un texto de prueba.

Ilustración 31. Muestra de las sugerencias que nos salen al escribir el detalle literario de una escena con las sugerencias activas. En este caso nos permite autocompletar el personaje “Super Mario”.

Con la inclusión de estos estilos predefinidos además del autocompletar de escenarios y

personajes, el usuario solo se tendrá que preocupar de escribir puesto que los estilos seguirán

el estándar con que acostumbran a trabajar los guionistas. Adicionalmente tendrán a todos sus

personajes y escenarios a golpe de teclado.

Cumplido el objetivo de terminar con la característica de gestión de biblia literaria y empezada

la característica de ayudas en la escritura con la gestión de encabezados, el siguiente paso es

aportar ayuda lingüística a los usuarios mientras escriben.

Tras analizar diferentes alternativas para ofrecer esta funcionalidad al usuario, se decide que se

va a generar un diálogo emergente donde podrán, o bien escribir una palabra para buscar su

significado o, por otro lado, escribir una palabra y buscar sus sinónimos. En un primer momento

se decide que al darle al botón de búsqueda de cada acción se abra un iframe haciendo una

petición contra el diccionario de la RAE y WordReference.

Sin embargo, como la aplicación se aloja en un servidor que soporta HTTPS y tanto la RAE como

WordReference no tienen sus servidores preparados para HTTPS, los navegadores web evitarían

la carga de las peticiones HTTP contra la RAE y WordReference en iframes. Por este motivo, se

decide rechazar esta aproximación y se pasa a la idea de abrir una nueva ventana con la petición

ya elaborada con la palabra que estaban buscando. En el futuro se podría valorar descargar el

HTML de la respuesta a estas páginas o bien contratar los servicios de alguna API específica.

De esta forma se implementa el componente de ayuda de diccionarios bajo el módulo de útiles,

componente que se crea para ser reutilizado en distintas vistas de la aplicación: detalle de

escenas, detalle de personajes o escenarios. La vista de este componente es la de un botón que

Page 94: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

90

permanece fijo en una posición de la pantalla y abre un modal de Bootstrap que incluye la

funcionalidad mencionada.

export class AyudaDiccionariosComponent { palabraAcepcion: string; palabraSinonimo: string; iniURLRAE: string = "http://dle.rae.es/srv/search?w={0}&m=10"; iniURLWR: string = "http://www.wordreference.com/sinonimos/{0}"; cambiarURLAcepciones() { window.open(this.iniURLRAE.replace("{0}", this.palabraAcepcion)); } cambiarURLSinonimos() { window.open(this.iniURLWR.replace("{0}", this.palabraSinonimo)); } }

Clase del componente de ayuda de diccionarios que se encarga de abrir una nueva ventana con la búsqueda en los diccionarios que los usuarios deseen.

Ilustración 32.Vista del componente de ayuda de diccionarios lingüísticos.

Abordados los cambios y añadidos anteriores, el siguiente paso es quizás el más tedioso de la

iteración, pero, a su vez, el más importante. Queremos dotar de seguridad al proyecto con la

autenticación de usuarios y llamadas al API, por lo que nos tenemos que centrar ahora en la

parte de servidor.

Como hemos comentado en el apartado anterior, se ha elegido JWT como la librería que va a

generar el token cifrado de sesión para los usuarios de la aplicación, pero no solo eso, también

se va a asegurar de que aquellas rutas marcadas como seguras sean accesibles sólo cuando el

usuario tenga un token válido de sesión incluido en la cabecera de la petición. Además, para la

integración de JWT con el API en ExpressJS se incluye la librería express-jwt16.

Importadas las librerías de utilidad para el API, la clave para empezar a autenticar las llamadas

está en el método en que se establecen las rutas.

private setRoutes(): void { […] this.api.use(jwtes({ "secret": this.config.secreto }).unless({ path: ['/api/usuario/login', '/api/usuario', /^(?!\/api).+/ ] }));

16 Repositorio de la librería express-jwt: https://github.com/auth0/express-jwt

Page 95: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

91

[…]

Método de establecimiento de rutas con el añadido de la librería express-jwt para forzar autenticación en las rutas

Con una sola sentencia haciendo uso de la librería express-jwt estamos haciendo que todas las

rutas, salvo la de login o registro de usuarios y aquellas que no empiecen por /api, requieran de

una cabecera de autenticación. Además, especificamos la clave secreta de la que vamos a hacer

uso para cifrar los tokens de sesión generados con JWT. Dicha clave se especifica en el momento

en que se inicia el API, por lo que solo pertenece al servidor y el cliente no tiene acceso a ella.

Hasta aquí es sencillo, pero la siguiente derivada es la de otorgar de confidencialidad a los

registros de usuario, es decir, tenemos que hacer que, a través del API, el usuario solo pueda

consultar registros que haya creado él. A falta de la gestión de roles para permitir que puedan

ver aquellos registros de los que son colaboradores, se han añadido métodos clave en la clase-

ruta de los proyectos.

public static crearFiltroAutor(req: express.Request): PeticionJson { let filtro = new PeticionJson(); filtro.find = { "autor": req.user.usuarioLogeado }; return filtro; } public static crearFiltroProyecto(req: express.Request) { let filtro = new PeticionJson(); filtro.populate = "proyecto"; filtro.populateFind = { "autor": req.user.usuarioLogeado }; return filtro; } public static alterarFiltroConProyecto(req: express.Request) : express.Request { let filtro = ProyectoRoute.crearFiltroProyecto(req); req.body.populate = filtro.populate; req.body.populateFind = filtro.populateFind; return req; }

Fragmento de la clase-ruta proyecto y sus nuevos métodos para consultar la base de datos MongoDB.

Estos nuevos métodos incluyen más propiedades en el filtro de búsqueda que se le pasa a través

de la petición al API. Esta propiedad la hemos llamado populate debido a que es el método que

utiliza Mongoose para hacer algo parecido a los join de SQL. Al nuevo campo populate se le

asigna el literal proyecto puesto que es la propiedad que comparten todos los elementos

pertenecientes a un proyecto y es la única manera de trazar su pertenencia a los mismos.

Adicionalmente, se le puede añadir un filtro a populate para que, una vez sacado el proyecto al

que pertenece el registro, se determine si el autor se corresponde con el usuario que viene a

través del token de sesión o no.

Express-JWT añade en las peticiones una propiedad “usuario” para aquellas rutas en que

precisamos de autorización. Dicha propiedad almacena lo que se haya guardado en el token de

Page 96: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

92

sesión. En nuestro caso, estos son los esquemas con que se comunican API y cliente en lo

referente a inicios de sesión:

export class Utils { static firmarTexto(texto: string): string { let oJssha = new jssha("SHA3-256", "TEXT"); oJssha.update(texto); return oJssha.getHash("HEX"); } } export class PeticionLogin { nombreUsuario: string; pass: string; constructor(nombreUsuario: string = "", pass: string = "") { this.nombreUsuario = nombreUsuario; this.pass = pass; } } export class RespuestaLogin { tokenUsuario: string; usuarioLogeado: string; constructor(tokenUsuario: string, usuarioLogeado: string) { this.tokenUsuario = tokenUsuario; // token de usuario generado por JWT this.usuarioLogeado = usuarioLogeado; } }

Código perteneciente al modelo de utilidades creado en ExpressJS

Dichos esquemas pertenecen al modelo Utils creado para la ocasión. Así mismo, se incluye la

función estática firmarTexto que se encarga de generar el resumen de una cadena con SHA3 de

256 bits haciendo uso de jssha.

La inclusión de estas funciones en la clase-ruta de los proyectos será utilizada por el resto de

métodos de las rutas para ajustar los filtros con que se realizan las consultas en el APIHelper. De

esta manera, nos aseguramos que los usuarios solo reciben resultados de aquellos registros que

son de su propiedad.

export class PersonajeRoute { […] public getPersonajes(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getAll(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public getPersonajeById(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getById(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public addPersonaje(req: express.Request, res: express.Response, next: express.NextFunction) {

Page 97: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

93

APIHelper.add(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public deletePersonaje(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.delete(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public getPersonajesByFilterAndSort(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getByFilterAndSort(PersonajeRoute.model, ProyectoRoute.alterarFiltroConProyecto(req), res); } } Ejemplo de cómo afecta la reingeniería de las clases-ruta en los métodos de la clase-ruta de los personajes.

Como consecuencia de estos cambios, se está llamando al APIHelper con un parámetro adicional

que es el filtro que habrá de tenerse en cuenta en los métodos de inserción, consulta,

modificación y eliminación sobre MongoDB por medio de Mongoose.

model.find(filtro.find).populate({ path: filtro.populate, match: filtro.populateFind }).select(filtro.select).exec(obtenerPorId);

Búsquedas modificadas del middleware de Mongoose, incluyendo el nuevo filtro populate en el APIHelper.

Por tanto, junto al filtro de la petición se agrega el populate con el proyecto y el match que busca

si el autor es igual al usuario que realiza la petición. Dicha petición sigue el mismo flujo de

siempre en función de si arroja resultados o no con la única salvedad de que ahora es más

restrictivo y, como resultado, más confidencial.

Centrándonos en el proceso de identificación de usuarios, para generar un token de sesión en

función de si el usuario y contraseña existen o no en la base de datos, se implementa un nuevo

método login dentro de la clase-ruta de usuarios.

public login(req: express.Request, res: express.Response, next: express.NextFunction) { if (req.body != undefined) { UsuarioRoute.model.find({ 'nombreUsuario': req.body.nombreUsuario, 'pass': req.body.pass }).exec(function (err, _res) { if (err) { res.json(APIHelper.buildJsonError("Ha habido un error iniciando sesión para el usuario: " + req.body.nombreUsuario)); } else { if (_res.length == 1) { req.body.usuarioLogeado = _res[0]._id; let respuestaLogin = new RespuestaLogin(jsonwebtoken.sign(req.body, "g423gj8f_GfsldGLPxcz"), _res[0]._id); res.json(APIHelper.buildJsonLogin(respuestaLogin)); } else {

Page 98: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

94

res.json(APIHelper.buildJsonError("No se ha podido iniciar sesión con el usuario " + req.body.nombreUsuario)); } }}); }}

Método de login de la clase-ruta de usuarios.

En él se hace uso del modelo usuario para buscar por nombre de usuario y contraseña si existen

coincidencias en la colección de usuarios de MongoDB. En caso afirmativo, se procede a coger

el ID del usuario resultado de la consulta y se genera el objeto JSON de respuesta de inicio de

sesión. Dicho objeto contendrá el ID del usuario y el token cifrado que genera y cifra JWT

haciendo uso del secreto de la configuración. Si hubiera cualquier error en el proceso, se

devolvería el JSON de error correspondiente.

Al final, después de estas modificaciones, la especificación del API queda como sigue después

de esta iteración:

Método Ruta Parámetros Auth Respuesta GET /api/escenas - Sí JSON

(RespuestaJSON) POST /api/escenasPorFiltro JSON

(PeticionJson) Sí JSON

(RespuestaJSON) POST /api/escena/ JSON (Escena) Sí JSON

(RespuestaJSON) GET /api/escena/:id String(Id) Sí JSON

(RespuestaJSON) DELETE /api/escena/:id String(Id) Sí JSON

(RespuestaJSON) GET /api/detallesTecnicos - Sí JSON

(RespuestaJSON) POST /api/detalleTecnico/ JSON (Det.Tec.) Sí JSON

(RespuestaJSON) GET /api/detalleTecnico/:id String(Id) Sí JSON

(RespuestaJSON) DELETE /api/detalleTecnico/:id String(Id) Sí JSON

(RespuestaJSON) GET /api/detallesLiterarios - Sí JSON

(RespuestaJSON) POST /api/detalleLiterario/ JSON (Det.Lit.) Sí JSON

(RespuestaJSON) GET /api/detalleLiterario/:id String(Id) Sí JSON

(RespuestaJSON) DELETE /api/detalleLiterario/:id String(Id) Sí JSON

(RespuestaJSON) GET /api/plantillas - Sí JSON

(RespuestaJSON) POST /api/plantillasPorFiltro JSON

(PeticionJson) Sí JSON

(RespuestaJSON) POST /api/plantilla/ JSON

(Plantilla) Sí JSON

(RespuestaJSON) GET /api/plantilla/:id String(Id) Sí JSON

(RespuestaJSON) DELETE /api/plantilla/:id String(Id) Sí JSON

(RespuestaJSON) GET /api/proyectos - Sí JSON

(RespuestaJSON) POST /api/proyectosPorFiltro JSON

(PeticionJson) Sí JSON

(RespuestaJSON)

Page 99: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

95

POST /api/proyecto/ JSON (Proyecto) Sí JSON (RespuestaJSON)

GET /api/proyecto/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/proyecto/:id String(Id) Sí JSON (RespuestaJSON)

GET /api/colaboraciones - Sí JSON (RespuestaJSON)

POST /api/colaboracionesPorFiltro

JSON (PeticionJson)

Sí JSON (RespuestaJSON)

POST /api/colaboracion/ JSON (Colaboracion)

Sí JSON (RespuestaJSON)

GET /api/colaboracion/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/colaboracion/:id String(Id) Sí JSON (RespuestaJSON)

GET /api/escenarios - Sí JSON (RespuestaJSON)

POST /api/escenariosPorFiltro JSON (PeticionJson)

Sí JSON (RespuestaJSON)

POST /api/escenario/ JSON (Escenario)

Sí JSON (RespuestaJSON)

GET /api/escenario/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/escenario/:id String(Id) Sí JSON (RespuestaJSON)

GET /api/personajes - Sí JSON (RespuestaJSON)

POST /api/personajesPorFiltro JSON (PeticionJson)

Sí JSON (RespuestaJSON)

POST /api/personaje/ JSON (Personaje)

Sí JSON (RespuestaJSON)

GET /api/personaje/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/personaje/:id String(Id) Sí JSON (RespuestaJSON)

GET /api/clasificaciones - Sí JSON (RespuestaJSON)

POST /api/clasificacion/ JSON (Clasificacion)

Sí JSON (RespuestaJSON)

GET /api/clasificacion/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/clasificacion/:id String(Id) Sí JSON (RespuestaJSON)

GET /api/generos - Sí JSON (RespuestaJSON)

POST /api/genero/ JSON (Clasificacion)

Sí JSON (RespuestaJSON)

GET /api/genero/:id String(Id) Sí JSON (RespuestaJSON)

DELETE /api/genero/:id String(Id) Sí JSON (RespuestaJSON)

POST /api/usuario JSON(Usuario) No JSON (RespuestaJSON)

POST /api/usuario/login JSON (PeticionLogin)

No JSON (RespuestaLogin)

Page 100: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

96

Con esto ya estaría todo listo desde el lado del servidor para atender las peticiones desde el

cliente de Angular2, en el cual habrá que hacer unos ajustes en busca de agregar cabeceras de

autenticación en sus peticiones contra el API y, por supuesto, mantener el objeto de respuesta

de inicio de sesión en lo que dure la sesión del usuario que está trabajando con la aplicación.

Para ello, lo primero será trabajar con el AngularAPIHelper que actúa como intermediario en las

llamadas al API de ExpressJS. En él nos tenemos que asegurar que en todas las peticiones se

añade la cabecera “Authorization: Bearer {token jwt}” correspondiente para que, desde

ExpressJS, se nos atienda la petición y no nos devuelva un error HTTP 401.

export class AngularAPIHelper […] usuarioLogeado(localStorageService: LocalStorageService) { let usuario = localStorageService.getPropiedad('usuarioLogeado'); let token = localStorageService.getPropiedad('tokenUsuario'); return token != undefined && usuario != undefined; } […] crearCabeceraAuth() : RequestOptions { let requestOptions: RequestOptions = null; let token = localStorage.getItem('tokenUsuario'); if (token != undefined){ let headers: Headers = new Headers({ 'Authorization': 'Bearer ' + token }); requestOptions = new RequestOptions({ headers: headers }); } return requestOptions; } […] this.http.get/post/delete(AngularAPIHelper.URL + entity + "/" + id, this.crearCabeceraAuth()).map(response => this.parse(response.text())).catch(this.handleError); // notación abreviada para los métodos de http […]

Fragmento del servicio AngularAPIHelper

Se puede observar que el token de sesión está almacenado en el localStorage del navegador, de

tal forma que cuando se van a realizar llamadas contra el API, se echa mano de la propiedad que

lo almacena y se lleva a la cabecera de las peticiones para asegurarse de que todo va

correctamente.

El momento en que este token de sesión se almacena en el localStorage es en el inicio de sesión,

componente que se ha tenido que crear para la ocasión. La vista de dicho componente es un

formulario con dos campos, usuario y contraseña, que rellenos y enviados al servidor, llevan a

cabo el flujo explicado a lo largo de estas líneas.

Cabe destacar que, al contrario del resto de formularios, en este caso sí que obligamos al usuario

a introducir tanto usuario como contraseña antes de realizar ninguna petición. Para conseguir

esto, se ha echado mano de la librería @angular/forms y sus componentes FormGroup,

FormBuilder y Validators. Una vez importados estos componentes, para meter la condición de

requerido en las propiedades del modelo, debemos crear un FormGroup por medio del

Page 101: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

97

FormBuilder y, a la hora de incluir los campos, indicar que estos son obligatorios por medio de

Validators.

export class LoginComponent { peticion: PeticionLogin; formLogin: FormGroup; error: boolean; constructor(private angularAPIHelper: AngularAPIHelper, private localStorageService: LocalStorageService, private router: Router, private fb : FormBuilder) { if (this.angularAPIHelper.usuarioLogeado(this.localStorageService)) { this.router.navigate(['/']); } this.formLogin = this.fb.group({ 'nombreUsuario': [null, Validators.required], 'pass': [null, Validators.required] }); this.peticion = new PeticionLogin(); } onLogin() { let hashedPass = Utils.firmarTexto(this.peticion.pass); let peticionHasheada = new PeticionLogin(this.peticion.nombreUsuario, hashedPass); this.angularAPIHelper.postEntryOrFilter('usuario/login', JSON.stringify(peticionHasheada)).subscribe(response => { let respuesta = response as RespuestaJson; if (respuesta.estado == ResponseStatus.OK) { let respuestaLogin = respuesta.login as RespuestaLogin; this.localStorageService.setPropiedad('usuarioLogeado', respuestaLogin.usuarioLogeado); this.localStorageService.setPropiedad('tokenUsuario', respuestaLogin.tokenUsuario); this.router.navigate(['/']); } else { this.error = true; } }); } }

Clase del componente de inicio de sesión

Para que todo esto se refleje en la vista, hay que incluir directivas específicas de

@angular/forms. Estas directivas también permitirán añadir elementos gráficos que ayuden al

usuario a comprender que esos campos son obligatorios y deben rellenarse en pos de poder

activar el botón para realizar la petición.

<form (ngSubmit)="onLogin()"> […]

Page 102: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

98

<td><label for="usuario">Usuario <i class="glyphicon glyphicon-asterisk" *ngIf="!formLogin.controls['nombreUsuario'].valid"></i></label></td> <td><input type="text" [(ngModel)]="peticion.nombreUsuario" name="usuario" id="usuario" class="form-control" [formControl]="formLogin.controls['nombreUsuario']" /></td> </tr> <tr> <td><label for="pass">Contraseña <i class="glyphicon glyphicon-asterisk" *ngIf="!formLogin.controls['pass'].valid"></i></label></td> <td><input type="password" [(ngModel)]="peticion.pass" name="pass" id="pass" class="form-control" [formControl]="formLogin.controls['pass']" /></td> </tr> <tr> <td colspan="2"> <button type="submit" class="btn btn-primary" [disabled]="!formLogin.valid">Iniciar sesión</button> […] </form>

Vista del componente de inicio de sesión

Entre esas directivas podemos ver formControl para asignar a qué control del formulario que

hemos creado pertenece el campo. Además, tendremos disponible el grupo formLogin que

hemos instanciado en la clase en caso de que necesitemos comprobar que los controles pasan

la validación o no. En este caso no habilitamos el botón de inicio de sesión hasta que el

formulario cumpla con todas las validaciones que hemos introducido con la directiva disabled

(todos los campos obligatorios).

Una vez el cliente inicia sesión satisfactoriamente, desde el API recibirá el OK y un token que

debe almacenar en el localStorage a lo largo de todas las peticiones de la sesión que tiene

abierta. Finalmente, cuando el usuario dé por concluida su sesión, podrá cerrarla por medio de

un enlace en el menú de la cabecera de la aplicación.

El cierre de sesión se consigue por medio del borrado de todas las propiedades del localStorage

y, adicionalmente, redirigir al usuario a la pantalla de inicio de sesión. Este método estará

alojado en el mismo componente que muestra el menú de navegación y tiene el router-outlet:

app.

export class AppComponent […] cerrarSesion() { this.localStorageService.borrar(); this.router.navigate(['/login']); } }

Método de cierre de sesión en el componente principal de la aplicación

En último lugar, la creación de usuarios se hace a través de un nuevo componente muy similar

al de login en términos de validación y de la inyección del FormBuilder. El usuario deberá tener

en cuenta a la hora de registrarse que habrá dos campos con índice único como son el del

nombre de usuario y el de email, por lo que, si envía al servidor una dirección de email repetida

o un nombre de usuario repetido, el cliente le devolverá un error.

Page 103: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

99

export class Usuario { […] this.schema.index({ nombreUsuario: 1 }, { unique: true }); this.schema.index({ email: 1 }, { unique: true }); }

Índices en el esquema del usuario en el API de ExpressJS.

6.3.4 Revisión y retrospectiva del sprint

Implementado lo correspondiente a las historias de usuario de la tercera iteración y probados

los criterios de aceptación de cada una de ellas, se decide mostrar los avances al cliente.

Tras revisar cada una de las historias de usuario con el cliente, hay satisfacción en lo que se ha

implementado, pero se observan algunos puntos a mejorar relacionados con el registro de

usuario y el cómo carga el cliente de Angular2 en Internet Explorer.

En concreto en el registro de usuario, como los campos están asociados al modelo, cuando se

pulsa el botón de registrar usuario que lanza la petición contra el API se ve cómo el campo de

contraseña se modifica al pasarle la función de resumen. Es por eso que se decide ocultar el

panel mientras que se está guardando y se espera una respuesta del API.

El cambio para que el cliente se muestre bien en Internet Explorer requiere de un poco más de

trabajo puesto que hay que cambiar el target en que se compilan los ficheros fuente en

TypeScript. Actualmente Internet Explorer no tiene soporte completo para ES6 y hay muchas

funciones del núcleo de Angular2 que requieren de dicho estándar. Por este motivo, con

mantener la referencia a las librerías de ES6 y marcando como objetivo de compilación la versión

ES5 basta. Una vez hecho este cambio se comprueba que todo funciona correctamente y se dan

por concluidas las incidencias de la iteración.

¿Qué es lo que se ha hecho bien?

o Ante la disyuntiva de cómo afrontar correctamente la ayuda en escritura en

términos de búsqueda de significados o sinónimos, se ha negociado con el

cliente una solución que satisfacía a las dos partes en tiempos, dejando la puerta

abierta a una posible mejora en el futuro.

o El uso de clases abstractas en clases de componentes del cliente favorece la

reutilización y la rapidez con que se implementan componentes en el cliente de

Angular2.

¿Qué se podría mejorar?

o Sigue estando pendiente la automatización de pruebas sobre los diferentes

métodos de las clases-ruta del API del que se nutre el cliente de Angular2.

o Alguna herramienta como Swagger para la especificación del API podría estar

interesante de cara al desarrollo de más clientes en otras plataformas y/o

tecnologías.

¿Qué es lo que se ha hecho mal?

o Así como en el resto de las iteraciones las estimaciones habían sido holgadas,

en este caso para la gestión de usuarios no se tuvo en cuenta que había que

realizar algunas modificaciones sustanciales sobre la clase auxiliar del API.

Page 104: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

100

6.3.5 Resultados visuales en el cliente durante el sprint

Durante esta iteración, aparte de lo mostrado en el apartado de documentación técnica con el

mostrar sugerencias en el editor de texto y el apoyo lingüístico, se ha terminado de implementar

el listado y el detalle de los elementos de la biblia literaria. Tanto el listado de personajes como

el de escenarios es el mismo y sigue la misma filosofía que los listados anteriores.

Ilustración 33. Ejemplo de uno de los listados de los elementos de la biblia literaria.

Con el detalle de escenarios y personajes sucede lo mismo, con la salvedad de que tienen

diferentes campos y, por tanto, diferente formulario. Sin embargo, la estética sigue siendo la

misma para ambos, de hecho, ambos tienen la posibilidad de subir una imagen al igual que el

detalle técnico de las escenas.

Ilustración 34. Vista de edición de los personajes, con la posibilidad de subir una imagen del personaje.

Page 105: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

101

Pero ésas no son las únicas vistas que se han desarrollado durante esta iteración. También se

han implementado aquellas relacionadas con el registro y la autenticación de usuarios. Tal y

como se ha comentado en la sección de la documentación técnica, hasta que no se inserten de

forma válida cada uno de los datos del formulario de registro, el botón de registro no se activa.

En el caso en que utilicemos un nombre de usuario o un email ya en uso, también se nos avisará

con una alerta por encima del formulario.

Ilustración 35. Vista de registro con todos los campos del formulario por rellenar.

El mismo criterio se ha seguido con el inicio de sesión, con dos campos obligatorios y que, hasta

que no se cumplimenten no se activará el submit. Si la combinación de usuario y contraseña no

se encuentra en la base de datos, se le mostrará al usuario una alerta similar a la que se muestra

en el registro de nuevos usuarios. Por el contrario, si proporcionamos unas credenciales válidas

nos redireccionará al inicio de GuionMaker, donde podremos fijar el proyecto y empezar a

trabajar.

Ilustración 36. Vista de inicio de sesión erróneo.

Page 106: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

102

6.3.6 Análisis de valor entregado y esfuerzo desempeñado

Junto a la revisión y retrospectiva cualitativas de la iteración, nuevamente llega el momento de

analizar cómo de constante ha sido nuestra entrega de desempeño y cómo el reparto de valor

de negocio se ha ido distribuyendo durante las diferentes semanas que ha durado la iteración.

Gráfica de desempeño de esfuerzo

Ilustración 37. En este diagrama se puede ver que la entrega de esfuerzo es casi lineal a excepción de dos semanas en que apenas se progresó en la entrega de historias de usuario.

Gráfica de entrega de valor acumulativo

Ilustración 38. Esta gráfica está bastante más sesgada que la anterior, mostrando que la mayor parte del valor entregado (referente a funcionalidades de autenticación de usuarios) se realizó en la mitad de la iteración.

0

5

10

15

20

25

30

19

/03

/20

17

21

/03

/20

17

23

/03

/20

17

25

/03

/20

17

27

/03

/20

17

29

/03

/20

17

31

/03

/20

17

02

/04

/20

17

04

/04

/20

17

06

/04

/20

17

08

/04

/20

17

10

/04

/20

17

12

/04

/20

17

14

/04

/20

17

16

/04

/20

17

18

/04

/20

17

20

/04

/20

17

22

/04

/20

17

SP (

pu

nto

s d

e h

isto

ria)

Fechas

Burndown chart de la iteración 3

0

100

200

300

400

500

600

700

800

19

/03

/20

17

21

/03

/20

17

23

/03

/20

17

25

/03

/20

17

27

/03

/20

17

29

/03

/20

17

31

/03

/20

17

02

/04

/20

17

04

/04

/20

17

06

/04

/20

17

08

/04

/20

17

10

/04

/20

17

12

/04

/20

17

14

/04

/20

17

16

/04

/20

17

18

/04

/20

17

20

/04

/20

17

22

/04

/20

17

UV

(u

nid

ades

de

valo

r d

e n

ego

cio

)

Fechas

Value up chart de la iteración 3

Page 107: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

103

Gráfica de estados de historias de usuario

Ilustración 39. Cuenta creciente en entregas y resolución de historias de usuario en la que se observa como a mitad de iteración había bastante funcionalidad en pruebas durante un buen tiempo antes de darse por concluida.

La lectura de estas gráficas deja entrever que ha sido una iteración en la que el esfuerzo se ha

repartido de manera constante sin altibajos. Esto mejora mucho lo que en otras iteraciones se

venía haciendo, sobre todo en la primera.

Por otro lado, se observa que la prioridad ha ido en base al valor de negocio de las historias de

usuario pertenecientes a la tercera iteración. Es por eso que la mayor cantidad de valor de

negocio se entrega prácticamente a la mitad de la iteración, dejando el resto de la misma para

la entrega de historias de usuario que no aportan tanto valor a la solución.

Otra lectura llamativa en el gráfico de estados de historias de usuario es cómo la mayor distancia

entre historias de usuario resueltas y cerradas se encuentra en la mitad, momento en que se

estaba implementando la autenticación de usuarios. Esto denota que ha sido desafiante y ha

llevado mucho tiempo de pruebas hasta que se ha dado por resuelto una vez todo estaba dentro

de los criterios de aceptación.

Con todo esto, se puede concluir que todavía hay margen para mejorar en la entrega de esfuerzo

y agilidad en el proceso de implementación ahora que las funcionalidades críticas ya están

implementadas.

6.4 Cuarto sprint

Según la planificación, la cuarta iteración sería suficiente para cubrir toda la funcionalidad del

producto que se ha ido desarrollando a lo largo de estas líneas. Sin embargo, se ha acordado

con el cliente postergar parte de la funcionalidad de esta iteración para realizar tareas de

ingeniería de software en la aplicación web como la elaboración de pruebas automatizadas y

crear una especificación completa con Swagger17 puesto que uno de los atractivos del proyecto

17 Swagger.IO, herramienta de especificación de APIs: http://swagger.io/

Page 108: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

104

es que se puedan crear clientes en más plataformas simplemente haciendo uso del API en

ExpressJS.

En lo referente a funcionalidad, se ha acordado implementar parte de la gestión de roles,

permitiendo a los usuarios añadidos como colaboradores dentro de un proyecto el ver los

registros del proyecto al que están asociados. También se va a proceder a añadir la capa de

personalización de la exportación de guiones con la gestión de plantillas y, además, parte de la

ayuda fuera de escritura con la introducción de un componente de miga de pan para permitir al

usuario tener una trazabilidad en su navegación.

Como siempre, en el Sprint Backlog de este apartado se capturan todos estos requisitos en

forma de historias de usuario separadas por características.

6.4.1 Sprint backlog

6.4.1.1 Gestión de roles

Historia de usuario: Listar contenido asociado a proyectos en que se es colaborador

Funcionalidad Como usuario registrado Quiero poder listar contenido asociado a proyectos en que soy colaborador De forma que pueda ver el contenido de los diferentes proyectos en que se me ha incluido como colaborador

Criterio de aceptación Dado una vista de listado de proyectos, elementos de biblia literaria y escenas Cuando se acceda a una de dichas vistas Entonces se deben mostrar los registros de proyectos en que estoy incluido como colaborador con una marca en ellos

Conversación Una de las ventajas de GuionMaker es que aspira a dejar a los usuarios participar colaborativamente en los proyectos de tal forma que se pueda trabajar en registros que no son estrictamente propios y a los que nos invitan a participar. Con la inclusión de esta historia de usuario, en el listado de proyectos debemos ser capaces de ver los registros de aquellos proyectos en que estamos marcados como colaboradores para fijarlos. Cuando se ha fijado el proyecto en que se quiere trabajar, con esta funcionalidad seremos capaces de ver el contenido de los proyectos, pero no editarlos

Esfuerzo: 2 SP Valor: 60 UV Prioridad: Media

Historia de usuario: Editar contenido en proyectos en que se tiene permiso

Funcionalidad

Page 109: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

105

Como usuario registrado Quiero poder editar el contenido de proyectos en que soy colaborador de escritura De forma que pueda aportar valor como colaborador al proyecto en que se me han confiado dichos permisos

Criterio de aceptación Dada la vista de edición de elementos de proyecto Cuando se acceda a ella y se realicen modificaciones Entonces se deben modificar los registros de dicha entidad, sin ningún tipo de pérdida cuando varios usuarios trabajan sobre lo mismo.

Conversación Adicionalmente a la historia de usuario de listar contenido que no es propio, está la de permitir editar registros cuando estos son compartidos por terceros. Hay que asegurarse de que cuando uno está editando el elemento del proyecto que sea, un tercero no pueda modificarlo pudiendo dar problemas de integridad.

Esfuerzo: 2 SP Valor: 65 UV Prioridad: Baja

6.4.1.2 Gestión de plantillas

Historia de usuario: Listar plantillas

Funcionalidad Como usuario registrado Quiero poder visualizar un listado de las plantillas que he creado De forma que pueda gestionarlas

Criterio de aceptación Dado el menú de navegación Cuando se pulse en el botón asociado a la vista de listado de plantillas Entonces se deberán mostrar las plantillas que el usuario ha creado

Conversación Actualmente existe una plantilla genérica que se aplica a todos los usuarios de la aplicación, pero la idea es dejar que personalicen en todo momento cómo quieren que se exporten sus guiones. Por ello, se debe habilitar una nueva sección en el portal de usuarios para permitir que estos puedan listar las plantillas previo a la creación, modificación o eliminación de las mismas.

Esfuerzo: 2 SP Valor: 40 UV

Page 110: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

106

Prioridad: Alta

Historia de usuario: Crear plantilla

Funcionalidad Como usuario registrado Quiero poder crear una nueva plantilla De forma que se añada al listado de plantillas creadas por mí y pueda asignarla como plantilla para exportación de guiones

Criterio de aceptación Dado el listado de plantillas Cuando se pulse en el botón de añadir plantilla Entonces se deberá mostrar una plantilla más en el listado de plantillas

Conversación Al igual que en el resto de secciones del cliente, nuevas plantillas se podrán añadir en el listado de plantillas por medio de un botón nueva plantilla ubicado arriba del listado para tal fin.

Esfuerzo: 1 SP Valor: 20 UV Prioridad: Alta

Historia de usuario: Modificar detalle plantilla

Funcionalidad Como usuario registrado Quiero poder modificar una plantilla De forma que se pueda editar tanto el html asociado a la cabecera de la exportación como el del detalle de cada escena

Criterio de aceptación Dado el listado de plantillas Cuando se pulse en el botón de edición de plantilla a nivel de plantilla Entonces se deberá mostrar la vista detalle de la plantilla que se ha elegido editar

Conversación Una vez añadidas las plantillas, el usuario debe poder modificar el HTML asociado tanto a la cabecera de exportación de guiones como al cuerpo de cada una de las escenas. En la vista de detalle, además de estos dos campos, también se le debe poder asignar un nombre a la plantilla y seleccionar si es o no la plantilla por defecto.

Esfuerzo: 2 SP Valor: 30 UV

Page 111: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

107

Prioridad: Alta

Historia de usuario: Eliminar plantilla

Funcionalidad Como usuario registrado Quiero poder eliminar una plantilla De forma que cuando considere que una plantilla deje de ser útil se elimine de la lista de plantillas creadas por mí

Criterio de aceptación Dado el listado de plantillas o la vista de detalle de plantilla Cuando se pulse en el botón de eliminar plantilla Entonces se borrará de la colección de plantillas del usuario

Conversación El usuario debe poder decidir si una plantilla le sigue siendo útil o no, es por eso que se debe habilitar un botón de eliminar plantilla tanto a nivel de plantilla en el listado como en la vista de detalle

Esfuerzo: 1 SP Valor: 10 UV Prioridad: Media

6.4.1.3 Ayuda fuera de escritura

Historia de usuario: Mostrar miga de pan

Funcionalidad Como usuario registrado Quiero poder ver cuál es el proyecto que estoy editando y en que sección me encuentro De forma que haya trazabilidad de mi navegación dentro del sitio

Criterio de aceptación Dado el menú de navegación y la cabecera del cliente Cuando se navegue por el sitio web Entonces se irá actualizando el componente de miga de pan para mostrar la sección actual y cómo se ha llegado a ella, así como el proyecto en que se está trabajando

Conversación Adicionalmente a la ayuda en escritura, funcionalidades como la de la miga de pan son necesarias para que el cliente pueda saber cómo ha llegado a determinadas vistas y poder dar pasos atrás en cualquier momento.

Page 112: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

108

Esfuerzo: 1 SP Valor: 25 UV Prioridad: Baja

Historia de usuario: Mostrar ayuda en las diferentes secciones

Funcionalidad Como usuario registrado Quiero poder ver ayuda relativa a la sección De forma que la funcionalidad existente en la vista quede clara

Criterio de aceptación Dada la vista actual Cuando se cargue y se pulse el botón de información Entonces se mostrará un diálogo modal con toda la información relativa a la funcionalidad de la vista.

Conversación Junto a la miga de pan, que el usuario disponga de un botón de información para saber más sobre el uso de la sección, es una ayuda que algunos usuarios podrían ver útil.

Esfuerzo: 1 SP Valor: 10 UV Prioridad: Baja

6.4.2 Desarrollo del sprint

Nuevamente, esta iteración no lleva cambios a nivel estructural en lo que se refiere a las

entidades que conforman el modelo de clases de la aplicación. Tan solo se prevé un cambio a

nivel de atributos de la clase Plantilla para adecuarse a la lógica de exportación y aumentar la

sencillez a la hora de trabajar con la colección en base de datos. Dichos cambios se describen en

la siguiente sección.

La razón por la que se retoca la clase Plantilla es porque se decide que se va a implementar todo

lo relacionado a su gestión tanto en el cliente como en el API. Esto provocará la creación de un

nuevo esquema en Mongoose, un nuevo grupo de componentes en el cliente de Angular2 y el

retoque oportuno en la exportación de guiones. Esto último se hace así con el fin de poder dejar

al usuario la elección de la plantilla antes de exportar la información relativa a los detalles

literarios y técnicos de las escenas de un proyecto en concreto.

La gestión de las plantillas nos va a obligar a pasarnos por el API. Por este motivo, vamos a

aprovechar y se van a implementar las modificaciones necesarias para que, cuando se están

pidiendo registros a las colecciones de la base de datos, además de tener en cuenta la autoría

del usuario que está autenticado a la hora de devolver los registros, también se va a tener en

Page 113: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

109

cuenta si ese usuario es colaborador del proyecto o elementos del proyecto que se están

consultando.

En el lado del cliente de Angular2, con esta nueva sección que se plantea para la gestión de

plantillas se continúa ampliando la variedad de enlaces por las que el usuario puede navegar.

Para facilitar la trazabilidad en la navegación y mejorar la experiencia de usuario, se ve oportuno

añadir un nuevo componente de miga de pan que en todo momento nos diga en qué sección

estamos y cómo hemos llegado a ella.

Desde un punto de vista no funcional, tal y como se comenta en la primera sección de este

sprint, se le plantea al cliente la posibilidad de postergar a futuras iteraciones parte de las

historias de usuario. El motivo por el que se plantea esto es para dedicar tiempo a actividades

de Ingeniería de Software con el fin de facilitar el mantenimiento, evolución y documentación

de la solución que se está implementando.

Entre las actividades de Ingeniería de Software que se le han planteado al cliente está la creación

de una especificación en Swagger, además de la publicación de dicha especificación junto al

cliente de Angular2. Además, también se acuerda la preparación de pruebas sobre el API REST

que se engloban dentro del concepto de pruebas de integración. Estas pruebas tienen que atacar

métodos de respuesta de ExpressJS y conexión con MongoDB por medio del ORM Mongoose.

Estas actividades facilitarán la tarea de mantener y evolucionar el software porque se

automatiza el comprobar que cualquier cambio o reingeniería de componentes o métodos no

interfiere en el normal y esperado funcionamiento de la API. Para esta actividad se va a hacer

uso tanto de Mocha18 como de Chai19, módulos bastante completos y aceptados en la

comunidad de desarrolladores de Node.js. Mocha aporta el armazón para la ejecución de las

pruebas y Chai es una librería de aserciones muy completa de la que haremos uso dentro de las

diferentes pruebas implementadas en Mocha.

Pero no sólo se facilita la tarea de mantener y evolucionar el software, crear la especificación

permite que cualquier persona pueda implementar su cliente haciendo uso de la documentación

que en ella se facilita. Para esta actividad, Swagger representa una herramienta bastante

completa puesto que, en conjunto con el cliente web y la documentación sobre nuestros

modelos y métodos, incluso se pueden probar los diferentes métodos del API.

Funcionalidades inherentes a las características correspondientes a esta iteración que quedan

postergadas serían la de edición cooperativa y la de mostrar los botones de ayuda en las

secciones detalle dentro del cliente de Angular.

En definitiva, y como en el resto de iteraciones, las novedades y cambios a nivel de arquitectura

quedan repartidos de la siguiente manera:

API REST ExpressJS

o Implementación del esquema de plantillas y las rutas de acceso a los diferentes

métodos de la clase-ruta Plantilla

o Cambios en los métodos del APIHelper para tener en cuenta al usuario

autenticado no sólo como autor de los registros sino como colaborador.

Únicamente serán usuarios de lectura en el caso de que sean colaboradores.

18 Mocha Testing Framework: https://mochajs.org/ 19 Chai Assertion Library: http://chaijs.com/

Page 114: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

110

o Modificación de las rutas para tener en cuenta una nueva ruta que hará

referencia al Swagger UI que mostrará la especificación del API: api-docs.

Angular2

o Creación del módulo de plantillas con los respectivos componentes para

gestionar la inserción, recuperación, eliminación y modificación de registros.

o Descarga y personalización del componente de miga de pan para Angular2

denominado ng2-breadcrumb20.

o Implementación de clase ModoColaborador. En dicha clase se tendrá constancia

de si el usuario es autor o no del proyecto o elemento del proyecto en que se

está trabajando. Este hecho se tendrá en cuenta en los diversos componentes

implicados con los proyectos o elementos de proyecto.

Otros

o Implementación de las pruebas de integración del API para probar todas y cada

una de las rutas. Dichas pruebas contendrán las aserciones necesarias para

comprobar los diferentes casos ante los que se pueden enfrentar las peticiones

contra el API.

o Escritura de la especificación del API haciendo uso del editor en línea de

Swagger.

o Descarga y personalización del cliente Swagger para apuntar a la nueva

especificación del API escrito para GuionMaker.

6.4.3 Documentación técnica del sprint

6.4.3.1 Implementación de las historias de usuario

Empezando por las modificaciones sobre el API de GuionMaker, es el turno de incluir un nuevo

esquema para alojar las plantillas dentro de las colecciones de la base de datos. Dicho esquema

deberá tener en cuenta que cada plantilla ha de contener el html perteneciente a la portada del

guion y, por otro lado, el html perteneciente a cada una de las escenas.

export class Plantilla { schema: mongoose.Schema; static current: Plantilla = new Plantilla(); constructor() { this.schema = new mongoose.Schema({ htmlPortada: String, htmlEscena: String, nombre: String, autor: { type: mongoose.Schema.Types.ObjectId, ref: Usuario.name, required: true }, porDefecto: Boolean, fechaCreacion: { type: Date, default: Date.now }, fechaModificacion: Date }); this.schema.post('findOneAndUpdate', function (plantilla) { if (plantilla['porDefecto']){ mongoose.model(Plantilla.name).find({ $and: [{ "_id": { "$ne": plantilla['_id'] } }, { "porDefecto": 1 }, { "autor": plantilla['autor'] }] }).exec((err, res) => {

20 Ng2-breadcrumb en NPM: https://www.npmjs.com/package/ng2-breadcrumb

Page 115: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

111

if (err) { console.log("Error al obtener la anterior plantilla por defecto del usuario " + err); } else if (res != undefined && res.length > 0) { res[0]["porDefecto"] = false; mongoose.model(Plantilla.name).update({ "_id": res[0]._id }, res[0], (err, raw) => { if (err) { console.log("Error al actualizar la anterior plantilla por defecto del usuario " + err); } else { console.log("Éxito al actualizar la anterior plantilla por defecto del usuario"); } }); } }); } }); }); mongoose.model(Plantilla.name, this.schema); } }

Nuevo modelo plantilla con una función asociada al esquema y al evento de actualización de registros en la colección.

Una buena forma para evitar que sea el cliente el que lleve a cabo la comprobación de que el

usuario no tiene más de una plantilla por defecto es llevar esta comprobación al propio API. Es

por ello que en el esquema de plantillas se incluye una función que se ejecuta después de que

se haya completado la actualización de un documento de la colección de plantillas. En dicha

función se busca, en caso de que la haya y de que la plantilla actualizada tenga marcado como

verdadero el atributo “porDefecto”, la anterior plantilla por defecto del usuario. Cuando la

plantilla es localizada se actualiza con el atributo “porDefecto” a falso en el que caso de que no

sea la propia plantilla que acabamos de actualizar.

Las plantillas serán registradas a nivel de usuario, por lo que el campo con la referencia al usuario

que la ha creado debe ser obligatorio. En caso de que no se facilite dicha referencia la inserción

o actualización de una hipotética plantilla sería errónea.

Aunque en la anterior iteración se hicieron una serie de cambios sobre las clases-ruta para tener

en cuenta a los autores de las referencias de proyecto dentro de los elementos de un proyecto,

en esta iteración se ha tenido que dar un paso más. Ahora ya no solo nos interesa comprobar

que el usuario sea el autor de los registros que intenta consultar o manipular, sino que, también,

tenemos que dejar que los colaboradores de un proyecto puedan ver registros que no son

propiamente suyos.

Para alcanzar el objetivo de permitir a los usuarios ver los registros asociados a proyectos en

que son colaboradores se tiene que realizar una modificación en el esquema de los proyectos.

Este cambio supone incluir la entidad colaboración como subdocumento de los proyectos, de

tal manera que se puede consultar rápidamente si un usuario es colaborador de un proyecto o

no. Esto supone un cambio respecto al planteamiento inicial, que supondrá realizar cambios

Page 116: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

112

también sobre el cliente de Angular al desaparecer la clase-ruta de colaboraciones, clase que ya

no es necesaria puesto que las colaboraciones formarán parte de los proyectos como un atributo

más.

this.schema = new mongoose.Schema({ nombre: String, sinopsis: String, genero: { type: mongoose.Schema.Types.ObjectId, ref: Genero.name }, clasificacion: { type: mongoose.Schema.Types.ObjectId, ref: Clasificacion.name }, autor: { type: mongoose.Schema.Types.ObjectId, ref: Usuario.name, required: true }, colaboradores: [Colaboracion.current.schema], publico: Boolean, fechaCreacion: { type: Date, default: Date.now() }, fechaModificacion: { type: Date }, cancelado: Boolean });

Esquema perteneciente a los proyectos en donde se aloja un array de esquemas del tipo Colaboracion.

this.schema = new mongoose.Schema({ usuario: { type: mongoose.Schema.Types.ObjectId, ref: Usuario.name, required: true }, fecha: Date, permisos: Number });

Esquema de las colaboraciones simplificado al no tener que hacer referencia al proyecto al que pertenece, por ser subdocumento del mismo.

Los subdocumentos son una manera bastante común de trabajar en MongoDB con elementos

que se asocian de la forma en que lo hacen proyectos y colaboraciones. El gran motivo por el

que esto se hace así es debido a que entre las operaciones de consulta no existen los join,

consecuencia de la filosofía no relacional de MongoDB.

Actualmente ya tenemos todo lo necesario para añadir las modificaciones necesarias a las

funciones que creaban filtros para, en el APIHelper, hacer menos restrictivas las operaciones de

consulta de datos permitiendo acceso también a los colaboradores.

public static crearFiltroAutor(req: express.Request): PeticionJson { let filtro = new PeticionJson(); filtro.find = { $or: [{ "autor": req.user.usuarioLogeado }] }; return filtro; } public static crearFiltroProyecto(req: express.Request): PeticionJson { let filtro = new PeticionJson() filtro.populate = { path: "proyecto", match: { $or: [{ "colaboradores.usuario": req.user.usuarioLogeado }, { "autor": req.user.usuarioLogeado }] }

Page 117: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

113

}; return filtro; } public static crearFiltroProyectoManipulacion(req: express.Request): PeticionJson { let filtro = new PeticionJson() filtro.populate = { path: "proyecto", match: { "autor": req.user.usuarioLogeado } }; return filtro; }

Funciones estáticas dentro de la clase-ruta de los proyectos para la creación de filtros relativos a los proyectos.

Como se puede observar en el fragmento de código de las funciones estáticas dentro de la clase-

ruta de los proyectos, el atributo populate pasa a ser un objeto. Dicho objeto albergará tanto la

ruta sobre la que realizar la operación de la carga del documento del proyecto asociado al

elemento de proyecto como las condiciones que se deben cumplir en dicho documento para

que sea un resultado positivo. En dichas condiciones se hace un OR entre el autor del proyecto

y el identificador de usuario dentro de los colaboradores del proyecto en la función

crearFiltroProyecto(). Aunque esto es correcto, es necesaria la existencia de otra función más

restrictiva que será la que utilicen las operaciones de manipulación de colecciones, quienes, por

el momento, sólo dejarán operar al autor del proyecto.

public getPersonajes(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getAll(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public getPersonajeById(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getById(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyecto(req)); } public addPersonaje(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.add(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyectoManipulacion(req)); } public deletePersonaje(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.delete(PersonajeRoute.model, req, res, ProyectoRoute.crearFiltroProyectoManipulacion(req)); } public getPersonajesByFilterAndSort(req: express.Request, res: express.Response, next: express.NextFunction) { APIHelper.getByFilterAndSort(PersonajeRoute.model, ProyectoRoute.alterarFiltroConProyecto(req), res); }

Page 118: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

114

Fragmento de código de la clase-ruta de personajes en que se hace uso de las funciones estáticas de los proyectos. Se hace distinción con los filtros entre operaciones de manipulación y consulta.

Aún quedan modificaciones que realizar en la parte de servidor concernientes a las pruebas y al

alojamiento de la especificación de Swagger, pero antes de eso llega el turno de las

modificaciones en el cliente web. Entre estas modificaciones está el añadir un nuevo módulo

de plantillas, incluir un nuevo componente de terceros para la miga de pan y realizar los cambios

necesarios para que usuarios colaboradores sean partícipes –pasivos por el momento- de los

proyectos en que lo son.

El primero paso es añadir un nuevo directorio al árbol de directorios de la aplicación. Dicho

directorio contiene el módulo de rutas de las plantillas, el módulo que contiene las rutas y los

componentes, además de los propios componentes.

Ilustración 40. Directorio de plantillas en la solución del cliente web.

Siguiendo la idea de la anterior iteración, tanto en el detalle como en el listado se va a hacer uso

de las clases genéricas con vistas a reutilizar código que ya está probado. Sin embargo, la

existencia de un atributo exclusivo21 dentro de las plantillas nos obliga a ampliar la clase de listas

genéricas para tenerlo en cuenta. De esta forma, desde la propia plantilla html podremos hacer

referencia al atributo exclusivo que hayamos facilitado y, con ello, notificar al usuario de cuál es

el registro que tiene su atributo exclusivo a verdadero.

export class ListaGenerica { […] propiedadDestacado: string; constructor(titulo: string, entidad: string, entidadPorFiltro: string, peticion: PeticionJson, nuevoElemento: any, rutaRetorno: string = '', propiedadDestacado: string = '') { […] this.propiedadDestacado = propiedadDestacado; } }

Nueva propiedad opcional en los listados genéricos: nombre del atributo exclusivo.

<div class="row"> <div class="col-md-12">

21 Con atributo exclusivo se hace referencia a aquellos atributos en los que en una colección solo puede haber uno marcado como verdadero.

Page 119: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

115

{{elemento.nombre}} <i class="glyphicon glyphicon-asterisk" *ngIf="listaGenerica.propiedadDestacado != '' ? elemento[listaGenerica.propiedadDestacado] : ''"></i> </div> […] <div class="alert alert-info" *ngIf="listaGenerica.propiedadDestacado != ''"> <i class="glyphicon glyphicon-asterisk"></i>: indica {{listaGenerica.entidad}} por defecto en caso de que exista. </div>

Plantilla de los listados genéricos, en donde se muestra un glyphicon asterisco en caso de que el registro sea el portador del atributo exclusivo establecido a verdadero.

this.listaGenerica = new ListaGenerica("Listado de plantillas disponibles para este usuario", "plantilla", "plantillasPorFiltro", this.angularAPIHelper.buildPeticion({}, {}), nuevaPlantilla, '', "porDefecto");

Instanciación de la lista genérica en el componente de listado de plantillas.

En lo referente al detalle de plantilla, se hace uso de la clase abstracta de detalle genérico que

se implementó para los elementos de la biblia literaria. Con el objetivo de hacerla más genérica

se exporta al módulo de utilidades y se modifica la referencia en los detalles de personajes y

escenarios. Esta sencilla operación nos permite reutilizar toda la lógica de la vista de detalle

incluyendo los avisos de confirmaciones de guardado o eliminación.

Con la gestión de plantillas implementada y probada en el cliente web, lo siguiente es limitar la

operativa en el cliente para que los usuarios que no son autores no puedan manipular registros.

Para ello se decide implementar una clase de la que heredarán aquellos componentes

encargados de permitir al usuario la manipulación de registros.

export class ModoColaborador { protected usuarioLogeadoAutor: boolean; constructor(protected angularAPIHelper: AngularAPIHelper, protected localStorageService: LocalStorageService, forceValue: boolean = false) { if (!forceValue) { ProyectoModel.getProyectoActual(this.angularAPIHelper, this.localStorageService).subscribe((respuesta) => { let proyectoR = respuesta as RespuestaJson; if (proyectoR != undefined && proyectoR.estado == ResponseStatus.OK && proyectoR.consulta.length == 1) { this.usuarioLogeadoAutor = this.localStorageService.esUsuarioLogeado((proyectoR.consulta[0] as ProyectoModel).autor); } }); } } }

Page 120: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

116

Clase de utilidad para tener en cuenta si el usuario que está identificado es autor del proyecto o no.

En esta clase se inyectan dependencias a los servicios de localStorage y el APIHelper en que nos

basamos para lanzar las peticiones contra el API REST de ExpressJS. Adicionalmente, hay un

parámetro opcional que permite forzar el valor del atributo usuarioLogeadoAutor en el caso en

que se necesite desde alguna clase en particular. Lo normal es que esto no sea necesario y se

establezca el valor en función de si el autor del proyecto actual que se ha fijado en la página

principal coincide con el usuario que ha iniciado sesión.

De esta clase heredan: el componente de botones de guardado, el de lista genérica y el listado

de escenas que es especial con respecto al resto de listados por la exportación. Como el

componente de botones de guardado se utiliza en todos los componentes de detalle, lo que hay

que hacer es utilizar la nueva propiedad para deshabilitar los botones de guardar o eliminar en

el caso en que el usuario autenticado no coincida con el autor del proyecto en que está

trabajado. Esta misma idea se exporta a los listados de la aplicación, donde el usuario que no

cumple con esta condición no podrá añadir nuevos elementos o eliminarlos, tan solo podrá

acceder al detalle para leerlo.

export class BotonesGuardadoComponent extends ModoColaborador { […] <button class="btn btn-default" (click)="onVolver()" *ngIf="oBotonesGuardado.mostrarVolver"><i class="glyphicon glyphicon glyphicon-chevron-left"></i> Volver</button> <button class="btn btn-default" data-toggle="modal" data-target="#mdlVolver" *ngIf="oBotonesGuardado.mostrarVolverConfirmacion"><i class="glyphicon glyphicon glyphicon-chevron-left"></i> Volver</button> <button class="btn btn-primary" (click)="onGuardarCambios()" *ngIf="oBotonesGuardado.mostrarGuardado" [disabled]="!usuarioLogeadoAutor"><i class="glyphicon glyphicon-save"></i> Guardar cambios</button> <button class="btn btn-danger" data-toggle="modal" data-target="#mdlBorrado" *ngIf="oBotonesGuardado.mostrarBorrado" [disabled]="!usuarioLogeadoAutor"><i class="glyphicon glyphicon-remove"></i> Eliminar</button> <button class="btn btn-danger" data-toggle="modal" data-target="#mdlCancelar" *ngIf="oBotonesGuardado.mostrarCancelar" [disabled]="!usuarioLogeadoAutor"><i class="glyphicon glyphicon-remove"></i> Cancelar</button>

Ejemplo de aplicación de la clase ModoColaborador en el componente de botones de guardado para deshabilitar botones haciendo uso de una directiva.

Alcanzado este punto del desarrollo vemos que los usuarios colaboradores ya pueden ver los

registros que deben, pero no pueden manipularlos, aunque tengan establecido que son

colaboradores con permisos de escritura. También pueden gestionar ya sus plantillas y

establecer una de ellas como plantilla por defecto. Esta plantilla por defecto es la que se va a

utilizar en la exportación, pero no se ha hecho todavía el cambio en el componente de listado

de escenas para que esto sea así.

Page 121: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

117

En los parámetros del fichero de configuración del cliente, donde se encuentra la URL del API

junto al puerto, se tiene que incluir el código HTML correspondiente a la plantilla por defecto en

el caso de que un usuario no tenga una plantilla por defecto o que, por otro lado, no tenga los

tokens requeridos para que se considere una plantilla bien implementada. Estos tokens son los

que, a la postre, se sustituyen por los valores de texto de detalles literarios y técnicos Las

comprobaciones correspondientes a estos tokens se decide implementarlas en el modelo de

plantilla como funciones estáticas.

export class PlantillaModel { […] public static getHtmlPortada(plantilla: PlantillaModel): string { if (plantilla.htmlPortada != undefined && plantilla.htmlPortada.indexOf("{{tituloProyecto}}") >= 0 && plantilla.htmlPortada.indexOf("{{tipoGuion}}") >= 0) { return plantilla.htmlPortada; } return AngularAPIHelper.plantillaPortada; } public static getHtmlEscena(plantilla: PlantillaModel): string { if (plantilla.htmlEscena != undefined && plantilla.htmlEscena.indexOf("{{tituloEscena}}") >= 0 && plantilla.htmlEscena.indexOf("{{contenidoEscena}}") >= 0) { return plantilla.htmlEscena; } return AngularAPIHelper.plantillaEscena; } }

Funciones de comprobación del html de escenas y portada para las plantillas de exportación.

En el caso en que la validación falle para el html de las escenas o de la portada, se devuelve el

html por defecto que se almacena en el APIHelper en el momento en que se carga el fichero de

configuración del cliente.

[…] private generarHtmlExportacion(literario: boolean, plantilla: PlantillaModel) { this.exportacionWindow = window.open(); this.exportacionWindow.document.title = "Vista completa del guión - GuionMaker"; let plantillaEscena = AngularAPIHelper.plantillaEscena; if (plantilla != undefined) { plantillaEscena = PlantillaModel.getHtmlEscena(plantilla); } for (let escena of this.escenas) { let escenaActual: EscenaModel = escena; let plantillaModificada: string = plantillaEscena.replace("{{tituloEscena}}", escenaActual.orden + ". " + this.getSituacionString(escenaActual) + ". " + escenaActual.titulo.toUpperCase() + ". " + this.getTemporalidadString(escenaActual)); this.htmlExportado += "{" + escenaActual.orden + "}"; let detalle: any = literario ? escenaActual.detalleLiterario : escenaActual.detalleTecnico; if (detalle.texto != undefined && detalle.texto != "") {

Page 122: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

118

this.htmlExportado = this.htmlExportado.replace("{" + escenaActual.orden + "}", plantillaModificada.replace("{{contenidoEscena}}", this.generarHtmlImagen(detalle) + detalle.texto)); } else { this.htmlExportado = this.htmlExportado.replace("{" + escenaActual.orden + "}", ""); } this.exportacionWindow.document.documentElement.innerHTML = this.htmlExportado; } } exportarGuion(literario: boolean) { this.htmlExportado = ""; this.angularAPIHelper.postEntryOrFilter("plantillasPorFiltro", JSON.stringify(this.angularAPIHelper.buildPeticion({ "porDefecto": 1 }, {}))).subscribe(response => { let plantilla = (response as RespuestaJson).consulta[0] as PlantillaModel; let plantillaPortada = AngularAPIHelper.plantillaPortada; if (plantilla != undefined) { plantillaPortada = PlantillaModel.getHtmlPortada(plantilla); } plantillaPortada = plantillaPortada.replace("{{tituloProyecto}}", this.localStorageService.getPropiedad('nombreProyectoActual')); this.htmlExportado += plantillaPortada.replace("{{tipoGuion}}", literario ? "Guión literario" : "Guión técnico"); this.generarHtmlExportacion(literario, plantilla); }); } […]

Fragmento de código perteneciente a la exportación de guiones en el listado de escenas.

Como se puede observar en el fragmento de código correspondiente a las funciones de

exportación, se intenta obtener la plantilla por defecto del usuario. En caso de que esto no sea

posible, la plantilla por defecto de la aplicación siempre va a estar disponible en el APIHelper

que ha cargado el fichero de configuración. La portada tiene unos tokens específicos que se

sustituyen por el valor de guion técnico o literario en función de lo que se haya decidido exportar

y el nombre del proyecto. Cada una de las escenas tiene en cuenta el título de la mismas a la

hora de reemplazar el token de título, mientras que el cuerpo se sustituye por el detalle literario

o técnico de la escena en función del tipo de exportación elegido. Portada y escenas se van

concatenando en una cadena de caracteres que representa el html definitivo que se debe

mostrar al usuario.

Finalmente, cuando se ha construido el html de la portada y de todas las escenas, se abre una

nueva ventana en la que se muestra el guion literario / técnico exportado. Para poder abrir esta

nueva ventana se debe habilitar en el navegador que se abran pop-ups para GuionMaker, en

caso contrario la mayor parte de los navegadores evitan la apertura de una nueva ventana.

Preparada la implementación de la gestión de plantillas y el poder exportar guiones haciendo

uso de plantillas propias por defecto, tan solo queda incluir el componente de miga de pan ng2-

breadcrumb. Haciendo uso de npm para la instalación del módulo y siguiendo las instrucciones

del autor, nos llevamos este módulo al componente principal de la aplicación: app.component.

Page 123: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

119

constructor(private localStorageService: LocalStorageService, private angularAPIHelper: AngularAPIHelper, private router: Router, private breadcrumbService: BreadcrumbService) { this.breadcrumbService.addCallbackForRouteRegex('^(.)+$', (str) => { let reg = new RegExp("^.*([A-Fa-f]|[0-9]){24}$"); if (reg.test(str)) { str = "Detalle"; } else { str = str.substring(0, 1).toUpperCase() + str.substring(1, str.length); } return str; }); }

Constructor del componente principal de la aplicación inyectando un nuevo servicio correspondiente al módulo del componente de la miga de pan

Entre sus opciones, el servicio del componente de miga de pan nos da la posibilidad de ejecutar

una función de callback cuando se cumple que en la ruta hay una expresión regular o un valor

concreto. Se ha echado mano de esta utilidad para asegurarnos de que cuando la ruta cumpla

con la expresión regular propia del identificador único de un documento en MongoDB, se

devuelva como nombre de la sección “Detalle”. En caso contrario, se devuelve el nombre de la

ruta original con el primer carácter en mayúsculas.

No basta con editar el constructor del componente, sino que también se tiene que editar la vista

del componente principal de la aplicación para insertar el componente de miga de pan.

<breadcrumb [prefix]="'GuionMaker'+ (localStorageService.propiedades['nombreProyectoActual'] == undefined ? '' : ' / ' + localStorageService.propiedades['nombreProyectoActual'])"></breadcrumb> <router-outlet></router-outlet>

Componente de miga de pan insertado en la vista del componente principal de la aplicación junto al router-outlet. Se incluye un prefijo para que se muestre por delante de todas las secciones por las que se va navegando

Implementado sin mucho esfuerzo, este componente ayudará al usuario a mantener una

trazabilidad sobre su navegación y le abre la posibilidad de volver hacia atrás ya no solo con el

botón de volver, sino que dispondrá de enlaces a los padres de la vista en que está trabajando

actualmente.

Ilustración 41. Muestra de cómo encaja el componente de miga de pan bajo la barra de navegación del cliente web.

Page 124: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

120

La última modificación reseñable durante esta iteración es la inclusión en el cliente de la interfaz

CanActivate22 de Angular2. Esta interfaz es útil desde el punto de vista en que podemos activar

o desactivar rutas en base a unos criterios. De entre todos los criterios posibles por los que

podríamos optar, en nuestro cliente web la única condición para que se active o no una ruta es

que hayamos iniciado sesión. En el caso en que no se cumpla la condición de que haya una sesión

iniciada, esta interfaz nos tiene que llevar al área de inicio de sesión. Para ello, decidimos

implementar esta interfaz nuevamente como una clase de utilidad de la que tiraremos en las

rutas que requieran que haya un usuario autenticado.

@Injectable() export class CanActivateIsLoggedGuard implements CanActivate { constructor(private angularAPIHelper: AngularAPIHelper, private localStorageService: LocalStorageService, private router: Router) { } canActivate() { let logeado = this.angularAPIHelper.usuarioLogeado(this.localStorageService); if (!logeado) { this.router.navigate(['/login']); } return logeado; }}

Servicio de guardia que implementa la interfaz CanActivate de Angular2.

Dicha interfaz tiene un método CanActivate que es el que ha de devolver un valor booleano en

función de si se cumple nuestra condición de activación o no. De forma específica, la

implementación de esta clase hace uso del localStorageService para comprobar si hay un usuario

que ha iniciado sesión. Una vez implementada, sólo queda utilizar esta guarda en los módulos

de rutas de la aplicación y declararla como provider en el módulo principal.

providers: [AngularAPIHelper, LocalStorageService, { provide: APP_INITIALIZER, useFactory: cargarConfiguracion, deps: [AngularAPIHelper, Injector], multi: true }, CanActivateIsLoggedGuard]

Fragmento del módulo principal de la aplicación donde se declaran los providers de los que se va a hacer uso.

const escenasRoutes: Routes = [ { path: 'escenas', children: [ { path: '', component: EscenasListComponent },

22 Documentación de la interfaz CanActivate: https://angular.io/docs/ts/latest/api/router/index/CanActivate-interface.html

Page 125: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

121

{ path: ':id', component: DetalleEscenaComponent } ], canActivate: [CanActivateIsLoggedGuard] }, ];

Ejemplo de uso de la guarda en el módulo de rutas de escenas. En este caso se aplica la guarda a absolutamente todas las rutas de escenas.

6.4.3.2 Preparación de las pruebas con Mocha y Chai + especificación en Swagger

Esta segunda parte de la documentación técnica centra el foco sobre la implementación de las

pruebas haciendo uso del módulo Mocha, pero también de Chai para apoyarnos en su utilidad

con las aserciones que se van a probar dentro de cada uno de los casos a tener en cuenta. Su

inclusión en el proyecto nos llevará a realizar cambios en la aplicación de ExpressJS, pero

también la inclusión de una ruta para mostrar la especificación en Swagger.

Para discernir entre si estamos realizando una ejecución habitual a una ejecución en pruebas,

nos vamos a valer de una variable de entorno de Node.js que es NODE_ENV. Esto es necesario

para que, en el caso en que estemos ejecutando el modo de pruebas, se haga uso de una nueva

base de datos de pruebas sobre la que se ejecutarán cada uno de los casos de prueba

implementados. Esto se consigue modificando el archivo package.json que alberga los módulos,

datos de la aplicación y scripts a correr desde npm.

"start": "tsc && set NODE_ENV=prod && node ./bin/www" "test": "tsc && set NODE_ENV=test && mocha --timeout 10000"

Scripts que se ejecutan cuando se quiere ejecutar la aplicación en el modo normal o en el modo de pruebas. Nótese que en test se ejecuta mocha después de compilar el código TypeScript y asignar la variable de entorno a test.

Que se haga uso de una base de datos u otra en función de la variable de entorno se debe

implementar en nuestro API o aplicación de ExpressJS.

constructor() { console.log(process.env.NODE_ENV); this.config = require('./public/dist/assets/apiconfig.json'); this.api = express(); this.setConfig(); require('mongoose').Promise = global.Promise; if (process.env.NODE_ENV != undefined && process.env.NODE_ENV.trim() === 'test') { mongoose.connect("mongodb://" + this.config.dbURL + "/" + this.config.dbCollectionName + 'Test'); } else { mongoose.connect("mongodb://" + this.config.dbURL + "/" + this.config.dbCollectionName); console.log(this.config); } }

Constructor de la aplicación ExpressJS y donde se discrimina la conexión a utilizar en función del valor de la variable de entorno de Node.js.

Page 126: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

122

El entorno de pruebas, por tanto, ya está preparado y sólo queda implementar los casos que

permitan verificar y validar la funcionalidad del API REST. Mocha se nutre de la carpeta test para

ejecutar las pruebas y, por defecto, las va ejecutando en orden alfabético según el nombre del

fichero. Es por eso que se decide crear un nuevo directorio test donde se van a ir implementado

las pruebas por nombre de modelo.

Ilustración 42. Nueva carpeta de test en la raíz del proyecto para alojar las aserciones y casos de prueba del API REST.

La estructura de un fichero de pruebas siempre es la misma, variando en lo que se está probando

en cada uno de ellos. En un primer momento se realiza la importación de las librerías de las que

se va a hacer uso:

import { THelper } from "../models/THelper"; import { ResponseStatus } from "../routes/APIHelper"; import * as chai from "chai"; import chaiHttp = require("chai-http"); let nombreEntidad = "clasificacion"; let nombreEntidadPlural = nombreEntidad + 'es'; let should = chai.should(); chai.use(chaiHttp);

Fragmento inicial del script preparado para las pruebas del modelo de clasificaciones de edad para proyectos.

Como se puede observar se hace uso de un modelo de utilidad llamado THelper que, al igual que

APIHelper sirve para tratar con la base de datos a través de Mongoose, éste nos sirve para

reutilizar aserciones que son comunes entre las diferentes pruebas a entidades que intervienen

en el API. Dichas aserciones y pruebas a peticiones http vienen asistidas por Chai y el módulo

HTTP, específico para aserciones a peticiones y respuestas web. A continuación, se establece el

nombre de la entidad sobre la que se van a realizar las pruebas, además de establecer el punto

de partida para las aserciones usando el getter de la función should().

El cuerpo de un fichero de pruebas también sigue un esquema muy parecido en todos los

ficheros de prueba que se alojan dentro del directorio test. En la siguiente tabla se facilita el

cuerpo del fichero de pruebas de la entidad Escenario. Dicha entidad es representativa para

explicar cómo se implementan las pruebas haciendo uso de las librerías que se han elegido.

Page 127: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

123

describe('Escenarios', () => { describe('/GET ' + nombreEntidadPlural, () => { it(THelper.notAuthVerbose, (done) => { THelper.getIsAuth(done, '/api/' + nombreEntidadPlural); }); it('debe dar error al no haber ' + nombreEntidadPlural + ' para el usuario test', (done) => { THelper.getColeccionVacia(done, '/api/' + nombreEntidadPlural); }); }); describe('/POST ' + nombreEntidad, () => { it(THelper.notAuthVerbose, (done) => { THelper.postIsAuth(done, '/api/' + nombreEntidad); }); it('debe devolver un error al no asignar un proyecto a la entidad', (done) => { let entidad = { nombre: "prueba", ubicacion: "una prueba más" } chai.request(THelper.app) .post('/api/' + nombreEntidad) .set('Authorization', THelper.getAuthValue()) .send(entidad) .end((err, res) => { res.should.have.status(200); res.body.estado.should.be.eql(ResponseStatus.KO); done(); }); }); it('debe devolver el documento guardado en la colección con un identificador asignado', (done) => { let entidad = { nombre: "prueba", ubicacion: "una prueba más", proyecto: THelper.testProjectId } chai.request(THelper.app) .post('/api/' + nombreEntidad) .set('Authorization', THelper.getAuthValue()) .send(entidad) .end((err, res) => { res.should.have.status(200); res.body.insertado.should.be.an('object'); res.body.insertado._id.should.be.a('string'); res.body.insertado._id.length.should.be.eql(THelper.testProjectId.length); res.body.estado.should.be.eql(ResponseStatus.OK); entidadIDToDelete = res.body.insertado._id; done(); }); }); }); […] });

Fragmento del fichero de pruebas de la entidad Escenarios

Page 128: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

124

La función describe de Mocha sirve para agrupar casos de prueba conceptualmente y así, en el

fichero resultado o en la impresión de los resultados en la línea de comandos, todo estará

ordenado según nuestro criterio. En este caso, y siguiendo el criterio de separar las pruebas por

entidad, se irán ejecutando todos los casos de prueba de una entidad antes de ir a la siguiente.

Por otro lado, la función it comprende el propio caso de prueba en sí. El primer parámetro que

recibe es un literal con la descripción del caso de prueba y el segundo es la función que ejecuta

el caso de prueba en sí. Adicionalmente, a esta función se le pasa como parámetro otra función

–la función done- que sirve para determinar que el test ha terminado una vez se ejecuta dentro

del cuerpo de la función que definimos para it.

En el fragmento del fichero de pruebas de ejemplo se puede ver cómo se hace uso de THelper

continuamente para implementar casos de prueba. En este modelo se tienen funciones y

atributos estáticos que gestionan información tan relevante como un token de sesión de un

usuario de prueba en la base de datos de prueba, el usuario y contraseña de este usuario de

prueba, su ObjectId, algunos mensajes que es necesario mostrarlos en muchos casos de prueba

o la referencia estática a la aplicación ExpressJS contra la que se lanzan las peticiones.

export class THelper { static testUserToken: string = “[…]” static testUsername: string = "[…]"; static testPassword: string = "[…]"; static testObjectId: string = "[…]"; static testProjectId: string = ""; static app: Express.Application = require("../app"); static notAuthVerbose: string = "debe dar error 401 al no proporcionar cabecera de autenticación"; static getAuthValue(): string { return 'Bearer ' + THelper.testUserToken; } static getIsAuth(done: MochaDone, route: string) {[…]} static postIsAuth(done: MochaDone, route: string) {[…]} static deleteIsAuth(done: MochaDone, route: string) { chai.request(THelper.app) .del(route) .end((err, res) => { res.should.have.status(401); done(); }); } static getColeccionVacia(done: MochaDone, route: string) {[…]} static getNoExistente(done: MochaDone, route: string) {[…]} static getExistente(done: MochaDone, route: string, porId: boolean = true) { […]} static deleteNoExistente(done: MochaDone, route: string) {[…]} static deleteExistente(done: MochaDone, route: string) {[…]} static postFiltroInexistente(done: MochaDone, route: string) {[…]} static postFiltroProyectoPrueba(done: MochaDone, route: string) {[…]} }

Aspecto del modelo de utilidad para las aserciones y casos de prueba.

Las funciones estáticas de THelper reciben siempre como parámetro la ruta a la cual hay que

hacer la aserción con el módulo Chai-HTTP y la función done a ejecutar para dar por terminado

el caso de prueba y evitar un error por tiempo de espera superado. En general, operaciones de

Page 129: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

125

consulta y manipulación que deben devolver un resultado o no en función de la cabecera con el

token de autenticación, son aserciones que se implementan en este modelo de utilidad al que

luego se llama desde cualquier fichero de prueba que lo importe. Sin embargo, esto no suple la

necesidad de que, en casos de prueba más complejos, tengamos que aportar nuestras

aserciones particulares en busca de que se cumpla algún requisito respecto al esquema o la

dependencia entre entidades.

Cabe destacar también que Mocha da la opción de ejecutar código antes o después de las

pruebas a una entidad. Esto es particularmente útil cuando nos enfrentamos a entidades que

no tienen un eliminar implementado todavía, como es el caso de los usuarios. De esta manera,

cuando se terminan de ejecutar los casos de prueba de usuarios, haciendo uso de una de estas

utilidades de Mocha, podemos eliminar los registros de prueba de una manera muy sencilla.

after((done) => { UsuarioRoute.model.findById(entidadId).exec((err, res) => { if (err) { console.log('Error al limpiar la base de datos después del test ' + err); } else { if (res != undefined) { res.remove((err, res) => { if (err) { console.log('Error al borrar el usuario de prueba ' + err); } done(); }); } } }); });

Uso de la función after de Mocha para limpiar la base de datos una vez se han ejecutado los casos de prueba implementados para los usuarios.

Siguiendo esta lógica se han implementado 97 casos de prueba entre todas las entidades que

forman la solución. El listado de entidades y casos de prueba que imprime Mocha cada vez que

se ejecuta npm test en la consola es el siguiente:

Clasificaciones GET clasificaciones √ debe mostrar un error 401 al intentar obtener el listado sin token de sesión √ debe devolver un listado de todas las clasificaciones GET clasificacion/id √ debe mostrar un error 401 al no proporcionar el token de sesión √ no debe devolver ningún registro puesto que no existe ninguno con id 590e078f6f22d01090f635d3 √ debe devolver la clasificación recomendada para la infancia Proyectos /GET proyectos √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber proyectos para el usuario test /POST proyecto

Page 130: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

126

√ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver el documento guardado en la colección con una fecha de creación √ debe devolver el documento insertado con el colaborador de mismo ID que test y de solo lectura /POST proyectosPorFiltro √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver el documento publicado y con nombre prueba test √ no debe devolver ningún registro /GET proyecto/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber proyecto 590e078f6f22d01090f635d3 para el usuario test √ debe devolver el documento generado anteriormente con id /GET proyectos √ ahora que hay documentos para el usuario, debe poder recuperarlos /DELETE proyecto/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber proyecto 590e078f6f22d01090f635d3 para el usuario test √ debe poder eliminar el último registro insertado anteriormente y mandar una confirmación /POST proyectosPorFiltro √ debe devolver el proyecto generado para el resto de pruebas tras eliminar el primero Escenarios /GET escenarios √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escenarios para el usuario test /POST escenario √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver un error al no asignar un proyecto a la entidad √ debe devolver el documento guardado en la colección con un identificador asignado /POST escenariosPorFiltro √ debe dar error 401 al no proporcionar cabecera de autenticación √ no debe devolver ningún registro √ debe devolver el registro introducido anteriormente con título prueba /GET escenario/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escenario 590e078f6f22d01090f635d3 para el usuario test √ debe devolver el documento generado anteriormente con id /DELETE escenario/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escenario 590e078f6f22d01090f635d3 para el usuario test √ debe devolver la confirmación de borrado para la entidad Escenas /GET escenas √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escenas para el usuario test /POST escena √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver un error puesto que no se facilita el identificador de proyecto de la escena √ debe devolver el documento guardado en la colección con una fecha de creación /POST escenasPorFiltro

Page 131: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

127

√ debe dar error 401 al no proporcionar cabecera de autenticación √ no debe devolver ningún registro √ debe devolver el registro introducido anteriormente con título prueba /GET escena/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escena 590e078f6f22d01090f635d3 para el usuario test √ debe devolver el documento generado anteriormente con id /DELETE escena/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber escena 590e078f6f22d01090f635d3 para el usuario test √ debe borrar la escena creada anteriormente Clasificaciones GET géneros √ debe mostrar un error 401 al intentar obtener el listado sin token de sesión √ debe devolver un listado de todos los géneros GET genero/id √ debe mostrar un error 401 al no proporcionar el token de sesión √ no debe devolver ningún registro puesto que no existe ninguno con id 590e078f6f22d01090f635d3 √ debe devolver el género comedia Personajes /GET personajes √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber personajes para el usuario test /POST personaje √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver un error al no asignar un proyecto a la entidad √ debe devolver el documento guardado en la colección con un identificador asignado /POST personajesPorFiltro √ debe dar error 401 al no proporcionar cabecera de autenticación √ no debe devolver ningún registro √ debe devolver el registro introducido anteriormente con título prueba /GET personaje/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber personaje 590e078f6f22d01090f635d3 para el usuario test √ debe devolver el documento generado anteriormente con id /DELETE personaje/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber personaje 590e078f6f22d01090f635d3 para el usuario test √ debe devolver la confirmación de borrado para la entidad /DELETE proyecto/id √ debe borrar el proyecto con que se han hecho las pruebas Plantillas /GET plantillas √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber plantillas para el usuario test /POST plantilla √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe devolver un error al no asignar un autor a la entidad

Page 132: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

128

√ debe devolver el documento guardado en la colección con un identificador asignado /POST plantillasPorFiltro √ debe dar error 401 al no proporcionar cabecera de autenticación √ no debe devolver ningún registro √ debe devolver el registro introducido anteriormente con título prueba /GET plantilla/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber plantilla 590e078f6f22d01090f635d3 para el usuario test √ debe devolver el documento generado anteriormente con id /DELETE plantilla/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ debe dar error al no haber plantilla 590e078f6f22d01090f635d3 para el usuario test √ debe devolver la confirmación de borrado para la entidad Usuarios POST usuario √ no debe permitir guardar un usuario sin sus campos obligatorios √ debe guardar el documento y devolverlo tras insertarlo √ debe actualizar el nombre de usuario pruebas a prueba y devolver la confirmación GET usuario/id √ debe dar error 401 al no proporcionar cabecera de autenticación √ no debe encontrar al usuario de id 000000000000000000000000 √ debe mostrar la información básica del usuario prueba creado √ no debe seleccionar la contraseña entre la información básica del usuario y el nombre debe ser prueba POST usuario/login √ no debe devolver el token de sesión al no facilitar nombre de usuario √ no debe devolver el token de sesión al no facilitar contraseña √ no debe devolver el token de sesión al no facilitar nombre y contraseña correctos √ debe devolver el token de sesión POST usuariosPorFiltro √ no debe dejar hacer la búsqueda sin token de sesión (401) √ debe mostrar información básica correspondiente al usuario de nombre de usuario prueba √ no debe mostrar la contraseña del usuario prueba 97 passing (740ms)

Conjunto de casos de prueba implementados para probar el API de GuionMaker.

Con este conjunto de pruebas se puede estar seguro de que, en futuras implementaciones sobre

el APIHelper o cualquier clase-ruta existente, tenemos una buena referencia para saber si lo

nuevo ha tenido algún tipo de repercusión negativa sobre la funcionalidad prevista.

Por último, haciendo uso del editor en línea de Swagger se crea una especificación en YAML por

medio de la cual podemos documentar cómo es el API, qué modelos hay, su utilidad y cómo se

utiliza correctamente, incluyendo la posibilidad de que se puedan realizar pruebas por cada uno

de los métodos que están publicados. Preparada la especificación en YAML haciendo uso del

editor, se exporta el fichero JSON del que se ha de nutrir el cliente de Swagger y lo ubicamos

dentro del directorio público de nuestra solución.

Page 133: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

129

Ilustración 43. Directorio api-docs que da cabida al cliente de Swagger y nuestra especificación con nombre SwaggerSpec.json.

Para poder hacer referencia a este directorio, queda hacer una modificación en la aplicación

ExpressJS. Dicha modificación consiste en añadir este directorio entre los directorios estáticos

que pueden servir documentos html y excluir la ruta de aquellas a las que se pide autenticación

con JWT.

this.api.use(express.static(path.join(__dirname, '/public'))); this.api.use(jwtes({ "secret": this.config.secreto }).unless({ path: ['/api/usuario/login', '/api/usuario', '/api-docs', /^(?!\/api).+/] }));

Fragmento de la configuración de la aplicación ExpressJS para permitir que se pueda acceder públicamente al directorio api-docs.

Como resultado de estas modificaciones, los usuarios pueden consultar la información relativa

al API y hacer las pruebas que necesiten para comprender su funcionamiento e implementación.

Esta tarea ayudará en mucho a que se creen nuevos clientes en pos de ayudar a la difusión del

proyecto llegando al mayor número de dispositivos posible.

Ilustración 44. Apariencia del cliente de Swagger con la especificación creada por medio de su editor en línea para GuionMaker.

Page 134: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

130

6.4.4 Revisión y retrospectiva del sprint

Con la funcionalidad lista, a falta de implementar lo que falta en una siguiente iteración, y el

esfuerzo en las actividades de Ingeniería de Software de especificación del API y el conjunto de

pruebas sobre el mismo, se puede dar por concluida la iteración con una nueva revisión del

trabajo hecho.

Durante la revisión se ha hecho hincapié en motivos más estéticos que funcionales con el

objetivo de dar un acabado más personalizado a la solución. También se ha hecho uso de las

plantillas para personalizar la exportación de un guion literario de pruebas con éxito, lo cual

supone otra característica que se ha completado sin problemas.

Desde el punto de vista de las historias de usuario relacionadas con la edición colaborativa, se

echa de menos que un colaborador, asignado de escritura para un proyecto, pueda editar

además de visualizar los textos, pero es una funcionalidad que se acordó no desarrollar en pos

de las actividades con Swagger, Mocha y Chai.

Como resultado de la revisión, se incluye el logotipo de GuionMaker en la barra de navegación

y la opción de elegir entre un tema de Bootstrap claro u oscuro para la hoja de estilos.

Internamente, a la hora de valorar cómo se ha trabajado en la última iteración, estas son las

consideraciones a tener en cuenta para futuras entregas.

¿Qué es lo que se ha hecho bien?

o Se ha implementado un conjunto de pruebas completas de cara a reingenierías

o refactorizaciones futuras, en pos de facilitar el mantenimiento y evolución de

GuionMaker.

o Adicionalmente al conjunto de pruebas se ha mejorado la documentación

creando una especificación en Swagger.

o Saber explicarle la utilidad de estas actividades al cliente de cara a la gestión del

conocimiento y agilizar futuras iteraciones.

o Con respecto a la anterior iteración, esta vez las estimaciones y la funcionalidad

elegida a compatibilizar con la creación de pruebas y especificación han sido

holgadas.

¿Qué se podría mejorar?

o Teniendo en mente futuros proyectos y futuras iteraciones, las pruebas

deberían acompañar en su implementación la creación de nuevos métodos o

entidades del API.

o Misma idea acompaña a la especificación pública.

¿Qué es lo que se ha hecho mal?

o No tener en cuenta en la planificación desde un comienzo la creación de

pruebas y especificaciones a la par que se desarrollan los métodos del API. Pese

a que no es un fallo específico de esta iteración, sí ha obligado a postergar

funcionalidades de la solución planificadas para este sprint.

Page 135: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

131

6.4.5 Resultados visuales en el cliente durante el sprint

Tal y como se ha visto durante la documentación técnica, ya se ha descrito y mostrado el

componente de miga de pan que aporta trazabilidad en la navegación a los usuarios, sin

embargo, también se han creado nuevas vistas relacionadas con la gestión de plantillas. No hay

muchos cambios con respecto al resto de listados salvo la inclusión de un cuadro de información

o leyenda, en que se indica que los elementos del listado que van marcados con un asterisco son

destacados en caso de que haya un elemento de la lista marcada por dicho signo.

Ilustración 45. Listado de plantillas con una de ellas marcada como plantilla por defecto.

Por otro lado, la vista de detalle de las plantillas la componen mayormente dos áreas de texto,

donde los usuarios introducen el HTML correspondiente a la portada o a las escenas. Es

imprescindible que se incluyan los tokens que se indican en las etiquetas de las áreas de texto,

porque si no se incluyen, se cargará el estilo de la plantilla por defecto a la hora de exportar el

guion.

El único punto desde el que los usuarios van a poder marcar una plantilla como el registro por

defecto del que se va a tirar en la exportación de guiones es desde esta vista. Marcando la caja

de confirmación bastaría para habilitar la plantilla actual como aquella de la que el módulo de

exportación va a hacer uso en el momento en que el usuario decida exportar los detalles

literarios o técnicos en que ha estado trabajando.

Page 136: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

132

Ilustración 46. Vista de detalle de la plantilla "Nueva plantilla"

Se ha tratado de disponer los elementos de las vistas lo mejor posible para que Bootstrap haga

el resto y permita que la web sea utilizable en dispositivos con otras resoluciones diferentes a

las de PC, como móviles y tabletas. De esta forma nos aseguramos una buena accesibilidad y

experiencia de usuario desde, probablemente, la mayor fuente de tráfico hoy día en Internet,

los usuarios móviles.

Ilustración 47. Ejemplo de la vista de listado de escenas haciendo uso del tema oscuro en una pantalla móvil.

Page 137: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

133

6.4.6 Análisis de valor entregado y esfuerzo desempeñado

Gráfica de desempeño de esfuerzo

Ilustración 48. Se puede ver que la duración de esta iteración ha sido menor debido a la reducción de funcionalidades

Gráfica de entrega de valor acumulativo

Ilustración 49. La entrega de valor se posterga a la segunda semana, cuando se empiezan a cerrar todas las historias de usuario

0

1

2

3

4

5

6

7

8

9

10

SP (

pu

nto

s d

e h

isto

ria)

Fechas

Burndown chart de la iteración 4

0

20

40

60

80

100

120

140

160

180

200

UV

(u

nid

ades

de

valo

r d

e n

ego

cio

)

Fechas

Value up chart de la iteración 4

Page 138: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

134

Gráfica de estados de historias de usuario

Ilustración 50. En la primera semana se ponen en marcha todas las historias de usuario, funcionalidad que se empieza a entregar en la segunda semana.

Tras la revisión por parte del usuario y la introspección en la manera en que se ha desarrollado

la iteración, las gráficas ayudan a dar una imagen de cómo ha ido todo objetivamente.

La única y gran diferencia que se aprecia es que esta iteración ha durado bastante menos que

las anteriores con motivo de aplazar ciertas funcionalidades a un futuro desarrollo. Por otro

lado, se puede decir que se ha sido bastante ágil en la entrega de valor puesto que en la segunda

semana se entregan todas las historias de usuario correspondientes a lo pactado con el cliente.

Esta agilidad junto a la implementación de las pruebas y la especificación, hace que esta cuarta

iteración sea una buena base para comenzar a mantener y evolucionar el proyecto, teniendo en

cuenta que hay funcionalidades importantes que desarrollar en el futuro.

Page 139: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

135

7 Conclusiones

Tras cuatro iteraciones completas, se puede decir que el desarrollo del producto tal y como se

concibió en el Inception Deck ha sido un éxito. Como reza uno de los principios del manifiesto

ágil, el mejor indicador de que todo va bien es la satisfacción del cliente con el software

funcionando. A falta de pulir detalles e implementar el resto de funcionalidades que han

quedado pendientes, las sensaciones con el proyecto son satisfactorias.

Cliente web y API REST se integran a la perfección y se han establecido buenas bases

metodológicas y tecnológicas para finalizar con la implementación y pasar al mantenimiento y

evolución del producto. Una buena hoja de ruta y escuchar lo que el cliente desea primero ha

sido la clave de que esto fuera así.

En el trayecto, he ido aprendiendo una metodología de trabajo de manera práctica y, además,

he trabajado con tecnologías que hasta el momento desconocía. De gran ayuda es la comunidad

de desarrolladores tan amplia detrás de los proyectos en que me he basado para satisfacer

necesidades específicas expresadas en las historias de usuario. En ningún momento la tecnología

en que se ha basado la solución ha supuesto un obstáculo, sino que, por el contrario, lo ha

agilizado.

Al tratarse de tecnología relativamente moderna, en algunas de las librerías utilizadas como es

el caso de Mongoose, la documentación va por detrás de las funcionalidades y ha sido necesario

invertir tiempo de más en encontrar cómo abordar ciertos problemas. En parte, estos problemas

venían de no conocer bien cómo funcionaba un gestor de bases de datos orientado a

documentos como MongoDB. Sin embargo, los problemas se convirtieron en oportunidades de

conocer más en profundidad este gestor de base de datos y en posibilidades de explotar sus

ventajas aplicadas a GuionMaker trabajando en el diseño de la aplicación de una forma

diferente.

Si no se hubiera hecho uso de alguna metodología, dado que este proyecto se ha hecho a

intervalos irregulares por exigencias externas, no hubiera sido posible desarrollar algo a la altura

de las necesidades del cliente. Es por eso que le agradezco a Scrum las herramientas teóricas

que llevadas a la práctica han sido muy útiles para retomar el proyecto tras periodos inactivos.

Especialmente las revisiones y las retrospectivas, donde uno se da cuenta regularmente de los

aspectos a tener en cuenta de cara al siguiente día de trabajo. Sin olvidar también la vital

importancia de los backlog de historias de usuario negociadas al principio de cada sprint,

reflejando la funcionalidad acordada con el cliente, su criticidad y el esfuerzo a llevar a cabo.

Otra lección muy valiosa que me ha aportado este proyecto es la importancia de la

implementación de las pruebas en todo tipo de desarrollos. Postergarlas en GuionMaker a la

cuarta iteración ha sido un error que, tras subsanarlo, supuso un ahorro de tiempo increíble. La

sola presencia de casos de prueba que te aseguren que todo lo que has implementado funciona

como se espera y que, ese resultado, se genere en menos de dos segundos, es un factor a tener

en cuenta cuando se hacen las pruebas manuales en lugar de las automatizadas al terminar de

implementar cualquier tarea.

Con todo esto en mente, aún queda trabajo por hacer en GuionMaker para que el aspecto

colaborativo esté completo y sea de total ayuda para los creativos audiovisuales. Aun así, esa

funcionalidad es la punta del iceberg de un conjunto de gestiones ya implementadas que, a día

de hoy, ya aspiran a agilizar el trabajo del cliente en sus proyectos.

Page 140: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

136

Entre las funcionalidades a incluir en un futuro, además de las características sociales y la edición

multiusuario, sería interesante la exportación de los elementos de la biblia literaria con vistas a

que diseñadores o técnicos de imagen y sonido no tuvieran que acceder a la aplicación para ver

las propiedades y/o bocetos asociados a personajes y escenarios. Pero no sólo eso, también se

podría eliminar la dependencia de canales de comunicación externos añadiendo chat en la

edición multiusuario o un apartado de sugerencias en cada escena, haciendo más colaborativa

la edición. Eso desde la perspectiva de usuario, pero convendría de igual manera añadir una

figura de administrador para poder gestionar los registros y accesos al sistema, algo que se ha

dejado de lado por las funcionalidades más críticas del producto.

8 Bibliografía

[Beck K., 2002]

“Test Driven Development: By Example”

Addison-Wesley Professional

[C. Martín R., 2008]

“Clean Code: A Handbook of Agile Software Craftsmanship”

Prentice Hall

[Chodorow K., 2013]

“MongoDB: The Definitive Guide: Powerful and Scalable Data Storage”

O'Reilly Media

[Copeland R., 2013]

“MongoDB Applied Design Patterns: Practical Use Cases with the Leading NoSQL Database”

O’Reilly Media

[Dafydd Stuttard M., 2011]

“The Web Application Hacker's Handbook: Finding and Exploiting Security Flaws”

Wiley

[Fain Y. & Moiseev A., 2016]

“Angular 2 Development with TypeScript”

Manning Publications

[Holzner S., 2006]

“Design Patterns For Dummies”

For Dummies

[Hunt A. & Thomas D., 1999]

“The Pragmatic Programmer: From Journeyman to Master”

Addison-Wesley Professional

[Q. Haviv A., 2014]

“MEAN Web Development”

Packt Publishing

Page 141: core.ac.uk · Agradecimientos Después de unos intensos años en que he tenido la oportunidad de compaginar mis estudios con un trabajo orientado a la misma área de conocimiento,

137

[Rasmusson J., 2010]

“The Agile Samurai”,

The Pragmatic Programmers

[Redmond E. & Wilson J., 2012]

“Seven Databases in Seven Weeks: A Guide to Modern Databases and the NoSQL Movement”

Pragmatic Bookshelf

[Sommerville I., 2015]

“Software Engineering”,

Pearson Education

[Strauch C., 2011]

“NoSQL Databases”

University Hochschule der Medien, Stuttgart

[Sutherland J. & Sutherland J.J., 2014]

“Scrum: The Art of Doing Twice the Work in Half the Time”

Crown Business