Breve reflexión sobre las leyes del tiempo

Existen infinidad de páginas en internet que pueden proporcionar información sobre técnicas de gestión del tiempo, así que el propósito de esta entrada es más una reflexión que otra cosa.

Advertidos quedan.

Una breve reflexión sobre las leyes que rigen la gestión del tiempo

Sobre la gestión del tiempo y las tareas existen leyes que son grandiosas en si mismas, por su simplicidad y por la carga de verdad que llevan asociada (nunca es el 100%, pero a veces, se aproxima de forma que que llega a espantar).

Voy a enumerar aquí unas pocas leyes con las que nos podemos encontrar en nuestra gestión del tiempo (personal y profesional, casi de manera diaria).

Ley sobre la expansión de los gases (y su extensión al tiempo que llevan las tareas)

Igual que la ley general de los gases donde se establece una relación entre Presión, Temperatura y Volumen que un gas ocupa, cualquier tarea tiende a dilatarse en el tiempo en función de la presión y la temperatura del proyecto. En condiciones ideales, una tarea tiende a dilatarse tanto tiempo como se le permita sin llegar nunca a finalizar.

Ley de Pareto (y su extensión a los resultados)

El economista Vilfredo Pareto dijo que el 80% de la riqueza estaba en el 20% de las manos. Esta ley es perfectamente extrapolable a los resultados de un proyecto. El 20% de las tareas de un proyecto puede llevar al 80% de los resultados.

Existe una variante de la Ley de Parto para la gestión de tareas. El 80% del tiempo inicial proporciona el 20% de los resultados, mientras que el otro 80% de resultados restantes se ejecuta en el 20% de tiempo que nos queda. En definitiva, se trata de la Ley sobre la expansión de las tareas en el tiempo enunciada como la Ley de Pareto.

Leyes de Acosta

Se trata de tres leyes muy fáciles de enunciar (en lo relativo a la gestión del tiempo, mejor lo claro y breve)

El tiempo que requiere una tarea crece cuando la interrumpimos y la reanudamos

Los orígenes de estas paradas pueden ser múltiples, externos e internos, pero lo que es muy cierto es que cada vez que interrumpimos una tarea requerimos de cierto esfuerzo para volver a retomarla.

Para una tarea corta siempre hay tiempo; para una larga siempre nos falta tiempo

Y es que siempre podemos encontrar un minuto o dos para completar una tarea. Y si podemos encontrarlo. ¡Mejor ahora que después! En cambio, finalizar una tarea que lleva cierta cantidad de tiempo suele ser un desafío en sí mismo, ya que el primer paso es encontrar tiempo para abordarla (así que divide y vencerás).

Lo perfecto rara vez es rentable

Y esto requiere de cierta disciplina personal y de mucho esfuerzo casi en cualquier entorno, sea profesional o personal. Equilibrar el grado de perfección (calidad del trabajo resultante) con el tiempo /esfuerzo invertido es una tarea difícil. Establecer objetivos o expectativas razonables en el resultado ayuda enormemente a que las tareas a realizar no se prolonguen de manera indefinida en el tiempo.

Leyes de Murphy

Y es que no podrían faltar porque.

Nada es nunca tan sencillo como parece

En realidad, a la hora de estimar el esfuerzo que nos lleva completar tareas es tan fácil subestimar como sobrevalorar.

Como decía Aristóteles “en el medio está la virtud

Si algo puede salir mal, saldrá mal.

¿Realmente hace falta que explique algo sobre esta ley? Pues si eres de esos optimistas redomados, déjame que te presente la siguiente.

Si algo va mal, no te preocupes. Irá peor.

Y el quid de la cuestión está en el “no te preocupes”.

¿Y hay algo que podamos hacer?

Afortunadamente sí (de otra forma mi trabajo como el de otros muchos sería una auténtica pesadilla).

El control de las tareas es un ejercicio fundamental en muchas áreas, también en la vida personal. Cuántas veces te has preguntado sobre un amigo o conocido: ¿Cómo lo hace, de dónde saca el tiempo?

Inmediatamente solemos buscar razones, sobre todo centrándonos en nuestro exceso de responsabilidad y, en consecuencia, el poco tiempo que nos resta para hacer todo lo que queremos. Bien, pues tengo una buena noticia:

Muchas veces nos equivocamos. Tenemos más tiempo del que creemos, sólo que no sabemos aprovecharlo tan bien.

En relación a la ley de los gases…

Procura establecerte plazos para finalizar tus tareas. Si haces una lista con tareas, marca un hito para finalizar cada tarea. Si tienes una aplicación para la gestión de tareas (como aTareado) asegúrate de que, como mínimo es posible establecer una fecha de finalización para cada tarea.

En relación a la Ley de Pareto (80% /20%)

Si el 20% de las tareas puede proporcionar el 80% de los resultados, significa que antes de comenzar cada proyecto deberías de crear una primera tarea que consista en analizar el resto y así poder priorizarlas. Los resultados rápidos incentivan y ayudan a valorar avance y reconducir los siguientes esfuerzos. Empieza por aquellas tareas que puedan proporcionarte resultados rápidos pero no ignores que el peligro se asoma tras las esquinas. Corres el riesgo de no terminar tu proyecto (ver la tercera ley de Acosta).

En relación a las Leyes de Acosta

Ley sobre la interrupción de las tareas

Esta ley depende mucho del entorno donde te encuentres. En muchos lugares de trabajo las interrupciones son frecuentes y, en ocasiones, incontrolables. Cuando trabajas con un equipo de personas, pon señales que les hagan darse cuenta de que estás concentrado en una tarea: semáforos, pegatinas, auriculares para escuchar música (esta última es la que trato de utilizar, sin demasiado éxito; mido la concentración en número de canciones escuchadas). Pero también proporciónales tiempo para que puedan acudir a ti, de otra forma lo único que estás haciendo es retrasar las interrupciones para otro momento.

