miércoles, 20 de noviembre de 2013

Guardar correctamente la copia de elemento enviado con Outlook gestionando varios buzones

Contexto:

Gestionas varios buzones de Exchange en un mismo perfil de Outlook, esto es, tienes un buzón principal y varios secundarios.

Problema:

Cuando envías mensajes utilizando como dirección de origen la de un buzón secundario los mensajes se almacenan en elementos enviados del buzón principal.

¿Cómo conseguir que se almacenen en los  elementos enviados del buzón del remitente especificado?

Solución

La configuración se realiza mediante Outlook Web Access pero es una configuración de servidor que resuelve el problema también en Outlook.

  1. Acceder con Outlook Web Access al buzón secundario
  2. Ir a "Ver todas las opciones" mediante la lista desplegable "Opciones" (arriba a la derecha).
  3. En Configuración/Correos enviados cambiar las listas desplegables a "Desde el buzón".
  4. Guardar y listo.


miércoles, 13 de noviembre de 2013

Imagen del sistema en Windows 8.1

Pregunta:
¿Cómo puedo crear una imagen del sistema en Windows 8.1?
Antes, en Windows 8, buscaba en configuración "copia de seguridad de Windows 7", pero ahora no obtengo resultados.
Solución:
Ves a Panel de control\Sistema y seguridad\Historial de archivos.
En la esquina inferior izquierda tienes un enlace Copia de seguridad de imagen del sistema

viernes, 25 de octubre de 2013

Perdida de conexión a red actualizando a Windows 8.1

Síntomas:

Cuando actualizas Windows 8 hasta Windows 8.1.
La conexión de red se pierde al cabo de unos minutos de arrancar el ordenador.
No puedes navegar por Internet, no puedes acceder a carpetas de red o al servidor de correo.
Si reinicias recuperas la conexión temporalmente.

Solución:

En mi caso y otros varios encontrados en Google, el problema esta el controlador del adaptador de red.

Para resolverlo:
Accede a administrador de dispositivos (tecla windows + x), ves a adaptadores de red, veras tu adaptador
Con el botón derecho dale a actualizar el controlador.
Elige la segunda opción: "Buscar Software de controlador en el equipo"
Luego nuevamente elige la segunda opción: "Elegir en una lista de controladores en el equipo"
Si te aparecen varios controladores, prueba a ver si alguno funciona.
Si solo aparece uno tendrás que descargar el controlador de la web de fabricante del equipo o del controlador.
Si aun no has actualizado valdrá la pena que descargues ese controlador previamente.

En mi caso  Broadcom NetLink (TM) Gibabit Ethernet aparecieron cuatro controladores compatibles (de hecho todos parecen para el mismo adaptador).
Yo opté por el tercero con "(TM)" al final en lugar de en medio de otras palabras y se resolvieron los problemas.

.

viernes, 13 de septiembre de 2013

Problema con los bordes de un mapa generado por D3.geo en Internet Explorer 10

Enunciado

En Internet Explorer 10, los paths de un mapa generado con D3js se extiende mas allá de los bordes definidos en los elementos hight y width del elemento SVG contenedor.

Esto no ocurre en Chrome o Firefox.

Solución

Incluye el área SVG dentro de un elemento DIV dimensionado como el SVG y aplica al DIV la propiedad CSS overflow: hidden


Internet Explorer 10 y d3.tsv

Enunciado:

Al leer un archivo tsv mediante d3js no se distribuyen los campos como era de esperar.
El problema aparece en IE10 pero no en Chrome o Firefox.

Solución:

El archivo tsv estaba generado desde Access y  por defecto codificado en Ansi.
La solución es generar en UTF8

Comentario:

Lo sorprendente para mí fue que IE10 causara el problema y no los otros navegadores.

Publicar umbraco en IIS7.5 (permisos)

Sintomas:

En la versión de producción de un sitio Umbraco instalado manualmente o publicado desde Visual Studio se producen errores del tipo
La sección de configuración 'System.web.extensions' no se puede leer porque falta una declaración de sección.

Contexto:

 IIS 7.5 y umbraco 6.1.5

Causa:

La causa del error son los permisos de acceso a las carpetas y archivos.

Enunciado del problema

¿Qué permisos se deben dar en la carpeta windows del sitio Web Umbraco? Y mas concretamente a quién?

Solución

¿A quién?

Los permisos se deben dar al usuario del pool de aplicaciones usado por Umbraco. Particularmente yo uso un pool que se llama umbraco y que se ejecuta en el framework 4.0 con canalización integrada.
Para hacer referencia al usuario del Pools se debe utilizar: "IIS APPPOOL\umbraco"
Si lo haces por GUI recuerda que este usuario es la máquina no el dominio.

¿A qué carpetas?

En la documentación actual esta configuración está un poco indeterminado:
In order for Umbraco to have enough permissions to write files to disk, you should give the IIS_IUSRS user modify permissions in the folder in which you've unzipped your Umbraco files.
While giving broad permissions is usually fine for development environments, you may want to restrict permissions further on a public facing server. In that case, at least the App_Data folder needs modify permissons for either the IIS_IUSRS group or the specific user that is linked to the application pool that you're using (assumes that you will not do live editing or installing on the server).
Mas o menos sugieren que si el sitio no va evolucionar (trabajar con CSS, vistas,...etc)  debería bastar con darle permisos de escritura al app_data.
Una solución alternativa es dar permisos en todos los archivos y carpetas, pero para un sitio en producción....

Solución

Yo utilizo este batch que derivé de una articulo de la wiki anterior y parece funcionar:
e:
cd E:\Inetpub\Aplicaciones\PUBLICO.NET\EUP
icacls app_code /grant "IIS APPPOOL\umbraco":(OI)(CI)RX
icacls app_browsers /grant "IIS APPPOOL\umbraco":(OI)(CI)RX
icacls app_data /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls bin /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls config /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls css /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls data /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls macroscripts /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls masterpages /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls media /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls python /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls scripts /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls umbraco /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls usercontrols /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls views /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls xslt /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls web.config /grant "IIS APPPOOL\umbraco":(OI)(CI)M
icacls web.config /grant "Servicio de Red":M
icacls robots.txt /grant "IIS APPPOOL\umbraco":M
Pause
Donde E:\Inetpub\Aplicaciones\PUBLICO.NET\EUP es el path a la carpeta que aloja el sitio umbraco duplicado

Notas:


  • Hay archivos como robots.txt que no existen y daran error, pero no importa.
  • Es posible que falten carpetas utilizadas por extensiones.


jueves, 4 de julio de 2013

Error al hacer click en un diagrama EF, el valor no puede ser nulo, nombre del parametro=Font

Enunciado:

En el Diseñador de Diagramas de Entity Framework de Visual Studio 2012.
Al hacer clic sobre una relación salta un error:
El valor no puede ser nulo, nombre del parámetro: Font.
Cuando aceptas el mensaje el diagrama es reemplazado por una aspa roja.
Si cierra y vuelves a abrir el diagrama se ve correcto pero el fallo sigue produciéndose.

Solución:

La solución que circula por la red es:
En panel de control/desinstalar programas busca y repara Entity Framework Designer for visual studio 2012.
Yo tenia dos instancias ENU y ESN y reparé las dos para curarme en salud.
Lo hice con Visual Studio 2012 cerrado, pero al volverlo a abrir seguía fallando.
Al final reinicié la maquina y se había arreglado.


miércoles, 19 de junio de 2013

Error 3034 en un modelo Entity Framework

Enunciado:

Al compilar un proyecto con un modelo Entity Framework creado en el EF Designer desde una base de datos existente te encuentras con este error:
Error 3034: Hay un problema en los fragmentos de asignación con inicio en las líneas 275, 284: Dos entidades con claves diferentes están asignadas a la misma fila. Asegúrese de que los dos fragmentos de la asignación no asignan dos grupos de entidades con claves solapadas al mismo grupo de filas.

Solución:

Elimina todas las entidades y vuelve a añadirlas desde la base de datos.

Comentario:

Después de mucho googlear volví a encontrarme con el chiste del ingeniero, el economista y el informático a los que se les estropea el coche en medio de una tormenta.........

sábado, 15 de junio de 2013

Insertar en multiples webs el aviso legal del uso de cookies

Enunciado

Alojas en uno mas servidores sitios web que utilizan Google Analytics (GA) para el seguimiento de visitas.
Dado que GA utiliza cookies para el seguimiento del usuario, estas obligado por el artículo 4 del Real Decreto Ley 13/2012 a visualizar una advertencia del uso estas cookies y permitir su eliminación.
Pero:
  • Son varios sitios web.
  • Algunos no los has desarrollado tú, ni llevas su mantenimiento.
  • Utilizan distintas técnicas de servidor para llamar al código GA.
¿Cómo visualizas el panel con mínimo esfuerzo y perjuicio de los sitios?
Contexto
IIS 7.5 c/ URL-Rewrite module 2.0 instalado.

Prefacio sobre la ley

Antes de presentar mi solución quiero comentar los aspectos legales de este panel. No soy abogado y puedo equivocarme muy facilmente, si tienes dudas asegúrate con expertos.

Sobre la obligatoriedad de visualizar el panel citado existen diversas opiniones,incluso mucha gente opina que no es obligatorio. Al fin y al cabo estas cookies no almacenan información personal.

Pero si consultas la Guia sobre el uso de las cookies que ha publicado la Agencia Española de Protección de Datos en la página 8, en el tipo de Cookie de Análisis (que son la de Google) leeras:

