miércoles, 4 de marzo de 2015

Sistema de encuestas

La empresa en la que trabajamos dispone de distintos certificados de calidad. Una de las condiciones que se deben cumplir para estar en posesión de estos certificados, consiste en la realización de encuestas de satisfacción al cliente para conocer su opinión sobre los servicios ofrecidos. Para ello, se nos pide integrar esta posibilidad en el software de gestión.

La forma en que desea que funcionen las encuestas es la siguiente: periódicamente, cada viernes, el servidor creará de forma automática una serie de alertas para encuestar a clientes, basándose en ciertas condiciones de negocio (básicamente servicios entregados al cliente en los últimos X días). En este caso, no desean que el sistema seleccione aleatoriamente las encuestas, sino que desean que se notifique a un administrador de forma que sea él quien asigne las encuestas a la persona encargada de hacerlas, que será a su vez notificada mediante otra alerta.



Posteriormente, generaremos también de forma automática y periódica informes con las respuestas dadas por los clientes así como datos estadísticos sobre las mismas entre las fechas deseadas.
La solución ha sido la siguiente. Hemos creado una serie de tablas para la gestión de encuestas, que se detallan a continuación:

Encuestras.Preguntas
En esta tabla almacenaremos las preguntas a realizar para cada tipo de encuesta. Su estructura es la siguiente:

id / nombre / descripción / area / p1 / v1 / t1 / p2 / v2 / t2 / ..... / p20 / v20 / t20 / notas / recomedaciones

Donde p1 = pregunta1, v1 = posibles valores, separados por punto y coma, de la respuesta 1, t1 = peso de la pregunta en la encuesta

Encuestas.Rechazos
Esta tabla almacena las encuestas que se han descartado, de forma que no aparezcan más como posibles para encuestarse. Se compone de:

id / id_encuesta / id_asignacion / tabla_referencia / codigo_referencia / cod_usuario / fecha / tipo / cod_motivo / motivo_rechazo

Encuestas.MotivosRechazo
Es simplemente una tabla auxiliar para cuando se rechaza una encuesta, se indique el motivo. Los motivos pueden ser distintos según el tipo de encuesta, por lo que su estructura es esta:

cod_motivo / descripcion / id_encuesta

Encuestas.Respuestas
Almacena las respuestas de los clientes, y está formada por:

id / id_encuesta / cod_usuario / fecha / id_asignacion / tabla_referencia / codigo_referencia / nombre_encuestado / r1 / r2 / .... / r20 / notas / recomendaciones / fecha_auditoria

Encuestas.Asignacion
Por último tenemos esta tabla, que es la que asocia una encuesta a una persona para que la realice, y se compone de:

id / tipo / tabla_referencia / codigo_referencia / fecha_decision / cod_decisor / encuestar / cod_encuestador

Con esta estructura de tablas, es posible montar el sistema. El código del mismo será publicado en otra entrada.


El código asociado se encuentra aquí.

viernes, 27 de febrero de 2015

Tips de Crystal Reports

Quitar decimales a un numero en una formula
ToText(valor,0)
valor es el número a convertir
0 son los decimales
Se puede incluir un tercer parámetro para indicar el formato de separadores (''=ninguno)

Utilizar parámetros en un comando de un subinforme
Crear el comando en el subinforme. A la derecha crear el parámetro. Para usarlo, incluirlo en la sentencia SQL como {?NombreParametro}.
Luego, desde el informe principal, vincular el campo deseado al parámetro en cuestión del subinforme.

Utilizar un valor calculado en un subinforme en el informe principal
Supongamos que tenemos un valor que se calcula o se obtiene en un subinforme y deseamos utilizarlo (para mostrarlo o utilizarlo) en el informe principal.

Esto se soluciona utilizando variables compartidas, usadas desde la versión 7 de Crystal Report, y la solución consiste en lo siguiente:

Se debe tener en cuenta que el reporte primero evalúa la fórmula en donde se llena el valor de la variable compartida, y luego realiza la fórmula en donde se muestran los datos, independientemente de dónde se encuentre la fórmula y la variable, ya sea en el principal o en el subinforme.

