11/4/16

[genexus] JSConstructor User Control

Hace un par de años, me tocó aprender a trabajar con un framework llamado GeneXus. Se trata de una "herramienta de desarrollo de software", tal como la describe la Wikipedia, que permite agilizar la construcción de aplicaciones, pues el desarrollador tiene por única tarea diseñar y luego sólo declarar (lo que se conoce como programación de alto nivel): luego GeneXus se encarga de convertir dichas definiciones y declaraciones en código fuente, sobre la plataforma deseada (web, escritorio, móvil).

Como humilde programadora rasa, esta herramienta me resultó (y sigue resultándome) algo engorrosa de aprender: para alguien que construye una casa colocando ladrillo a ladrillo, es difícil pasar a construirla usando muros ya prefabricados por otros. Es imposible no sentir una cierta desconfianza, pues acostumbrada a verificar cada ínfimo detalle de la programación, ahora le pasamos esa responsabilidad a un autómata. Según como lo veo yo, esta herramienta está hecha no sólo para agilizar la tarea de implementar una idea de software, sino para que los ingenieros que no programan, puedan hacerlo en el menor tiempo y menor cantidad de dificultad posibles. Un logro no falto de ambición y mérito.

Cuando me tocó adentrarme en esta herramienta, vi todos los tutoriales oficiales, hice los ejercicios, y entendí lo que más pude. Luego, al comenzar a trabajar en aplicaciones reales (para el trabajo), pude ir entendiéndola aun mejor. Mas, al cabo de un par de meses, me topé con ciertas limitaciones, más que nada referidas a la parte gráfica y visual del software final. Por ello, me adentré entonces en lo que la comunidad de desarrolladores en GeneXus llaman User Controls: estos son pequeños módulos o widgets que permiten ampliar las facultades de la herramienta base.

A la sazón, desarrollé dos User Controls, los cuales usé en las aplicaciones que construí usando GeneXus. Uno de ellos lo publiqué (compartí) en la plataforma social de GeneXus, el Market Place. El otro, a falta de tiempo y de finiquitar su documentación, nunca pude publicarlo (aunque lo subí al MP, pero sin poder publicarlo: la documentación es un requisito para ello, tras la revisión de los moderadores del sitio).

Esta entrada de mi bitacorita es para compartir con ustedes el link hacia el User Control que pude publicar en el Market Place: JSConstructor. Éste permite crear elementos HTML desde GeneXus en tiempo de ejecución. Los elementos creados no tienen relación con los elementos GeneXus, lamentablemente, pero dejé abierta la interrogante a otros desarrolladores que puedan solucionarla. A la fecha, no sé en qué versión de GeneXus va la cosa, pero el JSConstructor fue desarrollado para la  X Evolution 1.

Pues esop. Para los desarrolladores de GeneXus, espero les sirva en sus aplicaciones mi pequeño aporte ^_^

Saludos a todos!


© 2014 Carolina Casanova García
- Descargar User Control JSConstructor
- Documentación JSConstructor

29/9/15

[javascript] Averiguar si Objeto existe usando JQuery

Tengo un sistema donde al cargar la página, se ejecutan una serie de funciones e instrucciones dentro del evento onLoad de la misma. Ahora bien, detecté que un error JS se estaba gatillando en algunas páginas. Resulta que tengo una barra de herramientas (un DIV con botones), la cual tiene asociado cierto comportamiento JS. Esta barra se encuentra en ciertas páginas que la usarán, pero en las que no, precisamente es en donde el error se gatilla.

Ya que estoy usando JQuery, tengo todas las funciones de onLoad condensadas en un solo archivo JS, por lo que era posible que esto ocurriera.

Por supuesto podría crear un archivo JS por cada página del sistema, de modo que cada una tenga programado de forma personalizada lo que ocurre en el evento onLoad de la misma. Peeeeero, como esto me parece sumamente lento y engorroso, prefiero seguir con el archivo único que es compartido por todas las páginas.

Pero, entonces, ¿cómo soluciono lo del error por objeto no definido (la barra que no existe)? Lo lógico es preguntar si el objeto existe, de modo que si no, pues pasar de largo, evitando los errores.

Usando sintaxis Javascript clásica, habría aplicado:

if (type document.getElementById("divCualquiera") != "undefined")
{
 // realizar las tareas de divCualquiera
}

Pero quise averiguar si JQuery no tenía ya implementada una forma más elegante de hacerlo.

Y bien, pues no, no la tiene.

Pero la alternativa que tiene, y que es usada por todos los programadores (de acuerdo a los mensajes en diversos sitios donde se consultó este mismo tópico) es:

if ($("#divCualquiera").length)
{
 // realizar las tareas de divCualquiera
}

Ahora bien, la propiedad "length" en JQuery retorna siempre un valor numérico (nos informa la cantidad de elementos contenidos por nuestro objeto). De ahí que se pueda consultar como si fuera un booleano (si cero, es falso; de lo contrario, verdadero).

