Categoría: TopTal

13 Dic 2016

El Arte de Robar: Cómo Convertirse en un Diseñador Experto

El artículo original lo pueden encontrar en Totpal.

21 Nov 2016

Guarda la Calma y Transiciona a un Nuevo Equipo de Desarrollo

Gestionando el traspaso

Ahora que has cubierto todas las bases, necesitas gestionar el traspaso de un equipo a otro. Estos son algunos consejos básicos para enfrentar tanto al equipo entrante como al de salida.

Make sure you properly manage the technical and personal aspects of your project handoff. Make your new team feel at home and don’t antagonize your old team.

Asegúrate de administrar adecuadamente los aspectos técnicos y personales de tu traspaso de proyecto. Permite que tu nuevo equipo se sienta como en casa.

Equipo Entrante

  • Establece expectativas – El nuevo equipo debe saber cuáles son tus objetivos más importantes para que puedan centrarse en la dirección correcta. La gestión de tus propias expectativas sobre lo que el nuevo equipo puede lograr de inmediato es igualmente importante.
  • Realiza visitas de control a menudo – No dejes al nuevo equipo a su suerte. Debes hacer visitas de control con frecuencia para asegurarte de que tienen todo lo que necesitan, y que no se sientan como que tienen que valerse por sí mismos. Trata de hacer esto sin llegar a hacer micro gestión. Asegúrate de que sepan que estás para apoyar y ayudar si lo necesitan, pero no pongas presión innecesaria sobre ellos.
  • Sé paciente – Se necesita tiempo para que los desarrolladores puedan adaptarse a una nueva base de códigos. Entiende que habrá un poco de tiempo de aprendizaje antes de que el nuevo equipo pueda igualar el ritmo del anterior.

Equipo Saliente

  • Recolecta todo el código en circulación – Asegúrate de que todo el código fuente se haya registrado en el repositorio principal, y que sabes el estado de lo que se ha desplegado y lo que no. El nuevo equipo tendrá que saber exactamente dónde retomar y empezar a trabajar. Yo mismo he experimentado una situación en la que seguí el trabajo de un equipo que había desplegado código sin ponerlo en el repositorio principal. Esto llevó a que se crearan bugs, la duplicación de trabajo y dolores de cabeza que podrían haberse evitado fácilmente si el equipo saliente hubiera dejado el código fuente en un estado coherente.
  • Actualiza el nivel de acceso – Si se separaron en buenos términos, es posible que desees mantenerlos con acceso a tu código y/o despliegue. Muchos equipos están dispuestos a ayudar durante la fase de transición, hasta que el nuevo equipo pueda asumir plenamente. Si no, considera cambiar o revocar el acceso para evitar cualquier problema accidental o conflictos con el nuevo equipo.
  • Dales las gracias por su trabajo – Las transiciones pueden ser agitadas. Mientras estás ocupado con el nuevo equipo, no te olvides de agradecer a tu equipo saliente por su contribución al proyecto.

Conclusión

Cualquier transición en la vida puede dar miedo, lo cual trae la incertidumbre de si funcionará o no, el miedo a lo desconocido, y así sucesivamente. La transición a un nuevo equipo de desarrollo no es diferente, pero puedes y debes tomar medidas para que sea más fácil. En la mayoría de los casos, sólo se requiere un poco de planificación a largo plazo.

Tener un mayor conocimiento técnico y no técnico de tu producto de software, el proceso de desarrollo, y todas las cosas que entraron en el proceso ayudará a que cualquier transición de un equipo a otro, sea tan tranquila y sin dolor como sea posible.

Lo mejor de todo: ¡Tu nuevo equipo te respetará y dará las gracias por estar preparado! Es probable que les ahorre tiempo y esfuerzo, lo que también significa que vas a ahorrar dinero. Además, cuanto más pronto el nuevo equipo se dé cuenta de la insistencia en un alto nivel profesional, mejor. Lo más probable es que continúen la implementación de estas prácticas una vez tomen el control del proyecto, por lo que la próxima transición será también sin problemas.

Así que vamos a revisar los puntos clave que deben preceder a cualquier transferencia de propiedad de tu producto de software:

  • Recoger o crear la mayor documentación posible acerca de tu aplicación, el entorno de desarrollo y el proceso de despliegue.
  • Conocer tu producto dentro y por fuera.
  • Mantener el control de todos los servicios y dependencias de terceros de tu aplicación, y tener los nombres de usuario y contraseñas para todo.
  • Esté preparado para darle a tu nuevo equipo acceso a todo lo que necesitan para empezar a funcionar.
  • Sé proactivo y no dejes nada al azar, o para el equipo de desarrollo de salida.

 

El artículo original lo pueden encontrar en Totpal.

15 Nov 2016

Código Buggy Python: Los 10 Errores más Comunes que Cometen los Desarrolladores Python

Error común # 6: La Confusión de Cómo Python Enlaza las Variables en los Cierres

Teniendo en cuenta el siguiente ejemplo:

>>> def create_multipliers():
...     return [lambda x : i * x for i in range(5)]
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...

Deberías esperar el siguiente resultado:

0
2
4
6
8

Pero en realidad obtienes:

8
8
8
8
8

¡Sorpresa!

Esto sucede debido al comportamiento enlace tardío de Python, que dice que los valores de las variables utilizadas en los cierres, se buscan en el momento en el que se llama a la función interna. Así que en el código anterior, cuando cualquiera de las funciones devueltas es llamada, el valor de i se busca en el ámbito que lo rodea en el momento en que se llama (y en ese momento, el círculo se ha completado, por lo que a i ya se le ha asignado su valor final de 4).

La solución a este problema común Python es un poco de hack:

>>> def create_multipliers():
...     return [lambda x, i=i : i * x for i in range(5)]
...
>>> for multiplier in create_multipliers():
...     print multiplier(2)
...
0
2
4
6
8

¡Voilà! Estamos tomando ventaja de los argumentos por defecto para generar funciones anónimas, con el fin de lograr el comportamiento deseado. Algunos llamarían a esto, elegante. Algunos lo llamarían sutil. Algunos lo odian. Pero si eres un desarrollador de Python, es importante entender esto.

Error común # 7: Crear Dependencias de Módulos Circulares

Digamos que tienes dos archivos, a.py y b.py, cada uno de los cuales importa al otro, de la siguiente manera:

en a.py:

import b

def f():
    return b.x
	
print f()

Y en b.py:

import a

x = 1

def g():
    print a.f()