Dedica tiempo a la gente que trabaja contigo y no tengas miedo a decir. “Si puede esperar, dame un rato, que termino esto y luego te atiendo”.

Ley sobre la disponibilidad de tiempo para las tareas cortas

Para una tarea corta siempre hay tiempo. Si una tarea lleva uno o dos minutos (contestar a un mail, comprobar la disponibilidad de un componente, verificar que tenemos suficiente aceite en la despensa…) entonces hazla. No la dejes para después.

Me encanta la palabra procrastinar, pero es uno de los mayores peligros en la ejecución de tareas. De manera opuesta, encontrar tiempo para realizar una tarea más larga suele ser más complicado. Hay tareas largas que no pueden dividirse (hacer una llamada a un familiar con el que hace tiempo que no hablas, pintar la pared del salón) pero otras que pueden dividirse en tareas más pequeñas (pintar la casa, repasar el cierre de cuentas mensual).

Cuando te enfrentes a tareas largas que son difíciles de acomodar en tu agenda, pregúntate si puedes dividirlas en tareas más cortas.

Ley sobre la rentabilidad en la ejecución de las tareas

La rentabilidad es es la capacidad de producir o generar un beneficio adicional sobre la inversión de tiempo o esfuerzo realizado. Algo que, así dicho suena un poco abstracto, es un punto de vital importancia a la hora de ejecutar tareas.

La cultura del perfeccionismo es una cultura muy implantada en la sociedad en que vivimos (bueno, al menos en mi ámbito de relación más cercano tanto profesional como personal). “Si vas a hacer algo, hazlo bien; si no mejor no lo hagas”

Este planteamiento es completamente válido cuando dispones de recursos o tiempo ilimitados para ejecutar una tarea: no existe razón ninguna para no hacer una tarea perfectamente cuando tienes todo el tiempo o toda la capacidad al alcance de tu mano. Sin embargo, esto rara vez ocurre.

Cuando hay que lidiar con tiempo o recursos limitados, es importante (fundamental) determinar el grado de corrección en la ejecución de la tarea con los recursos disponibles. Dejadme que ponga dos ejemplos:

Si para pintar la casa dispones de una cantidad suficiente, pero limitada de pintura, es importante que seas muy consciente de ello antes de comenzar la labor. De otra forma corres el peligro de invertir más cantidad de pintura al comienzo, tratando de que las paredes queden perfectas y te encuentres con la terrible situación de tener que dejar una pared sin pintar al final porque has terminado toda la pintura. Si, por el contrario, eres plenamente consciente de la limitación de recursos (pintura) probablemente el acabado final no sea el óptimo, pero probablemente sí el suficientemente bueno como para que puedas decir “bien, ya he pintado la casa”.

Otro ejemplo.

Frecuentemente nos encontramos con que dedicamos mucho tiempo al comienzo de la tarea (ver la ley de Pareto) buscando el ejecutar nuestro trabajo de la manera más perfecta posible. El tiempo que disponemos para ejecutar dicha tarea es finito, y podemos encontrarnos con que hemos invertido el tiempo en dejar primorosamente una tarea a la mitad. La mitad finalizada está perfecta, pero por desgracia, la tarea no se ha podido terminar a tiempo.

Ser consciente de  la limitación de recursos nos ayuda a invertirlos de manera más eficiente, quizá el resultado final podría ser mejor pero. ¿Podría ser mejor con los recursos /tiempo disponibles?

En relación a  las Leyes de Murphy

Murphy es Grande.

¿Hay algo más que se pueda decir?

Migrar código de Windows 8 a Windows Phone 8

Como he comentado en alguna entrada anterior, recientemente tuvimos la ocasión de liberar aTareado para Windows 8.

Una de las primeras tareas a llevar a cabo, incluso antes de abordar nuevas funcionalidades, consiste en re-organizar el código y dejarlo preparado para poder sacar el máximo provecho en Windows Phone 8 al trabajo realizado en Windows 8. De esta manera, las nuevas funcionalidades estarán disponibles con más rapidez en ambos sistemas y podremos disponer de una versión en ambos sistemas con la mayor cantidad de código compartido posible.

aTareado surgió como un ejercicio de aprendizaje para crear aplicaciones de la Tienda de Aplicaciones de Windows, así que algunas decisiones de diseño (y me refiero a diseño técnico de la aplicación) han sido tomadas sin tener más propósito que el mero aprendizaje. En consecuencia, tras revisar un poco dichas decisiones, hay algunos problemas que habrá que tratar (en concreto para aTareado, que es muy sencillo, la lista puede variar de una aplicación a otra)

  • El modelo de la vista está en el mismo proyecto que la vista, lo que va a dificultar la reutilización del mismo en un proyecto de Windows Phone. Será necesario sacarlo a una biblioteca portable para su reutilización en ambas versiones.
  • El almacenamiento se encuentra demasiado acoplado a la fuente de datos. En Windows 8 y Windows Phone 8 el sistema de almacenamiento es diferente, con lo que este acoplamiento es bastante poco recomendable. Habrá que crear abstracciones para el repositorio que nos permita sacar versiones diferentes para ambos sistemas operativos.
  • El modelo de la vista hace uso de clases comunes que están demasiado relacionadas con clases de utilidad de Windows 8, así que para reutilizar el modelo de la vista en una versión de Windows Phone 8 habrá que crear algún tipo de abstracción para definir funcionalidades generícas, existentes en sistemas operativos diferentes.

