Entrevista a José David Romero, dircom en PielFort y founder del blog CIBASS

No hay mejor forma de volver a darle vidilla al blog que entrevistando a un profesional del marketing, la comunicación y las redes sociales como es José David Romero, con una carrera tan prolífica como DJ de Toteking en el pasado, director de comunicación y de operaciones en PielFort, fundador en el famoso y activo blog cultural y de tendencias “CanItBeAllSoSimple”, y ahora escritor con su reciente libro “Pop Marketing”, en el que desgrana situaciones reales en negocios y famosos y los conecta con su particular visión dentro de la cultura pop. No en vano, JD ha sido entrevistado por medios como Forbes, El Pais, El Economista y hasta por empresas como RojoMorgan.

jd_romero_pop_marketing_2

José David, gracias por atenderme y ayudarme a recuperar mi blog. ¿Qué te sugiere el concepto “Marca personal”? ¿Te parece algo manido y casi desvirtuado?

Puede ser, porque de algún modo se ha prostituído en exceso, lo hemos desvirtuado de tanto usarlo. Me parece fantástico que las personas cuiden su marca personal siempre que haya un bagaje, un conocimiento y un esfuerzo detrás, el problema viene cuando uno va una y otra vez a conferencias de supuestos gurús que no han hecho nada o casi nada en el sector privado y que se dedican a repetir frases hechas leídas de otras partes que suenan muy bien en la teoría y no funcionan en la práctica. Tener una cuidada marca personal puede abrirte muchas puertas y es algo importante, pero más lo es intentar estar preparado, estar al día y conocer el medio en que uno se mueve, más allá de las simples herramientas publicitarias que hoy están al alcance de todos (webs, blogs, redes sociales…).

Dime, ¿qué podemos encontrar en tu libro? ¿Está orientado a todo el público en general, a gerentes de pequeñas empresas?

En Pop marketing está todo lo que se necesita para emprender un negocio con cierto éxito hoy día; local idea, eslóganes, logotipos, trabajadores idóneos, sitio web, redes sociales… pero con el factor diferenciador de que se han tomado ejemplos de la cultura pop. Genios contemporáneos que hasta ahora mírabamos sólo desde el prisma del espectáculo pero que acabaron por cambiar el mundo de los negocios a muchos niveles. Merchandising (George Lucas), Ropa deportiva (Michael Jordan), negocio de la música (Michael Jackson), marcas propias (Jay-z) y muchísimos otros sectores están reflejados en el libro con todo lujo de detalles, tanto en definición como en manejo y proyección de las mismas.

pop_marketing_jd_romero

Sé que uno de los negocios en los que estás involucrado, Pielfort, triunfa con éxito a escala internacional. Ofrecéis como productos desde bolsos de piel hasta álbumes fotográficos o de boda. ¿Cuáles han sido las claves de esta expansión internacional?

Estamos en diferentes países de diferentes maneras: en algunos con canales de distribución específicos (como en Japón) y en otros vendiendo a particulares mediante las diferentes páginas webs y redes sociales. No sé si hay una clave, porque me gusta mirar lo que nos queda por hacer en vez del qué hemos hecho, pero si hay un modo quizás seria el manejo del inglés, sitios webs limpios y sencillos, mensaje internacional, facilidad de envío, máxima calidad en el producto y en el packaging y producto y marca con imagen global, nunca local.

¿Podrías nombrarnos algunos de tus clientes más reputados?

Trabajamos con algunos de los mejores fotógrafos del mundo mediante nuestra línea de álbumes, profesionales que van desde Nueva York hasta Caracas. Con respecto a los bolsos tenemos clientes de muchos países a pesar de que llevan relativamente poco tiempo, celebridades del mundo de la canción, del cine, realeza de los Emiratos Árabes y un largo etcétera.

Defíneme el éxito profesional en pocas palabras

Yo diría que el éxito profesional se trata de sentirte desarrollado con tu trabajo, hacer lo que te gusta y lo que mejor se te da y poder pagar las facturas y a los trabajadores a final de mes. Si un tiene éxito económico en un campo que no es el suyo raramente será plenamente feliz, y lo mismo a la inversa. Es una pregunta cuyas respuestas son bastante subjetivas, el éxito significa algo diferente para cada persona.

¿Eres de los que curran una cantidad de horas o de los que le sale natural durante el día?

Entro a trabajar a las 6.00 de la mañana cada día de lunes a viernes así que podemos decir que trabajo unas 12 o 13 horas al día. Soy estricto y cerrado en cuanto a eso, madrugando uno tiene la sensación de que antes de que acabe el día tendrá casi todo lo que deba hacer bajo control.

En cuanto al blog cultural y de tendencias CIBASS (premiado como mejor blog en España en 2015), ¿podrías contarnos cómo surge la idea? ¿qué nivel de visitas manejáis mensualmente?

Surge de manera fluída, ya que llevaba tiempo dándole vueltas al asunto. Quería que tuviera ese nombre en concreto y ví que el dominio estaba libre (el nombre está tomado de mis canciones favoritas de Wu-Tang clan), cuando me hice con él y dí con un aspecto limpio, actual y sencillo pero reconocible empecé a escribir artículos y a invitar a gente a que se uniera al mismo. Poco a poco fuimos haciendo un equipo consistente y hasta hoy, en el que somos más de veinte personas entre dirección, redacción, colaboradores, informáticos, etc.

A día de hoy recibe una media de 15.000 visitas al mes, aunque hay semanas con picos mucho más amplios cuando tocamos temas de actualidad desde una perspectiva personal o cuando nuestras opiniones gustan… o todo lo contrario.

Volviendo al tema del marketing, imagina que tengo una peluquería y mantengo una pequeña Web. ¿Qué consejo darías al peluquero para aumentar sus ventas?

Tener una web limpia y ordenada, que puedas llegar a cualquier información en sólo tres clicks. Información y fotos de los servicios y de precios, localización y contacto visibles. También sería importante alimentar el sitio web desde las redes sociales, especialmente desde Facebook ya que los posibles clientes no tienen por qué tener muchas más redes (usuari@s de a pie). Si el sitio web da imágen de profesionalidad y pulcritud (acompañando de unas buenas fotografías) deberían subir las ventas.