En primer lugar, vamos a tratar de importar a.py:

>>> import a
1

Funcionó muy bien. Tal vez te sorprendió. Después de todo, tenemos una importación circular aquí que presumiblemente debería ser un problema, ¿no?

La respuesta es que la mera presencia de una importación circular no es como tal un problema en Python. Si un módulo ya se ha importado, Python es lo suficientemente inteligente como para no intentar volverlo a importar. Sin embargo, dependiendo del punto en el que cada módulo está intentando acceder a las funciones o variables definidas en el otro, podrías tener problemas.

Así que volviendo a nuestro ejemplo, cuando importamos a.py, no tenía ningún problema al importar b.py, ya que b.py no requiere nada de b.py para definirse en el momento de su importación. La única referencia en b.py a a, es el llamado a a.f(). Pero ese llamado está en g() y nada en a.py o b.py invoca g(). Así que, la vida es bella.

Pero ¿qué ocurre si se intenta importar b.py (claro, sin haber previamente importado a.py):

>>> import b
Traceback (most recent call last):
  	  File "<stdin>", line 1, in <module>
  	  File "b.py", line 1, in <module>
    import a
  	  File "a.py", line 6, in <module>
	print f()
  	  File "a.py", line 4, in f
	return b.x
AttributeError: 'module' object has no attribute 'x'

Uh oh. ¡Eso no es bueno! El problema aquí es que, en el proceso de importación b.py, intenta importar a.py, lo que como resultado llama a f(), que a su vez intenta acceder a b.x. Pero b.x aún no ha sido definida. De ahí la excepción AttributeError.

Al menos una solución a esto es bastante trivial. Simplemente modifica b.py para importar a.py dentro de g():

x = 1

def g():
    import a	# This will be evaluated only when g() is called
    print a.f()

Cuando se importa, todo está bien:

>>> import b
>>> b.g()
1	# Printed a first time since module 'a' calls 'print f()' at the end
1	# Printed a second time, this one is our call to 'g'

Error común # 8: Choque de Nombres con Módulos de la Biblioteca Estándar de Python

Una de las ventajas de Python es la gran cantidad de módulos de biblioteca que trae desde el principio. Pero como resultado, si no estás evitando esto conscientemente, no es tan difícil encontrarse con un choque de nombres, entre el nombre de uno de tus módulos y un módulo con el mismo nombre en la biblioteca estándar que se incluye en Python (por ejemplo, es posible que tengas un módulo denominado email.py en tu código, lo que estaría en conflicto con el módulo de biblioteca estándar del mismo nombre).

Esto puede conducir a problemas muy agresivos, como la importación de otra biblioteca que a su vez intenta importar la versión de bibliotecas estándar Python de un módulo, pero como ya tienes un módulo con el mismo nombre, el otro paquete importa erróneamente tu versión, en lugar de la que se encuentra dentro de la biblioteca estándar Python, y es aquí es donde se producen los errores más graves.

Por lo tanto, se debe tener cuidado para evitar el uso de los mismos nombres que los de los módulos de biblioteca estándar Python. Es mucho más fácil para ti cambiar el nombre de un módulo dentro de tu paquete que presentar una propuesta de mejora de Python (PEP) para solicitar un cambio de nombre upstream y conseguir que lo aprueben.

Error común # 9: El No Poder Hacer Frente a las Diferencias entre Python 2 y Python 3

Considera el siguiente archivo foo.py:

import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)

def bad():
    e = None
    try:
        bar(int(sys.argv[1]))
    except KeyError as e:
        print('key error')
    except ValueError as e:
        print('value error')
    print(e)

bad()

En Python 2, esto funciona muy bien:

$ python foo.py 1
key error
1
$ python foo.py 2
value error
2

Pero ahora vamos a dar un giro en Python 3:

$ python3 foo.py 1
key error
Traceback (most recent call last):
  File "foo.py", line 19, in <module>
    bad()
  File "foo.py", line 17, in bad
    print(e)
UnboundLocalError: local variable 'e' referenced before assignment

¿Qué acaba de ocurrir aquí? El “problema” es que en Python 3 el objeto de excepción no es accesible más allá del alcance del bloque except. (La razón de esto es que, de lo contrario, mantendría un ciclo de referencia con el marco de pila en la memoria hasta que se ejecute el recolector de basura y purgue las referencias de la memoria. Más detalles técnicos sobre esto están disponibles aquí).

Una forma de evitar este problema es mantener una referencia al objeto de excepción fuera del alcance del bloque except, de modo que siga siendo accesible. Aquí hay una versión del ejemplo anterior que utiliza esta técnica, por lo tanto, dosificando el código y haciéndole más compatible con Python 2 y Python 3:

import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)

def good():
    exception = None
    try:
        bar(int(sys.argv[1]))
    except KeyError as e:
        exception = e
        print('key error')
    except ValueError as e:
        exception = e
        print('value error')
    print(exception)

good()

La ejecución de éste es en Py3k:

$ python3 foo.py 1
key error
1
$ python3 foo.py 2
value error
2

¡Yupi!

(Por cierto, nuestra Guía de contratación Python (Python Hiring Guide) analiza una serie de diferencias importantes, que se deben tener en cuenta al migrar el código de Python 2 a Python 3.)

Error común # 10: El Mal Uso del Método __del__

Digamos que tenías esto en un archivo llamado mod.py:

import foo

class Bar(object):
   	    ...
    def __del__(self):
        foo.cleanup(self.myhandle)

Y, luego, trataste de hacer esto desde another_mod.py:

import mod
mybar = mod.Bar()

Obtendrías una fea excepción AttributeError.

¿Por qué? Debido a que, como se informó aquí, al apagarse intérprete, las variables globales del módulo se ajustan a None. Como resultado, en el ejemplo anterior, en el punto que __del__ se invoca, el nombre foo ya se ha ajustado a None.

Una solución a este problema algo más avanzado que la programación Python, sería utilizaratexit.register() en su lugar. De esta manera, cuando el programa se haya ejecutado (al salir normalmente, quiero decir), tus gestores registrados son echados antes de que el intérprete se apague.

Con éste conocimiento, una solución para el anterior código mod.py podría ser algo como esto:

import foo
import atexit

def cleanup(handle):
    foo.cleanup(handle)


class Bar(object):
    def __init__(self):
        ...
        atexit.register(cleanup, self.myhandle)