Por otro lado, hay algunas decisiones que pueden simplificar la reutilización del código entre las versiones de Windows 8 y Windows Phone 8.

  • Parte del aprendizaje estaba enfocado en el patrón de diseño MVVM con lo que ya existe una separación clara entre la vista y el modelo de la vista, lo cual facilitará reutilizar este último en una versión de Windows Phone 8.
  • De entre de las posibles implementaciones de MVVM escogimos MVVM Light, que tiene versiones portables que pueden reutilizarse con facilidad en Windows 8 y Windows Phone 8.
  • La implementación de MVVM hace uso extensivo del patrón de diseño de Inversión de Control (IoC) lo que simplifica enormemente aprovechar abstracciones ya creadas para implementar versiones en sistemas operativos diferentes sin necesidad de tocar demasiado el código. Además la utilización de este patrón simplifica las pruebas unitarias y con poco esfuerzo podremos aprovechar el trabajo ya hecho para probar las clases nuevas que creemos para la versión de Windows Phone 8.
  • Para la serialización de datos se ha utilizado la biblioteca Newtonsoft.Json que también tiene sus versiones portables.

Reutilización W8 /WP8

La siguiente lista no es exahustiva, de hecho se limita a enumerar las consideraciones que hemos tenido que tener en cuenta al tratar de sacar el máximo provecho al código de aTareado.

Separación de Responsabilidades en la Vista

Si quieres sacarle el máximo provecho a tu aplicación en ambos mundos, empieza planteándote que un patrón de diseño que separe claramente la presentación de la lógica de presentación va ser de enorme ayuda, al fin y al cabo el XAML de Windows 8 y el XAML de Windows Phone 8 no son iguales. En este mundillo, esto lo consigues (con más o menos facilidad) utilizando MVVM.

Es buena idea que te lleves el modelo y la vista del modelo a bibliotecas portables de clases ya que con ese sencillo planteamiento ya tienes una buena base que compartir entre tus diferentes proyectos (W8 y WP8)

Es decir, partimos de algo parecido a esto:

image

Para terminar en algo similar a esto otro:

image

En realidad, parece que la cosa se ha complicado mucho (y es cierto) pero sólo porque el punto de partida era casi trivial.

A la hora de llevarnos el modelo y el modelo de la vista a bibliotecas portables partiendo de una versión en W8, la utilización de la versión portable MVVM Light ha sido de gran ayuda, pero tampoco ha estado exenta de problemas. El mayor problema encontrado aquí deriva de un pequeño bug en NuGet.

Cuando haces referencia a la MVVMLight (PCL) a través de NuGet, se realiza un cambio en la configuración de tu biblioteca de clases introduciendo una referencia al Runtime que no debería de introducirse. La solución es sencilla, simplemente elimina dicha referencia del fichero de configuración (puedes comprobar más detalles aquí).

Abstracción de Funcionalidades del SO

Por otro lado la utilización de patrones como IoC favorece enormemente el desacoplamiento entre clases. Así que, es una magnífica idea que construyas una biblioteca de clases portable donde definir todas las abstracciones (interfaces, fundamentalemente) y una biblioteca específica por Sistema Operativo donde implementen los servicios que se definen en la biblioteca de abstracciones. Tal y como se indicada en el gráfico anterior, parece que la cosa se ha complicado bastante. Pues bien, se ha complicado, pero esta complicación no es muy grande y simplifica enormemente el mantenimiento de ambas versiones.

Si ya estás utilizando MVVM Light en tus proyectos, seguro que estás familiarizado con la clase ViewModelLocator que es la que hace un uso exahustivo de los contenedores y localizadores de dependencias; además se encarga de registrar la correcta implementación del modelo de la vista. El ViewModelLocator puede registrar también las correctas implementaciones de las abstracciones (es decir, el ViewModelLocator registrará las versiones de W8 o de WP8 de dichas abstracciones). Hay un buen ejemplo aquí, donde pueden encontrarse ejemplos simples de estos conceptos.

Mensajes, mensajes, mensajes

Como sabéis, MVVM consigue desacoplamiento mediante un par de elementos fundamentales. Uno es el DataBinding y el otro es el itercambio de Mensajes entre los diferentes elementos del diseño.

No solo se utilizan como parte del DataBinding para avisar a la Vista que una propiedad ha cambiado, también pueden (deben) ser utilizados para comunicar algunos objetos sin que haya acoplamiento entre ellos.

Un Ejemplo de Mensaje

Supongamos que necesito un temporizador que dispare un evento de forma periódica. Cada sistema operativo suele disponer de clases que implementan estos temporizadores, además estas clases suelen venir por “duplicado” según estén diseñadas para interactuar con las vistas (ya que se ejecutan en el hilo de UI) o bien si están diseñadas para temporizar alguna acción que ha de ejecutarse en un hilo separado de la presentación.

Para evitar un diseño complicado y reutilizable en dos sistemas operativos diferentes, es perfectamente posible definir una clase que represente un mensaje de temporización. MVVM Light contiene una clase encargada de propagar mensajes de manera muy sencilla y desacoplada. Así pues, si nuestro modelo de vista tiene que reaccionar a mensajes del temporizador, simplemente lo subscribiremos a dichos mensajes y en la implementación específica del sistema operativo, nos limitaremos a lanzar dicho mensaje en cada pulsación del temporizador. De esta manera, el temporizador estará definido en una biblioteca de clases específica para el sistema operativo y la comunicación con el modelo de la vista se realizará, de forma desacoplada, a través del Messenger.

Este tipo de diseños permiten el desacoplamiento, pero vienen a un coste en la mantenibilidad ya que es muy difícil dar seguimiento a qué objetos se suscriben/desuscriben a qué mensajes y bajo qué condiciones lo hacen. De esta manera realizara pruebas unitarias que validen este comportamiento no es trivial.

En Resumen

Conseguir una versión “portable” de aTareado no ha sido complicado, pero ha requerido cierto esfuerzo de pruebas y refactorización. Las mayores dificultades han venido de un par de problemas relacionados uno con el diseño del código de aTareado y otro con un bug en NuGet, la mayor parte del trabajo ha venido a la hora de probar todo de nuevo.

