viernes, 25 de agosto de 2017
Mi primera aplicación móvil
Hola a todos. Quería compartirles un vídeo que re-encontré hace poco, de mi primera aplicación móvil, que hice alrdedor del año 2013 (ya me sentí viejo). En esos tiempos tenía casi nulo conocimiento de Android, y del funcionamiento de aplicaciones móviles en general, por lo que decidí hacerla con Phonegap, ya que me permitía utilizar tecnologías que ya tenía algo de tiempo de conocer y siempre me han gustado.
La aplicación fue hecha para unos amigos que querían iniciar un negocio proporcionando un sistema en el que pequeños negocios se pudieran anunciar, publicar sus eventos, menús, etc., y que el público pudiera verlos a través de una aplicación móvil. El diseño y los íconos fueron proporcionados por ellos.
La aplicación en sí fue construida utilizando AppFramework de Intel (posiblemente la versión 1, no recuerdo), que era un framework para Javascript que utilizaba el patrón MVC, proveía una versión liviana de JQuery (jqLite), y Dot.js para la renderización de las vistas. Adicionalmente incluía componentes que emulaban los controles nativos de iOS.
La aplicación mostrada en el vídeo fue una de las versiones iniciales de la aplicación, en la que aún no había incluido el diseño elegido para la página de los negocios, y en la que el menú lateral era delgado. Este fue sustituido posteriormente por un menú más amplio, que incluía los títulos de las páginas en lugar de solo los íconos.
Fue una experiencia interesante que me permitió conocer y experimentar la creación de APIs web, y la forma en que estas interactuaban con las aplicaciones móviles. De hecho, mi primer servicio web y aplicación fue realizado para este proyecto.
Otros datos interesantes son:
- La aplicación fue desarrollada principalmente en entorno Linux, con el fin de utilizar herramientas gratuitas pero poderosas, como el lenguaje de programación PHP, el IDE Netbeans, y el gestor de bases de datos MySQL; pero también debido a que la computadora no tenía los recursos suficientes para correr el emulador de Android en Windows, y este corría de forma más eficiente en Linux. Adicionalmente, la versión de Linux que utilizaba era Manjaro (mi primera experiencia con una distro basada en Arch Linux), con el entorno OpenBox, lo que la hacía significativamente más rápida y liviana que Windows y muchas versiones de Linux.
- El vídeo fue grabado con una herramienta cuyo nombre no recuerdo, pero que irónicamente no poseía interfaz gráfica.
- La ventana del emulador fue posicionada sobre la vista previa de una imagen de un teléfono celular, ajustada al tamaño del emulador, para que pareciera que el emulador tenía un skin de teléfono celular.
jueves, 24 de agosto de 2017
Instalar SBT en Linux
Saludos. En esta entrada explicaré como instalar la Scala Build Tool en Linux. Los pasos los describo a continuación:
- Descargar la última versión de SBT desde la página de descargas oficial: http://www.scala-sbt.org/download.html
- Descomprimir el paquete descargado. Para efectos de este mini-tutorial, se descargó la versión 1.0.0 en paquete TGZ, y por cuestiones de orden, se descomprimirá en el folder /opt, por lo que en este caso los comandos a ejecutar son:
$ su # Iniciar sesión en modo súper usuario # mkdir sbt # Crear directorio sbt, para colocar acá todas las versiones de SBT # cd sbt # Navegar al directorio sbt # tar -xvzf /home/usuario/Downloads/sbt-1.0.0.tgz # Descomprimir el paquete
- Hacer que el script sbt sea ejecutable:
# chmod u+x sbt-1.0.0/bin/sbt
- Crear la variable de ambiente SBT_HOME, y agregar la ruta que contiene el script sbt a la variable de ambiente PATH. Para ello, se agrega al final del archivo ~/.bash_profile las siguientes líneas (para establecer las variables para terminales bash para el usuario actual):
export SBT_HOME="/opt/sbt/sbt-1.0.0" export PATH="${PATH}:${SBT_HOME}/bin" - Cerrar sesión e iniciar sesión nuevamente, para cargar las nuevas variables de entorno. Luego de ello, podemos abrir la terminal, y ejecutar el comando sbt, el cual ya estará disponible.
sábado, 19 de agosto de 2017
Desactualizando paquete de Arch Linux sin pacman
A.K.A. El fin de semana después del día que ingenuamente quise actualizar Arch Linux por partes en vez de actualizarlo todo.
Acerca de como comenzó todo
Pueden omitir esta sección. Todo comenzó un día que quise actualizar mi instalación de Apricity OS mientras estaba cenando, pero no quise actualizar todo debido a que necesitaba trabajar en la computadora después de cena, en Windows, y el tamaño de la actualización era de 1.2 Gb, por lo que con una magnífica conexión de 2Mbps, eso iba a tomar un largo rato.Así que decidí a actualizar solo algunos paquetes que yo creía que eran independientes de todo lo demás. Empecé a ver la lista, y poco a poco fui actualizando algunos paquetes: geany, gimp, nano, samba... curl. Luego de eso seguí usando la computadora por un rato con normalidad, y la apagué. Todo bien.
Luego vine el fin de semana a querer utilizar la computadora para ver vídeos en Youtube mientras desayunaba. Para mis ratos de ocio prefiero ocupar Linux, ya que es más rápido, y no dependo de nada de Windows para ello. Todo bien. Luego noté que Variety no estaba corriendo (Variety se ha vuelto mi referencia de la salud del sistema operativo últimamente). ¡Qué raro?, pensé yo, pues lo había actualizado recientemente. Así que lo ejecuté desde la línea de comandos, para obtener más información. Apareció en pantalla un error de que no se podía encontrar la librería libssl.so.1.1.
Pensé que podría ser que Variety necesitaba otra actualización, así que procedí a actualizar la base de datos de paquetes de pacman, para ver si habían actualizaciones disponibles, con el comando:
sudo pacman -Syy
Cuando veo un error similar: No se puede encontrar la librería libssl.so.1.1. Así que ni modo, recurrí a Google para buscar alguna respuesta.
Después de un rato encontré un post que sugería crear enlaces simbólicos de las librerías requeridas hacia las librerías existentes, así que cree dos enlaces simbólicos, uno para libssl.so.1.1 y otro para libcrypto.so.1.1:
ln -s /usr/lib/libssl.so.1.0.0 /usr/lib/libssl.so.1.1 ln -s /usr/lib/libcrypto.so.1.0.0 /usr/lib/libcrypto.so.1.1
Luego intenté nuevamente actualizar la lista de paquetes de pacman, y obtuve el siguiente error:
Y allí me acordé: había actualizado curl, pero lo que no sabía es que curl requería versiones actualizadas de libssl y libcrypto. Además, lo había actualizado por sí solo pensando que yo había sido el que había instalado el paquete y que no había dependencias de este. Pero para mi horror, el mismísimo pacman dependía de curl.
Intenté buscar una forma de desactualizar curl, pero la forma estándar requería usar pacman, lo cual fue imposible. Oh, el horror.
Así que ni modo, a buscar una alternativa para hacer la actualización a pata. Gracias a Dios me encontré con esta entrada en Reddit: Pacman libcritpto.so.1.1 errors.
Desactualizando paquete de Arch sin pacman
Sabiendo que en mi caso el error había sido solamente con curl, procedí a realizar la desinstalación de la versión más nueva que encontré en /var/cache/pacman/pkg, la cual era curl-7.55.1-1. Ejecuté el siguiente comando, como super usuario:tar -tf /var/cache/pacman/pkg/curl-7.55.1-1-x86_64.pkg.tar.xz | sed s:^:/: | grep -v /$ | xargs rm
Explicando el comando por partes:
| Paso | Comando | Descripción | |
|---|---|---|---|
| 1 | tar -tf /var/cache/pacman/pkg/curl-7.55.1-1-x86_64.pkg.tar.xz | Obtiene la lista de archivos contenidos en el paquete. | |
| 2 | sed s:^:/: | Agrega / al principio de la ruta de cada archivo. | |
| 3 | grep -v /$ | Del listado anterior, quita los elementos que terminen en /, ya que estos corresponden a carpetas. | |
| 4 | xargs rm | Elimina los archivos listados. |
Luego, procedí a instalar la versión previa disponible en el cache de paquetes, que era curl-7.52.1-2, con el siguiente comando (siempre como usuario root):
tar -xf /var/cache/pacman/pkg/curl-7.52.1-2-x86_64.pkg.tar.xz
Y finalmente probé actualizar nuevamente la base de datos de pacman, y todo resultó bien:
Lección aprendida: No actualizar Arch Linux a medias.
sábado, 12 de agosto de 2017
Obtener documentación de base de datos SQL Server
Por cuestiones de tiempo, esta entrada es solo un "dump" de código fuente. Asumiendo que hiciste tu tarea de colocar los comentarios de los objetos de la base de datos utilizando la propiedad extendida MS_Description, a continuación incluyo unas consultas para obtener la documentación respectiva de los objetos más relevantes de la base de datos.Esquemas
SELECT
s.name AS esquema,
ep.value AS descripcion
FROM sys.schemas s
left join sys.extended_properties ep
ON s.schema_id = ep.major_id
AND ep.name = 'MS_Description'
WHERE s.name NOT IN (
-- Lista de esquemas a ignorar
'db_accessadmin',
'db_backupoperator',
'db_datareader',
'db_datawriter',
'db_ddladmin',
'db_denydatareader',
'db_denydatawriter',
'db_owner',
'db_securityadmin',
'guest',
'INFORMATION_SCHEMA',
'sys'
)
ORDER BY 1
Tablas
SELECT
SCHEMA_NAME(schema_id) esquema,
t.name AS tabla,
ep.value AS descripcion
FROM sys.tables AS t
left join sys.extended_properties ep
ON t.object_id = ep.major_id
AND 0 = ep.minor_id
AND ep.name = 'MS_Description'
ORDER BY 1, t.name
Columnas
Nota: No he descubierto aún por qué, pero hay ciertos casos en los que la columna que es llave primaria me aparece duplicada. Cualquier ayuda es bienvenida :)SELECT
SCHEMA_NAME(schema_id) + '.' + t.name AS tabla,
c.name AS columna,
TYPE_NAME(c.system_type_id) AS tipo_de_datos,
CASE c.max_length
WHEN -1 THEN 'MAX'
ELSE CAST(c.max_length AS VARCHAR(100))
END tamano,
CASE c.is_nullable
WHEN 1 THEN 'Sí'
ELSE 'No'
END AS permite_nulos,
CASE i.is_primary_key
WHEN 1 THEN 'Sí'
ELSE 'No'
END AS llave_primaria,
CASE
WHEN fk.referenced_column_id IS NOT NULL THEN 'Sí'
ELSE 'No'
END AS llave_foranea,
ep.value AS descripcion
FROM sys.tables AS t
INNER JOIN sys.columns c
ON t.object_id = c.object_id
LEFT JOIN sys.index_columns AS ic
ON ic.OBJECT_ID = c.OBJECT_ID AND ic.column_id = c.column_id
LEFT JOIN sys.indexes AS i
ON i.OBJECT_ID = ic.OBJECT_ID AND i.index_id = ic.index_id
LEFT JOIN sys.foreign_key_columns AS fk
ON fk.parent_object_id = c.object_id
AND fk.parent_column_id = c.column_id
LEFT JOIN sys.extended_properties ep
ON t.object_id = ep.major_id
AND c.column_id = ep.minor_id
AND ep.name = 'MS_Description'
ORDER BY 1, c.column_id
Llaves foráneas
Nota: Esta consulta devuelve una fila por columna, por lo que aparecerán tantas filas como columnas incluya la llave.SELECT
SCHEMA_NAME(t.schema_id) + '.' + t.name as tabla_hija,
c.name as tabla_hija_columna,
SCHEMA_NAME(tr.schema_id) + '.' + tr.name tabla_padre,
cr.name tabla_padre_columna,
fk.name fk_nombre,
CASE fk.update_referential_action
WHEN 0 THEN 'NO ACTION'
WHEN 1 THEN 'CASCADE'
WHEN 2 THEN 'SET NULL'
WHEN 3 THEN 'SET DEFAULT'
END regla_actualizacion,
CASE fk.delete_referential_action
WHEN 0 THEN 'NO ACTION'
WHEN 1 THEN 'CASCADE'
WHEN 2 THEN 'SET NULL'
WHEN 3 THEN 'SET DEFAULT'
END regla_eliminacion
FROM sys.foreign_key_columns as fkc
INNER JOIN sys.foreign_keys fk
ON fkc.constraint_object_id = fk.object_id
INNER JOIN sys.tables as t
ON fkc.parent_object_id = t.object_id
INNER JOIN sys.columns as c
ON fkc.parent_object_id = c.object_id
and fkc.parent_column_id = c.column_id
INNER JOIN sys.tables as tr
ON fkc.referenced_object_id = tr.object_id
INNER JOIN sys.columns as cr
ON fkc.referenced_object_id = cr.object_id
and fkc.referenced_column_id = cr.column_id
ORDER BY 1, fkc.constraint_column_id
Índices
Nota: Esta consulta devuelve una fila por índice, y muestra las columnas separadas por comas.SELECT DISTINCT
da.tabla AS tabla,
i.name AS indice,
da.tipo AS tipo,
CASE i.is_primary_key
WHEN 1 THEN 'Sí'
ELSE 'No'
END AS llave_primaria,
CASE i.is_unique
WHEN 1 THEN 'Sí'
ELSE 'No'
END AS llave_unica,
CASE i.is_unique_constraint
WHEN 1 THEN 'Sí'
ELSE 'No'
END AS restriccion_unicidad,
LEFT(cols.names, LEN(cols.names) - 1) columnas
FROM sys.tables AS t
INNER JOIN sys.columns c
ON t.object_id = c.object_id
INNER JOIN sys.index_columns AS ic
ON ic.OBJECT_ID = c.OBJECT_ID AND ic.column_id = c.column_id
INNER JOIN sys.indexes AS i
ON i.OBJECT_ID = ic.OBJECT_ID AND i.index_id = ic.index_id
CROSS APPLY (
SELECT
SCHEMA_NAME(schema_id) + '.' + t.name tabla,
CASE i.index_id
WHEN 1 THEN 'CLUSTERED'
ELSE 'NONCLUSTERED'
END AS tipo
) da
CROSS APPLY (
SELECT c_i.name + ','
FROM sys.columns c_i
INNER JOIN sys.index_columns AS ic_i
ON ic_i.OBJECT_ID = c_i.OBJECT_ID AND ic_i.column_id = c_i.column_id
WHERE i.OBJECT_ID = ic_i.OBJECT_ID AND i.index_id = ic_i.index_id
FOR XML PATH ('')
) cols(names)
ORDER BY 1, 2, 3
Sobreviviendo a un OS descontinuado - Apricity OS
Saludos a todos, ya tenía un largo tiempo de no escribir en el blog, debido a algunos proyectos que han consumido demasiada parte de mi tiempo libre :(
Precisamente debido a esto, ya que los proyectos habían sido desarrollados en en Windows (Visual Studio, .NET, y todas esas cosas del imperio), ya tenía un largo rato de no utilizar el sistema Linux que también tengo instalado en mi computadora personal. Pues bien, esta mañana finalmente encendí mi computadora e ingresé a mi muy apreciada instalación de Apricity OS, y como primera tarea decidí que quería hacer funcionar el administrador de fondos de escritorio que tengo instalado, llamado Variety, ya que la ultima vez que entré al sistema habia notado que este había dejado de funcionar y que no podría ser iniciado. Intente ejecutarlo desde la línea de comandos, para obtener más información acerca del posible error, y vi un mensaje que me indicaba que la razón por la cual no estaba funcionando era que la versión de Variety que tenía instalada era antigua, y que debía actualizar a la versión más reciente. Yo le temo a las actualizaciones, pues tienden a romper cosas, en Windows y Linux, pero bueno, era solo un paquete, así que procedí a la actualización usando la confiable herramienta pacman.
sudo pacman -S variety
Pero tristemente no se pudo, ya que en su lugar me apareció el siguiente mensaje:
Siendo un noob en Linux, y en particular en Arch, me puse a investigar en google, y de lo que logré encontrar, decidí que tal vez necesitaba actualizar las bases de datos de los repositorios, así que intenté eso:
sudo pacman -Syy
Y luego me apareció un mensaje más claro:
Falló la descarga del archivo 'apricity-core.db' desde static.aprictyoscom : Falló la conexión a static.apricityoscom puerto 80: Conexión rechazada
La triste sorpresa
Mmm, ¿será tal vez que el servidor no está disponible?¿Habrá algún espejo para poder usar?
¿Será que iban a hacer mantenimiento de sus servidores este día?
Bueno, a googlear para buscar alguna noticia. Y allí fue cuando, para mi sorpresa, me dí cuenta de la triste noticia: Apricity OS ha sido descontinuado, desde el 6 de mayo del presente 2017. Ingresando a su página de Github, se puede ver el siguiente mensaje:
¿Apricity OS?
La noticia es triste para mí, ya que Apricity OS había sido mi versión favorita de Linux, desde que empecé a ocupar Linux. Apricity OS era una distro basada en Arch Linux, que intentaba ofrecer lo mejor de este sistema operativo de un forma amigable y sencilla. Entre las cosas buenas que tenía era que oseía un instalador gráfico para facilitar la instalación y actualización de paquetes. También compartía el modelo de actualización de liberación continua de Arch, lo que permitía actualizar el sistema sin tener que instalarlo desde cero nuevamente cada vez que hubiera una nueva versión. Otra característica interesante es que incluía ICE, que es una aplicación que permite utilizar auna aplicación web como si fuera una aplicación de escritorio normal. También poseía una interfaz con un diseño limpio y moderno utilizando Gnome 3.¿Y ahora qué?
La verdad es que a mí me encanta este sistema operativo, sigue siendo el que más me ha gustado, y considero que siendo basado en Arch, aún puedo continuar utilizándolo, ya que buena parte de los paquetes provienen de ese entorno. Bajo esta premisa, mi siguiente paso fue desactivar el repositorio de Apricity OS de la configuración de pacman. Para ello abrí el archivo /etc/pacman.conf en un editor de texto con el usuario administrador (en mi caso utilicé el comando gksudo geany /etc/pacman.conf para ello). Luego marqué como comentarios las siguientes líneas:[apricity-core] SigLevel = Required Server = http://static.apricityos.com/apricity-core-signed/
Así:
#[apricity-core] #SigLevel = Required #Server = http://static.apricityos.com/apricity-core-signed/
Con ello pacman ignora el repositorio de Apricity OS, lo cual evita que se produzca el fallo debido a que no puede descargar su base de datos. Hecho esto ya pude actualizar el paquete que necesitaba.
Conclusiones finales
Es una triste noticia que un sistema operativo tan eficiente, elegante y amigable haya llegado a su fin, en un tiempo tan corto (creo que fue un poco más de un año el tiempo que el proyecto estuvo disponible), pero así es el mundo del software libre, ya que se depende del trabajo voluntario de comunidades de desarrolladores que buscan ofrecer piezas de software increíbles sin esperar nada a cambio. "Por amor al arte", como dicen. Solo nos queda agradecer a estas personas por la maravillosa experiencia que nos ofrecieron al traer al mundo a Apricity OS, y también nos deja la motivación para que nosotros nos animemos a integrarnos a alguna de estas apreciables comunidades.Saludos, y hasta la próxima.
viernes, 18 de noviembre de 2016
Eliminar ramas antiguas de repositorio GIT local
Al trabajar durante un tiempo con GIT utilizando un modelo de ramificación que implique la creación de varias ramas, como por ejemplo GitFlow, las ramas antiguas se van acumulando en nuestro repositorio local, aunque ya hayan sido eliminadas del repositorio remoto, debido a que las herramientas de GIT no hacen este proceso de forma automática, y no existe un comando de GIT que lo haga por nosotros.Sin embargo, con ayuda de algunos útiles comandos, es posible crear una instrucción que nos permita eliminar las ramas locales que hacen referencia a ramas remotas que ya han sido eliminadas:
git fetch -p && git branch -d `git branch -vv | grep ': gone]' | awk '{print $1}' | xargs`
A continuación se explica paso a paso lo que hace este conjunto de comandos:
| Paso | Comando | Descripción | |
|---|---|---|---|
| 1 | git fetch -p | Obtiene los cambios más recientes desde el repositorio remoto, y elimina ("corta") las referencias a las ramas remotas que ya no existen. | |
| 2 | git branch -vv | Lista las ramas de forma detallada, lo cual incluye información el vínculo entre las ramas locales y las referencias a las ramas remotas, así como el estado de las ramas remotas. | |
| 3 | grep ': gone]' | Del listado anterior de ramas, filtra aquellas que contengan el texto ': gone]', es decir, aquellas ramas cuyas ramas remotas vinculadas ya fueron eliminadas. Estas ramas eliminadas poseen el estado gone. | |
| 4 | awk '{print $1} | De la lista filtrada, obtiene la primera columna, la cual contiene el nombre de la rama local. | |
| 5 | xargs | Convierte las filas de nombres de ramas en una cadena de una sola línea, separando el contenido de cada fila mediante un espacio en blanco. | |
| 6 | git branch -d | Elimina las ramas listadas en el paso anterior. Al utilizar la d minuscula, se indica que no se forzará la eliminación de ramas, lo que previene que se eliminen ramas locales que no hayan sido combinadas (merged) a otras ramas. |
Esta serie de comandos trabaja solamente con el repositorio local, por lo que no hay peligro de afectar el repositorio remoto. Sin embargo, se recomienda asegurarse de haber subido (push) los cambios de todas las ramas, para prevenir la eliminación de ramas locales que aún tengan cambios no incluidos en el repositorio remoto.
martes, 11 de octubre de 2016
Fondo de escritorio rotativo en Elementary OS
Saludos. Despues de un par de semanas de esfuerzo, finalmente pude instala la ultima version de Elementary OS, Loki (v0.4), en mi laptop. Y para embellecer un poco mi entorno, decidi buscar alguna aplicacion con la que pudiera cambiar la imagen de fondo de mi escritorio de forma periodica y aleatoria (wallpaper slideshow). Despues de una rapida busqueda en Google, me tope con Variety.Características
Variety ha sido creado por Peter Levi, y es un cambiador de fondos de pantalla de código abierto, como se describe en su página. Sin embargo, esta definición se queda corta, ya que incluye una gran cantidad de funcionalidades:- Descarga periódica y automática de imágenes desde sitios de internet como Flickr, Wallhaven, entre otros. Esto realmente te quitará el aburrimiento, ya que tendrás nuevas imágenes cada vez que veas tu escritorio. Además tú puedes configurar cuánto espacio de disco dedicar a las imágenes descargadas, y cada cuánto tiempo descargarlas.
- Puedes escoger los directorios locales o sitios web desde los cuáles Variety toma las imágenes.
- Puedes habilitar el selector de fondos de pantalla (wallpaper selector), que te muestra en la parte de abajo de la pantalla las las imágenes que tienes disponibles, y te permite escoger la que más te guste para mostrarla en tu escritorio.
- Puedes indicar las imágenes que más te gustan, y guardarlas en una carpeta especial. Si te suscribes a VRTY.ORG, puedes compartir tus imágenes con otros usuarios de Variety, y además copiar tus imágenes favoritas en todas tus computadoras. Cabe advertir que el sitio web VRTY.ORG puede contener imágenes obscenas o para mayores de edad.
- Teniendo en cuenta la advertencia previa, es importante mencionar que a última versión de Variety hasta este momento (0.6.2) incluye un "modo seguro", que sirve para evitar imágenes no aptas para menores de edad. Este modo seguro se basa en los ratings de Wallhaven, así como en las clasificaciones provistas por la comunidad de Variety. Esto no significa que Variety pondrá cualquier cosa en tu escritorio, ya que por defecto solamente tiene habilitadas las fuentes de imágenes más seguras.
- No podía faltar además un conveniente reloj digital, que puedes personalizar y mostrar en tu escritorio con la fuente y tamaño que tú elijas.
Instalación
Variety se encuentra disponible ya en los repositorios oficiales de Ubuntu Xenial (16.04), que es el sistema operativo en el que se basa Elementary OS Loki, por lo que para instalarlo solo basta ejecutar el siguiente comando:sudo apt-get install variety
Sin embargo esta es la versión 0.6.0 de Variety, y la versión más reciente disponible hasta el momento de esta entrada es la 0.6.2, por lo que si quieres instalar la versión más reciente, debes añadir el repositorio oficial de variety, e instalar los paquetes variety y variety-slideshow, así:
sudo add-apt-repository ppa:peterlevi/ppa sudo apt-get update sudo apt-get install variety variety-slideshow
Variety también puede ser instalado en otros sistemas operativos. Las instrucciones más detalladas las puedes encontrar aquí.
Contribuye
Y si te ha gustado mucho esta aplicación, puedes contribuir a su creador a través de una donación, a la comunidad compartiendo imágenes en VRTY.ORG, o contribuyendo directamente al desarrollo del software de las distintas formas que se listan aquí. El código fuente, así como reportes de bugs, los puedes encontrar en Launchpad.
Con la tecnología de Blogger.