Esta aplicación ofrece una manera limpia y confiable de llamar a cualquier funcionalidad de limpieza necesaria para después de la terminación normal del programa. Obviamente, le toca a foo.cleanup decidir qué hacer con el objeto unido al nombre self.myhandle, pero se entiende la idea.

Para Terminar

Python es un lenguaje potente y flexible con muchos mecanismos y paradigmas que pueden mejorar considerablemente la productividad. Sin embargo, al igual que con cualquier herramienta de software o lenguaje, el tener una limitada comprensión o apreciación de sus capacidades a veces puede ser más un impedimento que una ventaja, dejándonos en el estado proverbial de “saber lo suficiente como para ser peligroso”.

Familiarizándose con los matices clave de Python, tales como (pero de ninguna manera se limita a) los problemas de programación moderadamente avanzados planteados en este artículo, ayudará a optimizar el uso de la lengua, evitando algunos de sus errores más comunes.

Deberías revisar nuestra Guía a Entrevistas Python (Insider’s Guide to Python Interviewing), para obtener sugerencias sobre preguntas de entrevistas que pueden ayudar a identificar a los expertos en Python.

Esperamos que te sean útiles los consejos en este artículo y apreciamos tus comentarios.

El artículo original lo pueden encontrar en Totpal.

31 Oct 2016

Guía para Desarrolladores iOS: Desde Objective-C hasta Swift

Clases y Estructuras

A diferencia de Objective-C, Swift no requiere que crees documentos de interfaz e implementación por separado para clases y estructuras personalizadas. Mientras aprendes sobre Swift, aprenderás a definir una clase o estructura en un solo documento y la interfaz externa para esa clase o estructura se hace disponible automáticamente para el uso de otro código.

Definir Clases

Las definiciones de clase son my sencillas:

class Bottle
{
   var volume: Int = 1000
   
   func description() -> String
   {
       return "This bottle has \(volume) ml"
   }
}
let b = Bottle()
print(b.description())

Como puedes ver, declaración e implementación están en el mismo documento. Swift ya no utiliza un encabezado ni documentos de implementación. Agreguemos una etiqueta a nuestro ejemplo:

class Bottle
{
   var volume: Int = 1000
   var label:String
   
   func description() -> String
   {
       return "This bottle of \(label) has \(volume) ml"
   }
}

El compilador se quejará, ya que la etiqueta es una variable no-opcional, y ésta no mantendrá un valor cuando se ejemplifica una Bottle (Botella). Necesitamos agregar un inicializador:

class Bottle
{
   var volume: Int = 1000
   var label:String
   
   init(label:String)
   {
       self.label = label
   }

   func description() -> String
   {
       return "This bottle of \(label) has \(volume) ml"
   }
}

O podríamos usar tipo Opcional para una propiedad, el cual no necesita ser inicializado. En el siguiente ejemplo convertimos en volumen un Número entero Opcional:

class Bottle
{
   var volume: Int?
   var label:String
   
   init(label:String)
   {
       self.label = label
   }

   func description() -> String
   {
        if self.volume != nil
        {   
               return "This bottle of \(label) has \(volume!) ml"
           }
           else
           {
               return "A bootle of \(label)"
           }
   }
}

Estructuras

El lenguaje Swift también tiene structs, pero son mucho más flexibles que en Objective-C. El siguiente tutorial de código define un struct:

struct Seat
{
    var row: Int
    var letter:String
    
    init (row: Int, letter:String)
    {
        self.row = row
        self.letter = letter
    }
    
    func description() -> String
    {
        return "\(row)-\(letter)"
    }
}

Como las clases en Swift, las estructuras pueden tener métodos, propiedades, inicializadores y se ajustan a los protocolos. La diferencia principal entre clases y estructuras es que las clases se pasan por referencia, mientras que las estructuras lo hacen por valor.

Este ejemplo demuestra el pasar las clases por referencia:

let b = Bottle()
print(b.description())    // "b" bottle has 1000 ml

var b2 = b
b.volume = 750
print(b2.description())    // "b" and "b2" bottles have 750 ml

Si intentamos hacer algo similar con struct, notarás que las variables se pasan con valores:

var s1 = Seat(row: 14, letter:"A")
var s2 = s1
s1.letter = "B"
print(s1.description())    // 14-B
print(s2.description())    // 14-A

¿Cuándo debemos usar struct y cuándo usamos class? Al igual que en Objective-C y C, usa structs cuando necesites agrupar algunos valores y espera que sean copiados en vez de referenciados o colores RGB.

La instancia de una clase es conocida tradicionalmente como un objeto. Sin embargo, las clases y estructuras Swift son mucho más cercanas en funcionalidad que en otros lenguajes y se puede aplicar mucha funcionalidad a instancias de tipo estructura o clase. Por esto, el término más general utilizado en referencia Swift es instancia, el cual se aplica a cualquiera de estos dos.

Aprende lo básico sobre las clases y estructuras Swift aquí.

Propiedades

Como vimos anteriormente, las propiedades en Swift se declaran con la palabra clave var dentro de la definición de una clase o estructura. También podemos declarar con la instrucción let.

struct FixedPointNumber
{
    var digits: Int
    let decimals: Int
}

var n = FixedPointNumber(digits: 12345, decimals: 2)
n.digits = 4567    // ok
n.decimals = 3     // error, decimals is a constant

También ten en cuenta que las propiedades de la clase son fuertemente referenciadas, a menos que uses el prefijo weak como palabra clave. Sin embargo, hay algunas sutilezas con propiedades no-opcionales de weak, así que lee el capítulo Contabilidad de referencia automática en la Guía Swift de Apple..

Propiedades Calculadas

Las propiedades calculadas no almacenan un valor. Por el contrario, proporcionan un getter y un setter opcional para recuperar y establecer otras propiedades y valores indirectamente.

El siguiente código proporciona un ejemplo de un valor calculado sign:

enum Sign
{
    case Positive
    case Negative
}

struct SomeNumber
{
    var number:Int
    var sign:Sign
    {
        get
        {
            if number < 0
            {
                return Sign.Negative
            }
            else
            {
                return Sign.Positive
            }
        }
        
        set (newSign)
        {
            if (newSign == Sign.Negative)
            {
                self.number = -abs(self.number)
            }
            else
            {
                self.number = abs(self.number)
            }
        }
    }
}

También podemos definir propiedades de solo lectura con solo implementar un getter:

struct SomeNumber
{
    var number:Int
    var isEven:Bool
    {
        get
        {
            return number % 2 == 0
        }
    }
}