Si tienes previsto crear una aplicación para WP8 o para W8, antes de comenzar hazte una sencilla pregunta. ¿La quiero para ambos sistemas operativos? Si la respuesta es sí, toma como referencia estos sencillos pasos a la hora de hacer el diseño y te simplificará la vida. Si la respuesta es no, hazte otra pregunta: ¿Seguro?

Subiendo aTareado al App. Store

Ha habido cierta cantidad de trabajo por detrás, pero aTareado ya está disponible en el App. Store.

846x468

aTareado se trata de una pequeña aplicación que nació con la única intención de permitirme practicar para aprender algunos de los entresijos de la programación de Aplicaciones de la Tienda de Windows 8 (Windows 8 AppStore Applications).

Permite realizar una gestión muy sencilla de tareas personales, añadiendo nuevas tareas, permitiendo su clasificación (¡hasta 100 categorías diferentes!) y seguimiento de forma sencilla e intuitiva.

screenshot_05032013_092430

Esta aplicación es fruto del trabajo de varias personas que han aportado sus ideas (gracias  a los amigos Rafa, Íñigo, Dannan y Javi) y también su esfuerzo como es el caso de Dannan que, además de aportar sus ideas, se ha encargado del diseño de la interfaz y del logotipo y que, en mi humilde opinión, ha realizado un excelente trabajo. O el propio Rafa que está aprendiendo por su lado la programación en Android utilizando: ¡aTareado!

screenshot_05032013_092722

screenshot_05032013_092748

Lecciones aprendidas

Primero de todo: organización.

Por mucho desarrollo amateur que se trate, hacer las cosas bien puede tener cierto coste relativo a tiempo y esfuerzo en una primera etapa.

La utilización de un control de código fuente ha sido fundamental. En mi caso, he hecho uso del Team Foundation Service, que es una mágnifica herramienta gratuita (para equipos reducidos) disponible en la nube.

Uno podría pensar que en situaciones tan triviales como las del desarrollo de una aplicación como esta, en las que las personas que desarrollan son uno mismo, hacer uso de una herramienta como TFS es innecesario. Pero no es así.

TFS permite no sólo realizar una magnífica gestión del código fuente de tu aplicación, también proporciona herramientas para la planificación y seguimiento del ciclo de vida de tu aplicación (ALM, o Application Lifecycle Management). En este caso, ha sido de gran utilidad para que un grupo de amigos documente de manera muy sencilla los requisitos que nuestra pequeña herramienta va a utilizar, genere seguimiento de los mismos (el avance lo vamos a dejar de lado, porque la vida profesional y personal deja poco espacio para este tipo de aficiones) así cómo la creación y seguimiento de nuevas incidencias (parte en la que Rafa ha resultado ser un auténtico crack)

Segundo: el conocimiento

Programar aplicaciones Windows 8 del App Store no es difícil, pero requiere cierta curva de aprendizaje.

El Framework

Si bien programas aplicaciones de la Tienda de Windows utilizando el Framework .NET 4.5, la realidad es que no tienes al alcance de tu mano el Framework Completo si no un subconjunto y un no tan pequeño recubrimiento alrededor del mismo.

No puedes hacer todo lo que estás acostumbrado a hacer. Muchas cosas tienen planteamientos diferentes y, en general más restrictivos (como, por poner un ejemplo, el acceso a ficheros) y otros aspectos sencillamente están fuera de tu alcance con este subconjunto de APIS que se pone a tu disposición como programador. ¿Es esto malo? Bueno, la discusión está bastante manida y no tengo nada nuevo que añadir a la misma, pero es la dicotomía entre la “protección” del usuario frente a la “libertad” del programador.

Me permito el lujo de poner “protección y “libertad” entre comillas ya que son aspectos un tanto controvertidos. ¿Nececsita el usuario ser protegido? Y, si es el caso, esta protección ¿no puede llevar fácilmente a una sobre-protección y, en consecuencia a una limitación de lo que el usuario puede hacer con la máquina y el sistema operativos que ha pagado? Y, por el otro lado. ¿Tiene el programador la “libertad” de hacer lo que quiera? Dejadme que os ponga un ejemplo: ¿Cuántas veces habéis tenido que aceptar que una aplicación acceda a tu localización y a internet sin que haya una causa funcional que justifique dichas capacidades? ¿Para qué quiere el programador acceder a mi localización y a internet si la aplicación lo único que hace, en principio, es encender el led de mi terminal?

El Lenguaje Visual y el Diseño

Es de sobra sabido que Windows 8 incluye un nuevo lenguaje de diseño, entendido como el conjunto de reglas, guías y prácticas a seguir para mostrar la información al usuario y para facilitarle al mismo el acceso y modificación de los datos que muestra su aplicación.

Este lenguaje, conocido como Modern UI tiene sus ventajas, pero también sus limitaciones ya que obliga a plantearse cosas de manera muy diferente a cómo un diseñador lo ha venido haciendo previamente (y si no que se lo digan a Dannan).

Comprender este lenguaje y crear un diseño partiendo de él es uno de los primeros puntos a tener claros.

El Lenguaje de Programación para la Interfaz Gráfica

Ahora sí, estamos hablando de XAML. Mi experiencia como programador no me ha llevado a cruzarme con este lenguaje de marcado hasta que no me he metido en esta pequeña aventura. La primera consecuencia de este punto es que el 80% de mi tiempo se ha ido en tratar de averiguar cómo hacer esto o aquello con XAML.

Si además le sumamos el hecho de que algunas características disponibles en WPF o Silverlight no lo están en el subconjunto de XAML que nos proporciona Windows 8, el desafío se ha presentado todavía más interesante.

Conclusión, si no sabes XAML y estás interesado en realizar Aplicaciones de la Tienda de Windows 8 debes de plantearte dos alternativas:

HTML + Javascript: Si ya dispones de esos conocimientos, probablemente tu curva de aprendizaje se reduzca.

XAML /C# XAML /VB.NET o XAML /C++: Esta alternativa requiere conocimientos en ambos lenguajes de cada pareja. En mi caso, la elección fue esta sencillamente por los prejuicios (fundados, siempre desde mi punto de vista, por supuesto) que tengo sobre Javascript. Si no conoces XAML asume que tu curva de aprendizaje será pronunciada en cuanto quieras que tu aplicación se distinga de alguna manera del resto.

Tercero: Las Ganas

Aprender es un reto, y sin ánimo de entrar en filosofías baratas, también es una forma de plantearte tú vida. Sin aprendizaje hay estancamiento y el estancamiento es una situación que en ocasiones puede resultar cómoda, pero que en el medio plazo (por no decir en el corto) lleva a un único sitio: “a ninguna parte”.

Evidentemente realizar aplicaciones es apasionante, pero también requiere esfuerzo. Sin la energía y las ganas, es muy difícil sacar cualquier proyecto adelante.

¿Y ahora qué?

Pues que aTareado ya está en el Store y, como no puede ser de otra manera, tiene bugs (algunos pequeños y otros no tanto). Además, ahora mismo proporciona una funcionalidad completa, pero sencilla en extremo. ¿No sería bueno poder organizar las tareas en listas? ¿Quizá poner avisos? ¿O integrarlas con el calendario? ¡Eh, que no hemos puesto tareas recurrentes! ¿Oye, y si pudiéramos hacer GTD con aTareado? ¿Y ponerle un módulo de Pomodoro? ¿Para cuándo la versión de Windows Phone y de Android?

Como véis, hay ganas.

¿Habrá tiempo?

¿Mostrar el cursor en un TextBox?

Mucho tiempo sin poner nada en el blog. Demasiado.

Probablemente siga durante una temporada sin poder contribuir de forma regular, pero vamos a intentar de que sea información útil (subrayemos intentar).

Durante los últimos meses he estado trabajando en un proyecto personal con un grupo de amigos. Espero que pronto pueda subir una entrada en este blog, de momento diré que estoy un poco aTareado.

Al grano. ¿Qué pasa con los cursores (caret)?

Dejadme que os sitúe: Windows 8 App Store y sus pequeñas cosillas (con el tiempo estoy descubriendo que no le faltan pequeñas cosillas)

Estamos en fase de pruebas y, repentinamente me llega una pequeña incidencia (pequeña por ser trivial en apariencia): “Oye, Juan, que el cursor no se ve cuando el campo de texto coge el foco y empiezo a escribir en él”

image

Vaya, claro. ¡Cómo va a verse si tiene un feo color negro sobre un fondo azul oscuro! Nada, no nos preocupemos. Bastará con dar con la propiedad o estilo que afecte al cursor (caret), y de paso al aspa de borrado, y la cambiamos de negro a blanco. ¿Verdad?

Pues no.

Dicho sea de paso que el aspa de borrado sí puede tocarse, pero no ocurre lo mismo con el cursor, tal y como puede verse aquí.

La referencia tiene casi medio año en el momento en que esta entrada se escribe y, sigo sin encontrar nada que lo contradiga en la procelosa web.

Así que. ¿Qué hacer?

Pues bien habrá que conformarse (de momento) con una solución un tanto desagradable: Cambiemos el fondo del cuadro de texto cuando el mismo tenga el foco.

Dicho y hecho.

Vamos a hacerlo de dos formas. Una utilizando únicamente XAML y la otra con unas pocas líneas de código que escribiremos por detrás.

Con la ayuda de Blend

Esta forma es muy sencilla y rápida. Primero, con la ayuda de Visual Studio (más porque lo tengo ya abierto que porque sea la única forma). Pulsaré con el botón derecho sobre el control y crearé una copia de la plantilla del mismo.

image

Una vez creada la copia podemos seguir en Visual Studio, pero os recomiendo que paséis a Blend donde podremos editar las propiedades según el estado visual de manera más sencilla (el paso descrito en el párrafo anterior también puede realizarse en Blend, por supuesto).

image

Pues bien, ya tenemos Blend mostrándonos la plantilla del control.

image

Centrémonos en la esquina superior izquierda, donde se enumeran los estados visuales del control. Concretamente, nos fijaremos en el estado visual “Focused”.

Una vez seleccionamos dicho estado, Blend se pone a “grabar” los cambios que realicemos sobre las propiedades del control. Concretamente nos centraremos en dos elementos del control de texto: “BackgroundElement” y “ContentElement”

image

En mi caso he creado un color blanco con transparencia, he creado un recurso para dicho color (luego veremos por qué) y se lo he asignado al fondo “background”.

Lo mismo para el color del texto (originalmente blanco). He creado un color negro, he creado un recurso para dicho color (de nuevo, un poco más adelante veremos por qué) y se lo he asignado al color de la fuente “foreground”.

image

Como veis, con muy poco esfuerzo hemos conseguido el objetivo secundario (no he cambiado el color del cursor, pero al menos consigo que se vea):

image

image

Con la ayuda de unas líneas de código

Sin embargo, no todos los controles de texto en la aplicación son TextBox, en algún punto del desarrollo decidimos introducir unos controles de terceros sobre los que la creación de la plantilla con Blend no nos proporciona el resultado adecuado. ¿Qué hacer entonces?

Escribamos un poquito de código, que no se nos caerán los anillos.

Lo primero es crear una clase que herede de la clase que representa el control de texto.