Imagina ahora que soy una maestra en paro sin Web, recursos ni marca personal, pero quiero hacer llegar mis ideas educativas al mundo, y quizá hacer negocio con ello ¿Qué me aconsejas?

Registrarme en las redes sociales mayoritarias (Facebook, twitter e instagram, por ejemplo) y empezar a enseñar mis ideas de una manera sencilla y gratuíta. Si la persona tiene talento y publica de modo cerebral (nunca lo primero que se le ocurra) pronto empezarán a unirse seguidores y posiblemente empezarán a ofrecerle más proyectos, todo con paciencia. Es importante también que la imagen (por ejemplo en los avatares) de las redes sociales sea personalizada y de imagen profesional, no simplemente cosas provenientes de Google y que dan ese toque impersonal y dañino.

¿Qué trucos sobre SEO puedes enseñarnos en general?

Darle la información que Google quiere, es decir; alimentar a Google. Archivos bien nombrados, descripciones largas con las palabras clave y una sección de blog donde al menos tres días a la semana se suban artículos de más de 500 palabras sobre temas que puedan interesar a nuestro target.

Sé que fuiste DJ con Tote, pero no manejo demasiado de rap, así que te voy a preguntar si conoces algo de la escena rock española y qué grupos podrias aconsejarnos

Conozco muy poco la escena rock española, y eso que en su día tocamos con muchos de ellos como Barricada. En mi infancia si oía cosas como Iron Maiden, Scorpions u Ozzy Osbourne, pero cuando descubrí el hip-hop (como a las 11 años) todo aquello desapareció.

jd_romero_pop_marketing_3

Por lo que he podido ver te gusta bastante el cine, ¿qué opinión tienes de estas películas antiguas? Adaptation (El ladrón de orquídeas), Origen, Dark city, Memento.

Adaptation me encanta, como la mayoría de Spike Jonze. Origen me gusta pero creo que está hinchada, como casi todo lo de Christopher Nolan. Dark City me gusta y es imaginativa pero no conecto en exceso con ella y Memento me parece una gran película, probablemente la mejor de Nolan.

¿Te decepcionó tanto como a mi la adaptación de The Spirit al cine por Frank Miller?

Mucho, Frank Miller fue (en su día) un magnífico creador de cómics, lo que hizo con Batman o Daredevil es increíble, pero no es un director de cine. Sin City fue una película genial, pero porque Miller iba de la mano de Robert Rodriguez. En cuanto se le dejó sólo se vio que no era capaz de llevar una película a buen término.

Imagina que busco un cómic que me provoque algún tipo de inspiración filosófica y me haga hacerme preguntas. ¿Qué me recomiendas? ¿Probablemente algo de Alan Moore?

Watchmen, es una respuesta previsible pero no se me ocurre un compendio mejor entre cierta profundidad y un contexto conocido como el cómic de súper héroes. También La cosa del pantano del propio Moore, que está editado en España en tres tomos preciosos.

He leído que tu libro está prologado por gente más que interesante…

El prólogo de Pop Marketing es de Toni García Ramón (Wall street journal, Esquire, El País, Forbes…) al que conocí cuando me entrevistaba para un medio económico. Siempre creyó en la idea de mi libro (aunque yo dudaba bastante al principio) y tal como le llegaron los primeros capítulos me dijo que adelante y que además me escribiría el prólogo.

Es la persona ideal porque además de ser un fantástico escritor me conoce de sobra tras la larguísima entrevista que me hizo para Forbes.

jd_romero_pop_marketing_1

¿Qué opinión tienes de estos personajes que arrancaron como pequeños y llegaron a lo más alto? ¿En qué crees que su estrategia fue ganadora? Coronel Sanders, Mark Zuckerberg, Los Beatles.

Del Coronel Sanders sin duda el tener un producto mejor que el de su competencia, servirlo rápido, de modo económico y situado junto a otras grandes franquicias que le proveen de tráfico. Mark Zuckerberg se sirvió de nuestro interés en querer conectar con chicas de las que sabemos el nombre y poco más y de ahí se fue de las manos y empezó a crecer y supo gestionarlo, aunque hace un siglo que está hiper asesorado, me gusta la idea de que ya los empresarios no vayan en traje y fumen puros mientras llevan cara de enfadados. Lo malo de Zuckerberg es que todos los que se hacen llamar emprendedores están a la espectatíva de que sus ingeniosas ideas los hagan millonarios, cosa que no ocurre.

No soy un gran fan de Los Beatles, su música era melódica y nada arriesgada y su mensaje era entre simplista y bonachón. Sin duda debieron tener un inmenso talento, Michael Jackson compró sus derechos en los ochenta, se hizo más millonario aun y aseguró la vida de sus hijos, nietos y bisnietos.

Por último, una pregunta algo más personal. Un sitio que mole para comer, otro para tomar una birra, y otro para tomar un café.

Para comer me gusta Chipotle, Five Guys y otra hamburguesería que se llama BRGR, Five guys ya lo hay en Madrid y supongo que pronto estará en todas partes. No me gusta la cerveza pero cualquier pub irlandés con interiores de madera me parece lo suficientemente acogedor para degustar una (o un Bulleit Bourbon) con una buena conversación. Y mi café favorito es probablemente el de Costa Café. Eso sí, para estar como en tu casa, nada como la Bodega Aurora en Sevilla.

Gracias por atenderme y suerte con tu libro, seguro que tendrá una gran acogida.

Sé el primero en valorar positivamente

NFC en la Raspberry Pi

Hace tiempo que no escribo en el blog debido a insertar-excusa-que-no-haya-usado-antes-aquí, pero me apetecía compartir el resultado de pasar unas pocas de tardes buscando una buena alternativa para utilizar NFC en la Raspberry Pi.

