Created by: Juan (admin) 4 months ago
Facelets es el mecanismo de plantillas empleado por JavaServer Faces (JSF). Esencialmente es un "JSP" mejorado para ofrecer más funcionalidad, convertir el documento en XML estricto y orientarlo totalmente al mundo de JSF.
En un principio Facelets fue el desarrollo de un único programador, que lo ofreció como código abierto y con una documentación bastante buena. En poco tiempo otros desarrolladores lo adoptaron en sus proyectos y pronto pasó a ser empleado como un estándar de facto para JSF. Algunas compañías se unieron también -notablemente JBoss, que desde el comienzo apostó por él en Seam- y rápidamente emplear JavaServer Faces se hizo sinónimo de emplear esta librería.
Finalmente la especificación de JSF 2 lo añadió como el mecanismo de plantillas por defecto, convirtiéndolo en un estándar oficial.
Facelets ofrece un API sencillo y muy limpio para implementar funciones EL. ¿Qué son exactamente "funciones EL"? Código que podemos invocar desde las plantillas de nuestras páginas. Facelets eliminó por completo la posibilidad de añadir scriptlets (fragmentos de código) típicos de las páginas JSP. Muchos desarrolladores -especialmente los menos experimentados- estaban abusando de ellos y las páginas se hacían difíciles de leer y mantener -cuando no introducían directamente problemas de arquitectura-. Facelets limitó ese uso de código puro potenciando el empleo de funciones y etiquetas que el programador podía diseñar fuera de la própia página para ser invocadas desde la misma.
Las etiquetas son exactamente eso: componentes estándar de JSF. Por ejemplo, un caso sencillo de seguridad en donde sólo quisieramos mostrar un contenido a un determinado rol podría ser:
La otra posibilidad sería escribir una función -código que se invocará-. En este caso podría ser:
Desde que la especificación del estándar JEE 6 fue presentada, quedó claro que habría aspectos en que su funcionalidad se solaparía con Spring: la plataforma empresarial de Java había aprendido mucho de SpringSource, pero seguiría un camino distinto. En su implementación actual, la inyección de dependencias es diferente, los APIs obviamente también lo son y, logícamente, las empresas detrás de ambos frameworks compiten en este espacio.
La reacción de Spring está siendo ignorar en parte algunos de los elementos presentes en JEE 6. Ambos frameworks son perfectamente integrables, pero si Spring ha sido siempre un líder en relación a la calidad de integración, se hace extraño ahora encontrar carencias en las librerías de Spring Security para JSF 2, la ausencia de apoyo firme para Facelets o que, en la documentación de uno de sus productos -Spring Roo, en su versión actual (XXX.XXX)- la sección referida a la generación de código para JSF 2 esté totalmente en blanco, los beans que se anotan empleando JSF 2 no son visibles para Spring, etc-.
En el caso concreto de Spring Security, se ofrece un pequeño conjunto de componentes y funciones para gestionar la seguridad. En algunos casos sin embargo se hace necesario ampliarlos y, como se puede ver, es algo trivial.
Una función básica requiere dos ficheros: uno en XML con la declaración de la función y otro en Java con la implementación.
El archivo XML debe contener este formato:
Hasta JSF 1.2
El DOCTYPE varía en función de si estamos empleando JSF 1.2 o JSF 2)
namespaceEs el nombre de este espacio de nombres. Podemos imaginar el espacio de nombres como el identificador único para estas funciones y componentes. Se emplea para evitar que dos funciones desarrolladas con el mismo nombre y parámetros por dos desarrolladores distintos colisionen. Más abajo lo veremos en acción y quedará más claro.
function-name - Es simplemente el nombre la función. Coincidirá con el nombre del método en Java.
function-class - Nombre cualificado de la función
function-signature - Firma de la función, con los parámetros y el tipo de retorno
Definir una función sencilla -comprobar si un usuario tiene un rol concreto en Spring Security- sería entonces:
<?xml version='1.0' encoding='UTF-8' ?> <!DOCTYPE facelet-taglib PUBLIC "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN" "facelet-taglib_1_0.dtd"> <facelet-taglib> <namespace>http://www.juanmedin.com/spring-security/taglibs</namespace> <function> <function-name>tieneRol</function-name> <function-class>com.juanmedin.security.taglibs.TieneRol</function-class> <function-signature>boolean tieneRol(java.lang.String)</function-signature> </function> </facelet-taglib>
La clase que contiene la implementación en Java es sencilla. En nuestro caso la dividiremos en dos partes: la implementación que satisface el contrato de las funciones para JSF (la clase TieneRol) y que delega las tareas en sí mismas a una segunda (XXX)
package com.juanmedin.security.taglibs; // Imports public class TieneRol extends TagHandler { private final TagAttribute rol; public TieneRol(ComponentConfig componentConfig) { super(componentConfig); this.rol = this.getRequiredAttribute("rol"); if(this.rol == null) { throw new TagAttributeException(this.rol, "Tienes que especificar el `rol`."); } } public void apply(FaceletContext faceletContext, UIComponent uiComponent) throws IOException, FacesException, FaceletException, ELException { if(this.rol == null) { throw new FaceletException("Tienes que proporcionar un rol. Ahora mismo es null."); } String roles = this.rol.getValue(faceletContext); if(roles == null || roles.trim().isEmpty()) { throw new FaceletException("Debes indicar un rol"); } if(isGranted(rols)) { this.nextHandler.apply(faceletContext, uiComponent); } } public static boolean tieneRol(String rol) { GrantedAuthority[] authorities = TagUtilities.getUserAuthorities(); for (GrantedAuthority authority : authorities) { if(authority.getAuthority().equals(rol)) { return true; } } return false; } }
Created by: Juan (admin) 11 months ago
A raíz de una entrada en la web de JavaHispano sobre unas declaraciones de Gosling en las que decía que poco le importaba ya Java como lenguaje quería comentar de forma más ampliada mi impresión sobre ello.
Lo cierto es que estamos en un punto interesante en la evolución de la plataforma Java: la desvinculación entre la máquina virtual y el lenguaje en sí mismo. Esto es algo que se tenía claro desde el mismo día en que se diseñó pero que no ha comenzado a tener fuerza hasta que han aparecido alternativas creíbles como lenguajes de desarrollo al propio Java.
Es difícil emplazar en el tiempo un momento concreto pero sí han existido varios eventos interesantes que parecen haberlo impulsado:
- La lentitud en la evolución de Java como lenguaje. Después de todos los cambios drásticos incorporados en Java 5 poco o nada nuevo llegó en Java 6. La versión 7 se hizo esperar y, con la compra de Sun por parte de Oracle, aún más -y ni hablar de la ya celebre falta de innovación de esta compañía, conocida por su impresionante fuerza comercial pero no por sus alardes técnicos-. Algunas personas se mostraron además partidarias de congelar la especificación del lenguaje -capturado muy bien en el podcast de Java Pose de comienzos del 2008 por Bruce Eckel- frente a quienes creían que debía continuar evolucionando. Era un caldo de cultivo ideal para quienes deseaban nuevas opciones.
- La aparición de otros paradigmas de desarrollo para los que Java no estaba preparado. Y especialmente los lenguajes funcionales en un mundo en donde la concurrencia era cada vez más importante -incluso en las máquinas de usuarios finales-.
- La aparición de Rails como framework para Ruby. Rails comenzó a ocupar desde el principio un espacio que pertenecía hasta el momento a Java y PHP. Su creador, David Heinemeier Hansson -más conocido en la comunidad como DHH-, una persona con opiniones muy fuertes, atacó directamente la complejidad y el coste de los desarrollos en Java. Muchos programadores comenzaron a usar este framework con Ruby, descubriendo este lenguaje de forma activa y las virtudes prácticas de emplear otro tipo de lenguajes más eficientes para el desarrollo Web. Es precisamente la plataforma Java desde donde llegaron una gran número de desarrolladores a Rails. Aunque, obviamente, existió también una corriente crítica con el recién llegado, nadie negó la elegancia y claridad de Rails y Ruby. Era un framework que explotaba las ventajas de un lenguaje muy flexible y cercano al desarrollador de una forma que era imposible de alcanzar desde Java.
A partir de su presentación en sociedad se pudo comenzar a leer todo tipo de opiniones en los blogs de reconocidos desarrolladores y líderes de la industria: defensas a capa y espada, ataques frontales, etc. Lo único cierto es que Rails no fue en forma alguna ignorado. Ahora ya existía una alternativa popular y comercial que mostraba las claras deficiencias de la programación en Java. El daño estaba hecho.
- Se había llevado al límite el lenguaje. Java, siendo diseñado específicamente para ser sencillo, no es tan moldeable como otras alternativas. Desde las primeras aplicaciones, los métodos encadenados (típicos de Hibernate y otras librerías y frameworks) para formar DSLs, la inclusión de complejos ficheros XML como pseudo-lenguajes para suplir las carencias existentes y los usos creativos de su capacidad de introspección, se había llegado al techo. Lo que se estaba viendo era lo que permitía el lenguaje y no era sencillo ir más allá.
El resultado en cualquier caso fue el sentimiento de que se escribía demasiado código en Java, el código no era tan legible como podría ser, no cubría los nuevos paradigmas de desarrollo, y por otro lado gestionar los DSL's camuflados como XML era un infierno. Si bien eso ya se sufría desde hacía tiempo, ahora había otros lenguajes y frameworks con aplicaciones en producción que facturaban millones para demostrarlo. Y, desde luego, siempre ha existido un deseo por explorar y avanzar en la comunidad Java. Que sería sin ese "deseo patológico por la complejidad" en la comunidad Java del que hablaba Rod Johson hace poco.
Varias razones:
- El valor de la marca 'Java' es indiscutible. Sobre todo en grandes corporaciones y la Administración. Se identifica como algo seguro, consolidado. Si en los 80 era típico el "nadie ha sido nunca despedido por contratar IBM" y en los 90 "Tampoco nadie ha sido nunca despedido por contratar Microsoft" durante la primera década del 2000 lo mismo era aplicable con Java como plataforma corporativa de desarrollo.
- Un conjunto de librerías disponibles increíblemente alto. Junto con C, Java es uno de los lenguajes con mayor número de librerías disponibles. Su número, en prácticamente cualquier escenario, es simplemente abrumador.. El coste de penetración de un nuevo lenguaje es absurdamente alto si hay que desarrollar todo desde cero pero, si es posible emplear la impresionante base de código existente (de la misma forma en que Ruby o Pythonemplean librerías en C), el escenario es completamente distinto.
- Facilidad de desarrollo entre diferentes arquitecturas de hardware. Toda la potencia de las librerías de Java se puede emplear en la mayor parte de las arquitecturas de hardware actuales sin apenas cambios. No hay que pasar por la pesadilla de las librerías en C, que deben ser recompiladas no sólo para cada plataforma sino para cada sistema operativo. Con Java disponer de un nuevo lenguaje es poderlo emplear inmediatamente en todas las máquinas. Y en la mayoría de los casos es tan sencillo como añadir un nuevo JAR.
Hay muchos lenguajes disponibles para la JVM, cada uno es un estado de madurez distinto. La situación es, sin embargo, muy diferente a cuando se incorporaron nuevos frameworks y librerías como herramientas "casi obligadas" de desarrollo en el pasado. O'Reilly acuñó el término "Alpha geeks" para referirse a las principales personas apasionadas por la tecnologías que definían el futuro de una tendencia en un sector. Un consenso entre ellos solía significar que una tecnología tenía muchísimas posibilidades de ser adoptada. Ocurrió contínuamente: cuando determinadas personas que eran consideradas "gurus" en la comunidad comenzaban a adoptar en conjunto ciertas soluciones, éstas llegaban al entorno corporativo: Struts hace años, Hibernate, Spring, ANT para luego pasar muchos de ellos a Maven, JPA cuando se aceptó que era una alternativa válida a Hibernate, etc, etc.
En su momento existió un consenso sobre que esas eran las mejores opciones en su campo y se comenzaron a implantar en entornos de producción reales. La diferencia es que no existe ese consenso en relación a los lenguajes.
No obstante hay dos que están presentes en muchos blogs, podcasts y foros: JRuby, una implementación de Ruby, y Scala, un lenguaje mixto funcional-orientado a objetos. Ambos con sus méritos y también ambos extremadamente interesantes. El último en llegar haciendo ruído ha sido Cylon, una evolución de Java a manos de Gavin King. Estoy seguro de que vamos a oír hablar mucho de ellos a la espera de que llegue una alternativa totalmente nueva, aunque también es cierto que mucha gente lo ha asociado a JBoss -RedHat- por venir de él. Esta "marca" no ha abandona nunca a Seam, por ejemplo, que no ha conseguido llegar tan lejos como podría por méritos propios.
Un ejemplo interesante sobre los problemas en la adopción de un nuevo lenguaje se pueden escuchar en el podcast de hace unas semanas de Java Pose. En él un grupo de desarrolladores con perfiles altos evalúan la idoneidad de añadir un nuevo lenguaje -en este caso Scala- en un equipo de desarrollo muy motivado y dispuesto a invertir tiempo personal en ello (uno de los mejores escenarios posibles). En esencia comentan cuatro problemas en el caso de Scala: falta de desarrolladores en el mercado para ese lenguaje, falta de experiencia ya no con el propio lenguaje -que se puede aprender relativamente rápido- sino en programación funcional, cambios contínuos en la propia definición del mismo (falta de madurez) y alto riesgo de incluir un nuevo elemento fundamental en el desarrollo que todavía está sin consolidar en una aplicación de misión crítica que deben mantener y continuar desarrollando.
Sin entrar en necesidades específicas de entornos concretos, hay algunas cosas que se han demostrado muy útiles y productivas en los escenarios que habitualemnte cubre Java. La fantástica capacidad de crear DSL's internos de Ruby ha llevado por un lado a crear un código increíblemente conciso y legible, con una reconocida productividad (aunque aquí habría que hablar de otras cosas, como su capacidad para redesplegar en caliente -el demoledor "turnaround" en el despliegue- sin traumas a pesar de JRebel -se dice que han encontrado a programadores fosilizados mientras introducían cambios en una clase-), que es algo que se echa muchísimo de menos en Java. Esta facilidad ha llevado a crear ficheros de apoyo escritos directamente en Ruby sin necesidad de XMLs infernales, difíciles de escribir y aún peores de entender cuando un equipo entero trabaja con ellos.
En relación a Scala y lenguajes funcionales, creo sinceramente que la búsqueda de opciones de concurrencia sencillas preocupan a un número bastante limitado y especializado de desarrolladores. No obstante las otras virtudes de Scala (evitar el exceso de código de Java, su flexibilidad para crear DSLs, ser un lenguaje estáticamente tipado -que lo hace más atractivo a los ojos de algunos desarrolladores-, su magnífica integración con las librerías existentes, etc) son extremadamente interesantes. Su complejidad -que algunos ya llegan a definir como el C++ de la JVM- puede ser sin embargo un problema para programadores casuales.
Hay que estar atentos a Scala y JRuby, con Cylon en el radar como potencial opción de futuro cuando esté disponible. El lenguaje que más tracción parece estar ganando es, sin embargo, Scala, con una notable diferencia frente a los demás.
Created by: Juan (admin) 11 months ago
He estado buscando una solución sencilla para realizar un backup semanal incremental de mi servidor. La opción por robustez, seguridad y coste ha sido Amazon S3.
Amazon S3 es un referente en este sentido pero a la vez no es un sistema con el que sea trivial interactuar de forma inmediata desde un shell Unix (nada de rsync, scp o demás). Las buenas noticias son que, en tanto es un servicio que lleva varios años funcionando, hay diversas herramientas disponibles para tratar con él.
Revisando las diferentes utilidades s3sync parecía, con diferencia, la mejor opción. Pero ha sido abandonada por su autor, quien ha comenzado a usar Tarsnap para sus propios backups.
No quería pasar por un servicio de terceros, así que siguiendo la búsqueda de una alternativa, me he encontrado con duplicity. Su punto fuerte es su sencillez de instalación y uso (en esta entrada de blog de Tim Riley se comenta de forma totalmente clara el proceso). Su punto débil es que pasamos a depender de este programa para recuperar los datos (no se podrán recuperar accediendo directamente al bucket en Amazon).
Si bien siempre es agradable poder acceder directamente a los archivos, en mi caso no representa un problema y es la solución adoptada.
Una combinación realmente buena: Amazon S3 + Duplicy = backups incrementales encriptados de forma sencilla en un espacio de la calidad de Amazon S3.
Una utilidad interesante para gestionar los buckets de forma gráfica es DragonDisk, disponible de forma gratuita para varias plataformas. Es un cliente de Amazon S3 que permite realizar tareas sencillas, especialmente el tedioso borrado de archivos.
Created by: Juan (admin) about 1 year ago
Drupal es un gestor de contenido (un CMS por su acrónimo en inglés) desarrollado en PHP, de código abierto de 10 años de antigüedad que está detrás del 1,5% de todos los sitios web del mundo –casi 800.000-
Su virtud ha sido la de crear un conjunto de librerías de desarrollo en PHP de gran calidad para controlar el día a día de una página web, permitiendo además a terceros ampliar su funcionalidad de forma estructurada, logrando crear una gran comunidad de usuarios y desarrolladores.
Drupal fue desarrollado por y para programadores, siendo esta es una de sus virtudes y a la vez, comparado con alternativas más enfocadas a usuarios finales, sus defectos: mucha de su funcionalidad y potencia viene a costa de implementaciones manuales de procesos.
Uno de los aspectos característicos de Drupal es el ofrecer unas librerías de desarrollo flexibles y potentes. Esto ha llevado a muchos programadores a escribir sus propias contribuciones -que modifican o amplían su funcionalidad- mediante los llamados “módulos”.
Se distinguen entonces dos partes claramente diferenciadas: el núcleo –la parte principal, autosuficiente que proporciona las librerías básicas- y los módulos, contribuciones de los programadores de la comunidad.
El núcleo es el que define las funciones básicas y la arquitectura y a la que se debe adaptar cualquier proyecto que se realice. Su desarrollo es muy cuidado y cada nuevo cambio profundo en el mismo implica una versión de Drupal (siento la actual la 7)
Entre otras cosas, aporta:
Los módulos proporcionan ampliaciones de todo tipo, ofreciendo desde funcionalidades muy sencillas como extraer un valor de la bolsa a grandes paquetes de funcionalidad, como una tienda virtual entera. A menudo forman cadenas de dependencias, necesitando tener módulos instalados para poder emplear los que dependen de ellos.
A nivel estructural, un módulo es conjunto de ficheros de código en PHP y modificaciones visuales (ficheros HTML, CSS, imágenes, etc.). Existen literalmente miles de ellos: se distribuyen como ficheros ZIP a través de la página de Drupal –que actúa como gran catálogo de los mismos- y se despliegan como directorios dentro de la instalación de Drupal. Cada módulo es gestionado por una o varias personas que se encargan de corregir errores y hacerlos avanzar, normalmente de forma gratuita. La calidad suele ser desigual, con módulos excelentes y muy bien acabados a auténticos desastres. La implicación de los desarrolladores suele variar también bastante, desde personas que siguen la discusión de la comunidad en torno a su desarrollo literalmente hora a hora a otras que ignoran el módulo y lo abandonan.
Es extremadamente atípico en Drupal encontrar módulos de pago siendo en casi todos los casos contribuciones gratuitas de código abierto de los autores.
Alguna funcionalidad que podemos encontrar a modo de ejemplo es:
Estos son sólo algunos de los ejemplos de la opciones actualmente disponibles. La funcionalidad de cada módulo interactúa con la de los demás, lo que lo hace extremadamente potente.
Por otro lado puede existir más de un módulo para cada cosa –esto es, diferentes programadores ofreciendo aproximaciones distintas entre las que podemos elegir-.
Drupal 6 es la versión actualmente instalada en el mayor número de máquinas. Drupal 7 ha sido lanzado en enero de este año siendo el futuro de este gestor de contenido. Su principal problema viene de los módulos que estaban disponibles, pues necesitan ser adaptados para la 7. Esto está ocurriendo lentamente y muchos aún están en fase beta o directamente no disponibles.
La plataforma sigue en crecimiento, con más usuarios, desarrolladores, libros, conferencias y presencia en la red año a año. Es el sistema empleado en la actualidad por numerosas multinacionales de primer orden y organizaciones gubernamentales, siendo simbólico el empleo de la misma por la Casa Blanca en Estados Unidos.
Hasta aquí ha sido una descripción general de este gestor de contenido. Vamos a destacar ahora qué aspectos es interesante conocer sobre Drupal.
La aproximación de los desarrolladores de Drupal ha sido interesante: definir unos criterios de nomenclatura para las funciones que se desean permitir modificar por otros módulos. Durante el proceso inicial de una invocación al servidor, aprovechando la capacidad de introspección de PHP, se comprueba si el usuario ha escrito funciones con ese nombre y, de ser así, se invoca su código.
El problema viene de la necesidad de prever por parte del programador original qué partes van a poder ser modificables por terceros desarrolladores. En caso de no estar éstas previstas, se puede proponer a este programador la inclusión de esas nuevas funciones.
El problema es dual: por un lado puede no ser aceptado, y por otro puede serlo pero tardar demasiado en llegar a una versión estable que podamos emplear en producción.
En caso de no integrar nuestra necesidad con el programador original, pasamos a ir por nuestra cuenta. Esto implica aplicar las correcciones que se produzcan en el módulo original, tanto en bugs como en problemas de seguridad y perder las evoluciones futuras o tener que integrarlas una a una con nuestro código modificado. Es una tarea mucho, mucho más seria de lo que parece, que consume gran cantidad de tiempo, es propensa a generar errores y debemos repetir periódicamente.
Aunque algunos de estos puntos se podrían resumir en aspectos más generales, son lo bastante importantes como para incluirlos de forma independiente.
Algunas cosas no pueden clasificarse como positivas o negativas, pero tienen definitivamente un peso real al elegir este CMS.
Es sencillo cegarse por la cantidad de funcionalidad disponible en Drupal vía su núcleo y módulos, o por la increíble cantidad de información disponible aportadada por su comunidad. Algunos de estos puntos pueden llevar a desestimar totalmente Drupal o incluso PHP como plataforma.
Hay diferentes escenarios: si tenemos un CMS que va a gestionar información estática que no cambia según el perfil y el propio usuario, no es un problema. Si la información depende del usuario (esto puede ser tan sencillo como mostrar los comentarios sin leer que tiene en un foro o tan complejo como mostrar una página tipo Facebook con información específica a cada persona que se conecte) entonces es un factor totalmente crítico a considerar.
Describir cómo optimizar el servidor y hacer una prueba real exigiría un artículo en sí mismo (Carles Climent ha escrito uno muy interesante en el wiki español de Drupal), pero algunas cosas, aunque obvias, es interesante comentarlas:
Es crítico desarrollar un aspecto concreto hasta el final porque sólo de esa forma se pueden ver las carencias reales de documentación del núcleo y los módulos afectados, la dificultad de trabajar con la peculiar arquitectura de Drupal, el esfuerzo real necesario en un módulo y los agujeros de formación que existen en el equipo.
De forma concreta, se puede plantear un escenario sencillo: personalizar cinco elementos de un foro. No vamos a entrar en la parte técnica, tan sólo en qué implica.
Montar un foro en Drupal es rápido. Añadir nuevos campos al perfil de un usuario también. Pero esa es simplemente la configuración por defecto, lo que ya nos dan hecho. ¿Qué ocurre si un usuario pide las siguientes cosas –nada descabelladas-?
Esencialmente hay dos problemas distintos: desconocimiento del API y coste de integración en Drupal.
Los puntos 1-3 son desconocimiento del API del núcleo. El coste en tiempo es encontrar qué funciones emplear y qué partes tocar para implementarlas. Hacerlo no es siempre tan evidente como parece y hay que recordar que no siempre disponemos de una documentación completa. Estos son sólo tres casos simples, pero en un desarrollo durante los primeros meses hay un bombardeo continuo. Por otro lado, suele ser normal no implementarlas de una forma afín a la arquitectura de Drupal, por lo que lo que estamos haciendo no es sólo perder el tiempo, sino poner bombas en el proyecto que estallarán en el futuro. La figura del mentor durante esta fase –alguien que conoce Drupal en profundidad- es extremadamente útil.
Los puntos 4-5 implican coste de integración. No sólo exigen conocer el API, es necesario conocer en profundidad la arquitectura de Drupal, en este caso en la parte de introducción de datos. Tan cercanos al usuario como pueden parecer (subir imágenes y vídeos) no son en absoluto inmediatos en tiempo de desarrollo. Obviamente podemos ofrecer al usuario la funcionalidad que ya existe en alguno de los módulos existentes, pero de ser necesario establecerla nosotros, el coste es muy alto si no conocemos en profundidad este CMS.
Por poner un ejemplo real: introducir imágenes de forma sencilla mediante es hoy en día un módulo existente con una persona detrás que ha tenido que dedicar una cantidad seria de tiempo y esfuerzo -y aún así existen flecos abiertos.