Debo decir que no me gusta para nada esta solución, ya que no es entendible para quien ve el código por primera vez. Yo esperaba encontrar algo como una función "exists()" o algo por el estilo, pero nada. (De hecho, alguien sugirió crearla por cuenta propia, pero fue increíble la cantidad de comentarios negativos que recibió: pues el fin último de todo programador es reducir el código escrito. Pero yo soy de la opinión que sería ideal mantener la legibilidad del mismo en la medida de lo posible, y una función que pregunta lo que uno realmente quiere, es lo ideal.)

Esop. Hasta pronto con otro tip :)

Referencias:
- Is there an “exists” function for jQuery?
 - Jquery .length

9/3/15

[Web] Error: No aparecen los tipos de documento al intentar Crear Nuevo en Google Drive desde Firefox

Holas!! Hace mil años que no me aparecía por acá, jejeje... Pero bueno, acá estoy :P

Esta vez, es para compartir un nuevo Tip. En esta ocasión, comentarles la rápida solución para aquellos que disfrutan de Google Drive, que usan Firefox, pero que les ha surgido un curioso fallo al intentar Crear un nuevo documento en sus carpetas.

El error que a mí me apareció fue que al intentar crear un nuevo documento, no me aparecían los tipos de documento para elegir. En su lugar, me decía "Conectar aplicaciones...", lo cual hice siguiendo los pasos en la misma web descritos. Pero en vista que ni reiniciando el navegador el error se corregía (vale decir, me seguía saliendo vacía la lista de tipos de documentos, aun cuando al abrir el panel de Conectar Aplicaciones, las apps que yo había "conectado", efectivamente aparecían "conectadas", y encima un error en fondo rojo sobre el Drive me decía unas veces "Surgieron algunos problemas al cargar tus aplicaciones" y otras "Error al conectar con el servidor" o algo parecido).

El caso es que de pronto recordé que en algún momento, no sé si Firefox o mi PC, me mostró alguna pantalla de actualización de Google, y yo no lo tomé en cuenta. Entonces, pensé que de pronto se trataría de algún plugin que ahora era necesario tener activado para hacer que GD funcionara con Firefox. Y de hecho, por allí fue la solución.

Pero no fue en los plugins, sino en los Complementos de Firefox, en donde coloqué en la Búsqueda de Complementos "Google Drive", y me salió algo como "GDrive Panel", asunto que instalé, que pidió reiniciar, y que finalmente, solucionó el problema.

Y esop. Espero que les sirva a ustedes también, si tienen el problema. Y si lo solucionaron de otra forma más sencilla u ortodoxa, les agradeceré compartirlo con nosotros para aumentar nuestra sabiduría, ok? Gracias a todos por leer y aún más por comentar!! Hasta pronto!! :D

28/11/13

[javascript] Función retorna descendiente de objeto DOM HTML

Holas a todos!

Hoy les traigo una funcioncita Javascript que hice para apoyar ciertas tareas que estoy haciendo. Así como hace un tiempo les presenté la función tatas(), que les devolvía los ancestros  de un objeto HTML, ahora les traigo la función inversa: la función getDescendiente() les devolverá los objetos hijos o descendientes del objeto HTML buscado.

En su momento, dejé el código fuente de la función tatas() en una página en mi host personal. En esta ocasión, lo colocaré acá no más :) Espero que les sea de utilidad :D

Recuerden que si van a reutilizar la función, respeten los créditos colocados en los comentarios! Gracias y hasta pronto!

/**
-----------------------------------------------------------------------------
@about    Retorna objeto DOM (unico o coleccion) descendiente del DOM padre.
          Puede ser hijo, nieto, bisnieto: vendra dado por la Generacion.
@author   Carolina Casanova Garcia, aka, quinqui
@date     25/11/2013
@param    fDom           object        Requerido.
@param    fGeneracion    integer       Requerido.
@return   object
*/

function getDescendiente(fDom, fGeneracion)
{
    var fHijo = false;
    if (typeof fDom === "undefined" || fDom === undefined || fDom === null)
    {
        //alert("Error: Objeto No Existe");
    }
    else
    {
        fGeneracion    = (typeof fGeneracion == "undefined" ? 1: fGeneracion);
        var fHijo      = fDom;
        var fPadre;
        var f          = 0;
       
        while (f < fGeneracion)
        {
            fPadre    = fHijo;
            fHijo     = fPadre.childNodes;
            f++;
        }
    }
    return fHijo;
}


7/8/13

[php] NuSOAP HTTP Error: socket read of headers timed out

Holas a todos. Este es para comentar un problema que he tenido al trabajar un servicio web montado en PHP con la clase NuSOAP. El problema surgió cuando intenté llamar al servicio web desde el otro servidor, pero se caía a los exactos 30 segundos de ejecución, mostrando el mensaje que titula este registro: HTTP Error: socket read of headers timed out

Sabía que el problema era el timeout, pero ¿el timeout de qué? En los servidores y páginas web hay timeouts por todos lados: el de la Conexión a internet o la red, el del Servidor (hardware), el del Servidor Web en sí (Apache, mi caso), el de PHP (mi caso)... Pero nunca se me habría ocurrido que las Aplicaciones o frameworks también pudieran tener :o