En Objective-C, las propiedades usualmente se respaldan con una variable de instancia, declarada explícitamente o creada automáticamente por el compilador. Por otra parte, en Swift, una propiedad no tiene una variable de instancia correspondiente. Es decir, no se puede acceder directamente al almacén de respaldo de una propiedad. Supón que tenemos esto en Objective-C:

// .h
@interface OnlyInitialString : NSObject

@property(strong) NSString *string;

@end

// .m

@implementation OnlyInitialString

- (void)setString:(NSString *newString)
{
    if (newString.length > 0)
    {
        _string = [newString substringToIndex:1];
    }
    else
    {
        _string = @"";
    }
}

@end

Ya que en Swift las propiedades calculadas no tienen un almacén de respaldo, necesitamos algo como esto:

class OnlyInitialString
{
    var initial:String = ""
    var string:String
    {
        set (newString)
        {
            if countElements(newString) > 0
            {
                self.initial = newString.substringToIndex(advance(newString.startIndex, 1))
            }
            else
            {
                self.initial = ""
            }
        }
        get
        {
            return self.initial
        }
    }
}

Las propiedades se explican con más detalle aquí

 

Continuará

Hay muchas cosas más importantes y nuevas que aprender en Swift como programación genérica, interacción con las bibliotecas Objective-C, cierres, optional chaining y sobrecarga de operadores. Un solo tutorial no puede describir completamente un nuevo lenguaje, pero no tengo dudas de que se escribirá mucho más sobre programación Swift. Sin embargo, creo que esta lectura rápida ayudará a muchos desarrolladores Objective-C, quienes no han encontrado el tiempo, ni detalles de aprendizaje sobre el lenguaje Swift, ponte en marcha y deja que el pájaro Swift te lleve a nuevas alturas.

 

El artículo original lo pueden encontrar en Totpal.

 

12 Oct 2016

Un Tutorial Paso-a-Paso para tu Primera Aplicación AngularJS

Cargando Datos del Servidor

Como ya sabemos cómo mostrar los datos de nuestro controlador en nuestra vista, es momento de traer datos en vivo desde un servidor RESTful.

Para facilitar la comunicación con los servidores HTTP, AngularJS proporciona los servicios $http y $resource. El primero es una capa en la parte superior de XMLHttpRequest o JSONP, mientras que el último proporciona un mayor nivel de abstracción. Vamos a utilizar $http.

Para abstraer nuestras llamadas a la API del servidor desde el controlador vamos a crear nuestro propio servicio personalizado, el cual va a capturar los datos y actuará como una envoltura alrededor de $http al añadirlo a nuestro services.js:

angular.module('F1FeederApp.services', []).
  factory('ergastAPIservice', function($http) {

    var ergastAPI = {};

    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    return ergastAPI;
  });

Con las dos primeras líneas, creamos un nuevo módulo (F1FeederApp.services) y registramos un servicio dentro de ese módulo (F1FeederApp.services). Nótese que pasamos $http como parámetro a ese servicio. Esto le dice al motor de inyección de dependenciade Angular, que nuestro nuevo servicio requiere (o depende) del servicio $http.

De una manera similar, tenemos que decirle a Angular que incluya nuestro nuevo módulo en nuestra aplicación. Vamos a registrarlo con app.js, reemplazando nuestro código existente con:

angular.module('F1FeederApp', [
  'F1FeederApp.controllers',
  'F1FeederApp.services'
]);

Ahora, lo único que tenemos que hacer es ajustar nuestra controller.js un poco, integrar ergastAPIservicecomo una dependencia, y estaremos listos para continuar:

angular.module('F1FeederApp.controllers', []).
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];

    ergastAPIservice.getDrivers().success(function (response) {
        //Dig into the responde to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  });

Ahora, recarga la aplicación y revisa el resultado. Observa que no hicimos ningún cambio en nuestra plantilla, pero añadimos una variable nameFilter a nuestro alcance. Vamos a poner esta variable en uso.

Filtros

¡Estupendo! Tenemos un controlador funcional. Pero sólo muestra una lista de conductores. Vamos a añadir algunas funciones mediante una simple entrada de búsqueda de texto, que filtrará la lista. Vamos a añadir la siguiente línea a nuestro index.html, justo debajo de la etiqueta <body>:

<input type="text" ng-model="nameFilter" placeholder="Search..."/>

Ahora estamos haciendo uso de la directriz ng-model. Esta directriz une nuestro campo de texto a la variable $scope.nameFilter y se asegura de que su valor esté siempre al día con el valor de entrada. Ahora, vamos a visitar index.html una vez más y hagamos un pequeño ajuste en la línea que contiene la directriz ng-repeat:

<tr ng-repeat="driver in driversList | filter: nameFilter">

Esta línea le dice a ng-repeat que, antes de dar salida a los datos, la matriz driversList debe ser filtrada por el valor almacenado en nameFilter.

En este punto, entran los datos bidireccionales binding: cada vez que un valor se introduce en el campo de búsqueda, Angular asegura inmediatamente que el $scope.nameFilter que asociamos a él se actualice con el nuevo valor. Dado que binding funciona en ambos sentidos, el momento en el que el valor nameFilter se actualiza, la segunda directriz asociada a la misma (es decir, ng-repeat) también recibe el nuevo valor y la vista se actualiza inmediatamente.

Actualiza la aplicación y observa la barra de búsqueda.

Observa que éste filtro buscará la palabra clave en todos los atributos del modelo, incluyendo los que no estamos usando. Digamos que sólo queremos filtrar Driver.givenName y Driver.familyName: En primer lugar, añadimos a driversController, justo por debajo de la línea $scope.driversList =[];:

$scope.searchFilter = function (driver) {
    var keyword = new RegExp($scope.nameFilter, 'i');
    return !$scope.nameFilter || keyword.test(driver.Driver.givenName) || keyword.test(driver.Driver.familyName);
};

Ahora, de vuelta a index.html, actualizamos la línea que contiene la directriz ng-repeat:

<tr ng-repeat="driver in driversList | filter: searchFilter">

Actualiza la aplicación una vez más y ahora tenemos una búsqueda por nombre.

Rutas

Nuestro próximo objetivo es crear una página de datos del conductor, la cual nos permitirá hacer clic en cada conductor y ver los detalles de su carrera.

En primer lugar, vamos a incluir el servicio $routeProvider (en app.js) lo que nos ayudará a lidiar con estas variadas rutas de aplicación. A continuación, añadiremos dos de estas rutas: una para la tabla del campeonato y otro para los datos del conductor. Aquí está nuestra nueva app.js:

angular.module('F1FeederApp', [
  'F1FeederApp.services',
  'F1FeederApp.controllers',
  'ngRoute'
]).
config(['$routeProvider', function($routeProvider) {
  $routeProvider.
	when("/drivers", {templateUrl: "partials/drivers.html", controller: "driversController"}).
	when("/drivers/:id", {templateUrl: "partials/driver.html", controller: "driverController"}).
	otherwise({redirectTo: '/drivers'});
}]);

Con éste cambio, la navegación hacia http://domain/#/drivers cargará el driversController y buscará la vista parcial que se va a renderizar en partials/drivers.html. ¡Pero espera! No tenemos ninguna vista parcial todavía, ¿verdad? Vamos a tener que crearlas también.

Vistas Parciales

AngularJS te permitirá unir tus rutas a los controladores y vistas específicas.

Pero primero, tenemos que decirle a Angular dónde renderizar estas vistas parciales. Para ello, usaremos la directriz ng-view, modificando nuestra index.html para reflejar lo siguiente:

<!DOCTYPE html>
<html>
<head>
  <title>F-1 Feeder</title>
</head>

<body ng-app="F1FeederApp">
  <ng-view></ng-view>
  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="js/app.js"></script>
  <script src="js/services.js"></script>
  <script src="js/controllers.js"></script>
</body>
</html>

Ahora, cada vez que naveguemos a través de nuestras rutas de aplicaciones, Angular cargará la vista asociada y la renderizará en lugar de la etiqueta <ng-view>. Lo único que tenemos que hacer es crear un archivo con el nombre partials/drivers.html, y poner nuestra tabla de campeonato HTML allí. También vamos a utilizar esta oportunidad para vincular el nombre del conductor a nuestra ruta de los detalles del conductor:

<input type="text" ng-model="nameFilter" placeholder="Search..."/>
<table>
<thead>
  <tr><th colspan="4">Drivers Championship Standings</th></tr>
</thead>
<tbody>
  <tr ng-repeat="driver in driversList | filter: searchFilter">
	<td>{{$index + 1}}</td>
	<td>
  	<img src="img/flags/{{driver.Driver.nationality}}.png" />
   	<a href="#/drivers/{{driver.Driver.driverId}}">
   	    	{{driver.Driver.givenName}}&nbsp;{{driver.Driver.familyName}}
   	  </a>
   	</td>
   <td>{{driver.Constructors[0].name}}</td>
   <td>{{driver.points}}</td>
  </tr>
</tbody>
</table>

Por último, vamos a decidir lo que queremos mostrar en la página de detalles. ¿Qué tal un resumen de todos los hechos relevantes sobre el conductor (por ejemplo, fecha de nacimiento, nacionalidad), junto con una tabla que contiene sus resultados recientes? Para hacer eso, añadimos a services.js, lo siguiente:

angular.module('F1FeederApp.services', [])
  .factory('ergastAPIservice', function($http) {

    var ergastAPI = {};

    ergastAPI.getDrivers = function() {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    ergastAPI.getDriverDetails = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/driverStandings.json?callback=JSON_CALLBACK'
      });
    }

    ergastAPI.getDriverRaces = function(id) {
      return $http({
        method: 'JSONP', 
        url: 'http://ergast.com/api/f1/2013/drivers/'+ id +'/results.json?callback=JSON_CALLBACK'
      });
    }

    return ergastAPI;
  });

Esta vez, proporcionamos la identificación del conductor al servicio para que podamos recuperar la información relevante de un conductor específico. Ahora, modificamos controllers.js:

angular.module('F1FeederApp.controllers', []).

  /* Drivers controller */
  controller('driversController', function($scope, ergastAPIservice) {
    $scope.nameFilter = null;
    $scope.driversList = [];
    $scope.searchFilter = function (driver) {
        var re = new RegExp($scope.nameFilter, 'i');
        return !$scope.nameFilter || re.test(driver.Driver.givenName) || re.test(driver.Driver.familyName);
    };

    ergastAPIservice.getDrivers().success(function (response) {
        //Digging into the response to get the relevant data
        $scope.driversList = response.MRData.StandingsTable.StandingsLists[0].DriverStandings;
    });
  }).

  /* Driver controller */
  controller('driverController', function($scope, $routeParams, ergastAPIservice) {
    $scope.id = $routeParams.id;
    $scope.races = [];
    $scope.driver = null;

    ergastAPIservice.getDriverDetails($scope.id).success(function (response) {
        $scope.driver = response.MRData.StandingsTable.StandingsLists[0].DriverStandings[0]; 
    });

    ergastAPIservice.getDriverRaces($scope.id).success(function (response) {
        $scope.races = response.MRData.RaceTable.Races; 
    }); 
  });

Lo importante a notar aquí es que solo inyectamos el servicio $routeParams en el controlador del conductor. Este servicio nos permitirá acceder a nuestros parámetros de URL (para el :id, en este caso) utilizando $routeParams.id.

Ahora que tenemos nuestros datos en el alcance, sólo necesitamos la vista parcial restante. Vamos a crear un archivo con el nombre partials/driver.html y agregamos:

<section id="main">
  <a href="./#/drivers"><- Back to drivers list</a>
  <nav id="secondary" class="main-nav">
    <div class="driver-picture">
      <div class="avatar">
        <img ng-show="driver" src="img/drivers/{{driver.Driver.driverId}}.png" />
        <img ng-show="driver" src="img/flags/{{driver.Driver.nationality}}.png" /><br/>
        {{driver.Driver.givenName}} {{driver.Driver.familyName}}
      </div>
    </div>
    <div class="driver-status">
      Country: {{driver.Driver.nationality}}   <br/>
      Team: {{driver.Constructors[0].name}}<br/>
      Birth: {{driver.Driver.dateOfBirth}}<br/>
      <a href="{{driver.Driver.url}}" target="_blank">Biography</a>
    </div>
  </nav>

  <div class="main-content">
    <table class="result-table">
      <thead>
        <tr><th colspan="5">Formula 1 2013 Results</th></tr>
      </thead>
      <tbody>
        <tr>
          <td>Round</td> <td>Grand Prix</td> <td>Team</td> <td>Grid</td> <td>Race</td>
        </tr>
        <tr ng-repeat="race in races">
          <td>{{race.round}}</td>
          <td><img  src="img/flags/{{race.Circuit.Location.country}}.png" />{{race.raceName}}</td>
          <td>{{race.Results[0].Constructor.name}}</td>
          <td>{{race.Results[0].grid}}</td>
          <td>{{race.Results[0].position}}</td>
        </tr>
      </tbody>
    </table>
  </div>