public sealed class MiTextBoxExt : TextBoxExtDeOtros
    {
        #region Constructor

        static MiTextBoxExt()
        {
            ResourceDictionary dict = new ResourceDictionary();
            dict.Source = new Uri("ms-appx:///Themes/TextBoxTemplate.xaml");

            FOCUSED_BACKGROUND_COLOR = dict["FocusedTextBoxBackground"] as SolidColorBrush;
            FOCUSED_FOREGROUND_COLOR = dict["FocusedTextBoxForeground"] as SolidColorBrush;
        }

        /// <summary>
        /// Constructor por defecto de la clase
        /// </summary>
        public MiTextBoxExt(): base()
        {
        }
        #endregion

        #region Miembros Protegidos

        protected override void OnGotFocus(Windows.UI.Xaml.RoutedEventArgs e)
        {
            base.OnGotFocus(e);

            _previousBackground = this.Background as SolidColorBrush;
            _previousForeground = this.Foreground as SolidColorBrush;

            this.Background = FOCUSED_BACKGROUND_COLOR;
            this.Foreground = FOCUSED_FOREGROUND_COLOR;
        }

        protected override void OnLostFocus(Windows.UI.Xaml.RoutedEventArgs e)
        {
            base.OnLostFocus(e);

            this.Background = _previousBackground;
            this.Foreground = _previousForeground;
        }

        #endregion

        #region Miembros Privados

        //Brush 
        private static readonly SolidColorBrush FOCUSED_BACKGROUND_COLOR;
        private static readonly SolidColorBrush FOCUSED_FOREGROUND_COLOR;

        private SolidColorBrush _previousForeground;
        private SolidColorBrush _previousBackground;
        #endregion
    }

Como se puede ver la solución es muy sencilla.

  1. Recuperamos del diccionario de recursos los colores definidos en el método anterior (así, si cambiamos el estilo no será necesario cambiar el código)
  2. Cuando el control toma el foco se almacenan los colores de fondo y fuente originales para luego poderlos restaurar con comodidad.
  3. Cuando el control pierde el foco, simplemente restaura los colores previamente almacenados.

Conclusión

Pues que no hemos solucionado el problema, pero hemos encontrado una forma de evitarlo.

¿Sabe alguien alguna forma mejor?

[WinRT] Reutilizando Paths

Recientemente me he encontrado con algún problema que todavía no he conseguido solucionar (espero que gente más sabia que yo sepa guiarme). Mientras el consejo llega, hay que seguir avanzando, así que aprovecho para contar qué bonitas sorpresas me he encontrado.

El Problema

Estoy creando un pequeño control de usuario muy sencillo, dicho control debería de mostrar información gráfica en modo de icono que represente categorías asociadas al elemento asociado al control de usuario. A modo de ejemplo nos bastaría con lo siguiente:

image

Como podéis ver se trata de algo extremadamente sencillo y su comportamiento es como cabría esperar cuando se muestran varias instancias de dicho control.

image

El siguiente paso es ser un poco más flexible: supongamos que cada instancia del control tiene asociada una categoría que puede cambiar, además deseamos que el gráfico vectorial que representa la categoría sea diferente para cada categoría.

Si queremos que nuestro control cambie de gráfico, nos bastaría con cambiar el Path (asociando el Path al modelo, lo cual no me gusta demasiado) o bien generando estilos y cambiando los estilos dependiendo de la categoría.

Esto último, por alguna razón, me sonó bien así que me puse con ello (un par de ejemplos deberían de ser suficientes para captar la idea):

 

    <Style x:Key="Calendar_Icon" TargetType="Path">
        <Setter Property="Data" Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M152.874,228.86L161.875,220.539C172.403,225.634 179.081,229.51 190.915,237.946 213.161,212.699 227.862,199.89 255.186,182.89L258.115,189.627C235.581,209.291 219.078,231.194 195.314,273.811 180.657,256.55 170.875,245.546 152.874,228.86z M152.874,335.86L161.875,327.539C172.403,332.634 179.081,336.51 190.915,344.946 213.161,319.699 227.862,306.89 255.186,289.89L258.115,296.627C235.581,316.291 219.078,338.194 195.314,380.811 180.657,363.55 170.875,352.546 152.874,335.86z M357.583,220.5L287.583,220.5 287.583,200.5 357.583,200.5 357.583,220.5z M357.583,238L287.583,238 287.583,258 357.583,258 357.583,238z M357.583,337.167L287.583,337.167 287.583,317.167 357.583,317.167 357.583,337.167z M357.583,354.667L287.583,354.667 287.583,374.667 357.583,374.667 357.583,354.667z" />
    </Style>

    <Style x:Key="Todo_Icon" TargetType="Path">
        <Setter Property="Data" Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M162.874,277.426L179.23,262.305C198.363,271.563 210.496,278.607 232.001,293.936 272.427,248.059 299.14,224.784 348.793,193.891L354.115,206.133C313.168,241.867 283.178,281.668 239.996,359.11 213.361,327.741 195.585,307.746 162.874,277.426z" />
    </Style>

Si ahora llevamos esto a nuestro control de usuario…

image

Lo primero que nos llama la atención es que el diseñador no muestra nada. Pero el comportamiento en tiempo de ejecución es todavía más raro.

image

Sólo se muestra el icono para la primera instancia del control Surprised smile

La solución

Pues para ser sinceros, todavía sigo sin dar con cuál es la causa de este comportamiento. ¿Algún error de primero de WinRT que se me haya escapado?

Evitando el Problema

Si bien es cierto que todavía no he dado con la causa del problema, me resisto a abandonar mi idea de utilizar gráficos vectoriales ya que escalaran mucho mejor que los iconos cuando cambie la resolución de la pantalla, así que vamos con una posible forma de “evitar el problema”

El fichero StandarStyles.xaml que encontraréis en cualquier solución no vacía de Visual Studio 2012 para aplicaciones del Store de Windows contiene una gran cantidad de recursos de estilos que podemos utilizar de salida en cualquiera de nuestras soluciones.