Las primeras semanas me centré en hacer funcionar el lector NFC ACR122u de ACS, lector que ya había utilizado en la empresa en la que trabajo para hacer una serie de investigaciones con Linux y que arrojaron un resultado aceptable. Pobre de mi, pensaba que con la Raspberry Pi y la misma librería automágica nfcpy que había utilizado en el pasado, encontrar una alternativa estable iba a ser pan comido. No fue así. La Raspberry Pi es un Linux, eso es cierto, concretamente una Debian. Pero la arquitectura no es i386, es ARM (más propia de pequeños dispositivos como smartphones y tablets). A pesar de funcionar más o menos regularmente, el lector ACR122u fallaba de manera aleatoria y sin explicación. Tal y como me comenta un experto en la materia como es Stephen Tiedemann (creador de nfcpy) en este hilo, además de que el lector es de lo peor para estas cosas, el subsistema USB de la Raspberry se muestra muy inestable en cuanto a periféricos más concretos como este (ratones, teclados y demás material básico funciona sin problemas).

El lector NFC ACR122U

El lector NFC ACR122U

Con el primer golpe en la frente, decido no hacerle caso a Stephen que me recomienda probar con una placa PN532 conectable vía serie en plan Arduino, usando electrónica, placas y demás. En su lugar, me tomé la libertad de realizar una pequeña inversión en la placa EXPLORE-NFC NXP PN512 que no tiene soporte por parte de nfcpy pero tiene programas de ejemplo ya escritos en C puro. Esta placa se engancha al puerto de expansión GPIO como si de un conector USB se tratase.

La placa explore-nfc

La placa explore-nfc

Hay programas escritos para los 3 modos de operación de NFC: tag, card emulation y P2P. Un handicap es que el modo P2P sólo tiene escrito un programa para la operación PUT (recibir de Raspberry) pero no para GET (que la Raspberry obtenga del móvil). He escrito un programa Python/GTK como interfaz gráfica hacia este programa P2P ya existente, para probar la operación PUT. El resultado lo podéis ver en el vídeo que adjunto, donde también pruebo la demo del modo Card Emulation, que recoge el valor que asignemos al tag 14443 y lo escribe en el muro de Facebook de mi cuenta dummy.

Enlaces de interés

* Código de ejemplo EXPLORE-NFC:
http://www.element14.com/community/docs/DOC-65447/l/explore-nfc-software-and-project?ICID=knode-devkitnfc-quick

* Comprar EXPLORE-NFC (marcar Business en la ventana modal, aunque seas un particular):
http://es.farnell.com/jsp/displayProduct.jsp?sku=2366201&CMP=KNC-GES-FES-GEN-SKU-MDC

* Código de demo gráfica personal Python/GTK:
https://bitbucket.org/jialvarez/raspberry-mpos

A 1 persona le gusta esta entrada

Realidad aumentada en Android: reconocimiento de imágenes y geolocalización usando Google Maps

Presentación utilizada para el evento Google DevFest Sur el 25/10/2013. En la charla revisamos el estado del arte en cuanto a bibliotecas de realidad aumentada para Android. Vimos cómo funcionan de manera general y estudiamos un ejemplo práctico de reconocimiento de imágenes y geolocalización con la biblioteca Wikitude.

Sé el primero en valorar positivamente

Comparativa de sistemas de persistencia en Python

En uno de los proyectos Python en los que he estado trabajando en Emergya, detectamos la necesidad de cambiar el sistema de persistencia bsddb por algún otro que fuera más rápido. Alejandro “CuasiInfinito” Leiva me pidió diseñar un comparador de sistemas de persistencia con el estado del arte actual. Este comparador es el que ocupa esta nueva entrada en el blog.

¿Qué sistemas de persistencia se evalúan?

Los sistemas de persistencia a evaluar en rendimiento son los siguientes:

  • bsddb
  • durus
  • ZODB
  • Redis
  • PyTables

¿Qué pruebas se utilizan para medir el rendimiento?

La primera prueba para cada sistema de persistencia consiste en tomar un fichero CSV línea a línea y utilizar la operación de escritura persistente proporcionada por el sistema que se está evaluando. Es importante el hecho de que esta escritura sea persistente, uno de los errores que cometí de inicio fue pensar que unos sistemas eran más rápidos que otros sin apreciar que estaban realizando la escritura en memoria directa. Para asegurar este punto, cerraremos el manejador de datos de cada sistema una vez hayamos realizado la escritura persistente del fichero CSV completo.

A continuación, creamos un nuevo manejador y leemos los datos almacenados por el sistema de persistencia. Estos datos deben coincidir con los del CSV línea a línea.

Ambas pruebas serán medidas con una marca de tiempo al inicio y al final del proceso. Se realiza una impresión por pantalla del tiempo consumido en cada prueba por parte de cada sistema de persistencia.

¿Dónde descargo el código?

El código puede descargarse de mi cuenta en BitBucket: https://bitbucket.org/jialvarez/persystems/

¿Cómo está estructurado el código?

El código consta de un programa principal que carga el backend escogido y realiza las dos pruebas descritas en un punto anterior. Las operaciones descritas en cada backend son las siguientes:

  • __init__
  • Inicialización del backend dependiendo de la prueba a ejecutar (lectura o escritura).

  • __setitem__
  • Asignación de un elemento de manera persistente.

  • __getitem__
  • Recuperación de un elemento.

  • __len__
  • Número de elementos almacenados por el sistema persistente.

  • first
  • Devuelve el primer elemento de los almacenados de forma persistente.

  • iteritems
  • Itera a través de los elementos almacenados de forma persistente devolviendo un elemento en cada iteración. No implementado aún en todos los backends.

  • close
  • Cierra y/o destruye el manejador del sistema persistente.

  • getTestDBItems
  • Devuelve una lista con todos los elementos almacenados de forma persistente.

¿Cómo pruebo el comparador?

El código se estructura de la siguiente manera:

neonigma@hyperion:~/things/persystems$ tree
.
??? backends
?   ??? __init__.py
?   ??? pybsddb.py
?   ??? pydurus.py
?   ??? pyredis.py
?   ??? pytables.py
?   ??? pyzodb.py
??? __init__.py
??? test.py

En el directorio backends se encuentran los módulos que implementan los diferentes sistemas de persistencia. En el directorio principal, encontramos el programa test.py. Hay que modificar la variable FILENAME para que apunte a un fichero CSV válido:

FILENAME = '/tmp/fichero1.csv'

Al final del proograma, encontramos las llamadas a la ejecución de pruebas sobre cada backend:

tester = Test(getCSVReader(), "pytables")
tester = Test(getCSVReader(), "pybsddb")
tester = Test(getCSVReader(), "pyzodb")
tester = Test(getCSVReader(), "pydurus")
tester = Test(getCSVReader(), "pyredis")

Basta con comentar las que no queramos incluir en la ejecución de las pruebas. Para ejecutar el programa, escribimos:

neonigma@hyperion:~/things/persystems$ python test.py
pytables writing time: 0.0846199989319
pytables reading time: 0.0147368907928

pybsddb writing time: 0.278237104416
pybsddb reading time: 0.0832049846649

pyzodb writing time: 0.164448976517
pyzodb reading time: 0.0207080841064

pydurus writing time: 0.253404855728
pydurus reading time: 0.0357837677002

pyredis writing time: 1.39871191978
pyredis reading time: 0.624420881271

Closing remaining open files: /tmp/testpytables.db... done

En este caso los tiempos son pequeños porque estoy utilizando un fichero CSV pequeño, de 10K líneas, pero ya puede apreciarse que el ganador de la comparativa es PyTables.

A 1 persona le gusta esta entrada

Conectar programas C/C++ con aplicaciones Android

En esta entrada voy a describir la manera de comunicar aplicaciones escritas en Android con programas escritos en C/C++ vía JNI utilizando el NDK de Android.

El NDK es un conjunto de herramientas que permite incorporar los componentes que hacen uso de código nativo en las aplicaciones de Android. Permite implementar parte de tus aplicaciones usando código nativo con lenguajes como C y C ++. Esto puede proporcionar beneficios a ciertas clases de aplicaciones, en la medida que se puede reutilizar el código existente y en algunos casos obtener un aumento de la velocidad.

Java Native Interface (JNI) es un framework de programación que permite que un programa escrito en Java ejecutado en la máquina virtual java (JVM) pueda interactuar con programas escritos en otros lenguajes como C, C++ y ensamblador.

La estructura de un proyecto Android que utilice JNI y NDK para permitir la intercomunicación entre aplicaciones C/C++ y Android es la siguiente:
carpeta raíz del proyecto/

  • jni/
  • libs/
  • res/
  • src/
  • AndroidManifest.xml
  • default.properties
  • … otros ficheros …

Descripción de los ficheros:

  • La carpeta src contiene el código Java de la aplicación Android
  • La carpeta res contiene los recursos de la aplicación (imágenes, archivos XML que describen las capas de interfaz, etc)
  • La carpeta libs *contendrá* las librerías nativas *después* de una construcción exitosa
  • La carpeta jni *contiene* el código C/C++ de la aplicación con la que queremos comunicarnos, además de dos scripts importantes: Android.mk y Application.mk. Estos scripts son dos Makefiles típicos que controlan el proceso de construcción de la aplicación C++.

Veamos los pasos para ejecutar una sencilla aplicación Android que se comunique con un programa escrito en C++.

  • Descargar y descomprimir NDK
    Sé que hay versiones mucho más actuales, pero aún así, utilizo esta de Crystax que está bastante optimizada.
  1. wget http://www.crystax.net/en/download/android-ndk-r4-linux-x86-crystax-4.tar.bz2 -O /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2
  2. cd ~ && tar xvjf /tmp/android-ndk-r4-linux-x86-crystax-4.tar.bz2

  • Crear la carpeta libs en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos libs como nombre de carpeta.
  • Crear la carpeta jni en el proyecto
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> Folder. Escribimos jni como nombre de carpeta.
  • Crear el programa C++ con el que queremos comunicarnos
    Pulsamos el botón derecho sobre la carpeta JNI, y escogemos la opción New -> File. Escribimos code.cpp como nombre de fichero. Hacemos doble clic sobre el fichero y escribimos el siguiente código:
  1. #include <jni.h>
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <getopt.h>
  5. #include <string>
  6. #include <cassert>
  7. #include <stdexcept>
  8.  
  9. using namespace std;
  10.  
  11. extern "C"
  12. {
  13. JNIEXPORT jstring JNICALL Java_blog_neonigma_AndroidActivity_SayHello(JNIEnv *env, jobject thiz, jstring name)
  14. {
  15.   try
  16.   {
  17.     const char *myName = env->GetStringUTFChars(name, 0);
  18.     std::string nameCPP(myName);
  19.     nameCPP = "Hello: " + nameCPP;
  20.     return env->NewStringUTF(nameCPP.c_str());
  21.   }
  22.   catch (exception &ex)
  23.   {
  24.     const char *error = "Failed";
  25.     return env->NewStringUTF(error);
  26.   }
  27. }
  28. }

  • Consideraciones sobre el código:
    • El bloque extern “C” indica que el programa está escrito en C++. Sí, en C++. Si queremos escribirlo en C, eliminamos el bloque extern.
    • La declaración de la función nativa tiene que coincidir con el namespace + nombre de actividad Android + nombre de función en el código Java/Android. En este caso, como nuestra actividad es AndroidActivity y se encuentra en el namespace blog.neonigma, la función debe nombrarse como Java_blog_neonigma_AndroidActivity_SayHello, siendo SayHello el nombre que damos a la función y “Java_” un texto estático.
    • La función siempre recibe un puntero al entorno (JNIEnv *env) y un objeto manejador (jobject thiz)
    • Para convertir const char * en jstring, usamos la función NewStringUTF del entorno
    • Más información en la API oficial de JNI

  • Crear los Makefiles para NDK
    Pulsamos el botón derecho sobre el proyecto, y escogemos la opción New -> File. Escribimos Android.mk como nombre de fichero. El código para este fichero sería el siguiente:
  1. LOCAL_PATH := $(call my-dir)
  2.  
  3. include $(CLEAR_VARS)
  4.  
  5. LOCAL_MODULE    := mixed_sample
  6. LOCAL_SRC_FILES := code.cpp
  7. LOCAL_LDLIBS +=  -llog -ldl
  8.  
  9. include $(BUILD_SHARED_LIBRARY)