Por eso, tras buscar por la red la solución a mi problema, la respuesta vino precisamente de alguien que señaló sencillamente que había que modificar el timeout de la clase NuSOAP. Y dicho y hecho, eso solucionó el problema.

Si están usando en su servidor y/o cliente la clase NuSOAP, y desean alterar el timeout, deberán abrir el archivo nusoap.php y editar en donde se declare la variable $response_timeout, la cual siempre estará definida con un valor de 30 (segundos). Yo intenté modificarla sólo en la definición de las propiedades de la clase, pero no bastó, por lo que terminé modificándola en todas las partes donde fuera llamada o utilizada (habrán sido unas 10 ó más veces en el archivo, dentro y fuera de métodos).

Espero que este tip les sirva, saludos a todos y gracias por leer!

Fuente de la respuesta:
- Foros del Web: Problema al intentar consumir muchos registros de una consulta en Web Services

20/6/13

[tip] Crea tu propio tema en Windows 7

Habiendo instalado el Windows 7 en el PC de mi trabajo, me enteré de aquella bella característica de los temas con fondo de escritorio cambiante. Aunque en la ventana de personalización de pantalla no aparece una opción de "Crear Tema", pues me di a la tarea de averiguar si existía la forma de hacerlo. Tras bastante tiempo sin saber cómo se hacía, hoy encontré la respuesta en Theme File Format y en la Community, ambos sitios de Microsoft.

La respuesta no podía ser más sencilla, y por eso se las dejo comentada acá.

Cuando seleccionamos un Tema en nuestra ventana de personalización, lo que hace Windows es buscar el archivo correspondiente a dicho tema. Ese archivo lleva por extensión "theme". De ese modo, tenemos el archivo "aero.theme" para el tema Aero; el archivo "nature.theme" para el tema Naturaleza; y así sucesivamente. Lo increíble y mejor de todo es que estos archivos "theme" no son sino simples archivos de texto, que guardan información en la clásica sintaxis de los archivos "ini"; o sea, parámetros y sus respectivos valores.

Ahora bien, lo importante es poder primero averiguar dónde Windows almacena estos archivitos, para luego poder editar el nuestro.

Los archivos de los temas que trae por defecto Windows los podemos encontrar en:

C:\Windows\Resources\Themes

Los archivos de los temas guardados por nosotros los encontramos en:

C:\Users\(Tu Usuario)\AppData\Local\Microsoft\Windows\Themes

Deben reemplazar donde yo puse (Tu Usuario) con el ID de usuario de ustedes. 

Es en esta carpetita donde encontrarán los temas guardados por ustedes.

¿Cómo se guarda un tema propio? Es tan sencillo como abrir la ventana de Personalizar (click derecho sobre el Escritorio), y darle a Guardar al tema actualmente en uso (que probablemente diga "Tema No Guardado"). Una vez que el tema está guardado, vamos a nuestra carpeta personal de temas y abrimos el archivo con un editor de texto cualquiera (un editor que permita abrir y guardar archivos de texto sin corromperlos, claro).

Estando dentro del archivo, verán un sinfín de parámetros con sus respectivos valores. No modifiquen nada de eso, pues no interfiere con nuestro objetivo, los fondos cambiantes. Diríjanse directo al final del archivo, y, si esta categoría no existe ya, agréguenla (las categorías son aquellas encerradas en corchetes):

[Slideshow]
Interval=1800000
Shuffle=0
ImagesRootPath=D:\Mis imágenes\MiTema


Ahora bien, cada parámetro debe ser completado como indica:

Interval : medido en milisegundos, indicará a Windows el tiempo que debe transcurrir entre cada cambio de imagen.
Shuffle : admite valor 1 ó 0 (booleano), e indica si queremos que las imágenes se muestren aleatoriamente (1) o en el orden en el que están guardadas en la carpeta (0).
ImagesRootPath : acá deberán señalar la ruta completa a la carpeta donde se encuentran las imágenes a ser mostradas en slide show.

Y pues, claro, que deben primero haber creado esa carpeta (en mi ejemplo, "D:\Mis Imágenes\MiTema"), y haberla llenado con todas las imágenes que quieran formen parte de su slide show. Algo importante a recordar es que seleccionen para esto imágenes que tengan una resolución  igual o superior a la de su pantalla, de modo que no se vean pixeladas a la hora de correr el slide.

Una vez terminen de editar el archivito, guárdenlo, ciérrenlo, y entonces vuelvan a abrir la ventana de Personalizar. Marquen otro tema, para que Windows asuma otra configuración, y entonces vuelvan a marcar sobre el Tema guardado por ustedes. En la vista previa, si todo salió bien, debiera salirles igual que los otros temas, así como con muchas imágenes hacia atrás.

Si quieren tener distintos temas, sería crear nuevos temas, abrir el archivo correspondiente e indicar la carpeta respectiva: imaginen tener 10 carpetas con imágenes de distintas temáticas dentro cada una, creando un tema para cada una, resulta muy genial, no? ^^

Y eso es :)
Espero que les sirva y así puedan disfrutar de un hermoso fondo de escritorio cambiante, y con sus imágenes favoritas.
Saludos y gracias por leer! ^___^