Concretamente, para la barra de tareas hay un buen montón de botones disponibles y todos ellos se basan en la misma idea.

  1. Crear un botón que defina fundamentalmente la estructura y composición del botón (de forma resumida consiste en un StackPanel, con un contenido interior, un marco circular que rodea dicho contenido y un texto)
  2. Un conjunto de estilos que lo único que hacen es cambiar el icono que se muestra en el contenido interno.

Siguiendo esa misma idea, voy a crear un estilo para botón que defina la estructura común (esencialmente, un botón sin marco) para luego crear un conjunto de estilos que definan el Path que dibuje el icono deseado.

<Style x:Key="NoBorderButtonStyle" TargetType="ButtonBase">
        <Setter Property="MinWidth" Value="0"/>
        <Setter Property="MinHeight" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ButtonBase">
                    <Grid Background="Transparent">
                        <ContentPresenter x:Name="Text" Content="{TemplateBinding Content}" />
                        <Rectangle
                            x:Name="FocusVisualWhite"
                            IsHitTestVisible="False"
                            Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="1.5"/>
                        <Rectangle
                            x:Name="FocusVisualBlack"
                            IsHitTestVisible="False"
                            Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="0.5"/>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPointerOverForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationPressedForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="FocusStates">
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualWhite" Storyboard.TargetProperty="Opacity"/>
                                        <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualBlack" Storyboard.TargetProperty="Opacity"/>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Unfocused"/>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="CheckStates">
                                <VisualState x:Name="Checked"/>
                                <VisualState x:Name="Unchecked">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ApplicationSecondaryForegroundThemeBrush}"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Indeterminate"/>
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Este ejemplo todavía no está muy trabajado, así que puede tener errores (avisados quedáis).

A continuación un estilo que aplica también a botones pero que define un Path en el contenido interior del botón (podría haber fusionado ambos en uno, pero de momento lo dejaré en dos estilos por separado).

    <Style x:Key="PathBasedButtonStyle" BasedOn="{StaticResource NoBorderButtonStyle}" TargetType="ButtonBase">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid>
                        <Path 
                            Stretch="Uniform"
                            Fill="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"
                            Data="{Binding Path=Content, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
                    </Grid>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Por último, ya podemos definir un estilo diferente por cada Path que queramos reutilizar en los botones de nuestra aplicación.

    <Style 
        x:Key="CalendarIconPathBasedButtonStyle" 
        BasedOn="{StaticResource PathBasedButtonStyle}"  
        TargetType="ButtonBase">
        
        <Setter 
            Property="Content" 
            Value="F1 M373.529,81.667C364.437,81.667,355.715,78.069,349.265,71.659L327.5,50 184.5,50 162.733,71.658C156.285,78.069,147.562,81.667,138.47,81.667L79.5,81.667 79.5,462 432.5,462 432.5,81.667 373.529,81.667z M392,421.5L119,421.5 119,121.5 181.333,121.5 206.25,146.5 307.5,146.5 332.75,121.5 392,121.5 392,421.5z M152.874,228.86L161.875,220.539C172.403,225.634 179.081,229.51 190.915,237.946 213.161,212.699 227.862,199.89 255.186,182.89L258.115,189.627C235.581,209.291 219.078,231.194 195.314,273.811 180.657,256.55 170.875,245.546 152.874,228.86z M152.874,335.86L161.875,327.539C172.403,332.634 179.081,336.51 190.915,344.946 213.161,319.699 227.862,306.89 255.186,289.89L258.115,296.627C235.581,316.291 219.078,338.194 195.314,380.811 180.657,363.55 170.875,352.546 152.874,335.86z M357.583,220.5L287.583,220.5 287.583,200.5 357.583,200.5 357.583,220.5z M357.583,238L287.583,238 287.583,258 357.583,258 357.583,238z M357.583,337.167L287.583,337.167 287.583,317.167 357.583,317.167 357.583,337.167z M357.583,354.667L287.583,354.667 287.583,374.667 357.583,374.667 357.583,354.667z" />
    </Style>

Ahora podemos reutilizar en nuestro control el botón que hemos definido:

<UserControl
    x:Class="Controls.BlogSamplePathProblem"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Controls"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    
    <Grid Background="CornflowerBlue">
        <StackPanel Orientation="Vertical">
        <Path 
            x:Name="CheckMarkPath" 
            Data="F1 M389.814,253.305L389.814,426.354 50,426.354 50,86.537 366.809,86.537 325.568,127.777 91.24,127.777 91.24,385.114 348.575,385.114 348.575,294.365 389.814,253.305z M409.469,85.646L245.997,249.117 175.19,178.291 122.683,230.808 245.998,354.16 462,138.143 409.469,85.646z"
            Fill="White"
            Stretch="Uniform"
            Margin="10,10,10,0" Height="100" UseLayoutRounding="False" Width="100" />

            <Button 
                Margin="10,10,10,0" 
                Height="100" 
                UseLayoutRounding="False" 
                HorizontalAlignment="Stretch"
                Width="100" 
                Style="{StaticResource CalendarIconPathBasedButtonStyle}"/>
        </StackPanel>
    </Grid>
</UserControl>

Lo primero de todo es que nuestro diseñador SÍ que muestra el botón con el gráfico vectorial.

image

Por último, podemos ver nuestra aplicación en tiempo de ejecución…

image

Y… ¡Ahí está! Los tres controles mostrando sus gráficos vectoriales tal y como cabría esperar.

Si consigo un poco más de información al respecto procuraré actualizar la entrada con la misma.

Problemas de estilo TFS 2012 Web Access

Recientemente me instalé el Team Foundation Server en mi máquina de casa donde puedo tenerlo para realizar mis pruebas y experimentos.

Todo va perfectamente salvo un pequeño detalle: al tratar de acceder a la interfaz web me encuentro con algo completamente carente de estilos.