</section>

Observa que ahora estamos dándole buen uso a la directriz ng-show. Esta directriz sólo mostrará el elemento HTML si la expresión proporcionada es true (es decir, ni false, ni null). En este caso, el avatar sólo aparecerá una vez que el objeto conductor ha sido cargado en el alcance, por el controlador.

Últimos Toques

Añade un montón de CSS y renderiza tu página. Deberías terminar con algo como esto:

Ahora estás listo para iniciar tu aplicación y asegúrate de que ambas rutas están funcionando como deseas. También puedes añadir un menú estático a index.html, para mejorar las capacidades de navegación del usuario. Las posibilidades son infinitas.

EDITADO (mayo de 2014): He recibido muchas peticiones para una versión descargable del código que construimos en este tutorial. Por lo tanto, he decidido hacerlo disponible aquí (despojado de cualquier CSS). Sin embargo, la verdad es que no recomiendo descargarlo, ya que ésta guía contiene cada paso que necesitas para generar la misma aplicación con tus propias manos, que será un ejercicio de aprendizaje mucho más útil y eficaz.

Conclusión

En este punto del tutorial, hemos cubierto todo lo que necesitarías para escribir una aplicación sencilla (como un informador de Fórmula 1). Cada una de las páginas restantes en el demo en vivo (por ejemplo, tabla del campeonato de constructores, detalles del equipo, calendario) comparten la misma estructura y conceptos básicos que hemos revisado.

Por último, ten en cuenta que Angular es un marco muy potente y que apenas hemos tocado la superficie, en términos de todo lo que tiene que ofrecer. En la parte 2 de éste tutorial, vamos a dar ejemplos de por qué Angular se destaca entre sus semejantes en marcos MVC front-end: capacidad de prueba. Vamos a revisar el proceso de escribir y ejecutar pruebas unitarias con Karma, lograr la integración continua con Yeomen, Grunt, y Bower y otros puntos fuertes de éste fantástico marco front-end.

 

El artículo original lo pueden encontrar en Totpal.

26 Sep 2016

Diseñando un Mejor Portal Web: Los Fundamentos Primero

BY ALEX GUREVICH – FREELANCE DESIGNER @ TOPTAL

 

¿Cuántas veces has terminado un proyecto, sólo para mirar atrás y decir: “¡Si tan sólo pudiera regresar en el tiempo y hacerlo todo de nuevo con lo que sé ahora!?” Al menos yo lo he pensado.

A todos nos pasa; estamos emocionados por un nuevo proyecto. Hacemos chistes con el cliente sobre el sitio web actual, firmamos los contratos y luego comienzan los planes y prototipos. Cuando se trata de proyectos pequeños este tipo de estrategias podrían funcionar. Pero cuando se trata de proyectos grandes, el crecimiento repentino y cambios no planeados después de la primera presentación. Y si llegas a terminar el proyecto, probablemente acabarás con una monstruosidad que se parece a lo que debías arreglar, sólo que éste lucirá un poco mejor que el original.

Un excelente diseño de flujo de trabajo comienza con un buen proceso de planificación.

Un excelente diseño de flujo de trabajo comienza con un buen proceso de planificación.

Un buen diseño y una buena experiencia de usuario no empiezan con un prototipo o maqueta atractiva, o con un plano, sino con una base conceptual que se centra en los objetivos de la empresa. Y dichos objetivos no son “Hagamos un sitio web más sencillo y bonito.” Como tú eres responsable de la UX y UI (Experiencia de Usuario e Interfaz del Usuario), por lo que es necesario asegurarse que los usuarios tengan la mejor experiencia en la interfaz que se está diseñando. Y para lograr esto, se debe planificar a fondo desde el principio.

Investigación Inicial para el Diseño de Páginas Web

Una vez que se termina la investigación inicial del campo del cliente y el de su competencia, lo siguiente es saber con qué se va comenzar. Se puede tener un precio separado para este análisis (que se incluirá en la cotización final del proyecto). Esto evitará cotizaciones altas y ambiguas que supuestamente incluirán cualquier ítem que salga más adelante.

El cliente que se tratará como ejemplo es una organización sin fines de lucro en el campo de la arqueología. La compañía explora y protege sitios arqueológicos en el sur-oeste del país. Actualmente, el sitio web está descomunalmente desorganizado. Imagina montañas y montañas de contenido poco organizado en un antiguo portal cms. Teniendo esto en cuenta, el primer paso es organizar el contenido.

Paso 1: Familiarización con el contenido

Cuando estás construyendo una casa nueva, no derrumbas todas las paredes sin antes sacar lo que está dentro de la misma. El primer objetivo sería ir dentro de todas las habitaciones y sacar todo el contenido. En diseño hablado (Design Speak), debemos ir a todas las entradas y determinar cuáles deberían ser estáticas y cuales tienen material para ser tipo post.

Al tener la información se puede hacer una estrategia para organizar mejor el sitio. En principio no se comprueba lo que debe ser eliminado definitivamente y que no, debido a que en el mundo real no se trabaja de esta forma. Cada parte del portal está asignado a un equipo de trabajo, debido a que hay mucho contenido. Lo mejor es clasificar todo primero.

Por lo general se comienza con la página principal y luego se van analizando el resto de las páginas. De esta forma, tengo un esquema del sitio web antes de la primera reunión. Aunque este no fue el caso para el proyecto que tenemos aquí, por qué la mayoría de los enlaces estaban escondidos en el contenido y no eran accesibles en la navegación inicial del portal, (si la cotización del proyecto se basó en la navegación principal o la página principal, este será el momento en el que te arrepientes). Una vez se habló con diferentes personas en el equipo del cliente, se pudo aclarar la situación para continuar.

Al final, la solución fue hablar con el cliente y tener una sesión de intercambio de ideas (brainstorming). Durante esta sesión se le pidió al cliente identificar las características, el contenido, el flujo de trabajo y el enfoque de la página web. Luego se establecieron los siguientes núcleos (core types), algunos son nuevos y otros ya estaban establecidos:

Better Website Design Workflow

La parte más difícil de este proyecto es que los enlaces no estaban disponibles a primera vista, sino ocultos dentro del contenido del sitio. Y una vez que se habló con el cliente y el equipo del mismo, se pudo continuar con el proyecto.