Cookies de análisis: Son aquéllas que permiten al responsable de las mismas, el seguimiento y análisis del comportamiento de los usuarios de los sitios web a los que están vinculadas. La información recogida mediante este tipo de cookies se utiliza en la medición de la actividad de los sitios web, aplicación o plataforma y para la elaboración de perfiles de navegación de los usuarios de dichos sitios, aplicaciones y plataformas, con el fin de introducir mejoras en función del análisis de los datos de uso que hacen los usuarios del servicio.
Respecto al tratamiento de datos recabados a través de las cookies de análisis, el grupo de trabajo del articulo 29 ha manifestado que, a pesar de que no están exentas del deber de obtener un consentimiento informado para su uso, es poco probable que representen un riesgo para la privacidad de los usuarios siempre que se trate de cookies de primera parte, que traten datos agregados con una finalidad estrictamente estadística, que se facilite información sobre sus uso y se incluya la posibilidad de que los usuarios manifiesten su negativa sobre su utilización

Yo interpreto de este texto que es obligatorio un panel que informe del uso de cookies y permita desactivarlas. Otra cosa es que el número de sitios que violen la legalidad sea tan grande y el delito tan pequeño que la mayoría se libren de la sanción.

Otro tema a discutir es el contenido, permanencia y flujo del panel.

En mi caso visualizo el panel al principio de cada sesión, informo y doy la oportunidad de desactivar las cookies. Si pasan a otra página el panel ya no aparece. En cualquier caso el panel les informa de que pueden volver a activar o desactivar las cookies accediendo a la política de privacidad del sitio.

Dos sitios para leer sobre este tema son: Analítica Web o LetsLaw

Solución técnica

Resumiendo lo que vamos a hacer:
La primera vez que un usuario acceda a alguna página de alguno de nuestros sitios web aparecerá en la parte superior un panel informativo que:
  • Informará del uso de cookies analíticas que respentan la privacidad.
  • Permitirá deshabilitar estas cookies.
Por ejemplo:
Uso de cookies: Según la legislación española tiene la posibilidad de desactivar las cookies analíticas. Usamos estas cookies para mejorar nuestros servicios, respetando su privacidad y sin almacenar datos personales. Para recuperar este panel acceda a la política de privacidad del sitio.

Este panel se visualizará solo al visualizar la primera página. El efecto de deshabilitar las cookies será borrarlas  inmediatamente y luego cada vez que se cargue una página del sitio. El borrado será selectivo, respetando las cookies técnicas.

Lo importante de nuestra solución es que no modificaremos ninguna página directamente,  usaremos el modulo url-Rewrite de IIS7.5  para  inyectar en la página código JavaScript almacenado en un punto común.

Es una solución similar pero independiente a la que mostré en un post anterior Insertar código de seguimiento Google Analytics sin editar todas las páginas, ambas soluciones son compatibles pero no se requieren.

Veamos primero la regla de salida (outbound-rule) del modulo url-rewrite tal como se almacena en el Web.config de cada sitio:

   
       
            
            
      
    
    
        
             
             
        
        

La regla busca la etiqueta </head> y le antepone el siguiente código:
<link href="http://www.miHost.es/LSSI/CookiesPolicy.css" rel="stylesheet" type="text/css">
<script src="http://code.jquery.com/jquery-1.10.1.min.js" type="text/javascript">
<script src="http://www.miHost.es/LSSI/jquery.cookies.2.2.0.min.js" type="text/javascript">
<script src="http://www.miHost.es/LSSI/CookiesPolicy.js" type="text/javascript">

Dado que la etiqueta </head> aparece frecuentemente en código java generado por ASP.net, la regla utiliza una pre-condición (a continuación de la definición) para que se aplique solo al contenido HTML. Esto se consigue con dos requisitos:
  • Exigiendo que el REQUEST-CONTENT-TYPE sea text/html
  • Evitando cargar archivos en cuyo nombre (REQUEST-FILE-NAME) aparezca la extensión .ashx
Según tu entorno es posible que tengas que añadir mas pre-condiciones, vigila la consola de java por si salen errores.

La página reescrita cargará los siguientes archivos externos:
  • Un hoja de estilo que se aplicará al panel.
  • La librería jQuery
  • La librería de gestión de cookies disponible aquí.
  • Una librería en javaScript que constituye el núcleo de la solución que os presento mas abajo.
Antes de continuar observa que:
  • La libreria jQuery se carga del repositorio CDN
  • El CSS y las otras dos librerías se cargan de un sitio central  http://www.miHost.es/LSSI que unifica el mantenimiento.
Ese sitio central debe ser tuyo y deberás editar esta parte de la regla para señala al sitio donde almacenas las librerías.