image

Como podéis ver, abriendo la herramienta de desarrollo web que viene con IE se puede ver un error a la hora de cargar las hojas de estilo.

Un poco de búsqueda por la web me llevó a este hilo donde se menciona una solución que, en mi caso funcionó perfectamente.

El problema viene, aparentemente, al intentar cargar contenido estático. Así que, de cabeza al panel de control, activar o desactivar características de windows, internet information services habilitamos la característica correspondiente.

image

Y… ¡Ya está! Mi Team Foundation Server 2010 web access completamente operativo.

image

Windows 8: Terminando Apps.

Un detalle interesante sobre el ciclo de vida de las aplicaciones Windows 8 (del que ya hablé brevemente aquí) es que al estado “Terminado” puede llegarse por varias razones.

Un paseo por la vida de…

Ya hemos visto en otras ocasiones el ciclo de vida de una aplicación del App. Store.

image_thumb[1]

La aplicación del App. Store que se encuentra en pantalla es aquella que se está llevando el tiempo de procesador (no es cierto del todo, pero a efectos de esta entrada, nos sirve la simplificación). Si el usuario cambia de aplicación, la que estaba en ejecución pasa a estado “Suspendida” y la que el usuario acaba de arrancar pasa a estado “En Ejecución”.

Sin embargo, no es lo mismo que una aplicación pase al estado “Terminada” porque el S.O. reclame los recursos que esta ocupa mientras está suspendida (e.d. reclame la memoria ocupada) que una aplicación pase al estado “Terminada” porque el usuario decida finalizarala (ya sea utilizando Ctrl Alt+F4 o arrastrándola a la parte inferior de la pantalla). En el segundo caso no queda rastro de la aplicación en la interfaz de usuario, en el primer caso todavía aparece la aplicación en la zona izquierda de la pantalla, tal y como estaría si la aplicación siguiera suspendida y no terminada (y si lo piensas es normal, el usuario no tienen por qué saber que el S.O. ha terminado la aplicación y, si esta desaparece sin motivo aparente de la parte izquierda de la pantalla, la sensación del usuario sería que algo no marcha del todo bien en su dispositivo).

I’ll be back.

Pues sí, tal y como dijo Terminator: “volveré”, pero no de cualquier manera. Cuando una aplicación pasa del estado “suspendida” al estado “running” debería de ser capaz de restaurar el estado en que se encontraba previamente (supongo que aquí no tendremos discusión). Cuando una aplicación es terminada por el usuario, no existe motivo aparente para querer volver al mismo estado donde la aplicación se encontraba antes de ser finalizada (bueno, salvo que tu aplicación ofrezca esta característica al usuario, claro está). Pero. ¿Qué pasa cuando una aplicación suspendida es finalizada por el SO y posteriormente vuelta a poner en marcha por el usuario?

Pues bien, seamos coherentes con el comportamiento del SO. Si Windows 8 pretende que todo siga como si la aplicación nunca hubiera sido terminada (al fin y al cabo, su tile sigue disponible en la zona izquierda de la pantalla), nuestra aplicación debería de ser capaz de restaurar el estado como si volviera de “suspendida” en vez de “terminada”.

¿Pero, es posible determinar durante el arranque si una aplicación fue terminada por el SO o por el usuario?

Pues sí, y es bastante fácil.

   1: if (args.PreviousExecutionState == ApplicationExecutionState.ClosedByUser)

   2: {

   3:     // Comprobar aquí si el usuario quiere que se recuerde su estado anterior de navegación    

   4:     // Y si así es, entonces cargar dicho estado.

   5: }

En definitiva. Parte de la filosofía que se esconde tras las aplicaciones disponibles en la nueva Tienda de Windows 8 es proporcionar coherencia en la experiencia de usuario en su nuevo dispositivo. Es interesante estar pendiente de estos pequeños detalles para proporcionar dicho grado de coherencia.

Depurando una aplicación Windows 8

Llevaba ya mucho tiempo sin escribir ninguna entrada, así que este es un buen momento para retomar el hábito.

Recientemente he estado revisando el desarrollo de aplicaciones Windows 8 (utilizando WinRT).

Existen algunas semejanzas con las experiencias previas en Windows Phone 7.5, pero no nos engañemos, son semejanzas superficiales. Sin embargo, sí es cierto que el ciclo de vida de las Aplicaciones de Tienda de Windows 8 (madre mía, menudo nombre) es prácticamente idéntico al ciclo de vida de aplicaciones Windows Phone.

El Ciclo de Vida

Nos encontramos con el siguiente conjunto de estados.

image

Como podéis ver, prácticamente idéntico al ciclo de vida de aplicaciones Windows Phone 7.5

De nuevo cabe destacar que, una vez una aplicación ha sido suspendida, no hay garantía de que vuelva a pasar a “en ejecución” ya que el S.O. puede decidir descargarla de memoria en cualquier momento.

Depurando los cambios de estado

Una de las las facilidades que se encuentra con Visual Studio 2012 a la hora de depurar aplicaciones Windows 8 es la posibilidad de mandar mensajes a la aplicación que nos servirán para depurarla cuando lleguen los cambios de estado. ¿De qué forma? Pues nada más sencillo.

image

A un simple click de ratón tenemos la posibilidad de enviar nuestra aplicación de el estado “en ejecución” a “suspendida”, de “suspendida” de vuelta a “en ejecución” y, por último ya sea desde el estado “en ejecución” o “suspendida” a “terminada”.

Actualización: ¿Y dónde puedo encontrar esta ayuda para la depuración? Pues simplemente hay que activar la barra de herramientas «Ubicación de Depuración» o «Debug Location».

Magnífica ayuda para comprobar que nuestra aplicación es capaz de guardar el estado y recuperarlo correctamente.