<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-1518844712460479967</id><updated>2024-08-28T21:07:04.636-07:00</updated><category term="Visual Studio"/><category term="Gestión de Cambios"/><category term="JSON"/><category term="JavaScript"/><category term="SQL"/><category term="SQL Server Express"/><category term="T-SQL"/><category term="jQuery"/><category term="C#"/><category term="Codificación"/><category term="Git"/><category term="MSBuild"/><category term="Mejores Prácticas"/><category term="MemoryStream"/><category term="Mercurial"/><category term="Metodologías"/><category term="MySQL"/><category term="MySQL Workbench"/><category term="OSS"/><category term="Programación"/><category term="StreamReader"/><category term="StringBuilder"/><category term="Subversion"/><category term="UTF-8"/><category term="VIPRE"/><category term="Web"/><category term="XML"/><category term="XmlWriter"/><category term="XslCompiledTransform"/><category term="antivirus"/><category term="personalización"/><category term="plug-in"/><category term="propaganda"/><title type='text'>The Xint0 Code</title><subtitle type='html'>Notas sobre programación en diversos lenguajes.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://blog.xint0.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>13</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-7817301110156058103</id><published>2010-05-25T13:27:00.000-06:00</published><updated>2010-05-25T13:27:53.455-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="C#"/><category scheme="http://www.blogger.com/atom/ns#" term="Codificación"/><category scheme="http://www.blogger.com/atom/ns#" term="MemoryStream"/><category scheme="http://www.blogger.com/atom/ns#" term="StreamReader"/><category scheme="http://www.blogger.com/atom/ns#" term="StringBuilder"/><category scheme="http://www.blogger.com/atom/ns#" term="UTF-8"/><category scheme="http://www.blogger.com/atom/ns#" term="XML"/><category scheme="http://www.blogger.com/atom/ns#" term="XmlWriter"/><category scheme="http://www.blogger.com/atom/ns#" term="XslCompiledTransform"/><title type='text'>XslCompiledTransform y codificaciones de caracteres.</title><content type='html'>&lt;p&gt;Recientemente estaba depurando una aplicación en la que se utiliza &lt;code&gt;System.Xml.Xsl.XslCompiledTransform&lt;/code&gt; para aplicar una hoja de estilo XSL a un archivo XML en .Net y me topé con que la aplicación de la directiva &lt;code&gt;&amp;lt;xsl:output encoding=&quot;iso-8859-1&quot; /&amp;gt;&lt;/code&gt; depende de la clase utilizada para serializar la salida.&lt;/p&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;h2&gt;Codificación de caracteres en una transformación XML&lt;/h2&gt;
&lt;p&gt;Dado que XML utiliza la UTF-8 como la codificación predeterminada, se puede utilizar cualquier caracter Unicode en los valores de los elementos (algunos caracteres como &amp;lt; y &amp;gt; se deben codificar con su notación de entidad XML: &lt;code&gt;&amp;amp;lt; para &amp;lt; y &amp;amp;gt; para &amp;gt;&lt;/code&gt;). A través de la directiva &lt;code&gt;&amp;lt;xsl:output ... /&amp;gt;&lt;/code&gt; se puede indicar la codificación de caracteres que debe tener el resultado de la transformación utilizando el atributo &lt;code&gt;encoding&lt;/code&gt;. De esta manera se puede producir un documento con una codificación diferente a UTF-8 en la cual los caracteres que utilicen códigos superiores a 255 (0x00FF) se codifican con la notación para códigos Unicode: &lt;code&gt;&amp;amp;#nnnn;&lt;/code&gt; para la notación decimal o &lt;code&gt;&amp;amp;#xnnnn;&lt;/code&gt; para la notación hexadecimal.&lt;/p&gt;
&lt;h2&gt;Codificación de caracteres con un XmlWriter&lt;/h2&gt;
&lt;p&gt;Normalmente se utiliza código como el siguiente para obtener el resultado de una transformación en una variable tipo &lt;code&gt;string&lt;/code&gt; en memoria:&lt;/p&gt;
&lt;pre class=&quot;brush: csharp&quot;&gt;
//Crear instancia de StringBuilder para construir salida.
var sb = new StringBuilder();
//Crear instancia para la transformación.
var xsl = new XslCompiledTransform();
//Cargar hoja de estilo para la transformación.
xsl.Load(&quot;transformacion.xsl&quot;);
//Crear instancia de XmlWriter a partir del StringBuilder y la configuración de salida
//indicada en &amp;lt;xsl:output /&amp;gt;
var xw = XmlWriter.Create(sb, xsl.OutputSettings);
//Aplicar transformación.
xsl.Transform(&quot;entrada.xml&quot;, xw);
//Obtener resultado.
var resultado = sb.ToString();
&lt;/pre&gt;
&lt;p&gt;Si ejecutamos este código el resultado que obtenemos es la entrada transformada, pero simpre codificada en UTF-8 aún cuando se indique otra codificación en la directiva &lt;code&gt;&amp;lt;xsl:ouput encoding=&quot;...&quot; /&amp;gt;&lt;/code&gt;. ¿Por qué? Resulta, que las variables de tipo &lt;code&gt;string&lt;/code&gt; son cadenas de caracteres Unicode y la codificación predeterminada es UTF-8 y StringBuilder no permite indicar el tipo de codificación deseada.&lt;/p&gt;
&lt;p&gt;Para poder obtener el resultado con la codificación correcta en lugar de utilizar &lt;code&gt;StringBuilder&lt;/code&gt; debemos utilizar un &lt;code&gt;MemoryStream&lt;/code&gt; para obtener el resultado como una serie de bytes:&lt;/p&gt;
&lt;pre class=&quot;brush:csharp&quot;&gt;
var resultado = string.Empty;
using (var ms = new MemoryStream()) {
  //Crear instancia para la transformación.
  var xsl = new XslCompiledTransform();
  //Cargar hoja de estilo para la transformación.
  xsl.Load(&quot;transformacion.xsl&quot;);
  //Crear instancia de XmlWriter a partir del StringBuilder y la configuración de salida
  //indicada en &amp;lt;xsl:output /&amp;gt;
  var xw = XmlWriter.Create(ms, xsl.OutputSettings);
  //Aplicar transformación.
  xsl.Transform(&quot;entrada.xml&quot;, xw);
  //Cerrar instancia de XmlWriter
  xw.Close();
  //Restablecer posición del MemoryStream.
  ms.Position = 0;
  //Crear una instancia de StreamReader a partir del MemoryStream.
  using (var sr = new StreamReader(ms))
    //Obtener resultado.
    resultado = sr.ReadToEnd();
}
&lt;/pre&gt;
&lt;p&gt;De esta manera la cadena resultante respetará la codificación indicada en la directiva &lt;code&gt;&amp;lt;xsl:output encoding=&quot;...&quot; /&amp;gt;&lt;/code&gt;. Espero que esta nota les ahorre tiempo y dolores de cabeza al depurar transformaciones XSL utilizando .Net Framework.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/7817301110156058103/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/05/xslcompiledtransform-y-codificaciones.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/7817301110156058103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/7817301110156058103'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/05/xslcompiledtransform-y-codificaciones.html' title='XslCompiledTransform y codificaciones de caracteres.'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-951347233044149757</id><published>2010-05-02T11:43:00.000-06:00</published><updated>2010-05-02T11:43:40.931-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><category scheme="http://www.blogger.com/atom/ns#" term="jQuery"/><category scheme="http://www.blogger.com/atom/ns#" term="JSON"/><category scheme="http://www.blogger.com/atom/ns#" term="plug-in"/><title type='text'>Listas dependientes con jQuery: El plug-in</title><content type='html'>&lt;p&gt;En la nota &quot;&lt;a href=&quot;http://blog.xint0.com/2010/04/listas-dependientes-con-jquery.html&quot;&gt;Listas dependiente con jQuery&lt;/a&gt;&quot; presenté el código necesario para cargar dinámicamente las opciones de una lista en base al valor seleccionado en otra lista. En esta ocasión voy a presentar el resultado de la evolución del código presentado y cómo se transformó en un &lt;em&gt;plug-in&lt;/em&gt; de jQuery.&lt;/p&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;h2&gt;jQuery plug-in&lt;/h2&gt;
&lt;p&gt;Para los neófitos en la programación web, jQuery es una biblioteca de funciones que facilitan la creación de aplicaciones web, a través de la manipulación del modelo de objetos de documento (DOM) y la comunicación con un servidor utilizando AJAX y JSON. Además de proveer una amplia gama de funcionalidad, jQuery permite la integración de funcionalidad personalizada a través de un mecanismo de extensión: los agregados o &lt;em&gt;plug-ins&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Después de codificar la solución al problema de las listas dependientes, pensé que era un patrón lo suficientemente común como para que alguien hubiera creado un solución antes que yo. Entonces busqué en la biblioteca de extensiones de jQuery y efectivamente encontré un par de agregados que permiten cambiar las opciones disponibles en una lista dinámicamente a partir del valor seleccionado en otra lista. Sin embargo, estos agregados asumen que los datos ya están cargados, o dicho de otra manera, sólo filtran las opciones, no las cargan dinámicamente. Entonces, el siguiente paso lógico es crear un agregado de jQuery a partir de mi código.&lt;/p&gt;
&lt;h2&gt;La metamorfosis&lt;/h2&gt;
&lt;p&gt;Comencemos primero por ver el código base que se encarga de cargar las opciones de una lista dependiendo del valor seleccionado en otra:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: javascript&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
//Definir identificadores de campos
var idPadre = &#39;estado&#39;;
var idHijo = &#39;municipio&#39;;
//Definir objeto contenedor de lista de hijos por padre.
var padreHijos = {&quot;0&quot;:[]};
//Definir código que se ejecuta al terminar de cargar la estructura del documento.
$(document).ready(function() {
    //Obtener referencia al campo padre.
    var padreSelect = $(&#39;#&#39; + idPadre);
    //Definir código que se ejecuta al cambiar el valor del campo padre.
    padreSelect.change(function() {
        //Obtener referencia al campo hijo.
        var hijoSelect = $(&#39;#&#39; + idHijo);
        //Obtener clave de identificación del estado seleccionado.
        var padreId = padreSelect.val();
        //Deshabilitar el campo hijo
        hijoSelect[0].disabled = true;
        //Vaciar la lista de hijos
        hijoSelect.empty();
        //Agregar la opción predeterminada.
        hijoSelect.append(&#39;&amp;lt;option value=&quot;0&quot; selected=&quot;selected&quot;&amp;gt;-- Seleccionar --&amp;lt;/option&amp;gt;&#39;);
        //Definir función para cargar opciones en elemento $lt;select&amp;gt;
        var cargarHijos = function(padreId) {
            //Obtener lista de hijos
            var hijos = padreHijos[padreId];
            //Agregar opciones para cada municipio.
            for (id in hijos) {
                hijoSelect.append(&#39;&amp;lt;option value=&quot;&#39; + id + &#39;&quot;&amp;gt;&#39; + hijos[id] + &#39;&amp;lt;/option&amp;gt;&#39;);
            }
            //Habilitar la lista de hijos.
            hijoSelect[0].disabled = (padreId == 0);
        };
        //Si la clave de identificación del padre es mayor que cero
        //cargar lista de hijos.
        if (padreId &gt; 0) {
            //Si no existe la lista de hijos, obtener la información
            //del archivo codificado en JSON.
            if (!padreHijos[padreId]) {
                var url = &#39;m&#39; + padreId + &#39;.json&#39;;
                $.getJSON(url, function(data) {
                    //Guardar lista de hijos en copia local.
                    padreHijos[padreId] = data;
                    cargarHijos(padreId);
                });
            } else {
                //Si ya existe la lista de hijos en la copia local, simplemente cargarla
                cargarHijos(padreId);
            }
        }
    });
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Del código anterior, se pueden identificar las siguientes funciones:
&lt;ul&gt;
&lt;li&gt;Inicializar - Obtener referencia al campo padre y agregar código que responda al evento &lt;code&gt;change&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Limpiar Hijo - Elimina las opciones del campo hijo y agrega la opción predeterminada.&lt;/li&gt;
&lt;li&gt;Cargar Hijo - Agrega las opciones del campo hijo de acuerdo al valor del campo padre.&lt;/li&gt;
&lt;li&gt;Obtener Datos - Si las opciones no están en el almacén local, obtenerlas del almacén persistente (servidor) y guardarlas en el almacén local.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;Adicionalmente, ya que la idea de crear un agregado es que pueda aplicarse de manera genérica, el código debe permitir la configuración de la funcionalidad. Entonces es deseable que se pueda configurar el patrón de la nomenclatura para los archivos que contienen los datos.&lt;/p&gt;
&lt;p&gt;Finalmente todo esto lo tenemos que empaquetar de acuerdo al patrón sugerido en la documentación de jQuery. Este patrón consiste basicamente de una función que recibe como parámetro el objeto global jQuery al cual le agregamos una función con el nombre del agregado a través de la propiedad &lt;code&gt;fn&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La arquitectura de jQuery consiste en extender un objeto del DOM. En este caso el agregado proveería una función adicional a través de la cual se extiende el campo padre indicándole el identificador del campo hijo y el patrón de la nomenclatura de los archivos de datos.&lt;/p&gt;
&lt;h2&gt;El código resultante&lt;/h2&gt;
&lt;div&gt;&lt;pre class=&quot;brush: javascript&quot;&gt;
(function($) {
    var ParentChild;
    ParentChild = function(element, childId, urlPattern) {
        this.domSelect = element;
        this.domSelect.data(&#39;parentChild&#39;, this);
        this.child = $(&#39;#&#39; + childId);
        this.pattern = urlPattern;
        this.data = {};
        var me = this;
        this.getData = function(parentId) {
            $.getJSON(this.getUrl(parentId), function(data) {
                me.data[parentId] = data;
                me.populateChild(parentId);
            });
        };
    };
    ParentChild.prototype = {
        emptyChild: function() {
            this.child.empty();
            this.child.append(&#39;&lt;option value=&quot;0&quot; selected=&quot;selected&quot;&gt;-- Seleccionar --&lt;/option&gt;&#39;);
        },
        getUrl: function(parentId) {
            var url;
            if ((!this.pattern) || (this.pattern == &quot;&quot;))
                url = parentId;
            else
                url = this.pattern.replace(new RegExp(&quot;\\{p\\}&quot;, &quot;gi&quot;), parentId);
            return url;
        },
        disableChild: function() {
            this.child[0].disabled = true;
        },
        populateChild: function(parentId) {
            if(!this.data[parentId]) {
                this.getData(parentId);
            } else {
                for (childId in this.data[parentId])
                    this.child.append(&#39;&lt;option value=&quot;&#39; + childId + &#39;&quot;&gt;&#39; + this.data[parentId][childId] + &#39;&lt;/option&gt;&#39;);
                this.child[0].disabled = false;
            }
        }
    };
    $.fn.parentChild = function(childId, urlPattern) {
        var child = $(&#39;#&#39; + childId);
        this.each(function() {
            var pc;
            pc = $(this).data(&quot;parentChild&quot;);
            if ((!pc) || (typeof pc === &quot;undefined&quot;)) {
                pc = new ParentChild($(this), childId, urlPattern);
            }
            $(this).change(function() {
                var parentId = $(this).val();
                pc.emptyChild();
                pc.disableChild();
                if (parentId &gt; 0)
                    pc.populateChild(parentId)
            });
        });
    };
})(jQuery);
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;El código de las líneas 2 a la 42 definen una clase &lt;em&gt;privada&lt;/em&gt; que modela la funcionalidad básica y contiene los datos. En el constructor (líneas 3 a la 16), se guarda la referencia al elemento padre extendido en la propiedad &lt;code&gt;domSelect&lt;/code&gt;. Luego, se almacena la instancia de la clase utilizando la llave &lt;code&gt;parentChild&lt;/code&gt;. Se obtiene y se guarda la referencia al elemento hijo en la propiedad &lt;code&gt;child&lt;/code&gt;. Se guarda el patrón para construir los URL en la propiedad &lt;code&gt;pattern&lt;/code&gt;. Se inicializa la propiedad &lt;code&gt;data&lt;/code&gt; a un objeto vacío (recuerden que en JavaScript no hay arreglos asociativos, sino que los objetos permiten acceder a los valores de sus propiedades utilizando la sintaxis de arreglos). Se almacena la autoreferencia &lt;code&gt;this&lt;/code&gt; en la variable &lt;code&gt;me&lt;/code&gt;. En las líneas 10 a la 15 se define el método &lt;code&gt;getData&lt;/code&gt; que recibe un parámetro con la clave de identificación del padre y utiliza el método de jQuery &lt;code&gt;getJSON&lt;/code&gt; para obtener los datos de manera asíncrona. El URL se obtiene de la función &lt;code&gt;getUrl&lt;/code&gt; definida más adelante, y en la función de retrollamada (líneas 12 y 13) básicamente se almacenan los datos recibidos en el almacén local y se llama la función &lt;code&gt;populateChild&lt;/code&gt; con la clave de identificación del padre. Nótese que se utiliza la variable &lt;code&gt;me&lt;/code&gt; para referenciar la instancia de la clase &lt;code&gt;ParentChild&lt;/code&gt; ya que en este contexto &lt;code&gt;this&lt;/code&gt; es la referencia a la función de retrollamada (la función anónima definida en las líneas 12 y 13).&lt;/p&gt;
&lt;p&gt;En las líneas 17 a la 42, se definen las funciones:
&lt;ul&gt;&lt;li&gt;&lt;code&gt;emptyChild&lt;/code&gt; - Líneas 18 a la 21. Vacía las opciones de la lista dependiente y agrega la opción predeterminada.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getUrl&lt;/code&gt; - Líneas 22 a la 29. Genera el URL del archivo con los datos en formato JSON a partir de la clave de identificación del padre. Se utiliza una expresión regular muy simple para remplazar &lt;code&gt;{p}&lt;/code&gt; en el patrón con la clave de identificación del padre.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disableChild&lt;/code&gt; - Líneas 30 a la 32. Deshabilita el campo hijo.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;populateChild&lt;/code&gt; - Líneas 33 a la 41. Verifica si existen los datos para el padre indicado. Si no existen, llama a la función &lt;code&gt;getData&lt;/code&gt;. Si ya existen, agrega las opciones correspondientes y habilita el campo hijo.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;
&lt;p&gt;Lo interesante de estos métodos es que están definidos en el &lt;em&gt;prototipo&lt;/em&gt; de la clase, y por lo tanto sólo existe una copia de ellos. A diferencia del método &lt;code&gt;getData&lt;/code&gt;, el cual debido al concepto de &lt;em&gt;cerradura&lt;/em&gt; (&lt;em&gt;Closure&lt;/em&gt;) se replicaría en cada instancia de la clase &lt;code&gt;ParentChild&lt;/code&gt;.&lt;p&gt;
&lt;p&gt;Finalmente en las líneas 43 a la 59, se define la función de extensión de jQuery, es decir, el agregado. Cabe notar que jQuery opera sobre uno o más elementos del DOM, por lo que el patrón sugerido itera cada uno de los elementos asociados al objeto jQuery (línea 45). Básicamente primero determina si ya existe una instancia de la clase &lt;code&gt;ParentChild&lt;/code&gt; asociada con el elemento en cuestión, si no existe, entonces la crea pasando la referencia al elemento extendido con jQuery, la clave del elemento hijo y el patrón para construir el URL. Finalmente, agrega código para responder al evento &lt;code&gt;change&lt;/code&gt; (Líneas 51 a la 57) que obtiene el valor del campo padre, vacía el campo hijo, deshabilita el campo hijo y si el valor del padre es mayor que cero, llena la lista del campo hijo.&lt;/p&gt;
&lt;h2&gt;Conclusión&lt;/h2&gt;
&lt;p&gt;Crear un agregado para jQuery es relativamente sencillo. Hay que tomar en cuenta que se está extendiendo el objeto jQuery que a su vez extiene uno o más elementos del DOM. Hay que tener en cuenta el contexto de ejecución cuando se utiliza la referencia &lt;code&gt;this&lt;/code&gt;, ya que puede hacer referencia a algo totalmente diferente de lo que creemos. El propósito de un agregado es encapsular funcionalidad genérica, por lo que debemos proveer flexibilidad a través de parámetros de configuración. Eventualmente, liberaré el código con una licensia de código abierto.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/951347233044149757/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/05/listas-dependientes-con-jquery-el-plug.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/951347233044149757'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/951347233044149757'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/05/listas-dependientes-con-jquery-el-plug.html' title='Listas dependientes con jQuery: El plug-in'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-6649049362857541227</id><published>2010-04-24T14:29:00.000-06:00</published><updated>2010-04-24T14:29:34.652-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Gestión de Cambios"/><category scheme="http://www.blogger.com/atom/ns#" term="Git"/><category scheme="http://www.blogger.com/atom/ns#" term="Mercurial"/><category scheme="http://www.blogger.com/atom/ns#" term="Subversion"/><title type='text'>De Subversion a Git</title><content type='html'>Llevo años utilizando algún tipo de sistema para la administración de cambios. Desde mi primer acercamiento a la materia con Visual Source Safe, pasando por CVS, luego dándole preferencia a Subversion y recientemente decidí cambiar a &lt;a href=&quot;http://git-scm.com&quot;&gt;Git&lt;/a&gt;. A continuación explico las razones por las que decidí explorar otras alternativas, porque opté por Git.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;h2&gt;Desarrollo de Software Esbelto o haciendo software en tiempos de crisis&lt;/h2&gt;
&lt;p&gt;Una de las premisas de la manufactura esbelta que se han trasladado al desarrollo de software es la de la eliminación de aquellas actividades o consumo de recursos que no aportan valor al producto final. Esta práctica ha tomado mayor relevancia en la actualidad debido a la crisis que se vive y a la necesidad de hacer más con menos.&lt;/p&gt;
&lt;p&gt;Para los proyectos en los que participo había estado utilizando repositorios de Subversion con tres proveedores (&lt;a href=&quot;http://assembla.com&quot;&gt;assembla.com&lt;/a&gt;, &lt;a href=&quot;http://repositoryhosting.com&quot;&gt;repositoryhosting.com&lt;/a&gt;, &lt;a href=&quot;http://xp-dev.com&quot;&gt;xp-dev.com&lt;/a&gt;) evitando las complicaciones administrativas de mantener un repositorio de Subversion, aún y cuando soy el único programador en la mayoría de los casos. Sin embargo, el inconveniente es que Subversion &lt;strong&gt;requiere&lt;/strong&gt; conexión al repositorio para prácticamente todas las operaciones, por lo mismo puede resultar &lt;strong&gt;muy&lt;/strong&gt; tardado dependiendo del enlace, especialmente las operaciones que envían datos al repositorio a través de un enlace asíncrono (Alta velocidad de recepción/Baja velocidad de envío), entiéndase todos los enlaces domésticos.&lt;/p&gt;
&lt;p&gt;Entonces decidí explorar alternativas que hicieran más eficiente la operación de un sistema de gestión de cambios y de esta manera reducir los tiempos de espera. Fue entonces que investigué acerca de los sistemas de gestión de cambios distribuidos como Git, &lt;a href=&quot;http://bazaar.canonical.com&quot;&gt;Bazaar&lt;/a&gt;, y &lt;a href=&quot;http://mercurial.selenic.com&quot;&gt;Mercurial&lt;/a&gt;.&lt;p&gt;
&lt;h2&gt;Sistemas de gestión de cambios distribuidos&lt;/h2&gt;
&lt;p&gt;La principal diferencia entre Subversion y Git, es que el primero pertenece a la categoría de Sistemas de Gestión de Cambios Centralizados y el segundo a la categoría de Sistemas de Gestión de Cambios Distribuidos. En el esquema centralizado, los meta-datos y la historia se almacenan en un repositorio central (servidor) y los clientes se conectan a ese servidor central para enviar sus cambios y obtener la versión deseada (por lo general la última) del repositorio.&lt;/p&gt;
&lt;p&gt;Por otra parte, en el esquema distribuido cada copia del repositorio contiene &lt;em&gt;toda&lt;/em&gt; la historia y todos los meta-datos. Ya no es necesario un servidor central para que funcione el repositorio. La principal ventaja es la velocidad, pues ya no se requiere comunicarse con el servidor central a través de la red para realizar las operaciones comunes.&lt;/p&gt;
&lt;p&gt;Todavía se puede mantener un repositorio principal para efectos administrativos, a partir de ese repositorio principal los participantes de un equipo de desarrollo pueden obtner una copia (clonar el repositorio) y colaborar entre sí (compartir modificaciones entre sus propios repositorios) y una vez que se ha concluido, enviar las modificaciones al repositorio principal, ya sea a través de parches (archivos diff) o mezclando las modificaciones y copiando la historia de los repositorios locales.&lt;/p&gt;
&lt;h2&gt;Consumo de espacio (Tu repositorio a dieta)&lt;/h2&gt;
&lt;p&gt;Otro factor que influyó en la decisión, fue el espacio que consume el repositorio. En mi caso, un repositorio que en Subversión ocupaba alrededor de 80 MB, en Git se redujo a 28 MB. Sin embargo esto puede variar considerablemente, por ejemplo si almacenan archivos binarios (imágenes, ejecutables, etc.), en Git estos pueden consumir mucho espacio rápidamente, ya que no puede grabar las diferencias únicamente, y en la mayoría de los casos no se pueden comprimir (archivos JPEG por ejemplo).&lt;/p&gt;
&lt;h2&gt;Mire m&#39;ijo, algún día, todo este repositorio será suyo...¿Y las herramientas apá?&lt;/h2&gt;
&lt;p&gt;Aún cuando las herramientas disponibles para trabajar con Git en Windows no tienen la madurez de las disponibles para Subversion, son aceptables y quien se sienta agusto en la línea de comandos quedará más que satisfecho, pues Git como Subversion tienen como interfaz nativa la línea de comandos.&lt;/p&gt;
&lt;p&gt;Dicen que Mercurial tiene mejores herramientas para Windows, no me consta. No utilicé Mercurial, porque Git aunque más complicado, es más flexible. Tal vez, un día de estos experimente con Mercurial y les platico. Bazaar sólo lo he utilizado para obtener directamente código de proyectos como MySQL y OpenERP. No lo utilicé, porque simplemente los servicios que utilizo para hospedar mis repositorios privados no ofrecen Bazaar. Eso sí, si algún día deciden colaborar en proyectos de software libre tales como: Ubuntu, MySQL, Inkscape, OpenERP o Bugzilla, pues van a tener que utilziar Bazaar.&lt;/p&gt;
&lt;h2&gt;Conclusión&lt;/h2&gt;
&lt;p&gt;Los sistemas más populares son Subversion, Git y Mercurial, en ese orden. Las principales ventajas de utilizar Git son: velocidad, consumo de espacio, flexibilidad. La mayor desventaja es cambiar la manera de pensar y trabajar con tu sistema de gestión de cambios distribuido. En mi caso particular, la era de Subversion y en general de los sistemas de gestión de cambios centralizados está terminando. De ahora en adelante, utilizaré sistemas de gestión de cambios distribuidos, de preferencia Git, aunque aún me falta experimentar con Mercurial.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/6649049362857541227/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/04/de-subversion-git.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/6649049362857541227'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/6649049362857541227'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/04/de-subversion-git.html' title='De Subversion a Git'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-8004374985637149743</id><published>2010-04-24T00:15:00.000-06:00</published><updated>2010-04-24T00:15:37.163-06:00</updated><title type='text'>Nuevos precios 2010</title><content type='html'>&lt;h2&gt;Xint0 Software reduce sus precios&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;$15.00 dólares/hora + IVA en contrataciones de menos de 40 horas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$10.00&lt;/strong&gt; dólares/hora + IVA en contrataciones de 40 horas o más&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Para más información visite: &lt;a href=&quot;http://xint0.com&quot;&gt;xint0.com&lt;/a&gt; o escriba a: &lt;a href=&quot;mailto:ventas@xint0.com&quot;&gt;ventas@xint0.com&lt;/a&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/8004374985637149743/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/04/nuevos-precios-2010.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/8004374985637149743'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/8004374985637149743'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/04/nuevos-precios-2010.html' title='Nuevos precios 2010'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-5508932220311069215</id><published>2010-04-23T20:32:00.000-06:00</published><updated>2010-04-23T20:32:42.038-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><category scheme="http://www.blogger.com/atom/ns#" term="jQuery"/><category scheme="http://www.blogger.com/atom/ns#" term="JSON"/><category scheme="http://www.blogger.com/atom/ns#" term="Web"/><title type='text'>Listas dependientes con jQuery</title><content type='html'>&lt;p&gt;Recientemente me solicitaron que modificara un formulario de un sitio web para que al seleccionar un Estado, se cargara la lista de Municipios correspondientes de manera dinámica. La restricción era que no podía depender de procesamiento en el servidor debido a la plataforma en la que está construido el sitio. La solución que se me ocurrió fue utilizar JavaScript para cargar dinámicamente los municipios. A continuación les presento el código y una breve explicación de su funcionamiento.&lt;/p&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;br /&gt;
&lt;h2&gt;El problema: Campos dependientes&lt;/h2&gt;
&lt;p&gt;Es muy común que al crear formularios tengamos campos dependientes como el caso de los Estados y Municipios o Categoría y Productos. El reto es cargar las opciones del campo dependiente de manera dinámica en base al valor seleccionado en el campo padre. También es deseable que, para mejorar la experiencia del usuario, la información se cargue progresivamente. Es decir, cargar los municipios hasta que se seleccione el estado y cargar sólamente los municipios del estado seleccionado.&lt;/p&gt;
&lt;h2&gt;Herramientas&lt;/h2&gt;
&lt;h3&gt;jQuery&lt;/h3&gt;
&lt;p&gt;En algún momento experimenté con AJAX, antes de que se hicieran populares bibliotecas como &lt;a href=&quot;http://jquery.com&quot;&gt;jQuery&lt;/a&gt;, &lt;a href=&quot;http://prototypejs.org&quot;&gt;Prototype&lt;/a&gt;, &lt;a href=&quot;http://mootools.net&quot;&gt;MooTools&lt;/a&gt;, etc. Luego, utilicé Prototype en algunos proyectos, pero nunca había trabajado con jQuery, así que me aproveche la oportunidad y utilicé jQuery para aislar los problemas de compatibilidad entre navegadores a la hora de manipular la estructura de una página web.&lt;/p&gt;
&lt;h3&gt;JSON&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;JavaScript Object Notation&lt;/em&gt; es una notación simple para describir objetos desarrollada por Douglas Crockfort basada en el lenguaje JavaScript. Es un formato apliamente utilizado debido a que es más breve que XML y permite transferir datos relativamente complejos de manera eficiente.&lt;/p&gt;
&lt;h2&gt;La solución&lt;/h2&gt;
&lt;p&gt;A grandes rasgos la solución es simple:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Determinar el Estado seleccionado.&lt;/li&gt;
&lt;li&gt;Vaciar la lista de Municipios.&lt;/li&gt;
&lt;li&gt;Cargar los Municipios del Estado seleccionado.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Para lograr esto podemos responder al evento &lt;code&gt;change&lt;/code&gt; del elemento &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; utilizando jQuery de la siguiente manera:&lt;/p&gt;
&lt;h3&gt;Primera versión&lt;/h3&gt;
&lt;pre class=&quot;brush: javascript&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
//Definir objeto con las listas de municipios por estado.
var padreHijos = {
    &quot;0&quot;: [],
    &quot;1&quot;: {
        &quot;1&quot;: &quot;Aguascalientes&quot;,
        &quot;2&quot;: &quot;Asientos&quot;
        //Agregar municipios por estado.
        }
    };
//Definir identificadores de campos
var idPadre = &#39;estado&#39;;
var idHijo = &#39;municipio&#39;;
//Definir código que se ejecuta cuando se termine de cargar la estructura del documento.
$(document).ready(function() {
    //Obtener referencia al campo padre.
    var padreSelect = $(&#39;#&#39; + idPadre);
    //Definir código que se ejecuta cuando cambia valor de campo padre.
    padreSelect.change(function() {
        //Obtener referencia al campo hijo.
        var hijoSelect = $(&#39;#&#39; + idHijo);
        //Obtener clave de identificación del elemento padre.
        var padreId = padreSelect.val();
        //Deshabilitar el campo hijo.
        hijoSelect[0].disabled = true;
        //Eliminar las opciones del campo hijo.
        hijioSelect.empty();
        //Agregar opción predeterminada.
        hijoSelect.append(&#39;&amp;lt;option value=&quot;0&quot; selected=&quot;selected&quot;&amp;gt;-- Seleccionar --&amp;lt;/option&amp;gt;&#39;);
        //Obtener lista de hijos para el padre seleccionado.
        var hijos = padreHijos[padreId];
        //Agregar opciones para cada hijo.
        for (id in hijos) {
            hijoSelect.append(&#39;&amp;lt;option value=&quot;&#39; + id + &#39;&quot;&amp;gt;&#39; + hijos[id] + &#39;&amp;lt;/option&amp;gt;&#39;);
        }
        //Habilitar campo hijo.
        hijoSelect[0].disabled = (padreId == 0);
    });
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;p&gt;El código anterior depende de que exista un objeto &lt;code&gt;padreHijos&lt;/code&gt; que funciona como un arreglo asociativo de las claves de identificación de los estados y su lista de municipios. Luego, se definen los identificadores de los elementos &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; para los campos &lt;code&gt;estado&lt;/code&gt; y &lt;code&gt;municipio&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;El primer problema con este código es que la &lt;em&gt;base de datos&lt;/em&gt; puede requerir mucho espacio y por lo tanto, retrasar la descarga del código. Por lo tanto, la primer mejora sería cargar la información gradualmente. Para esto, podemos usar la función &lt;code&gt;getJSON&lt;/code&gt; que permite hacer una petición asíncrona de un archivo con datos en notación JSON. Entonces, en lugar de declarar todos los municipios en el código que se carga inicialmente, creamos un archivo con la lista de municipios para cada estado. Por ejemplo, para el caso de los municipios correspondientes a Aguascalientes, creamos el siguiente archivo:&lt;/p&gt;
&lt;h3&gt;m1.json&lt;/h3&gt;
&lt;pre class=&quot;brush: javascript&quot;&gt;
{&quot;1&quot;:&quot;Aguascalientes&quot;,
&quot;2&quot;:&quot;Asientos&quot;,
&quot;3&quot;:&quot;Calvillo&quot;,
&quot;4&quot;:&quot;Cosío&quot;,
&quot;5&quot;:&quot;Jesús María&quot;,
&quot;6&quot;:&quot;Pabellón de Arteaga&quot;,
&quot;7&quot;:&quot;Rincón de Romos&quot;,
&quot;8&quot;:&quot;San José de Gracia&quot;,
&quot;9&quot;:&quot;Tepezalá&quot;,
&quot;10&quot;:&quot;El Llano&quot;,
&quot;11&quot;:&quot;San Francisco de los Romo&quot;}
&lt;/pre&gt;
&lt;p&gt;Nótese que los archivos con la lista de municipios van a utilizar la nomenclatura: &lt;em&gt;&lt;code&gt;m{id}.json&lt;/code&gt;&lt;/em&gt; en la que se remplaza &lt;em&gt;&lt;code&gt;{id}&lt;/code&gt;&lt;/em&gt; por la clave de identificación del estado correspondiente.&lt;/p&gt;
&lt;p&gt;Luego hay que modificar el código para cargar los municipios dinámicamente a partir el estado seleccionado:&lt;/p&gt;
&lt;h3&gt;Segunda versión&lt;/h3&gt;
&lt;pre class=&quot;brush: javascript&quot;&gt;
&amp;lt;script type=&quot;text/javascript&quot;&amp;gt;
//Definir identificadores de campos
var idPadre = &#39;estado&#39;;
var idHijo = &#39;municipio&#39;;
//Definir objeto contenedor de lista de hijos por padre.
var padreHijos = {&quot;0&quot;:[]};
//Definir código que se ejecuta al terminar de cargar la estructura del documento.
$(document).ready(function() {
    //Obtener referencia al campo padre.
    var padreSelect = $(&#39;#&#39; + idPadre);
    //Definir código que se ejecuta al cambiar el valor del campo padre.
    padreSelect.change(function() {
        //Obtener referencia al campo hijo.
        var hijoSelect = $(&#39;#&#39; + idHijo);
        //Obtener clave de identificación del estado seleccionado.
        var padreId = padreSelect.val();
        //Deshabilitar el campo hijo
        hijoSelect[0].disabled = true;
        //Vaciar la lista de hijos
        hijoSelect.empty();
        //Agregar la opción predeterminada.
        hijoSelect.append(&#39;&amp;lt;option value=&quot;0&quot; selected=&quot;selected&quot;&amp;gt;-- Seleccionar --&amp;lt;/option&amp;gt;&#39;);
        //Definir función para cargar opciones en elemento $lt;select&amp;gt;
        var cargarHijos = function(padreId) {
            //Obtener lista de hijos
            var hijos = padreHijos[padreId];
            //Agregar opciones para cada municipio.
            for (id in hijos) {
                hijoSelect.append(&#39;&amp;lt;option value=&quot;&#39; + id + &#39;&quot;&amp;gt;&#39; + hijos[id] + &#39;&amp;lt;/option&amp;gt;&#39;);
            }
            //Habilitar la lista de hijos.
            hijoSelect[0].disabled = (padreId == 0);
        };
        //Si la clave de identificación del padre es mayor que cero
        //cargar lista de hijos.
        if (padreId &gt; 0) {
            //Si no existe la lista de hijos, obtener la información
            //del archivo codificado en JSON.
            if (!padreHijos[padreId]) {
                var url = &#39;m&#39; + padreId + &#39;.json&#39;;
                $.getJSON(url, function(data) {
                    //Guardar lista de hijos en copia local.
                    padreHijos[padreId] = data;
                    cargarHijos(padreId);
                });
            } else {
                //Si ya existe la lista de hijos en la copia local, simplemente cargarla
                cargarHijos(padreId);
            }
        }
    });
});
&amp;lt;/script&amp;gt;
&lt;/pre&gt;
&lt;h2&gt;Conclusión&lt;/h2&gt;
&lt;p&gt;Al utilizar jQuery se evitan las complicaciones de compatibilidad entre navegadores. También se facilita la creación de código que cargue gradualmente la información necesaria en lugar de cargar todo desde el principio.&lt;/p&gt;
&lt;p&gt;Posteriormente publicaré un artículo con la versión final y con la incorporación de esta funcionalidad en un &lt;em&gt;plug-in&lt;/em&gt; de jQuery.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/5508932220311069215/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/04/listas-dependientes-con-jquery.html#comment-form' title='6 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5508932220311069215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5508932220311069215'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/04/listas-dependientes-con-jquery.html' title='Listas dependientes con jQuery'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-1071400836544254115</id><published>2010-03-27T15:38:00.000-07:00</published><updated>2010-03-27T15:38:04.287-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Gestión de Cambios"/><category scheme="http://www.blogger.com/atom/ns#" term="Mejores Prácticas"/><category scheme="http://www.blogger.com/atom/ns#" term="Metodologías"/><category scheme="http://www.blogger.com/atom/ns#" term="Programación"/><title type='text'>Gestión de Configuración</title><content type='html'>&lt;h1&gt;
Gestión de Configuración de Software&lt;/h1&gt;
Durante el desarrollo de cualquier producto de software, sea pequeño o sea grande, no importa el tamaño del equipo, ni el lenguaje de programación, el único factor que permanece constante irónicamente es el cambio. El &lt;i&gt;desarrollo&lt;/i&gt;&amp;nbsp;se refiere a que los componentes o artefactos que creamos y combinamos para crear nuestro producto de software, van evolucionando a lo largo del tiempo hasta que llegamos a una configuración que satisface el conjunto de requisitos que son prioritarios para los interesados en el producto en ese momento.&lt;br /&gt;
&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;p&gt;Cuando hacemos un programa pequeño, de manera personal, el grado de complejidad es bajo. Por lo tanto somos capaces de administrar los cambios de manera aceptable. Sin embargo, qué pasa cuando tres meses después de estar trabajando en otras cosas, volvemos a tratar de mejorar ese pequeño programa, cambiando el código para utilizar algún patrón que recién aprendimos o agregarle funcionalidad.&lt;/p&gt;&lt;p&gt;La mayoría de los programadores aborrecen la documentación y en un proyecto pequeño, inclusive evitan agregar comentarios al código, pues &lt;em&gt;resulta obvio&lt;/em&gt; lo que está haciendo y cómo lo hace. Muy probablemente sí era obvio el qué y el cómo mientras se estaba tecleando el código. Pero tres meses después, ya no resulta tan obvio e inclusive puede despertar dudas acerca de la efectividad o eficiencia del código. Esa capacidad limitada de retención de memoria a largo plazo tiene su lado positivo, es lo que permite la evolución de las ideas.&lt;/p&gt;&lt;p&gt;En cuanto comenzamos a modificar el código de hace unos meses, estamos modificando la configuración de los componentes de nuestro producto de software, o sea, nuestro programa. Si tenemos suerte, después de hacer las modificaciones el código compila, pero eso no significa que el programa siga funcionando como esperamos. De hecho lo peor que puede suceder es que el código pase sin problemas las fases de análisis léxico y sintáctico del compilador y se produzca un nuevo ejecutable y que al utilizarlo nos genere un error de ejecución críptico en el método con el código más &lt;em&gt;obvio&lt;/em&gt; de todos y que ni siquiera tocamos durante la modificación.&lt;/p&gt;
&lt;h2&gt;Aprender de la historia o ¿por qué el programador odia la documentación?&lt;/h2&gt;
&lt;p&gt;Utilizar un sistema de gestión de configuración, también conocido como control de cambios o control de versiones, es registrar la evolución de nuestro producto a lo largo del tiempo. Considero que es la documentación más importante que podemos registrar en cualquier proyecto de desarrollo.&lt;/p&gt;
&lt;p&gt;La aversión que tienen la mayoría de los programadores con respecto de la documentación, proviene de las prácticas del milenio pasado. Las primeras metodologías para el desarrollo de software eran muy estrictas y extrapolaban prácticas que funcionaban bien en otras industrias, como la construcción, en las que el grado de incertidumbre es relativamente bajo. Sin embargo, todo proyecto para el desarrollo de software comienza con una gran incertidumbre que va disminuyendo paulatinamente hasta que al final conocemos la configuración final del producto el día en que se pone en operación.&lt;/p&gt;
&lt;p&gt;Debido a esta incertidumbre, el tratar de definir un producto de software y plasmarlo en documentación inamovible es una causa perdida. En el desarrollo de software es imposible tener los planos antes de empezar a codificar. Para empezar, la persona o grupo que pide la creación de un producto de software, rara vez sabe exactamente y a nivel detallado qué es lo que quiere, y no se diga qué necesita en realidad. Luego, a diferencia de la construcción de un edificio en la que resulta relativamente sencillo conocer el impacto que tiene la modificación de las especificaciones iniciales. En el desarrollo de software, al ser un producto intangible, resulta muy difícil comprender el impacto que tiene un cambio en los requisitos, por minúsculos e insignificantes que le parezcan al interesado.&lt;/p&gt;
&lt;p&gt;Entonces, ese tipo de documentación aplicado al desarrollo de software simplemente &lt;strong&gt;no sirve&lt;/strong&gt;. En cambio, las prácticas contemporáneas que se han agrupado bajo el término de Metodologías Ágiles, tienen como premisa la incertidumbre inherente al desarrollo de software y en lugar de tratar de amoldar el desarrollo a la rigidez del método, se adaptan de manera flexible al proceso evolutivo del desarrollo de software.&lt;/p&gt;
&lt;p&gt;Como ya lo expuse, desarrollo implica un proceso evolutivo, cambios constantes y es por eso que en lugar de tratar de reducir la cantidad de cambios a través de documentación rígida, lo racional y lógico es aceptar la constancia del cambio y documentar la evolución. En lugar de intentar detener el río y caminar sobre el agua, hay que fluir con la corriente.&lt;/p&gt;
&lt;p&gt;Un sistema de gestión de configuración nos permite guardar la historia evolutiva de nuestro producto de software. Así la podemos revisar para saber por qué se tomaron decisiones en el diseño de los componentes y entender cómo llegamos al estado actual. A partir de esta historia, podemos deducir cuánto costará agregar o modificar funcionalidad. También puede servir como un curso abreviado para miembros nuevos del equipo. Es más interesante para un programador novato ver la evolución del código que leer manuales técnicos con análisis y diseño que nunca llegó al producto final.&lt;/p&gt;
&lt;h2&gt;¿Por qué es importante?&lt;/h2&gt;
&lt;p&gt;A medida que crece un producto de software y que el equipo de desarrollo pasa de una sola persona a una docena, la complejidad de la configuración de los componentes, así como la de los canales de comunicación crece geométricamente, o sea, no se suma, se multiplica. Resulta pues, que aventurarse a intentar desarrollar software sin un sistema de gestión de configuración, es como conducir un automóvil de carreras a través de un túnel de doble sentido, sin luces y con los ojos vendados.&lt;/p&gt;&lt;p&gt;Cualquiera que se autodenomine como &lt;em&gt;desarrollador de software&lt;/em&gt; debe utilizar un sistema de gestión de configuración para &lt;strong&gt;todos&lt;/strong&gt; sus proyectos. Si te fascina codificar y compilas en la mente, te hace falta una cosa para ser un buen programador: registrar la historia de tu código. Si tan sólo pudieramos ejecutar &lt;pre&gt;diff&lt;/pre&gt; entre el presente y la historia del mundo y aplicar &lt;pre&gt;patch&lt;/pre&gt; sobre todas las injusticias del mundo...&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/1071400836544254115/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2010/03/gestion-de-configuracion.html#comment-form' title='2 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/1071400836544254115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/1071400836544254115'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2010/03/gestion-de-configuracion.html' title='Gestión de Configuración'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-5601010892937972015</id><published>2009-11-25T09:33:00.000-07:00</published><updated>2009-11-25T09:33:32.564-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="MySQL"/><category scheme="http://www.blogger.com/atom/ns#" term="MySQL Workbench"/><category scheme="http://www.blogger.com/atom/ns#" term="OSS"/><title type='text'>Colaboración en proyectos de código abierto: MySQL Workbench</title><content type='html'>&lt;p&gt;Hoy recibí la contestación de Mike Lischke en relación a un pequeño parche (dos líneas) que envié para corregir un ligero error en &lt;a href=&quot;http://wb.mysql.com/&quot;&gt;MySQL Workbench 5.2&lt;/a&gt;, parece que sí lo integraron. Me siento bien orgulloso, porque hace mucho que no leía y depuraba código C++ mentalmente&amp;mdash;es decir, en notepad, fuera de Visual Studio&amp;mdash;. Pueden ver el reporte de incidencia (bug) en:&lt;br /&gt;
&lt;a href=&quot;http://bugs.mysql.com/39929&quot;&gt;http://bugs.mysql.com/39929&lt;/a&gt;&lt;br /&gt;
Por cierto, si trabajan con bases de datos MySQL, les recomiendo que pongan a prueba la siguiente versión de MySQL Workbench, 5.2 que se encuentra en beta actualmente. Todavía le falta afinar muchos detallitos&amp;mdash;claro que siempre pueden contribuir a solucionarlos&amp;mdash;, pero en general es un gran avance de versiones anteriores y se perfila como un excelente remplazo para las herramientas GUI de MySQL al integrar la funcionalidad de MySQL Query Browser, MySQL Server Administrator y una herramienta de documentación y modelación de esquemas de bases de datos extensible a través de rutinas escritas en Lua.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/5601010892937972015/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/11/colaboracion-en-proyectos-de-codigo.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5601010892937972015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5601010892937972015'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/11/colaboracion-en-proyectos-de-codigo.html' title='Colaboración en proyectos de código abierto: MySQL Workbench'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-4087140802841727719</id><published>2009-11-25T09:16:00.000-07:00</published><updated>2009-11-25T09:16:29.997-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="antivirus"/><category scheme="http://www.blogger.com/atom/ns#" term="propaganda"/><category scheme="http://www.blogger.com/atom/ns#" term="VIPRE"/><title type='text'>Aproveche descuento en antivirus este viernes</title><content type='html'>&lt;p&gt;Sunbelt Software tiene una oferta especial para este viernes 27 de noviembre:&lt;/p&gt;
&lt;h2&gt;70% de descuento en la compra de VIPRE Antivirus.&lt;/h2&gt;
&lt;p&gt;Esto significa que pueden adquirir la suscripción personal por un año a tan sólo $9.95 USD y por $19.95 USD pueden adquirir la licencia para el hogar, con la que pueden proteger TODAS las computadoras de su casa.&lt;/p&gt;
&lt;p&gt;Para adquirir su licencia haga clic en la siguiente imagen:&lt;br /&gt;
&lt;a href=&quot;http://www.sunbeltsoftware.com/sap/c/?aff_id=43804&amp;p=411&amp;b=411a&quot;&gt;&lt;img src=&quot;http://www.sunbeltsoftware.com/sap/i/411a.jpg&quot; border=&quot;0&quot; width=&quot;75&quot; height=&quot;115&quot; alt=&quot;VIPRE Antivirus + Antispyware&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;¿Por qué recomiendo este producto?&lt;/h2&gt;
&lt;p&gt;Simplemente te da más valor por tu dinero:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;No entorpece el desempeño de tu computadora. Yo buscaba un producto que protegiera mi configuración, sin degradar el desempeño durante los procesos de compilación.&lt;/li&gt;
&lt;li&gt;Ocupa pocos recursos. A diferencia de otros productos, VIPRE ocupa pocos recursos durante su operación, algo crítico para mi entorno de trabajo.&lt;/li&gt;
&lt;li&gt;Buen desempeño y bajo costo. Con la licencia para el hogar, protejo la computadora de mi esposa que abre todas las cadenas de correo que le envían y desde que lo instalé no he tenido que reinstalar el sistema operativo en su computadora.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Además te regalo un descuento de $5 USD en la compra de una licencia de VIPRE, simplemente ingresa el código de descuento:&lt;br /&gt;
&lt;strong&gt;VIPRESAP&lt;/strong&gt;&lt;br /&gt;
durante el proceso de pago.&lt;/p&gt;
&lt;p&gt;Recuerda sólo este viernes 27 de noviembre de 2009 &quot;Black Friday&quot;:&lt;br /&gt;&lt;a href=&quot;http://www.sunbeltsoftware.com/sap/c/?aff_id=43804&amp;p=411&amp;b=411a&quot;&gt;&lt;img src=&quot;http://www.sunbeltsoftware.com/sap/i/411a.jpg&quot; border=&quot;0&quot; width=&quot;75&quot; height=&quot;115&quot; alt=&quot;VIPRE Antivirus + Antispyware&quot;&gt;&lt;/a&gt;&lt;br/&gt;&lt;a href=&quot;http://www.sunbeltsoftware.com/sap/c/?aff_id=43804&amp;p=411&amp;b=411a&quot;&gt;VIPRE Antivirus + Antispyware por $9.95 USD&lt;/a&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/4087140802841727719/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/11/aproveche-descuento-en-antivirus-este.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/4087140802841727719'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/4087140802841727719'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/11/aproveche-descuento-en-antivirus-este.html' title='Aproveche descuento en antivirus este viernes'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-469268230317033563</id><published>2009-11-06T09:00:00.000-07:00</published><updated>2009-11-06T09:00:24.472-07:00</updated><title type='text'>Google libera herramientas para JavaScript</title><content type='html'>&lt;p&gt;Atención programadores de JavaScript, Google ha liberado como Código Abierto algunas de las herramientas que utilizan sus programadores para crear y optimizar el código JavaScript de sus aplicaciones web como GMail, Google Docs, etc. He aquí una liga a la anotación del blog en la que se anuncia la liberación:&lt;/p&gt;
&lt;a href=&quot;http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html&quot;&gt;Google Code Blog: Introducing Closure Tools&lt;/a&gt;&lt;p&gt;Posted using &lt;a href=&quot;http://sharethis.com&quot;&gt;ShareThis&lt;/a&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/469268230317033563/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/11/google-libera-herramientas-para.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/469268230317033563'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/469268230317033563'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/11/google-libera-herramientas-para.html' title='Google libera herramientas para JavaScript'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-6161193789888934505</id><published>2009-11-05T03:42:00.000-07:00</published><updated>2009-11-05T03:42:32.434-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="MSBuild"/><category scheme="http://www.blogger.com/atom/ns#" term="personalización"/><category scheme="http://www.blogger.com/atom/ns#" term="Visual Studio"/><title type='text'>Personalización del proceso de construcción de MSBuild</title><content type='html'>&lt;p&gt;En la anotación &lt;a href=&quot;http://blog.xint0.com/2009/10/ejecutar-una-rutina-t-sql-en-los.html&quot;&gt;anterior&lt;/a&gt; utilicé los eventos que se generan antes y después de la construcción (pre-build y post-build) para ejecutar, primero una rutina que genera el código para restaurar un respaldo de una base de datos en el ambiente de desarrollo; y finalmente ejecutar el código generado previo al despliegue de la actualización de un proyecto de base de datos. Ahora voy a explorar una alternativa más flexible y poderosa: personalizar el proceso de construcción de MSBuild.&lt;/p&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;p&gt;Los entornos para desarrollo integrados, se llaman así porque integran diversas herramientas que se utilizan durante la elaboración de software. Una de esas herramientas es la que &lt;em&gt;construye&lt;/em&gt; el producto de software, utilizo el verbo construir para referirme a este proceso, ya que con la evolución de los entornos y herramientas, ahora la construcción involucra más que la compilación del código fuente en código objeto, el enlace de bibliotecas y la creación de un programa ejecutable o biblioteca de rutinas.&lt;/p&gt;
&lt;p&gt;En el caso de Visual Studio y de .Net Framework en Windows, la herramienta encargada de orquestar la construcción se llama MSBuild.  De hecho, MSBuild es parte de .Net Framework y técnicamente no se necesita Visual Studio para crear asambleas o programas que utilicen CLR (Ejecutor de Lenguaje Común, por sus siglas en inglés).  Más bien, es al revés, Visual Studio requiere de MSBuild para construir los ejecutables y asambleas.&lt;/p&gt;
&lt;p&gt;Un proceso de construcción se define en MSBuild a partir de los siguientes elementos en términos generales:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;Elementos&lt;/li&gt;&lt;li&gt;Conjuntos de elementos&lt;/li&gt;&lt;li&gt;Tareas&lt;/li&gt;&lt;li&gt;Condiciones&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;La manera de expresar estos elementos es a través de un archivo XML, en el caso de Visual Studio el archivo de proyecto contiene además de las características utilizadas por el entorno, la definición de tareas requeridas para construir el proyecto.&lt;/p&gt;&lt;p&gt;De entrada, MSBuild cuenta con una amplia gama de tareas predefinidas. Estas van desde lo más simple como copiar un archivo, hasta la invocación del compilador de recursos de .Net para generar las asambleas satélite de recursos para globalización. Si no es suficiente, existe la tarea &lt;em&gt;Exec&lt;/em&gt; que permite ejecutar un comando arbitrario. De hecho, los eventos pre-build, post-build son tareas Exec y simplemente ejecutan la línea de comandos que se captura a través de los atributos del proyecto. Además, se pueden definir otras tareas a través de una asamblea, que contenga clases que implementen algunas interfaces. Una discusión más detallada será tema de futuras entradas. Por ahora, me limitaré a decir que podemos modificar el archivo de proyecto, para agregar algunas tareas que dependan del paso &lt;em&gt;build&lt;/em&gt; y que a su vez, sean requisitos del paso &lt;em&gt;deploy&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;En la siguiente entrada, detallaré paso a paso, como moví las líneas de comando de los eventos pre-build y post-build a un par de tareas que dependen de la ejecución exitosa del paso build, que se ejecutan sólo cuando lo indique una variable definida en el archivo de proyecto, y que sean requisitos previos del paso deploy.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/6161193789888934505/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/11/personalizacion-del-proceso-de.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/6161193789888934505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/6161193789888934505'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/11/personalizacion-del-proceso-de.html' title='Personalización del proceso de construcción de MSBuild'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-3392759939748546305</id><published>2009-10-23T12:16:00.002-06:00</published><updated>2009-10-23T19:18:06.628-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="SQL"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server Express"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><category scheme="http://www.blogger.com/atom/ns#" term="Visual Studio"/><title type='text'>Ejecutar una rutina T-SQL en los eventos Pre-Build y Post-Build en Visual Studio 2008</title><content type='html'>En la &lt;a href=&quot;http://blog.xint0.com/2009/10/sql-server-2005-express-y-visual-studio.html&quot;&gt;entrada anterior&lt;/a&gt; expuse una rutina para obtener la información de los respaldos de una base de datos de Microsoft SQL Server para generar una rutina que restaurara el respaldo.  El objetivo es integrar la rutina en el proceso de construcción de la solución/proyecto en Visual Studio 2008. En esta ocasión voy a utilizar los eventos &quot;Pre-Build&quot; y &quot;Post-Build&quot; para generar la rutina de restauración y ejecutarla para obtener una copia fresca de los datos antes de que se ejecute el despliegue (Deploy) de un proyecto de base de datos.&lt;br /&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;h4&gt;
Entorno&lt;/h4&gt;
Para poner en práctica esta idea, vamos a utilizar:
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio 2008 Team System Database Edition GDR R2&lt;/li&gt;
&lt;li&gt;Microsoft SQL Server Express 2005&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
Los eventos Pre-Build y Post-Build&lt;/h4&gt;
Visual Studio nos ofrece una manera fácil y rápida de ejecutar un comando previo a la construcción de un proyecto y otro después de la construcción. Para ver/modificar los comandos debemos acceder a la pestaña &quot;Eventos de Construcción&quot; (Build Events) en la ventana de Atributos del Proyecto (Project Properties):&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgimGzHNiML_aIjN4kF7WBinKl9KLDX5YdWunOA5RvogijPZcH4DiN0y9XrNRniCMECZPQ09THzVdzOiErkNKdzsoQfYr0ECCwy_spppclMf15C94MzYhY-rC40iQWU441FPpM39Xo9LYs/s1600-h/VS2008BuildEvents.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgimGzHNiML_aIjN4kF7WBinKl9KLDX5YdWunOA5RvogijPZcH4DiN0y9XrNRniCMECZPQ09THzVdzOiErkNKdzsoQfYr0ECCwy_spppclMf15C94MzYhY-rC40iQWU441FPpM39Xo9LYs/s640/VS2008BuildEvents.png&quot; /&gt;&lt;p&gt;Pestaña de Eventos de Construcción (Build Events)&lt;/p&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;
[Insertar Imagen]
&lt;br /&gt;
En los campos para la línea de comando de los eventos podemos escribir comandos que se ejecutarán en la línea de comandos de Windows, con la característica de que tenemos acceso a ciertas macros que nos permiten incrustar los valores de algunos atributos del proyecto en el texto del comando que se ejecuta:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYEwMfj0wyciM6Ky1r6GUS6Ox-MAf0v9NzDaCe4uTOEfaIh-JQGTjeSF9AZBje2-QeYigmjcroqzznQRxd7AO-9e8Y5F6wvZ9EZMnvud633b6OhyqpcqvUftVPRZ92F0RzsVgiMxHPa5M/s1600-h/VS2008PreBuildEvent.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjYEwMfj0wyciM6Ky1r6GUS6Ox-MAf0v9NzDaCe4uTOEfaIh-JQGTjeSF9AZBje2-QeYigmjcroqzznQRxd7AO-9e8Y5F6wvZ9EZMnvud633b6OhyqpcqvUftVPRZ92F0RzsVgiMxHPa5M/s640/VS2008PreBuildEvent.png&quot; /&gt;&lt;p&gt;Ventana de Línea de Comando para el Evento Pre-Build&lt;/p&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;h4&gt;
sqlcmd&lt;/h4&gt;
Antes de la interfaz gráfica existía el nirvana de la línea de comandos. Una simple interfaz que nos permite automatizar diversas tareas. Digamos que es el lenguaje natural de programación que nos ofrece el sistema operativo.&lt;br /&gt;
Cuando instalamos SQL Server Express, tenemos la opción de instalar las herramientas de administración y línea de comandos.  Para el mortal promedio, estos programas adicionales no son de mucha utilidad, sin embargo para el verdadero programador son como una caja llena de bloques Lego(tm). Uno de esos bloques es: sqlcmd.exe, un programa que nos permite:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Conectarnos a un servidor SQL Server —valga la redundancia— y enviarle comandos de manera interactiva o,&lt;/li&gt;
&lt;li&gt;lo que nos interesa, especificando un archivo de entrada a través del modificador &lt;code&gt;-i&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Además, podemos utilizar variables en el código del archivo y especificar su valor al momento de ejecución a través del modificador &lt;code&gt;-v&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
Ejecución condicional en la línea de comandos&lt;/h4&gt;
A pesar de que el intérprete de comandos de Windows está muy limitado en comparación con Bash, nos permite condicionar la ejecución de un comando utilizando la directiva &lt;code&gt;if&lt;/code&gt; que tiene variantes para evaluar expresiones booleanas que comparen cadenas de caracteres, determinar la existencia de un archivo, entre otras.&lt;br /&gt;
Si creen que necesitan un intérprete más rico en expresiones y comandos, pueden utilizar Windows Scripting Host para ejecutar rutinas escritas en VBScript o JScript. Otra alternativa más reciente y que exploraré próximamente es PowerShell&lt;br /&gt;
&lt;h4&gt;
El comando para generar la rutina de restauración&lt;/h4&gt;
Recordando los viejos tiempos de DOS e inspirado por las rutinas de Bash tan socorridas en Linux, escribí el siguiente comando para ejecutar mi rutina de T-SQL para generar el código para restaurar la base de datos en mi ambiente de desarrollo (ajusté el formato para mayor claridad, pero en realidad va en una sola línea):&lt;br /&gt;
&lt;div&gt;
&lt;pre&gt;&lt;code&gt;
if &quot;$(Configuration)&quot; == &quot;Debug&quot; (
  if &quot;$(DeployToDatabase)&quot; == &quot;True&quot; (
    sqlcmd -U sa -P contraseñaSA -S nombre.host.servidor,puerto
      -v ProductionDatabaseName=&quot;BaseDeDatosProduccion&quot;
      -v DatabaseName=&quot;$(FinalTargetDatabase)&quot;
      -v RestorePath=&quot;X:\Ruta\Restauracion&quot;
      -i &quot;$(SolutionDir)Ruta\A\Rutina\GenerarRutinaRestauracion.sql&quot;
      -o &quot;$(SolutionDir)NombreProyecto\$(OutputPath)Restaurar.sql&quot;
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
Básicamente el comando anterior se traduce a:&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Si la configuración del proyecto es igual a &quot;Debug&quot;, entonces:
  &lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Si está marcada la opción &quot;Deploy to Database&quot;, entonces:
    &lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Utilizar sqlcmd para ejecutar la rutina que genera la rutina de restauración, utilizando los parámetros indicados&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
El comando para ejecutar la rutina de restauración&lt;/h4&gt;
El comando para ejecutar la rutina que se generó en el event Pre-build es el siguiente:
&lt;br /&gt;
&lt;div&gt;
&lt;pre&gt;&lt;code&gt;
if &quot;$(Configuration)&quot; == &quot;Debug&quot; (
  if &quot;$(DeployToDatabase)&quot; == &quot;True&quot; (
    if exist $(SolutionDir)NombreProyecto\$(OutputPath)Restaurar.sql (
      sqlcmd -U sa -P contraseñaSA -S nombre.host.servidor,puerto
        -v DatabaseName=&quot;$(FinalTargetDatabase)&quot;
        -i $(SolutionDir)NombreProyecto\$(OutputPath)Restaurar.sql
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
Como ven es muy similar, les dejo de tarea su interpretación.&lt;br /&gt;
&lt;h4&gt;
Conclusión&lt;/h4&gt;
Visual Studio nos permite integrar acciones de manera fácil y rápida al proceso de construcción de una solución. Con un poco de ingenio podemos mejorar nuestra eficiencia al dejar que las tareas tediosas y repetitivas de nuestro proceso de desarrollo sean ejecutadas por la misma herramienta de construcción.&lt;br /&gt;
En algún momento en el futuro, exploraré más a detalle el proceso de construcción de Visual Studio.  Tengo curiosidad por conocer el proceso de construcción más a fondo y poder extenderlo más allá de los simples eventos.  Por lo pronto esta solución simple funciona y me ha permitido tener más tiempo disponible (mientras espero a que se complete el proceso de construcción de Visual Studio) para poder publicar esta entrada.&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/3392759939748546305/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/10/ejecutar-una-rutina-t-sql-en-los.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/3392759939748546305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/3392759939748546305'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/10/ejecutar-una-rutina-t-sql-en-los.html' title='Ejecutar una rutina T-SQL en los eventos Pre-Build y Post-Build en Visual Studio 2008'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgimGzHNiML_aIjN4kF7WBinKl9KLDX5YdWunOA5RvogijPZcH4DiN0y9XrNRniCMECZPQ09THzVdzOiErkNKdzsoQfYr0ECCwy_spppclMf15C94MzYhY-rC40iQWU441FPpM39Xo9LYs/s72-c/VS2008BuildEvents.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-8338213934941949737</id><published>2009-10-21T13:22:00.003-06:00</published><updated>2010-04-23T20:46:26.367-06:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="SQL"/><category scheme="http://www.blogger.com/atom/ns#" term="SQL Server Express"/><category scheme="http://www.blogger.com/atom/ns#" term="T-SQL"/><category scheme="http://www.blogger.com/atom/ns#" term="Visual Studio"/><title type='text'>SQL Server 2005 Express y Visual Studio 2008 : Restaurar Respaldo</title><content type='html'>&lt;p&gt;En uno de los proyectos en los que trabajo, tuve la necesidad de modificar el esquema de la base de datos.&amp;nbsp; Aún cuando Visual Studio Team Suite 2008 Database Edition GDR R2 facilita el trabajo con bases de datos de SQL Server, hay que programar rutinas para la migración de datos.&amp;nbsp; Además, creo que es bastante común el utilizar un respaldo de la base de datos del ambiente de producción en nuestro ambiente de prueba o de representación. Este artículo trata de cómo generar una rutina para automatizar la restauración del respaldo como parte del proceso de construcción del proyecto en Visual Studio 2008.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ya está publicada &lt;a href=&quot;http://blog.xint0.com/2009/10/ejecutar-una-rutina-t-sql-en-los.html&quot;&gt;la nota&lt;/a&gt; sobre la integración de esta rutina en el proceso de construcción de Visual Studio&lt;/em&gt;&lt;/p&gt;
&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;
&lt;h5&gt;
El ambiente de desarrollo&lt;/h5&gt;
En mi caso particular tengo la siguiente configuración:   

&lt;ul&gt;
&lt;li&gt;Ambiente de Desarrollo      
&lt;ul&gt;
&lt;li&gt;Visual Studio 2008 Team System SP1 &lt;/li&gt;
&lt;li&gt;Visual Studio 2008 Team Suite Database Edition GDR R2 &lt;/li&gt;
&lt;/ul&gt;

&lt;/li&gt;
&lt;li&gt;Servidor de Base de Datos:      
&lt;ul&gt;
&lt;li&gt;SQL Server 2005 Express Edition SP3 &lt;/li&gt;
&lt;/ul&gt;

&lt;/li&gt;
&lt;/ul&gt;
La solución está compuesta por varios proyectos, incluyendo dos de base de datos:   

&lt;ul&gt;
&lt;li&gt;Proyecto de Base de Datos de Servidor - Incluye la base de datos &#39;master&#39; y la definición de un usuario y su respectiva cuenta utilizada por la aplicación para conectarse a la base de datos. &lt;/li&gt;
&lt;li&gt;Proyecto de Base de Datos de la Aplicación - Este es el proyecto en el que se define el esquema de la base de datos de la aplicación en cuestión.&amp;nbsp; Tiene una referencia a la base de datos del servidor y es en este proyecto en el que agregué las rutinas de migración de datos y de generación de rutina para restaurar el respaldo. &lt;/li&gt;
&lt;/ul&gt;
La base de datos de la aplicación está configurada con el modelo de recuperación completa (FULL RECOVERY) y se tiene calendarizada la ejecución de rutinas de respaldo para generar un respaldo completo de la base de datos a las 2:00 a.m. todos los días y generar un respaldo de las bitácoras de transacciones (TRANSACTION LOGS) a partir de las 2:30 horas, cada hora.&amp;nbsp; Los respaldos se almacenan en un archivo correspondiente al día del respaldo.
Debido a la naturaleza de los cambios al esquema de la base de datos, se requiere la creación de rutinas de migración de datos.&amp;nbsp; Para poder probar estas rutinas, se me ocurrió restaurar un respaldo de la base de datos del ambiente de producción en el ambiente de prueba.&amp;nbsp; Al principio, hice el proceso manualmente. Pero al estar depurando las rutinas de migración de datos, se hizo evidente que necesitaba automatizar el proceso e integrarlo al proceso de construcción/compilación de la solución en Visual Studio.
El primer reto fue averiguar cómo generar la rutina para restaurar el respaldo.&amp;nbsp; Utilizando SQL Server Management Studio Express, se puede generar el código en Transact-SQL con algunos clics.&amp;nbsp; Y se obtiene algo como:
&lt;div&gt;
&lt;pre class=&quot;brush: sql&quot;&gt;
RESTORE DATABASE [LaBaseDeDatosDesarrollo]
FROM DISK = N&#39;X:\Ruta\Al\Archivo\De\Respaldo.bak&#39; WITH FILE = 3,
MOVE N&#39;BaseDeDatosProduccion.mdf&#39; TO N&#39;X:\Ruta\Al\Archivo\Restaurado1.mdf&#39;,
MOVE N&#39;BaseDeDatosProduccionBitacora.ldf&#39; TO N&#39;X:\Ruta\Al\Archivo\Restaurado1.ldf&#39;,
NORECOVERY, NOUNLOAD, REPLACE, STATS = 10;
GO
RESTORE LOG [LaBaseDeDatosDesarrollo]
FROM DISK = N&#39;X:\Ruta\Al\Archivo\De\Respaldo.bak&#39; WITH FILE = 4,
NORECOVERY, NOUNLOAD, STATS = 10;
GO
RESTORE LOG [LaBaseDeDatosDesarrollo]
FROM DISK = N&#39;X:\Ruta\Al\Archivo\De\Respaldo.bak&#39; WITH FILE = 5,
NORECOVERY, NOUNLOAD, STATS = 10;
GO
RESTORE LOG [LaBaseDeDatosDesarrollo]
FROM DISK = N&#39;X:\Ruta\Al\Archivo\De\Respaldo.bak&#39; WITH FILE = 6,
NOUNLOAD, STATS = 10;
GO
&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Sin embargo, yo quería que fuera un proceso totalmente automático y que se ejecutara como parte de la acción &quot;desplegar&quot; (deploy) en Visual Studio.&amp;nbsp; Entonces, pensé: Si SQL Server Management Studio Express puede generar el código de Transact-SQL, debe haber una manera en la que yo pueda hacer lo mismo en Transact-SQL.&lt;/p&gt;
&lt;h5&gt;
La base de datos msdb&lt;/h5&gt;
&lt;p&gt;SQL Server guarda la información de los respaldos en las tablas &lt;code&gt;[backupfile]&lt;/code&gt;, &lt;code&gt;[backupset]&lt;/code&gt;, &lt;code&gt;[backupmediafamily]&lt;/code&gt;, &lt;code&gt;[backupmediaset]&lt;/code&gt; de la base de datos &lt;code&gt;[msdb]&lt;/code&gt;.&amp;nbsp; Ya que el motor de SQL Server está diseñado para cubrir una amplia gama de necesidades en aplicaciones de todos los tamaños, muchas veces el control interno puede parecer muy rebuscado y complejo.&amp;nbsp; Sin embargo, leyendo un poco de la documentación podemos entender lo suficiente para nuestro propósito.&lt;/p&gt;
&lt;p&gt;No voy a repetir aquí la documentación de SQL Server.&amp;nbsp; Lo único que nos interesa es lo siguiente:
&lt;ul&gt;
&lt;li&gt;El nombre del archivo en el que se almacenó el respaldo está en la columna &lt;code&gt;[physical_device_name]&lt;/code&gt; de la tabla &lt;code&gt;[msdb]..[backupmediafamily]&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;La tabla &lt;code&gt;[msdb]..[backupset]&lt;/code&gt; contiene un registro por cada respaldo que se generó con mucha información del mismo.&amp;nbsp; Las columnas que nos interesan son: 
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[position]&lt;/code&gt; - La posición dentro del archivo de respaldo &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[database_name]&lt;/code&gt; - El nombre de la base de datos que se respaldó &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[type]&lt;/code&gt; - &#39;D&#39; si fue respaldo de base de datos o &#39;L&#39; si fue respaldo de la bitácora de transacciones &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[backup_set_id]&lt;/code&gt; - Número de identificación del conjunto &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;La tabla &lt;code&gt;[msdb]..[backupfile]&lt;/code&gt; contiene un registro por cada archivo de base de datos que se respaldó en un conjunto.&amp;nbsp; Las columnas que nos interesan son: 
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[logical_name]&lt;/code&gt; - Nombre lógico del archivo &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[file_type]&lt;/code&gt; - Tipo de archivo (&#39;D&#39; si es de base de datos o &#39;L&#39; si es archivo de la bitácora de transacciones) &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[create_lsn]&lt;/code&gt; - Número de secuencia de creación de la base de datos &lt;/li&gt;
&lt;li&gt;&lt;code&gt;[backup_set_id]&lt;/code&gt; - Número de identificación del conjunto al que pertenece &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;El comando &lt;code&gt;RESTORE&lt;/code&gt; y sus variantes: 
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RESTORE DATABASE&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;RESTORE LOG&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;
&lt;h5&gt;
El Código&lt;/h5&gt;
&lt;p&gt;Armado con la información anterior, hice la siguiente rutina:&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;brush: sql&quot;&gt;
DECLARE @backup_set_id INT;
DECLARE @physical_device_name NVARCHAR(260);
DECLARE @batch_text VARCHAR(MAX);
DECLARE @last_position INT;

-- Obtener el valor de [backup_set_id] para el último
-- respaldo completo de la base de datos del ambiente
-- de producción
SELECT @backup_set_id = MAX([backup_set_id])
FROM [msdb]..[backupset]
WHERE [database_name]=&#39;$(ProductionDatabaseName)&#39;
AND [type]=&#39;D&#39;;

-- Obtener el valor de [physical_device_name] de
-- la tabla [backupmediafamily] asociado al [backupset]
-- identificado por el comando anterior
SELECT
@physical_device_name = [mf].[physical_device_name]
FROM
[msdb]..[backupset] AS [bs] INNER JOIN
[msdb]..[backupmediafamily] AS [mf]
ON [bs].[media_set_id] = [mf].[media_set_id]
WHERE
[bs].[backup_set_id] = @backup_set_id;

-- Obtener el valor de [position] para el último
-- respaldo de la bitácora de transacciones realizado
-- después del último respaldo de base de datos
SELECT @last_position = MAX([position])
FROM [msdb]..[backupset]
WHERE [database_name]=&#39;$(ProductionDatabaseName)&#39;
AND [type]=&#39;L&#39;
AND [backup_set_id] &amp;gt; @backup_set_id;

-- Comenzar a generar el texto de la rutina
SET @batch_text =
&#39;PRINT &#39;&#39;RESTAURANDO BASE DE DATOS DE DESARROLLO &#39;
+ &#39;$(DatabaseName)...&#39;&#39;;&#39; + CHAR(13) + CHAR(10)
+ &#39;GO&#39; + CHAR(13) + CHAR(10)
+ &#39;USE [master]&#39; + CHAR(13) + CHAR(10)
+ &#39;GO&#39; + CHAR(13) + CHAR(10);

-- Agregar el comando para restaurar el último
-- respaldo de base de datos
SELECT @batch_text = @batch_text +
&#39;RESTORE DATABASE [$(DatabaseName)] FROM DISK = N&#39;&#39;&#39;
+ @physical_device_name + &#39;&#39;&#39; WITH FILE = &#39; 
+ CONVERT(VARCHAR, [position]) + &#39;,&#39; + CHAR(13) + CHAR(10)
FROM [msdb]..[backupset]
WHERE [backup_set_id] = @backup_set_id;

-- Agregar cada uno de los archivos en el conjunto de
-- respaldo, indicando su nueva ruta de localización
SELECT @batch_text = @batch_text +
CHAR(9) + &#39;MOVE N&#39;&#39;&#39; + [logical_name]
+ &#39;&#39;&#39; TO N&#39;&#39;$(RestorePath)\$(DatabaseName)&#39;
+ CONVERT(VARCHAR, ROW_NUMBER() OVER
(PARTITION BY [file_type] ORDER BY [create_lsn]))
+ CASE WHEN file_type=&#39;D&#39; THEN
&#39;.mdf&#39;
ELSE
&#39;_log.ldf&#39;
END + &#39;&#39;&#39;,&#39; + CHAR(13) + CHAR(10)
FROM [msdb]..[backupfile]
WHERE [backup_set_id] = @backup_set_id;

-- Terminar el comando de restauración de base de datos
SET @batch_text = @batch_text + CHAR(9)
+ &#39;NORECOVERY, NOUNLOAD, REPLACE, STATS = 10;&#39;
+ CHAR(13) + CHAR(10)
+ &#39;GO&#39; + CHAR(13) + CHAR(10);

-- Para cada respaldo de la bitácora de transacciones en el
-- conjunto de respaldos, agregar un comando que restaure
-- dicho respaldo
SELECT @batch_text = @batch_text
+ &#39;RESTORE LOG [$(DatabaseName)] FROM DISK = N&#39;&#39;&#39;
+ @physical_device_name + &#39;&#39;&#39; WITH FILE = &#39;
+ CONVERT(VARCHAR, [position]) + &#39;,&#39;
+ CASE WHEN [position] &amp;lt; @last_position THEN
&#39; NORECOVERY,&#39;
ELSE
&#39;&#39;
END
+ &#39; NOUNLOAD, STATS = 10;&#39; + CHAR(13) + CHAR(10)
+ &#39;GO&#39; + CHAR(13) + CHAR(10)
FROM [msdb]..[backupset]
WHERE [backup_set_id] &amp;gt; @backup_set_id
AND [POSITION] &amp;lt;= @last_position
AND [database_name]=&#39;$(ProductionDatabaseName)&#39;;

-- Finalmente terminar con un mensaje 
SET @batch_text = @batch_text
+ &#39;PRINT &#39;&#39;BASE DE DATOS DE DESARROLLO RESTAURADA...&#39;&#39;;&#39;
+ CHAR(13) + CHAR(10)
+ &#39;GO&#39; + CHAR(13) + CHAR(10)
+ &#39;USE [$(DatabaseName)]&#39; + CHAR(13) + CHAR(10)
+ &#39;GO&#39;;

-- Escribir el resultado a la salida estándar
PRINT @batch_text;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Nótese la utilización de variables de sustitución (todas las cadenas con el patrón “&lt;code&gt;$(identificador)&lt;/code&gt;”) para la ejecución con &lt;code&gt;SQLCMD&lt;/code&gt;.  De esta manera podemos pasar parámetros a nuestra rutina de acuerdo a valores de variables del proyecto de Visual Studio, la idea es que se ejecute esta rutina como parte de la construcción del proyecto. En la siguiente parte del artículo utilizaremos los eventos de pre-construcción y post-construcción para ejecutar esta rutina.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/8338213934941949737/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/10/sql-server-2005-express-y-visual-studio.html#comment-form' title='1 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/8338213934941949737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/8338213934941949737'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/10/sql-server-2005-express-y-visual-studio.html' title='SQL Server 2005 Express y Visual Studio 2008 : Restaurar Respaldo'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-1518844712460479967.post-5555980742143956599</id><published>2009-10-21T11:55:00.001-06:00</published><updated>2009-10-21T11:55:01.618-06:00</updated><title type='text'>El Código Xint0 / The Xint0 Code</title><content type='html'>&lt;p&gt;Este es mi blog dedicado a mi área de conocimiento especializado: Programación y Desarrollo de Software.&amp;#160; Publicaré artículos en español, enfocados principalmente en los lenguajes y tecnologías que utilizo a diario:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;.Net Framework&lt;/li&gt;    &lt;li&gt;C#&lt;/li&gt;    &lt;li&gt;SQL Server 2005&lt;/li&gt;    &lt;li&gt;XHTML&lt;/li&gt;    &lt;li&gt;CSS&lt;/li&gt;    &lt;li&gt;PHP&lt;/li&gt;    &lt;li&gt;MySQL&lt;/li&gt; &lt;/ul&gt;  &lt;p&gt;Sus preguntas y comentarios son bienvenidos.&lt;/p&gt;  </content><link rel='replies' type='application/atom+xml' href='http://blog.xint0.com/feeds/5555980742143956599/comments/default' title='Enviar comentarios'/><link rel='replies' type='text/html' href='http://blog.xint0.com/2009/10/el-codigo-xint0-xint0-code.html#comment-form' title='0 comentarios'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5555980742143956599'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/1518844712460479967/posts/default/5555980742143956599'/><link rel='alternate' type='text/html' href='http://blog.xint0.com/2009/10/el-codigo-xint0-xint0-code.html' title='El Código Xint0 / The Xint0 Code'/><author><name>Anonymous</name><uri>http://www.blogger.com/profile/11966805079366661772</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>