Vamos a ver la librería nuclear:  "cookiesPolicy.js"
$(document).ready(function () {
    if ($.cookies.test() ) { /*si el navegaro no hacepta cookies para que molestarse*/
        var flagCookies = $.cookies.get("banderaCookies");
        if (flagCookies == null) {  /*hay que mostrar el panel */
            $.cookies.set("banderaCookies", "SI");
            genAvisoCookies();
        } else { /*Borramos las cookies que se han creado hasta el momento */
            if ($.cookies.get("banderaCookies") == "NO") {
                borraCookies();
            }
        }
    }; 
});
function borraCookies() {
    /*Borra las cookies de Google y una mia*/
    var dominio = '.' + /\w*\.\w*$/.exec(window.location.host)[0];
    $.cookies.del("__utma", { path: '/', domain: dominio });
    $.cookies.del("__utmb", { path: '/', domain: dominio });
    $.cookies.del("__utmc", { path: '/', domain: dominio });
    $.cookies.del("__utmz", { path: '/', domain: dominio });
    $.cookies.del("__utmv", { path: '/', domain: dominio });
    $.cookies.del("idCart", { path: '/', domain: null });
}
function genAvisoCookies() 
/*Añade un div centrado justo despues de la etiqueta body*/
{
    $("body").prepend("
" + textoCookies() + "
"); $("#desactivarCookies").click(function (e) { $.cookies.set("banderaCookies", "NO"); borraCookies(); window.location.reload(false); }) } function textoCookies() /*implantación de bi-idioma para el panel */ /*Se considera tanto el idioma del navegador como la posibilidad de un parametro lang en el query string*/ { var lang = navigator.language || navigator.userLanguage var param = QueryString("lang"); var texto if ((param=="en-GB") || ((param==null) && !(/es/.test(lang)))) { texto = "Cookies usage:"; texto += " According to Spanish law you have the option of"; texto += " disable analytical cookies." texto += " We use these cookies to improve our services, respecting your privacy and without storing personal data."; texto += " Access the privacy policy to recover this panel."; } else { texto = "Uso de cookies: "; texto += "Según la legislación española tiene la posibilidad de"; texto += " desactivar las cookies analíticas."; texto += " Usamos estas cookies para mejorar nuestros servicios, respetando su privacidad y sin almacenar datos personales."; texto += " Para recuperar este panel acceda a la política de privacidad del sitio."; } return texto } function QueryString(sParam) { /*Funcion de utilidad para extraer el query string*/ var sPageURL = window.location.search.substring(1); var sURLVariables = sPageURL.split("&"); for (var i = 0; i < sURLVariables.length; i++) { var sParameterName = sURLVariables[i].split("="); if (sParameterName[0] == sParam) { return sParameterName[1]; }; } } /*Las funciones siguientes se utilizan en enlaces del documento de politica de privacidad Para volver a activar las cookies o desactivarlas independientemente del panel*/ function activaCookies() { $.cookies.set("banderaCookies", "SI"); if (!$('#centradoAC').length) { genAvisoCookies(); } goToByScroll(); } function desactivarCookies() { $.cookies.set("banderaCookies", "NO"); borraCookies(); if (!$('#centradoAC').length) { genAvisoCookies(); } goToByScroll(); } function goToByScroll() { /*efecto de animación para ir a la parte superior donde se visualiza el panel*/ $('html,body').animate({ scrollTop: $('#centradoAC').offset().top }, 'slow'); }
Antes de explicar esto te advierto una cosa, no soy un experto en javaScript ni jQuery, consigo que mi código funcione pero puede optimizarse, ... y explicarse mejor. Comencemos:

Vemos que al principio...
$(document).ready(function () {
    if ($.cookies.test() ) { /*si el navegaro no hacepta cookies para que molestarse*/
        var flagCookies = $.cookies.get("banderaCookies");
        if (flagCookies == null) {  /*hay que mostrar el panel */
            $.cookies.set("banderaCookies", "SI");
            genAvisoCookies();
        } else { /*Borramos las cookies que se han creado hasta el momento */
            if ($.cookies.get("banderaCookies") == "NO") {
                borraCookies();
            }
        }
    }; 
});
... estamos enlazando el evento documento cargado (jquery document ready) a una función que según convenga puede hacer tres cosas:
  • Nada (si el navegador no soporta cookies, ¿para que molestarse?)
  • Visualizar el panel requerido por la ley (si es la primera vez en esta sesión que accede al sitio)
  • Borra las cookies (si durante esta sesión el usuario ha dicho que no quiere cookies de análisis)
Usamos una cookie de sesion "banderaCookies" que puede valer null, 'SI', 'NO'

La siguiente parte del código...
function borraCookies() {
    /*Borra las cookies de Google y una mia*/
    var dominio = '.' + /\w*\.\w*$/.exec(window.location.host)[0];
    $.cookies.del("__utma", { path: '/', domain: dominio });
    $.cookies.del("__utmb", { path: '/', domain: dominio });
    $.cookies.del("__utmc", { path: '/', domain: dominio });
    $.cookies.del("__utmz", { path: '/', domain: dominio });
    $.cookies.del("__utmv", { path: '/', domain: dominio });
    $.cookies.del("idCart", { path: '/', domain: null });
}
...corresponde al borrado de las cookies. En este código borramos las cookies de Google y una propia insertada desde código.
Si tus desarrolladores usan otras cookies deberas incluirlas aquí (cuidado con el dominio y el path, puede darte problemas).

A continuación tenemos la función se ocupa de insertar el panel:
function genAvisoCookies() 
/*Añade un div centrado justo despues de la etiqueta body*/
{
    $("body").prepend("
" + textoCookies() + "
"); $("#desactivarCookies").click(function (e) { $.cookies.set("banderaCookies", "NO"); borraCookies(); window.location.reload(false); }) }
Vemos que tiene dos partes:
  1. Insertar unos marcos (<div>) con el texto del panel
  2. Enlazar una función a un elemento #desactivarCookies que veremos mas adelante
Para el centrado y maquetado del texto se hace referencia a estilos CSS definidas en el archivo: CookiesPolicy.css.
En mi caso bastaron estos:
#centradoAC {width:100%;text-align:center;}
#marcoAC
{
    background-color: white;
    text-align: left;
    width: 920px;
    margin: 0 auto;
    padding-bottom: 10px;
    font-family: Arial;
    color: #999999;
    font-size: 13px;
    font-weight: lighter;
}
#marcoAC strong {font-weight: bold;}
#marcoAC a:link
{
    color: #999999;
    text-decoration: underline;
    font-weight:normal;
}
#marcoAC a:visited
{
    color: #999999;
    text-decoration: underline;
    font-weight:normal;
}
#marcoAC a:link
{
    text-decoration: underline;
    font-weight:normal;
}
#marcoAC a:visited
{
    color: #999999;
    text-decoration: underline;
    font-weight:normal;
}
#marcoAC a:hover
{
    color: #999999;
    text-decoration: underline;
    font-weight:bold;
}
(Nuevamente, no soy un experto en nada, tampoco en CSS)
Según la variedad y modernidad de tus diseñadores webs es posible que tengas que alterar mas o menos estas clases. ¡Suerte!.
En mi caso basta que el panel se vea así:
Con un ancho de 900px que se adapta bien a diseños no adaptativos (valga la redundancia).
Siguiendo con el código javaScript, tenemos la generación del texto dentro del panel en dos idiomas:

function textoCookies()
/*implantación de bi-idioma para el panel */
/*Se considera tanto el idioma del navegador como la posibilidad de un parametro lang en el query string*/
{
var lang = navigator.language || navigator.userLanguage
var param = QueryString("lang");
var texto
if ((param=="en-GB") || ((param==null) && !(/es/.test(lang)))) {
    texto = "Cookies usage:";
    texto += " According to Spanish law you have the option of";
    texto += " disable analytical cookies."
    texto += " We use these cookies to improve our services, respecting your privacy and without storing personal data.";
    texto += " Access the privacy policy to recover this panel.";
 
}
else {
    texto = "Uso de cookies: ";
    texto += "Según la legislación española tiene la posibilidad de";
    texto += " desactivar las cookies analíticas.";
    texto += " Usamos estas cookies para mejorar nuestros servicios, respetando su privacidad y sin almacenar datos personales.";
    texto += " Para recuperar este panel acceda a la política de privacidad del sitio.";
}
    return texto
}

function QueryString(sParam) {
/*Funcion de utilidad para extraer el query string*/
    var sPageURL = window.location.search.substring(1);
    var sURLVariables = sPageURL.split("&");
    for (var i = 0; i < sURLVariables.length; i++) 
    {
        var sParameterName = sURLVariables[i].split("=");
        if (sParameterName[0] == sParam) 
        {
            return sParameterName[1];
        };
    }
}
Aquí puedes tener variantes (la mas sencilla visualizarlo siempre en español). En mi caso el idioma en la mayor parte de webs viene especificado por un parámetro "lang" del query string. Por si este parámetro está ausente incluyo también el idioma del navegador.

Hasta aquí la visualización inicial del panel.  Pero tambien es conveniente que la política de privacidad del sitio incluya a su vez opciones para activar o desactivar las cookies de análisis.
Por ejemplo:
Si lo desea puede:


  1. Desactivar las cookies de análisis.
  2. Activar las cookies de análisis.

Los editores wysiwig de los gestores de contenido permiten insertar normalmente estos enlaces con referencia a las funciones javaScript siguientes.
/*Las funciones siguientes se utilizan en enlaces del documento de politica de privacidad
Para volver a activar las cookies o desactivarlas independientemente del panel*/
function activaCookies() {
    $.cookies.set("banderaCookies", "SI");
    if (!$('#centradoAC').length) {
        genAvisoCookies();
    }
    goToByScroll();
}

function desactivarCookies() {
    $.cookies.set("banderaCookies", "NO");
    borraCookies();
    if (!$('#centradoAC').length) {
        genAvisoCookies();
    }
    goToByScroll();

}

function goToByScroll() {
/*efecto de animación para ir a la parte superior donde se visualiza el panel*/
    $('html,body').animate({ scrollTop: $('#centradoAC').offset().top }, 'slow');
}

Al situar

Curiosidades

Es curioso que en el momento de redactar el sitio web del gobierno  utiliza cookies de análisis de GA pero no visualiza el panel. ¡ En cierta medida el gobierno viola la ley que el mismo aprobó!
Actualización:
No violan la ley, ¡se excluyeron de la ley!. Bueno al menos en los últimos tiempos han incluido el aviso de cookies y hacen lo que todos.

Todo este trabajo se habría simplificado si la ley hubiera excluido claramente este tipo de cookies. Esto es lo que ha hecho el gobierno francés en su transposición de la directiva europea.

Por que todo esto cacao lo provoca  la Directiva 2009/136/CE. que endureció la Directiva 2002/58/CE
¿No tienen otra cosa que hacer nuestros burócratas europeos?  Por ejemplo, resolver la crisis económica de la zona Euro.

domingo, 9 de junio de 2013

Distintas formas de visualizar datos con gráficos

ENUNCIADO
En un entorno Microsoft quieres visualizar datos mediante gráficos ¿Qué alternativas tienes?
SOLUCIÓN
Cuando hablo de un entorno Microsoft quiero decir que dispones de Visual Studio y SQL Server, pero como veras no me restringiré a las herramientas incluidas en estos paquetes
Visual Studio  con Web Forms tienes una serie de controles para construir gráficos o crear un informe con procesado local. En cuanto a los controles no los he utilizado y no puedo decirte gran cosa sobre ellos. Sobre los informes los he utilizado poco porque al final los informes de servidor tienen mas capacidades. Nos ocuparemos en el siguiente párrafo tienen.
Si dispones de SQL Server puedes utilizar Reporting Services y  diseñarlos con Bussines Inteligence Developper Studio, BIDS en breve.
Las ventajas que aporta este entorno son:
  • Un interfaz gráfico durante el diseño
  • Orientado a la impresión
  • Exportación sencilla a distintos formatos.
Las desventajas que presenta son:
  • Aunque ofrece todos los gráficos clásicos, incluidos los mapas, se echan de menos algunos modelos, por ejemplo los gráficos jerárquicos. 
  • Ajustar los gráficos a los datos en tiempo de ejecución es complicado. (ver  mi ejemplo con mapas)
  • Interacción con los gráficos es limitada
  • Sin animación.
  • En proyectos MVC debes incorporar rutas webFoms.
El desarrollo es muy rápido una vez dominas el modelo conceptual y la exportación a PDF ofrece muchas ventajas, entre otras la capacidad de remitir por eMail los informes.
Pero ¿qué soluciones tienes si quieres gráficos con mas personalización e interactividad?
Entonces debes recurrir a librerías javaScript y preferiblemente proyectos MVC (en un entorno Microsoft, insisto).
¿Qué librerias javaScript utilizar?
La solución mas simple es Google Charts, te ofrece una mayor variedad de gráficos y no hace falta que seas un experto en java.
En cuatro horas desarrollé un gráfico jerárquico de países por distintas clasificaciones y magnitudes, ... pero la presentación resultó demasiado simple comparada con los gráficos obtenidos con Report Server y no triunfó entre mis 'clientes'.
Puedes conseguir mejores resultados con infoVis, Pero mi apuesta en cuanto tenga un proyecto complejo es la librería Data-Driven Documents (D3),  te sugiero que te dediques a navegar entre los ejemplos y entenderás por qué.
 

miércoles, 29 de mayo de 2013

¿Qué significa T4?

Pregunta:

¿De dónde viene el nombre de las plantillas T4?

Respuesta:

Text Template Transformation Toolkit

sábado, 25 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (y 5)

ENUNCIADO


En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
En la entrada (4) calculamos los intervalo numéricos de la regla.


En está última entrada de la serie veremos como obtener una escala de colores con degradados centrados en el rango que contiene el valor cero.

En nuestro caso para simplificar y porque los colores son los mas intuitivos situaremos el centro en el color amarillo y degradaremos hacia el azulo por los valores negativos y hacia el rojo por los valores positivos.

En mi algoritmo el color amarillo se situará a la derecha o a la izquierda del cero según el número de rangos que haya en cada lado. El objetivo es aminorar la perdida de definición en los colores y visualizar siempre los tres colores, lo que podría no ocurrir si el cero estuviera en primera o última posición.

SOLUCIÓN

El algoritmo rellena la lista en tres pasos:
Primero por la parte derecha donde:
  • El azul está en máximos (#FF o 255) y decrece hasta casi #00.
  • El rojo y el verde crecen desde #00 hasta  "casi"  #FF
En este punto entra el amarillo (#FFFF00)
Luego la parte derecha:
  • El azul se mantiene en #00
  • El rojo que empieza en casi #FF deberá terminar en #FF (aunque puede sufrir una perdida de intensidad intermedia).
  • El verde empieza el casi #FF pero termina en #00
Obviamente el "truco" del algoritmo está en calcular:

  • El punto medio donde situamos el amarillo
  • Los pasos para cada color en los degradados derecho e izquierdo.
  • 'Operar' con colores para generar el string hexadecimal.


    ''' 
    ''' Calcula un rango de colores que empieza en azul, pasa por el verde en el valor cero y termina en el rojo
    ''' 
    ''' colección de extremos de rango que contiene el valor cero
    ''' devuelve la lista de colores
    ''' 
    Public Function coloresCero(rango As List(Of Double)) As List(Of String)
        Dim colores As New List(Of String)
        Dim posicionMedio = rango.IndexOf(0) + 1
        If posicionMedio > rango.Count / 2 Then posicionMedio -= 1
        Dim cuentaIniciales = posicionMedio - 1
        Dim cuentaFinales = rango.Count - posicionMedio

        Dim pasoInicial = 255 / cuentaIniciales
        Dim pasoMedio = 255 / (cuentaIniciales + 1)

        For i = 0 To cuentaIniciales - 1
            colores.Add(HexString(pasoMedio * i, pasoMedio * i, 255 - pasoInicial * i))
        Next
        colores.Add(HexString(255, 255, 0))
        Dim pasoFinal = 255 / cuentaFinales
        pasoMedio = 255 / cuentaFinales
        For i = 1 To cuentaFinales
            colores.Add(HexString((pasoFinal * i) + (pasoMedio * (cuentaFinales - i)),
                                  pasoMedio * (cuentaFinales - i), 0))
        Next
        Return colores
    End Function

    Function HexString(r As Integer, g As Integer, b As Integer) As String
        ' cálcula el string hexadecimal de color RGB
      Return "#" + hex(r) + hex(g) + hex(b)
    End Function

    Function hex(v As Integer) As String
        ' Obtiene el hexadecimal de un entero con cero por delante si hace falta
        Dim r = String.Format("{0:x}", v)
        If r.Length > 1 Then
            Return r
        Else
            Return "0" + r
        End If

    End Function

Reconozco que hay un poco de juego fácil en la elección de los colores, pero eran los que me pedían. ¡Más fácil habría sido elegir como central el verde¡.

Una regla de color uniforme, redondeada, con cero y colores desplazados (4)

ENUNCIADO

En la entrada (1) de esta serie definimos los objetivos: una regla de color personalizada para un mapa generado mediante Reporting Services que fuera uniforme, redondeada, con cero y colores desplazados
En la entrada (2) vimos como podemos cargar un informe en memoria, para modificarlo y volcarlo en un control ReportViewer.
En la entrada (3) modificamos la definición XML del informe dejando pendientes las funciones que cálculan los rangos y colores.
Durante la modificación de las reglas de color del informe utilizamos una función rangosMapa que desde una sentencia MDX devuelve la lista de valores doubles para construir los rangos de la regla.

Dicho de otra forma, los números que vemos aquí:

Recordemos que estos números deben satisfacer las condiciones:

  1. Cada rango de color debe contener aproximadamente al misma cantidad de países.
  2. Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.

SOLUCIÓN

En nuestro ejemplo los datos provederán de un servidor SQL Server Analisys Services por lo que necesitaremos la librería Microsoft.AnalysisServices.adomdClient 
Imports Microsoft.AnalysisServices.AdomdClient

Dividimos nuestro trabajo en dos pasos: evaluar la sentencia MDX y calcular los rangos:

Public Function RangosMapa (s As String) As List(Of Double, numRangos as Integer)
    Dim lista As List(Of Double)= EvaluaMDX(s)
    Dim Rangos as List (Of Double)=RangosRedondos(lista, NumRangos, 0, True)
    Return (Rangos)
End Function

Evaluar la expresión MDX y almacenamos los resultados en una lista de doubles no tiene gran secreto:

 Public Function EvaluaMDX(s As String) As List(Of Double)
        Dim lista As New List(Of Double)
        Dim cn As New AdomdConnection(My.Settings.SSASConexion)
        cn.Open()
        Dim cmd As AdomdCommand = cn.CreateCommand()
        cmd.CommandText = evaluaSentencia(s)
        Dim lector = cmd.ExecuteReader()
        Do While lector.Read
            lista.Add(lector.GetDecimal(4)) 'el valor está en la posición 4
        Loop
        lector.Close()
        cn.Close()
        cn.Dispose()
        Return (lista)
End Function

El procedimiento siguiente será crear la lista de rangos.

El truco está en ordenar la lista y luego la recorrerla en saltos con un paso calculado desde el total de valores y numero de intervalos que queramos. En cada salto extremos el valor y lo añadimos a la lista de rangos Obtendríamos así una la lista que satisfacerla  la condición 1 más arriba..

Para el caso de que la lista contenga el cero (condición 2), el procedimiento es ligeramente distinto. Trabajamos con dos listas (positivos y negativos) y aplicamos el algoritmo anterior a cada uno de ellos con un numero de rangos en cada lista proporcional a la posición del cero.

Esta lista tendrá valores extraídos de la lista por ejemplo 9457,89. La siguiente tarea será redondear a un múltiplo de 10 sin alterar los valores incluidos en cada rango, por ejemplo 9000,00. pero deberemos hacer evitando afectar el número de valores entre los nuevos extremos.
Veamos el código que quizás sea lo mas simple:
 
   ''' 
    ''' Desde una lista de valores decimales obtiene otra lista de valores decimales que reparte de forma uniforme los valores.
    ''' 
    ''' Lista original
    ''' Número de rangos en los que se repartirá
    ''' número decimales
    ''' Lista de rangos
    ''' 
    Public Function RangosRedondos(lista As List(Of Double), numRangos As Integer, decimales As Integer, redondear As Boolean) As List(Of Double)
        Dim rangos As List(Of Double)
        lista.Sort() 'ordenamos
        'obtención del rango variable 
        If lista(0) <= 0 And lista(lista.Count - 1) >= 0 Then
            'cuando hay un cero deberá aparecer en un extremo de los rangos
            rangos = repartoConCero(lista, numRangos)
        Else
            rangos = reparto(lista, numRangos)
        End If
        'ahora vamos redondeando sin alterar el reparto
        If redondear Then rangos = RedondeoLista(rangos, lista, decimales)
        'y para terminar nos aseguramos de que los valores extremos esten incluidos
        rangos(0) -= 1
        rangos(rangos.Count - 1) += 1
        Return rangos
    End Function

    ''' 
    ''' Crea una lista de rangos que contienen aproximandamente la misma cantidad de elementos
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function reparto(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim paso As Decimal = lista.Count / numRangos
        Dim rangos As New List(Of Double)
        rangos.Add(lista(0))
        For i = 1 To numRangos - 1
            rangos.Add(lista(Math.Round(i * paso, System.MidpointRounding.AwayFromZero)))
        Next
        rangos.Add(lista(lista.Count - 1))
        Return rangos
    End Function

    ''' 
    ''' Reparte los valores de una lista decimal que cruza el valor cero entre una serie de rangos.
    ''' Dos rangos compartiran el extremo con valor cero
    ''' 
    ''' lista ordenada de números decimales desde donde se genera
    ''' numero de rangos
    ''' lista con numrangos + 1 valores decimales que definen los rangos
    ''' 
    Private Function repartoConCero(lista As List(Of Double), numRangos As Integer) As List(Of Double)
        Dim Negativos = (From v In lista Where v < 0).ToList
        Dim Positivos = (From v In lista Where v >= 0).ToList
        Dim rangoNegativos = reparto(Negativos, numRangos * Negativos.Count / lista.Count)
        Dim rangoPositivos = reparto(Positivos, numRangos * Positivos.Count / lista.Count)
        If (-rangoNegativos(rangoNegativos.Count - 1)) < rangoPositivos(0) Then
            'el mayor número negativo está mas próximo a cero que el menor positivo
            rangoNegativos(rangoNegativos.Count - 1) = 0
            rangoPositivos.RemoveAt(0)
        Else
            rangoPositivos(0) = 0
            rangoNegativos.RemoveAt(rangoNegativos.Count - 1)
        End If
        Dim resultado As New List(Of Double)
        resultado.AddRange(rangoNegativos)
        resultado.AddRange(rangoPositivos)
        Return resultado
    End Function

    ''' 
    ''' Redondea todo lo posible los valores del rango.
    ''' 
    ''' rango a redondear
    ''' lista que controla el rango
    ''' número de decimales a conservar
    ''' 
    ''' 
    Private Function RedondeoLista(rangos As List(Of Double), lista As List(Of Double), decimales As Integer) As List(Of Double)
        For i = 1 To rangos.Count - 2
            Dim limiteRango = rangos(i)
            'calculamos la diferencia entre el último valor en el rango anterior y el primero del siguiente
            Dim difSupInf = (From v In lista Where v >= limiteRango).Min - (From v In lista Where v < limiteRango).Max
            'calculamos el primer digito no significativo mediante la parte entera del logaritmo en base 10
            Dim factorDigitos = 10 ^ Math.Floor(Math.Log10(difSupInf) + decimales - 1) 'le sumamos los decimales (ver linea siguiente)
            Dim factorDecimales = 10 ^ decimales
            limiteRango *= factorDecimales 'estamos trasladando los decimales a la parte entera.
            limiteRango = Math.Floor(limiteRango / factorDigitos) * factorDigitos
            rangos(i) = limiteRango / factorDecimales
        Next
        Return rangos
    End Function

Antes de terminar llamar la atención sobre dos parámetros que no he comentado:

  • decimales: que permite establecer el número de decimales con los que trabajamos en el redondeo.
  • redondeo: por si en un momento preferimos obtener la lista sin redondeo.
La próxima y última entrada de esta serie definirá una lista de colores para el rango calculado si este contuviera el "cero".

Una regla de color uniforme, redondeada, con cero y colores desplazados (3)


ENUNCIADO:

En la primera entrada de esta serie presentamos nuestro objetivo: Personalizar la regla de colores en un mapa incluido en un informe SSRS de forma que los valores y colores cumplieran ciertos requisitos.
En la segunda entrada vimos cómo podíamos cargar en memoria del cliente la definición XML de un informe SSRS para poder modificarla antes de entregarsela al control ReportViewer.
En esta entrada veremos como modificamos la entrada XML.

SOLUCIÓN:

El código esta comentado:
 
 Public Function ParametrizaMapa(informe As XDocument, 
                  s as string,
                  numRangos as integer) As XDocument
' s contiene una sentencia MDX 
' numRangos numero de rangos que deseamos

        'Calculamos los rangos de la regla de colores
        dim rangosMapa As List(Of Double)=rangosMapa(s,numRangos)
        
        ' Para escribir menos.
        Dim df = informe.Root.Name.Namespace
        Dim capas = informe.Root.Element(
            df + "ReportSections").Element(
            df + "ReportSection").Element(
            df + "Body").Element(
            df + "ReportItems").Element(
            df + "Map").Element(
            df + "MapLayers").Element(
            df + "MapPolygonLayer")

        ' comienzan las reglas de color
        Dim reglasColor As XElement

        ' borramos cualquier regla existente en el informe original
        If capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule") IsNot Nothing Then 
              capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule").Remove()
        end if
        If capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule") IsNot Nothing Then
              capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule").Remove()
        end if
        'Vamos a establecer los colores y sus degradados
        If rangosMapa.Contains(0) Then
            'mapa coloreado alrededor del cero 
            'hay que generar la personalización "MapColorRangeRule" mediante la función coloresCero
            capa.Element(df + "MapPolygonRules").Add(New XElement(df + "MapCustomColorRule"))
            reglasColor = capa.Element(df + "MapPolygonRules").Element(df + "MapCustomColorRule") 'Personalizado
            reglasColor.Add(New XElement(df + "MapCustomColors"))
            Dim colorines = reglasColor.Element(df + "MapCustomColors")
            dim colores as list (of String)= coloresCero(rangosMapa)  'obtenemos una lista de colores
            For Each color In colores
                colorines.Add(New XElement(df + "MapCustomColor", color))
            Next
        Else
            'mapa estándar podemos dejar utilizar la generación automática "MapColorRangeRule"
            capa.Element(df + "MapPolygonRules").Add(New XElement(df + "MapColorRangeRule")) 'Generado por SSRS
            reglasColor = capa.Element(df + "MapPolygonRules").Element(df + "MapColorRangeRule")
            reglasColor.Add(New XElement(df + "StartColor", My.Settings.ReglaColorInicio))
            reglasColor.Add(New XElement(df + "MiddleColor", My.Settings.ReglaColorIntermedio))
            reglasColor.Add(New XElement(df + "EndColor", My.Settings.reglaColorFin))

        End If
        'algunas cosas que siempre son necesarias
        reglasColor.Add(New XElement(df + "ShowInColorScale", "true"))
        reglasColor.Add(New XElement(df + "DataValue", "=Sum(Fields!Dato.Value)"))
        reglasColor.Add(New XElement(df + "BucketCount", rangosMapa.count.toString))
        reglasColor.Add(New XElement(df + "LegendText", "#FROMVALUE{N0} - #TOVALUE{N0}"))

        'Vamos a rellenar los rangos de valores
        Dim culturaUSA = New System.Globalization.CultureInfo("en-US")
        If rangosMapa.Count = 2 Then 'solo max y min
            reglasColor.Add(New XElement(df + "DistributionType", "EqualInterval"))
            reglasColor.Add(New XElement(df + "StartValue"), rangosMapa(0).ToString(culturaUSA.NumberFormat))
            reglasColor.Add(New XElement(df + "EndValue"), rangosMapa(1).ToString(culturaUSA.NumberFormat))
        Else
            'aquí es donde realmente estamos personalizando con nuestro rango personalizado
            reglasColor.Add(New XElement(df + "DistributionType", "Custom"))
            reglasColor.Add(New XElement(df + "MapBuckets"))
            Dim rangos = reglasColor.Element(df + "MapBuckets")
            For i = 0 To rangosMapa.Count - 2
                rangos.Add(New XElement(df + "MapBucket"))
                Dim rango = rangos.Elements(df + "MapBucket")(i)
                rango.Add(New XElement(df + "StartValue", rangosMapa(i).ToString(culturaUSA.NumberFormat)))
                rango.Add(New XElement(df + "EndValue", (rangosMapa(i + 1)).ToString(culturaUSA.NumberFormat)))
            Next
        End If

        'Establecemos la sentencia MDX que suministra los datos al informe
        Dim xdatos = (From f In doc.Root.Element(df + "DataSets").Elements(df + "DataSet")).First(
            Function(x As XElement) x.Attribute("Name") = "Datos").Element(df + "Query").Element(df + "CommandText")
            xdatos.SetValue(s)
        Return informe
End Function
          

Con esto hemos terminado la personalización del informe, nos quedan por definir las  dos funciones rangosMapa y coloresCero que presentaremos en la siguientes entradas



Una regla de color uniforme, redondeada, con cero y colores desplazados (2)

ENUNCIADO:

En la primera entrada de esta serie presentamos nuestro objetivo: Personalizar la regla de colores en un mapa incluido en un informe SSRS de forma que los valores y colores cumplieran ciertos requisitos.

El diseñador de informes de MS (BIDS) no nos permite definir reglas de color automáticas para nuestros requisitos, deberemos utilizar intervalos y colores personalizados.

Dado que esta personalización depende de los valores de la consulta no puede estar establecida en el informe, y deberemos modificar este cada vez que vayamos a visualizarlo.

En esta entrada vamos a ver como cargar un informe desde el servidor SSRS en nuestro programa cliente, modificar el XML e inyectar el resultado en un control ReportViewer que finalmente solicitará al servidor el procesado.

SOLUCIÓN:

Partimos de que hemos creado una página web que contiene un control ReportViewer llamado Visor y que está configurado para remoteProcessing.
Usaremos la libreria ReportingServices2010 nos permite recuperar la definición de un informe  mediante los servicios Web de reporting services:

Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

El esquema de nuestro proceso será:
 
Sub Main()
 Dim informe as byte()=cargaInforme(nombre as string) 'cargamos el informe
 Dim informeXML as system.XML.linq.xDocument= bytesToXML(informe)  'lo pasamos a XML
 informeXML=parametrizaMapa(informeXML, sentenciaMDX,10)  'Personalizamos e informe
 informe = XMLtoBytes(InformeXML)  'Volvemos a array de bytes
 Visor.ServerReport.LoadReportDefinition(informe) 'lo cargamos en el visor de informes.
 Visor.ServerReport.Refresh()
End Sub 

Deberemos importar la librería de acceso proxy al SSRS
Imports mapasAduanas.Microsoft.SqlServer.ReportingServices2010

Para poder cargar el informe:

    Private Function CargaInforme(nombre As String) As byte()
        Dim SSRS As New ReportingService2010
        SSRS.Credentials = (New MyReportServerCredentials).NetworkCredentials
        Dim informe = SSRS.GetItemDefinition(My.Settings.pathMapas + nombre)
        SSRS = Nothing
        Return doc
    End Function

Para conectarnos necesitamos suministrar una credenciales utilizando la clase myReportServerCredentials descrita en Autenticación Reporting Services.

La función getItemDefinition y  nuetra función CargaInforme nos devuelven una matriz de bytes que deberemos convertir en un documento system.XML.link.xDocument para poder editarla:
    Private Function bytesToXML(informe As byte()) As xDocument
        Dim stream = New System.IO.MemoryStream(informe)
        Dim lector = System.Xml.XmlReader.Create(stream)
        Dim doc As XDocument = XDocument.Load(lector, LoadOptions.None)
        Return doc
    End Function

Cuando hayamos terminado de personalizar este documento deberemos volverlo a convertir en matriz de bytes.

   Private Function XMLBytes(doc As XDocument) As System.IO.Stream
        Dim stream As New System.IO.MemoryStream
        Dim escritor = System.Xml.XmlWriter.Create(stream)
        doc.Save(escritor)
        escritor.Close()
        stream.Position = 0
        Return stream
    End Function

En la siguiente entrada editaremos el XML del informe usando linq-to-xml.

viernes, 24 de mayo de 2013

Una regla de color uniforme, redondeada, con cero y colores desplazados (1)

ENUNCIADO

Desde una consulta que puede devolver valores negativos y positivos para todos los países del mundo quieres generar un mapa geográfico cuyos colores reflejen estos valores con una regla de color basada en tres colores: color inferior, color medio y color superior con degradados desde el color medio al inferior y desde el color medio al superior.
Hasta aquí todo se puede resolver con los mapas del servidor de informes del Microsoft (SSRS). Pero además deben satisfacerse los siguientes requisitos:
  1. Uniforme: Cada rango de color debe contener aproximadamente la misma cantidad de países.
  2. Redondeada: Los extremos de cada rango deben redondearse con ceros a la derecha sin afectar la condición anterior.
  3. Con cero: Si los valores inferior y exterior tienen distinto signo dos rangos de color deben tener como extremo el cero, con mínima afección sobre el requisito 1.
  4. Colores desplazados: En caso de visualizarse el cero el color medio se situara en un rango que contenga este valor entre sus extremos.
Veamos con un ejemplo que ilustra una caída de ventas mundial en los inicios de la crisis del 2008:




Fíjate en los números redondeados y en el cero que desplaza el color amarillo a la derecha de la barra.

UN COMENTARIO PREVIO

Este entrada reemplaza a algunas que escribí anteriormente, pero en esta nueva versión he mejorado los algoritmos y voy a redactarlo mas claramente. De hecho dedicaré cuatro entradas a presentar la solución.
Comencemos.

SOLUCIÓN

El mapa lo visualizaremos mediante un control reportViewer de  WindowsForms o de WebForms configurado para informes de servidor SSRS (remote processing).

Aunque partiremos de un informe diseñado BIDS y plublicado en el SSRS, el visor no utilizará este informe directamente. En su lugar nuestra aplicación cargará la definición XML, la modificará en memoria y la cargará en el visor de informes que solicitará el procesado al SSRS.

Las modificaciones consistirán en personalizar las reglas de color de color de la regla tanto en lo que atañe a los rangos de valor (extremo superior e inferior para cada uno) como en los colores aplicados en cada rango.

Para personalizar los rangos numéricos utilizaremos una función que nos devolverá un lista de "Double" que contendrá los extremos de estos rangos según los requisitos 1, 2, 3 dichos arriba. A esta función dedicaremos la tercera entrada. Otra función nos devolverá una lisa de colores centrada en el rango de cambio de signo.

Dedicaremos cuatro entradas a estos trabajos

Comentario:

Antes de seguir quería decir que desarrollar una aplicación para visualizar magnitudes sobre mapas me ha recordado una vez más que cuando ves resultados estadísticos tienes que estar muy atento a lo que te muestran y como te lo muestran.

Por ejemplo el mapa mas arriba visualizaba las  variaciones de ventas interanuales  entre octubre 2009 y octubre 2008.
Si en lugar del acumulado mostráramos el acumulado interanual de ventas en octubre de 2009, podriamos decir ¿ Crisis? ¿Qué crisis?:







martes, 21 de mayo de 2013

Añadir etiquetas con jerarquía en el mapa del documento de un informe Microsoft

ENUNCIADO

Tienes un informe con grupos y detalles, ¿Donde añadir las etiquetas para que el mapa del documento esté estructurado jerarquicamente. ¿En los cuadros de texto? ¿En la celda del tablix?

SOLUCIÓN

Olvídate del área de diseño.
En los grupos y detalles del el panel inferior, les das propiedades y usas la ficha Avanzado del formulario que se abre.

viernes, 17 de mayo de 2013

Repetir las filas de encabezados en informes Microsoft

Enunciado

¿Cómo hacer que se repitan las filas de encabezado en tablix de report server?

Respuesta

En el panel de grupos (abajo) activa la vista avanzada
(La vista avanzada se consigue desplegando una flecha mínima a la derecha del panel que incluye los titulos grupo de filas y grupos de columnas ¡!)
Entonces veras unos grupos de filas (static) que corresponden a tus encabezados (si hay varios).
Selecciona uno.
Cambia en el panel de propiedades a la derecha:
KeepWithGroup: After
RepeatOnNewPage: True

martes, 14 de mayo de 2013

T-SQL para cambiar la intercalación

Enunciado

Cambiar la intercalación de todas las columnas de todas las tablas de una base de datos.

Solución

Ejecutas el siguiente T-sql en una consulta SSMS.
DECLARE @collation NVARCHAR(64)
SET @collation = 'Modern_Spanish_CI_AI'

SELECT 
    'ALTER TABLE [' + INFORMATION_SCHEMA.COLUMNS.TABLE_SCHEMA  + '].[' + INFORMATION_SCHEMA.COLUMNS.TABLE_NAME + '] '
  + 'ALTER COLUMN [' + COLUMN_NAME + '] '
  + DATA_TYPE + '(' + CASE CHARACTER_MAXIMUM_LENGTH 
        WHEN -1 THEN 'MAX' 
        ELSE CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR) END + ') '
  + 'COLLATE ' + @collation + ' '
  + CASE WHEN IS_NULLABLE = 'NO' THEN 'NOT NULL' ELSE 'NULL' END
FROM INFORMATION_SCHEMA.columns
INNER JOIN INFORMATION_SCHEMA.TABLES
ON INFORMATION_SCHEMA.TABLES.TABLE_NAME=INFORMATION_SCHEMA.COLUMNS.TABLE_NAME
WHERE  COLLATION_NAME IS NOT NULL
AND COLLATION_NAME <> @collation
AND INFORMATION_SCHEMA.TABLES.TABLE_TYPE ='BASE TABLE'

El resultado lo copias y lo pegas en otra ventana de consulta.
Revisa ese código generado y si está correcto ejecutas.

viernes, 10 de mayo de 2013

Generar miniaturas de archivos TIF, JPG, PDF,...

Enunciado

Desarrollas una aplicación donde los usuarios deben subir archivos varios gráficos en diversos formatos gráficos (JPG, TIF,...etc), así como archivos PDF.
Quieres devolverles un informe en PDF con los archivos y las informaciones asociadas.
Sin embargo no los informes de Microsoft no permiten el uso de muchos de los tipos de archivos o estos tienen excesivo peso lo que repercute en la generación y peso mismo del informe.

Solución

Genera las miniaturas en el momento en que suban el archivo, para ello utiliza http://imageresizing.net que puedes instalar mediante NuGet.
Para usarla lee esta página: http://imageresizing.net/docs/howto/upload-and-resize
Para que pueda leer y procesar PDFs añádele el plugin PdfRenderer.
Para instalar este último plugin usa NuGet pero además debes editar el archivo web.config.

  
    
..... sigue el web config

El código será el mismo que para generar las miniaturas desde archivos gráficos. El plugin le añade la capacidad de leer PDFs.

Comentarios

Es una librería perfecta que tiene muchas mas aplicaciones. Es tan simple de usar que al principio no te lo crees.

lunes, 22 de abril de 2013

Uso de plantillas dinámicas de Blogger en un blog con código de programación

Pregunta:

¿Es recomendable o por lo menos interesante usar la plantillas dinámicas de Blogger en un blog que incluya código?

Respuesta:

Yo personalmente no me arriesgaría, mi experiencia no ha sido buena.
El principal problema aparece con la inclusión del código  SyntaxHighlighter que hace mucho mas legible el código fuente.
Estuve pegándome durante casi una año, cada cierto tiempo creía que lo conseguía. Incluso publique una entrada sobre como usar SyntaxHighliter con plantillas dinámicas.Pero cada cierto tiempo descubría que el código había perdido su formato.

Al final me harté y volví  a las plantillas estáticas.
Y entonces descubrí el sentido inverso del aviso que me advirtió sobre un incremento en las estadísticas de visitas provocado por el uso de plantillas dinámicas era cierto a la inversa.
Tuve que quitar el gráfico de visitas porque parecía que todo el mundo me había abandonado. No escribo esto para presumir, pero me molestaba cada vez que lo veía.

Además me parece que con las plantillas se puede jugar mas como programador (si tienes tiempo).

Incialización del valor en combox y listbox de windows forms mediante ValueMember

Problema

En un desarrollo Windows forms rellenas una combo o una listBox desde una consulta linq y estableces el ValueMember en un campo.

lista.ValueMember="id"
lista.addRange(Bd.mitabla.toArray())

Posteriormente quieres incializar seleccionando un elemento de lista que tiene un valor concreto:
lista.selectedValue=20

¡Y resulta que no funciona.!

Solución:

Usar:
  • Datasource en lugar de addRange
  • ToList en lugar de toArray.
lista.ValueMember="id"
lista.DataSource(Bd.mitabla.toList())

¡Y esto si que funciona!: lista.selectedValue=20

Comentario

Reconozco haber perdido mucho tiempo hasta haber descubierto esto. En mi descargo debo decir que en 1998 dejé de programa en Visual Basic 2012 y solo hace unos tres años que he vuelto a desarrollar en Windows Forms y muy esporadicamente.
Este es una de los tips que escribo prácticamente para mí, porque siempre se me olvida

viernes, 5 de abril de 2013

Menú contextual y grid view de windows forms

Enunciado

Imagina un gridview con varias filas. Haces click con el botón derecho y se despliega un menú contextual con opciones editar, borrar,...etc.
Quieres que si eliges editar se abra una ventana para editar la fila sobre la que estas pinchando.
El problema es que las propiedades currentRow y SelectedRows no corresponden a la fila que pinchas.

Solución

Hay que interceptar el evento mousedown para cambiar la selección a la fila debajo del pincho del ratón.

    Private Sub gvContactos_clickContexto(sender As Object, e As MouseEventArgs) Handles gvContactos.MouseDown

        Dim hti = gvContactos.HitTest(e.X, e.Y)
        If (hti.Type = DataGridViewHitTestType.Cell) Or (hti.Type = DataGridViewHitTestType.RowHeader) Then
            gvContactos.ClearSelection()
            gvContactos.Rows(hti.RowIndex).Selected = True
        End If

    End Sub

    Private Sub gvContactos_EditarActiv(sender As Object, e As System.EventArgs) Handles gvContactos.DoubleClick,
                                                                                        btnEditaActivContacto.Click,
                                                                                         btnCtxEditActivCto.Click
        If gvContactos.SelectedRows.Count = 1 Then editaActivContacto(CType(gvContactos.SelectedRows(0).DataBoundItem, tblFbContacto))
    End Sub

    Private Sub gvcontactoEdita_Click(sender As Object, e As System.EventArgs) Handles btnEditaContacto.Click,
                                                                                    btnCtxEditarContacto.Click
        If gvContactos.SelectedRows.Count = 1 Then
            editaContacto(CType(gvContactos.SelectedRows(0).DataBoundItem, tblFbContacto))
        End If
    End Sub
Algunos ejemplos que he visto comprueban si se ha pulsado el botón derecho o el izquierdo en el argumento del evento mousedown. En mi caso no lo hago porque en cualquier caso quiero reducir la selección a una fila.

martes, 2 de abril de 2013

Entrar en Windows Server 2012 con la cuenta de dominio

Enunciado

Instalas Windows 2012 en una maquina virtual vmWare.
Cuando terminas la instalación, haces login con la cuenta del administrador de maquina creada durante la instalación.
Procedes a añadir el equipo al dominio y reinicias.
No encuentras la forma de hacer login con una cuenta de dominio en lugar de la del administrador.

Solución

Supongo que realmente lo que quieres es autorizar una cuenta de dominio para el acceso con el escritorio remoto.
Puedes hacerlo con la cuenta de administrador del equipo y escribiendo el nombre del usuario de dominio de la forma habitual \
Podrás hacer login y además a partir de entonces tendrás opción a elegir el nombre del usuario.

Comentario

Es posible que haya otras formas ¿dar de alta el usuario de dominio en usuarios?.  Pero con esto me funcionó y después de haber perdido una hora con la maravillosa METRO-usabilidad.....no estoy para despilfarros.

jueves, 21 de marzo de 2013

Plantillas Endpoint Proteccion

Enunciado

Estas configurando diversas políticas para la detección de malware en estaciones y servidores con System Center Configuration Manager2012 y End Point Protection.
Especialmente en controladores de dominio, servidores de correo, etc esto puede ser complejo.
¿Hay algo que pueda ayudarte?

Solución:

En http://configmgrblog.com/2012/07/09/out-of-the-box-endpoint-protection-2012-policy-templates/ descubrí que en cualquier ordenador donde tengas instalada la consola de administración puedes acceder a la carpeta:
%programfiles%\"Microsoft Configuration Manager\AdminConsole\XmlStorage\EPTemplates"
Donde encontraras una colección de plantillas para distintas situaciones.

lunes, 11 de marzo de 2013

Problemas con el lenguaje español en umbraco

Síntomas

En Umbraco 6.0 se producen errores por la ausencia de código Java cuando un usuario que tiene establecido el idioma en español edita propiedades del tipo texto enriquecido.
Los errores hacen referencia la ausencia de módulos de lenguaje como es.js o o es_dlg.js.
En Chrome impiden la edición de la propiedad completamente.

Estos módulos es deberían figurar en la carpeta lang de tinyMCE3 y de diversos pluggins (por ejemplo faltan en   Umbraco_Client\Tinymce3\Plugins\Umbracoimg\Langs y en Umbraco_Client\Tinymce3\Plugins\Umbracoembed\Langs

Solución

Haz una copia del modulo en.js, en es.js (o similar).
Editarlo cambiando las referencias de idioma.
Por ejemplo: tinyMCE.addI18n('en.umbracoembed',... por tinyMCE.addI18n('es.umbracoembed',....
También si tienes mucho interés y tiempo puedes cambiar los textos descriptivos.

Actualización:
Tambien puedes copiar las traducciones del tinymce para la versión que usas:
http://www.tinymce.com/i18n3x/index.php?ctrl=lang&act=download&pr_id=1

martes, 5 de marzo de 2013

Error corrector ortográfico de Umbraco

Sintomas

Si intentas utilizar el corrector ortográfico del editor de texto enriquecido de Umbraco (Versión 6) te salta un error.
[ArgumentOutOfRangeException: El índice y la longitud deben hacer referencia a una ubicación en la cadena.
( Index and length must refer to a location within the string)

Solución: 

La solución está esbozada en: http://our.umbraco.org/forum/ourumb-dev-forum/bugs/28540-4711-spell-check-in-RTE.
Lo único que añado en este post son las instrucciones "Using" que faltan en el código del controlador genérico.

Básicamente es:
1.- Modificar /config/tinyMceConfig.config con la línea:
<config key="spellchecker_rpc_url">GSpellChecker.ashx</config>

Y agregar un controlador genérico: /umbraco/GSpellChecker.ashx
using System;
using System.Web;
using umbraco.presentation.umbraco_client.tinymce3.plugins.spellchecker;
using System.Xml;
using System.IO;
using System.Web.Script.Serialization;
using System.Net;
using System.Text;
public class GSpellChecker : SpellChecker, IHttpHandler {
  // Methods
  public override SpellCheckerResult CheckWords(string language, string[] words) {
    XmlDocument document = new XmlDocument();
    string data = string.Join(" ", words);
    string xml = SendRequest(language, data);
    document.LoadXml(xml);
    data = HttpContext.Current.Server.UrlEncode(data);
    SpellCheckerResult result = new SpellCheckerResult();
    foreach (XmlNode node in document.SelectNodes("//c")) {
      XmlElement element = (XmlElement)node;
      result.result.Add(data.Substring(Convert.ToInt32(element.GetAttribute("o")), Convert.ToInt32(element.GetAttribute("l"))));
    }
    return result;
  }

  public override SpellCheckerResult GetSuggestions(string language, string word) {
    XmlDocument document = new XmlDocument();
    string xml = SendRequest(language, word);
    document.LoadXml(xml);
    SpellCheckerResult result = new SpellCheckerResult();
    foreach (XmlNode node in document.SelectNodes("//c")) {
      XmlElement element = (XmlElement)node;
      foreach (string str2 in element.InnerText.Split(new char[] { '\t' })) {
        if (!string.IsNullOrEmpty(str2)) {
          result.result.Add(str2);
        }
      }
    }
    return result;
  }

  public void ProcessRequest(HttpContext context) {
    SpellCheckerInput input = SpellCheckerInput.Parse(new StreamReader(context.Request.InputStream));
    SpellCheckerResult suggestions = null;
    string method = input.Method;
    if (method != null) {
      if (!(method == "checkWords")) {
        if (method == "getSuggestions") {
          suggestions = this.GetSuggestions(input.Language, input.Words[0]);
          goto Label_007C;
        }
      } else {
        suggestions = this.CheckWords(input.Language, input.Words.ToArray());
        goto Label_007C;
      }
    }
    suggestions = new SpellCheckerResult();
  Label_007C:
    suggestions.id = input.Id;
    string s = new JavaScriptSerializer().Serialize(suggestions);
    context.Response.Write(s);
  }

  private static string SendRequest(string lang, string data) {
    string str;
    string requestUriString = string.Format("https://www.google.com:443/tbproxy/spell?lang={0}&hl={0}", lang);
    string s = string.Format("<?xml version=\"1.0\" encoding=\"utf-8\" ?><spellrequest textalreadyclipped=\"0\" ignoredups=\"0\" ignoredigits=\"1\" ignoreallcaps=\"1\"><text>{0}</text></spellrequest>", HttpContext.Current.Server.UrlEncode(data));
    StreamReader reader = null;
    HttpWebResponse response = null;
    Stream requestStream = null;
    try {
      HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUriString);
      request.KeepAlive = false;
      request.Method = "POST";
      request.ContentType = "application/PTI26";
      request.ContentLength = s.Length;
      WebHeaderCollection headers = request.Headers;
      headers.Add("MIME-Version: 1.0");
      headers.Add("Request-number: 1");
      headers.Add("Document-type: Request");
      headers.Add("Interface-Version: Test 1.4");
      requestStream = request.GetRequestStream();
      byte[] bytes = new ASCIIEncoding().GetBytes(s);
      requestStream.Write(bytes, 0, bytes.Length);
      response = (HttpWebResponse)request.GetResponse();
      reader = new StreamReader(response.GetResponseStream());
      str = reader.ReadToEnd();
    } finally {
      if (requestStream != null) {
        requestStream.Close();
      }
      if (reader != null) {
        reader.Close();
      }
      if (response != null) {
        response.Close();
      }
    }
    return str;
  }

  // Properties
  public bool IsReusable {
    get {
      return false;
    }
  }
}

martes, 12 de febrero de 2013

Error java script con Report Viewer e Internet Explorer 9 (window.getComputedStyle)

Contexto

Página con ReportViewer en Internet Explorer 9,

Sintomas

Aparece un "Error en tiempo de ejecución de Microsoft jscript, Interfaz no compatible" al desplegar una de las listas de criterios.
El error esta relacionado con la función window.getComputedStyle.

Solución

Fuente: MikeHoffer en un articulo de Microsoft Community
  1. Prepara la página para usar jquery si aún no existe.
  2. Inserta el siguiente código en la sección de cabecera.
<!-- IE 9 throws an error when this is called, so we need to override it and catch that error -->
    <!--[if IE 9]>
    <script language="javascript" type="text/javascript">
        $(window).ready(function () {
            window.getComputedStyle = function (el, pseudo) {
                this.el = el;
                this.getPropertyValue = function (prop) {
                    var re = /(\-([a-z]){1})/g;
                    if (prop == 'float') prop = 'styleFloat';
                    if (re.test(prop)) {
                        prop = prop.replace(re, function () {
                            return arguments[2].toUpperCase();
                        });
                    }
                    //error occures on the currentStyle
                    //return el.currentStyle[prop] ? el.currentStyle[prop] : null;
                    if (typeof(el.currentStyle) == 'undefined') {
                        return null;
                    } else {
                        return el.currentStyle[prop] ? el.currentStyle[prop] : null;
                    }
                }
                return this;
            }
        })        
    </script>
    <![endif]-->

lunes, 11 de febrero de 2013

Insertar código de seguimiento Google Analytics sin editar todas las páginas

Problema

Tienes un sitio escrito que a lo largo de los años has rellenado con abundantes páginas HTML, ASP, ASPX,....etc.
Ahora quieres incorporarle el código Javascript para el seguimiento con Google Analytics.
Pero no quieres editar todas las páginas del sitio ni modificar todos los desarrollos.
¿Cómo lo haces?
(El sitio se aloja bajo IIS7.5)

Antes que nada

Esta solución es parecida a la propuesta por Ruslan Yakushev en:

Using Outbound Rules to add Web Analytics tracking code


Pero no es la misma y he añadido algún detalle necesario, al final del post detallo los cambios.

Solución

Primer paso

Copia el código de google analytics y salvalo en un archivo accesible de tu sitio web.
A efectos de este ejemplo supondremos que lo guardamos con el nombre google.js en un directorio /scripts
El código lo puedes copiar y pegar de:
Panel Analytics / tu sitio / administrador / Información de seguimiento

Segundo paso

Utiliza el modulo URL Rewrite y crea una regla de salida (outbound Rule)
Le creas una pre-condición con esta información:
  1. Name: "EsHTML"
  2. Using: "Regular Expressions"

Haz clic en "Add" para que tener el cuadro de dialogo  "Add condition"
En el cuadro de dialogo escribe:

  1. Condition input: "{RESPONSE_CONTENT_TYPE}"
  2. Check if input string: "Matches the pattern"
  3. Pattern: "^text/html"

Ahora debes 
  1. El matching scope : Content
  2. The content:  matches the Pattern
  3. Pattern:</head>
Marca la casilla ignorar mayúsculas (Ignore case)
La acción debe ser:

  1. Action type: Rewrite
  2. Value: <script src="/scripts/google.js" type="text/javascript"></script></head>

Explicación

Lo que hacemos es localizar el final de los encabezados de página para insertar el código Java que necesita Google Analytics.
Lo hacemos con una simple búsqueda y reemplazo. Solo procesamos los archivos con HTML que es para los que tiene sentido.

Hay dos diferencias con el articulo de Ruslan Yakushev  mencionado más arriba:
  1. Utilizamos una archivo externo. De hecho si intentamos usar el código Java directamente en el valor de reemplazo tendremos problemas porque los simbolos $ utilizados entran en contradicción con la sintaxis de URL rewriter.
  2. Reemplazamos </head> en lugar </body>. Es mas acorde con las recomendaciones de Google Analytics.

miércoles, 6 de febrero de 2013

El cliente del SCCM no se intala en algunos ordenadores

Síntomas

Cuando ya creías que tenias resuelta la instalación del cliente para System Configuration Center mediante las actualizaciones de Windows Update  resulta que en algunos ordenadores no se instala.
Ni siquiera se crea el directorio %windir%\CCM.

Solución

En %windir%\windowsupdate.log encuentras una linea sospechosa  # AU is not configured yet que te lo dice todo.
Tienes que habilitar "Configurar actualizaciones automática"s en la sección Windows Update de la GPO donde has configurado "Especificar la ubicación del servidor de Windows Update en la Intranet"

Explicación

¿Por qué funcionaba en unas maquinas y en otras no?
En muchas maquinas la persona que instala configura los servicios de Windows Updates en el primier arranque. En otros casos no se hace, confiando que las actualizaciones se descargaran del WSUS.
Para ello la GPO del WSUS anterior configuraba las actualizaciones automáticas para que se instalaran a una hora determinada.
Pero al cambiar la GPO se recuperaba la configuración propia de la maquina en el apartado configuración de Actualizaciones de Windows.


miércoles, 23 de enero de 2013

Problemas con jquery 1.9 en visual studio 2012

Síntomas:

Actualizas la versión de jQuery a 1.9.0 con el administrador de paquetes.
Tus aplicaciones dejan de funcionar incluso aunque actualices el resto de paquetes con dependencias de jQuery.
En mi caso dejo de funcionar el entorno jquery.mobile

Solución:

Vuelve a la versión 1.8.3 de jQuery utilizando la consola de Powershell de VS2012
Dado que hay diversos paquetes que dependen de jQuery te será comodo un powershell script.
Yo me cree este:
Uninstall-Package jquery.mobile
Uninstall-Package jQuery.UI.Combined
Uninstall-Package Microsoft.jQuery.Unobtrusive.Ajax
uninstall-package Microsoft.jQuery.Unobtrusive.Validation
Uninstall-Package jQuery.Validation 

Uninstall-Package jquery
install-Package jquery -Version 1.8.3

install-Package jquery.mobile
install-Package jQuery.UI.Combined
install-Package jQuery.Validation 
install-package Microsoft.jQuery.Unobtrusive.Validation
install-Package Microsoft.jQuery.Unobtrusive.Ajax

Dependiendo de los paquetes que tengas instalados pueden salir errores, solo tienes que ir añadiendo los paquetes al script. Las dependencias también te pueden crear problemas, todo es cambiar el orden.

Comentario:

Con el tiempo se resolverá pero es interesante conocer como revertir Nuget.

martes, 22 de enero de 2013

Los paquetes de software no se distribuyen en SCCM 2012

Síntomas:

Observas que los paquetes de software (por ejemplo actualizaciones) no están distribuyéndose mediante System Center Configuration Manager 2012
Cuando vas a \Monitoring\Overview\Deployments y miras el status de un  paquete con error ves que hay un conflicto de grupos (Group Conflict Error 0x87d00692)

Solución

Ves a un equipo con el problema y abre  WUAHander.log (en mi caso en c:\windows\CCM\logs) con el Configuration manager trace log tool
Probablemente veras una línea donde dice

Enabling WUA Managed server policy to use server: http://wsus.empresa.org:80 WUAHandler 14/01/2013 12:50:47 2728 (0x0AA8)
Y luego otra línea donde dice:
Group policy settings were overwritten by a higher authority (Domain Controller) to: Server  http://wsus.empresa.org and Policy ENABLED WUAHandler 14/01/2013 12:50:49 2728 (0x0AA8)
Failed to Add Update Source for WUAgent of type (2) and id ({77E243DD-3DD0-4F77-B5DF-2029B77820B9}). Error = 0x87d00692. WUAHandler 18/01/2013 16:21:09 38480 (0x9650)
La clave está en lo que he marcado en negrita: en el segundo mensaje falta el puerto 80 detrás del servidores WSUS.
Debes corregirlo en la GPO que creaste para instalar el cliente SCCM.
Otro posible error es no usar la fqdn completa, por ejemplo http//wsus:80 en lugar de http://wsus.empresa.org:80



domingo, 20 de enero de 2013

jquery.mvc.net y vb.net

Enunciado

En un proyecto web mvc instalas desde nuget jquery.mvc.net y nada funciona

Solución

Cambia todas las  plantillas, controladores, startapp a vb.net. Estan en c#

Comentario

Si quieres el código posteame un comentario y lo publicaré.  (Voy corto de tiempo)

miércoles, 16 de enero de 2013

Errores al generar código en un modelo EDM en VS2012

Cómo reproducir el problema:

En Visual Studio 2012 crear un modelo de entidades de datos (Entity Data Model o EDM) desde una base de datos existente.

Primer problema:

Aparecen errores relativos a conflictos entre nombres de propiedad que se repiten, una por cada campo.
Solución:
Compila la aplicación y desaparecen. (magia pura al estilo datasets)

Continuamos:

Añades un elemento de generación de código, concretamente: EF 5.x dbContextGenerator.

Síntomas:

Aparecen errores que señalan métodos duplicados: New(), OnModelCreating y propiedades....
Por ejemplo:

'Protected Overrides Sub OnModelCreating(modelBuilder As System.Data.Entity.DbModelBuilder)' tiene varias definiciones con firmas idénticas.

Solución:

En el explorador de soluciones activa mostrar todos los archivos. Ves al EDM que has creado,
Veras archivos tt (templates) que a su vez contienen el código generado.
Tienes los que incorpora por defecto
  • (nombre del EDM).tt
  • (nombre del EDM).context.tt
y los que has añadido.
  • (nombre del elemento a generador de código).tt
  • (nombre del elemento a generador de código).context.tt
Bueno ahora viene lo divertido
En Visual Studio 2012 no hace falta usar EF 5.x dbContextGenerator, ya se utiliza por defecto para generar el código.
En  VS2010 no eran estas las plantillas por defecto y veras  por ahí artículos paso a paso en que te dicen que añadas dbContextGenerator.

Así que borra los elementos que has añadido:
  • (nombre del elemento a generador de código).tt
  • (nombre del elemento a generador de código).context.tt
Los errores desparecerán. Y ya sabes no vuelvas a añadir el elemento de generador de código..

Nota: Podría ocurrir que hayas usado otro tipo de elemento de generación de código, En ese caso deberías borrar las originales del proyecto.

Ver: EF Designer Code Generation Templates  en http://msdn.microsoft.com/en-us/data/jj613116.aspx