Los valores más importantes son code.cpp, que indica el fichero de código con el que queremos comunicar y mixed_sample, que es el nombre que damos a la librería que se va generar vía JNI.

Repetimos el proceso y creamos un fichero Application.mk.

  1. APP_STL := gnustl_static
  2. APP_CPPFLAGS := -frtti -fexceptions
  3. APP_ABI := armeabi
  4. APP_PROJECT_PATH := ~/workspace/blog-jni-c

En este caso, los valores más importantes son la arquitectura armeabi (puede cambiarse por la más moderna armeabi-v7a) y el path del proyecto, donde hay que indicar el path al workspace actual.

  • Crear un builder para la compilación
    Debe generarse un builder en Eclipse para la compilación del código C/C++ con NDK.

    1. Pulsamos en projects -> properties y escogemos la opción Builders. Pulsamos el botón New.
    2. En la ventana que aparece, escogemos la opción Program (sin escribir nada), y pulsamos OK.
    3. En la siguiente ventana configuramos el Builder en sí. En la pestaña Main, escribimos en el campo Location la ruta absoluta al programa ndk-build, en mi caso /home/neonigma/android-ndk-r4-crystax/ndk-build En el campo Working directory, pulsamos en el botón Browse workspace y escogemos el proyecto en el que estamos trabajando.
    4. Ahora vamos a la pestaña Refresh y marcamos Refresh upon completion, Specific resources y Recursively include sub-folders.

      Pulsamos en el botón Specify resources y en la ventana que aparece, escogemos el recurso libs.
    5. Por último, vamos a la pestaña Build Options, dejamos las casillas ya marcadas y marcamos During auto builds y Specify working set of relevant resources.

      Pulsamos en el botón Specify resources y escogemos el recurso jni de nuestro proyecto.
  • Escribir el código de la actividad Android
    Para enviar datos al programa en C++ y recogerlos, necesitamos escribir el código de la actividad principal de la siguiente manera:
  1. package blog.neonigma;
  2.  
  3. import src.blog.neonigma.R;
  4. import android.app.Activity;
  5. import android.os.Bundle;
  6. import android.widget.Toast;
  7.  
  8. public class AndroidActivity extends Activity {
  9.     @Override
  10.     public void onCreate(Bundle savedInstanceState) {
  11.         super.onCreate(savedInstanceState);
  12.         setContentView(R.layout.main);
  13.        
  14.         String greeting = SayHello("neonigma");
  15.         Toast.makeText(this, greeting, Toast.LENGTH_LONG).show();
  16.     }
  17.    
  18.     public native String SayHello(String name);
  19.    
  20.     static {
  21.         System.loadLibrary("mixed_sample");
  22.     }

  • Consideraciones sobre el código
    • La llamada a función nativa se define como public native.
    • String se equipara a jstring, e int a jint, a la hora de enviar y recibir parámetros.
    • Debe definirse como estática la carga del módulo de la librería generada y que especificamos en el código del Makefile Android.mk.

Referencias:
http://opencv.itseez.com/doc/tutorials/introduction/android_binary_package/android_binary_package_using_with_NDK.html#android-binary-package-with-ndk
http://zsoluciones.com/datos/?p=246

El código del ejemplo puede descargarse desde el repositorio creado en BitBucket.

A 1 persona le gusta esta entrada

Comprobación PEP8 de estilo del código Python para VIM

PEP8 es el nombre clave de la Guía de estilo para código Python publicada en julio de 2001 y que aún a día de hoy, diez años después, sigue revisándose.

Para ayudarnos en esta tarea, mi compañero de batallas @pipotux me descubrió una muy buena extensión para Vim mientras trabajábamos en un proyecto Python que nos traemos entre manos.

La instalación es sencilla:

  1. neonigma@neonigma-desktop:~$ cd /tmp
  2. neonigma@neonigma-desktop:/tmp$ git clone https://github.com/cburroughs/pep8.py.git
  3. neonigma@neonigma-desktop:/tmp$ sudo apt-get install python-setuptools
  4. neonigma@neonigma-desktop:/tmp$ cd pep8.py
  5. neonigma@neonigma-desktop:/tmp/pep8.py$ python setup.py build
  6. neonigma@neonigma-desktop:/tmp/pep8.py$ python setup.py install
  7. neonigma@neonigma-desktop:/tmp/pep8.py$ wget http://www.vim.org/scripts/download_script.php?src_id=14366 -O pep8.vim
  8. neonigma@neonigma-desktop:/tmp/pep8.py$ mkdir -p  ~/.vim/ftplugin/python
  9. neonigma@neonigma-desktop:/tmp/pep8.py$ mv pep8.vim ~/.vim/ftplugin/python

Una vez realizados estos pasos, basta con abrir cualquier archivo Python con Vim y pulsar F5. El plugin informará de los errores de estilo cometidos o arrojará un mensaje indicando que el código está escrito conforme a la PEP8.

Fuente: página oficial del plugin pep8 para Vim.

A 1 persona le gusta esta entrada

Tip: cómo modificar el último commit en bazaar / Launchpad

Hoy he cometido un pequeño error al comitear los cambios a un proyecto en el que estoy involucrado junto con @edortix y @pipotux. Dichos cambios me venían en forma de megaparche por parte de @pipotux y al subirlos, no me di cuenta que no tenia configurada mi cuenta de correo en el equipo que me encontraba trabajando.

Para solucionarlo, basta con revertir el commit, configurar correctamente la cuenta de usuario y reescribir el commit de la siguiente manera:

  1. bzr config email="neonigma &lt;neonigma@gmail.com&gt;"
  2. bzr launchpad-login
  3. bzr uncommit 27 # el número de revisión a "descomitear"
  4. bzr ci -m "A lot of changes. The most important one: changed the turn method"
  5. bzr push lp:project --overwrite

A 5 personas les gusta esta entrada

Clientes SOAP en Python y PHP

En el post anterior vimos como crear servicios Web personalizados con el protocolo SOAP en Drupal. Por supuesto, un servicio Web en SOAP puede hacerse en multitud de lenguajes. Al igual que los clientes que lo consumen. En este caso vamos a ver ejemplos para consumir el servicio Web creado en el post anterior con clientes SOAP escritos en los lenguajes Python y PHP.

Cliente en Python
En primer lugar, instalamos easy_install:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install python-setuptools python-dev build-essential

A continuación, instalamos suds:

  1. neonigma@neonigma-desktop:/opt/lampp/htdocs$ sudo easy_install suds
  2. install_dir /usr/local/lib/python2.6/dist-packages/
  3. Searching for suds
  4. Reading http://pypi.python.org/simple/suds/
  5. Reading https://fedorahosted.org/suds
  6. Best match: suds 0.4
  7. Downloading http://pypi.python.org/packages/2.6/s/suds/suds-0.4-py2.6.egg#md5=94a9414e90e01243262548ad9eaf2784
  8. Processing suds-0.4-py2.6.egg
  9. creating /usr/local/lib/python2.6/dist-packages/suds-0.4-py2.6.egg
  10. Extracting suds-0.4-py2.6.egg to /usr/local/lib/python2.6/dist-packages
  11. Adding suds 0.4 to easy-install.pth file
  12.  
  13. Installed /usr/local/lib/python2.6/dist-packages/suds-0.4-py2.6.egg
  14. Processing dependencies for suds
  15. Finished processing dependencies for suds

A continuación, con sólo este pequeño programa en Python usando la librería suds podemos utilizar la operación que definimos en el servicio Web del post anterior:

  1. from suds import WebFault
  2. from suds.client import Client
  3. import traceback as tb
  4. import logging
  5.  
  6. logging.basicConfig(level=logging.INFO)
  7. logging.getLogger('suds.client').setLevel(logging.DEBUG)
  8.  
  9. try:
  10.     client = Client('http://debian-virtual/drupal/?q=services/soap/?wsdl')
  11.     print client.service.add_numbers(10,3)
  12. except WebFault, f:
  13.     print f
  14.     print f.fault
  15. except Exception, e:
  16.     print e
  17.     tb.print_exc()

El resultado de la ejecución es el siguiente:

  1. root@debian:/var/www# python cliente.py
  2. DEBUG:suds.client:sending to (http://debian-virtual/drupal/services/soap/?wsdl/)
  3. message:
  4. &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  5. &lt;SOAP-ENV:Envelope xmlns:ns3="http://debian-virtual/drupal/services/soap/?wsdl/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns2="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"&gt;
  6.    &lt;SOAP-ENV:Header/&gt;
  7.    &lt;ns1:Body&gt;
  8.       &lt;ns3:add_numbers&gt;
  9.          &lt;number_one xsi:type="ns2:int"&gt;10&lt;/number_one&gt;
  10.          &lt;number_two xsi:type="ns2:int"&gt;3&lt;/number_two&gt;
  11.       &lt;/ns3:add_numbers&gt;
  12.    &lt;/ns1:Body&gt;
  13. &lt;/SOAP-ENV:Envelope&gt;
  14. DEBUG:suds.client:headers = {'SOAPAction': u'"http://debian-virtual/drupal/services/soap/?wsdl/add_numbers"', 'Content-Type': 'text/xml; charset=utf-8'}
  15. DEBUG:suds.client:http succeeded:
  16. &lt;?xml version="1.0" encoding="UTF-8"?&gt;&lt;SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"&gt;&lt;SOAP-ENV:Body&gt;&lt;ns1:soap_call_wrapperResponse xmlns:ns1="http://debian-virtual/drupal/services/soap/?wsdl/"&gt;&lt;return xsi:type="xsd:int"&gt;13&lt;/return&gt;&lt;/ns1:soap_call_wrapperResponse&gt;&lt;/SOAP-ENV:Body&gt;&lt;/SOAP-ENV:Envelope&gt;
  17. 13

Cliente en PHP
Para el cliente en PHP, necesitamos la librería nuSOAP. En este caso, la tenemos ya instalada del post anterior, por lo que apuntaremos a esa ruta. Escribimos este pequeño cliente en PHP:

  1. &lt;?php
  2. ini_set('soap.wsdl_cache_enabled', '0');
  3. require_once('drupal/modules/soap_server/nusoap/lib/nusoap.php');
  4.  
  5. $wsdl = "http://localhost/drupal-testing/?q=services/soap/?wsdl";
  6. $soap = new soapclient($wsdl);
  7.  
  8. $result = $soap-&gt;add_numbers(10,3);
  9. print "El resultado es: " . $result;
  10. ?&gt;

Apuntando a la URL de este ejemplo (http://localhost/cliente.php en este caso), veremos que nos imprime el resultado de la suma correctamente.

Sé el primero en valorar positivamente

Crea tu propio servicio Web con NuSOAP en Drupal

Una de las cosas que más me gustan del software libre es la liberación del conocimiento. Incluso (o sobre todo), cuando lo que has investigado, usado o desarrollado al final no te sirve. Me parece realmente mal que se deseche todo ese conocimiento, ya que se ha invertido el tiempo en ello, por qué no invertir 20 minutos más en estructurarlo y escribirlo. Otro podría necesitar ese conocimiento tan cercano. Aunque no esté 100% depurado y sea directamente utilizable, podría ser un gran punto de partida para otras personas, o incluso para uno mismo en momentos posteriores.

El presente artículo es uno de los que sí me han servido, pero ya empezaba a olvidar. Así que lo escribo. Y tengo en mente el siguiente, totalmente relacionado, que cumple una de las premisas del párrafo anterior: al principio no me sirvió para mucho y meses más tarde ha sido la llave para desbloquear el cierre de un proyecto.

Sin más preámbulos, veamos como crear tu propio servicio Web utilizando SOAP en Drupal. Esta forma da una idea de lo extendible que es un servicio Web dentro de este CMS y me sirve para recordar en cualquier momento cómo llevar a cabo este proceso de extensión.

En primer lugar, como siempre, instalamos drush:

  1. neonigma@neonigma-laptop:~$ sudo apt-get install drush

Descargamos el módulo services:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush dl services -r modules
  2. Project services (6.x-2.3) downloaded to                             [success]
  3. /var/www/drupal-testing/modules/.

Lo habilitamos:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush en services
  2. The following modules will be enabled: services
  3. Do you really want to continue? (y/n): y
  4. Services was enabled successfully.                                   [ok]

Descargamos el módulo soap_server:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush dl soap_server -r modules/services
  2. Project soap_server (6.x-1.2-beta1) downloaded to                    [success]
  3. /var/www/drupal-testing/modules/.

Lo habilitamos:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules$ drush en soap_server
  2. The following modules will be enabled: soap_server
  3. Do you really want to continue? (y/n): y
  4. SOAP Server was enabled successfully.                                [ok]

Para completar la instalación de SOAP Server, no basta con haber instalado el módulo. Debemos instalar la librería NuSOAP que es la que proporcionará realmente el servicio. La descargamos de este enlace por ejemplo a /tmp y la descomprimimos de la siguiente forma:

  1. neonigma@neonigma-laptop:/tmp$ mkdir nusoap
  2. neonigma@neonigma-laptop:/tmp$ mv nusoap-0.9.5.zip nusoap
  3. neonigma@neonigma-laptop:/tmp$ cd nusoap/
  4. neonigma@neonigma-laptop:/tmp/nusoap$ unzip nusoap-0.9.5.zip
  5. neonigma@neonigma-laptop:/tmp/nusoap$ rm nusoap-0.9.5.zip
  6. neonigma@neonigma-laptop:/tmp/nusoap$ cd ..
  7. neonigma@neonigma-laptop:/tmp$ mv nusoap/ /var/www/drupal-testing/modules/soap_server/

En este punto, tenemos un servidor SOAP escuchando en http://localhost/drupal-testing/?q=services/soap. Pero existe un bug en esta versión de SOAP server, por el cual al acceder a esa URL por primera vez no nos muestra las operaciones disponibles, sino el mensaje de error You must specify a name when you register an operation.

Afortunadamente tiene fácil solución. Nos bajamos este parche visto en este enlace en la ruta del módulo soap_server y lo parcheamos de esta forma:

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/soap_server$ wget http://drupal.org/files/issues/751326.patch
  2. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/soap_server$ patch -p0 &lt; 751326.patch

Si volvemos a acceder de nuevo a la URL de escucha, ahora deberíamos de ver las operaciones disponibles y un enlace al WSDL. Esta URL al WSDL para mi no encaja con la realidad, por lo que yo siempre uso http://localhost/drupal-testing/?q=services/soap/?wsdl a la hora de proporcionar este parámetro a los clientes que deseen conectarse al servicio Web personalizado.

Otro de los hándicaps de utilizar SOAP en Drupal es que se requiere URLs limpias, tema bastante espinoso en Drupal 6, al menos para mi gusto. Para habilitarlas debemos seguir los siguientes pasos:

  • Comprobamos si tenemos instalado el módulo rewrite de Apache:
    1. root@neonigma-laptop:/var/www/drupal-testing# apache2ctl -M
  • Si no está instalado, lo instalamos:
    1. root@neonigma-laptop:/var/www/drupal-testing# a2enmod rewrite
    2. Enabling module rewrite.
    3. Run '/etc/init.d/apache2 restart' to activate new configuration!
    4. root@neonigma-laptop:/var/www/drupal-testing# /etc/init.d/apache2 restart
  • Visitar la URL http://localhost/drupal/?q=admin/settings/clean-urls y, si está disponible el botón de radio con la opción Activado, lo marcamos y guardamos la configuración. Si el botón no está disponible para marcado, debemos revisar cómo habilitar las URLs limpias en este post.

Con todo instalado, toca el turno de lo interesante, crearnos nuestro propio servicio Web. Los servicios Web preinstalados y listos para usar se encuentran en modules/services/services, por lo que un buen punto de partida es copiarnos uno de ellos y modificarlo a nuestro gusto.

  1. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services$ cp -R comment_service/ nuestro_service
  2. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.info nuestro_service.info
  3. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.inc nuestro_service.inc
  4. neonigma@neonigma-laptop:/var/www/drupal-testing/modules/services/services/nuestro_service$ mv comment_service.module nuestro_service.module

La utilidad de cada fichero es la siguiente:

  • nuestro_service.info: este fichero contiene la información de visualización del módulo. Es decir, la identificación del módulo dentro de http://localhost/drupal-testing/?q=admin/build/modules, en la zona Services.
  • nuestro_service.module: este fichero contiene la descripción estática de las funciones del servicio Web que se van a compartir.
  • nuestro_service.inc: este fichero contiene el código de las funciones del servicio Web definidas en el fichero anterior.

Lo primero que vamos a hacer es cambiar la información de identificación de nuestro servicio Web en el fichero nuestro_service.info. Para ello, modificamos el primer bloque del código original:

  1. ; $Id: comment_service.info,v 1.1.2.1 2009/06/06 22:57:40 marcingy Exp $
  2. name = Comment Service
  3. description = Provides a comment service.
  4. package = Services - services
  5. dependencies[] = services
  6. dependencies[] = comment
  7. core = 6.x

y escribimos en su lugar este otro:

  1. ; $Id: nuestro_service.info,v 1.1.2.1 2010/12/04 11:28:00 marcingy Exp $
  2. name = Nuestro propio servicio Web
  3. description = Proporciona nuestra propia funcionalidad
  4. package = Services - services
  5. dependencies[] = services
  6. core = 6.x

Nótese que hemos eliminado la dependencia al módulo comment.

A continuación, editamos el fichero nuestro_service.module:, eliminando todo el código existente y colocando este en su lugar:

[PHP]
<?php
// $Id: nuestro_service.module,v 1.1.2.1.2.9 2010/07/16 02:59:56 skyredwang Exp $

/**
* @file
* Link functionality to services module.
*/

/**
* Implementation of hook_perm().
*/
function nuestro_service_perm() {
return array(‘access add two numbers’);
}
/**
* Implementation of hook_service().
*/
function nuestro_service_service() {
return array(

// nuestro.add
array(
‘#method’ => ‘add_numbers’,
‘#callback’ => ‘nuestro_service_add’,
‘#access arguments’ => array(‘access add two numbers’),
‘#file’ => array(‘file’ => ‘inc’, ‘module’ => ‘nuestro_service’),
‘#args’ => array(
array(
‘#name’ => ‘number_one’,
‘#type’ => ‘int’,
‘#description’ => t(‘The first number.’),
),
array(
‘#name’ => ‘number_two’,
‘#type’ => ‘int’,
‘#description’ => t(‘The second number.’),
),
),
‘#return’ => ‘int’,
‘#help’ => t(‘This method adds two numbers and return the result’),
),
);
}[/PHP]

Por último, editamos el fichero nuestro_service.inc:, eliminamos todo el código existente y definimos lo que hará la función que acabamos de exponer:

  1. &lt;?php
  2. // $Id: nuestro_service.inc,v 1.1.2.1.2.5 2010/05/08 19:26:00 heyrocker Exp $
  3.  
  4. /**
  5.  * @file
  6.  *  Link functionality to services module.
  7.  */
  8.  
  9. /**
  10.  * Returns the addition of two parameters received.
  11.  *
  12.  * @param $number_one
  13.  *   First number to add.
  14.  * @param $number_two
  15.  *   Second number to add.
  16.  * @param $since
  17.  *   Timestamp to indicate what nodes are new. Defaults to time of last user acces to node.
  18.  * @return
  19.  *   Number of nuestros that node has.
  20.  */
  21. function nuestro_service_add($number_one, $number_two) {
  22.   return (int)($number_one + $number_two);
  23. }

Es el momento de verificar que todo está en su sitio. Ahora veamos la configuración de módulos en http://localhost/drupal-testing/?q=admin/build/modules. Debemos activar nuestro nuevo módulo que representa el servicio Web que acabamos de crear.

Nos vamos a http://localhost/drupal-testing/?q=admin/user/permissions y verificamos que los permisos que hemos creado en el fichero services.modules, concretamente access add two numbers, están disponibles para asignar a los roles que tenemos creados. Inicialmente se encuentran desmarcados, deberemos marcarlos para acceso a usuario anónimo si es nuestro deseo.

Daremos permisos a todo el mundo para utilizar nuestro servicio Web. Para hacer que sólo puedan utilizar el servicio Web los usuarios registrados, me remito a la entrada anterior en este blog, donde se explicaba este hecho.

En http://localhost/drupal-testing/?q=admin/build/services podemos ver el servidor SOAP ejecutándose y las operaciones expuestas de nuestro servicio Web.

Para acceder a un testeador de nuestro servicio Web pulsamos en el enlace a nuestro.add. En la pantalla que aparece podemos escribir los dos argumentos y pulsar en Call method para realizar la llamada al servicio Web y obtener el resultado.

A 1 persona le gusta esta entrada

Gestión ligera de máquinas virtuales con KVM y Virtual Manager bajo Ubuntu

De la mano de mi compañero jhernandez descubro un gestor de máquinas virtuales mucho más ligero que el pesado VMWare al que llevaba acostumbrado muchos años.

Antes que nada nos creamos un directorio en nuestro home para almacenar nuestras máquinas virtuales.

  1. neonigma@neonigma-desktop:~$ mkdir vm

La idea es instalar KVM junto con la GUI Virtual Manager:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install kvm virt-manager

Luego podemos acceder de la forma que se muestra en la imagen:

Si al acceder nos muestra un error de este tipo:

Significa que nuestra carpeta personal de virt-manager se ha creado erróneamente con permisos de root. La solución es cambiar el propietario de esta carpeta:

  1. neonigma@neonigma-desktop:~$ sudo chown neonigma:neonigma .virt-manager -R

Una vez abierto, añadiremos una conexión nueva en el menú Archivo – Añadir conexión y especificaremos como Hipervisor QEMU/KVM y como conexión Local. Si a continuación vemos el error que aparece en esta imagen:

instalaremos el siguiente paquete para resolverlo:

  1. neonigma@neonigma-desktop:~$ sudo apt-get install libvirt-bin

y daremos acceso al fichero /var/run/libvirt/libvirt-sock:

  1. neonigma@neonigma-desktop:~$ sudo chmod 777 /var/run/libvirt/libvirt-sock

Ya podemos ejecutar tranquilamente Virtual Manager como usuario. Ahora podemos ver en la pantalla principal una conexión a Localhost. Pulsamos dos veces sobre ella y nos aparece un menú con cuatro pestañas. Seleccionamos la pestaña Almacenamiento, y pulsamos en el botón que muestra un icono con el símbolo + en verde. En la ventana que se nos abre, podemos poner como nombre myHome y en Tipo elegimos Directorio del sistema de archivos. Pulsamos Adelante y donde pone Ruta de destino escribimos la ruta que creamos en el primer paso del artículo, es decir, /home/neonigma/vm en mi caso. Ya tenemos un espacio creado y dentro de nuestro /home.

Cerramos esta ventana donde hemos creado nuestro espacio de almacenamiento y volvemos a la ventana principal. Ahora pulsamos en el único icono activo que muestra un monitor y que nos va a permitir crear nuestra máquina virtual. En la primera pantalla escribimos el nombre de nuestra máquina virtual y dejamos el parámetro intacto:

En la siguiente pantalla seleccionamos Utilizar imagen ISO, pulsamos en el botón Explorar y a continuación pulsamos el botón Explorar localmente, seleccionamos la ISO del sistema operativo a instalar y pulsamos Aceptar. La siguiente imagen muestra cómo debería quedar la configuración:

En la siguiente imagen, indicamos el tamaño de memoria RAM que cedemos al sistema operativo virtual, pulsamos Adelante y vemos la última pantalla que se refiere al almacenamiento en el disco virtual. En esta pantalla seleccionamos la opción Seleccione un almacenamiento gestionado o de otro tipo existente, pulsamos en explorar, en la zona izquierda en nuestro espacio myHome, y finalmente en la zona derecha en nuestro debian-virtual.img. La configuración queda así:

La pantalla final nos muestra un resumen de la configuración escogida y pulsamos en Finalizar. En este momento, comienza automáticamente la instalación de nuestro sistema operativo virtual.

A 1 persona le gusta esta entrada