Editado 27/06/2013
No habían pasado unas horas de que lograra hacer esto, que uno de mis compañeros de trabajo me dice "pero si eso se hace por este lado, has reinventado la rueda", aludiendo a la efectiva existencia de la opción que yo buscaba, dentro del mismo panel de Personalizar -____- . Como ya se me pasó la rabia, lo comento acá xD. Pues eso, que estando en dicha ventana, en la parte inferior, hay que hacer click donde dice "Fondo de Escritorio". Al entrar en esta opción viene la parte donde poder seleccionar las imágenes a usar como slideshow. No lo he probado en sí, ya que simplemente entré cuando mi compañero me dijo, y allí estaban las imágenes de la carpeta que yo configuré en el archivo .theme . Lo único que me queda como descargo es decirle nuevamente a los de Microsoft, que si van a darnos la opción de hacer un slideshow, coloquen en el título de la opción "slideshow" (o su equivalente en español), y no "fondo de escritorio", que a todas luces a mí me daba la impresión de "un" fondo, "uno solo", se capta? Esop. Gracias por leer.

11/6/13

[php] Averiguar si una cadena contiene código HTML

Holas a todos.

Estaba buscando una manera rápida de averiguar si un texto era código HTML, y tras una breve indagación googleana, les presento la solución que encontré.

Se usa la función nativa de PHP strip_tags(), que lo que hace es limpiar una cadena de toda etiqueta HTML que encuentre, devolviéndonos dicha cadena "limpia". Ya que al usar esta función sobre una cadena que efectivamente tiene etiquetas dentro, nos retornará una cadena totalmente distinta, lo que hacemos es averiguar el resultado de comparar la cadena original con la procesada.

/**
----------------------------------------
@author Carolina Casanova García
@date 11/06/2013
@return boolean
*/

function is_html($fval)
{
 // si las cadenas son distintas, entonces se trata efectivamente 
 // de una cadena con etiquetas HTML dentro

 return (strip_tags($fval) != $fval);
}
// acá un ejemplo de uso:
$cadena = '<u><b>¡Hola!</b></u>';
print "<p>La cadena original: ".$cadena."</p>"
print "<p>Es HTML? ".(is_html($cadena) ? "Sí": "Nup")."</p>";


Espero que les haya servido ^^ Yo la usé sin recurrir a la función, sino directamente con la comparación para obtener el booleano, por ello puede que este código les dé errores, ya que no lo probé, pero me captan la idea, no? ^^

Hasta pronto y gracias por leer!

14/5/13

[tip] Videos se reproducen muy rápido

Holas a todos. Hoy les traigo un tip para aquellos que han tenido problemas con sus videos en su reproductor en Windows 7. En mi caso particular, la situación era esta:

Tenía un pc con Windows XP, en donde Winamp era mi reproductor de audio-video de cabecera. La razón de usar Winamp principalmente radicaba en (para mí) su super útil opción de acoplarse a uno de los bordes de la ventana (como si fuera otra barra de herramientas más), y encima poder reproducir mis mini videos en formato FLV. Aquí entro en explicar que, como es el pc del trabajo, suelo descargar mis videos favoritos de internet en la más baja resolución, de modo de tener muchos, y de paso que se reproduzcan dentro de mi playlist como otro track más, sin interferir con mi escritorio de trabajo.

El problema surgió cuando me cambiaron el pc, y con él, el sistema operativo. Ahora tengo Windows 7, y al instalar el Winamp, pues nada, que no había caso para poder reproducir los videitos en formato FLV. Sabía que era problema de Winamp porque al reproducir el mismo video en Windows Media no había problemas. No obstante, con más investigación caí en la cuenta que no eran los software el problema, sino los (dichosos) códecs. De hecho, bajé el clásico pack de K-Lite, esperando encontrar el decoder "ffdshow", que es el que solía asumir la lectura de todos mis videos. Como este pack aparentemente no funcionó, intenté instalando otros (FLVSplitter -no es pack, pero igual-, Windows 7 Codecs Pack). Pero ni eso funcionó, así que tuve que seguir indagando.

Para resumir un poco, y sólo incluyendo los pasos más útiles y trascendentales de lo que hice para que esto funcionara, les comento:

¿Cómo hacer para que Winamp reproduzca los videos FLV?
La respuesta más sencilla y repetida iba así:
  • Entrar al menú de Preferencias (click derecho sobre la ventana de Winamp, o bien presionar CTRL+P), siguiendo la ruta "Preferencias » Plug-ins » Entrada". 
  • En la lista de plugins instalados, hacer doble click sobre el decodificador "DirectShow".
  • En la ventanita que se abre, aparece una serie de extensiones de archivos de video; lo típico es "MPG;MPEG;M2V;AVI". Lo que hay que hacer es agregar la extensión FLV a la lista, de forma que quede algo como: "MPG;MPEG;M2V;AVI;FLV". 
O sea, más sencillo dónde. A muchas personas les bastó con esto para que su problema de reproducción se solucionara. No fue mi caso.