1. En el subinforme, crear una formula parecida a esta:
//@SubFormula
//Guardar el total del campo {Orders.Order Amount}
//en una variable compartida llamada 'miTotal'
Shared CurrencyVar miTotal := Sum ({Orders.Order Amount})
Poner esta fórmula en el subinforme.
En el informe principal, crear una fórmula que declara el nombre de la misma variable:
//@FormulaPrincipal
//Retorna el valor que fue guardado en la variable compartida llamada
//miTotal del subinforme
Shared CurrencyVar miTotal;
miTotal
Poner la @FormulaPrincipal en el informe principal debajo de la sección que contiene el subinforme. Esto sirve para asegurar que el Crystal evalue antes la @SubFormula antes de la @FormulaPrincipal.

Una vez que tenemos el valor que necesitamos en la @FormulaPrincipal podemos hacer uso de ella como más nos convenga, por ejemplo, ocultar una sección dependiendo del monto que devuelve la @SubFormula.

jueves, 12 de febrero de 2015

Cargar el resultado de un fichero PHP dentro de un DIV con jQuery

Esta entrada muestra como podemos, dentro de una etiqueta DIV de nuestra página, colocar el resultado obtenido al hacer una llamada a una página php. Para poder hacer esto, lo primero que necesitamos es definir el DIV donde vamos a introducir el contenido:
<div class="contentWrapper" id="contenido"></div>
A cotinuación, con jQuery, en el lugar donde nos interese, haremos lo siguiente:
$("#contenido").load('url_al_archivo.php');
Paso de parámetros GET
Si queremos además pasar algún parametro por GET, se puede hacer de la siguiente forma:
$("#contenido").load('url_al_archivo.php?var1=x&var2=y&var3=z');
Paso de parámetros POST
En el caso de que los parámetros sean por POST, utilizaremos el siguiente formato.
$("#contenido").load('url_al_archivo.php', {var1:x, var2:y, var3:z});
Es todo, así de sencillo.

miércoles, 11 de febrero de 2015

Uso de fuentes externas sólo en fichero CSS

Si se desean usar fuentes externas, como por ejemplo Google Fonts, normalmente nos indicarán que para hacerlo necesitamos hacer un link href en la cabecera de nuestra página, y luego en nuestro fichero CSS ya podremos hacer referencia a la fuente. He aqui un ejemplo:
<link href="http://fonts.googleapis.com/css?family=Cuprum" type="text/css" rel="stylesheet" />
body {
  font-family: 'Cuprum', serif;
  font-size: 48px;
}
En ocasiones no nos interesa hacerlo así, ya que en cada página tendremos que incluir la referencia a la fuente, y si queremos cambiarla tendremos que ir página a página. O puede que estemos editando un fichero CSS de WordPress y queramos incluir una fuente sin tener acceso a las páginas HTML o PHP. En estos casos, podemos importar la fuente directamente desde dentro del fichero CSS. Hay varias formas de hacerlo, y aquí vamos a ver dos de ellas:

Forma 1
@font-face{
  font-family:'Droid Serif';
  font-style:normal;
  font-weight:400;
  src:local('Droid Serif'),local('DroidSerif'),url(http://fonts.gstatic.com/s/droidserif/v6/0AKsP294HTD-nvJgucYTaIbN6UDyHWBl620a-IRfuBk.woff) format('woff');
 }
body {
  font-family: 'Droid Serif';
  font-size: 14px;
}
Forma 2
@import url(http://fonts.googleapis.com/css?family=Open+Sans);
body {
  font-family: 'Open Sans';
  font-size: 14px;
}
Facil, ¿verdad?

viernes, 6 de febrero de 2015

Crear un WebService con PHP

Si trabajamos con PHP, una tarea con la casi seguro que nos encontraremos habitualmente es con la de construir un servicio web que exponga una serie de datos. Esto es muy sencillo de hacer en PHP, y vamos a ver como se hace.

Antecedentes
Supongamos que vamos a realizar un webservice que se conecte a una base de datos MySQL para lanzarle consultas y devolver los resultados. Esto será válido para la mayoría de casos con que nos encontramos, independientemente de que después debamos ampliar el servicio con otros métodos (como por ejemplo incluir seguridad, encriptación, etc).

Lo primero que se nos puede ocurrir, es crear una serie de métodos en el WebService de forma que cada uno reciba sus parámetros, monte las consultas, y las lance contra la base de datos para terminar devolviendo los resultados. Esto es correcto, pero vamos a crear una técnica más potente.

En vez de hacer un método por cada consulta a lanzar, vamos a tener un sólo método. Este, recibirá un nombre de opción a ejecutar, y según esa opción, lanzará una u otra consulta. Es más, no tendremos que tener las consultas dentro del propio WebService, sino que nos basaremos para configurarlas en un fichero XML. Esto nos va a permitir disponer de un código muy limpio y claro, con muy poco mantenimiento posterior, y configuraremos las posibles opciones y sus consultas relacionadas en un fichero XML, de forma que para crear, modificar, o eliminar nuevos métodos, no modificaremos el servicio web, sino únicamente el XML. Veamos cómo.

Fichero de configuración
Vamos a crear un fichero XML de configuración que contendrá los métodos de que dispondrá el servicio web. Posteriormente, dicho servicio recibirá el método a ejecutar, su lista de parámetros, y devolverá los resultados.

El fichero podría ser el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<OpcionesDisponibles>
  <opcion>
    <nombre>GetPaises</nombre>
    <SQL>SELECT cod_pais,pais FROM paises order by pais</SQL>
  </opcion>
  <opcion>
    <nombre>GetPais</nombre>
    <SQL>SELECT cod_pais,pais FROM paises WHERE cod_pais='#PARAMETRO_1#'</SQL>
  </opcion>
</OpcionesDisponibles>
Como se puede ver, es extremadamente simple, y nos permitirá ampliar, modificar, o reducir el servicio web sin tener que modificarlo.

El WebService
Una vez que sabemos lo que queremos, y que tenemos nuestro fichero de configuración de métodos definido, sólo nos queda crear el servicio web.

Nuestro servicio recibirá tres parámetros: la opción a ejecutar, la lista de parámetros relativas a esa función, y el formato de resultado que deseamos. Así pues, manos a la obra:
<?php
  // Recoger parámetros de entrada. Se permite GET y POST
  $paramOpcion = $_REQUEST['opcion'];
  $paramLista = $_REQUEST['listaParametros'];
  $formato = strtolower($_REQUEST['format']) == 'xml' ? 'xml' : 'json';

  // Obtener consulta SQL a ejecutar
  $sql = getSQL($paramOpcion);
  $sql = aplicarParametrosSQL($sql,$paramLista);

  // Lanzar consulta a la base de datos
  if ($sql == '')
    die ('Opcion incorrecta');
  else
  {    
    $datos = getArrayDatos($sql);
    echo formatData($datos,$formato); 
  }


  // Dado un nombre de opción, buscar en el xml su SQL correspondiente
  function getSQL($nombreOpcion)
  {
    $xmlOpciones = simplexml_load_file('opciones.xml');
    $sql = ""; 
    foreach ($xmlOpciones->opcion as $opcion)
    {
      $nombre = $opcion->nombre;
      if ($nombre == $nombreOpcion)
      {
        $sql = $opcion->SQL;
        break;
      }
    }
    return $sql;
  }

  // Aplicar parámetros de la sentencia SQL
  function aplicarParametrosSQL($sql, $listaPametros)
  {    
    if ($listaPametros != '')
    {
      $arrParam = explode('#',$listaPametros);
      for ($i=0; $i<count($arrParam); $i++)
      {
        $nomParam = '#PARAMETRO_'.($i+1).'#';
        $valor = $arrParam[$i];
        $sql = str_replace($nomParam, $valor, $sql);
      }
    }
    return $sql;
  }

  function getArrayDatos($sql)
  {
    $db = mysql_connect('servidor_base_datos','usuario','clave') or die('No se pudo conectar con la base de datos');
    mysql_select_db('base_de_datos',$db) or die('No se pudo seleccionar la base de datos.');
    $resul = mysql_query($sql,$db) or die('Consulta incorrecta:  '.$sql);

    /* create one master array of the records */
    $datos = array();
    if(mysql_num_rows($resul)) 
    {
     while($row = mysql_fetch_assoc($resul)) 
     {
      $datos[] = $row;
     }
    }

    /* disconnect from the db */
    @mysql_close($db);    

    /* Return data array */
    return $datos;        
  }

  function formatData($data,$format='json')
 {
    if($format == 'json' || $format == 'jsonp') 
    {
      header('Content-type: application/json');
      if (isset($_REQUEST['callback']))   // Peticiones ajax
        return $_REQUEST['callback'].'('.json_encode($data).')';
      else
        return json_encode($data);
    }
    else
    {
      header('Content-type: text/xml');
      $xml = new SimpleXMLElement('<response/>');
      return array2xml($data, $xml);
    }
  }

  function array2xml($array, $xml = false)
  {
    if($xml === false)
    {
      $xml = new SimpleXMLElement('<root/>');
    }
    foreach($array as $key => $value)
    {
      if(is_array($value))
      {
        //array2xml($value, $xml->addChild($key));
        array2xml($value, $xml->addChild('item'));
      }
      else
      {
        $xml->addChild($key, htmlspecialchars($value));
      }
    }
    return $xml->asXML();
  }

?>
Y eso es todo. Potente, ¿verdad?. Ahora sólo debemos modificar el fichero XML a nuestro gusto para adaptarlo a nuestras necesidades (ojo, recordar introducir algún sistema de seguridad, y a ser posible, excluir el fichero XML en el fichero robots.txt de nuestro servidor para que no sea indexado).

En otra entrada veremos como consumir un webservice.

Crear un WebService con .NET

Si trabajamos con .NET, una tarea con la casi seguro que nos encontraremos habitualmente es con la de construir un servicio web que exponga una serie de datos. Esto es muy sencillo de hacer en .NET, y vamos a ver como se hace.

Antecedentes
Supongamos que vamos a realizar un webservice que se conecte a una base de datos SQL Server para lanzarle consultas y devolver los resultados. Esto será válido para la mayoría de casos con que nos encontramos, independientemente de que después debamos ampliar el servicio con otros métodos (como por ejemplo incluir seguridad, encriptación, etc).

Lo primero que se nos puede ocurrir, es crear una serie de métodos en el WebService de forma que cada uno reciba sus parámetros, monte las consultas, y las lance contra la base de datos para terminar devolviendo los resultados. Esto es correcto, pero vamos a crear una técnica más potente.

En vez de hacer un método por cada consulta a lanzar, vamos a tener un sólo método. Este, recibirá un nombre de opción a ejecutar, y según esa opción, lanzará una u otra consulta. Es más, no tendremos que tener las consultas dentro del propio WebService, sino que nos basaremos para configurarlas en un fichero XML. Esto nos va a permitir disponer de un código muy limpio y claro, con muy poco mantenimiento posterior, y configuraremos las posibles opciones y sus consultas relacionadas en un fichero XML, de forma que para crear, modificar, o eliminar nuevos métodos, no modificaremos el servicio web, sino únicamente el XML. Veamos cómo.

Fichero de configuración
Vamos a crear un fichero XML de configuración que contendrá los métodos de que dispondrá el servicio web. Posteriormente, dicho servicio recibirá el método a ejecutar, su lista de parámetros, y devolverá los resultados.

El fichero podría ser el siguiente:
<?xml version="1.0" encoding="utf-8" ?>
<OpcionesDisponibles>
  <opcion>
    <nombre>GetPaises</nombre>
    <SQL>SELECT cod_pais,pais FROM paises order by pais</SQL>
  </opcion>
  <opcion>
    <nombre>GetPais</nombre>
    <SQL>SELECT cod_pais,pais FROM paises WHERE cod_pais='#PARAMETRO_1#'</SQL>
  </opcion>
</OpcionesDisponibles>
Como se puede ver, es extremadamente simple, y nos permitirá ampliar, modificar, o reducir el servicio web sin tener que modificarlo.

El WebService: La clase mágica
Una vez que sabemos lo que queremos, y que tenemos nuestro fichero de configuración de métodos definido, sólo nos queda crear el servicio web.

Nuestro servicio recibirá tres parámetros: la opción a ejecutar, la lista de parámetros relativas a esa función, y el formato de resultado que deseamos. Así pues, manos a la obra:
using System;
using System.Data;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;

public class WS_Clase
{
  static string ficheroOpciones = HttpContext.Current.Server.MapPath("~/") + "\\opciones.xml";

  /// <summary>
  /// Ejecutar y devolver un XML resultado de ejecutar una sentencia SQL configurada en un fichero XML.
  /// </summary>
  /// <param name="nombre"></param>
  /// <param name="listaParametros"></param>
  /// <returns></returns>
  public static XmlDocument GetOpcion(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      dt = AccesoDatos.GetDataTable(SQL);

      System.Xml.XmlDocument x = DataTableToXmlDocument(dt);
      dt.Dispose();
      ds.Dispose();
      return x;

    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  public static int SetOpcion(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      int resul = AccesoDatos.ExecuteSQL(SQL);

      // Devolver resultado
      dt.Dispose();
      ds.Dispose();
      return resul;
    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  /// <summary>
  /// GetOpcion en formato JSON
  /// </summary>
  /// <param name="nombre"></param>
  /// <param name="listaParametros"></param>
  /// <returns></returns>
  public static String GetOpcionJSON(string nombre, string listaParametros)
  {
    try
    {
      // Buscar opcion en el fichero de parametros para obtener la SQL a ejecutar
      DataSet ds = new DataSet();
      ds.ReadXml(ficheroOpciones);
      DataTable dt = new DataTable();
      dt = ds.Tables[0];
      string SQL = string.Empty;

      for (int i = 0; i < dt.Rows.Count; i++)
      {
        if (Convert.ToString(dt.Rows[i]["nombre"]).ToUpper() == nombre.ToUpper())
        {
          SQL = Convert.ToString(dt.Rows[i]["SQL"]);
          break;
        }
      }
      if (SQL == string.Empty)
        throw new Exception("Opción no disponible: " + nombre);

      // Asignar parametros
      string[] parametros;
      parametros = listaParametros.Split('|');
      for (int i = 0; i < parametros.Length; i++)
      {
        SQL = SQL.Replace("#PARAMETRO_" + Convert.ToString(i + 1) + "#", parametros[i]);
      }

      // Ejecutar SQL
      dt = AccesoDatos.GetDataTable(SQL);

      string x = DataTableToJSONDocument(dt);
      dt.Dispose();
      ds.Dispose();
      return x;

    }
    catch (Exception ex)
    {
      throw new Exception(ex.Message);
    }
  }

  #region " Métodos privados "

  /// <summary>
  /// Configurar DataTable con nomenclatura estandar para hacer mas fácil la lectura desde aplicaciones cliente (siempre mismos campos).
  /// </summary>
  /// <param name="dt"></param>
  /// <returns></returns>
  private static DataTable DataTableToStandard(DataTable dt)
  {
    try
    {
      DataTable dtResultado = new DataTable(dt.TableName);
      dtResultado.Columns.Add("campo", typeof(System.String));
      dtResultado.Columns.Add("valor", typeof(System.String));

      DataRow dr;
      for (int i = 0; i < dt.Rows.Count; i++)
      {
        dr = dtResultado.NewRow();
        dr["campo"] = dt.Rows[i][0];
        dr["valor"] = dt.Rows[i][1];
        dtResultado.Rows.Add(dr);
      }

      return dtResultado;
    }
    catch (Exception ex)
    {
      throw ex;
    }
  }

  private static String DataTableToJSONDocument(DataTable dt)
  {

    string[] StrDc = new string[dt.Columns.Count];
    string HeadStr = string.Empty;
    for (int i = 0; i < dt.Columns.Count; i++)
    {
      StrDc[i] = dt.Columns[i].Caption;
      HeadStr += "\"" + StrDc[i] + "\":\"" + StrDc[i] + i.ToString() + "¾" + "\",";
    }

    HeadStr = HeadStr.Substring(0, HeadStr.Length - 1);

    StringBuilder Sb = new StringBuilder();

    Sb.Append("[");

    for (int i = 0; i < dt.Rows.Count; i++)
    {
      string TempStr = HeadStr;
      for (int j = 0; j < dt.Columns.Count; j++)
      {
        TempStr = TempStr.Replace(dt.Columns[j] + j.ToString() + "¾", dt.Rows[i][j].ToString().Trim());
      }
      //Sb.AppendFormat("{{{0}}},",TempStr);

      Sb.Append("{" + TempStr + "},");
    }

    Sb = new StringBuilder(Sb.ToString().Substring(0, Sb.ToString().Length - 1));

    if (Sb.ToString().Length > 0)
      Sb.Append("]");

    return StripControlChars(Sb.ToString());

  }

  public static string StripControlChars(string s)
  {
    return Regex.Replace(s, @"[^\x20-\x7F]", "");
  }

  /// <summary>
  /// Pasar DataTable a XML estandarizado para su devolución por el WebService.
  /// </summary>
  /// <param name="dt"></param>
  /// <returns></returns>
  private static XmlDocument DataTableToXmlDocument(DataTable dt)
  {
    XmlDocument xml = new XmlDocument();
    System.Text.StringBuilder sb = new System.Text.StringBuilder();

    sb.Append("<?xml version='1.0' encoding='utf-8' ?>");
    sb.Append("<DocumentElement>");

    foreach (DataRow dr in dt.Rows)
    {
      sb.Append("<Dato>");

      foreach (DataColumn dc in dt.Columns)
      {
        sb.Append("<" + dc.ColumnName + ">" + dr[dc].ToString() + "</" + dc.ColumnName + ">");
      }

      //foreach (DataColumn dc in dt.Columns)
      //  sb.Append("<" + dc.ColumnName + ">" + dr[dc].ToString() + "</" + dc.ColumnName + ">");

      sb.Append("</Dato>");
    }

    sb.Append("</DocumentElement>");
    xml.LoadXml(sb.ToString());

    return xml;
  }

  #endregion

}

El WebService: La interfaz
Una vez hemos definido la clase que hace lo que deseamos, simplemente exponemos el webservice:
using WS_Clase; // Nuestra clase creada antes
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Xml;

namespace eCliente_WebService
{
  /// <summary>
  /// Servicio web para el acceso a datos de la web de eCliente
  /// </summary>
  [WebService(Namespace = "http://nuestra_url")]
  [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
  [System.ComponentModel.ToolboxItem(false)]
  // Para permitir que se llame a este servicio web desde un script, usando ASP.NET AJAX, quite la marca de comentario de la línea siguiente. 
  // [System.Web.Script.Services.ScriptService]
  public class eCliente_WebService : System.Web.Services.WebService
  {
    [WebMethod]
    public string Test()
    {
      return "Hola desde el WebService de eCliente.";
    }

    [WebMethod]
    public XmlDocument GetOpcion(string nombre, string listaParametros)
    {
      return WS_Clases.GetOpcion(nombre, listaParametros);
    }

    [WebMethod]
    public int SetOpcion(string nombre, string listaParametros)
    {
      return WS_Clases.SetOpcion(nombre, listaParametros);
    }

    [WebMethod]
    [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
    public void GetOpcionJSON(string nombre, string listaParametros)
    {
      HttpContext.Current.Response.Write(WS_Clases.GetOpcionJSON(nombre, listaParametros));
    }


    [WebMethod]
    [ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
    public void GetOpcionJSONAjax(string nombre, string listaParametros, string callback = "")
    {
      System.Text.StringBuilder sb = new System.Text.StringBuilder();
      if (callback != "")
      {
        sb.Append(callback + "(");
        sb.Append(WS_Clases.GetOpcionJSON(nombre, listaParametros));
        sb.Append(");");
      }
      else
        sb.Append(WS_Clases.GetOpcionJSON(nombre, listaParametros));

      Context.Response.Clear();
      Context.Response.ContentType = "application/json";
      Context.Response.Write(sb.ToString());
      Context.Response.End();
    }

  }
}

Y eso es todo. Potente, ¿verdad?. Ahora sólo debemos modificar el fichero XML a nuestro gusto para adaptarlo a nuestras necesidades (ojo, recordar introducir algún sistema de seguridad, y a ser posible, excluir el fichero XML en el fichero robots.txt de nuestro servidor para que no sea indexado).

En otra entrada veremos como consumir un webservice.

Enlazando a Crystal Reports con .NET

En esta entrada vamos a crear una clase genérica que nos permita interactuar con informes realizados en Crystal Reports. La clase se logará en la base de datos, ejecutará los informes y nos los devolverá en el formato que nos interese. Si examinamos el código de la clase, realizado en C#, no son necesarias muchas explicaciones. Veámoslo pues:
// *************************************************************************
// CLASE PARA CREAR INFORMES UTILIZANDO CRYSTAL REPORTS (BUSINESS OBJECT)
//
// Referencias a importar (C:\Archivos de Programa\Business Objects\Common\3.5\managed\dotnet2)
//    CrystalDecisions.CrystalRports.Engine
//    CrystalDecisions.Shared
//
// Manuel Toscano Blancas
// *************************************************************************

using CrystalDecisions.CrystalReports.Engine;
using CrystalDecisions.Shared;
using System;
using System.Collections;

namespace GestorInformes
{
  class InformesCrystalReports
  {

    #region " Variables miembro "

    // Informacion de conexion a la BD
    private static string _bbdd_servidor;
    private static string _bbdd_nombre;
    private static string _bbdd_usuario;
    private static string _bbdd_clave;
    private static bool _bbdd_seguridadIntegrada;

    #endregion

    #region " Métodos públicos "

    /// <summary>
    /// Establecer las propiedades de conexión a la base de datos para los informes.
    /// </summary>
    /// <param name="servidor"></param>
    /// <param name="nombreBBDD"></param>
    /// <param name="usuario"></param>
    /// <param name="clave"></param>
    /// <param name="seguridadIntegrada"></param>
    public static void SetInfoConexion(string servidor, string nombreBBDD, string usuario, string clave, bool seguridadIntegrada)
    {
      _bbdd_servidor = servidor;
      _bbdd_nombre = nombreBBDD;
      _bbdd_usuario = usuario;
      _bbdd_clave = clave;
      _bbdd_seguridadIntegrada = seguridadIntegrada;
    }

    /// <summary>
    /// Generar informe y devolver resultado en forma de fichero en PDF.
    /// </summary>
    /// <param name="ficheroRPT"></param>
    /// <param name="hashTableFormulas"></param>
    /// <param name="ficheroPDF"></param>
    public static void ReportToPDF(string ficheroRPT, Hashtable hashTableFormulas, string ficheroPDF)
    {
      try
      {
        ReportDocument rpt;
        rpt = GetResultadoRPT(ficheroRPT, hashTableFormulas);
        rpt.ExportToDisk(ExportFormatType.PortableDocFormat, ficheroPDF);
        rpt.Close();
        rpt.Dispose();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    /// <summary>
    /// Generar informe y devolver resultado por impresora.
    /// </summary>
    /// <param name="ficheroRPT"></param>
    /// <param name="hashTableFormulas"></param>
    /// <param name="nombreImpresora"></param>
    /// <param name="numeroCopias"></param>
    /// <param name="dobleCara"></param>
    public static void ReportToPrinter(string ficheroRPT, Hashtable hashTableFormulas, string nombreImpresora = "", int numeroCopias = 1, bool dobleCara = false)
    {
      try
      {
        ReportDocument rpt;
        rpt = GetResultadoRPT(ficheroRPT, hashTableFormulas);
        // Se podrian configurar otras opciones de impresion con rpt.PrintOptions 
        rpt.PrintOptions.PrinterName = nombreImpresora;
        rpt.PrintOptions.PaperOrientation = CrystalDecisions.Shared.PaperOrientation.DefaultPaperOrientation;
        rpt.PrintOptions.PaperSize = CrystalDecisions.Shared.PaperSize.DefaultPaperSize;
        rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Default;
        rpt.PrintOptions.PaperSource = CrystalDecisions.Shared.PaperSource.Auto;
        if (dobleCara)
        {
          if (rpt.PrintOptions.PaperOrientation == PaperOrientation.Portrait)
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Vertical;
          else if (rpt.PrintOptions.PaperOrientation == PaperOrientation.Landscape)
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Horizontal;
          else
            rpt.PrintOptions.PrinterDuplex = CrystalDecisions.Shared.PrinterDuplex.Default;
        }
        // Imprimir
        rpt.PrintToPrinter(numeroCopias, true, 0, 0);
        rpt.Close();
        rpt.Dispose();
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    #endregion

    #region " Métodos privados para la generación del informe "

    private static ReportDocument GetResultadoRPT(string ficheroRPT, Hashtable hashTableFormulas = null)
    {
      try
      {
        // Validaciones
        if (!System.IO.File.Exists(ficheroRPT)) throw new ApplicationException("No se encontró el fichero de reporte " + ficheroRPT);

        // Informe
        CrystalDecisions.CrystalReports.Engine.ReportDocument rpt = new CrystalDecisions.CrystalReports.Engine.ReportDocument();
        rpt.Load(ficheroRPT);
        // Pasar fórmulas
        if (hashTableFormulas != null && hashTableFormulas.Count > 0)
        {
          foreach (string key in hashTableFormulas.Keys)
            SetFormula(rpt, key, hashTableFormulas[key].ToString());
        }
        // Logarse en todas las tablas
        LoginEnTablas(rpt);

        return rpt;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }
    }

    private static void SetFormula(ReportDocument rpt, string nombre, string valor)
    {
      try
      {
        rpt.DataDefinition.FormulaFields[nombre].Text = "\"" + valor.ToString() + "\"";
      }
      catch (Exception ex)
      {
        throw new Exception("Fórmula no válida. " + ex.Message);
      }
    }

    private static void LoginEnTablas(ReportDocument rpt)
    {
      try
      {
        CrystalDecisions.Shared.TableLogOnInfo tableLogonInfo = new CrystalDecisions.Shared.TableLogOnInfo();
        CrystalDecisions.Shared.ConnectionInfo connectionInfo = new CrystalDecisions.Shared.ConnectionInfo();

        connectionInfo.ServerName = _bbdd_servidor;
        connectionInfo.DatabaseName = _bbdd_nombre;
        connectionInfo.UserID = _bbdd_usuario;
        connectionInfo.Password = _bbdd_clave;
        connectionInfo.IntegratedSecurity = _bbdd_seguridadIntegrada;

        // Tablas del informe
        foreach (CrystalDecisions.CrystalReports.Engine.Table table in rpt.Database.Tables)
        {
          if (table.LogOnInfo.ConnectionInfo.DatabaseName == connectionInfo.DatabaseName)    // Esto permite cambiar el login solo en tablas de la misma BBDD, no interfiere si son otras como por ejemplo XML
          {
            tableLogonInfo = table.LogOnInfo;
            tableLogonInfo.ConnectionInfo = connectionInfo;
            table.ApplyLogOnInfo(tableLogonInfo);
            if (table.Location.Contains("."))
              table.Location = _bbdd_nombre + ".dbo.[" + table.Location + "]";
          }
        }

        // Tablas de los SubInformes
        for (int i = 0; i < rpt.Subreports.Count; i++)
        {
          foreach (CrystalDecisions.CrystalReports.Engine.Table table in rpt.Subreports[i].Database.Tables)
          {
            if (table.LogOnInfo.ConnectionInfo.DatabaseName == connectionInfo.DatabaseName)    // Esto permite cambiar el login solo en tablas de la misma BBDD, no interfiere si son otras como por ejemplo XML
            {
              tableLogonInfo = table.LogOnInfo;
              tableLogonInfo.ConnectionInfo = connectionInfo;
              table.ApplyLogOnInfo(tableLogonInfo);
              if (table.Location.Contains("."))
                table.Location = _bbdd_nombre + ".dbo.[" + table.Location + "]";
            }
          }
        }

        tableLogonInfo = null;
        connectionInfo = null;
      }
      catch (Exception ex)
      {
        throw new Exception(ex.InnerException.ToString());
      }

    }

    #endregion

  }
}
La utilización de la clase genérica anterior, podría resumirse en el código siguiente, que sería el que utilizaríamos en nuestra aplicación:
System.Collections.Hashtable htFormulas = new System.Collections.Hashtable();
htFormulas.Clear();
htFormulas.Add("Codigo",dt.Rows[i]["ParametrosGeneracion"].ToString());
InformesCrystalReports.ReportToPDF(dt.Rows[i]["FicheroOrigen"].ToString(), htFormulas, ficheroDestinoPDF);
Eso crea un PDF con el resultado de ejecutar el informe, pero como hemos incluido otros métodos, podríamos haber hecho que directamente saliera por impresora, o bien recoger el objeto resultante para mostrarlo por pantalla en un contenedor de report.

 

Copyright @ 2015 Tosblama