Paso 2: Creación del Enfoque, Simplificación y Organización

Con el contenido de la casa fuera de la misma, empaquetado y etiquetado, es momento de hacer los planos para una nueva casa que tendrá un mejor estilo y mostrará el contenido de mejor manera. Pero antes de llegar a ello, se debe crear el enfoque.

Según el cliente, los usuarios que visitan la página web buscan información sobre arqueología, tanto como sus proyectos de investigación, ver los próximos eventos, o leer la revista mensual. A pesar de que estas son las áreas de contenido es donde terminan los usuarios, el sitio web no se desenvuelve en torno a ellas; para poder encontrar el foco del portal, debes prestar atención al núcleo de la organización.

Se delimitó que la “localización” está en el centro de los contenidos. Sin la ubicación o localización no existirían los sitios arqueológicos, ruinas, museos o exposiciones. En el fondo, la arqueología se desenvuelve en torno a las ubicaciones.

Una vez se tiene el enfoque es momento de simplificar y organizar. Abajo, se despliega el contenido que se sacó del sitio que no tenía relación con ubicaciones, tales como las páginas del equipo, informes anuales, y así sucesivamente. Luego de ordenar y categorizar el contenido, se obtuvo este mapa:

Website Design Workflow

Ahora se tienen dos áreas principales “Cosas por hacer” y “Ubicaciones ”. En “Cosas por hacer” se tratan todos los temas que tienen que ver organización, mientras que en “Lugares” se cubre el contenido relacionado a una ubicación específica. Es posible que el usuario promedio no sepa el nombre del video o el del proyecto, pero si la ubicación de donde se desarrolló. Por ejemplo, en la sección de proyectos, el usuario podría encontrar el que le interesa a través de su ubicación.

Además, hay un color para cada tipo de post. Desde el punto de vista organizativo, los eventos, exposiciones, clases y exposiciones online son todos, esencialmente, eventos, sólo que hay diferentes tipos.

En el sitio actual había una página estática para una revista, y una para la tienda. Se decidió eliminar el paso adicional de ir a la tienda, y en su lugar tener una plantilla para artículos de la revista en la tienda. El resto es sencillo: una página con información acerca de la organización, un enlace directo a la tienda, una página de donaciones (por qué esas páginas generan ganancias y necesitan una sección en el navegador principal), también nuevas páginas para actualizaciones y un enlace directo a la tienda/donaciones. Debido a que esas páginas generan dinero, merecen un lugar en la navegación principal. Ahora se tiene el plano del proyecto, y es momento de vincular contenido a la misma.

Paso 3: Looping al Cliente

En el mapa del sitio exhibido con anterioridad, se incluyen los tipos de páginas pero no el mapa de los contenidos. Como de seguro ya sabrán, la mayoría de los problemas se producen una vez el cliente comienza a agregar contenido al sitio. Para evitar esta situación se hace un loop con el cliente desde el principio. Lo siguiente es crear un documento de Google con el mapa del sitio que tenemos, y luego enviárselo al cliente para que nos diga cuál es el contenido actual y cómo lo quiere mapear en la nueva estructura.

Si algo no encaja se puede resolver más adelante. Esto es uno de los pasos más importantes, debido a que no sólo involucra al cliente sino que también nos deja saber cualquier problema de la estructura antes de comience la implementación. Para el proyecto que estamos tratando, resultó que se cambiaron algunos elementos del mapa del sitio con respecto al menú; cómo el cliente tenía varias páginas de donaciones, la solución fue crear un post único para las mismas.

Creación de Estructura Visual a través de Wireframing

Lo siguiente es crear una estructura visual para el sitio. Para que la función del sistema trabaje exitosamente, y para lograr la idea de “todo relacionado a la ubicación”, se creó una relación bidireccional entre los diferentes tipos de posts.

Esta es la idea: Cuando un visitante llega al sitio de arqueología y escoge el Gran Cañón, no sólo verá la información sobre el lugar, sino que también encontrará los eventos, proyectos, exposiciones y cualquier otra cosa que el cliente haya enlazado en relación a la página del Gran Cañón. Y así terminamos con algo como esto:

Website Design Workflow

La página de índice de ubicaciones muestra los lugares más recientes en la parte superior. La barra de búsqueda es lo primero que verá el usuario al entrar en la página. Cuando el usuario entra en el sitio, podrá ver el mapa de Google en un 80% de la pantalla, y cuando escoja un punto en el mismo o van viendo la página, aparecerán la red de listas para facilitar su búsqueda.

En una página individual de ubicación la navegación principal estará a la izquierda, debido a que es la información más relevante, mientras que la información meta relacionada al lugar estará a la derecha. Cuando se diseña un plano, para obtener una buena composición se debe tener bloques de elementos primarios, secundarios y terciarios bien definidos. Así el ojo humano seguirá cada uno sin quedarse estancado en un solo elemento.

En este diseño, el usuario empezará en la cabecera del sitio, y luego continuará hacia el bloque de contenido, y después la información a la derecha. Cada segmento de contenido relacionado se muestra en orden de relevancia e importancia. Si el usuario está leyendo sobre el Gran Cañón, por ejemplo, probablemente a esto le seguirán fotos y un mapa del lugar.

Este sitio es esencialmente educacional, por lo que después vendrá “En relación a esta ubicación”. Debido a que el cliente no tiene mucha información única asociada con cada ubicación, se combinó el contenido apenas usado en un bloque exclusivo debajo del menú del sitio.

Colocando miniaturas de la revista y los videos en contenido relacionado añade elementos visuales adicionales y guía a los usuarios hacia la página de “compra”. La página está terminada al mostrarle las ubicaciones, ya que esto incentiva a los usuarios a seguir explorando el sitio web.

El siguiente paso es continuar con esta estructura para otros tipos de posts. Éstos son algunos de ellos:

Better Website Design Workflow

Ahora que se tiene un modelo general para los tipos de posts, me puedo concentrar en la página principal. Al igual que con todo interfaz de usuario, el primer paso es llegar a la meta de la página principal. La investigación del cliente mostró que muchos usuarios se tropiezan con el sitio sin entender completamente lo que es. Por lo tanto, una introducción y un texto de bienvenida debe ser lo primero que ven los usuarios. El nuevo enfoque central gira en torno a lugares y éste debe ser el bloque secundario, seguido de lo que está pasando en ArchaeologySouthwest.org (la revista actual, blog, eventos, noticias, etc.). Esta es la iteración del proceso de diseño:

Better Website Design Workflow

Con V1, se creó un diseño básico que se asemeja a la página inicial. No hay mucha jerarquía; lo primero que un usuario verá es el lugar destacado y luego se perderá en las columnas. Con V2, se creó una columna separada que hace que sea más fácil de seguir. Sin embargo, todavía se puede mejorar. Aquí es donde el conocimiento del contenido juega un papel importante. Se sabe que ArchaeologySouthwest.org no tiene más de dos eventos sucediendo a la vez, por lo que no tiene sentido tener muchos en la página de inicio. En V3, el foco está en los próximos eventos de manera que si va a haber más de dos, el usuario puede hacer clic en un botón “más” y ver el resto. También se hizo un énfasis adicional en la revista actual, ya que es fuente de dinero del cliente. Los usuarios comienzan en la parte superior y con el uso de un patrón F, se mueven hacia abajo. Así que ahora, el flujo del ojo es Localización> Bienvenido> Revista> Eventos> Noticias.

Ahora que se tiene un wireframe visual y estructura del sitio es mucho más fácil solidificar características y cómo funcionarán las cosas. Se tiene otra reunión con el cliente para repasar la funcionalidad real (que ahora sale a la luz) y el flujo de la interacción del usuario. Se sabe que todavía habrá ajustes en el final, pero serán solo ajustes y no cambios en todo el proceso. Lo más importante es que no habrá sorpresas.

 

Diseño de páginas web

Ahora viene la parte emocionante. La conversión del wireframe a algo que la gente va a usar y apreciar. Con este diseño, se quería reforzar el flujo mucho más mediante el uso de las marcas de colores y la tipografía. La guía de estilo del cliente es el siguiente:

Website Design Workflow

Comenzando con la tipografía

La tipografía es la base de un buen diseño y es por eso que se determinó desde el comienzo. La mayor parte de su identidad utiliza los tipos de letra Univers Condensed Light y Adobe Caslon. No había reglas sobre cuándo Adobe Caslon se iba a utilizar, pero se notó que no se utiliza tan a menudo como Universe. Se llevó a cabo un pequeño estudio de la tipografía para ver qué uniones creaban el mejor ambiente para una organización sin fines de lucro, humilde y profesional, sin que se viera diferente en comparación con su garantía actual.

Al hacer comparaciones de la tipografía, está claro que Adobe Caslon hará un gran trabajo como tipografía del título, y Universe para subtítulos. Al tener los títulos principales en mayúsculas le da a la marca un toque más personal. Tenerlo todo en mayúsculas hace que la Arqueología se sienta como una entidad corporativa.

Better Website Design Workflow

Para los encabezados principales, se va a usar Adobe Caslon, y el uso de Univers Condensed Light para todo lo demás para que coincida con su garantía de marca actual.

La creación de la apariencia de la página web

Se quería crear una experiencia ligera y abierta para los usuarios, quienes deben sentir que esta organización sin fines de lucro los toma en serio sin ser fríos y corporativo.

Basándonos en la base de los datos de análisis, la mayoría de los visitantes entran desde computadoras de escritorio (probablemente porque la mayoría de las personas visitan el sitio para investigar) y, por tanto, el enfoque inicial se centró en el diseño para los usuarios de computadoras de escritorio. Este resultó ser el diseño final:

Al entrar desde una computadora de escritorio, quería que los usuarios vieran de inmediato la ubicación ofrecida, texto de bienvenida y los próximos eventos seguidos por parte del título de la revista. De esta manera la gente puede ver que hace la compañía, y lo que están promoviendo antes de que tengan que desplazarse en la mayoría de los dispositivos portátiles. Con el uso de una sombra suave en la columna izquierda se le da más atención y se solidifica la jerarquía.

En los dispositivos móviles las prioridades son un poco diferentes, ya que los usuarios tienen acceso a la información sobre la marcha, los eventos son más importantes y por eso están más arriba en la lista. El diseño completo termina así:

Se actualizó el botón de donación en el pie de página para que sea más cándido cambiándolo por una oración en lugar de un botón.

Se actualizó el botón de donación en el pie de página para que sea más cándido cambiándolo por una oración en lugar de un botón.

Creación del Resto

A continuación, se puede aplicar de igual modo, este concepto de diseño en nuestros otros diseños.

Sabemos con certeza que los usuarios llegan a la página de detalles por dos razones: o bien que quieren aprender más sobre el punto de referencia, o ya saben acerca de esta ubicación y solo buscan información (direcciones, números de teléfono, etc.). Por lo tanto, es importante presentar las dos opciones de inmediato para que nuestros usuarios no pierdan tiempo en la búsqueda.

Better Website Design Workflow

Se decidió romper la columna de datos fuera del área de contenido para darle más peso, así como hacer la página más interesante. Esto ayuda a crear una jerarquía de composición de modo que cuando el usuario llega a la página, ven primero el título, seguido por las imágenes de la galería y la columna de detalles meta a continuación. Esto asegura que notarán la información meta adicional inmediatamente. Un poco de arreglo adicional a la columna mantiene los ojos en ella y hace que sea más fácil de hojear la información.

El cliente no va a tener muchos videos y revistas relacionadas con cada ubicación, por lo tanto, sólo estamos exhibiendo artículos y si hay algo más pueden hacer clic en el enlace. La vista móvil colapsa como es de esperarse con el contenido apareciendo primero, y luego la meta-información. Los videos y revistas fueron hechos de último en nuestra página móvil, ya que son menos importantes para los usuarios de móviles. Otras secciones internas siguen esta misma estructura y wireframes para crear una buena experiencia y flujo constante.

Reflexionando sobre el proceso de diseño se puede ver que la mayoría del tiempo se gastó en la organización y la planificación. Mientras que solo el 30% del tiempo se dedicó al diseño. A menudo, cuando los diseñadores muestran su trabajo, son deshonestos acerca de cuánto tiempo se dedica a la organización de Google docs, en lugar de hacer mockup llamativos. Por lo tanto, muchos otros diseñadores saltan directamente a los mockup y terminan con proyectos descarrilados y clientes insatisfechos. No hay una forma única para planificar, sólo se tiene que hacer, si quiere tener un proyecto exitoso. Déjanos tus comentarios sobre cómo es tu proceso, o cómo se diferencia – Sería interesante ver los flujos de trabajo de los demás.

 

El artículo original lo pueden encontrar en Totpal.