Al hacer esto, lo que logré fue que, efectivamente, Winamp reprodujera los videos FLV. El problema era que, si bien los mostraba correctamente, el audio se oía acelerado, y como el video no quería desentonar, se aceleraba también. O sea, era como ver todos los videos en cámara rápida, con voces de ratoncitos.

Entendí que el problema era el Audio cuando, al entrar a ver la información del video (presionando ALT+3), me aparecían los códecs que estaban trabajando en la lectura del archivo:
  • ffdshow Video Decoder
  • Microsoft DTV-DTD Audio Decoder

Ahora bien, al buscar la forma de configurar el códec de audio de Microsoft, no encontré prácticamente nada en internet. Mi ideal era poder leer el audio de los videos con el ffdshow, así como ocurría con el video. Por ello busqué en internet información acerca de cómo configurar este decoder de Windows 7, pero nada.

¿Cómo seleccionar manualmente el códec de Audio para nuestros videos?
Lo único que terminé por entender, de todo lo que leí, era que este códec de Windows 7 era prioritario, y que la única forma de deshabilitarlo era eliminando el archivo DLL respectivo (lo que en la práctica, y siguiendo siempre el esquema de recuperabilidad, era renombrar el archivo, de modo que Windows no lo encontrara). El drama era que además de entrar a modificar un archivo de sistema, había que actualizar el registro, y ya con eso era mucho para mí, especialmente porque no es mi pc personal. Eso sin contar con que, según algunos testimonios, esto igual generaba problemas por otros lados.

Seguí indagando, y al fin se me ocurrió buscar por "cómo deshabilitar" este bendito códec. Y alli por fin encontré lo que necesitaba: alguien que liberó un pequeño programa que hacía lo anterior por nosotros. Este programita se llama Win7DSFilterTweaker y lo pueden encontrar en Codec Guide, específicamente acá.

Con este programita lo que se nos permite es, precisamente, deshabilitar los Microsoft DTV-DTD códecs. En mi caso particular, lo que hice fue deshabilitar el códec de Audio. Ahora, cuando abro la ventana de información del video que reproduzco, me aparece:
  • ffdshow Video Decoder
  • ffdshow Audio Decoder
¡Ahora puedo ver y escuchar mis mini videos en Winamp como siempre! ^____^ *Felicidad*
Pues eso. Espero que les ayude este tip, que yo estuve casi 3 semanas sufriendo de no poder ver mis videos kpoperos, jejeje :P

Gracias por leer y hasta pronto!

PD: El único detallito que me quedó en el tintero con haber logrado esto, es que, lamentablemente al apretar y cambiar tanta opción en las configuraciones de los códecs, algo hice que el audio ahora se escucha muy bajo, jajaja xD Pero es un mal menor al lado de no poder ver mis videos favoritos ^^ jejeje Editado (17/05/2013): Todavía no sé qué fue lo que hice mal antes, pero ahora ya encontré la "solución" al problema, y fue en el control de volumen de Windows, que ahora trae un control maestro y un control especial por cada aplicación abierta: sólo tuve que aumentar el volumen del control asociado a Winamp y listo :)
PD2: Otra de las cosas que hice para poder encontrar la solución deseada, fue instalar otros reproductores. Así instalé el que conozco más, KMPlayer, pero como adolece de la acoplabilidad, tampoco solucionaba mi problema. Al respecto de esto mismo, en mi búsqueda hasta dejé preguntas en foros y sitios de consulta, pero como a muchos con la misma duda, siempre la sugerencia era cambiarse de reproductor, y esto lo encuentro tan mala respuesta como desubicada. Si uno va a un restaurant y pide "arroz con pollo", el mesero no va y le dice a uno "mejor coma puré con carne, que es más rico", sino que trae lo que uno pide. En fin, sólo un pequeño descargo para esos users soberbios de la red...

Referencias:
- How to play YouTube/Google Flash Videos (FLV) in Winamp
- How to Disable Microsoft Windows7 built-in Microsoft DTV-DVD Decoder
- Microsoft DTV-DVD Audio decoder - help
- Preferred Filter Tweaker for Windows 7 and 8
Visité muchas páginas más, pero estas son las que valieron al final para lo conseguido. Muchas gracias a todos ellos! :D

28/3/13

[tsql] Error: La instrucción INSERT EXEC no se puede anidar

Holas a todos.

Mientras programaba un procedimiento almacenado, intenté obtener los datos de otro procedimiento, como lo he venido haciendo desde que descubrí tamaña maravilla de la programación sql.

Pero hoy me topé con este extraño error: La instrucción INSERT EXEC no se puede anidar.

Tras investigar por algunos lados, di con la respuesta: no se puede almacenar en una tabla temporal de procedimiento almacenado, el resultado de otro procedimiento que también esté realizando una inserción de este tipo.

Esto es algo como tener:

CREATE PROCEDURE miProcedimiento
AS

 INSERT INTO #tablita EXEC otroProcedimiento;
 SELECT * FROM #tablita;

END;

CREATE PROCEDURE nuevoProcedimiento
AS

 INSERT INTO #tabla1 EXEC miProcedimiento;

END;

Esto significará que si ejecuto:

EXEC nuevoProcedimiento;

...SQL me arrojará el error antes mencionado.
La solución al problema es no llamar a un procedimiento que esté llamando a otro ya en su interior. En algunos lados leí que transformaban el procedimiento intermedio (en el ejemplo, "miProcedimiento"), en una función que retorne un dato tipo TABLE. Pero esto no lo he probado, así que no sé si cambiaría las cosas.

Bueno, esop. Saludos!

Info:
Guardar el resultado de un procedimiento almacenado en una tabla
Problems with INSERT from stored procedure

13/3/13

[sql] Búsqueda por palabras en una frase

Holas de nuevo.

Esta vez les he traído un tip especial para principiantes ultra novatos en desarrollo de consultas a bases de datos ^^. Se trata del método que uso para poder hacer consultas a una tabla, filtrando los resultados de acuerdo una frase de búsqueda. Usaré sentencias en TSQL y lenguaje PHP para hacer los ejemplos.

Todos sabemos cómo hacer una consulta teniendo una palabra o frase exacta de búsqueda:

SELECT *
FROM tabla
WHERE campo LIKE '%palabra o frase clave%';

Si queremos aplicar la palabra o frase de búsqueda en varias columnas, la sintaxis sería:

SELECT *
FROM tabla
WHERE campo1 LIKE '%palabra o frase clave%'
OR campo2 LIKE '%palabra o frase clave%';

...añadiendo todas las columnas deseadas a continuación, siempre separando con el OR. Si hubiesen más condiciones de filtro, sería encapsular todos los elementos separados por OR, dentro paréntesis redondo:

SELECT *
FROM tabla
WHERE
campoX = 'valor'
AND
(campo1 LIKE '%palabra o frase clave%'
OR campo2 LIKE '%palabra o frase clave%');

También podríamos realizar la búsqueda en varias columnas concatenadas, si así nos conviene:

SELECT *
FROM tabla
WHERE campo1 + ' ' + campo2 LIKE '%palabra o frase clave%';

Ahora bien, todo esto va perfecto, pero... ¿qué pasa si queremos que la búsqueda usando la "frase clave" se realice buscando cada palabra de la frase, dentro del texto de la columna individual o las columnas concatenadas? Me explico con un ejemplo: la clásica búsqueda de personas mediante su nombre y apellidos.

Tenemos la tabla persona, teniendo varias columnas, y entre ellas: nombre1, nombre2, apellido1 y apellido2.
Esta tabla contiene los siguientes registros:

------- - ------- - --------- - ---------
nombre1 - nombre2 - apellido1 - apellido2
------- - ------- - --------- - ---------
Juan - Antonio - Pérez - Cortés
María - Inés - González - Rojas
Luis - Alberto - Rojas - Morales
Karina - Pamela - Pérez - González
Diego - Juan - Contreras - Pérez
------- - ------- - --------- - ---------

Si en mi buscador yo quisiera buscar a "Juan Pérez", podría hacerlo de cualquiera de las siguientes maneras:

A) Buscador detallado.
Colocar en el formulario de búsqueda una caja de texto para cada uno de los campos a consultar. O sea, una caja para hacer la búsqueda en la columna nombre1; otra, para la columna nombre2; y así etc.

Búsqueda
Primer Nombre :
Segundo Nombre :
Primer Apellido :
Segundo Apellido :

Luego, cuando por programación recupere el valor de las cuatro cajas de texto, podría armar la consulta por programación así:

En lenguaje PHP:

$consulta = "SELECT * FROM persona WHERE ";
$consulta .= (!empty($_POST["nombre1"]) ? " nombre1 LIKE '%".$_POST["nombre1"]."%'": "");
$consulta .= (!empty($_POST["nombre2"]) ? (!empty($_POST["nombre1"]) ? " AND ": "")." nombre2 LIKE '%".$_POST["nombre2"]."%'": "");

Y así continuar con los apellidos, etc...

Ahora bien, ¿qué ocurre si el usuario no sabe si "Juan" es el primer o segundo nombre, y/o lo mismo con el apellido "Pérez"? Más aún, ¿qué ocurrirá si por extraña razón hay una persona a la que han bautizado "Pérez", o que uno de sus apellidos es "Juan"? Etc...

En ese sentido, esta solución, aunque efectiva, resulta aparatosa y engorrosa, tanto para el usuario como el programador.

B) Buscador de Frase Exacta.
Un formulario de búsqueda con una caja de texto única.

Ingrese Palabras : 
Por programación, recibiremos el valor de la caja de texto y podríamos crear una gran consulta donde abarcar la mayor cantidad de alternativas de uso de la frase exacta de búsqueda:

En lenguaje PHP:

$consulta = "SELECT * FROM persona WHERE ";
$consulta .= " nombre1 LIKE '%".$_POST["frase"]."%'";
$consulta .= " OR nombre2 LIKE '%".$_POST["frase"]."%'";
$consulta .= " OR apellido1 LIKE '%".$_POST["frase"]."%'";
$consulta .= " OR apellido2 LIKE '%".$_POST["frase"]."%'";


Incluso podemos aplicar la concatenación de columnas:

$consulta .= " OR nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%".$_POST["frase"]."%'";

El resultado, no obstante, no será el esperado, sea cual sea la condición usada. Si las analizamos una por una, tenemos que:

"Juan Pérez" en Nombre1 => Falso
"Juan Pérez" en Nombre2 => Falso
"Juan Pérez" en Apellido1 => Falso
"Juan Pérez" en Apellido2 => Falso
"Juan Pérez" en Nombre1 + Nombre2 + Apellido1 + Apellido2 => Falso

Para obtener los resultados esperados, sería necesario programar desarme de la frase y generar todas las combinaciones de columnas x palabras de búsqueda posibles. Por eso, es mejor pasar directo a la opción C).

C) Buscador de frase, por palabra (ordenado)
Teniendo el mismo formulario de búsqueda que en B), sólo tenemos que cambiar la consulta SQL armada por programación, de modo que la búsqueda sea realizada en todas las columnas, pero tomando todas las palabras de la frase por separado, aunque respetando el orden en el que fueron escritas.

En lenguaje PHP:

$consulta = "SELECT * FROM persona WHERE ";
$consulta .= " nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%".str_replace(" ", "%", $_POST["frase"])."%'";

En SQL, la consulta generada tendría la siguiente apariencia:

SELECT *
FROM persona
WHERE nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%Juan%Perez%';

Ya que hemos colocado esos comodines entre las palabras de la frase, SQL buscará dentro de la concatenación de columnas, todos los registros que contengan las palabras "Juan" y "Perez" en su interior. El resultado de esta consulta, nos retornará 2 registros:

------- - ------- - --------- - ---------
nombre1 - nombre2 - apellido1 - apellido2
------- - ------- - --------- - ---------
Juan - Antonio - Pérez - Cortés
Diego - Juan - Contreras - Pérez
------- - ------- - --------- - ---------

...ya que "Juan" fue encontrado en el nombre1 del primer registro y en el nombre2 del quinto registro; y "Perez" fue encontrado en el apellido1 del primer registro y en el apellido2 del quinto registro.

Finalmente, si analizamos esta consulta SQL armada por programación, podremos notar que incluso podríamos haber prescindido de dicha programación: aprovechando las funcionalidades del lenguaje de SQL, podemos armar la misma consulta de la forma:

SELECT *
FROM persona
WHERE nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%' + REPLACE('Juan Perez', ' ', '%') + '%';


Y si trabajamos netamente en SQL, usando variables TSQL, también podría quedar:

DECLARE @frase VARCHAR(255);
SET @frase = 'Juan Perez';
SELECT *
FROM persona
WHERE nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%' + REPLACE(@frase, ' ', '%') + '%';


¡Esto nos ayudará mucho si trabajamos las búsquedas dentro de procedimientos y/o funciones almacenadas!

Observación:
Es importante recalcar que la forma de separar la frase, explicada con anterioridad, asume que queremos buscar las palabras en el mismo orden en que fueron escritas las palabras de la frase la primera vez.

Si por alguna razón quisiéramos que no tomara en cuenta el orden, y que buscara indistintamente por todas las palabras, tendríamos que separar previamente por programación cada palabra de la frase y repetir la condición de búsqueda por cada palabra encontrada.

Esto lo podemos hacer por programación:

En lenguaje PHP:

$consulta = "SELECT * FROM persona WHERE ";
$palabras = explode(" ", $_POST["frase"]);
$c = 0;
foreach ($palabras as $palabra)
{
$c++;
$consulta .= " nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%".$palabra."%' ";
$consulta .= (count($palabras) == $c ? "": " OR ");
}

Lo que daría por resultado:

SELECT *
FROM persona
WHERE
nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%Juan%'
OR nombre1 + ' ' + nombre2 + ' ' + apellido1 + ' ' + apellido2 LIKE '%Perez%';

Para hacerlo por SQL, podrían valerse de la forma que explico en mi tip Pasar array a parámetro de procedimiento almacenado, aunque sólo es efectivo cuando las columnas contienen un solo valor (una palabra sola o una frase que sea no-separable, como nombres compuestos, etc.).

Si existe una forma correcta de hacerlo en SQL, sería bueno leerlo en los comentarios ^_^ Al menos por ahora no conozco una función tipo split o explode en SQL.

Y eso sería. Resultó largo de explicar, pero espero que les sirva de alguito ^^.
Saludos y gracias por leer!

11/3/13

[linux] Cambio de hora en servidor CentOS (Chile)

No soy una experta en Linux, pero me ha tocado trabajar periódicamente sobre este sistema operativo, especialmente en modo consola, ya que la mitad de los sitios web de mis trabajo se encuentran alojados en esta plataforma.

Uno de los problemas con lo que me he topado (y seguro muchos de ustedes también), es el tema del Cambio de Hora Chileno, que en los últimos años ha venido variando como loco, por lo que hemos tenido que realizar el ajuste de hora prácticamente de forma manual en clientes y servidores.

Hoy, al llegar al trabajo, precisamente me topé con la sorpresa de que uno de los servidores tenía la hora cambiada. Claro: la configuración del servidor tenía puesto cambio de hora el Sábado 9 de Marzo (y el siguiente cambio en Octubre), ya que esa es la regla de cambio de hora que Chile tuvo por muchos años, por lo que me tuve que poner a la tarea de corregir el fallo.

En años pasados había tenido que solucionar lo mismo, por lo que busqué entre mis favoritos y reencontré este muy buen tutorial la respecto: Guía: Modificar tzdata, cambio/no cambio de hora Chile.

Ahora bien, el tutorial es del 2011, pero aplica bien para todos los años. Yo me puse a hacer todos los pasos, pero en el camino descubrí que, ya que había hecho el tutorial en años anteriores, me pude saltar varios pasos.

Por ejemplo, no tenía que editar el archivo de las reglas (Rules), ya que al bajar la última versión actualizada del sitio IAIA - Time Zone Database, era sólo cosa de descargar el archivo:

# wget http://www.iana.org/time-zones/repository/releases/tzdata2013b.tar.gz

...descomprimirlo:

# tar zxvf tzdata2013b.tar.gz

...y actualizar el archivo local con el contenido en el zip:

# zic southamerica
# zic backward


Al ejecutar de nuevo la instrucción que nos muestra las reglas para este año:

# zdump -v /etc/localtime | grep 2013

...las reglas se habrán actualizado correctamente de forma automática:

/etc/localtime  Sun Apr 28 02:59:59 2013 UTC = Sat Apr 27 23:59:59 2013 CLST isdst=1 gmtoff=-10800
/etc/localtime  Sun Apr 28 03:00:00 2013 UTC = Sat Apr 27 23:00:00 2013 CLT isdst=0 gmtoff=-14400
/etc/localtime  Sun Sep  8 03:59:59 2013 UTC = Sat Sep  7 23:59:59 2013 CLT isdst=0 gmtoff=-14400
/etc/localtime  Sun Sep  8 04:00:00 2013 UTC = Sun Sep  8 01:00:00 2013 CLST isdst=1 gmtoff=-10800


Y para asegurarse de que la fecha actual se encuentre correcta, pueden ejecutar date:

# date
Mon Mar 11 09:40:20 CLST 2013


Si les tocara el caso de que aun habiendo hecho esto, las reglas y la fecha no se han actualizado, deberán pisar manualmente la configuración de localtime con la actualizada de Chile/Continental:

# cp /usr/share/zoneinfo/Chile/Continental /etc/localtime

Si ejecutan de nuevo el zdump, les debieran aparecer las reglas actualizadas; lo mismo si ejecutan el date.

Dentro del tutorial antes mencionado, algunos usuarios aludían al sencillo uso de:

# yum update tzdata

El problema de usar esta actualización ultra simple es que dependemos de los servidores de repositorios que tengamos configurados en nuestro servidor Linux. Esto quiere decir que si en la lista de repositorios que tengamos configurada no está el que contiene la última versión del tzdata, de nada nos servirá tratar de usar el yum update. Esto también quiere decir que si contamos con la URL del repositorio indicado, podemos agregarlo a nuestra lista y así poder usar el yum update a gusto.

Finalmente, y no necesariamente referido a Linux, en mi caso particular tengo un servidor Mysql instalado en la misma máquina, y al hacer un:

SELECT NOW();

...tras haber realizado la corrección de la hora en Linux, Mysql me seguía mostrando la hora "incorrecta".

Para solucionar eso, deberán reiniciar el server Mysql:

# service mysqld restart

Esto último deben hacerlo tomando todas las precauciones pertinentes a vuestro caso particular.

Y eso sería el tip. No olviden leer la guía del link de CHW.
Saludos y gracias por leer!

7/1/13

[js] Autoajustar posición de capas con Javascript

Holas a todos. Hoy he querido compartir con ustedes una función Javascript que he logrado crear con el fin de realizar la tarea de ajustar la posición de un grupo de capas, de modo que queden todas pegadas, con un valor de margen determinado entre ellas.

Esta característica la quería aplicar en mis sitios web desde hacía tiempo, y lo había intentado aplicando la propiedad float:left al CSS de las capas, pero no era suficiente. Si bien en los primeros elementos se veía bastante bien la cosa, luego, cuando debían bajar, pues venía el desorden. En otro intento por acercarme al efecto deseado sin usar programación extra, le asigné a cada capa el mismo alto, de modo que al menos se vieran ordenadas hacia abajo. Pero esto no me convencía, y me di a la tarea de ver la manera de hacerlo con Javascript.

Y he aquí el resultado :D

Antes:
 Después:


Pueden ver el código fuente para lograr este efecto en los siguientes enlaces: pero antes, por favor, ¡no borren los créditos! No espero que me mencionen en sus páginas como la autora de la función (si lo hacen, muchas gracias de antemano), pero al menos respeten mi trabajo manteniendo mis comentarios y créditos dentro del archivo Javascript! Gracias y ojalá les sirva!


Página de ejemplo: qAjustar.html
URL del archivo Javascript: qAjustar.js

Saludos!

PD: A la fecha, he aplicado esta funcionalidad en mi página El Baúl, por si la quieren ver en ejecución ^^.